ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

wagmi 中 simulateContract Action 完全指南:合约交互模拟与校验实战

wagmi 中 simulateContract Action 完全指南:合约交互模拟与校验实战 wagmi 中 simulateContract Action 完全指南合约交互模拟与校验实战【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmisimulateContract是 wagmiwagmi/core中用于模拟/校验合约交互的 Action它会在真实发送交易之前先在链上“试跑”一遍调用验证调用是否会成功、预估执行结果与交易请求参数。本指南基于 simulateContract.md 文档结合仓库源码与测试完整讲解其导入方式、全部参数、返回值、类型推断机制与底层实现原理帮助你在 wagmi 应用中安全地预检写入操作。什么是 simulateContractsimulateContract的核心定位是Action for simulating/validating a contract interaction——即对一次合约交互进行模拟与校验。它与writeContract通常成对出现先用simulateContract确认调用不会 revert回滚拿到包含abi、address、functionName、args等完整信息的request再把这个request直接交给writeContract发送真实交易。从源码看该 Action 的实现位于 simulateContract.ts它内部是对 viem 的simulateContract的封装并额外完成三件事解析执行账户如果参数中未显式传入account则通过getConnectorClient从当前连接的钱包/连接器Connector中取出账户见 simulateContract.ts获取目标链客户端通过config.getClient({ chainId })定位到指定链的 viem Client并用getAction提取或回退到 tree-shakable 的simulateContract实现见 simulateContract.ts 与 getAction.ts组装返回值返回{ chainId, result, request }三元组request上会补上本次模拟所用的chainId见 simulateContract.ts。Import导入方式simulateContract由wagmi/core包导出可直接命名导入import { simulateContract } from wagmi/core相关的类型同样从wagmi/core导出import { type SimulateContractParameters } from wagmi/core import { type SimulateContractReturnType } from wagmi/core import { type SimulateContractErrorType } from wagmi/core如果你是使用框架适配层如 React还可以在wagmi/core/query中获取simulateContractQueryOptions等 TanStack Query 集成类型或直接使用wagmi包的useSimulateContractHook见 useSimulateContract.ts。Usage基本用法以一个典型的 ERC-20transferFrom授权转账为例。假定项目中已有 ABI 文件abi.ts与配置config.ts仓库中对应的示例片段分别为 abi-write.ts 与 config.ts调用方式如下import { simulateContract } from wagmi/core import { abi } from ./abi import { config } from ./config const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], })其中abi.ts片段定义了approve与transferFrom两个函数并用as const断言以获得最大程度的类型推断export const abi [ { type: function, name: approve, stateMutability: nonpayable, inputs: [ { name: spender, type: address }, { name: amount, type: uint256 }, ], outputs: [{ type: bool }], }, { type: function, name: transferFrom, stateMutability: nonpayable, inputs: [ { name: sender, type: address }, { name: recipient, type: address }, { name: amount, type: uint256 }, ], outputs: [{ type: bool }], }, ] as constconfig.ts片段展示了标准的多链配置主网 Sepolia 测试网每个链一个 HTTP transportimport { createConfig, http } from wagmi/core import { mainnet, sepolia } from wagmi/core/chains export const config createConfig({ chains: [mainnet, sepolia], transports: { [mainnet.id]: http(), [sepolia.id]: http(), }, })Parameters参数详解simulateContract接受一个参数对象其类型为SimulateContractParameters。以下逐一说明每个字段的语义、类型与使用示例。abi类型Abi说明合约的 ABI应用二进制接口用于描述合约可调用的函数、事件与返回结构。关于如何设置 ABI 以获得最大类型推断与安全性可参考仓库中的 TypeScript 文档 typescript.md。import { simulateContract } from wagmi/core import { abi } from ./abi // [!code focus] import { config } from ./config const result await simulateContract(config, { abi, // [!code focus] address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], })accessList类型AccessList | undefined说明访问列表EIP-2930。提前声明交易将访问的地址与存储槽可用于在部分场景下降低 gas 成本或适配不支持 EIP-1559 的网络。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], accessList: [{ // [!code focus] address: 0x1, // [!code focus] storageKeys: [0x1], // [!code focus] }], // [!code focus] })account类型Address | Account | undefined说明用于签署数据的账户。若在connector上找不到该账户会抛出错误。当不传入时wagmi 会回退到getConnectorClient自动解析当前连接账户见 simulateContract.ts这一点在测试parameters: account用例中也有印证见 simulateContract.test.ts。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], account: 0xd2135CfB216b74109775236E36d4b433F1DF507B, // [!code focus] })address类型Address说明目标合约的部署地址。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, // [!code focus] functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], })args类型readonly unknown[] | undefined说明调用合约时传入的参数列表。类型推断由abi与functionName联合推断得出——也就是说只要 ABI 定义正确参数的类型、顺序与数量会在编译期被严格约束。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ // [!code focus] 0xd2135CfB216b74109775236E36d4b433F1DF507B, // [!code focus] 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, // [!code focus] 123n, // [!code focus] ], // [!code focus] })blockNumber类型bigint | undefined说明模拟所针对的区块高度。指定后会在该历史区块的状态上执行模拟适合需要“回看”历史时刻调用结果的场景。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], blockNumber: 17829139n, // [!code focus] })blockTag类型latest | earliest | pending | safe | finalized | undefined说明模拟所针对的区块标签。默认通常为latestsafe/finalized适用于对确定性有要求的场景pending则模拟未打包的待定状态。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], blockTag: safe, // [!code focus] })注意blockNumber与blockTag互斥只能二选一指定模拟目标区块。chainId类型config[chains][number][id] | undefined说明发送交易前用于校验的链 ID。它限定模拟在哪条链上执行同时参与返回值中chainId与request.chainId的组装。类型上它被约束为当前config所配置链的 ID 集合见 simulateContract.ts 与 properties.ts 中的ChainIdParameter。import { mainnet } from wagmi/chains // [!code focus] // 实际项目中更推荐import { mainnet } from wagmi/core/chains const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], chainId: mainnet.id, // [!code focus] })connector类型Connector | undefined说明用于模拟交易的连接器钱包适配器。通常在调用前先通过getConnection拿到当前激活的connector再传入测试用例parameters: connector演示了先connect再以 connector 发起模拟的完整流程见 simulateContract.test.ts。import { getConnection, simulateContract } from wagmi/core import { abi } from ./abi import { config } from ./config const { connector } getConnection(config) const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], connector, // [!code focus] })dataSuffix类型0x${string} | undefined说明追加在 calldata 末尾的数据。常用于给交易附加“域名标签”domain tag等元信息例如 OpenSea Seaport 订单归属标记。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], dataSuffix: 0xdeadbeef, // [!code focus] })functionName类型string说明要调用的合约函数名。类型推断由abi推断得出——函数名必须是 ABI 中存在的nonpayable/payable写入函数否则编译期即报错。源码类型签名中明确限定了ContractFunctionNameabi, nonpayable | payable见 simulateContract.ts。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: approve, // [!code focus] args: [0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n] })gas类型bigint | undefined说明交易执行提供的 gas 上限。示例中使用 viem 的parseGwei(20)将 20 Gwei 解析为 wei 单位的 bigint。import { parseGwei } from viem const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], gas: parseGwei(20), // [!code focus] })gasPrice类型bigint | undefined说明每单位 gas 的价格以 wei 计。仅适用于 Legacy 交易传统类型 0 交易。import { parseGwei } from viem const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], gasPrice: parseGwei(20), // [!code focus] })maxFeePerGas类型bigint | undefined说明每单位 gas 的总费用上限以 wei 计已包含maxPriorityFeePerGas。仅适用于 EIP-1559 交易类型 2 交易。import { parseGwei } from viem const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], maxFeePerGas: parseGwei(20), // [!code focus] })maxPriorityFeePerGas类型bigint | undefined说明每单位 gas 的最大优先费用小费以 wei 计。仅适用于 EIP-1559 交易通常与maxFeePerGas搭配使用。import { parseGwei } from viem const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], maxFeePerGas: parseGwei(20), maxPriorityFeePerGas: parseGwei(2), // [!code focus] })nonce类型number说明标识这笔交易的唯一序号。EVM 中同一账户的 nonce 必须连续递增一般由钱包自动管理仅在需要精确控制如替换 pending 交易时才显式指定。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], nonce: 123, // [!code focus] })type类型legacy | eip1559 | eip2930 | undefined说明可选的交易请求类型用于收窄参数类型集合。例如指定eip1559后gasPrice等仅适用于 legacy 交易的参数将不再出现在类型中。const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], type: eip1559, // [!code focus] })value类型bigint | undefined说明随交易发送的 ETH 数量以 wei 计。示例用 viem 的parseEther(0.01)解析 0.01 ETH。import { parseEther } from viem const result await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], value: parseEther(0.01), // [!code focus] })Return Type返回值simulateContract返回类型为SimulateContractReturnType即“模拟结果 写请求”。源码中该类型对 config 中每条链做联合展开并为request补充chainId字段见 simulateContract.ts。request说明包含全部parameters的写交易请求。这正是可以直接交给writeContract使用的对象——它携带了abi、address、functionName、args、account以及chainId等完整信息是“模拟 → 发送”流水线的中间产物。response即返回对象中的result类型unknown说明合约模拟执行的结果返回值。类型推断由abi、functionName与args联合推断。例如对transferFromoutputs: [{ type: bool }]result会被推断为boolean类型测试中对此有明确断言见 simulateContract.test-d.ts。完整的返回结构在运行时形如{ chainId: 1, request: { abi: [...], account: { address: 0x..., type: json-rpc }, address: 0x..., args: undefined, chainId: undefined, dataSuffix: undefined, functionName: mint }, result: undefined }该结构来自测试快照见 simulateContract.test.ts。Type Inference类型推断当abi正确设置推荐使用as const断言后TypeScript 会自动推断functionName必须是 ABI 中存在的nonpayable/payable函数args根据所选函数的inputs推断参数类型与顺序value若所选函数为payable则value成为可用参数result根据函数的outputs推断返回类型。类型推断还与config的链配置联动不同链上可能支持不同的参数如 Celo 链的feeCurrencychainId的选择会影响可传入参数的类型集合。类型测试chain formatters用expectTypeOf验证了这些约束见 simulateContract.test-d.ts例如在 Celo 链上允许传feeCurrency: 0x...在 mainnet 上传feeCurrency会触发ts-expect-error编译报错functionName被收窄为 ABI 中写入函数的联合类型如approve | transfer | transferFrom。关于 ABI 设置与类型安全的更多细节可参考 typescript.md。此外类型测试还覆盖了函数重载overload场景同一函数名不同参数组合会推断出不同的result类型见 simulateContract.test-d.ts。Error错误处理simulateContract的错误类型为SimulateContractErrorType从源码看它是以下三类错误的联合见 simulateContract.tsGetConnectorClientErrorType来自getConnectorClient()账户/连接器解析失败基础错误BaseErrorType与通用ErrorTypewagmi 内部错误体系见 errors/base.tsviem_SimulateContractErrorType来自 viem 底层模拟执行的错误如合约 revert、estimateGas失败等。使用 TanStack Query 集成时相关错误类型如SimulateContractErrorType也会贯穿到simulateContractQueryOptions等 API 中。Viem 底层关联simulateContract是 viem 同名 Action 的 wagmi 封装。viem 提供最底层的链上模拟实现eth_call语义上的状态模拟而 wagmi 在此基础上叠加了 config/connector/chainId 解析与类型系统。viem 侧的完整实现可参考其simulateContract文档wagmi 侧如何从客户端提取或回退到该实现见 getAction.ts 与 simulateContract.ts。典型实战模拟后发送交易将上述知识点串联起来最常见的模式是先模拟、再写入保证用户操作在真正上链前得到校验import { simulateContract, writeContract } from wagmi/core import { abi } from ./abi import { config } from ./config // 1. 先模拟验证调用不会 revert并拿到写请求 const { request } await simulateContract(config, { abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], }) // 2. 模拟成功后再发送真实交易 const hash await writeContract(config, request)总结simulateContract是 wagmi 中模拟/校验合约交互的核心 Action返回{ chainId, result, request }其request可直接喂给writeContract参数覆盖 ABI、地址、函数、参数、账户/连接器、链选择chainId/blockNumber/blockTag以及手续费相关字段gas、gasPrice、maxFeePerGas、maxPriorityFeePerGas、type、nonce、value、accessList、dataSuffix在 ABI 正确设置的前提下functionName、args、value、result均可获得精确的编译期类型推断并与多链配置联动底层是对 viemsimulateContract的封装经由getConnectorClient、config.getClient与getAction完成账户解析与客户端装配。配套参考官方 API 文档 simulateContract.md、实现源码 simulateContract.ts、运行时测试 simulateContract.test.ts 与类型测试 simulateContract.test-d.ts。【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表