+
diff --git a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx
index 570cbb9..bf16a56 100644
--- a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx
+++ b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/NetworkOptions.tsx
@@ -1,5 +1,5 @@
import { useTheme } from "next-themes";
-import { useNetwork, useSwitchNetwork } from "wagmi";
+import { useAccount, useSwitchChain } from "wagmi";
import { ArrowsRightLeftIcon } from "@heroicons/react/24/solid";
import { getNetworkColor } from "~~/hooks/scaffold-eth";
import { getTargetNetworks } from "~~/utils/scaffold-eth";
@@ -11,8 +11,8 @@ type NetworkOptionsProps = {
};
export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => {
- const { switchNetwork } = useSwitchNetwork();
- const { chain } = useNetwork();
+ const { switchChain } = useSwitchChain();
+ const { chain } = useAccount();
const { resolvedTheme } = useTheme();
const isDarkMode = resolvedTheme === "dark";
@@ -26,7 +26,7 @@ export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => {
className="menu-item btn-sm !rounded-xl flex gap-3 py-3 whitespace-nowrap"
type="button"
onClick={() => {
- switchNetwork?.(allowedNetwork.id);
+ switchChain?.({ chainId: allowedNetwork.id });
}}
>
diff --git a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx
index d0d6040..6521200 100644
--- a/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx
+++ b/packages/nextjs/components/scaffold-eth/RainbowKitCustomConnectButton/index.tsx
@@ -7,7 +7,7 @@ import { AddressQRCodeModal } from "./AddressQRCodeModal";
import { WrongNetworkDropdown } from "./WrongNetworkDropdown";
import { ConnectButton } from "@rainbow-me/rainbowkit";
import { Address } from "viem";
-import { useAutoConnect, useNetworkColor } from "~~/hooks/scaffold-eth";
+import { useNetworkColor } from "~~/hooks/scaffold-eth";
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth";
@@ -15,7 +15,6 @@ import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth";
* Custom Wagmi Connect Button (watch balance + custom design)
*/
export const RainbowKitCustomConnectButton = () => {
- useAutoConnect();
const networkColor = useNetworkColor();
const { targetNetwork } = useTargetNetwork();
diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts
index 9160ed9..07d1975 100644
--- a/packages/nextjs/contracts/deployedContracts.ts
+++ b/packages/nextjs/contracts/deployedContracts.ts
@@ -6,145 +6,126 @@ import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract";
const deployedContracts = {
31337: {
- YourContract: {
- address: "0x5FbDB2315678afecb367f032d93F642f64180aa3",
+ OracleReader: {
+ address: "0x5fbdb2315678afecb367f032d93f642f64180aa3",
abi: [
{
- inputs: [
- {
- internalType: "address",
- name: "_owner",
- type: "address",
- },
- ],
- stateMutability: "nonpayable",
type: "constructor",
- },
- {
- anonymous: false,
inputs: [
{
- indexed: true,
- internalType: "address",
- name: "greetingSetter",
+ name: "chronicle_",
type: "address",
+ internalType: "address",
},
{
- indexed: false,
- internalType: "string",
- name: "newGreeting",
- type: "string",
- },
- {
- indexed: false,
- internalType: "bool",
- name: "premium",
- type: "bool",
- },
- {
- indexed: false,
- internalType: "uint256",
- name: "value",
- type: "uint256",
+ name: "selfKisser_",
+ type: "address",
+ internalType: "address",
},
],
- name: "GreetingChange",
- type: "event",
+ stateMutability: "nonpayable",
},
{
+ type: "function",
+ name: "chronicle",
inputs: [],
- name: "greeting",
outputs: [
{
- internalType: "string",
name: "",
- type: "string",
+ type: "address",
+ internalType: "contract IChronicle",
},
],
stateMutability: "view",
- type: "function",
},
{
+ type: "function",
+ name: "read",
inputs: [],
- name: "owner",
outputs: [
{
- internalType: "address",
- name: "",
- type: "address",
+ name: "val",
+ type: "uint256",
+ internalType: "uint256",
},
],
stateMutability: "view",
- type: "function",
},
{
+ type: "function",
+ name: "selfKisser",
inputs: [],
- name: "premium",
outputs: [
{
- internalType: "bool",
name: "",
- type: "bool",
+ type: "address",
+ internalType: "contract ISelfKisser",
},
],
stateMutability: "view",
- type: "function",
},
+ ],
+ inheritedFunctions: {},
+ },
+ },
+ 11155111: {
+ OracleReader: {
+ address: "0xa2b4473120b9d18abfe90eeb2722a57cb72c4212",
+ abi: [
{
+ type: "constructor",
inputs: [
{
- internalType: "string",
- name: "_newGreeting",
- type: "string",
+ name: "chronicle_",
+ type: "address",
+ internalType: "address",
+ },
+ {
+ name: "selfKisser_",
+ type: "address",
+ internalType: "address",
},
],
- name: "setGreeting",
- outputs: [],
- stateMutability: "payable",
- type: "function",
+ stateMutability: "nonpayable",
},
{
+ type: "function",
+ name: "chronicle",
inputs: [],
- name: "totalCounter",
outputs: [
{
- internalType: "uint256",
name: "",
- type: "uint256",
+ type: "address",
+ internalType: "contract IChronicle",
},
],
stateMutability: "view",
- type: "function",
},
{
- inputs: [
- {
- internalType: "address",
- name: "",
- type: "address",
- },
- ],
- name: "userGreetingCounter",
+ type: "function",
+ name: "read",
+ inputs: [],
outputs: [
{
- internalType: "uint256",
- name: "",
+ name: "val",
type: "uint256",
+ internalType: "uint256",
},
],
stateMutability: "view",
- type: "function",
},
{
- inputs: [],
- name: "withdraw",
- outputs: [],
- stateMutability: "nonpayable",
type: "function",
- },
- {
- stateMutability: "payable",
- type: "receive",
+ name: "selfKisser",
+ inputs: [],
+ outputs: [
+ {
+ name: "",
+ type: "address",
+ internalType: "contract ISelfKisser",
+ },
+ ],
+ stateMutability: "view",
},
],
inheritedFunctions: {},
diff --git a/packages/nextjs/hooks/scaffold-eth/index.ts b/packages/nextjs/hooks/scaffold-eth/index.ts
index 10862ff..6effb7e 100644
--- a/packages/nextjs/hooks/scaffold-eth/index.ts
+++ b/packages/nextjs/hooks/scaffold-eth/index.ts
@@ -1,16 +1,16 @@
-export * from "./useAccountBalance";
export * from "./useAnimationConfig";
export * from "./useBurnerWallet";
+export * from "./useContractLogs";
export * from "./useDeployedContractInfo";
-export * from "./useNativeCurrencyPrice";
+export * from "./useFetchBlocks";
+export * from "./useInitializeNativeCurrencyPrice";
export * from "./useNetworkColor";
export * from "./useOutsideClick";
export * from "./useScaffoldContract";
-export * from "./useScaffoldContractRead";
-export * from "./useScaffoldContractWrite";
-export * from "./useScaffoldEventSubscriber";
export * from "./useScaffoldEventHistory";
+export * from "./useScaffoldReadContract";
+export * from "./useScaffoldWatchContractEvent";
+export * from "./useScaffoldWriteContract";
+export * from "./useTargetNetwork";
export * from "./useTransactor";
-export * from "./useFetchBlocks";
-export * from "./useContractLogs";
-export * from "./useAutoConnect";
+export * from "./useWatchBalance";
diff --git a/packages/nextjs/hooks/scaffold-eth/useAccountBalance.ts b/packages/nextjs/hooks/scaffold-eth/useAccountBalance.ts
deleted file mode 100644
index b52fb21..0000000
--- a/packages/nextjs/hooks/scaffold-eth/useAccountBalance.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useCallback, useEffect, useState } from "react";
-import { useTargetNetwork } from "./useTargetNetwork";
-import { Address } from "viem";
-import { useBalance } from "wagmi";
-import { useGlobalState } from "~~/services/store/store";
-
-export function useAccountBalance(address?: Address) {
- const [isEthBalance, setIsEthBalance] = useState(true);
- const [balance, setBalance] = useState
(null);
- const price = useGlobalState(state => state.nativeCurrencyPrice);
- const { targetNetwork } = useTargetNetwork();
-
- const {
- data: fetchedBalanceData,
- isError,
- isLoading,
- } = useBalance({
- address,
- watch: true,
- chainId: targetNetwork.id,
- });
-
- const onToggleBalance = useCallback(() => {
- if (price > 0) {
- setIsEthBalance(!isEthBalance);
- }
- }, [isEthBalance, price]);
-
- useEffect(() => {
- if (fetchedBalanceData?.formatted) {
- setBalance(Number(fetchedBalanceData.formatted));
- }
- }, [fetchedBalanceData, targetNetwork]);
-
- return { balance, price, isError, isLoading, onToggleBalance, isEthBalance };
-}
diff --git a/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts b/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts
deleted file mode 100644
index 549c7fe..0000000
--- a/packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import { useEffectOnce, useLocalStorage, useReadLocalStorage } from "usehooks-ts";
-import { Chain, hardhat } from "viem/chains";
-import { Connector, useAccount, useConnect } from "wagmi";
-import scaffoldConfig from "~~/scaffold.config";
-import { burnerWalletId } from "~~/services/web3/wagmi-burner/BurnerConnector";
-import { getTargetNetworks } from "~~/utils/scaffold-eth";
-
-const SCAFFOLD_WALLET_STORAGE_KEY = "scaffoldEth2.wallet";
-const WAGMI_WALLET_STORAGE_KEY = "wagmi.wallet";
-
-// ID of the SAFE connector instance
-const SAFE_ID = "safe";
-
-/**
- * This function will get the initial wallet connector (if any), the app will connect to
- * @param initialNetwork
- * @param previousWalletId
- * @param connectors
- * @returns
- */
-const getInitialConnector = (
- initialNetwork: Chain,
- previousWalletId: string,
- connectors: Connector[],
-): { connector: Connector | undefined; chainId?: number } | undefined => {
- // Look for the SAFE connector instance and connect to it instantly if loaded in SAFE frame
- const safeConnectorInstance = connectors.find(connector => connector.id === SAFE_ID && connector.ready);
-
- if (safeConnectorInstance) {
- return { connector: safeConnectorInstance };
- }
-
- const allowBurner = scaffoldConfig.onlyLocalBurnerWallet ? initialNetwork.id === hardhat.id : true;
-
- if (!previousWalletId) {
- // The user was not connected to a wallet
- if (allowBurner && scaffoldConfig.walletAutoConnect) {
- const connector = connectors.find(f => f.id === burnerWalletId);
- return { connector, chainId: initialNetwork.id };
- }
- } else {
- // the user was connected to wallet
- if (scaffoldConfig.walletAutoConnect) {
- if (previousWalletId === burnerWalletId && !allowBurner) {
- return;
- }
-
- const connector = connectors.find(f => f.id === previousWalletId);
- return { connector };
- }
- }
-
- return undefined;
-};
-
-/**
- * Automatically connect to a wallet/connector based on config and prior wallet
- */
-export const useAutoConnect = (): void => {
- const wagmiWalletValue = useReadLocalStorage(WAGMI_WALLET_STORAGE_KEY);
- const [walletId, setWalletId] = useLocalStorage(SCAFFOLD_WALLET_STORAGE_KEY, wagmiWalletValue ?? "", {
- initializeWithValue: false,
- });
- const connectState = useConnect();
- useAccount({
- onConnect({ connector }) {
- setWalletId(connector?.id ?? "");
- },
- onDisconnect() {
- window.localStorage.setItem(WAGMI_WALLET_STORAGE_KEY, JSON.stringify(""));
- setWalletId("");
- },
- });
-
- useEffectOnce(() => {
- const initialConnector = getInitialConnector(getTargetNetworks()[0], walletId, connectState.connectors);
-
- if (initialConnector?.connector) {
- connectState.connect({ connector: initialConnector.connector, chainId: initialConnector.chainId });
- }
- });
-};
diff --git a/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts b/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts
index 0538fc3..2b8cf88 100644
--- a/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts
+++ b/packages/nextjs/hooks/scaffold-eth/useBurnerWallet.ts
@@ -1,8 +1,10 @@
import { useCallback, useEffect, useRef, useState } from "react";
+import { useTargetNetwork } from "./useTargetNetwork";
import { useLocalStorage } from "usehooks-ts";
import { Chain, Hex, HttpTransport, PrivateKeyAccount, createWalletClient, http } from "viem";
+import { WalletClient } from "viem";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
-import { WalletClient, usePublicClient } from "wagmi";
+import { usePublicClient } from "wagmi";
const burnerStorageKey = "scaffoldEth2.burnerWallet.sk";
@@ -61,7 +63,8 @@ export const useBurnerWallet = (): BurnerAccount => {
initializeWithValue: false,
});
- const publicClient = usePublicClient();
+ const { targetNetwork } = useTargetNetwork();
+ const publicClient = usePublicClient({ chainId: targetNetwork.id });
const [walletClient, setWalletClient] = useState>();
const [generatedPrivateKey, setGeneratedPrivateKey] = useState("0x");
const [account, setAccount] = useState();
@@ -99,14 +102,14 @@ export const useBurnerWallet = (): BurnerAccount => {
console.log("โ Could not create burner wallet");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [publicClient.chain.id]);
+ }, [publicClient?.chain.id]);
/**
* Load wallet with burnerSk
* connect and set wallet, once we have burnerSk and valid provider
*/
useEffect(() => {
- if (burnerSk && publicClient.chain.id) {
+ if (burnerSk && publicClient?.chain.id) {
let wallet: WalletClient | undefined = undefined;
if (isValidSk(burnerSk)) {
const randomAccount = privateKeyToAccount(burnerSk);
@@ -132,7 +135,7 @@ export const useBurnerWallet = (): BurnerAccount => {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [burnerSk, publicClient.chain.id]);
+ }, [burnerSk, publicClient?.chain.id]);
return {
walletClient,
diff --git a/packages/nextjs/hooks/scaffold-eth/useContractLogs.ts b/packages/nextjs/hooks/scaffold-eth/useContractLogs.ts
index a5cf7d0..27775d5 100644
--- a/packages/nextjs/hooks/scaffold-eth/useContractLogs.ts
+++ b/packages/nextjs/hooks/scaffold-eth/useContractLogs.ts
@@ -1,13 +1,16 @@
import { useEffect, useState } from "react";
+import { useTargetNetwork } from "./useTargetNetwork";
import { Address, Log } from "viem";
import { usePublicClient } from "wagmi";
export const useContractLogs = (address: Address) => {
const [logs, setLogs] = useState([]);
- const client = usePublicClient();
+ const { targetNetwork } = useTargetNetwork();
+ const client = usePublicClient({ chainId: targetNetwork.id });
useEffect(() => {
const fetchLogs = async () => {
+ if (!client) return console.error("Client not found");
try {
const existingLogs = await client.getLogs({
address: address,
@@ -21,8 +24,8 @@ export const useContractLogs = (address: Address) => {
};
fetchLogs();
- return client.watchBlockNumber({
- onBlockNumber: async (blockNumber, prevBlockNumber) => {
+ return client?.watchBlockNumber({
+ onBlockNumber: async (_blockNumber, prevBlockNumber) => {
const newLogs = await client.getLogs({
address: address,
fromBlock: prevBlockNumber,
diff --git a/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts b/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts
index f82dce1..8f649c3 100644
--- a/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts
+++ b/packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts
@@ -17,23 +17,28 @@ export const useDeployedContractInfo = (cont
useEffect(() => {
const checkContractDeployment = async () => {
- if (!deployedContract) {
- setStatus(ContractCodeStatus.NOT_FOUND);
- return;
- }
- const code = await publicClient.getBytecode({
- address: deployedContract.address,
- });
+ try {
+ if (!isMounted() || !publicClient) return;
- if (!isMounted()) {
- return;
- }
- // If contract code is `0x` => no contract deployed on that address
- if (code === "0x") {
+ if (!deployedContract) {
+ setStatus(ContractCodeStatus.NOT_FOUND);
+ return;
+ }
+
+ const code = await publicClient.getBytecode({
+ address: deployedContract.address,
+ });
+
+ // If contract code is `0x` => no contract deployed on that address
+ if (code === "0x") {
+ setStatus(ContractCodeStatus.NOT_FOUND);
+ return;
+ }
+ setStatus(ContractCodeStatus.DEPLOYED);
+ } catch (e) {
+ console.error(e);
setStatus(ContractCodeStatus.NOT_FOUND);
- return;
}
- setStatus(ContractCodeStatus.DEPLOYED);
};
checkContractDeployment();
diff --git a/packages/nextjs/hooks/scaffold-eth/useDisplayUsdMode.ts b/packages/nextjs/hooks/scaffold-eth/useDisplayUsdMode.ts
new file mode 100644
index 0000000..7d44cac
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-eth/useDisplayUsdMode.ts
@@ -0,0 +1,21 @@
+import { useCallback, useEffect, useState } from "react";
+import { useGlobalState } from "~~/services/store/store";
+
+export const useDisplayUsdMode = ({ defaultUsdMode = false }: { defaultUsdMode?: boolean }) => {
+ const nativeCurrencyPrice = useGlobalState(state => state.nativeCurrency.price);
+ const isPriceFetched = nativeCurrencyPrice > 0;
+ const predefinedUsdMode = isPriceFetched ? Boolean(defaultUsdMode) : false;
+ const [displayUsdMode, setDisplayUsdMode] = useState(predefinedUsdMode);
+
+ useEffect(() => {
+ setDisplayUsdMode(predefinedUsdMode);
+ }, [predefinedUsdMode]);
+
+ const toggleDisplayUsdMode = useCallback(() => {
+ if (isPriceFetched) {
+ setDisplayUsdMode(!displayUsdMode);
+ }
+ }, [displayUsdMode, isPriceFetched]);
+
+ return { displayUsdMode, toggleDisplayUsdMode };
+};
diff --git a/packages/nextjs/hooks/scaffold-eth/useInitializeNativeCurrencyPrice.ts b/packages/nextjs/hooks/scaffold-eth/useInitializeNativeCurrencyPrice.ts
new file mode 100644
index 0000000..0cab720
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-eth/useInitializeNativeCurrencyPrice.ts
@@ -0,0 +1,32 @@
+import { useCallback, useEffect } from "react";
+import { useTargetNetwork } from "./useTargetNetwork";
+import { useInterval } from "usehooks-ts";
+import scaffoldConfig from "~~/scaffold.config";
+import { useGlobalState } from "~~/services/store/store";
+import { fetchPriceFromUniswap } from "~~/utils/scaffold-eth";
+
+const enablePolling = false;
+
+/**
+ * Get the price of Native Currency based on Native Token/DAI trading pair from Uniswap SDK
+ */
+export const useInitializeNativeCurrencyPrice = () => {
+ const setNativeCurrencyPrice = useGlobalState(state => state.setNativeCurrencyPrice);
+ const setIsNativeCurrencyFetching = useGlobalState(state => state.setIsNativeCurrencyFetching);
+ const { targetNetwork } = useTargetNetwork();
+
+ const fetchPrice = useCallback(async () => {
+ setIsNativeCurrencyFetching(true);
+ const price = await fetchPriceFromUniswap(targetNetwork);
+ setNativeCurrencyPrice(price);
+ setIsNativeCurrencyFetching(false);
+ }, [setIsNativeCurrencyFetching, setNativeCurrencyPrice, targetNetwork]);
+
+ // Get the price of ETH from Uniswap on mount
+ useEffect(() => {
+ fetchPrice();
+ }, [fetchPrice]);
+
+ // Get the price of ETH from Uniswap at a given interval
+ useInterval(fetchPrice, enablePolling ? scaffoldConfig.pollingInterval : null);
+};
diff --git a/packages/nextjs/hooks/scaffold-eth/useNativeCurrencyPrice.ts b/packages/nextjs/hooks/scaffold-eth/useNativeCurrencyPrice.ts
deleted file mode 100644
index edcb9db..0000000
--- a/packages/nextjs/hooks/scaffold-eth/useNativeCurrencyPrice.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { useEffect, useState } from "react";
-import { useTargetNetwork } from "./useTargetNetwork";
-import { useInterval } from "usehooks-ts";
-import scaffoldConfig from "~~/scaffold.config";
-import { fetchPriceFromUniswap } from "~~/utils/scaffold-eth";
-
-const enablePolling = false;
-
-/**
- * Get the price of Native Currency based on Native Token/DAI trading pair from Uniswap SDK
- */
-export const useNativeCurrencyPrice = () => {
- const { targetNetwork } = useTargetNetwork();
- const [nativeCurrencyPrice, setNativeCurrencyPrice] = useState(0);
-
- // Get the price of ETH from Uniswap on mount
- useEffect(() => {
- (async () => {
- const price = await fetchPriceFromUniswap(targetNetwork);
- setNativeCurrencyPrice(price);
- })();
- }, [targetNetwork]);
-
- // Get the price of ETH from Uniswap at a given interval
- useInterval(
- async () => {
- const price = await fetchPriceFromUniswap(targetNetwork);
- setNativeCurrencyPrice(price);
- },
- enablePolling ? scaffoldConfig.pollingInterval : null,
- );
-
- return nativeCurrencyPrice;
-};
diff --git a/packages/nextjs/hooks/scaffold-eth/useScaffoldContract.ts b/packages/nextjs/hooks/scaffold-eth/useScaffoldContract.ts
index a97d674..1add8cb 100644
--- a/packages/nextjs/hooks/scaffold-eth/useScaffoldContract.ts
+++ b/packages/nextjs/hooks/scaffold-eth/useScaffoldContract.ts
@@ -1,6 +1,7 @@
-import { Account, Address, Chain, Transport, getContract } from "viem";
-import { PublicClient, usePublicClient } from "wagmi";
-import { GetWalletClientResult } from "wagmi/actions";
+import { useTargetNetwork } from "./useTargetNetwork";
+import { Account, Address, Chain, Client, Transport, getContract } from "viem";
+import { usePublicClient } from "wagmi";
+import { GetWalletClientReturnType } from "wagmi/actions";
import { useDeployedContractInfo } from "~~/hooks/scaffold-eth";
import { Contract, ContractName } from "~~/utils/scaffold-eth/contract";
@@ -13,7 +14,7 @@ import { Contract, ContractName } from "~~/utils/scaffold-eth/contract";
*/
export const useScaffoldContract = <
TContractName extends ContractName,
- TWalletClient extends Exclude | undefined,
+ TWalletClient extends Exclude | undefined,
>({
contractName,
walletClient,
@@ -22,23 +23,30 @@ export const useScaffoldContract = <
walletClient?: TWalletClient | null;
}) => {
const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName);
- const publicClient = usePublicClient();
+ const { targetNetwork } = useTargetNetwork();
+ const publicClient = usePublicClient({ chainId: targetNetwork.id });
let contract = undefined;
- if (deployedContractData) {
+ if (deployedContractData && publicClient) {
contract = getContract<
Transport,
Address,
Contract["abi"],
+ TWalletClient extends Exclude
+ ? {
+ public: Client;
+ wallet: TWalletClient;
+ }
+ : { public: Client },
Chain,
- Account,
- PublicClient,
- TWalletClient
+ Account
>({
address: deployedContractData.address,
abi: deployedContractData.abi as Contract["abi"],
- walletClient: walletClient ? walletClient : undefined,
- publicClient,
+ client: {
+ public: publicClient,
+ wallet: walletClient ? walletClient : undefined,
+ } as any,
});
}
diff --git a/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts b/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts
deleted file mode 100644
index cc99834..0000000
--- a/packages/nextjs/hooks/scaffold-eth/useScaffoldContractWrite.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-import { useState } from "react";
-import { useTargetNetwork } from "./useTargetNetwork";
-import { Abi, ExtractAbiFunctionNames } from "abitype";
-import { useContractWrite, useNetwork } from "wagmi";
-import { useDeployedContractInfo, useTransactor } from "~~/hooks/scaffold-eth";
-import { getParsedError, notification } from "~~/utils/scaffold-eth";
-import { ContractAbi, ContractName, UseScaffoldWriteConfig } from "~~/utils/scaffold-eth/contract";
-
-type UpdatedArgs = Parameters>["writeAsync"]>[0];
-
-/**
- * Wrapper around wagmi's useContractWrite hook which automatically loads (by name) the contract ABI and address from
- * the contracts present in deployedContracts.ts & externalContracts.ts corresponding to targetNetworks configured in scaffold.config.ts
- * @param config - The config settings, including extra wagmi configuration
- * @param config.contractName - contract name
- * @param config.functionName - name of the function to be called
- * @param config.args - arguments for the function
- * @param config.value - value in ETH that will be sent with transaction
- * @param config.blockConfirmations - number of block confirmations to wait for (default: 1)
- * @param config.onBlockConfirmation - callback that will be called after blockConfirmations.
- */
-export const useScaffoldContractWrite = <
- TContractName extends ContractName,
- TFunctionName extends ExtractAbiFunctionNames, "nonpayable" | "payable">,
->({
- contractName,
- functionName,
- args,
- value,
- onBlockConfirmation,
- blockConfirmations,
- ...writeConfig
-}: UseScaffoldWriteConfig) => {
- const { data: deployedContractData } = useDeployedContractInfo(contractName);
- const { chain } = useNetwork();
- const writeTx = useTransactor();
- const [isMining, setIsMining] = useState(false);
- const { targetNetwork } = useTargetNetwork();
-
- const wagmiContractWrite = useContractWrite({
- chainId: targetNetwork.id,
- address: deployedContractData?.address,
- abi: deployedContractData?.abi as Abi,
- functionName: functionName as any,
- args: args as unknown[],
- value: value,
- ...writeConfig,
- });
-
- const sendContractWriteTx = async ({
- args: newArgs,
- value: newValue,
- ...otherConfig
- }: {
- args?: UseScaffoldWriteConfig["args"];
- value?: UseScaffoldWriteConfig["value"];
- } & UpdatedArgs = {}) => {
- if (!deployedContractData) {
- notification.error("Target Contract is not deployed, did you forget to run `yarn deploy`?");
- return;
- }
- if (!chain?.id) {
- notification.error("Please connect your wallet");
- return;
- }
- if (chain?.id !== targetNetwork.id) {
- notification.error("You are on the wrong network");
- return;
- }
-
- if (wagmiContractWrite.writeAsync) {
- try {
- setIsMining(true);
- const writeTxResult = await writeTx(
- () =>
- wagmiContractWrite.writeAsync({
- args: newArgs ?? args,
- value: newValue ?? value,
- ...otherConfig,
- }),
- { onBlockConfirmation, blockConfirmations },
- );
-
- return writeTxResult;
- } catch (e: any) {
- const message = getParsedError(e);
- notification.error(message);
- } finally {
- setIsMining(false);
- }
- } else {
- notification.error("Contract writer error. Try again.");
- return;
- }
- };
-
- return {
- ...wagmiContractWrite,
- isMining,
- // Overwrite wagmi's write async
- writeAsync: sendContractWriteTx,
- };
-};
diff --git a/packages/nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts b/packages/nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts
index a8c2b3a..48f8ed6 100644
--- a/packages/nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts
+++ b/packages/nextjs/hooks/scaffold-eth/useScaffoldEventHistory.ts
@@ -1,13 +1,10 @@
-import { useEffect, useMemo, useState } from "react";
+import { useEffect, useState } from "react";
import { useTargetNetwork } from "./useTargetNetwork";
+import { useInfiniteQuery } from "@tanstack/react-query";
import { Abi, AbiEvent, ExtractAbiEventNames } from "abitype";
-import { useInterval } from "usehooks-ts";
-import { Hash } from "viem";
-import * as chains from "viem/chains";
-import { usePublicClient } from "wagmi";
+import { BlockNumber, GetLogsParameters } from "viem";
+import { Config, UsePublicClientReturnType, useBlockNumber, usePublicClient } from "wagmi";
import { useDeployedContractInfo } from "~~/hooks/scaffold-eth";
-import scaffoldConfig from "~~/scaffold.config";
-import { replacer } from "~~/utils/scaffold-eth/common";
import {
ContractAbi,
ContractName,
@@ -15,6 +12,44 @@ import {
UseScaffoldEventHistoryData,
} from "~~/utils/scaffold-eth/contract";
+const getEvents = async (
+ getLogsParams: GetLogsParameters,
+ publicClient?: UsePublicClientReturnType,
+ Options?: {
+ blockData?: boolean;
+ transactionData?: boolean;
+ receiptData?: boolean;
+ },
+) => {
+ const logs = await publicClient?.getLogs({
+ address: getLogsParams.address,
+ fromBlock: getLogsParams.fromBlock,
+ args: getLogsParams.args,
+ event: getLogsParams.event,
+ });
+ if (!logs) return undefined;
+
+ const finalEvents = await Promise.all(
+ logs.map(async log => {
+ return {
+ ...log,
+ blockData:
+ Options?.blockData && log.blockHash ? await publicClient?.getBlock({ blockHash: log.blockHash }) : null,
+ transactionData:
+ Options?.transactionData && log.transactionHash
+ ? await publicClient?.getTransaction({ hash: log.transactionHash })
+ : null,
+ receiptData:
+ Options?.receiptData && log.transactionHash
+ ? await publicClient?.getTransactionReceipt({ hash: log.transactionHash })
+ : null,
+ };
+ }),
+ );
+
+ return finalEvents;
+};
+
/**
* Reads events from a deployed contract
* @param config - The config settings
@@ -45,133 +80,84 @@ export const useScaffoldEventHistory = <
watch,
enabled = true,
}: UseScaffoldEventHistoryConfig) => {
- const [events, setEvents] = useState();
- const [isLoading, setIsLoading] = useState(false);
- const [error, setError] = useState();
- const [fromBlockUpdated, setFromBlockUpdated] = useState(fromBlock);
-
- const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName);
- const publicClient = usePublicClient();
const { targetNetwork } = useTargetNetwork();
-
- const readEvents = async (fromBlock?: bigint) => {
- setIsLoading(true);
- try {
- if (!deployedContractData) {
- throw new Error("Contract not found");
- }
-
- if (!enabled) {
- throw new Error("Hook disabled");
- }
-
- const event = (deployedContractData.abi as Abi).find(
- part => part.type === "event" && part.name === eventName,
- ) as AbiEvent;
-
- const blockNumber = await publicClient.getBlockNumber({ cacheTime: 0 });
-
- if ((fromBlock && blockNumber >= fromBlock) || blockNumber >= fromBlockUpdated) {
- const logs = await publicClient.getLogs({
- address: deployedContractData?.address,
- event,
- args: filters as any, // TODO: check if it works and fix type
- fromBlock: fromBlock || fromBlockUpdated,
- toBlock: blockNumber,
- });
- setFromBlockUpdated(blockNumber + 1n);
-
- const newEvents = [];
- for (let i = logs.length - 1; i >= 0; i--) {
- newEvents.push({
- log: logs[i],
- args: logs[i].args,
- block:
- blockData && logs[i].blockHash === null
- ? null
- : await publicClient.getBlock({ blockHash: logs[i].blockHash as Hash }),
- transaction:
- transactionData && logs[i].transactionHash !== null
- ? await publicClient.getTransaction({ hash: logs[i].transactionHash as Hash })
- : null,
- receipt:
- receiptData && logs[i].transactionHash !== null
- ? await publicClient.getTransactionReceipt({ hash: logs[i].transactionHash as Hash })
- : null,
- });
- }
- if (events && typeof fromBlock === "undefined") {
- setEvents([...newEvents, ...events]);
- } else {
- setEvents(newEvents);
- }
- setError(undefined);
- }
- } catch (e: any) {
- console.error(e);
- setEvents(undefined);
- setError(e);
- } finally {
- setIsLoading(false);
- }
- };
-
- useEffect(() => {
- readEvents(fromBlock);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [fromBlock, enabled]);
-
- useEffect(() => {
- if (!deployedContractLoading) {
- readEvents();
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [
- publicClient,
- contractName,
- eventName,
- deployedContractLoading,
- deployedContractData?.address,
- deployedContractData,
- // eslint-disable-next-line react-hooks/exhaustive-deps
- JSON.stringify(filters, replacer),
- blockData,
- transactionData,
- receiptData,
- ]);
-
- useEffect(() => {
- // Reset the internal state when target network or fromBlock changed
- setEvents([]);
- setFromBlockUpdated(fromBlock);
- setError(undefined);
- }, [fromBlock, targetNetwork.id]);
-
- useInterval(
- async () => {
- if (!deployedContractLoading) {
- readEvents();
- }
+ const publicClient = usePublicClient({
+ chainId: targetNetwork.id,
+ });
+ const [isFirstRender, setIsFirstRender] = useState(true);
+
+ const { data: blockNumber } = useBlockNumber({ watch: watch, chainId: targetNetwork.id });
+
+ const { data: deployedContractData } = useDeployedContractInfo(contractName);
+
+ const event =
+ deployedContractData &&
+ ((deployedContractData.abi as Abi).find(part => part.type === "event" && part.name === eventName) as AbiEvent);
+
+ const isContractAddressAndClientReady = Boolean(deployedContractData?.address) && Boolean(publicClient);
+
+ const query = useInfiniteQuery({
+ queryKey: [
+ "eventHistory",
+ {
+ contractName,
+ address: deployedContractData?.address,
+ eventName,
+ fromBlock: fromBlock.toString(),
+ chainId: targetNetwork.id,
+ },
+ ],
+ queryFn: async ({ pageParam }) => {
+ if (!isContractAddressAndClientReady) return undefined;
+ const data = await getEvents(
+ { address: deployedContractData?.address, event, fromBlock: pageParam, args: filters },
+ publicClient,
+ { blockData, transactionData, receiptData },
+ );
+
+ return data;
},
- watch ? (targetNetwork.id !== chains.hardhat.id ? scaffoldConfig.pollingInterval : 4_000) : null,
- );
-
- const eventHistoryData = useMemo(
- () =>
- events?.map(addIndexedArgsToEvent) as UseScaffoldEventHistoryData<
+ enabled: enabled && isContractAddressAndClientReady,
+ initialPageParam: fromBlock,
+ getNextPageParam: () => {
+ return blockNumber;
+ },
+ select: data => {
+ const events = data.pages.flat();
+ const eventHistoryData = events?.map(addIndexedArgsToEvent) as UseScaffoldEventHistoryData<
TContractName,
TEventName,
TBlockData,
TTransactionData,
TReceiptData
- >,
- [events],
- );
+ >;
+ return {
+ pages: eventHistoryData?.reverse(),
+ pageParams: data.pageParams,
+ };
+ },
+ });
+
+ useEffect(() => {
+ const shouldSkipEffect = !blockNumber || !watch || isFirstRender;
+ if (shouldSkipEffect) {
+ // skipping on first render, since on first render we should call queryFn with
+ // fromBlock value, not blockNumber
+ if (isFirstRender) setIsFirstRender(false);
+ return;
+ }
+
+ query.fetchNextPage();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [blockNumber, watch]);
return {
- data: eventHistoryData,
- isLoading: isLoading,
- error: error,
+ data: query.data?.pages,
+ status: query.status,
+ error: query.error,
+ isLoading: query.isLoading,
+ isFetchingNewEvent: query.isFetchingNextPage,
+ refetch: query.refetch,
};
};
diff --git a/packages/nextjs/hooks/scaffold-eth/useScaffoldContractRead.ts b/packages/nextjs/hooks/scaffold-eth/useScaffoldReadContract.ts
similarity index 52%
rename from packages/nextjs/hooks/scaffold-eth/useScaffoldContractRead.ts
rename to packages/nextjs/hooks/scaffold-eth/useScaffoldReadContract.ts
index 39c0cb1..9d9e8f0 100644
--- a/packages/nextjs/hooks/scaffold-eth/useScaffoldContractRead.ts
+++ b/packages/nextjs/hooks/scaffold-eth/useScaffoldReadContract.ts
@@ -1,6 +1,9 @@
+import { useEffect } from "react";
import { useTargetNetwork } from "./useTargetNetwork";
+import { QueryObserverResult, RefetchOptions, useQueryClient } from "@tanstack/react-query";
import type { ExtractAbiFunctionNames } from "abitype";
-import { useContractRead } from "wagmi";
+import { ReadContractErrorType } from "viem";
+import { useBlockNumber, useReadContract } from "wagmi";
import { useDeployedContractInfo } from "~~/hooks/scaffold-eth";
import {
AbiFunctionReturnType,
@@ -17,7 +20,7 @@ import {
* @param config.functionName - name of the function to be called
* @param config.args - args to be passed to the function call
*/
-export const useScaffoldContractRead = <
+export const useScaffoldReadContract = <
TContractName extends ContractName,
TFunctionName extends ExtractAbiFunctionNames, "pure" | "view">,
>({
@@ -28,21 +31,43 @@ export const useScaffoldContractRead = <
}: UseScaffoldReadConfig) => {
const { data: deployedContract } = useDeployedContractInfo(contractName);
const { targetNetwork } = useTargetNetwork();
+ const { query: queryOptions, watch, ...readContractConfig } = readConfig;
+ // set watch to true by default
+ const defaultWatch = watch ?? true;
- return useContractRead({
+ const readContractHookRes = useReadContract({
chainId: targetNetwork.id,
functionName,
address: deployedContract?.address,
abi: deployedContract?.abi,
- watch: true,
args,
- enabled: !Array.isArray(args) || !args.some(arg => arg === undefined),
- ...(readConfig as any),
- }) as Omit, "data" | "refetch"> & {
+ ...(readContractConfig as any),
+ query: {
+ enabled: !Array.isArray(args) || !args.some(arg => arg === undefined),
+ ...queryOptions,
+ },
+ }) as Omit, "data" | "refetch"> & {
data: AbiFunctionReturnType | undefined;
- refetch: (options?: {
- throwOnError: boolean;
- cancelRefetch: boolean;
- }) => Promise>;
+ refetch: (
+ options?: RefetchOptions | undefined,
+ ) => Promise, ReadContractErrorType>>;
};
+
+ const queryClient = useQueryClient();
+ const { data: blockNumber } = useBlockNumber({
+ watch: defaultWatch,
+ chainId: targetNetwork.id,
+ query: {
+ enabled: defaultWatch,
+ },
+ });
+
+ useEffect(() => {
+ if (defaultWatch) {
+ queryClient.invalidateQueries({ queryKey: readContractHookRes.queryKey });
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [blockNumber]);
+
+ return readContractHookRes;
};
diff --git a/packages/nextjs/hooks/scaffold-eth/useScaffoldEventSubscriber.ts b/packages/nextjs/hooks/scaffold-eth/useScaffoldWatchContractEvent.ts
similarity index 74%
rename from packages/nextjs/hooks/scaffold-eth/useScaffoldEventSubscriber.ts
rename to packages/nextjs/hooks/scaffold-eth/useScaffoldWatchContractEvent.ts
index 389553c..844b4a0 100644
--- a/packages/nextjs/hooks/scaffold-eth/useScaffoldEventSubscriber.ts
+++ b/packages/nextjs/hooks/scaffold-eth/useScaffoldWatchContractEvent.ts
@@ -1,7 +1,7 @@
import { useTargetNetwork } from "./useTargetNetwork";
import { Abi, ExtractAbiEventNames } from "abitype";
import { Log } from "viem";
-import { useContractEvent } from "wagmi";
+import { useWatchContractEvent } from "wagmi";
import { addIndexedArgsToEvent, useDeployedContractInfo } from "~~/hooks/scaffold-eth";
import { ContractAbi, ContractName, UseScaffoldEventConfig } from "~~/utils/scaffold-eth/contract";
@@ -11,28 +11,27 @@ import { ContractAbi, ContractName, UseScaffoldEventConfig } from "~~/utils/scaf
* @param config - The config settings
* @param config.contractName - deployed contract name
* @param config.eventName - name of the event to listen for
- * @param config.listener - the callback that receives events. If only interested in 1 event, call `unwatch` inside of the listener
+ * @param config.onLogs - the callback that receives events.
*/
-export const useScaffoldEventSubscriber = <
+export const useScaffoldWatchContractEvent = <
TContractName extends ContractName,
TEventName extends ExtractAbiEventNames>,
>({
contractName,
eventName,
- listener,
+ onLogs,
}: UseScaffoldEventConfig) => {
const { data: deployedContractData } = useDeployedContractInfo(contractName);
const { targetNetwork } = useTargetNetwork();
const addIndexedArgsToLogs = (logs: Log[]) => logs.map(addIndexedArgsToEvent);
- const listenerWithIndexedArgs = (logs: Log[]) =>
- listener(addIndexedArgsToLogs(logs) as Parameters[0]);
+ const listenerWithIndexedArgs = (logs: Log[]) => onLogs(addIndexedArgsToLogs(logs) as Parameters[0]);
- return useContractEvent({
+ return useWatchContractEvent({
address: deployedContractData?.address,
abi: deployedContractData?.abi as Abi,
chainId: targetNetwork.id,
- listener: listenerWithIndexedArgs,
+ onLogs: listenerWithIndexedArgs,
eventName,
});
};
diff --git a/packages/nextjs/hooks/scaffold-eth/useScaffoldWriteContract.ts b/packages/nextjs/hooks/scaffold-eth/useScaffoldWriteContract.ts
new file mode 100644
index 0000000..4bd903f
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-eth/useScaffoldWriteContract.ts
@@ -0,0 +1,130 @@
+import { useState } from "react";
+import { useTargetNetwork } from "./useTargetNetwork";
+import { MutateOptions } from "@tanstack/react-query";
+import { Abi, ExtractAbiFunctionNames } from "abitype";
+import { Config, UseWriteContractParameters, useAccount, useWriteContract } from "wagmi";
+import { WriteContractErrorType, WriteContractReturnType } from "wagmi/actions";
+import { WriteContractVariables } from "wagmi/query";
+import { useDeployedContractInfo, useTransactor } from "~~/hooks/scaffold-eth";
+import { notification } from "~~/utils/scaffold-eth";
+import {
+ ContractAbi,
+ ContractName,
+ ScaffoldWriteContractOptions,
+ ScaffoldWriteContractVariables,
+} from "~~/utils/scaffold-eth/contract";
+
+/**
+ * Wrapper around wagmi's useWriteContract hook which automatically loads (by name) the contract ABI and address from
+ * the contracts present in deployedContracts.ts & externalContracts.ts corresponding to targetNetworks configured in scaffold.config.ts
+ * @param contractName - name of the contract to be written to
+ * @param writeContractParams - wagmi's useWriteContract parameters
+ */
+export const useScaffoldWriteContract = (
+ contractName: TContractName,
+ writeContractParams?: UseWriteContractParameters,
+) => {
+ const { chain } = useAccount();
+ const writeTx = useTransactor();
+ const [isMining, setIsMining] = useState(false);
+ const { targetNetwork } = useTargetNetwork();
+
+ const wagmiContractWrite = useWriteContract(writeContractParams);
+
+ const { data: deployedContractData } = useDeployedContractInfo(contractName);
+
+ const sendContractWriteAsyncTx = async <
+ TFunctionName extends ExtractAbiFunctionNames, "nonpayable" | "payable">,
+ >(
+ variables: ScaffoldWriteContractVariables,
+ options?: ScaffoldWriteContractOptions,
+ ) => {
+ if (!deployedContractData) {
+ notification.error("Target Contract is not deployed, did you forget to run `yarn deploy`?");
+ return;
+ }
+
+ if (!chain?.id) {
+ notification.error("Please connect your wallet");
+ return;
+ }
+ if (chain?.id !== targetNetwork.id) {
+ notification.error("You are on the wrong network");
+ return;
+ }
+
+ try {
+ setIsMining(true);
+ const { blockConfirmations, onBlockConfirmation, ...mutateOptions } = options || {};
+ const makeWriteWithParams = () =>
+ wagmiContractWrite.writeContractAsync(
+ {
+ abi: deployedContractData.abi as Abi,
+ address: deployedContractData.address,
+ ...variables,
+ } as WriteContractVariables,
+ mutateOptions as
+ | MutateOptions<
+ WriteContractReturnType,
+ WriteContractErrorType,
+ WriteContractVariables,
+ unknown
+ >
+ | undefined,
+ );
+ const writeTxResult = await writeTx(makeWriteWithParams, { blockConfirmations, onBlockConfirmation });
+
+ return writeTxResult;
+ } catch (e: any) {
+ throw e;
+ } finally {
+ setIsMining(false);
+ }
+ };
+
+ const sendContractWriteTx = <
+ TContractName extends ContractName,
+ TFunctionName extends ExtractAbiFunctionNames, "nonpayable" | "payable">,
+ >(
+ variables: ScaffoldWriteContractVariables,
+ options?: Omit,
+ ) => {
+ if (!deployedContractData) {
+ notification.error("Target Contract is not deployed, did you forget to run `yarn deploy`?");
+ return;
+ }
+ if (!chain?.id) {
+ notification.error("Please connect your wallet");
+ return;
+ }
+ if (chain?.id !== targetNetwork.id) {
+ notification.error("You are on the wrong network");
+ return;
+ }
+
+ wagmiContractWrite.writeContract(
+ {
+ abi: deployedContractData.abi as Abi,
+ address: deployedContractData.address,
+ ...variables,
+ } as WriteContractVariables,
+ options as
+ | MutateOptions<
+ WriteContractReturnType,
+ WriteContractErrorType,
+ WriteContractVariables,
+ unknown
+ >
+ | undefined,
+ );
+ };
+
+ return {
+ ...wagmiContractWrite,
+ isMining,
+ // Overwrite wagmi's writeContactAsync
+ writeContractAsync: sendContractWriteAsyncTx,
+ // Overwrite wagmi's writeContract
+ writeContract: sendContractWriteTx,
+ };
+};
diff --git a/packages/nextjs/hooks/scaffold-eth/useTargetNetwork.ts b/packages/nextjs/hooks/scaffold-eth/useTargetNetwork.ts
index e2f318b..ff0b23d 100644
--- a/packages/nextjs/hooks/scaffold-eth/useTargetNetwork.ts
+++ b/packages/nextjs/hooks/scaffold-eth/useTargetNetwork.ts
@@ -1,5 +1,5 @@
-import { useEffect } from "react";
-import { useNetwork } from "wagmi";
+import { useEffect, useMemo } from "react";
+import { useAccount } from "wagmi";
import scaffoldConfig from "~~/scaffold.config";
import { useGlobalState } from "~~/services/store/store";
import { ChainWithAttributes } from "~~/utils/scaffold-eth";
@@ -9,7 +9,7 @@ import { NETWORKS_EXTRA_DATA } from "~~/utils/scaffold-eth";
* Retrieves the connected wallet's network from scaffold.config or defaults to the 0th network in the list if the wallet is not connected.
*/
export function useTargetNetwork(): { targetNetwork: ChainWithAttributes } {
- const { chain } = useNetwork();
+ const { chain } = useAccount();
const targetNetwork = useGlobalState(({ targetNetwork }) => targetNetwork);
const setTargetNetwork = useGlobalState(({ setTargetNetwork }) => setTargetNetwork);
@@ -20,10 +20,13 @@ export function useTargetNetwork(): { targetNetwork: ChainWithAttributes } {
}
}, [chain?.id, setTargetNetwork, targetNetwork.id]);
- return {
- targetNetwork: {
- ...targetNetwork,
- ...NETWORKS_EXTRA_DATA[targetNetwork.id],
- },
- };
+ return useMemo(
+ () => ({
+ targetNetwork: {
+ ...targetNetwork,
+ ...NETWORKS_EXTRA_DATA[targetNetwork.id],
+ },
+ }),
+ [targetNetwork],
+ );
}
diff --git a/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx b/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx
index 73a38f0..0d14e14 100644
--- a/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx
+++ b/packages/nextjs/hooks/scaffold-eth/useTransactor.tsx
@@ -1,14 +1,14 @@
-import { WriteContractResult, getPublicClient } from "@wagmi/core";
-import { Hash, SendTransactionParameters, TransactionReceipt, WalletClient } from "viem";
-import { useWalletClient } from "wagmi";
+import { getPublicClient } from "@wagmi/core";
+import { Hash, SendTransactionParameters, WalletClient } from "viem";
+import { Config, useWalletClient } from "wagmi";
+import { SendTransactionMutate } from "wagmi/query";
+import { wagmiConfig } from "~~/services/web3/wagmiConfig";
import { getBlockExplorerTxLink, getParsedError, notification } from "~~/utils/scaffold-eth";
+import { TransactorFuncOptions } from "~~/utils/scaffold-eth/contract";
type TransactionFunc = (
- tx: (() => Promise) | (() => Promise) | SendTransactionParameters,
- options?: {
- onBlockConfirmation?: (txnReceipt: TransactionReceipt) => void;
- blockConfirmations?: number;
- },
+ tx: (() => Promise) | Parameters>[0],
+ options?: TransactorFuncOptions,
) => Promise;
/**
@@ -47,23 +47,19 @@ export const useTransactor = (_walletClient?: WalletClient): TransactionFunc =>
}
let notificationId = null;
- let transactionHash: Awaited["hash"] | undefined = undefined;
+ let transactionHash: Hash | undefined = undefined;
try {
const network = await walletClient.getChainId();
// Get full transaction from public client
- const publicClient = getPublicClient();
+ const publicClient = getPublicClient(wagmiConfig);
notificationId = notification.loading();
if (typeof tx === "function") {
// Tx is already prepared by the caller
const result = await tx();
- if (typeof result === "string") {
- transactionHash = result;
- } else {
- transactionHash = result.hash;
- }
+ transactionHash = result;
} else if (tx != null) {
- transactionHash = await walletClient.sendTransaction(tx);
+ transactionHash = await walletClient.sendTransaction(tx as SendTransactionParameters);
} else {
throw new Error("Incorrect transaction passed to transactor");
}
@@ -96,6 +92,7 @@ export const useTransactor = (_walletClient?: WalletClient): TransactionFunc =>
console.error("โก๏ธ ~ file: useTransactor.ts ~ error", error);
const message = getParsedError(error);
notification.error(message);
+ throw error;
}
return transactionHash;
diff --git a/packages/nextjs/hooks/scaffold-eth/useWatchBalance.ts b/packages/nextjs/hooks/scaffold-eth/useWatchBalance.ts
new file mode 100644
index 0000000..26acda9
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-eth/useWatchBalance.ts
@@ -0,0 +1,21 @@
+import { useEffect } from "react";
+import { useTargetNetwork } from "./useTargetNetwork";
+import { useQueryClient } from "@tanstack/react-query";
+import { UseBalanceParameters, useBalance, useBlockNumber } from "wagmi";
+
+/**
+ * Wrapper around wagmi's useBalance hook. Updates data on every block change.
+ */
+export const useWatchBalance = (useBalanceParameters: UseBalanceParameters) => {
+ const { targetNetwork } = useTargetNetwork();
+ const queryClient = useQueryClient();
+ const { data: blockNumber } = useBlockNumber({ watch: true, chainId: targetNetwork.id });
+ const { queryKey, ...restUseBalanceReturn } = useBalance(useBalanceParameters);
+
+ useEffect(() => {
+ queryClient.invalidateQueries({ queryKey });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [blockNumber]);
+
+ return restUseBalanceReturn;
+};
diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index c77574e..0cf0b77 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -1,57 +1,59 @@
{
"name": "@se-2/nextjs",
- "private": true,
"version": "0.1.0",
+ "private": true,
"scripts": {
- "dev": "next dev",
- "start": "next dev",
"build": "next build",
- "serve": "next start",
- "lint": "next lint",
- "format": "prettier --write . '!(node_modules|.next|contracts)/**/*'",
"check-types": "tsc --noEmit --incremental",
+ "dev": "next dev",
+ "format": "prettier --write . '!(node_modules|.next|contracts)/**/*'",
+ "lint": "next lint",
+ "serve": "next start",
+ "start": "next dev",
"vercel": "vercel",
"vercel:yolo": "vercel --build-env NEXT_PUBLIC_IGNORE_BUILD_ERROR=true"
},
"dependencies": {
- "@ethersproject/providers": "^5.7.2",
- "@heroicons/react": "^2.0.11",
- "@rainbow-me/rainbowkit": "1.3.5",
- "@uniswap/sdk-core": "^4.0.1",
- "@uniswap/v2-sdk": "^3.0.1",
- "blo": "^1.0.1",
+ "@heroicons/react": "~2.0.11",
+ "@rainbow-me/rainbowkit": "2.1.2",
+ "@tanstack/react-query": "~5.28.6",
+ "@uniswap/sdk-core": "~4.0.1",
+ "@uniswap/v2-sdk": "~3.0.1",
+ "blo": "~1.0.1",
+ "burner-connector": "~0.0.8",
"daisyui": "4.5.0",
- "next": "^14.0.4",
- "next-themes": "^0.2.1",
- "nprogress": "^0.2.0",
- "qrcode.react": "^3.1.0",
- "react": "^18.2.0",
- "react-copy-to-clipboard": "^5.1.0",
- "react-dom": "^18.2.0",
- "react-hot-toast": "^2.4.0",
- "use-debounce": "^8.0.4",
- "usehooks-ts": "^2.13.0",
- "viem": "1.19.9",
- "wagmi": "1.4.12",
- "zustand": "^4.1.2"
+ "next": "~14.0.4",
+ "next-themes": "~0.2.1",
+ "nprogress": "~0.2.0",
+ "qrcode.react": "~3.1.0",
+ "react": "~18.2.0",
+ "react-copy-to-clipboard": "~5.1.0",
+ "react-dom": "~18.2.0",
+ "react-hot-toast": "~2.4.0",
+ "use-debounce": "~8.0.4",
+ "usehooks-ts": "2.13.0",
+ "viem": "2.13.6",
+ "wagmi": "2.9.8",
+ "zustand": "~4.1.2"
},
"devDependencies": {
- "@trivago/prettier-plugin-sort-imports": "^4.1.1",
- "@types/node": "^17.0.35",
+ "@trivago/prettier-plugin-sort-imports": "~4.1.1",
+ "@types/node": "^17.0.45",
"@types/nprogress": "^0",
- "@types/react": "^18.0.9",
+ "@types/react": "^18.0.21",
"@types/react-copy-to-clipboard": "^5.0.4",
- "@typescript-eslint/eslint-plugin": "^5.39.0",
- "autoprefixer": "^10.4.12",
- "eslint": "^8.15.0",
- "eslint-config-next": "^14.0.4",
- "eslint-config-prettier": "^8.5.0",
- "eslint-plugin-prettier": "^4.2.1",
- "postcss": "^8.4.16",
- "prettier": "^2.8.4",
- "tailwindcss": "^3.3.3",
- "type-fest": "^4.6.0",
- "typescript": "^5.1.6",
- "vercel": "^32.4.1"
+ "@typescript-eslint/eslint-plugin": "~5.40.0",
+ "abitype": "1.0.2",
+ "autoprefixer": "~10.4.12",
+ "eslint": "~8.24.0",
+ "eslint-config-next": "~14.0.4",
+ "eslint-config-prettier": "~8.5.0",
+ "eslint-plugin-prettier": "~4.2.1",
+ "postcss": "~8.4.16",
+ "prettier": "~2.8.4",
+ "tailwindcss": "~3.4.3",
+ "type-fest": "~4.6.0",
+ "typescript": "5.1.6",
+ "vercel": "~32.4.1"
}
}
diff --git a/packages/nextjs/scaffold.config.ts b/packages/nextjs/scaffold.config.ts
index f5ead03..7870ecc 100644
--- a/packages/nextjs/scaffold.config.ts
+++ b/packages/nextjs/scaffold.config.ts
@@ -6,12 +6,11 @@ export type ScaffoldConfig = {
alchemyApiKey: string;
walletConnectProjectId: string;
onlyLocalBurnerWallet: boolean;
- walletAutoConnect: boolean;
};
const scaffoldConfig = {
// The networks on which your DApp is live
- targetNetworks: [chains.hardhat],
+ targetNetworks: [chains.foundry],
// The interval at which your front-end polls the RPC servers for new data
// it has no effect if you only target the local network (default is 4000)
@@ -31,13 +30,6 @@ const scaffoldConfig = {
// Only show the Burner Wallet when running on hardhat network
onlyLocalBurnerWallet: true,
-
- /**
- * Auto connect:
- * 1. If the user was connected into a wallet before, on page reload reconnect automatically
- * 2. If user is not connected to any wallet: On reload, connect to burner wallet if burnerWallet.enabled is true && burnerWallet.onlyLocal is false
- */
- walletAutoConnect: true,
} as const satisfies ScaffoldConfig;
export default scaffoldConfig;
diff --git a/packages/nextjs/services/store/store.ts b/packages/nextjs/services/store/store.ts
index da69755..5c2832c 100644
--- a/packages/nextjs/services/store/store.ts
+++ b/packages/nextjs/services/store/store.ts
@@ -1,4 +1,4 @@
-import { create } from "zustand";
+import create from "zustand";
import scaffoldConfig from "~~/scaffold.config";
import { ChainWithAttributes } from "~~/utils/scaffold-eth";
@@ -12,15 +12,25 @@ import { ChainWithAttributes } from "~~/utils/scaffold-eth";
*/
type GlobalState = {
- nativeCurrencyPrice: number;
+ nativeCurrency: {
+ price: number;
+ isFetching: boolean;
+ };
setNativeCurrencyPrice: (newNativeCurrencyPriceState: number) => void;
+ setIsNativeCurrencyFetching: (newIsNativeCurrencyFetching: boolean) => void;
targetNetwork: ChainWithAttributes;
setTargetNetwork: (newTargetNetwork: ChainWithAttributes) => void;
};
export const useGlobalState = create(set => ({
- nativeCurrencyPrice: 0,
- setNativeCurrencyPrice: (newValue: number): void => set(() => ({ nativeCurrencyPrice: newValue })),
+ nativeCurrency: {
+ price: 0,
+ isFetching: true,
+ },
+ setNativeCurrencyPrice: (newValue: number): void =>
+ set(state => ({ nativeCurrency: { ...state.nativeCurrency, price: newValue } })),
+ setIsNativeCurrencyFetching: (newValue: boolean): void =>
+ set(state => ({ nativeCurrency: { ...state.nativeCurrency, isFetching: newValue } })),
targetNetwork: scaffoldConfig.targetNetworks[0],
setTargetNetwork: (newTargetNetwork: ChainWithAttributes) => set(() => ({ targetNetwork: newTargetNetwork })),
}));
diff --git a/packages/nextjs/services/web3/wagmi-burner/BurnerConnector.ts b/packages/nextjs/services/web3/wagmi-burner/BurnerConnector.ts
deleted file mode 100644
index 311c6d9..0000000
--- a/packages/nextjs/services/web3/wagmi-burner/BurnerConnector.ts
+++ /dev/null
@@ -1,155 +0,0 @@
-import { StaticJsonRpcProvider } from "@ethersproject/providers";
-import { Address, Chain, HttpTransport, PrivateKeyAccount, WalletClient, createWalletClient, http } from "viem";
-import { privateKeyToAccount } from "viem/accounts";
-import { Connector } from "wagmi";
-import { loadBurnerSK } from "~~/hooks/scaffold-eth";
-import { BurnerConnectorError, BurnerConnectorErrorList } from "~~/services/web3/wagmi-burner/BurnerConnectorErrors";
-import { BurnerConnectorData, BurnerConnectorOptions } from "~~/services/web3/wagmi-burner/BurnerConnectorTypes";
-
-export const burnerWalletId = "burner-wallet";
-export const burnerWalletName = "Burner Wallet";
-
-/**
- * This class is a wagmi connector for BurnerWallet. Its used by {@link burnerWalletConfig}
- */
-export class BurnerConnector extends Connector {
- readonly id = burnerWalletId;
- readonly name = burnerWalletName;
- readonly ready = true;
-
- private provider?: StaticJsonRpcProvider;
-
- // store for getWallet()
- private burnerWallet: WalletClient | undefined;
-
- constructor(config: { chains?: Chain[]; options: BurnerConnectorOptions }) {
- super(config);
- this.burnerWallet = undefined;
- }
-
- async getProvider() {
- if (!this.provider) {
- const chain = this.getChainFromId();
- this.provider = new StaticJsonRpcProvider(chain.rpcUrls.default.http[0]);
- }
- return this.provider;
- }
-
- async getWalletClient(config?: { chainId?: number | undefined } | undefined) {
- const chain = this.getChainFromId(config?.chainId);
- if (!this.burnerWallet) {
- const burnerAccount = privateKeyToAccount(loadBurnerSK());
-
- const client = createWalletClient({
- chain: chain,
- account: burnerAccount,
- transport: http(),
- });
- this.burnerWallet = client;
- }
- return Promise.resolve(this.burnerWallet);
- }
-
- async connect(config?: { chainId?: number | undefined } | undefined): Promise> {
- const chain = this.getChainFromId(config?.chainId);
-
- this.provider = new StaticJsonRpcProvider(chain.rpcUrls.default.http[0]);
- const account = await this.getAccount();
-
- if (this.provider == null || account == null) {
- throw new BurnerConnectorError(BurnerConnectorErrorList.couldNotConnect);
- }
-
- if (!account) {
- throw new BurnerConnectorError(BurnerConnectorErrorList.accountNotFound);
- }
-
- const data: Required = {
- account,
- chain: {
- id: chain.id,
- unsupported: false,
- },
- provider: this.provider,
- };
-
- return Promise.resolve(data);
- }
-
- private getChainFromId(chainId?: number) {
- const resolveChainId = chainId ?? this.options.defaultChainId;
- const chain = this.chains.find(f => f.id === resolveChainId);
- if (chain == null) {
- throw new BurnerConnectorError(BurnerConnectorErrorList.chainNotSupported);
- }
- return chain;
- }
-
- disconnect(): Promise {
- console.log("disconnect from burnerwallet");
- return Promise.resolve();
- }
-
- async getAccount(): Promise {
- const bunerAccount = privateKeyToAccount(loadBurnerSK());
- return bunerAccount.address as Address;
- }
-
- async getChainId(): Promise {
- const network = await this.provider?.getNetwork();
- const chainId = network?.chainId ?? this.options.defaultChainId;
- if (chainId == null) {
- throw new BurnerConnectorError(BurnerConnectorErrorList.chainIdNotResolved);
- }
-
- return Promise.resolve(chainId);
- }
-
- async isAuthorized() {
- try {
- const account = await this.getAccount();
- return !!account;
- } catch {
- return false;
- }
- }
-
- protected async onAccountsChanged() {
- const chainId = await this.getChainId();
- const chain = this.getChainFromId(chainId);
- const bunerAccount = privateKeyToAccount(loadBurnerSK());
-
- const client = createWalletClient({
- chain: chain,
- account: bunerAccount,
- transport: http(),
- });
- this.burnerWallet = client;
- }
-
- async switchChain(chainId: number) {
- const chain = this.getChainFromId(chainId);
- this.provider = new StaticJsonRpcProvider(chain.rpcUrls.default.http[0]);
-
- await this.onChainChanged();
- return chain;
- }
-
- protected async onChainChanged() {
- const chainId = await this.getChainId();
- const chain = this.getChainFromId(chainId);
- const bunerAccount = privateKeyToAccount(loadBurnerSK());
-
- const client = createWalletClient({
- chain: chain,
- account: bunerAccount,
- transport: http(),
- });
- this.burnerWallet = client;
- this.emit("change", { chain: { id: chainId, unsupported: false } });
- }
-
- protected onDisconnect(error: Error): void {
- if (error) console.warn(error);
- }
-}
diff --git a/packages/nextjs/services/web3/wagmi-burner/BurnerConnectorErrors.ts b/packages/nextjs/services/web3/wagmi-burner/BurnerConnectorErrors.ts
deleted file mode 100644
index 4847870..0000000
--- a/packages/nextjs/services/web3/wagmi-burner/BurnerConnectorErrors.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-/**
- * Error list used by {@link BurnerConnectorError}
- */
-export const BurnerConnectorErrorList = {
- accountNotFound: "Account not found",
- couldNotConnect: "Could not connect to network",
- unsupportedBurnerChain: "This network is not supported for burner connector",
- chainIdNotResolved: "Could not resolve chainId",
- chainNotSupported: "Chain is not supported, check burner wallet config",
-} as const;
-
-/**
- * A union of all the BurnerConnectorErrorList
- */
-export type BurnerConnectorErrorTypes = (typeof BurnerConnectorErrorList)[keyof typeof BurnerConnectorErrorList];
-
-export class BurnerConnectorError extends Error {
- constructor(errorType: BurnerConnectorErrorTypes, message?: string) {
- const msg = `BurnerConnectorError ${errorType}: ${message ?? ""} `;
- super(msg);
- console.warn(msg);
- }
-}
diff --git a/packages/nextjs/services/web3/wagmi-burner/BurnerConnectorTypes.ts b/packages/nextjs/services/web3/wagmi-burner/BurnerConnectorTypes.ts
deleted file mode 100644
index 02821a6..0000000
--- a/packages/nextjs/services/web3/wagmi-burner/BurnerConnectorTypes.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-import { StaticJsonRpcProvider } from "@ethersproject/providers";
-import { ConnectorData } from "wagmi";
-
-export type BurnerConnectorOptions = {
- defaultChainId: number;
-};
-
-export type BurnerConnectorData = ConnectorData & {
- provider: StaticJsonRpcProvider;
-};
diff --git a/packages/nextjs/services/web3/wagmi-burner/burnerWalletConfig.ts b/packages/nextjs/services/web3/wagmi-burner/burnerWalletConfig.ts
deleted file mode 100644
index 9b1d3de..0000000
--- a/packages/nextjs/services/web3/wagmi-burner/burnerWalletConfig.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Chain, Wallet } from "@rainbow-me/rainbowkit";
-import { hardhat } from "viem/chains";
-import scaffoldConfig from "~~/scaffold.config";
-import { BurnerConnector, burnerWalletId, burnerWalletName } from "~~/services/web3/wagmi-burner/BurnerConnector";
-import { getTargetNetworks } from "~~/utils/scaffold-eth";
-
-const { onlyLocalBurnerWallet } = scaffoldConfig;
-const targetNetworks = getTargetNetworks();
-
-export type BurnerWalletOptions = {
- chains: Chain[];
-};
-
-const burnerWalletIconBase64 =
- "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzUzIiBoZWlnaHQ9IjM1MiIgdmlld0JveD0iMCAwIDM1MyAzNTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHg9IjAuNzE2MzA5IiB5PSIwLjMxNzEzOSIgd2lkdGg9IjM1MS4zOTQiIGhlaWdodD0iMzUxLjM5NCIgZmlsbD0idXJsKCNwYWludDBfbGluZWFyXzNfMTUxKSIvPgo8Y2lyY2xlIGN4PSIzNC40OTUzIiBjeT0iMzQuNDk1MyIgcj0iMzQuNDk1MyIgdHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMjA3LjAxOCAyNTQuMTIpIiBmaWxsPSIjRkY2NjBBIi8+CjxwYXRoIGQ9Ik0xNTQuMzE4IDMxNy45NTVDMTcxLjI3MyAzMTAuODkgMTc2LjU4MiAyOTAuNzE1IDE3Ni4xNTcgMjgzLjQ4N0wyMDcuMDE4IDI4OC44NjRDMjA3LjAxOCAzMDMuMzE0IDIwMC4yMTIgMzA5LjQwMiAxOTcuODI0IDMxMi40MzNDMTkzLjQ3NCAzMTcuOTU1IDE3My4zNTEgMzMwLjAzIDE1NC4zMTggMzE3Ljk1NVoiIGZpbGw9InVybCgjcGFpbnQxX3JhZGlhbF8zXzE1MSkiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfZF8zXzE1MSkiPgo8cGF0aCBmaWxsLXJ1bGU9ImV2ZW5vZGQiIGNsaXAtcnVsZT0iZXZlbm9kZCIgZD0iTTIyNy4zNzcgMzAyLjI3NkMyMjYuNDI2IDMwNS44OTcgMjMwLjMxNSAzMDkuNDA1IDIzMy4zOTYgMzA3LjI3OUMyNTQuNTM4IDI5Mi42ODQgMjcwLjQ3OSAyNjkuOTQ1IDI3NC44OSAyNDcuNDg5QzI4Mi4yNCAyMTAuMDcxIDI3Mi4yMzUgMTc1LjcyNyAyMzguMDI4IDE0NS45MjVDMjAwLjg3NCAxMTMuNTU2IDE5MS44NDQgODguNDU2MSAxOTAuMTYyIDUwLjg3MThDMTg5Ljc5NyA0Mi43MjE4IDE4MS42MDQgMzcuMjk0NyAxNzQuODI0IDQxLjgzMTdDMTUyLjY2OCA1Ni42NTc0IDEzMi41MTIgODQuNDk5IDEzOC45MTEgMTIwLjc1OEMxNDEuMDA0IDEzMi42MjEgMTQ2Ljc5NCAxNDEuMDE2IDE1MS45NyAxNDguNTIzQzE1OC40OTEgMTU3Ljk3OCAxNjQuMDM5IDE2Ni4wMjMgMTU5Ljk5NyAxNzcuODFDMTU1LjIwMyAxOTEuNzk0IDEzOS4xMzQgMTk5LjE2MiAxMjguNzQ3IDE5Mi40MjlDMTE0LjE3IDE4Mi45ODEgMTEzLjI1MyAxNjYuNjUxIDExNy45NjkgMTQ5LjQ1NkMxMTguOTAyIDE0Ni4wNTUgMTE1LjQ3MSAxNDMuMjA0IDExMi42OCAxNDUuMzU5QzkxLjM2MDQgMTYxLjgyMSA2OS4xNTMyIDE5OS4yNjcgNzcuNjY0NyAyNDcuNDg5Qzg1Ljk3OTIgMjc2LjIxMiA5Ny45Mjc3IDI5Mi41MzcgMTEwLjk3MSAzMDEuNTQxQzExMy43NjMgMzAzLjQ2OCAxMTcuMTU5IDMwMC42MzEgMTE2LjU5NyAyOTcuMjg2QzExNi4wODEgMjk0LjIxMiAxMTUuODEzIDI5MS4wNTQgMTE1LjgxMyAyODcuODMzQzExNS44MTMgMjU2LjUxMyAxNDEuMjAzIDIzMS4xMjMgMTcyLjUyMyAyMzEuMTIzQzIwMy44NDIgMjMxLjEyMyAyMjkuMjMyIDI1Ni41MTMgMjI5LjIzMiAyODcuODMzQzIyOS4yMzIgMjkyLjgyNCAyMjguNTg3IDI5Ny42NjUgMjI3LjM3NyAzMDIuMjc2WiIgZmlsbD0idXJsKCNwYWludDJfbGluZWFyXzNfMTUxKSIvPgo8L2c+CjxkZWZzPgo8ZmlsdGVyIGlkPSJmaWx0ZXIwX2RfM18xNTEiIHg9IjcyLjExMTIiIHk9IjM2LjQ5NCIgd2lkdGg9IjIwOC43NDIiIGhlaWdodD0iMjc1LjEyIiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQvPgo8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIxLjg0NTA2Ii8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9Im91dCIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAxIDAgMCAwIDAgMC40MiAwIDAgMCAwIDAgMCAwIDAgMC43IDAiLz4KPGZlQmxlbmQgbW9kZT0ibXVsdGlwbHkiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9ImVmZmVjdDFfZHJvcFNoYWRvd18zXzE1MSIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluPSJTb3VyY2VHcmFwaGljIiBpbjI9ImVmZmVjdDFfZHJvcFNoYWRvd18zXzE1MSIgcmVzdWx0PSJzaGFwZSIvPgo8L2ZpbHRlcj4KPGxpbmVhckdyYWRpZW50IGlkPSJwYWludDBfbGluZWFyXzNfMTUxIiB4MT0iMTc2LjQxMyIgeTE9IjAuMzE3MTM5IiB4Mj0iMTc2LjQxMyIgeTI9IjM1MS43MTEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agc3RvcC1jb2xvcj0iI0ZGRjI3OSIvPgo8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNGRkQzMzYiLz4KPC9saW5lYXJHcmFkaWVudD4KPHJhZGlhbEdyYWRpZW50IGlkPSJwYWludDFfcmFkaWFsXzNfMTUxIiBjeD0iMCIgY3k9IjAiIHI9IjEiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKDIxOC4wNDggMjQ5LjM0Nykgcm90YXRlKDEyNC4wMTgpIHNjYWxlKDg5LjI5NTUgMjY0LjgwOSkiPgo8c3RvcCBvZmZzZXQ9IjAuNjQwODUiIHN0b3AtY29sb3I9IiNGRjY2MEEiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkZCRTE1Ii8+CjwvcmFkaWFsR3JhZGllbnQ+CjxsaW5lYXJHcmFkaWVudCBpZD0icGFpbnQyX2xpbmVhcl8zXzE1MSIgeDE9IjE3Ni40ODIiIHkxPSI0MC4xODQxIiB4Mj0iMTc2LjQ4MiIgeTI9IjMxNy4yNzgiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KPHN0b3Agb2Zmc2V0PSIwLjMzODU0MiIgc3RvcC1jb2xvcj0iI0ZGOEYzRiIvPgo8c3RvcCBvZmZzZXQ9IjAuNjU2MjUiIHN0b3AtY29sb3I9IiNGRjcwMjAiLz4KPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjRkYzRDAwIi8+CjwvbGluZWFyR3JhZGllbnQ+CjwvZGVmcz4KPC9zdmc+Cg==";
-
-/**
- * Wagmi config for burner wallet
- */
-export const burnerWalletConfig = ({ chains }: BurnerWalletOptions): Wallet => ({
- id: burnerWalletId,
- name: burnerWalletName,
- iconUrl: burnerWalletIconBase64,
- iconBackground: "#ffffff",
- hidden: () => {
- if (onlyLocalBurnerWallet) {
- return targetNetworks.some(({ id }) => id !== hardhat.id);
- }
-
- return false;
- },
- createConnector: () => {
- const connector = new BurnerConnector({ chains, options: { defaultChainId: targetNetworks[0].id } });
-
- return {
- connector,
- };
- },
-});
diff --git a/packages/nextjs/services/web3/wagmiConfig.tsx b/packages/nextjs/services/web3/wagmiConfig.tsx
index 240aea0..e207843 100644
--- a/packages/nextjs/services/web3/wagmiConfig.tsx
+++ b/packages/nextjs/services/web3/wagmiConfig.tsx
@@ -1,8 +1,30 @@
+import { wagmiConnectors } from "./wagmiConnectors";
+import { Chain, createClient, http } from "viem";
+import { hardhat, mainnet } from "viem/chains";
import { createConfig } from "wagmi";
-import { appChains, wagmiConnectors } from "~~/services/web3/wagmiConnectors";
+import scaffoldConfig from "~~/scaffold.config";
+import { getAlchemyHttpUrl } from "~~/utils/scaffold-eth";
+
+const { targetNetworks } = scaffoldConfig;
+
+// We always want to have mainnet enabled (ENS resolution, ETH price, etc). But only once.
+export const enabledChains = targetNetworks.find((network: Chain) => network.id === 1)
+ ? targetNetworks
+ : ([...targetNetworks, mainnet] as const);
export const wagmiConfig = createConfig({
- autoConnect: false,
+ chains: enabledChains,
connectors: wagmiConnectors,
- publicClient: appChains.publicClient,
+ ssr: true,
+ client({ chain }) {
+ return createClient({
+ chain,
+ transport: http(getAlchemyHttpUrl(chain.id)),
+ ...(chain.id !== (hardhat as Chain).id
+ ? {
+ pollingInterval: scaffoldConfig.pollingInterval,
+ }
+ : {}),
+ });
+ },
});
diff --git a/packages/nextjs/services/web3/wagmiConnectors.tsx b/packages/nextjs/services/web3/wagmiConnectors.tsx
index 1d42e30..8167996 100644
--- a/packages/nextjs/services/web3/wagmiConnectors.tsx
+++ b/packages/nextjs/services/web3/wagmiConnectors.tsx
@@ -1,6 +1,5 @@
import { connectorsForWallets } from "@rainbow-me/rainbowkit";
import {
- braveWallet,
coinbaseWallet,
ledgerWallet,
metaMaskWallet,
@@ -8,69 +7,37 @@ import {
safeWallet,
walletConnectWallet,
} from "@rainbow-me/rainbowkit/wallets";
+import { rainbowkitBurnerWallet } from "burner-connector";
import * as chains from "viem/chains";
-import { configureChains } from "wagmi";
-import { alchemyProvider } from "wagmi/providers/alchemy";
-import { publicProvider } from "wagmi/providers/public";
import scaffoldConfig from "~~/scaffold.config";
-import { burnerWalletConfig } from "~~/services/web3/wagmi-burner/burnerWalletConfig";
-import { getTargetNetworks } from "~~/utils/scaffold-eth";
-const targetNetworks = getTargetNetworks();
-const { onlyLocalBurnerWallet } = scaffoldConfig;
+const { onlyLocalBurnerWallet, targetNetworks } = scaffoldConfig;
-// We always want to have mainnet enabled (ENS resolution, ETH price, etc). But only once.
-const enabledChains = targetNetworks.find(network => network.id === 1)
- ? targetNetworks
- : [...targetNetworks, chains.mainnet];
-
-/**
- * Chains for the app
- */
-export const appChains = configureChains(
- enabledChains,
- [
- alchemyProvider({
- apiKey: scaffoldConfig.alchemyApiKey,
- }),
- publicProvider(),
- ],
- {
- // We might not need this checkout https://github.com/scaffold-eth/scaffold-eth-2/pull/45#discussion_r1024496359, will test and remove this before merging
- stallTimeout: 3_000,
- // Sets pollingInterval if using chains other than local hardhat chain
- ...(targetNetworks.find(network => network.id !== chains.hardhat.id)
- ? {
- pollingInterval: scaffoldConfig.pollingInterval,
- }
- : {}),
- },
-);
-
-const walletsOptions = { chains: appChains.chains, projectId: scaffoldConfig.walletConnectProjectId };
const wallets = [
- metaMaskWallet({ ...walletsOptions, shimDisconnect: true }),
- walletConnectWallet(walletsOptions),
- ledgerWallet(walletsOptions),
- braveWallet(walletsOptions),
- coinbaseWallet({ ...walletsOptions, appName: "scaffold-eth-2" }),
- rainbowWallet(walletsOptions),
- ...(!targetNetworks.some(network => network.id !== chains.hardhat.id) || !onlyLocalBurnerWallet
- ? [
- burnerWalletConfig({
- chains: appChains.chains.filter(chain => targetNetworks.map(({ id }) => id).includes(chain.id)),
- }),
- ]
+ metaMaskWallet,
+ walletConnectWallet,
+ ledgerWallet,
+ coinbaseWallet,
+ rainbowWallet,
+ safeWallet,
+ ...(!targetNetworks.some(network => network.id !== (chains.hardhat as chains.Chain).id) || !onlyLocalBurnerWallet
+ ? [rainbowkitBurnerWallet]
: []),
- safeWallet({ ...walletsOptions }),
];
/**
* wagmi connectors for the wagmi context
*/
-export const wagmiConnectors = connectorsForWallets([
+export const wagmiConnectors = connectorsForWallets(
+ [
+ {
+ groupName: "Supported Wallets",
+ wallets,
+ },
+ ],
+
{
- groupName: "Supported Wallets",
- wallets,
+ appName: "scaffold-eth-2",
+ projectId: scaffoldConfig.walletConnectProjectId,
},
-]);
+);
diff --git a/packages/nextjs/tailwind.config.js b/packages/nextjs/tailwind.config.js
index d0e358b..9099dc5 100644
--- a/packages/nextjs/tailwind.config.js
+++ b/packages/nextjs/tailwind.config.js
@@ -3,6 +3,7 @@ module.exports = {
content: ["./app/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", "./utils/**/*.{js,ts,jsx,tsx}"],
plugins: [require("daisyui")],
darkTheme: "dark",
+ darkMode: ["selector", "[data-theme='dark']"],
// DaisyUI theme colors
daisyui: {
themes: [
diff --git a/packages/nextjs/types/abitype/abi.d.ts b/packages/nextjs/types/abitype/abi.d.ts
index 8b1301a..c216a07 100644
--- a/packages/nextjs/types/abitype/abi.d.ts
+++ b/packages/nextjs/types/abitype/abi.d.ts
@@ -2,14 +2,20 @@ import "abitype";
type AddressType = string;
+declare module "abitype" {
+ export interface Register {
+ AddressType: AddressType;
+ }
+}
+
declare module "viem/node_modules/abitype" {
- export interface Config {
+ export interface Register {
AddressType: AddressType;
}
}
-declare module "abitype" {
- export interface Config {
+declare module "wagmi/node_moudles/abitype" {
+ export interface Register {
AddressType: AddressType;
}
}
diff --git a/packages/nextjs/utils/scaffold-eth/common.ts b/packages/nextjs/utils/scaffold-eth/common.ts
index 22d031a..967167b 100644
--- a/packages/nextjs/utils/scaffold-eth/common.ts
+++ b/packages/nextjs/utils/scaffold-eth/common.ts
@@ -1,3 +1,7 @@
// To be used in JSON.stringify when a field might be bigint
// https://wagmi.sh/react/faq#bigint-serialization
export const replacer = (_key: string, value: unknown) => (typeof value === "bigint" ? value.toString() : value);
+
+export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
+
+export const isZeroAddress = (address: string) => address === ZERO_ADDRESS;
diff --git a/packages/nextjs/utils/scaffold-eth/contract.ts b/packages/nextjs/utils/scaffold-eth/contract.ts
index f092d5a..bf4580d 100644
--- a/packages/nextjs/utils/scaffold-eth/contract.ts
+++ b/packages/nextjs/utils/scaffold-eth/contract.ts
@@ -1,3 +1,4 @@
+import { MutateOptions } from "@tanstack/react-query";
import {
Abi,
AbiParameter,
@@ -18,8 +19,11 @@ import {
GetTransactionReturnType,
Log,
TransactionReceipt,
+ WriteContractErrorType,
} from "viem";
-import { UseContractEventConfig, UseContractReadConfig, UseContractWriteConfig } from "wagmi";
+import { Config, UseReadContractParameters, UseWatchContractEventParameters } from "wagmi";
+import { WriteContractParameters, WriteContractReturnType } from "wagmi/actions";
+import { WriteContractVariables } from "wagmi/query";
import deployedContractsData from "~~/contracts/deployedContracts";
import externalContractsData from "~~/contracts/externalContracts";
import scaffoldConfig from "~~/scaffold.config";
@@ -153,6 +157,9 @@ type UseScaffoldArgsParam<
> = TFunctionName extends FunctionNamesWithInputs
? {
args: OptionalTupple, TFunctionName>>>;
+ value?: ExtractAbiFunction, TFunctionName>["stateMutability"] extends "payable"
+ ? bigint | undefined
+ : undefined;
}
: {
args?: never;
@@ -163,29 +170,41 @@ export type UseScaffoldReadConfig<
TFunctionName extends ExtractAbiFunctionNames, ReadAbiStateMutability>,
> = {
contractName: TContractName;
+ watch?: boolean;
} & IsContractDeclarationMissing<
- Partial,
+ Partial,
{
functionName: TFunctionName;
} & UseScaffoldArgsParam &
- Omit
+ Omit
>;
-export type UseScaffoldWriteConfig<
+export type ScaffoldWriteContractVariables<
TContractName extends ContractName,
TFunctionName extends ExtractAbiFunctionNames, WriteAbiStateMutability>,
-> = {
- contractName: TContractName;
- onBlockConfirmation?: (txnReceipt: TransactionReceipt) => void;
- blockConfirmations?: number;
-} & IsContractDeclarationMissing<
- Partial,
+> = IsContractDeclarationMissing<
+ Partial,
{
functionName: TFunctionName;
} & UseScaffoldArgsParam &
- Omit
+ Omit
>;
+type WriteVariables = WriteContractVariables;
+
+export type TransactorFuncOptions = {
+ onBlockConfirmation?: (txnReceipt: TransactionReceipt) => void;
+ blockConfirmations?: number;
+};
+
+export type ScaffoldWriteContractOptions = MutateOptions<
+ WriteContractReturnType,
+ WriteContractErrorType,
+ WriteVariables,
+ unknown
+> &
+ TransactorFuncOptions;
+
export type UseScaffoldEventConfig<
TContractName extends ContractName,
TEventName extends ExtractAbiEventNames>,
@@ -195,9 +214,10 @@ export type UseScaffoldEventConfig<
>,
> = {
contractName: TContractName;
+ eventName: TEventName;
} & IsContractDeclarationMissing<
- Omit & {
- listener: (
+ Omit & {
+ onLogs: (
logs: Simplify<
Omit, "args" | "eventName"> & {
args: Record;
@@ -206,8 +226,8 @@ export type UseScaffoldEventConfig<
>[],
) => void;
},
- Omit, TEventName>, "listener"> & {
- listener: (
+ Omit