连接钱包
步骤一:配置钱包connector
首先我们需要创建client
,并向client
传入钱包connector,以下代码以injected, MetaMask, Coinbase Wallet, WalletConnect为例。
用WagmiConfig
包裹整个app,并传入上一步构造的client
,然后就可以WagmiConfig
包裹的范围内使用wagmi提供的各种hook了。
import {
configureChains,
chain,
createClient,
WagmiConfig,
Chain,
} from 'wagmi';
import { publicProvider } from 'wagmi/providers/public';
import { CoinbaseWalletConnector } from 'wagmi/connectors/coinbaseWallet';
import { InjectedConnector } from 'wagmi/connectors/injected';
import { MetaMaskConnector } from 'wagmi/connectors/metaMask';
import { WalletConnectConnector } from 'wagmi/connectors/walletConnect';
const { chains, provider } = configureChains(
[chain.goerli],
[publicProvider()],
);
const client = createClient({
autoConnect: true,
provider,
connectors: [
new MetaMaskConnector({ chains }),
new CoinbaseWalletConnector({
chains,
options: {
appName: 'wagmi',
},
}),
new WalletConnectConnector({
chains,
options: {
qrcode: true,
},
}),
new InjectedConnector({
chains,
options: {
name: 'Injected',
shimDisconnect: true,
},
}),
],
});
const App = ({ children }) => {
return <WagmiConfig client={client}>{children}</WagmiConfig>;
};
export default App;
步骤二:显示连接不同钱包的按钮
import { useAccount, useConnect, useDisconnect } from 'wagmi';
const Connect = () => {
const { connector: currentConnector, isConnected } = useAccount();
const { connect, connectors, isLoading, error, pendingConnector } =
useConnect();
const { disconnect } = useDisconnect();
return (
<div>
{/* 显示当前连接中的connector的名字 */}
<h3>Current connector: {currentConnector?.name || 'None'}</h3>
<div style={{ display: 'flex', columnGap: '10px' }}>
{/* 断开连接的按钮 */}
{isConnected && (
<button
style={{ color: 'red' }}
onClick={() => {
disconnect();
}}
>
Disconnect
</button>
)}
{/* 筛选出可用的connector */}
{connectors
.filter(
(connector) =>
connector.ready && connector.id !== currentConnector?.id,
)
.map((connector) => (
// 连接connector的按钮
<button
key={connector.id}
onClick={() => connect({ connector: connector })}
>
Connect {connector.name}
{isLoading &&
connector.id === pendingConnector?.id &&
' (connecting)'}
</button>
))}
</div>
{error && <div>{error.message}</div>}
</div>
);
};
export default Connect;
获取基本信息
import { useAccount, useBalance } from 'wagmi';
export default function BasicInfo() {
const { address, isConnected, status } = useAccount();
const { connector } = useAccount();
const balance = useBalance({
addressOrName: address,
formatUnits: 'ether',
});
return (
<div>
<ul>
<li>isConnected: {isConnected}</li>
<h3>Current connector: {connector?.name || 'None'}</h3>
<li>Address: {address}</li>
<li>Connecting status: {status}</li>
<li>balance: {balance.data?.formatted || '-'}</li>
</ul>
</div>
);
}
切换链
import { useNetwork, useSwitchNetwork } from 'wagmi';
const NetworkSwitcher = () => {
const { chain: currentChain } = useNetwork();
const { chains, error, isLoading, pendingChainId, switchNetwork } =
useSwitchNetwork();
return (
<div>
<h3>
Current chain: {currentChain?.name ?? currentChain?.id}
{currentChain?.unsupported && ' (unsupported)'}
</h3>
{switchNetwork && (
<div>
{chains.map((chain) =>
chain.id === currentChain?.id ? null : (
<button key={chain.id} onClick={() => switchNetwork(chain.id)}>
Switch to {chain.name}
{isLoading && chain.id === pendingChainId && ' (switching)'}
</button>
),
)}
</div>
)}
<div>{error && error.message}</div>
</div>
);
};
export default NetworkSwitcher;
发送交易
import { utils } from 'ethers';
import { useState } from 'react';
import { useDebounce } from 'use-debounce';
import {
usePrepareSendTransaction,
useSendTransaction,
useWaitForTransaction,
} from 'wagmi';
const SendTransaction = () => {
const [to, setTo] = useState('');
const [debouncedTo] = useDebounce(to, 500);
const [amount, setAmount] = useState('');
const [debouncedValue] = useDebounce(amount, 500);
// usePrepareSendTransaction会尝试解析目标地址是否有效和交易可能花费的gas
const { config, error } = usePrepareSendTransaction({
request: {
to: debouncedTo,
value: debouncedValue ? utils.parseEther(debouncedValue) : undefined,
},
});
const { data, sendTransaction } = useSendTransaction(config);
const { isLoading, isSuccess } = useWaitForTransaction({
hash: data?.hash,
});
return (
<form
onSubmit={(e) => {
e.preventDefault();
sendTransaction?.();
}}
style={{
display: 'flex',
flexDirection: 'column',
rowGap: '8px',
width: '400px',
}}
>
<div>
<label>To: </label>
<input
aria-label="Recipient"
onChange={(e) => setTo(e.target.value)}
placeholder="0xA0Cf…251e"
value={to}
style={{ width: '100%' }}
/>
</div>
<div>
<label>Amount: </label>
<input
aria-label="Amount (ether)"
onChange={(e) => setAmount(e.target.value)}
placeholder="0.05"
value={amount}
style={{ width: '100%' }}
/>
</div>
<br />
<button disabled={isLoading || !sendTransaction || !to || !amount}>
{isLoading ? 'Sending...' : 'Send'}
</button>
{isSuccess && (
<div>
Successfully sent {amount} ether to {to}
<div>
<a href={`https://goerli.etherscan.io/tx/${data?.hash}`}>
Etherscan
</a>
</div>
</div>
)}
{error && <div>{error.message}</div>}
</form>
);
};
export default SendTransaction;
对消息进行签名
import { verifyMessage } from 'ethers/lib/utils';
import { useRef } from 'react';
import { useSignMessage } from 'wagmi';
const SignMessage = () => {
const signerAddress = useRef<string>();
const { data, error, isLoading, signMessage } = useSignMessage({
onSuccess: (data, variables) => {
// 解析进行签名的地址
const address = verifyMessage(variables.message, data);
signerAddress.current = address;
},
});
return (
<form
onSubmit={(event) => {
event.preventDefault();
const formData = new FormData(event.target as any);
const message = formData.get('message') as string;
signMessage({ message });
}}
style={{
display: 'flex',
flexDirection: 'column',
width: '400px',
rowGap: '8px',
}}
>
<label htmlFor="message">Enter a message to sign</label>
<textarea
id="message"
name="message"
placeholder="Enter a message to sign…"
/>
<button disabled={isLoading}>
{isLoading ? 'Check Wallet' : 'Sign Message'}
</button>
{data && (
<div>
<div style={{ marginTop: '8px' }}>
Recovered Address: {signerAddress.current}
</div>
<div style={{ marginTop: '8px' }}>Signature: {data}</div>
</div>
)}
{error && <div style={{ marginTop: '8px' }}>{error.message}</div>}
</form>
);
};
export default SignMessage;
从合约中读取数据
import { useAccount, useContractRead } from 'wagmi';
const BalanceOf = () => {
const { address } = useAccount();
const { data, refetch, isFetching } = useContractRead({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: [
{
inputs: [{ internalType: 'address', name: 'owner', type: 'address' }],
name: 'balanceOf',
outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
},
],
functionName: 'balanceOf',
args: [address || '0x'],
enabled: !!address,
});
return (
<div>
<h3>Read contract</h3>
Balance of your wagmi NFT : {data?.toString()}
<button
style={{ marginLeft: '8px' }}
onClick={() => {
refetch();
}}
>
{isFetching ? 'Reading...' : 'Read'}
</button>
</div>
);
};
export default BalanceOf;
调用改变链上状态的合约方法
import {
useContractWrite,
usePrepareContractWrite,
useWaitForTransaction,
} from 'wagmi';
const MintNFT = () => {
const {
config,
error: prepareError,
isError: isPrepareError,
} = usePrepareContractWrite({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: [
{
name: 'mint',
type: 'function',
stateMutability: 'nonpayable',
inputs: [],
outputs: [],
},
],
functionName: 'mint',
});
const { data, error, isError, write } = useContractWrite(config);
const { isLoading, isSuccess } = useWaitForTransaction({
hash: data?.hash,
});
return (
<div>
<h3>Write contract without param</h3>
<button disabled={!write || isLoading} onClick={() => write?.()}>
{isLoading ? 'Minting...' : 'Mint an NFT'}
</button>
{isSuccess && (
<div>
Successfully minted your NFT!
<div>
<a href={`https://goerli.etherscan.io/tx/${data?.hash}`}>
Etherscan
</a>
</div>
</div>
)}
{(isPrepareError || isError) && (
<div>Error: {(prepareError || error)?.message}</div>
)}
</div>
);
};
export default MintNFT;
mint成功后可以通过读取合约数据例子看到自己的NFT数量改变,建议将这两个组件放在同一个页面,方便观察效果。
监听合约事件
import { useState } from 'react';
import { useContractEvent } from 'wagmi';
const ListenContractEvent = () => {
const [eventData, setEventData] = useState<{
from: string;
to: string;
tokenId: string;
}>();
useContractEvent({
address: '0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2',
abi: [
{
inputs: [
{
indexed: true,
internalType: 'address',
name: 'from',
type: 'address',
},
{
indexed: true,
internalType: 'address',
name: 'to',
type: 'address',
},
{
indexed: true,
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
],
name: 'Transfer',
type: 'event',
},
],
eventName: 'Transfer',
listener: (from, to, tokenId) => {
setEventData({
from,
to,
tokenId: tokenId.toString(),
});
},
});
return (
<div>
<h3>Current Mint Event</h3>
<ul>
<li>From: {eventData?.from}</li>
<li>To: {eventData?.to}</li>
<li>Token ID: {eventData?.tokenId}</li>
</ul>
</div>
);
};
export default ListenContractEvent;
调用前面的mint合约方法就可以触发该事件,建议和前两个组件放在同一个页面,方便观察效果。