🌴

WAGMI + WalletConnect + React でフロントエンド開発(zkSync)

2024/08/17に公開

WAGMI、WalletConnect、React を利用して基本的な操作を行うフロントエンド開発を紹介します。ここでは以下の機能を作成します。

  • ウォレットに接続する
  • 接続中のウォレットアドレスと ETH 残高を表示する
  • あるコントラクトの情報を表示する
    • 今回は zkSync のテストネットで試してみるため、system contracts である NonceHolder にアクセスし、接続中のウォレットの nonce を表示します
  • 指定したウォレットに ETH を送る
  • 接続中のウォレットを切断する

開発

環境の構築

以下のコマンドを実行し React の雛形を作成します。 Project name は何でも OK ですがここでは walletconnect-tutorial としています。

npm create vite@latest

✔ Project name: … walletconnect-tutorial
✔ Select a framework: › React
✔ Select a variant: › TypeScript + SWC

上記のコマンドを実行後、作成したプロジェクトフォルダ内に移動し、必要なライブラリをインストールします。

npm install @web3modal/wagmi wagmi viem

WalletConnect の project ID を発行

https://walletconnect.com/
こちらのページからログインし開発者コンソールを開き、projectId を入手します。アカウント発行は無料です。

React 実装

main.ts, App.ts, App.css を以下のように書き換えます。

main.ts

main.ts
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'

import { createWeb3Modal } from '@web3modal/wagmi/react'
import { defaultWagmiConfig } from '@web3modal/wagmi/react/config'

import { WagmiProvider } from 'wagmi'
import { zksyncSepoliaTestnet } from 'wagmi/chains'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'

// 0. Setup queryClient
const queryClient = new QueryClient()

// 1. Get projectId from https://cloud.walletconnect.com
const projectId = import.meta.env.VITE_WALLETCONNECT_PROJECT_ID || ""

// 2. Create wagmiConfig
const metadata = {
  name: 'AppKit',
  description: 'AppKit Example',
  url: 'https://web3modal.com', // origin must match your domain & subdomain
  icons: ['https://avatars.githubusercontent.com/u/37784886']
}

const chains = [zksyncSepoliaTestnet] as const
const config = defaultWagmiConfig({
  chains,
  projectId,
  metadata,
})

// 3. Create modal
createWeb3Modal({
  metadata,
  wagmiConfig: config,
  projectId,
  enableAnalytics: true
})

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    </WagmiProvider>
  </StrictMode>,
)

WalletConnect 使用のための設定などを行っています。ほとんど公式ドキュメントのコピペです。
https://docs.walletconnect.com/appkit/react/core/installation

App.ts

App.ts
import {
  useAccount,
  useBalance,
  useReadContract,
  useDisconnect,
  type BaseError,
  useSendTransaction,
  useWaitForTransactionReceipt,
} from "wagmi";
import { parseEther, formatUnits } from "viem";
import { useWeb3Modal } from "@web3modal/wagmi/react";
import "./App.css";

function App() {
  const { address, isConnecting, isDisconnected } = useAccount();
  const { open } = useWeb3Modal();
  const { disconnect } = useDisconnect();

  // 残高取得
  const balance = useBalance({
    address: address,
  });

  // NonceHolder コントラクトを読み取る
  const abi = [
    {
      type: "function",
      name: "getMinNonce",
      stateMutability: "view",
      inputs: [{ name: "_address", type: "address" }],
      outputs: [{ type: "uint256" }],
    },
  ];

  const nonce = useReadContract({
    abi,
    address: "0x0000000000000000000000000000000000008003",
    functionName: "getMinNonce",
    args: [address],
  });

  // 指定量の ETH を送る
  const {
    data: hash,
    error,
    isPending,
    sendTransaction,
  } = useSendTransaction();

  async function submit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    const formData = new FormData(e.target as HTMLFormElement);
    const to = formData.get("address") as `0x${string}`;
    const value = formData.get("value") as string;
    sendTransaction({ to, value: parseEther(value) });
  }

  const { isLoading: isConfirming, isSuccess: isConfirmed } =
    useWaitForTransactionReceipt({
      hash,
    });

  // wallet に接続されていない場合
  if (isConnecting) return <div>Connecting…</div>;
  if (isDisconnected) return <button onClick={() => open()}>Connect to a wallet</button>;

  // wallet に接続されている場合
  return (
    <>
      <div>address: {address}</div>
      <div>balance: {balance.isFetched ? formatUnits(balance.data!.value, balance.data!.decimals) : "???"}</div>
      <div>rawNonce: {nonce.isFetched ? Number(nonce.data) : "???"}</div>
      <hr />
      <center>
        <form onSubmit={submit} className="form-example">
          <center>
            <div className="form-example">
              <label htmlFor="address">Wallet Address: </label>
              <input name="address" placeholder="0xA0Cf…251e" required />
            </div>
            <div className="form-example">
              <label htmlFor="value">Value(ETH): </label>
              <input name="value" placeholder="0.05" required />
            </div>
          </center>
          <div>
            <button disabled={isPending} type="submit">
              {isPending ? "Confirming..." : "Send"}
            </button>
          </div>
          {hash && <div>Transaction Hash: {hash}</div>}
          {isConfirming && <div>Waiting for confirmation...</div>}
          {isConfirmed && <div>Transaction confirmed.</div>}
          {error && (
            <div>
              Error: {(error as BaseError).shortMessage || error.message}
            </div>
          )}
        </form>
      </center>
      <hr />
      <button onClick={() => disconnect()}>Disconnect</button>
    </>
  );
}

export default App;

WAGMI にさまざまなカスタムフックが用意されているため、それらを利用すればとても簡単に実装することが可能です。
https://wagmi.sh/react/api/hooks

zkSync の system contract のアドレスはここに記載があります。もちろん他のコントラクトでもアドレスや ABI を変えれば動きます。

App.css

App.css
#root {
  max-width: 1280px;
  margin: 0 auto;
  padding: 2rem;
  text-align: center;
}

form.form-example {
  display: table;
  text-align: center;
}

div.form-example {
  display: table-row;
}

label,
input {
  display: table-cell;
  margin-bottom: 10px;
}

label {
  padding-right: 10px;
}

フォームなどの見た目を少し整えています。

.env

.env ファイルを作成し、先ほど walletconnect の開発者コンソールで取得した projectId を以下のように記載します。<projectId> の部分は自身のものに置き換えてください。

VITE_WALLETCONNECT_PROJECT_ID=<projectId>

起動

ローカル環境

以下を実行すると立ち上がります。

npm run dev

このようなページが表示されれば成功です。



クラウド

GitHub Pages にデプロイしたデモサイトがこちらです。
https://takahiro-kimura.github.io/walletconnect-tutorial/

基本的にはそのまま動きますが、walletconnect のみ追加の設定が必要です。セキュリティリスクを下げるため、開発者コンソールでサイトのドメインを登録する必要があります。ドメイン検証と .well-known/walletconnect.txt ファイルの作成の2つの方法があります。今回は GitHub Pages でドメイン検証が使えないため後者の方法を利用しました。
詳細はこちらをご覧ください。
https://docs.walletconnect.com/cloud/verify

Discussion