One-liner
useAccountClient の async effect に cleanup が無く、wallet 切り替え中に古い effect が後勝ちで setClient を上書きすると、ユーザーの意図と異なる wallet で writeContract が署名される。
Description
pkgs/frontend/hooks/useWallet.ts:17-45 の useAccountClient の useEffect は以下のような構造:
setClient(undefined);
setWallet(undefined);
// ...
const provider = await wallet.getEthereumProvider();
// ...
setClient(walletClient);
cancelled flag / AbortController が無いため、新しい wallets 配列が await の途中に到着すると、古い effect が後勝ちで setClient(walletClient_old) を実行する。
失敗シナリオ
- ユーザーが MetaMask の wallet A → wallet B にスイッチ(Privy の
wallets 配列が更新)
- 古い effect の
getEthereumProvider() がまだ resolve していない
- 新しい effect が即座に開始 & 完了
- その後で古い effect が
setClient(walletClient_old) を上書き
- 次の
writeContract は wallet A で署名される(ユーザーは B で署名するつもり)
特に admin 系の destructive な操作(renounce hat / revoke authority)で誤った account から実行される危険がある。
Requirements
Supplements
関連ファイル:
pkgs/frontend/hooks/useWallet.ts (L17-45)
pkgs/frontend/hooks/useViem.ts
出典: v3 code-review (/code-review ultra-high effort, 2026-05-27)。
One-liner
useAccountClientの async effect に cleanup が無く、wallet 切り替え中に古い effect が後勝ちでsetClientを上書きすると、ユーザーの意図と異なる wallet でwriteContractが署名される。Description
pkgs/frontend/hooks/useWallet.ts:17-45のuseAccountClientのuseEffectは以下のような構造:cancelled flag / AbortController が無いため、新しい
wallets配列がawaitの途中に到着すると、古い effect が後勝ちでsetClient(walletClient_old)を実行する。失敗シナリオ
wallets配列が更新)getEthereumProvider()がまだ resolve していないsetClient(walletClient_old)を上書きwriteContractは wallet A で署名される(ユーザーは B で署名するつもり)特に admin 系の destructive な操作(renounce hat / revoke authority)で誤った account から実行される危険がある。
Requirements
useAccountClient内のuseEffectに cancelled フラグ(または AbortController)を導入await後にif (cancelled) return;を入れるcancelled = trueを設定useViem.tsや smart-wallet 系にも無いか確認Supplements
関連ファイル:
pkgs/frontend/hooks/useWallet.ts(L17-45)pkgs/frontend/hooks/useViem.ts出典: v3 code-review (
/code-reviewultra-high effort, 2026-05-27)。