Wagmi Solid 中 useSimulateContract 原语实战:合约交互的模拟、校验与 useWriteContract 组合
Wagmi Solid 中 useSimulateContract 原语实战:合约交互的模拟、校验与 useWriteContract 组合【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmiuseSimulateContract是wagmi/solid(Wagmi 的 SolidJS 适配层)中用于「模拟/校验一次合约交互」的响应式原语:它在真正向钱包发起交易之前,先在链上对functionNameargs做一次eth_call级别的模拟,提前发现 revert、授权不足、余额不足等问题,并产出可直接传给useWriteContract的request对象。读完本文,你能掌握它的完整参数(含 TanStack Query 查询选项)、返回类型结构、Solid 响应式传参方式,以及如何与useWriteContract组合实现「先模拟、后写入」的低验证成本交易流程,并能从源码层面理解其查询键构造、启用条件与错误处理机制。导入与适用前提import { useSimulateContract } from wagmi/solid该原语属于 SolidJS 框架适配包(源码位于 packages/solid/src/primitives/useSimulateContract.ts),它构建在核心包的simulateContractaction 与simulateContractQueryOptions查询选项工厂之上(见 packages/core/src/query/simulateContract.ts),并使用tanstack/solid-query的查询能力实现响应式缓存。文档末尾列出的底层 Action 是 simulateContract,其参数与返回结构是该原语的类型基础。快速上手:模拟一次 ERC-20transferFrom下面是一个可直接复制的用法示例:在应用组件中模拟一笔transferFrom调用,并配合一个标准的createConfig配置:import { useSimulateContract } from wagmi/solid import { abi } from ./abi function App() { const result useSimulateContract(() ({ abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], })) }配套的配置(对应官方文档 code-group 中引用的 site/snippets/solid/config.ts):import { createConfig, http } from wagmi/solid import { mainnet, sepolia } from wagmi/solid/chains export const config createConfig({ chains: [mainnet, sepolia], transports: { [mainnet.id]: http(), [sepolia.id]: http(), }, })注意两个要点:参数以 getter 函数传入:useSimulateContract(() ({ ... }))的第一参数是一个返回参数对象的函数(而非对象字面量)。这是为了维护 Solid 的响应式追踪——当abi、address、args等依赖的 Solid 响应式信号变化时,查询会自动重算。源码中该参数类型即AccessorSolidParameters...,见 useSimulateContract.ts#L78-L80。只允许nonpayable | payable函数:从泛型签名可以看到functionName extends ContractFunctionNameabi, nonpayable | payable(见 useSimulateContract.ts#L21-L28),即纯只读的view/pure函数不属于该原语的目标场景,那类调用应使用useReadContract之类的读原语。参数详解所有参数通过 getter 传递以维持 Solid 响应式:useSimulateContract(() ({ abi, address: 0x..., functionName: transferFrom, args: [0x..., 0x..., 123n], // other parameters... }))abiAbi | undefined合约的 ABI。建议按 TypeScript 文档 以as const方式声明 ABI,以获得最大的类型推断与安全。abi是模拟的必填项:核心查询工厂的queryFn中若abi缺失会直接抛出abi is required(见 simulateContract.ts#L76)。accountAccount | undefined发起合约调用所使用的账户,即链上视角的msg.sender。若不显式传入,Solid 原语会自动回退到当前连接的地址(见下文「源码视角」);若最终解析不到有效账户,则会抛出错误。addressAddress | undefined合约的部署地址。与functionName同为模拟必填项,缺失时queryFn抛出address is required(simulateContract.ts#L79)。argsreadonly unknown[] | undefined调用合约时传递的参数;类型由abi与functionName联合推断(ContractFunctionArgsabi, nonpayable | payable, functionName),写错参数个数或类型会直接产生编译期报错。chainIdconfig[chains][number][id] | undefined执行模拟时使用的链 ID。缺省时,原语回退到当前连接所处链(useChainId的结果),类型上限定为该config所注册链的 ID 集合。configConfig | undefined用于替代「从最近的 WagmiProvider 获取」的 Config。在多个WagmiProvider嵌套或测试场景中,可显式指定使用哪份配置。connectorConnector | undefined用于模拟交易的 Connector。这是模拟的必填项之一:模拟调用需要经由 connector 获得account/chainId上下文,缺失时queryFn抛出connector is required(simulateContract.ts#L77)。functionNamestring | undefined要调用的合约函数名;由abi推断,只能取nonpayable | payable的函数名。scopeKeystring | undefined将缓存限定到某一上下文。上下文(参数)相同的原语实例会共享同一份缓存;scopeKey用于人为区分不同调用点,避免不同 UI 位置复用同一份模拟结果。注意scopeKey参与 queryKey 构造,而connector会被从 queryKey 中剔除(见下文)。query(TanStack Query 选项)该原语支持标准 TanStack Query 查询参数(注意:queryFn、queryKey等由 Wagmi 内部占用,不可覆盖)。完整支持项包括:参数类型说明enabledboolean \| undefined设为false禁用自动查询,可实现依赖查询gcTimenumber \| Infinity \| undefined缓存未被使用后的存活时间,默认 5 分钟(SSR 时为Infinity)initialDataTData \| (() TData) \| undefined初始数据,会被持久化到缓存initialDataUpdatedAtnumber \| (() number \| undefined) \| undefinedinitialData的更新时间戳metaRecordstring, unknown \| undefined附加到缓存条目上的元信息networkModeonline \| always \| offlineFirst \| undefined默认online,控制离线时的请求行为notifyOnChangePropsstring[] \| all \| (() string[] \| all) \| undefined限制触发重渲染的属性placeholderDataTData \| ((prev, prevQuery) TData) \| undefinedpending 状态下的占位数据,不持久化queryClientQueryClient \| undefined自定义 QueryClientrefetchIntervalnumber \| false \| ((data, query) number \| false \| undefined) \| undefined轮询间隔(毫秒),函数形式可按最新数据动态计算refetchIntervalInBackgroundboolean \| undefined标签页后台时是否继续轮询refetchOnMountboolean \| always \| ((query) boolean \| always) \| undefined默认true,数据过期时挂载即重取refetchOnReconnectboolean \| always \| ((query) boolean \| always) \| undefined默认true,重连时按数据新鲜度重取refetchOnWindowFocusboolean \| always \| ((query) boolean \| always) \| undefined默认true,窗口聚焦时按数据新鲜度重取retryboolean \| number \| ((failureCount, error) boolean) \| undefined客户端默认 3 次、服务端默认 0 次重试retryDelaynumber \| ((retryAttempt, error) number) \| undefined重试延迟,可写成指数/线性退避函数retryOnMountboolean \| undefined默认true,挂载时是否重试含错误的查询select((data: TData) unknown) \| undefined转换/截取返回数据,不影响缓存内容staleTimenumber \| Infinity \| undefined默认 0,数据被判定过期的毫秒数structuralSharingboolean \| ((old, new) TData) \| undefined默认true,是否对结果做结构性共享这些选项的完整语义可参考 site/shared/query-options.md。返回类型返回类型声明为:import { useSimulateContract } from wagmi/solid useSimulateContract.ReturnType其data属性可通过abifunctionNameargs三者组合推断出来(SimulateContractData),具体类型机制见 TypeScript 文档。完整字段结构(site/shared/query-result.md)包括:data:TData,最后一次成功模拟的数据,默认undefined;dataUpdatedAt/errorUpdatedAt/errorUpdateCount:数据与错误的时间戳、累计次数;error:null | SimulateContractErrorType,模拟失败(如 revert)时的错误对象;failureCount/failureReason:失败计数与最近一次失败原因,查询成功后重置;fetchStatus(fetching | idle | paused)及isFetching/isPaused;状态派生布尔:isError/isPending/isSuccess/isLoading/isLoadingError/isRefetchError/isRefetching/isStale/isFetched/isFetchedAfterMount/isPlaceholderData;status(error | pending | success)与手动refetch({ cancelRefetch?, throwOnError? });queryKey:Solid 适配层额外合并进结果的查询键(见 packages/solid/src/utils/query.ts#L94-L98)。模拟成功时data的核心负载是request(含abi片段、account、address、args、chainId、dataSuffix、functionName)、result与chainId——测试快照完整呈现了这一结构(见下文「测试印证」)。类型推断正确设置abi(推荐as const断言)后,TypeScript 会为functionName、args以及(写入场景的)value推断出正确类型。abi使用const泛型捕获(useSimulateContract.ts#L22),保证了函数名/参数类型与 ABI 严格对齐。更多信息见 Wagmi TypeScript 文档。与 useWriteContract 组合:先模拟、后写入useSimulateContract的典型用法是与 useWriteContract 组合,以减少钱包要求的验证工作量:模拟阶段完成参数校验与数据编码,写入阶段直接消费data.request:import { useSimulateContract, useWriteContract } from wagmi/solid import { abi } from ./abi function App() { const { data } useSimulateContract(() ({ abi, address: 0x6b175474e89094c44da98b954eedeac495271d0f, functionName: transferFrom, args: [ 0xd2135CfB216b74109775236E36d4b433F1DF507B, 0xA0Cf798816D4b9b9866b5330EEa46a18382f251e, 123n, ], })) const writeContract useWriteContract() const request data?.request // Call writeContract.mutate(request) when ready }这个模式的价值在于:用户点击「发送」之前,data.request已经是编码完成、经过链上模拟验证的交易请求;此时若status error,可以直接把error(例如合约 revert 原因)展示给用户,避免把注定失败的交易推给钱包签名。源码视角:回退逻辑、启用条件与 queryKey 构造Solid 原语本体很薄,核心是把参数回退到连接上下文后交给核心查询工厂,再交给统一的useQuery封装(useSimulateContract.ts#L49-L60):const config useConfig(parameters) const connection useConnection() const chainId useChainId(() ({ config: config() })) const options createMemo(() simulateContractQueryOptions(config(), { ...(parameters() as any), account: parameters().account ?? connection().address, chainId: parameters().chainId ?? chainId(), connector: parameters().connector ?? connection().connector, }), ) return useQuery(options) as anyaccount、chainId、connector三项遵循「显式参数优先,否则取当前连接」的优先级;createMemo保证只有 getter 内实际读取的响应式依赖变化时才重算查询选项,这是「参数以 getter 传入」的设计落点。核心工厂simulateContractQueryOptions则负责三个关键行为(packages/core/src/query/simulateContract.ts#L66-L90):启用条件:enabled: Boolean(abi address connector functionName (options.query?.enabled ?? true))。即四个必填项任意缺失时,查询自动处于禁用态(pending)而不是抛错——这正是官方文档「属性缺失时禁用」语义的来源;queryFn 的硬校验:在真正执行前再检查abi、connector、address、functionName,缺失即抛出带明确文案的错误,最后调用核心simulateContract(config, { ... })action 完成模拟;queryKey 构造:[simulateContract, filterQueryOptions(rest)],并且从 key 中剔除了connector(simulateContract.ts#L133-L135)——因为同一地址/账户/链上的模拟结果不应因连接方式不同而缓存隔离;scopeKey则保留在 key 中,实现上文「缓存作用域」的能力。此外,Solid 适配层的useQuery封装统一注入了queryKeyHashFn: hashFn(packages/solid/src/utils/query.ts#L90-L93),解决 queryKey 中包含bigint(如 ERC-20 数量123n)时 JSON 哈希失效的问题,并把queryKey以 getter 形式合并进返回值,方便在渲染层调试。测试印证:快照结构与禁用行为官方测试 useSimulateContract.test.ts 提供了两条可验证事实:成功路径的完整快照:连接 mock connector 后模拟wagmiMintExample.mint,结果queryKey为[simulateContract, { account, address, chainId, functionName }]——与上文「connector 被剔除、scopeKey 之外的核心参数进入 key」的结论一致;data包含编码好的request、result: undefined、chainId: 1;缺省禁用路径:useSimulateContract()(不传任何参数)时,结果保持isPending且不会发起请求,对应「disabled when properties missing」测试(useSimulateContract.test.ts#L87-L92)。小结与相关文档useSimulateContract的定位是「写操作前的最后一道链上验证 请求预编码」:以 getter 形式传入abi/address/functionName/args等参数,自动回退到当前连接的account、chainId、connector,在abi、address、connector、functionName齐备时自动启用查询,失败时给出SimulateContractErrorType错误,成功时输出可供useWriteContract直接消费的request。进一步阅读:simulateContract(core action)useWriteContractcreateConfigconnectorsWagmiProviderWagmi TypeScript 类型推断文档源码:Solid 原语实现、核心查询选项工厂、Solid useQuery 封装、测试用例【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考