ABI Toolchain
Treat your ABI like any other versioned artifact. Most frontend-contract sync bugs are ABI lifecycle problems in disguise.
Scripts
Ready-to-use tools in scripts/:
| Script | Purpose |
|---|
| INLINECODE1 | Sync compiled ABIs from Foundry/Hardhat artifacts to frontend |
| INLINECODE2 |
Compare two ABI files: find added/removed/changed, flag breaking changes |
sync-abi.sh
CODEBLOCK0
Handles both Foundry (out/Foo.sol/Foo.json) and Hardhat (artifacts/contracts/) artifacts. Uses jq if available, falls back to Python.
abi-diff.js
CODEBLOCK1
Accepts raw ABI arrays or Foundry/Hardhat artifacts (auto-detected).
ABI Types Reference
See references/abi-formats.md for complete coverage of ABI entry types, function selectors, event topics, Foundry artifact structure, and common gotchas (tuples, uint vs uint256, as const).
The Core Problem
When a contract changes:
- 1. New ABI gets compiled by Foundry/Hardhat
- Frontend still imports the old ABI from a file that wasn't updated
- Calls either fail silently or revert on-chain
The fix isn't careful manual updating — it's making the pipeline impossible to get wrong.
Pattern 1: Foundry → TypeScript Auto-Sync
After every forge build, auto-export ABIs to your frontend:
CODEBLOCK2
Add to foundry.toml as a post-build hook or wire into package.json:
CODEBLOCK3
Pattern 2: Typed ABIs with Viem (no codegen)
Viem's as const trick gives you full TypeScript types directly from your ABI JSON:
CODEBLOCK4
Pattern 3: Wagmi CLI Code Generation
For large projects, @wagmi/cli generates fully typed React hooks from your ABIs:
CODEBLOCK5
CODEBLOCK6
CODEBLOCK7
Add to CI: npx wagmi generate && git diff --exit-code src/generated.ts — fails if ABI was changed but not regenerated.
Pattern 4: Proxy Contract ABIs
Proxy contracts (UUPS, Transparent) have two ABIs:
- 1. Proxy ABI — just
upgradeTo, upgradeToAndCall, admin functions - Implementation ABI — your actual business logic
Always use the implementation ABI for user-facing calls, pointed at the proxy address:
CODEBLOCK8
For Hardhat upgrades, the generated .json artifacts include the merged ABI automatically. For Foundry, merge manually:
CODEBLOCK9
Pattern 5: CI/CD Enforcement
Block merges where ABI changed but frontend wasn't updated:
CODEBLOCK10
Common Failure Modes
| Symptom | Cause | Fix |
|---|
| INLINECODE17 on-chain | Calling old function name that was renamed | Re-sync ABI, check function selector |
| TypeScript accepts wrong arg type |
as const missing on ABI | Add
as const to ABI definition |
| Proxy call reverts | Using proxy ABI instead of implementation ABI | Always use implementation ABI at proxy address |
| Works in dev, fails on mainnet | ABI from local build ≠ deployed contract | Pin ABI to verified deployment, not latest build |
| Wagmi hook types wrong | Generated file not up to date | Re-run
npx wagmi generate |
References
- - ABI formats, types, selectors, gotchas: INLINECODE21
- Viem ABI types: https://viem.sh/docs/glossary/types#abi
- Wagmi CLI: https://wagmi.sh/cli/getting-started
- Foundry artifacts format: https://book.getfoundry.sh/reference/forge/forge-build
ABI 工具链
将你的 ABI 像其他版本化工件一样对待。大多数前端-合约同步错误本质上都是 ABI 生命周期问题。
脚本
scripts/ 中即用型工具:
| 脚本 | 用途 |
|---|
| sync-abi.sh | 将编译后的 ABI 从 Foundry/Hardhat 工件同步到前端 |
| abi-diff.js |
比较两个 ABI 文件:查找新增/删除/变更,标记破坏性变更 |
sync-abi.sh
bash
设置要同步的合约:在项目根目录创建 .abi-sync
echo MyToken >> .abi-sync
echo MyVault:Vault >> .abi-sync # 写入为 Vault.json
从项目根目录运行
ABI
SOURCE=out ABIDEST=frontend/src/abis bash path/to/sync-abi.sh
或使用默认值(Foundry: out/ → frontend/src/abis/)
bash scripts/sync-abi.sh
同时处理 Foundry(out/Foo.sol/Foo.json)和 Hardhat(artifacts/contracts/)工件。优先使用 jq,否则回退到 Python。
abi-diff.js
bash
node scripts/abi-diff.js old/MyToken.json new/MyToken.json
→ { added: [], removed: [], changed: [], breaking: false, summary: 0 added, 0 removed, 1 changed }
破坏性变更时退出码为 1(在 CI 中很有用):
node scripts/abi-diff.js prev.json current.json || echo BREAKING CHANGE
接受原始 ABI 数组或 Foundry/Hardhat 工件(自动检测)。
ABI 类型参考
参见 references/abi-formats.md 了解 ABI 条目类型、函数选择器、事件主题、Foundry 工件结构以及常见陷阱(元组、uint 与 uint256、as const)的完整覆盖。
核心问题
当合约发生变更时:
- 1. Foundry/Hardhat 编译出新的 ABI
- 前端仍然从未更新的文件中导入旧的 ABI
- 调用要么静默失败,要么在链上回滚
解决方案不是小心地手动更新——而是让流水线不可能出错。
模式 1:Foundry → TypeScript 自动同步
每次 forge build 后,自动将 ABI 导出到前端:
bash
scripts/sync-abi.sh
#!/bin/bash
set -e
CONTRACTS=(MyToken MyVault MyFactory)
SRC=out # Foundry 输出目录
DEST=frontend/src/abis
mkdir -p $DEST
for contract in ${CONTRACTS[@]}; do
jq .abi $SRC/$contract.sol/$contract.json > $DEST/$contract.json
echo ✅ 已同步 $contract ABI
done
添加到 foundry.toml 作为构建后钩子,或接入 package.json:
json
{
scripts: {
build:contracts: forge build && bash scripts/sync-abi.sh,
dev: npm run build:contracts && next dev
}
}
模式 2:使用 Viem 的类型化 ABI(无需代码生成)
Viem 的 as const 技巧让你直接从 ABI JSON 获得完整的 TypeScript 类型:
typescript
// abis/MyToken.ts — 从同步的 JSON 导出
export const myTokenAbi = [
{
name: transfer,
type: function,
stateMutability: nonpayable,
inputs: [{ name: to, type: address }, { name: amount, type: uint256 }],
outputs: [{ name: , type: bool }],
},
// ...
] as const // ← 关键:让 TypeScript 推断精确类型
// 现在 readContract/writeContract 会类型检查函数名和参数
const result = await client.readContract({
abi: myTokenAbi,
functionName: transfer, // ← 自动补全,拼写错误在编译时捕获
args: [0x..., 100n], // ← 参数类型被推断
})
模式 3:Wagmi CLI 代码生成
对于大型项目,@wagmi/cli 从你的 ABI 生成完全类型化的 React 钩子:
bash
npm install --save-dev @wagmi/cli
typescript
// wagmi.config.ts
import { defineConfig } from @wagmi/cli
import { foundry, react } from @wagmi/cli/plugins
export default defineConfig({
out: src/generated.ts,
plugins: [
foundry({ project: ../contracts }), // 直接读取 Foundry 工件
react(), // 生成 useReadMyToken、useWriteMyToken 等
],
})
bash
npx wagmi generate # 每次合约变更时重新生成
添加到 CI:npx wagmi generate && git diff --exit-code src/generated.ts — 如果 ABI 已变更但未重新生成则失败。
模式 4:代理合约 ABI
代理合约(UUPS、透明代理)有两个 ABI:
- 1. 代理 ABI — 仅包含 upgradeTo、upgradeToAndCall、管理员函数
- 实现 ABI — 你的实际业务逻辑
始终对面向用户的调用使用实现 ABI,指向代理地址:
typescript
// 错误:使用代理 ABI 会丢失所有函数
const client = getContract({ address: proxyAddr, abi: proxyAbi })
// 正确:实现 ABI + 代理地址
const client = getContract({ address: proxyAddr, abi: myContractV2Abi })
对于 Hardhat 升级,生成的 .json 工件会自动包含合并后的 ABI。对于 Foundry,手动合并:
bash
合并代理 + 实现 ABI
jq -s .[0].abi + .[1].abi | unique_by(.name) \
out/ERC1967Proxy.sol/ERC1967Proxy.json \
out/MyContractV2.sol/MyContractV2.json \
> frontend/src/abis/MyContractProxy.json
模式 5:CI/CD 强制执行
阻止 ABI 已变更但前端未更新的合并:
yaml
.github/workflows/abi-check.yml
run: |
forge build
bash scripts/sync-abi.sh
git diff --exit-code frontend/src/abis/
# 如果任何 ABI 文件已变更但未提交更新则失败
常见失败模式
| 症状 | 原因 | 修复 |
|---|
| 链上 function not found | 调用已重命名的旧函数名 | 重新同步 ABI,检查函数选择器 |
| TypeScript 接受错误的参数类型 |
ABI 上缺少 as const | 在 ABI 定义中添加 as const |
| 代理调用回滚 | 使用代理 ABI 而非实现 ABI | 始终在代理地址使用实现 ABI |
| 开发环境正常,主网失败 | 本地构建的 ABI ≠ 已部署的合约 | 将 ABI 固定到已验证的部署,而非最新构建 |
| Wagmi 钩子类型错误 | 生成的文件未更新 | 重新运行 npx wagmi generate |
参考
- - ABI 格式、类型、选择器、陷阱: references/abi-formats.md
- Viem ABI 类型:https://viem.sh/docs/glossary/types#abi
- Wagmi CLI:https://wagmi.sh/cli/getting-started
- Foundry 工件格式:https://book.getfoundry.sh/reference/forge/forge-build