dApp预发布审计Skill qa

此技能用于审核基于Scaffold-ETH 2构建的以太坊去中心化应用(dApp)在发布前的常见错误,确保dApp质量,覆盖钱包连接、按钮流程、品牌移除等关键点。关键词:dApp审计、Scaffold-ETH 2、钱包连接、按钮流程、品牌移除、区块链测试、移动端深度链接、智能合约、QA检查。

DApp开发 0 次安装 0 次浏览 更新于 3/24/2026

名称: qa 描述: 针对基于Scaffold-ETH 2构建的以太坊dApp的预发布审计检查清单。在构建完成后,将此交给单独的审核代理(或新上下文)。仅覆盖AI代理实际发布的错误——通过与标准LLMs的基线测试验证。

dApp QA — 预发布审计

此技能用于审核,而非构建。 在dApp构建完成后,交给新代理。审核员应:

  1. 阅读源代码(app/components/contracts/
  2. 在浏览器中打开应用并点击每个流程
  3. 检查以下每个项目——报告PASS/FAIL,不要修复

🚨 关键:钱包流程——按钮而非文本

打开应用,未连接钱包。

  • FAIL: 显示文本如“连接你的钱包来玩” / “请连接以继续” / 任何告诉用户连接的段落
  • PASS: 一个大的、明显的连接钱包按钮是主要的UI元素

这是AI代理最常见的错误。 每个标准LLM都会写<p>请连接你的钱包</p>而不是渲染<RainbowKitCustomConnectButton />


🚨 关键:四状态按钮流程

应用必须一次只显示一个主按钮,按以下顺序进展:

1. 未连接  → 连接钱包按钮
2. 错误网络  → 切换到[链]按钮
3. 需要批准 → 批准按钮
4. 就绪          → 操作按钮(质押/存款/交换)

具体检查:

  • FAIL: 批准和操作按钮同时可见
  • FAIL: 无网络检查——应用尝试在错误链上工作并静默失败
  • FAIL: 用户可以点击批准,在钱包中签名,返回,并在交易待处理时再次点击批准
  • PASS: 一次一个按钮。批准按钮显示旋转器,保持禁用直到区块在链上确认。然后切换到操作按钮。

在代码中: 按钮的disabled属性必须绑定到useScaffoldWriteContractisPending。验证它使用useScaffoldWriteContract(等待区块确认),而不是原始的wagmi useWriteContract(在钱包签名时解析):

grep -rn "useWriteContract" packages/nextjs/

任何在scaffold-eth内部之外的匹配 → 错误。


🚨 关键: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默认

重要:合同地址显示

  • FAIL: 部署的合同地址在页面上无处显示
  • PASS: 合同地址使用<Address/>组件显示(blockie、ENS、复制、浏览器链接)

代理显示连接的钱包地址,但忘记显示用户正在交互的合同。


重要:USD值

  • FAIL: 代币金额显示为“1,000 TOKEN”或“0.5 ETH”,没有美元值
  • PASS: “0.5 ETH (~$1,250)”带USD转换

代理从不主动添加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_*变量并且环境变量在托管平台上确认设置

验证环境变量已设置,不仅仅是引用。 AI代理将更改代码以使用process.env,看到模式匹配PASS,然后继续——从未在Vercel/托管上设置实际变量。检查:

vercel env ls | grep RPC

重要:RainbowKit中的Phantom钱包

Phantom不在SE2默认钱包列表中。许多用户有Phantom——如果缺失,他们无法连接。

  • FAIL: Phantom钱包不在RainbowKit钱包列表中
  • PASS: phantomWalletwagmiConnectors.tsx

重要:移动端深度链接

RainbowKit v2 / WalletConnect v2不自动深度链接到钱包应用。 它依赖推送通知,这很慢且不可靠。你必须自己实现深度链接。

在移动端,当用户点击需要签名的按钮时,必须打开他们的钱包应用。测试此:在手机上打开应用,通过WalletConnect连接钱包,点击操作按钮——钱包应用是否打开并准备签名交易?

  • FAIL: 无反应,用户必须手动切换到钱包应用
  • FAIL: 深度链接在交易之前触发——用户到达钱包但无事可签
  • FAIL: window.location.href = "rainbow://"writeContractAsync()之前调用——导航离开,交易从未触发
  • FAIL: 它打开错误钱包(例如,用户用Rainbow连接,却打开MetaMask)
  • FAIL: 在钱包的应用内浏览器中进行深度链接(不必要——你已经在钱包中)
  • PASS: 每个交易按钮先触发交易,然后延迟深度链接到正确的钱包应用

如何实现

模式:writeAndOpen辅助函数。 先触发写入调用(通过WalletConnect发送交易请求),然后延迟深度链接以切换用户到他们的钱包:

const writeAndOpen = useCallback(
  <T,>(writeFn: () => Promise<T>): Promise<T> => {
    const promise = writeFn(); // 触发TX——进行gas估计和WC中继
    setTimeout(openWallet, 2000); // 在请求中继后切换到钱包
    return promise;
  },
  [openWallet],
);

// 用法——包装每个写入调用:
await writeAndOpen(() => gameWrite({ functionName: "click", args: [...] }));

为什么2秒? writeContractAsync必须估计gas、编码调用数据,并通过WalletConnect的服务器中继签名请求。300毫秒太快——钱包可能尚未收到请求。

检测钱包: 来自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; // 如果是桌面或应用内浏览器,跳过

  // 检查连接器、wagmi存储和WalletConnect会话数据
  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]);

关键规则:

  1. 先触发交易,后深度链接。 永远不要在写入调用之前window.location.href
  2. 如果window.ethereum存在,跳过深度链接——意味着你已经在钱包的应用内浏览器中
  3. 检查WalletConnect会话数据在localStorage中——仅connector.id不会告诉你哪个钱包
  4. 使用简单方案URLrainbow://——不是rainbow://dapp/...,这会重新加载页面
  5. 包装每个写入调用——批准、操作、认领、批量——不仅仅是主要的

审计总结

报告每个为PASS或FAIL:

阻止发布

  • [ ] 钱包连接显示按钮,而非文本
  • [ ] 错误网络显示切换按钮
  • [ ] 一次一个按钮(连接 → 网络 → 批准 → 操作)
  • [ ] 批准按钮在区块确认期间禁用并显示旋转器
  • [ ] SE2页脚品牌移除
  • [ ] SE2标签标题移除
  • [ ] SE2 README替换

应修复

  • [ ] 合同地址使用<Address/>显示
  • [ ] 所有代币/ETH金额旁边有USD值
  • [ ] OG图像是绝对生产URL
  • [ ] pollingInterval是3000
  • [ ] RPC覆盖设置(不是默认SE2密钥)并且环境变量在托管平台上确认设置
  • [ ] 网站图标从SE2默认更新
  • [ ] Phantom钱包在RainbowKit钱包列表中
  • [ ] 移动端:所有交易按钮深度链接到钱包(先触发交易,然后setTimeout(openWallet, 2000)
  • [ ] 移动端:钱包检测检查WC会话数据,不仅仅是connector.id
  • [ ] 移动端:当window.ethereum存在时无深度链接(应用内浏览器)