qa について
Scaffold-ETH 2 dApp の公開前 QA レビューガイドです。独立したレビュアーがコードとユーザーフローを確認し、ウォレット操作、コントラクトアドレス、RPC、モバイル利用、信頼上の前提を PASS/FAIL で報告します。
主な機能
- 01ユーザーフローとウォレット状態の確認
- 02コントラクトアドレス表示と入力の検査
- 03RPC 設定と外部コントラクト登録の確認
- 04ブランド、メタデータ、テーマのレビュー
- 05モバイルでのウォレットアプリ連携テスト
- 06信頼上の前提に基づく CROPS Review
活用シーン
- 01公開前に SE2 dApp の一連のユーザーフローをレビュー
- 02承認やネットワーク切り替えなどの状態を確認
- 03本番設定とモバイルのウォレット接続を検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ austintgriffith/ ethskills --skill qaインストール後、Claude Code で/qaを入力して読み込まれたか確認します。
詳細
dApp QA — Scaffold-ETH 2 ビルドのリリース前監査
よくある間違い
「アプリがデプロイされたので、これで完了です。」 SE2 ビルドのリリースには、UX の正確さ、メタデータ、RPC の信頼性、コントラクトの検証、ブランディングの整理も含まれます。
「操作の流れは見ればわかります。」 接続、ネットワーク、承認、操作を適切な処理中状態とともに、必ず一度に1つずつ進めなければ、ユーザーは重複したトランザクションや失敗するトランザクションを発生させてしまいます。
「SE2 のデフォルト設定は本番環境でも問題ありません。」 デフォルトの README/フッター/タイトル/ファビコンや、デフォルトの RPC フォールバックはテンプレートの足場であり、本番環境での判断ではありません。
「合格とは、コンソールにエラーがないことです。」 ここでの QA の合否は、動作とユーザーに見える内容で判断します。実際のウォレット操作、モバイルでのディープリンクの動作、わかりやすいエラー、信頼性を示す要素を検証する必要があります。
dApp の構築後、新しいエージェントにこれを渡してください。レビュー担当者は次の作業を行います。
- ソースコード(
app/、components/、contracts/)を読む - ブラウザーでアプリを開き、すべての操作フローを一通り確認する
- 以下のすべての項目を確認し、修正はせず、合格/不合格を報告する
まず crops/SKILL.md を取得し、レポートに CROPS レビューを含めてください。シンプルなアプリは簡潔な基本レビューで構いませんが、資金、承認、カストディ、ウォレット権限、L2/ブリッジのフロー、非公開のユーザーデータ、ID、ステーブルコイン、管理者権限、またはホスト型の RPC/インデクサー/リレイヤー/ペイマスター/フロントエンドのインフラが関係するアプリには、4つの柱すべてを網羅したレビューが必要です。
🚨 重要:ウォレット操作 — テキストではなくボタン
ウォレットを接続していない状態でアプリを開いてください。
- ❌ 不合格: 「プレイするにはウォレットを接続してください」/「続行するには接続してください」など、接続を促すテキストや段落が表示される
- ✅ 合格: 目立つ大きな「ウォレットを接続」ボタンが主要な UI 要素になっている
これは AI エージェントが最もよく間違える点です。 ありきたりな LLM はどれも、<RainbowKitCustomConnectButton /> を表示する代わりに <p>Please connect your wallet</p> と書いてしまいます。
🚨 重要:4 段階ボタンフロー
アプリに表示する主要ボタンは、常に 1 つだけでなければなりません。次の順に進みます。
1. Not connected → Connect Wallet button
2. Wrong network → Switch to [Chain] button
3. Needs approval → Approve button
4. Ready → Action button (Stake/Deposit/Swap)
特に次の点を確認してください:
- ❌ 不合格: 承認ボタンとアクションボタンが同時に表示されている
- ❌ 不合格: ネットワークを確認していないため、アプリが誤ったチェーンで処理を試み、何も表示せずに失敗する
- ❌ 不合格: 接続中のウォレットが誤ったネットワーク上にあるとき、「Switch to [Chain]」ボタンではなく、メインのオンチェーン CTA が表示される。SE-2 のヘッダーにある
WrongNetworkDropdownだけでは不十分です。アクションボタン自体が切り替え CTA になる必要があります。そうしないと、ユーザーが誤ったチェーン上で「署名」/「ステーク」/「入金」をクリックし、wagmi のエラーが何も表示されないままになります。 - ❌ 不合格: ユーザーが「承認」をクリックしてウォレットで署名し、戻ってきて、トランザクションの処理中にもう一度「承認」をクリックできる
- ✅ 合格: ボタンは常に 1 つだけです。承認ボタンにはスピナーが表示され、ブロックがオンチェーンで承認されるまで無効のままです。その後、アクションボタンに切り替わります。
- ✅ 合格: アクションボタンのレンダリング処理は
useChainId() === targetNetwork.id(または同等の条件)に基づいて分岐します。不一致の場合、メイン CTA と同じ位置に、useSwitchChainを使った「Switch to [Chain]」ボタンが表示されます。
コード内: ボタンの disabled プロパティは、useScaffoldWriteContract の isPending に連動させる必要があります。useScaffoldWriteContract(ブロックの承認を待つ)を使用していることを確認してください。ウォレットの署名が完了すると処理が解決する、wagmi の生の useWriteContract は使用しないでください。
grep -rn "useWriteContract" packages/nextjs/
scaffold-eth の内部以外で該当するものがあれば、バグです。
注意:二重承認を許してしまう問題が 2 つあります。
wagmi の isPending は、トランザクションが確定したときではなく、ウォレットからトランザクションハッシュが返ると false になります。writeContractAsync は引き続き承認を待っています。この間は isPending = false かつ approveCooldown = false となり、処理中にボタンが再び有効になります。
修正には 2 つの状態が必要です:
approvalSubmitting— ハンドラーの先頭で設定し、finally {}で解除します(クリックからハッシュ取得までの間をカバー)approveCooldown—awaitの解決後に設定し、4 秒後に再取得とともに解除します(承認からキャッシュ更新までの間をカバー)
const [approvalSubmitting, setApprovalSubmitting] = useState(false);
const [approveCooldown, setApproveCooldown] = useState(false);
const handleApprove = async () => {
if (approvalSubmitting || approveCooldown) return;
setApprovalSubmitting(true);
try {
await approveWrite({ functionName: "approve", args: [spender, amount] });
setApproveCooldown(true);
setTimeout(() => { setApproveCooldown(false); refetchAllowance(); }, 4000);
} catch (e) {
notifyError("Approval failed");
} finally {
setApprovalSubmitting(false); // must be finally — releases on rejection too
}
};
<button disabled={isPending || approvalSubmitting || approveCooldown}>
- ❌ 不合格: ボタンの
disabledがisPendingまたはapproveCooldownの一方しか参照していない - ❌ 不合格:
approvalSubmitting状態がない、またはfinally {}で解除されない - ✅ 合格:
disabled={isPending || approvalSubmitting || approveCooldown}で、両方の状態が正しく管理されている
🚨 重要:SE2 のブランド要素を削除
AI エージェントはスキャフォールドを神聖視し、デフォルトのブランド要素をすべて残してしまいます。
- フッター: BuidlGuidl へのリンク、「Built with 🏗️ SE2」、「Fork me」リンク、サポートリンクを削除します。プロジェクト独自のリポジトリへのリンクに置き換えるか、すべて削除します
- タブのタイトル: アプリ名にします。「Scaffold-ETH 2」、「SE-2 App」、「App Name | Scaffold-ETH 2」は使用しません
- README: このプロジェクトについて説明します。SE2 テンプレートの README ではありません。「Built with Scaffold-ETH 2」のセクションと SE2 ドキュメントへのリンクを削除します
- ファビコン: SE2 のデフォルトを使用しないでください
重要:信頼に関する前提の CROPS レビュー
- ❌ 不合格: QA レポートに
CROPS Reviewブロックがない - ❌ 不合格: CROPS Review に一般的な値しか記載されておらず、誰がユーザーの利用を妨げられるのか、どのようなデータが漏洩するのか、誰が資金/アップグレード/復旧を管理するのか、ユーザーがどのように退出できるのかが明記されていない
- ❌ 不合格: アプリに本番環境に関わる信頼上の前提(資金、承認、カストディ、ウォレット権限、L2/ブリッジ、プライベートデータ、ID、ステーブルコイン、管理者権限、ホスト型 RPC/インデクサー/リレイヤー/ペイマスター/フロントエンド)があるにもかかわらず、CROPS Review が簡単な確認にとどまり、詳細なテンプレートに
crops/SKILL.mdを使用していない - ✅ 合格: 具体的な
CROPS Reviewブロックがあり、選択したデフォルト、受け入れた妥協点、ユーザーが利用をやめる方法が明記されている。内容の詳細さは、アプリの信頼上の前提に見合っている
重要:コントラクトアドレスの表示
- ❌ 不合格: デプロイ済みコントラクトのアドレスがページ上のどこにも表示されていない
- ✅ 合格:
<Address/>コンポーネントを使ってコントラクトアドレスを表示している(blockie、ENS、コピー、エクスプローラーへのリンク)
エージェントは接続中のウォレットアドレスを表示する一方で、ユーザーが操作しているコントラクトの表示を忘れてしまいます。
重要:アドレス入力 — 常に <AddressInput/> を使用
Ethereum アドレスを受け付けるすべての入力には、通常の <input type="text"> ではなく、必ず <AddressInput/> を使用してください。
- ❌ FAIL:
<input type="text" placeholder="0x..." value={addr} onChange={e => setAddr(e.target.value)} /> - ✅ PASS:
<AddressInput value={addr} onChange={setAddr} placeholder="0x... or ENS name" />
<AddressInput/> を使うと、ENS 解決(「vitalik.eth」と入力するとアドレスに解決)、ブロックieアバターのプレビュー、検証、貼り付け処理を利用できます。アドレスの収集に通常のテキスト入力を使うことは認められません。
SE2 では、@scaffold-ui/components にあります:
import { AddressInput } from "@scaffold-ui/components";
// or
import { AddressInput } from "~~/components/scaffold-eth"; // if re-exported
簡単な確認:
grep -rn 'type="text"' packages/nextjs/app/ | grep -i "addr\|owner\|recip\|0x"
grep -rn 'placeholder="0x' packages/nextjs/app/
一致するものがあれば → FAIL。<AddressInput/> に置き換えてください。
使い分け:表示には <Address/>、入力には <AddressInput/>。必ず守ってください。
重要:USD の値
- ❌ FAIL: ドル換算値を付けずに「1,000 TOKEN」や「0.5 ETH」と表示する
- ✅ PASS: USD 換算を添えて「0.5 ETH(約 $1,250)」と表示する
エージェントは、指示がない限り USD の値を追加しません。入力欄も含め、トークンまたは ETH の数量が表示される場所をすべて確認してください。
重要:OG 画像には絶対 URL が必須
- ❌ FAIL:
images: ["/thumbnail.jpg"]— 相対パスのため、どこでもリンクの展開が機能しない - ✅ PASS:
images: ["https://yourdomain.com/thumbnail.jpg"]— 本番環境の絶対 URL
簡単な確認:
grep -n "og:image\|images:" packages/nextjs/app/layout.tsx
重要:RPC とポーリングの設定
packages/nextjs/scaffold.config.ts を開きます。
- ❌ FAIL:
pollingInterval: 30000(デフォルト — UI の動作が不安定に感じられ、更新が 30 秒遅れる) - ✅ PASS:
pollingInterval: 3000 - ❌ FAIL: SE2 に同梱されているデフォルトの Alchemy API キーを使用する
- ❌ FAIL: コードで
process.env.NEXT_PUBLIC_*を参照しているが、デプロイ環境(Vercel/ホスティング)に変数が実際には設定されていない。公開 RPC(mainnet.base.orgなど)にフォールバックし、レート制限を受ける - ✅ PASS:
rpcOverridesがprocess.env.NEXT_PUBLIC_*変数を使用し、その環境変数がホスティングプラットフォームに設定されていることを確認済み - ❌ FAIL:
services/web3/wagmiConfig.tsxにベアのhttp()フォールバックトランスポートがまだ含まれている(気付かないうちに並行して公開 RPC に接続し、レート制限の原因になる) - ✅ PASS: ベアの
http()フォールバックを削除し、意図した設定済みトランスポートのみを残している
環境変数が参照されているだけでなく、設定されていることも確認してください。 AI エージェントは、コードを process.env を使うように変更し、パターンが PASS に一致したのを見て、Vercel/ホスティング環境に実際の変数を設定せずに先へ進んでしまいます。確認してください:
vercel env ls | grep RPC
重要:SE2 の externalContracts.ts への登録
スキャフォールドのフックは、deployedContracts.ts(自動生成)または externalContracts.ts(手動)に登録されたコントラクトでのみ機能します。外部コントラクトが登録されていない場合、フロントエンドの読み取り/書き込みはエラーを表示せずに失敗します。
- ❌ FAIL: フロントエンドのコードが、
packages/nextjs/contracts/externalContracts.tsにないトークン/プロトコルコントラクトを参照している - ❌ FAIL: 外部コントラクトの追加のために
deployedContracts.tsを手動で編集している - ✅ PASS: すべての外部コントラクトが、正しいチェーン、アドレス、ABI とともに
externalContracts.tsに定義されている
例:
export default {
8453: {
USDC: {
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
abi: [...],
},
},
} as const;
deployedContracts.ts を直接編集しないでください。デプロイ時に再生成されます。
重要:ダークモード — 背景色をハードコードしないでください
AI エージェントはダーク UI の見た目を好み、ページのラッパーに直接ハードコードしがちです。
// ❌ FAIL — hardcoded black background, ignores system preference AND DaisyUI theme
<div className="min-h-screen bg-[#0a0a0a] text-white">
これでは DaisyUI のテーマシステム全体が回避されます。ライトモードのユーザーにはページが黒く表示されます。SE2 ヘッダーの SwitchTheme 切り替えが機能しなくなり、prefers-color-scheme も無視されます。
次のパターンがないか確認してください:
grep -rn 'bg-\[#0\|bg-black\|bg-gray-9\|bg-zinc-9\|bg-neutral-9\|bg-slate-9' packages/nextjs/app/
ルートレイアウトの div またはページのラッパーで一致した場合 → FAIL。
- ❌ FAIL: ページのルートラッパーが、ハードコードされた 16 進カラーまたは Tailwind のダーク背景クラス(
bg-[#0a0a0a]、bg-black、bg-zinc-900など)を使用している - ❌ FAIL: ヘッダーに
SwitchTheme切り替えがあるのに、ページ全体でdata-themeが無視されている - ✅ PASS: すべての背景で DaisyUI のセマンティック変数(
bg-base-100、bg-base-200、text-base-content)を使用している - ✅ PASS(ダークモードのみの例外):
<html>にdata-theme="dark"を明示的に設定し、かつ ヘッダーから<SwitchTheme/>コンポーネントを削除している
修正方法:
// ✅ CORRECT — responds to light/dark toggle and prefers-color-scheme
<div className="min-h-screen bg-base-200 text-base-content">
重要:RainbowKit の Phantom Wallet
Phantom は SE2 のデフォルトのウォレットリストに含まれていません。Phantom を使っているユーザーは多いため、リストにないと接続できません。
- ❌ FAIL: RainbowKit のウォレットリストに Phantom ウォレットがない
- ✅ PASS:
wagmiConnectors.tsxにphantomWalletがある
重要:モバイルのディープリンク
RainbowKit v2 / WalletConnect v2 は、ウォレットアプリへのディープリンクを自動で行いません。 代わりにプッシュ通知に依存しますが、通知は遅く、信頼性も低いです。ディープリンクは自分で実装する必要があります。
モバイルでは、署名が必要なボタンをユーザーがタップしたときに、ウォレットアプリが開く必要があります。次のようにテストしてください。スマートフォンでアプリを開き、WalletConnect 経由でウォレットを接続し、アクションボタンをタップします。トランザクションが署名できる状態でウォレットアプリが開きますか?
- ❌ FAIL: 何も起こらず、ユーザーが手動でウォレットアプリに切り替える必要がある
- ❌ FAIL: トランザクションより先にディープリンクが実行され、ウォレットを開いても署名するものがない
- ❌ FAIL:
writeContractAsync()の前にwindow.location.href = "rainbow://"を呼び出している — 画面が移動し、TX が実行されない - ❌ FAIL: 間違ったウォレットが開く(例:ユーザーが Rainbow で接続したのに MetaMask が開く)
- ❌ FAIL: ウォレットのアプリ内ブラウザーでディープリンクを実行している(不要です。すでにウォレット内にいます)
- ✅ PASS: すべてのトランザクションボタンで、先に TX を実行してから、遅延を設けて正しいウォレットアプリへのディープリンクを実行する
実装方法
パターン:writeAndOpen ヘルパー。 先に書き込み呼び出しを実行し(WalletConnect 経由で TX リクエストを送信)、遅延後にディープリンクを実行してユーザーをウォレットに切り替えます。
const writeAndOpen = useCallback(
<T,>(writeFn: () => Promise<T>): Promise<T> => {
const promise = writeFn(); // Fire TX — does gas estimation + WC relay
setTimeout(openWallet, 2000); // Switch to wallet AFTER request is relayed
return promise;
},
[openWallet],
);
// Usage — wraps every write call:
await writeAndOpen(() => gameWrite({ functionName: "click", args: [...] }));
なぜ 2 秒なのですか? writeContractAsync では、ガスの見積もり、calldata のエンコード、WalletConnect のサーバー経由での署名リクエストの中継が必要です。300ms では短すぎるため、ウォレットがリクエストをまだ受け取れていません。
ウォレットの検出: wagmi の connector.id は "walletConnect" を返し、"rainbow" や "metamask" は返しません。複数の情報源を確認する必要があります。
const openWallet = useCallback(() => {
if (typeof window === "undefined") return;
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (!isMobile || window.ethereum) return; // Skip if desktop or in-app browser
// Check connector, wagmi storage, AND WalletConnect session data
const allIds = [connector?.id, connector?.name,
localStorage.getItem("wagmi.recentConnectorId")]
.filter(Boolean).join(" ").toLowerCase();
let wcWallet = "";
try {
const wcKey = Object.keys(localStorage).find(k => k.startsWith("wc@2:client"));
if (wcKey) wcWallet = (localStorage.getItem(wcKey) || "").toLowerCase();
} catch {}
const search = `${allIds} ${wcWallet}`;
const schemes: [string[], string][] = [
[["rainbow"], "rainbow://"],
[["metamask"], "metamask://"],
[["coinbase", "cbwallet"], "cbwallet://"],
[["trust"], "trust://"],
[["phantom"], "phantom://"],
];
for (const [keywords, scheme] of schemes) {
if (keywords.some(k => search.includes(k))) {
window.location.href = scheme;
return;
}
}
}, [connector]);
重要なルール:
- 先に TX を実行し、次にディープリンクを実行します。 書き込み呼び出しの前に
window.location.hrefを実行しないでください window.ethereumが存在する場合はディープリンクをスキップします — すでにウォレットのアプリ内ブラウザーにいることを意味します- localStorage の WalletConnect セッションデータを確認します —
connector.idだけでは、どのウォレットか分かりません rainbow://のようなシンプルなスキーム URL を使います — ページを再読み込みするrainbow://dapp/...は使わないでください- すべての書き込み呼び出しをラップします — approve、アクション、claim、batch など。メインの呼び出しだけではありません
🚨 重要:ブロックエクスプローラーでのコントラクト検証
デプロイ後、すべてのコントラクトをブロックエクスプローラーで必ず検証してください。未検証のコントラクトは、信頼性に疑問を抱かせます。ユーザーがソースコードを読めず、何かを隠しているように見えてしまいます。
- ❌ FAIL: デプロイ済みのコントラクトのいずれかについて、ブロックエクスプローラーに「Contract source code not verified」と表示される
- ✅ PASS: デプロイ済みのすべてのコントラクトについて、ブロックエクスプローラーに検証済みのソースコードと緑色のチェックマークが表示される
確認方法: deployedContracts.ts から各コントラクトのアドレスを取得し、ブロックエクスプローラー(Etherscan、Basescan、Arbiscan など)で開いて、「Contract」タブに ✅ チェックマークがあることを確認します。バイトコードのみが表示されている場合は、未検証です。
修正方法(SE2):
yarn verify --network mainnet # or base, arbitrum, optimism, etc.
修正方法(Foundry):
forge verify-contract <ADDRESS> <CONTRACT> --chain <CHAIN_ID> --etherscan-api-key $ETHERSCAN_API_KEY
AI エージェントは、yarn deploy が成功すると次に進んでしまい、検証を省略することがよくあります。検証が完了するまで、デプロイは完了ではありません。
重要:ボタンの読み込み状態 — DaisyUI の loading クラスは誤りです
DaisyUI + SE2 を使用する場合、AI エージェントはボタンの読み込み状態をほぼ必ず誤って実装します。
誤った実装: btn に loading をクラスとして直接追加する:
// ❌ FAIL — DaisyUI's `loading` class on a `btn` replaces the entire button content
// with a spinner that fills the full button. No text, misaligned, looks broken.
<button className={`btn btn-primary ${isPending ? "loading" : ""}`}>
{isPending ? "Approving..." : "Approve"}
</button>
正しい実装: ボタンのクラスから loading を削除し、テキストの横にあるボタン内に loading-spinner の span を追加する:
// ✅ PASS — small spinner inside the button, text visible next to it
<button className="btn btn-primary" disabled={isPending}>
{isPending && <span className="loading loading-spinner loading-sm mr-2" />}
{isPending ? "Approving..." : "Approve"}
</button>
コード内で確認する項目:
grep -rn '"loading"' packages/nextjs/app/
ボタンの className に "loading" という文字列があれば → FAIL。
- ❌ FAIL: ボタンに
className={... isPending ? "loading" : ""}がある - ✅ PASS: ボタン内に
<span className="loading loading-spinner loading-sm" />がある
重要:SE2 のピル型入力欄(--radius-field)
SE2 の DaisyUI テーマでは、デフォルトで --radius-field: 9999rem が設定されており、テキストエリアやセレクトボックスがピル型になって、コンテンツが切り取られることがよくあります。
- ❌ FAIL:
packages/nextjs/styles/globals.cssに--radius-field: 9999remが残っている - ✅ PASS: ライトテーマとダークテーマの両方で、
--radius-fieldが0.5rem(または同等の値)に変更されている
テーマで修正します(コンポーネントごとではありません):
/* In BOTH @plugin "daisyui/theme" blocks */
--radius-field: 0.5rem;
各入力欄に rounded-* ユーティリティクラスを追加して対処しないでください。テーマレベルで一度だけ修正してください。
SE2 の参考資料
- ドキュメント:https://docs.scaffoldeth.io/
- UI コンポーネント:https://ui.scaffoldeth.io/
- SpeedRun Ethereum:https://speedrunethereum.com/
監査概要
各項目を PASS または FAIL として報告してください。
リリースを妨げる問題
- ウォレット接続時にテキストではなくボタンが表示される
- ネットワークが異なる場合、ヘッダーのドロップダウンだけでなく、プライマリ CTA の位置にネットワーク切り替えボタンが表示される
- 一度に表示するボタンは1つ(接続 → ネットワーク → 承認 → アクション)
- 承認ボタンは一連の処理全体を通してロックされる:
approvalSubmitting(クリック→ハッシュ)、approveCooldown(承認→キャッシュ更新)。両方の状態が必要で、どちらもdisabledプロパティに設定されている - ブロックエクスプローラー(Etherscan/Basescan/Arbiscan)でコントラクトが検証されており、誰でもソースコードを読める
- QA レポートに CROPS レビューが含まれている:選択したデフォルト設定、許容した妥協点、ユーザーをブロックできる人、漏えいするデータ、資金・アップグレード・復旧を管理する人、ユーザーが離脱する方法を明記している
- SE2 のフッターのブランディングが削除されている
- SE2 のタブタイトルが削除されている
- SE2 の README が置き換えられている
修正必須
-
<Address/>で表示されるコントラクトアドレス - すべてのアドレス入力に
<AddressInput/>を使用 — アドレスに生の<input type="text">を使わない - すべてのトークン/ETH の金額の横に USD の値を表示
- OG 画像の URL は本番環境の絶対 URL
- pollingInterval は 3000
- RPC のオーバーライドを設定(デフォルトの SE2 キーは使わない)し、ホスティングプラットフォームで環境変数が設定済みであることを確認
- Favicon を SE2 のデフォルトから変更
-
globals.cssの--radius-fieldを9999remから0.5rem(または同程度の値)に変更 — ピル型のテキストエリアを使わない - すべてのコントラクトエラーを人が読めるメッセージにマッピング — 握りつぶす catch ブロックや、生の 16 進数セレクターを使わない
- ハードコードされた暗い背景色を使わない — ページのラッパーには
bg-base-200 text-base-contentを使用(またはdata-theme="dark"を強制し、<SwitchTheme/>を削除) - ボタンのローディング表示にはインラインの
<span className="loading loading-spinner loading-sm" />を使用 — ボタン自体にclassName="... loading"を指定しない - RainbowKit のウォレット一覧に Phantom ウォレットを追加
- モバイル:すべてのトランザクションボタンでウォレットへのディープリンクを実行(先に TX を実行し、その後
setTimeout(openWallet, 2000)を呼び出す) - モバイル:ウォレットの検出には
connector.idだけでなく、WC のセッションデータを確認 - モバイル:
window.ethereumが存在する場合(アプリ内ブラウザー)はディープリンクしない