Speedrun Ethereum Tokenization Checkpoint 3 - MetaMask Mint 交易耗尽 Gas
Checkpoint 3:MetaMask Mint 交易耗尽 gas
挑战地址:https://speedrunethereum.com/challenge/tokenization
本文是 Speedrun Ethereum Tokenization 系列第二篇,记录 Checkpoint 3 中 Mint NFT 失败的完整排错过程。
隐私说明:本文不包含真实钱包地址、交易哈希、部署 URL、IPFS 哈希或本地绝对路径。
系列目录
- 索引:完整排错路线
- 上一篇:Checkpoint 2 - Deploy to a Testnet
- 当前:Checkpoint 3 - Mint NFTs
- 下一篇:Checkpoint 4 - Ship
问题:MetaMask EIP-7702 智能账户 Mint 时耗尽 gas
当前 Challenge 步骤:Checkpoint 3 - Mint NFTs
合约已经部署到 Sepolia,前端也已经连接到新合约。现在正在通过 MetaMask 调用
mintItem铸造 NFT。
1. 现象
合约已经在 Sepolia 部署成功:
1 | YourCollectible: |
前端连接 MetaMask 后点击 Mint,MetaMask 显示交易已经确认,但链上最终失败。
交易哈希:
1 | <FAILED_MINT_TX_HASH> |
交易信息:
1 | From: |
表面上看,这笔交易像是在调用 MetaMask Delegation Framework,而不是直接调用 NFT 合约。
但解码 calldata 后可以看到,内层调用目标是:
1 | <DEPLOYED_CONTRACT_ADDRESS> |
函数选择器是:
1 | 0x110bcd45 |
对应函数:
1 | mintItem(address,string) |
所以 MetaMask 的 EIP-7702 智能账户把真正的 mintItem 调用包装在:
1 | redeemDelegations(...) |
里面执行。
2. 先排除 ERC-721 Receiver 问题
mintItem 内部调用的是:
1 | _safeMint(to, tokenId); |
如果 to 是合约,ERC-721 会调用:
1 | onERC721Received(...) |
最初我怀疑 MetaMask EIP-7702 账户有代码,但没有实现 ERC-721 Receiver,导致 safe mint 失败。
链上查询后可以排除这个原因:
1 | supportsInterface(0x150b7a02) -> true |
这说明 MetaMask 智能账户已经正确实现了 ERC-721 Receiver。
因此失败不是收款账户不支持 NFT safe mint。
3. 重放交易后找到真实失败点
使用 Foundry 重放失败交易:
1 | cast run \ |
关键 trace 简化后如下:
1 | DelegationManager::redeemDelegations(...) |
这说明:
mintItem已经被成功调用。- Transfer 事件已经发出。
- MetaMask 智能账户的
onERC721Received已经返回正确结果。 - 后续执行 token URI 相关逻辑时,内层调用耗尽 gas。
- 最外层交易回滚,所以前面的 Mint 状态也全部撤销。
链上最终状态也能证明回滚:
1 | tokenIdCounter() = 0 |
4. Gas 数据对比
失败交易中,内层 mintItem 大约只获得:
1 | 961,602 gas |
但直接在 Sepolia 上估算同样的 mintItem 调用,需要:
1 | 1,032,313 gas |
验证结果:
1 | 961,602 gas -> out of gas |
外层 EIP-7702 交易虽然设置了:
1 | 1,448,890 gas |
但其中相当一部分被以下逻辑消耗:
redeemDelegations- delegation 签名验证
- balance enforcer
- ERC-721 balance enforcer
executeFromExecutor
留给真正 mintItem 的 gas 只有约 961,602,不足以完成需要约 1,032,313 gas 的 Mint。
5. 与问题二的关系
问题二和问题三都属于 OutOfGas,但发生层级不同:
1 | 问题二:Sepolia 部署 |
两者的共同点是 gas 不足。
不同点是:
- 问题二是 Foundry 对部署交易的估算偏低。
- 问题三是 MetaMask EIP-7702 包装交易产生了额外 gas 开销。
6. 推荐解决方案一:使用普通 MetaMask EOA
这个挑战的标准流程默认使用普通 EOA。
如果 MetaMask 地址已经开启 Smart Account,并被升级为 EIP-7702 委托账户,Mint 交易就会经过 redeemDelegations,额外 gas 开销明显增加。
最简单、最稳定的解决方式是:
- 在 MetaMask 中切换到普通 EOA 账户。
- 确保账户连接到 Sepolia。
- 使用 Sepolia 测试 ETH。
- 在前端重新点击 Mint。
普通 EOA 直接调用 mintItem 时,不需要经过 delegation 包装,gas 开销会小很多。
7. 推荐解决方案二:为前端 Mint 请求提高 gas
前端 Mint 代码位于:
1 | packages/nextjs/app/myNFTs/page.tsx |
原来的调用:
1 | await writeContractAsync({ |
可以改为:
1 | await writeContractAsync({ |
增加的是:
1 | gas: 3000000n, |
直接 Mint 实际需要约 1,032,313 gas。设置 3,000,000 是为了给 EIP-7702 包装逻辑预留足够空间。
修改后需要:
- 保存文件。
- 等待 Next.js 编译完成。
- 强制刷新网页,例如
Command + Shift + R。 - 重新发起 Mint。
- 在 MetaMask 中确认 gas limit 确实提高。
如果 MetaMask 仍然强制使用自己的 gas 估算,或者交易依旧是低 gas 的 redeemDelegations,则应使用普通 EOA 或 CLI 方案。
8. 推荐解决方案三:使用 CLI 直接 Mint
mintItem 没有访问控制限制,任何账户都可以调用。
因此可以使用已经创建的:
1 | sepolia-deployer |
发起 Mint,并把 NFT 直接铸给 MetaMask 地址:
1 | cd "/path/to/challenge-tokenization/packages/foundry" |
输入 sepolia-deployer 的密码。
成功后:
- NFT 会属于:
1 | <YOUR_WALLET_ADDRESS> |
tokenIdCounter会变成1balanceOf(<YOUR_WALLET_ADDRESS>)会变成1- 前端刷新后应该能看到这个 NFT
这种方式绕过了 MetaMask EIP-7702 包装层,适合先完成挑战和验证合约。
9. Mint 问题复盘
遇到 MetaMask “已确认但前端报错”时,不要只看钱包确认页面,还要检查:
1 | 交易类型 |
本次交易的类型和函数:
1 | Type 4 |
说明它经过了 MetaMask EIP-7702 智能账户包装,而不是普通 ERC-721 调用。
最终 trace:
1 | mintItem -> Transfer -> onERC721Received -> OutOfGas |
说明不是合约不接受 NFT,也不是 Solidity 业务条件 revert,而是内层调用拿到的 gas 不够。
下一篇
Checkpoint 3 完成后,继续阅读:
返回:完整排错路线
