📝

Wagmi v2が来たぞ

2024/01/08に公開

前置き

個人的に好きなライブラリであるwagmiのv2が先日正式リリースされました!
ドキュメントを読んだり実際に動かしたりして気になった内容をピックアップして書いていきたいと思います。
※今回はReact向けライブラリについてのみです
https://twitter.com/wevm_dev/status/1742991699842916629

v1からのMigrate手順全体は以下です。
https://wagmi.sh/react/guides/migrate-from-v1-to-v2

また、基本的に以下のドキュメントをベースに記載しています。
https://wagmi.sh/react/getting-started

Wagmiとは?

ウォレットとの接続や署名、トランザクションの送信などを簡単に行えるようにしてくれるdapp開発者向けのOSSライブラリです。
開発者体験(型安全)、パフォーマンス、機能の充実性、安定性を重視しており、今回リリースされたWagmi v2にもその思想がバッチリ反映されています。

大きな変更点

TanStack Queryのフルサポート

今までもwagmiの内部ではTanStack Queryが使用されていましたが、ユーザから細かいオプションの設定はできませんでした。
しかし、v2では以下のようにキャッシュ設定が可能になっており、パフォーマンスの向上に役立てられます。
TanStack QueryのdevToolを使ってキャッシュの状態をチェックすることもできます。

const result = useReadContract({
    abi,
    address: '0x6b175474e89094c44da98b954eedeac495271d0f',
    functionName: 'totalSupply',
    query: { // TanStack Queryのオプション
        gcTime: 1000 * 60 * 60 
        staleTime: 1000 * 5,
	...
    }
  })

また、hooksの戻り値としてqueryKeyが取得できるようになっています。このqueryKeyを利用してTanStack Query同様にinvalidateも可能です。
以下はユーザがボタンを押すとinvalidateする実装例です。

import { useBalance } from 'wagmi'

function App() {
  // 1. Extract `queryKey` from the useBalance Hook. 
  const { queryKey } = useBalance() 

  return (
    <button
      onClick={async () => {
        // 2. Invalidate the query when the user clicks "Invalidate". 
        await queryClient.invalidateQueries({ queryKey }) 
      }}
    >
      Invalidate
    </button>
  )
}

function Example() {
  // 3. Other `useBalance` Hooks in your rendered React tree will be refetched! 
  const { data: balance } = useBalance() 

  return <div>{balance}</div>
}

さらに、以下ドキュメントに従って設定を行うと、localStorageにデータを保存することもできます。
https://wagmi.sh/react/guides/tanstack-query#persistence-via-external-stores

他にもTanStack Queryの便利な機能が使えるようになっているため、気になる方はドキュメントを見てみてください。

mutation関数の形が変わった

TanStack Queryに倣い、mutation関数の引数はhooksではなく関数側に設定するよう変更が入っています。

import { useSignMessage } from 'wagmi'

- const { signMessage } = useSignMessage({ message: 'foo bar baz' })
+ const { signMessage } = useSignMessage() 

<button
- onClick={() => signMessage()} 
+ onClick={() => signMessage({ message: 'foo bar baz' })} 
>
  Sign message
</button>

上記のuseSignMessageだけでなく、よく使われるであろうuseWriteContractにも変更が入っているため、そこそこ影響範囲は広いと思います。

watchプロパティ削除

blockごとに値を再取得してくれるwatchというプロパティが削除されました。
これは上記のTanStack Queryサポートに伴い、以下のようにユーザ側でハンドリングできるようになったことが要因かと思われます。

+ import { useQueryClient } from '@tanstack/react-query' 
+ import { useEffect } from 'react' 
+ import { useBlockNumber, useBalance } from 'wagmi' 

+ const { data: blockNumber } = useBlockNumber({ watch: true }) 
+ const { data: balance, queryKey } = useBalance({ 
+   address: '0x4557B18E779944BFE9d78A672452331C186a9f48',
-   watch: true,
+ })

+ useEffect(() => { 
+   queryClient.invalidateQueries({ queryKey }) 
+ }, [blockNumber]) 

