欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

web3 连接库 wagmi 基本示例

最编程 2024-04-29 15:02:29
...

连接钱包

步骤一:配置钱包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合约方法就可以触发该事件,建议和前两个组件放在同一个页面,方便观察效果。

推荐阅读