WAGMI + WalletConnect + React でフロントエンド開発(zkSync)
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 を発行
こちらのページからログインし開発者コンソールを開き、projectId を入手します。アカウント発行は無料です。
React 実装
main.ts, App.ts, App.css を以下のように書き換えます。
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 使用のための設定などを行っています。ほとんど公式ドキュメントのコピペです。
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 にさまざまなカスタムフックが用意されているため、それらを利用すればとても簡単に実装することが可能です。
zkSync の system contract のアドレスはここに記載があります。もちろん他のコントラクトでもアドレスや ABI を変えれば動きます。
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 にデプロイしたデモサイトがこちらです。
基本的にはそのまま動きますが、walletconnect のみ追加の設定が必要です。セキュリティリスクを下げるため、開発者コンソールでサイトのドメインを登録する必要があります。ドメイン検証と .well-known/walletconnect.txt ファイルの作成の2つの方法があります。今回は GitHub Pages でドメイン検証が使えないため後者の方法を利用しました。
詳細はこちらをご覧ください。
Discussion