diff --git a/templates/react/next-ethers/README.md b/templates/react/next-ethers/README.md index dd7eeb2..effe786 100644 --- a/templates/react/next-ethers/README.md +++ b/templates/react/next-ethers/README.md @@ -2,6 +2,14 @@ This is a [zkSync](https://zksync.io) + [ethers v6](https://docs.ethers.org/v6/) # Getting Started +## Requirements +- A wallet extension like MetaMask installed in your browser. +- Node.js and npm installed. +- To use the `dockerized local node` or `in memory local node` setups, you will need to run the respective services in Docker. For detailed instructions on setting up and running these nodes, refer to the [Documentation](https://docs.zksync.io/build/test-and-debug). + +## Installation +Install dependencies with `npm install`. + Run `npm run dev` in your terminal, and then open [localhost:3000](http://localhost:3000) in your browser. Once the webpage has loaded, changes made to files inside the `src/` directory (e.g. `src/pages/index.tsx`) will automatically update the webpage. diff --git a/templates/react/next-ethers/src/components/Balance.tsx b/templates/react/next-ethers/src/components/Balance.tsx index 5bc3de6..1e59ae3 100644 --- a/templates/react/next-ethers/src/components/Balance.tsx +++ b/templates/react/next-ethers/src/components/Balance.tsx @@ -1,6 +1,6 @@ -'use client' +'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { ethers } from 'ethers'; import { useAsync } from '../hooks/useAsync'; @@ -17,25 +17,30 @@ export function Balance() { - ) + ); } export function AccountBalance() { const { getProvider, account } = useEthereum(); - const { result: balance, execute: fetchBalance, error } = useAsync(address => getProvider()!.getBalance(address)); + + const fetchBalance = useCallback((address: string) => { + return getProvider()!.getBalance(address); + }, [getProvider]); + + const { result: balance, execute, error } = useAsync(fetchBalance); useEffect(() => { if (account?.address) { - fetchBalance(account.address); + execute(account.address); } - }, [account]); + }, [account, execute]); return (
Connected wallet balance: - {balance ? ethers.formatEther(balance) : ""} - + {balance ? ethers.formatEther(balance) : "0"} +
{error &&
Error: {error.message}
}
@@ -46,13 +51,13 @@ export function FindBalance() { const [address, setAddress] = useState(''); const { getProvider } = useEthereum(); - const fetchBalanceFunc = async (address: string) => { + const fetchBalanceFunc = useCallback(async (address: string) => { const provider = getProvider(); if (!provider) throw new Error("Provider not found"); return provider.getBalance(address); - }; + }, [getProvider]); - const { result: balance, execute: fetchBalance, inProgress, error } = useAsync(fetchBalanceFunc); + const { result: balance, execute, inProgress, error } = useAsync(fetchBalanceFunc); return (
@@ -64,11 +69,11 @@ export function FindBalance() { type="text" placeholder="wallet address" /> -
-
{balance ? ethers.formatEther(balance) : ""}
+
{balance ? ethers.formatEther(balance) : "0"}
{error &&
Error: {error.message}
} ); diff --git a/templates/react/next-ethers/src/components/BlockNumber.tsx b/templates/react/next-ethers/src/components/BlockNumber.tsx index 8fa1849..5e57c9c 100644 --- a/templates/react/next-ethers/src/components/BlockNumber.tsx +++ b/templates/react/next-ethers/src/components/BlockNumber.tsx @@ -1,32 +1,54 @@ 'use client' -import { useState, useEffect } from 'react'; - +import { useState, useEffect, useCallback } from 'react'; import { useEthereum } from './Context'; export function BlockNumber() { const { getProvider } = useEthereum(); const [blockNumber, setBlockNumber] = useState(null); + const [error, setError] = useState(null); - useEffect(() => { + const fetchAndSubscribeToBlockUpdates = useCallback(async () => { + setError(null); const provider = getProvider(); + if (!provider) { + setError("Provider not available"); + return () => {}; + } + + try { + const currentBlockNumber = await provider.getBlockNumber(); + setBlockNumber(BigInt(currentBlockNumber)); + + const onBlockHandler = (blockNumber: number) => { + setBlockNumber(BigInt(blockNumber)); + }; + + provider.on("block", onBlockHandler); + + return () => { + provider.off("block", onBlockHandler); + }; + } catch (err) { + setError(`Error: ${err instanceof Error ? err.message : String(err)}`); + return () => {}; + } + }, [getProvider]); - if (!provider) return; - - const onBlockHandler = (block: bigint) => { - setBlockNumber(block); - }; - - provider.on("block", onBlockHandler); + useEffect(() => { + const unsubscribe = fetchAndSubscribeToBlockUpdates(); - return () => { - provider.off("block", onBlockHandler); - }; - }, [getProvider]); + }, [fetchAndSubscribeToBlockUpdates]); return (
- {blockNumber?.toString()} + {error ? ( +
Error: {error}
+ ) : blockNumber === null ? ( +
Loading block number...
+ ) : ( +
{blockNumber.toString()}
+ )}
); } diff --git a/templates/react/next-ethers/src/components/Context.tsx b/templates/react/next-ethers/src/components/Context.tsx index e191fc6..4793170 100644 --- a/templates/react/next-ethers/src/components/Context.tsx +++ b/templates/react/next-ethers/src/components/Context.tsx @@ -1,7 +1,7 @@ 'use client'; import { JsonRpcSigner } from 'ethers'; -import { useState, useEffect, createContext, useContext } from 'react'; +import { useState, createContext, useContext } from 'react'; import { BrowserProvider } from 'zksync-ethers'; type Chain = { @@ -20,7 +20,7 @@ const zkSync: Chain = { const zkSyncSepoliaTestnet: Chain = { id: 300, name: "zkSync Sepolia Testnet", - rpcUrl: "https://rpc.ankr.com/eth_sepolia", + rpcUrl: "https://sepolia.era.zksync.dev", blockExplorerUrl: "https://sepolia.etherscan.io" } export const chains: Chain[] = [ @@ -63,6 +63,7 @@ const EthereumContext = createContext(null); export const EthereumProvider = ({ children }: { children: React.ReactNode }) => { const [account, setAccount] = useState<{ isConnected: true; address: string; } | { isConnected: false; address: null; }>({ isConnected: false, address: null }); const [network, setNetwork] = useState(null); + const [error, setError] = useState(null); const getEthereumContext = () => (window as any).ethereum; @@ -84,16 +85,19 @@ export const EthereumProvider = ({ children }: { children: React.ReactNode }) => } const connect = async () => { - if (!getEthereumContext()) throw new Error("No injected wallets found"); - - web3Provider = new BrowserProvider((window as any).ethereum, "any"); - const accounts = await web3Provider?.send("eth_requestAccounts", []); - if (accounts.length > 0) { - onAccountChange(accounts); - getEthereumContext()?.on("accountsChanged", onAccountChange); - web3Provider?.on("network", onNetworkChange); - } else { + try { + if (!getEthereumContext()) throw new Error("No injected wallets found"); + web3Provider = new BrowserProvider((window as any).ethereum, "any"); + const accounts = await web3Provider?.send("eth_requestAccounts", []); + if (accounts.length > 0) { + onAccountChange(accounts); + getEthereumContext()?.on("accountsChanged", onAccountChange); + web3Provider?.on("network", onNetworkChange); + } else { throw new Error("No accounts found"); + } + } catch (err: any) { + setError(err.message); } } @@ -107,14 +111,6 @@ export const EthereumProvider = ({ children }: { children: React.ReactNode }) => web3Provider?.off("network", onNetworkChange); } - useEffect(() => { - connect(); - - return () => { // Clean-up on component unmount - disconnect(); - } - }, []); - const switchNetwork = async (chainId: number) => { const chain = chains.find((chain: any) => chain.id === chainId); if (!chain) throw new Error("Unsupported chain"); @@ -159,6 +155,7 @@ export const EthereumProvider = ({ children }: { children: React.ReactNode }) => getSigner }}> {children} + {error &&
Error: {error}
} ); } diff --git a/templates/react/next-ethers/src/components/ReadContract.tsx b/templates/react/next-ethers/src/components/ReadContract.tsx index 920255c..288cff7 100644 --- a/templates/react/next-ethers/src/components/ReadContract.tsx +++ b/templates/react/next-ethers/src/components/ReadContract.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect } from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { Contract } from 'zksync-ethers'; import { useAsync } from '../hooks/useAsync'; @@ -21,28 +21,31 @@ export function ReadContract() { function TotalSupply() { const { getProvider } = useEthereum(); - const { - result: supply, - execute: fetchTotalSupply, - inProgress, - error, - } = useAsync(async () => { + + const fetchTotalSupply = useCallback(async () => { const provider = getProvider(); if (!provider) throw new Error("Provider is not available"); const contract = new Contract(daiContractConfig.address, daiContractConfig.abi, provider); return await contract.totalSupply(); - }); + }, [getProvider]); + + const { + result: supply, + execute: executeFetchTotalSupply, + inProgress, + error, + } = useAsync(fetchTotalSupply); useEffect(() => { - fetchTotalSupply(); - }, []); + executeFetchTotalSupply(); + }, [executeFetchTotalSupply]); return (
Total Supply: {supply?.toString()} -
@@ -52,25 +55,37 @@ function TotalSupply() { } function BalanceOf() { - const { getProvider } = useEthereum(); - const { account } = useEthereum(); - + const { getProvider, account } = useEthereum(); const [address, setAddress] = useState(account.address); + const fetchBalance = useCallback(async () => { + if (!address) throw new Error("Address is not set"); + const provider = getProvider(); + if (!provider) throw new Error("Provider is not available"); + + const contract = new Contract(daiContractConfig.address, daiContractConfig.abi, provider); + return await contract.balanceOf(address); + }, [getProvider, address]); + const { result: balance, - execute: fetchBalance, + execute: executeFetchBalance, inProgress, error - } = useAsync(async () => { - const contract = new Contract(daiContractConfig.address, daiContractConfig.abi, getProvider()!); - return contract.balanceOf(address); - }); + } = useAsync(fetchBalance); useEffect(() => { - fetchBalance(); + executeFetchBalance(); + }, [executeFetchBalance]); + + const handleAddressChange = useCallback((e: React.ChangeEvent) => { + setAddress(e.target.value); }, []); + const handleFetchBalance = useCallback(() => { + executeFetchBalance(); + }, [executeFetchBalance]); + return (
@@ -78,12 +93,12 @@ function BalanceOf() {
setAddress(e.target.value)} + value={address || ""} + onChange={handleAddressChange} type="text" placeholder="wallet address" /> -
diff --git a/templates/react/next-ethers/src/components/SendTransactionPrepared.tsx b/templates/react/next-ethers/src/components/SendTransactionPrepared.tsx index 2c3b355..830005b 100644 --- a/templates/react/next-ethers/src/components/SendTransactionPrepared.tsx +++ b/templates/react/next-ethers/src/components/SendTransactionPrepared.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState, useEffect } from 'react'; +import { useState, useCallback } from 'react'; import { ethers } from 'ethers'; import { useAsync } from '../hooks/useAsync'; @@ -11,52 +11,62 @@ export function SendTransactionPrepared() { const [address, setAddress] = useState(null); const [value, setValue] = useState(null); - - const { result: preparedTransaction, execute: prepareTransaction, inProgress: prepareInProgress, error: prepareError } = useAsync(async () => { - if (!address || !value) return; + + const prepareAndSendTransaction = useCallback(async () => { + if (!address || !value) return null; + + const signer = await getSigner(); + if (!signer) throw new Error("Signer not available"); const transaction = { to: address, value: ethers.parseEther(value), }; + const gasPrice = await getProvider()!.getGasPrice(); - const gasLimit = await (await getSigner())!.estimateGas({ + const gasLimit = await signer.estimateGas({ ...transaction, gasPrice, }); - return { + const preparedTransaction = { ...transaction, gasPrice, gasLimit, }; - }); - const { result: transaction, execute: sendTransaction, inProgress, error } = useAsync(async () => { - const result = await (await getSigner())!.sendTransaction(preparedTransaction!); - waitForReceipt(result.hash); + const result = await signer.sendTransaction(preparedTransaction); return result; - }); + }, [address, value, getSigner, getProvider]); + + const { result: transaction, execute: executePrepareAndSendTransaction, inProgress, error } = useAsync(prepareAndSendTransaction); const { result: receipt, execute: waitForReceipt, inProgress: receiptInProgress, error: receiptError } = useAsync(async (transactionHash: string) => { return await getProvider()!.waitForTransaction(transactionHash); }); - useEffect(() => { - if (address && value) { - prepareTransaction(); + const handleSubmit = useCallback(async (e: React.FormEvent) => { + e.preventDefault(); + const result = await executePrepareAndSendTransaction(); + if (result) { + waitForReceipt(result.hash); } - }, [address, value]); + }, [executePrepareAndSendTransaction, waitForReceipt]); return (
-
{ - e.preventDefault(); - sendTransaction(); - }}> - setAddress(e.target.value)} placeholder="address" /> - setValue(e.target.value)} placeholder="value (ether)" /> - + + setAddress(e.target.value)} + placeholder="address" + /> + setValue(e.target.value)} + placeholder="value (ether)" + /> +
{inProgress &&
Transaction pending...
} @@ -74,7 +84,6 @@ export function SendTransactionPrepared() {
)} - {prepareError &&
Preparing Transaction Error: {prepareError?.message}
} {error &&
Error: {error?.message}
} {receiptError &&
Receipt Error: {receiptError?.message}
}
diff --git a/templates/react/next-ethers/src/components/WatchContractEvents.tsx b/templates/react/next-ethers/src/components/WatchContractEvents.tsx index 0728f7f..c755287 100644 --- a/templates/react/next-ethers/src/components/WatchContractEvents.tsx +++ b/templates/react/next-ethers/src/components/WatchContractEvents.tsx @@ -30,7 +30,7 @@ export function WatchContractEvents() { return () => { contract.off('Transfer', handleTransfer); }; - }, []); + }, [getProvider]); const logs = events .slice() diff --git a/templates/react/next-ethers/src/components/WatchPendingTransactions.tsx b/templates/react/next-ethers/src/components/WatchPendingTransactions.tsx index 4df59c9..953d271 100644 --- a/templates/react/next-ethers/src/components/WatchPendingTransactions.tsx +++ b/templates/react/next-ethers/src/components/WatchPendingTransactions.tsx @@ -27,7 +27,7 @@ export function WatchPendingTransactions() { provider.off("block", onBlock); } }; - }, []); + }, [getProvider]); return (
diff --git a/templates/react/next-ethers/src/components/WriteContractPrepared.tsx b/templates/react/next-ethers/src/components/WriteContractPrepared.tsx index ca074cd..48a4104 100644 --- a/templates/react/next-ethers/src/components/WriteContractPrepared.tsx +++ b/templates/react/next-ethers/src/components/WriteContractPrepared.tsx @@ -1,8 +1,7 @@ 'use client' -import { useState, useEffect } from 'react'; +import { useState, useCallback } from 'react'; import { Contract } from 'zksync-ethers'; - import { useAsync } from '../hooks/useAsync'; import { daiContractConfig } from './contracts'; import { useEthereum } from './Context'; @@ -11,11 +10,13 @@ export function WriteContractPrepared() { const [amount, setAmount] = useState(null); const { getSigner, getProvider } = useEthereum(); - const getContractInstance = async () => { + const getContractInstance = useCallback(async () => { return new Contract(daiContractConfig.address, daiContractConfig.abi, await getSigner()!); - } + }, [getSigner]); + + const prepareAndSendTransaction = useCallback(async () => { + if (!amount) return null; - const { result: preparedTransaction, execute: prepareTransaction, inProgress: prepareInProgress, error: prepareError } = useAsync(async () => { const contract = await getContractInstance(); // random address for testing, replace with contract address that you want to allow to spend your tokens @@ -24,41 +25,42 @@ export function WriteContractPrepared() { const gasPrice = await getProvider()!.getGasPrice(); const gasLimit = await contract.getFunction("approve").estimateGas(spender, amount); - return { + const preparedTransaction = { args: [spender, amount], overrides: { gasPrice, gasLimit } }; - }); - const { result: transaction, execute: sendTransaction, inProgress, error } = useAsync(async () => { - const contract = await getContractInstance(); - const result = await contract.approve(...preparedTransaction!.args, preparedTransaction!.overrides); - waitForReceipt(result.hash); + const result = await contract.approve(...preparedTransaction.args, preparedTransaction.overrides); return result; - }); + }, [amount, getContractInstance, getProvider]); + + const { result: transaction, execute: executePrepareAndSendTransaction, inProgress, error } = useAsync(prepareAndSendTransaction); - const { result: receipt, execute: waitForReceipt, inProgress: receiptInProgress, error: receiptError } = useAsync(async (transactionHash) => { + const { result: receipt, execute: waitForReceipt, inProgress: receiptInProgress, error: receiptError } = useAsync(async (transactionHash: string) => { return await getProvider()!.waitForTransaction(transactionHash); }); - useEffect(() => { - if (!amount) return; - prepareTransaction(); - }, [amount]); - - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = useCallback(async (e: React.FormEvent) => { e.preventDefault(); - sendTransaction(); - }; + const result = await executePrepareAndSendTransaction(); + if (result) { + waitForReceipt(result.hash); + } + }, [executePrepareAndSendTransaction, waitForReceipt]); return (
- setAmount(e.target.value)} type="number" placeholder="allowance amount" /> - + setAmount(e.target.value)} + type="number" + placeholder="allowance amount" + /> +
{inProgress ? ( @@ -79,7 +81,6 @@ export function WriteContractPrepared() { ) )} - {prepareError &&
Preparing Transaction Error: {prepareError.message}
} {error &&
Error: {error.message}
} {receiptError &&
Receipt Error: {receiptError.message}
}
diff --git a/templates/react/next-ethers5/README.md b/templates/react/next-ethers5/README.md index 9e110e2..7c41c61 100644 --- a/templates/react/next-ethers5/README.md +++ b/templates/react/next-ethers5/README.md @@ -2,6 +2,14 @@ This is a [zkSync](https://zksync.io) + [ethers v5](https://docs.ethers.org/v5/) # Getting Started +## Requirements +- A wallet extension like MetaMask installed in your browser. +- Node.js and npm installed. +- To use the `dockerized local node` or `in memory local node` setups, you will need to run the respective services in Docker. For detailed instructions on setting up and running these nodes, refer to the [Documentation](https://docs.zksync.io/build/test-and-debug). + +## Installation +Install dependencies with `npm install`. + Run `npm run dev` in your terminal, and then open [localhost:3000](http://localhost:3000) in your browser. Once the webpage has loaded, changes made to files inside the `src/` directory (e.g. `src/pages/index.tsx`) will automatically update the webpage. diff --git a/templates/react/next-ethers5/src/components/Balance.tsx b/templates/react/next-ethers5/src/components/Balance.tsx index 4bbb561..9373437 100644 --- a/templates/react/next-ethers5/src/components/Balance.tsx +++ b/templates/react/next-ethers5/src/components/Balance.tsx @@ -1,7 +1,7 @@ -'use client' +'use client'; -import { useState, useEffect } from 'react'; -import { formatEther } from 'ethers/lib/utils'; +import { useState, useEffect, useCallback } from 'react'; +import { ethers } from 'ethers'; import { useAsync } from '../hooks/useAsync'; import { useEthereum } from './Context'; @@ -17,25 +17,31 @@ export function Balance() {
- ) + ); } export function AccountBalance() { const { getProvider, account } = useEthereum(); - const { result: balance, execute: fetchBalance, error } = useAsync(address => getProvider()!.getBalance(address)); + + // Memoize the fetchBalance function + const fetchBalance = useCallback((address: string) => { + return getProvider()!.getBalance(address); + }, [getProvider]); + + const { result: balance, execute, error } = useAsync(fetchBalance); useEffect(() => { if (account?.address) { - fetchBalance(account.address); + execute(account.address); } - }, [account]); + }, [account, execute]); return (
Connected wallet balance: - {balance ? formatEther(balance) : ""} - + {balance ? ethers.utils.formatEther(balance) : "0"} +
{error &&
Error: {error.message}
}
@@ -46,13 +52,13 @@ export function FindBalance() { const [address, setAddress] = useState(''); const { getProvider } = useEthereum(); - const fetchBalanceFunc = async (address: string) => { + const fetchBalanceFunc = useCallback(async (address: string) => { const provider = getProvider(); if (!provider) throw new Error("Provider not found"); return provider.getBalance(address); - }; + }, [getProvider]); - const { result: balance, execute: fetchBalance, inProgress, error } = useAsync(fetchBalanceFunc); + const { result: balance, execute, inProgress, error } = useAsync(fetchBalanceFunc); return (
@@ -64,11 +70,11 @@ export function FindBalance() { type="text" placeholder="wallet address" /> -
-
{balance ? formatEther(balance) : ""}
+
{balance ? ethers.utils.formatEther(balance) : "0"}
{error &&
Error: {error.message}
}
); diff --git a/templates/react/next-ethers5/src/components/Context.tsx b/templates/react/next-ethers5/src/components/Context.tsx index 815b591..5a5896a 100644 --- a/templates/react/next-ethers5/src/components/Context.tsx +++ b/templates/react/next-ethers5/src/components/Context.tsx @@ -1,6 +1,6 @@ 'use client'; -import React, { useState, useEffect, createContext, useContext } from 'react'; +import React, { useState, createContext, useContext } from 'react'; import { Signer, Web3Provider } from 'zksync-ethers'; type Chain = { @@ -19,19 +19,12 @@ const zkSync: Chain = { const zkSyncSepoliaTestnet: Chain = { id: 300, name: "zkSync Sepolia Testnet", - rpcUrl: "https://rpc.ankr.com/eth_sepolia", + rpcUrl: "https://sepolia.era.zksync.dev", blockExplorerUrl: "https://sepolia.etherscan.io" } -const zkSyncGoerliTestnet: Chain = { - id: 280, - name: "zkSync Goerli Testnet", - rpcUrl: "https://testnet.era.zksync.dev", - blockExplorerUrl: "https://goerli.explorer.zksync.io" -} export const chains: Chain[] = [ zkSync, zkSyncSepoliaTestnet, - zkSyncGoerliTestnet, ...( process.env.NODE_ENV === "development" ? [ @@ -69,6 +62,7 @@ const EthereumContext = createContext(null); export const EthereumProvider = ({ children }: { children: React.ReactNode }) => { const [account, setAccount] = useState<{ isConnected: true; address: string; } | { isConnected: false; address: null; }>({ isConnected: false, address: null }); const [network, setNetwork] = useState(null); + const [error, setError] = useState(null); const getEthereumContext = () => (window as any).ethereum; @@ -89,18 +83,22 @@ export const EthereumProvider = ({ children }: { children: React.ReactNode }) => } const connect = async () => { - if (!getEthereumContext()) throw new Error("No injected wallets found"); - - web3Provider = new Web3Provider((window as any).ethereum, "any"); - const accounts = await web3Provider?.send("eth_requestAccounts", []); - if (accounts.length > 0) { - onAccountChange(accounts); - getEthereumContext()?.on("accountsChanged", onAccountChange); - web3Provider?.on("network", onNetworkChange); - } else { - throw new Error("No accounts found"); - } + try { + if (!getEthereumContext()) throw new Error("No injected wallets found"); + + web3Provider = new Web3Provider((window as any).ethereum, "any"); + const accounts = await web3Provider?.send("eth_requestAccounts", []); + if (accounts.length > 0) { + onAccountChange(accounts); + getEthereumContext()?.on("accountsChanged", onAccountChange); + web3Provider?.on("network", onNetworkChange); + } else { + throw new Error("No accounts found"); + } + } catch (err: any) { + setError(err.message); } +} const disconnect = () => { setAccount({ @@ -112,14 +110,6 @@ export const EthereumProvider = ({ children }: { children: React.ReactNode }) => web3Provider?.off("network", onNetworkChange); } - useEffect(() => { - connect(); - - return () => { // Clean-up on component unmount - disconnect(); - } - }, []); - const switchNetwork = async (chainId: number) => { const chain = chains.find((chain: any) => chain.id === chainId); if (!chain) throw new Error("Unsupported chain"); @@ -164,6 +154,7 @@ export const EthereumProvider = ({ children }: { children: React.ReactNode }) => getSigner }}> {children} + {error &&
Error: {error}
} ); } @@ -174,4 +165,4 @@ export const useEthereum = () => { throw new Error("useEthereum must be used within an EthereumProvider"); } return context; -} \ No newline at end of file +} diff --git a/templates/react/next-ethers5/src/components/ReadContract.tsx b/templates/react/next-ethers5/src/components/ReadContract.tsx index 920255c..8c1c6e5 100644 --- a/templates/react/next-ethers5/src/components/ReadContract.tsx +++ b/templates/react/next-ethers5/src/components/ReadContract.tsx @@ -1,11 +1,11 @@ -'use client' +'use client'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useCallback } from 'react'; import { Contract } from 'zksync-ethers'; import { useAsync } from '../hooks/useAsync'; import { useEthereum } from './Context'; -import { daiContractConfig } from './contracts' +import { daiContractConfig } from './contracts'; export function ReadContract() { return ( @@ -16,33 +16,31 @@ export function ReadContract() { - ) + ); } function TotalSupply() { const { getProvider } = useEthereum(); - const { - result: supply, - execute: fetchTotalSupply, - inProgress, - error, - } = useAsync(async () => { + + const fetchTotalSupply = useCallback(async () => { const provider = getProvider(); if (!provider) throw new Error("Provider is not available"); const contract = new Contract(daiContractConfig.address, daiContractConfig.abi, provider); return await contract.totalSupply(); - }); + }, [getProvider]); + + const { result: supply, execute, inProgress, error } = useAsync(fetchTotalSupply); useEffect(() => { - fetchTotalSupply(); - }, []); + execute(); + }, [execute]); return (
Total Supply: {supply?.toString()} -
@@ -52,24 +50,19 @@ function TotalSupply() { } function BalanceOf() { - const { getProvider } = useEthereum(); - const { account } = useEthereum(); - + const { getProvider, account } = useEthereum(); const [address, setAddress] = useState(account.address); - const { - result: balance, - execute: fetchBalance, - inProgress, - error - } = useAsync(async () => { + const fetchBalance = useCallback(async () => { const contract = new Contract(daiContractConfig.address, daiContractConfig.abi, getProvider()!); return contract.balanceOf(address); - }); + }, [getProvider, address]); + + const { result: balance, execute, inProgress, error } = useAsync(fetchBalance); useEffect(() => { - fetchBalance(); - }, []); + execute(); + }, [execute]); return (
@@ -78,12 +71,12 @@ function BalanceOf() {
setAddress(e.target.value)} type="text" placeholder="wallet address" /> -
diff --git a/templates/react/next-ethers5/src/components/SendTransactionPrepared.tsx b/templates/react/next-ethers5/src/components/SendTransactionPrepared.tsx index 4110674..6a46e4d 100644 --- a/templates/react/next-ethers5/src/components/SendTransactionPrepared.tsx +++ b/templates/react/next-ethers5/src/components/SendTransactionPrepared.tsx @@ -1,7 +1,7 @@ -'use client' +'use client'; -import { useState, useEffect } from 'react'; -import { parseEther } from 'ethers/lib/utils'; +import { useState, useCallback } from 'react'; +import { ethers } from 'ethers'; import { useAsync } from '../hooks/useAsync'; import { useEthereum } from './Context'; @@ -9,60 +9,90 @@ import { useEthereum } from './Context'; export function SendTransactionPrepared() { const { getSigner, getProvider } = useEthereum(); - const [address, setAddress] = useState(null); - const [value, setValue] = useState(null); - - const { result: preparedTransaction, execute: prepareTransaction, inProgress: prepareInProgress, error: prepareError } = useAsync(async () => { - if (!address || !value) return; + const [address, setAddress] = useState(''); + const [value, setValue] = useState(''); + + const prepareTransaction = useCallback(async () => { + if (!address || !value) return null; + + const signer = await getSigner(); + if (!signer) throw new Error("Signer not available"); + + const provider = getProvider(); + if (!provider) throw new Error("Provider not available"); const transaction = { to: address, - value: parseEther(value), + value: ethers.utils.parseEther(value), }; - const gasPrice = await getProvider()!.getGasPrice(); - const gasLimit = await getSigner()!.estimateGas({ + + const gasPrice = await provider.getGasPrice(); + console.log("Gas Price:", gasPrice.toString()); + + const gasLimit = await signer.estimateGas({ ...transaction, gasPrice, }); + console.log("Gas Limit:", gasLimit.toString()); - return { + const preparedTransaction = { ...transaction, gasPrice, gasLimit, }; - }); + console.log("Prepared Transaction:", preparedTransaction); - const { result: transaction, execute: sendTransaction, inProgress, error } = useAsync(async () => { - const result = await getSigner()!.sendTransaction(preparedTransaction!); - waitForReceipt(result.hash); + return preparedTransaction; + }, [address, value, getSigner, getProvider]); + + const { result: preparedTransaction, execute: executePrepareTransaction, inProgress: prepareInProgress, error: prepareError } = useAsync(prepareTransaction); + + const sendTransaction = useCallback(async () => { + if (!preparedTransaction) return; + const signer = await getSigner(); + if (!signer) throw new Error("Signer not available"); + + const result = await signer.sendTransaction(preparedTransaction); return result; - }); + }, [preparedTransaction, getSigner]); + + const { result: transaction, execute: executeSendTransaction, inProgress: sendInProgress, error: sendError } = useAsync(sendTransaction); const { result: receipt, execute: waitForReceipt, inProgress: receiptInProgress, error: receiptError } = useAsync(async (transactionHash: string) => { - return await getProvider()!.waitForTransaction(transactionHash); + const provider = getProvider(); + if (!provider) throw new Error("Provider not available"); + + return await provider.waitForTransaction(transactionHash); }); - useEffect(() => { - if (address && value) { - prepareTransaction(); + const handleSubmit = useCallback(async (e: React.FormEvent) => { + e.preventDefault(); + const result = await executeSendTransaction(); + if (result) { + waitForReceipt(result.hash); } - }, [address, value]); + }, [executeSendTransaction, waitForReceipt]); return (
-
{ - e.preventDefault(); - sendTransaction(); - }}> - setAddress(e.target.value)} placeholder="address" /> - setValue(e.target.value)} placeholder="value (ether)" /> - + + setAddress(e.target.value)} + placeholder="address" + /> + setValue(e.target.value)} + placeholder="value (ether)" + /> +
- {inProgress &&
Transaction pending...
} + {(prepareInProgress || sendInProgress) &&
Transaction pending...
} {transaction && (
-
Transaction Hash: {transaction?.hash}
+
Transaction Hash: {transaction.hash}
Transaction Receipt: {receiptInProgress ? ( @@ -74,9 +104,9 @@ export function SendTransactionPrepared() {
)} - {prepareError &&
Preparing Transaction Error: {prepareError?.message}
} - {error &&
Error: {error?.message}
} - {receiptError &&
Receipt Error: {receiptError?.message}
} + {prepareError &&
Preparing Transaction Error: {prepareError.message}
} + {sendError &&
Sending Transaction Error: {sendError.message}
} + {receiptError &&
Receipt Error: {receiptError.message}
}
); } diff --git a/templates/react/next-ethers5/src/components/WatchContractEvents.tsx b/templates/react/next-ethers5/src/components/WatchContractEvents.tsx index 2cb8daa..55608c4 100644 --- a/templates/react/next-ethers5/src/components/WatchContractEvents.tsx +++ b/templates/react/next-ethers5/src/components/WatchContractEvents.tsx @@ -31,7 +31,7 @@ export function WatchContractEvents() { return () => { contract.off('Transfer', handleTransfer); }; - }, []); + }, [getProvider]); const logs = events .slice() diff --git a/templates/react/next-ethers5/src/components/WatchPendingTransactions.tsx b/templates/react/next-ethers5/src/components/WatchPendingTransactions.tsx index 4df59c9..953d271 100644 --- a/templates/react/next-ethers5/src/components/WatchPendingTransactions.tsx +++ b/templates/react/next-ethers5/src/components/WatchPendingTransactions.tsx @@ -27,7 +27,7 @@ export function WatchPendingTransactions() { provider.off("block", onBlock); } }; - }, []); + }, [getProvider]); return (
diff --git a/templates/react/next-ethers5/src/components/WriteContractPrepared.tsx b/templates/react/next-ethers5/src/components/WriteContractPrepared.tsx index cdde3af..d5fa912 100644 --- a/templates/react/next-ethers5/src/components/WriteContractPrepared.tsx +++ b/templates/react/next-ethers5/src/components/WriteContractPrepared.tsx @@ -1,8 +1,7 @@ -'use client' +'use client'; -import { useState, useEffect } from 'react'; +import { useState, useCallback, useEffect } from 'react'; import { Contract } from 'zksync-ethers'; - import { useAsync } from '../hooks/useAsync'; import { daiContractConfig } from './contracts'; import { useEthereum } from './Context'; @@ -11,17 +10,22 @@ export function WriteContractPrepared() { const [amount, setAmount] = useState(null); const { getSigner, getProvider } = useEthereum(); - const getContractInstance = () => { - return new Contract(daiContractConfig.address, daiContractConfig.abi, getSigner()!); - } + const getContractInstance = useCallback(() => { + const signer = getSigner(); + if (!signer) throw new Error("Signer not available"); + return new Contract(daiContractConfig.address, daiContractConfig.abi, signer); + }, [getSigner]); - const { result: preparedTransaction, execute: prepareTransaction, inProgress: prepareInProgress, error: prepareError } = useAsync(async () => { + const prepareTransaction = useCallback(async () => { const contract = getContractInstance(); // random address for testing, replace with contract address that you want to allow to spend your tokens - const spender = "0xa1cf087DB965Ab02Fb3CFaCe1f5c63935815f044" + const spender = "0xa1cf087DB965Ab02Fb3CFaCe1f5c63935815f044"; + + const provider = getProvider(); + if (!provider) throw new Error("Provider not available"); - const gasPrice = await getProvider()!.getGasPrice(); + const gasPrice = await provider.getGasPrice(); const gasLimit = await contract.estimateGas.approve(spender, amount); return { @@ -31,28 +35,40 @@ export function WriteContractPrepared() { gasLimit } }; + }, [amount, getContractInstance, getProvider]); + + const { result: preparedTransaction, execute: executePrepareTransaction, inProgress: prepareInProgress, error: prepareError } = useAsync(prepareTransaction); + + const { result: receipt, execute: waitForReceipt, inProgress: receiptInProgress, error: receiptError } = useAsync(async (transactionHash: string) => { + const provider = getProvider(); + if (!provider) throw new Error("Provider not available"); + + return await provider.waitForTransaction(transactionHash); }); - const { result: transaction, execute: sendTransaction, inProgress, error } = useAsync(async () => { + const sendTransaction = useCallback(async () => { + if (!preparedTransaction) throw new Error("Prepared transaction is not available"); + const contract = getContractInstance(); - const result = await contract.approve(...preparedTransaction!.args, preparedTransaction!.overrides); + const result = await contract.approve(...preparedTransaction.args, preparedTransaction.overrides); waitForReceipt(result.hash); return result; - }); + }, [getContractInstance, preparedTransaction, waitForReceipt]); - const { result: receipt, execute: waitForReceipt, inProgress: receiptInProgress, error: receiptError } = useAsync(async (transactionHash) => { - return await getProvider()!.waitForTransaction(transactionHash); - }); + const { result: transaction, execute: executeSendTransaction, inProgress, error } = useAsync(sendTransaction); useEffect(() => { if (!amount) return; - prepareTransaction(); - }, [amount]); + executePrepareTransaction(); + }, [amount, executePrepareTransaction]); - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = useCallback(async (e: React.FormEvent) => { e.preventDefault(); - sendTransaction(); - }; + const result = await executeSendTransaction(); + if (result) { + waitForReceipt(result.hash); + } + }, [executeSendTransaction, waitForReceipt]); return (
diff --git a/templates/react/next-wagmi-rainbowkit/README.md b/templates/react/next-wagmi-rainbowkit/README.md index ad4a2f3..3713e63 100644 --- a/templates/react/next-wagmi-rainbowkit/README.md +++ b/templates/react/next-wagmi-rainbowkit/README.md @@ -2,6 +2,24 @@ This is a [zkSync](https://zksync.io) + [wagmi](https://wagmi.sh) + [RainbowKit] # Getting Started +## Requirements +- A wallet extension like MetaMask installed in your browser. +- A WalletConnect project ID to connect. +- Node.js and npm installed. +- To use the `dockerized local node` or `in memory local node` setups, you will need to run the respective services in Docker. For detailed instructions on setting up and running these nodes, refer to the [Documentation](https://docs.zksync.io/build/test-and-debug). + +## Setup + +1. Copy the `.env.example` file to `.env`: + +```bash +cp .env.example .env +``` +2. Update the .env file with your WalletConnect project ID. + +## Installation +Install dependencies with `npm install`. + Run `npm run dev` in your terminal, and then open [localhost:3000](http://localhost:3000) in your browser. Once the webpage has loaded, changes made to files inside the `src/` directory (e.g. `src/pages/index.tsx`) will automatically update the webpage. diff --git a/templates/react/next-wagmi-web3modal/README.md b/templates/react/next-wagmi-web3modal/README.md index ce68878..225bbfc 100644 --- a/templates/react/next-wagmi-web3modal/README.md +++ b/templates/react/next-wagmi-web3modal/README.md @@ -2,6 +2,24 @@ This is a [zkSync](https://zksync.io) + [wagmi](https://wagmi.sh) + [Web3Modal]( # Getting Started +## Requirements +- A wallet extension like MetaMask installed in your browser. +- A WalletConnect project ID to connect. +- Node.js and npm installed. +- To use the `dockerized local node` or `in memory local node` setups, you will need to run the respective services in Docker. For detailed instructions on setting up and running these nodes, refer to the [Documentation](https://docs.zksync.io/build/test-and-debug). + +## Setup + +1. Copy the `.env.example` file to `.env`: + +```bash +cp .env.example .env +``` +2. Update the .env file with your WalletConnect project ID. + +## Installation +Install dependencies with `npm install`. + Run `npm run dev` in your terminal, and then open [localhost:3000](http://localhost:3000) in your browser. Once the webpage has loaded, changes made to files inside the `src/` directory (e.g. `src/pages/index.tsx`) will automatically update the webpage. diff --git a/templates/react/next-wagmi/README.md b/templates/react/next-wagmi/README.md index c96b709..09c2b79 100644 --- a/templates/react/next-wagmi/README.md +++ b/templates/react/next-wagmi/README.md @@ -2,6 +2,14 @@ This is a [zkSync](https://zksync.io) + [wagmi](https://wagmi.sh) + [Next.js](ht # Getting Started +## Requirements +- A wallet extension like MetaMask installed in your browser. +- Node.js and npm installed. +- To use the `dockerized local node` or `in memory local node` setups, you will need to run the respective services in Docker. For detailed instructions on setting up and running these nodes, refer to the [Documentation](https://docs.zksync.io/build/test-and-debug). + +## Installation +Install dependencies with `npm install`. + Run `npm run dev` in your terminal, and then open [localhost:3000](http://localhost:3000) in your browser. Once the webpage has loaded, changes made to files inside the `src/` directory (e.g. `src/pages/index.tsx`) will automatically update the webpage. diff --git a/templates/react/vite-ethers/README.md b/templates/react/vite-ethers/README.md index aa3220c..43084dd 100644 --- a/templates/react/vite-ethers/README.md +++ b/templates/react/vite-ethers/README.md @@ -2,6 +2,14 @@ This is a [zkSync](https://zksync.io) + [ethers](https://docs.ethers.org/v6/) + # Getting Started +## Requirements +- A wallet extension like MetaMask installed in your browser. +- Node.js and npm installed. +- To use the `dockerized local node` or `in memory local node` setups, you will need to run the respective services in Docker. For detailed instructions on setting up and running these nodes, refer to the [Documentation](https://docs.zksync.io/build/test-and-debug). + +## Installation +Install dependencies with `npm install`. + Run `npm run dev` in your terminal, and then open [localhost:5173](http://localhost:5173) in your browser. Once the webpage has loaded, changes made to files inside the `src/` directory (e.g. `src/App.tsx`) will automatically update the webpage. diff --git a/templates/react/vite-ethers/src/components/Context.tsx b/templates/react/vite-ethers/src/components/Context.tsx index 39ee791..7eee04d 100644 --- a/templates/react/vite-ethers/src/components/Context.tsx +++ b/templates/react/vite-ethers/src/components/Context.tsx @@ -20,7 +20,7 @@ const zkSync: Chain = { const zkSyncSepoliaTestnet: Chain = { id: 300, name: "zkSync Sepolia Testnet", - rpcUrl: "https://rpc.ankr.com/eth_sepolia", + rpcUrl: "https://sepolia.era.zksync.dev", blockExplorerUrl: "https://sepolia.etherscan.io" } export const chains: Chain[] = [ @@ -169,4 +169,4 @@ export const useEthereum = () => { throw new Error("useEthereum must be used within an EthereumProvider"); } return context; -} \ No newline at end of file +} diff --git a/templates/react/vite-ethers5/README.md b/templates/react/vite-ethers5/README.md index 19415c9..7a2a384 100644 --- a/templates/react/vite-ethers5/README.md +++ b/templates/react/vite-ethers5/README.md @@ -2,6 +2,14 @@ This is a [zkSync](https://zksync.io) + [ethers v5](https://docs.ethers.org/v5/) # Getting Started +## Requirements +- A wallet extension like MetaMask installed in your browser. +- Node.js and npm installed. +- To use the `dockerized local node` or `in memory local node` setups, you will need to run the respective services in Docker. For detailed instructions on setting up and running these nodes, refer to the [Documentation](https://docs.zksync.io/build/test-and-debug). + +## Installation +Install dependencies with `npm install`. + Run `npm run dev` in your terminal, and then open [localhost:5173](http://localhost:5173) in your browser. Once the webpage has loaded, changes made to files inside the `src/` directory (e.g. `src/App.tsx`) will automatically update the webpage. diff --git a/templates/react/vite-ethers5/src/components/Context.tsx b/templates/react/vite-ethers5/src/components/Context.tsx index 3f7923f..5d43a99 100644 --- a/templates/react/vite-ethers5/src/components/Context.tsx +++ b/templates/react/vite-ethers5/src/components/Context.tsx @@ -17,7 +17,7 @@ const zkSync: Chain = { const zkSyncSepoliaTestnet: Chain = { id: 300, name: "zkSync Sepolia Testnet", - rpcUrl: "https://rpc.ankr.com/eth_sepolia", + rpcUrl: "https://sepolia.era.zksync.dev", blockExplorerUrl: "https://sepolia.etherscan.io" } export const chains: Chain[] = [ @@ -165,4 +165,4 @@ export const useEthereum = () => { throw new Error("useEthereum must be used within an EthereumProvider"); } return context; -} \ No newline at end of file +} diff --git a/templates/react/vite-wagmi-web3modal/README.md b/templates/react/vite-wagmi-web3modal/README.md index efb849a..0efb778 100644 --- a/templates/react/vite-wagmi-web3modal/README.md +++ b/templates/react/vite-wagmi-web3modal/README.md @@ -2,6 +2,24 @@ This is a [zkSync](https://zksync.io) + [wagmi](https://wagmi.sh) + [Web3Modal]( # Getting Started +## Requirements +- A wallet extension like MetaMask installed in your browser. +- A WalletConnect project ID to connect. +- Node.js and npm installed. +- To use the `dockerized local node` or `in memory local node` setups, you will need to run the respective services in Docker. For detailed instructions on setting up and running these nodes, refer to the [Documentation](https://docs.zksync.io/build/test-and-debug). + +## Setup + +1. Copy the `.env.example` file to `.env`: + +```bash +cp .env.example .env +``` +2. Update the .env file with your WalletConnect project ID. + +## Installation +Install dependencies with `npm install`. + Run `npm run dev` in your terminal, and then open [localhost:5173](http://localhost:5173) in your browser. Once the webpage has loaded, changes made to files inside the `src/` directory (e.g. `src/App.tsx`) will automatically update the webpage. diff --git a/templates/react/vite-wagmi/README.md b/templates/react/vite-wagmi/README.md index 78fe565..148adc3 100644 --- a/templates/react/vite-wagmi/README.md +++ b/templates/react/vite-wagmi/README.md @@ -2,6 +2,14 @@ This is a [zkSync](https://zksync.io) + [wagmi](https://wagmi.sh) + [Vite](https # Getting Started +## Requirements +- A wallet extension like MetaMask installed in your browser. +- Node.js and npm installed. +- To use the `dockerized local node` or `in memory local node` setups, you will need to run the respective services in Docker. For detailed instructions on setting up and running these nodes, refer to the [Documentation](https://docs.zksync.io/build/test-and-debug). + +## Installation +Install dependencies with `npm install`. + Run `npm run dev` in your terminal, and then open [localhost:5173](http://localhost:5173) in your browser. Once the webpage has loaded, changes made to files inside the `src/` directory (e.g. `src/App.tsx`) will automatically update the webpage.