From a97bfbaeb615cfef04665e5e7348d85d17f960f0 Mon Sep 17 00:00:00 2001 From: jxom Date: Tue, 16 Apr 2024 07:53:14 +1000 Subject: [PATCH] fix: active chain when disconnected (#3822) * fix: resolves #3814 * chore: changeset --- .changeset/fuzzy-avocados-taste.md | 6 + packages/core/src/actions/disconnect.ts | 2 +- packages/core/src/actions/reconnect.ts | 2 +- packages/core/src/createConfig.test.ts | 2 +- packages/core/src/createConfig.ts | 6 +- packages/core/src/createStorage.test-d.ts | 4 +- playgrounds/next/src/app/contracts.ts | 202 +++++++++++ playgrounds/next/src/app/page.tsx | 421 ++++++++++++++++++++-- playgrounds/next/src/wagmi.ts | 5 +- playgrounds/next/tsconfig.json | 2 +- 10 files changed, 607 insertions(+), 45 deletions(-) create mode 100644 .changeset/fuzzy-avocados-taste.md create mode 100644 playgrounds/next/src/app/contracts.ts diff --git a/.changeset/fuzzy-avocados-taste.md b/.changeset/fuzzy-avocados-taste.md new file mode 100644 index 0000000000..73855ab016 --- /dev/null +++ b/.changeset/fuzzy-avocados-taste.md @@ -0,0 +1,6 @@ +--- +"@wagmi/core": patch +"wagmi": patch +--- + +Fixed an issue where Wagmi would not correctly rehydrate the active chain when a persisted store was being used. diff --git a/packages/core/src/actions/disconnect.ts b/packages/core/src/actions/disconnect.ts index b907bf88ab..6efb4c790c 100644 --- a/packages/core/src/actions/disconnect.ts +++ b/packages/core/src/actions/disconnect.ts @@ -47,7 +47,7 @@ export async function disconnect( return { ...x, connections: new Map(), - current: undefined, + current: null, status: 'disconnected', } diff --git a/packages/core/src/actions/reconnect.ts b/packages/core/src/actions/reconnect.ts index 5db7e4cf96..265cefc7bc 100644 --- a/packages/core/src/actions/reconnect.ts +++ b/packages/core/src/actions/reconnect.ts @@ -116,7 +116,7 @@ export async function reconnect( config.setState((x) => ({ ...x, connections: new Map(), - current: undefined, + current: null, status: 'disconnected', })) else config.setState((x) => ({ ...x, status: 'connected' })) diff --git a/packages/core/src/createConfig.test.ts b/packages/core/src/createConfig.test.ts index 29b552a326..ab98ebf1c4 100644 --- a/packages/core/src/createConfig.test.ts +++ b/packages/core/src/createConfig.test.ts @@ -210,7 +210,7 @@ test('behavior: migrate chainId', async () => { { "chainId": 10, "connections": Map {}, - "current": undefined, + "current": null, "status": "disconnected", } `) diff --git a/packages/core/src/createConfig.ts b/packages/core/src/createConfig.ts index cca6cc616f..8cf0b487d3 100644 --- a/packages/core/src/createConfig.ts +++ b/packages/core/src/createConfig.ts @@ -192,7 +192,7 @@ export function createConfig< return { chainId: chains.getState()[0].id, connections: new Map(), - current: undefined, + current: null, status: 'disconnected', } satisfies State } @@ -358,7 +358,7 @@ export function createConfig< return { ...x, connections: new Map(), - current: undefined, + current: null, status: 'disconnected', } @@ -526,7 +526,7 @@ export type State< > = { chainId: chains[number]['id'] connections: Map - current: string | undefined + current: string | null status: 'connected' | 'connecting' | 'disconnected' | 'reconnecting' } diff --git a/packages/core/src/createStorage.test-d.ts b/packages/core/src/createStorage.test-d.ts index d6dacd6d31..3e730ec592 100644 --- a/packages/core/src/createStorage.test-d.ts +++ b/packages/core/src/createStorage.test-d.ts @@ -21,7 +21,7 @@ test('getItem', () => { | { chainId?: number | undefined connections?: Map | undefined - current?: string | undefined + current?: string | null | undefined status?: | 'connected' | 'connecting' @@ -33,7 +33,7 @@ test('getItem', () => { | Promise<{ chainId?: number | undefined connections?: Map | undefined - current?: string | undefined + current?: string | null | undefined status?: | 'connected' | 'connecting' diff --git a/playgrounds/next/src/app/contracts.ts b/playgrounds/next/src/app/contracts.ts new file mode 100644 index 0000000000..d7d66754a2 --- /dev/null +++ b/playgrounds/next/src/app/contracts.ts @@ -0,0 +1,202 @@ +export const wagmiContractConfig = { + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: [ + { inputs: [], stateMutability: 'nonpayable', type: 'constructor' }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'approved', + type: 'address', + }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address', + }, + { + indexed: true, + name: 'operator', + type: 'address', + }, + { + indexed: false, + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address', + }, + { indexed: true, name: 'to', type: 'address' }, + { + indexed: true, + name: 'tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + inputs: [ + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'approve', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'getApproved', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'owner', type: 'address' }, + { name: 'operator', type: 'address' }, + ], + name: 'isApprovedForAll', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ internalType: 'uint256', name: 'tokenId', type: 'uint256' }], + name: 'mint', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'name', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'ownerOf', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + { name: '_data', type: 'bytes' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { name: 'operator', type: 'address' }, + { name: 'approved', type: 'bool' }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [{ name: 'interfaceId', type: 'bytes4' }], + name: 'supportsInterface', + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'symbol', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: 'tokenId', type: 'uint256' }], + name: 'tokenURI', + outputs: [{ name: '', type: 'string' }], + stateMutability: 'pure', + type: 'function', + }, + { + inputs: [], + name: 'totalSupply', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { name: 'from', type: 'address' }, + { name: 'to', type: 'address' }, + { name: 'tokenId', type: 'uint256' }, + ], + name: 'transferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], +} as const diff --git a/playgrounds/next/src/app/page.tsx b/playgrounds/next/src/app/page.tsx index f5dcbdf812..60defc583e 100644 --- a/playgrounds/next/src/app/page.tsx +++ b/playgrounds/next/src/app/page.tsx @@ -1,48 +1,401 @@ 'use client' -import { useAccount, useConnect, useDisconnect } from 'wagmi' +import { FormEvent } from 'react' +import { Hex, parseAbi, parseEther } from 'viem' +import { + BaseError, + useAccount, + useAccountEffect, + useBalance, + useBlockNumber, + useChainId, + useConnect, + useConnections, + useConnectorClient, + useDisconnect, + useEnsName, + useReadContract, + useReadContracts, + useSendTransaction, + useSignMessage, + useSwitchAccount, + useSwitchChain, + useWaitForTransactionReceipt, + useWriteContract, +} from 'wagmi' +import { switchChain } from 'wagmi/actions' +import { optimism, sepolia } from 'wagmi/chains' -function App() { +import { config } from '../wagmi' +import { wagmiContractConfig } from './contracts' + +export default function App() { + useAccountEffect({ + onConnect(_data) { + // console.log('onConnect', data) + }, + onDisconnect() { + // console.log('onDisconnect') + }, + }) + + return ( + <> + + + + + + + + + + + + + + + + ) +} + +function Account() { const account = useAccount() - const { connectors, connect, status, error } = useConnect() const { disconnect } = useDisconnect() + const { data: ensName } = useEnsName({ + address: account.address, + }) return ( - <> +
+

Account

+
-

Account

- -
- status: {account.status} -
- addresses: {JSON.stringify(account.addresses)} -
- chainId: {account.chainId} + account: {account.address} {ensName} +
+ chainId: {account.chainId} +
+ status: {account.status} +
+ + {account.status !== 'disconnected' && ( + + )} +
+ ) +} + +function Connect() { + const chainId = useChainId() + const { connectors, connect, status, error } = useConnect() + + return ( +
+

Connect

+ {connectors.map((connector) => ( + + ))} +
{status}
+
{error?.message}
+
+ ) +} + +function SwitchAccount() { + const account = useAccount() + const { connectors, switchAccount } = useSwitchAccount() + + return ( +
+

Switch Account

+ + {connectors.map((connector) => ( + + ))} +
+ ) +} + +function SwitchChain() { + const chainId = useChainId() + const { chains, switchChain, error } = useSwitchChain() + + return ( +
+

Switch Chain

+ + {chains.map((chain) => ( + + ))} + + {error?.message} +
+ ) +} + +function SignMessage() { + const { data, signMessage } = useSignMessage() + + return ( +
+

Sign Message

+ +
{ + event.preventDefault() + const formData = new FormData(event.target as HTMLFormElement) + signMessage({ message: formData.get('message') as string }) + }} + > + + +
+ + {data} +
+ ) +} + +function Connections() { + const connections = useConnections() + + return ( +
+

Connections

+ + {connections.map((connection) => ( +
+
connector {connection.connector.name}
+
accounts: {JSON.stringify(connection.accounts)}
+
chainId: {connection.chainId}
+ ))} +
+ ) +} - {account.status === 'connected' && ( - - )} -
+function Balance() { + const { address } = useAccount() -
-

Connect

- {connectors.map((connector) => ( - - ))} -
{status}
-
{error?.message}
-
- + const { data: default_ } = useBalance({ address }) + const { data: account_ } = useBalance({ address }) + const { data: optimism_ } = useBalance({ + address, + chainId: optimism.id, + }) + + return ( +
+

Balance

+ +
Balance (Default Chain): {default_?.formatted}
+
Balance (Account Chain): {account_?.formatted}
+
Balance (Optimism Chain): {optimism_?.formatted}
+
+ ) +} + +function BlockNumber() { + const { data: default_ } = useBlockNumber({ watch: true }) + const { data: account_ } = useBlockNumber({ + watch: true, + }) + const { data: optimism_ } = useBlockNumber({ + chainId: optimism.id, + watch: true, + }) + + return ( +
+

Block Number

+ +
Block Number (Default Chain): {default_?.toString()}
+
Block Number (Account Chain): {account_?.toString()}
+
Block Number (Optimism): {optimism_?.toString()}
+
+ ) +} + +function ConnectorClient() { + const { data, error } = useConnectorClient() + return ( +
+

Connector Client

+ client {data?.account?.address} {data?.chain?.id} + {error?.message} +
+ ) +} + +function SendTransaction() { + const { data: hash, error, isPending, sendTransaction } = useSendTransaction() + + async function submit(e: FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const to = formData.get('address') as Hex + const value = formData.get('value') as string + sendTransaction({ to, value: parseEther(value) }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+

Send Transaction

+
+ + + +
+ {hash &&
Transaction Hash: {hash}
} + {isConfirming && 'Waiting for confirmation...'} + {isConfirmed && 'Transaction confirmed.'} + {error && ( +
Error: {(error as BaseError).shortMessage || error.message}
+ )} +
+ ) +} + +function ReadContract() { + const { data: balance } = useReadContract({ + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }) + + return ( +
+

Read Contract

+
Balance: {balance?.toString()}
+
+ ) +} + +function ReadContracts() { + const { data } = useReadContracts({ + allowFailure: false, + contracts: [ + { + ...wagmiContractConfig, + functionName: 'balanceOf', + args: ['0x03A71968491d55603FFe1b11A9e23eF013f75bCF'], + }, + { + ...wagmiContractConfig, + functionName: 'ownerOf', + args: [69n], + }, + { + ...wagmiContractConfig, + functionName: 'totalSupply', + }, + ], + }) + const [balance, ownerOf, totalSupply] = data || [] + + return ( +
+

Read Contract

+
Balance: {balance?.toString()}
+
Owner of Token 69: {ownerOf?.toString()}
+
Total Supply: {totalSupply?.toString()}
+
) } -export default App +function WriteContract() { + const { data: hash, error, isPending, writeContract } = useWriteContract() + + async function submit(e: FormEvent) { + e.preventDefault() + const formData = new FormData(e.target as HTMLFormElement) + const tokenId = formData.get('tokenId') as string + writeContract({ + address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2', + abi: parseAbi(['function mint(uint256 tokenId)']), + functionName: 'mint', + args: [BigInt(tokenId)], + }) + } + + const { isLoading: isConfirming, isSuccess: isConfirmed } = + useWaitForTransactionReceipt({ + hash, + }) + + return ( +
+

Write Contract

+
+ + +
+ {hash &&
Transaction Hash: {hash}
} + {isConfirming && 'Waiting for confirmation...'} + {isConfirmed && 'Transaction confirmed.'} + {error && ( +
Error: {(error as BaseError).shortMessage || error.message}
+ )} +
+ ) +} + +export function Repro() { + const chainId = useChainId() + + console.log('chainId from useChainId is', chainId) + return ( +
+ Current Chain Id: {chainId} + + +
+ ) +} diff --git a/playgrounds/next/src/wagmi.ts b/playgrounds/next/src/wagmi.ts index ebf5b89018..d2f77d27c0 100644 --- a/playgrounds/next/src/wagmi.ts +++ b/playgrounds/next/src/wagmi.ts @@ -1,9 +1,9 @@ import { http, cookieStorage, createConfig, createStorage } from 'wagmi' -import { mainnet, sepolia } from 'wagmi/chains' +import { mainnet, optimism, sepolia } from 'wagmi/chains' import { injected, walletConnect } from 'wagmi/connectors' export const config = createConfig({ - chains: [mainnet, sepolia], + chains: [mainnet, sepolia, optimism], connectors: [ injected(), walletConnect({ @@ -18,6 +18,7 @@ export const config = createConfig({ transports: { [mainnet.id]: http(), [sepolia.id]: http(), + [optimism.id]: http(), }, }) diff --git a/playgrounds/next/tsconfig.json b/playgrounds/next/tsconfig.json index f8a79128af..9eb40a20d5 100644 --- a/playgrounds/next/tsconfig.json +++ b/playgrounds/next/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "ESNext", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true,