今まではwatch: trueのようなコードを書けば実現できていましたが、少しコード量が増えてしまいます。
ですが、ここには 「多少コードが増えたとしても、内部コードが減ることとユーザにコントロールを与えるほうが良い」 というwagmiの思想が出ているようです。
例えば、上記のコードはuseEffect内に処理を追加することで、5blockごとに再取得する、といった処理にユーザ側で簡単に変更が可能です。

suspenseプロパティ削除

個人的には少し残念なのですが、suspenseプロパティが削除になっています。
一応以下のようにquery部分を取り出してTanStack QueryのuseSuspenseQueryに突っ込むことで実現はできるようですが、冗長だなと感じます。

import { useSuspenseQuery } from '@tanstack/react-query' 
import { useConfig } from 'wagmi' 
import { getBalanceQueryOptions } from 'wagmi/query' 

const config = useConfig() 
const options = getBalanceQueryOptions(config, { address: '0x…' }) 
const result = useSuspenseQuery(options) 

将来的にuseSuspenseReadContractのようなhooksを作成して対応する予定はあるみたいですが、すぐではないとのことです。
https://github.com/wevm/wagmi/discussions/3068#discussioncomment-7958487

usePrepareContract→useSimulateContract

同チームが開発しているviemと同名になるように変更されました。
(他にもuseContractWriteuseWriteContractに変わったりと微妙に変更が入ってます)

EIP-6963対応

EIP-6963は、複数のウォレットが同時に開いていると、ユーザが本当に選びたいウォレットと接続できない問題に対応したEIPです。
例えば、MetaMaskとRabbyの拡張機能がインストールされており、MetaMask→Rabbyの順で読み込まれるとします。この場合、Rabbyが1つしかないwindow.ethereumの席を奪い取るため、ユーザはMetaMaskとは接続することができません。

EIP-6963の詳しい説明は以下が神記事なので割愛させていただきます。
https://zenn.dev/yosket/articles/ffc512aac25941

SSR対応

以下の設定を行うことで、SSR時にHydrationエラーが出ることを回避できるようになりました。

import { createConfig, http } from 'wagmi'
import { mainnet, sepolia } from 'wagmi/chains'

const config = createConfig({ 
  chains: [mainnet, sepolia],
+ ssr: true, 
  transports: {
    [mainnet.id]: http(),
    [sepolia.id]: http(),
  },
})

Third-party Wallet Libraryについて

Wallet Connecor API周りで変更があり、 Third-party Wallet Libraryは2024/1/8現在wagmi v2では動作しません。
ただし、以下のように各ライブラリで対応は進められているようです。

Rainbowkit

対応中Issue
https://github.com/rainbow-me/rainbowkit/discussions/1539

Connectkit

対応中Issue
https://github.com/family/connectkit/discussions/320

Web3Modal

対応中PR(中の人によるとあと1週間くらいでマージされるらしい?)
https://github.com/WalletConnect/web3modal/pull/1588

Dynamic

OSSではないので不明

chain関連の型安全

異常なchainIDを設定しようとした場合、それを静的解析時に検知できるようになりました。
例えば、以下のような設定で実装をしていたとします。

export const config = createConfig({
  chains: [mainnet, sepolia],
  transports: {
    [mainnet.id]: http(), // chainID: 1
    [sepolia.id]: http(), // chainID: 11155111
  },
})

この時に以下のように設定にないchainIDを代入すると、エディター上で警告を出してくれます。

useBlockNumber({ chainId: 123 })

この機能を使うためには、上記のcreateConfigを行っているのと同じファイルに以下実装が必要です。

declare module 'wagmi' { 
  interface Register { 
    config: typeof config 
  } 
} 

おまけ

色々触るのに使っていたリポジトリ
https://github.com/POKENA7/wagmiv2-playground

後書き

まだまだ触れておらず紹介できていない機能も多々ありますが、何かの役に立てば幸いです!

補足:v2への移行はまだいいや、という方は以下からv1のドキュメントが見られます。
https://1.x.wagmi.sh/react/getting-started
ただし、v2の新機能はv1には搭載されない方針みたいです。

Discussion