Skip to content

Commit

Permalink
update from review
Browse files Browse the repository at this point in the history
  • Loading branch information
jennyg0 committed Aug 29, 2024
1 parent 48841e4 commit 3d9b487
Show file tree
Hide file tree
Showing 26 changed files with 437 additions and 250 deletions.
8 changes: 8 additions & 0 deletions templates/react/next-ethers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
31 changes: 18 additions & 13 deletions templates/react/next-ethers/src/components/Balance.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -17,25 +17,30 @@ export function Balance() {
<FindBalance />
</div>
</>
)
);
}

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 (
<div>
<div>
Connected wallet balance:
{balance ? ethers.formatEther(balance) : ""}
<button onClick={() => fetchBalance(account?.address)}>refetch</button>
{balance ? ethers.formatEther(balance) : "0"}
<button onClick={() => account?.address && execute(account?.address)}>refetch</button>
</div>
{error && <div>Error: {error.message}</div>}
</div>
Expand All @@ -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 (
<div>
Expand All @@ -64,11 +69,11 @@ export function FindBalance() {
type="text"
placeholder="wallet address"
/>
<button onClick={() => fetchBalance(address)}>
<button onClick={() => execute(address)} disabled={!address}>
{inProgress ? 'fetching...' : 'fetch'}
</button>
</div>
<div>{balance ? ethers.formatEther(balance) : ""}</div>
<div>{balance ? ethers.formatEther(balance) : "0"}</div>
{error && <div>Error: {error.message}</div>}
</div>
);
Expand Down
52 changes: 37 additions & 15 deletions templates/react/next-ethers/src/components/BlockNumber.tsx
Original file line number Diff line number Diff line change
@@ -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<bigint | null>(null);
const [error, setError] = useState<string | null>(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 (
<div>
{blockNumber?.toString()}
{error ? (
<div>Error: {error}</div>
) : blockNumber === null ? (
<div>Loading block number...</div>
) : (
<div>{blockNumber.toString()}</div>
)}
</div>
);
}
35 changes: 16 additions & 19 deletions templates/react/next-ethers/src/components/Context.tsx
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -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[] = [
Expand Down Expand Up @@ -63,6 +63,7 @@ const EthereumContext = createContext<EthereumContextValue | null>(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<Chain | null>(null);
const [error, setError] = useState<string | null>(null);

const getEthereumContext = () => (window as any).ethereum;

Expand All @@ -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);
}
}

Expand All @@ -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");
Expand Down Expand Up @@ -159,6 +155,7 @@ export const EthereumProvider = ({ children }: { children: React.ReactNode }) =>
getSigner
}}>
{children}
{error && <div>Error: {error}</div>}
</EthereumContext.Provider>
);
}
Expand Down
61 changes: 38 additions & 23 deletions templates/react/next-ethers/src/components/ReadContract.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 (
<div>
<div>
Total Supply: {supply?.toString()}
<button onClick={fetchTotalSupply}>
<button onClick={executeFetchTotalSupply} disabled={inProgress}>
{inProgress ? 'fetching...' : 'refetch'}
</button>
</div>
Expand All @@ -52,38 +55,50 @@ 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<HTMLInputElement>) => {
setAddress(e.target.value);
}, []);

const handleFetchBalance = useCallback(() => {
executeFetchBalance();
}, [executeFetchBalance]);

return (
<div>
<div>
Token balance: {balance?.toString()}
</div>
<div>
<input
value={address!}
onChange={(e) => setAddress(e.target.value)}
value={address || ""}
onChange={handleAddressChange}
type="text"
placeholder="wallet address"
/>
<button onClick={fetchBalance}>
<button onClick={handleFetchBalance} disabled={inProgress}>
{inProgress ? 'fetching...' : 'refetch'}
</button>
</div>
Expand Down
Loading

0 comments on commit 3d9b487

Please sign in to comment.