wagmi useSignTypedData Hook 完全指南:EIP-712 类型化数据签名实战
wagmi useSignTypedData Hook 完全指南EIP-712 类型化数据签名实战【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi导读useSignTypedData是 wagmi React 包中用于签名 EIP-712 类型化数据Typed Data的核心 Hook它封装了底层signTypedDataaction返回经过 TanStack Query 管理状态的 mutation 对象可用于实现登录认证、交易授权、元交易Meta Transaction签名等场景。本文将带你完整掌握该 Hook 的导入方式、参数与返回值、类型推断机制并结合仓库源码Hook 实现、底层 action 与 测试用例深入讲解其工作原理与最佳实践。一、什么是 EIP-712 类型化数据签名在以太坊生态中对数据进行签名通常有两种方式一种是普通的signMessage对任意字符串的 32 字节哈希签名另一种就是 EIP-712 规范定义的类型化数据签名。EIP-712 的优势在于签名内容不再是一串看不懂的十六进制而是结构化的、带字段类型描述的数据用户在钱包中可以看到清晰可读的字段含义如from、to、contents从而避免盲目签名带来的钓鱼风险。useSignTypedData正是为这一场景设计它签名类型化数据并计算出一个符合 EIP-712 规范的以太坊签名。该签名通常用于登录认证Sign-In with Ethereum 的底层签名机制之一链下授权与许可如 Permit 风格的 ERC-20 授权元交易/中继交易Relayer 代为提交交易时对意图的签名链下订单、票据、凭证等结构化信息的防篡改确认。二、导入 Hook在 React 应用中直接从wagmi包导入即可import { useSignTypedData } from wagmiimport { type UseSignTypedDataParameters } from wagmi import { type UseSignTypedDataReturnType } from wagmi此外若需要类型与底层选项也可以从wagmi/query导入对应的 TanStack Query 相关类型详见 mutation-imports 模板的展开形式import { type SignTypedDataData, type SignTypedDataVariables, type SignTypedDataMutate, type SignTypedDataMutateAsync, signTypedDataMutationOptions, } from wagmi/query这些类型均派生自 packages/core/src/query/signTypedData.tsSignTypedDataData即签名的返回类型0x${string}十六进制签名SignTypedDataVariables即签名所需参数types、primaryType、message、可选domain、account、connector。三、基础用法在组件中发起签名useSignTypedData是典型的 mutation 型 Hook——它不立即执行而是返回一个带mutate方法的对象由你在事件回调中触发。下面是一个完整的 React 组件示例对应官方文档 useSignTypedData.md 中的用法import { useSignTypedData } from wagmi function App() { const signTypedData useSignTypedData() return ( button onClick{() signTypedData.mutate({ types: { Person: [ { name: name, type: string }, { name: wallet, type: address }, ], Mail: [ { name: from, type: Person }, { name: to, type: Person }, { name: contents, type: string }, ], }, primaryType: Mail, message: { from: { name: Cow, wallet: 0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826, }, to: { name: Bob, wallet: 0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB, }, contents: Hello, Bob!, }, }) } Sign message /button ) }配套的 configconfig.tsimport { createConfig, http } from wagmi import { mainnet, sepolia } from wagmi/chains export const config createConfig({ chains: [mainnet, sepolia], transports: { [mainnet.id]: http(), [sepolia.id]: http(), }, })在项目入口处需要用 WagmiProvider 包裹应用将config注入到组件树中Hook 才能从最近的 Provider 上下文自动获取配置。3.1 签名的数据结构上例中的types定义了两个自定义类型Person与MailprimaryType: Mail指明本次要签名的顶层类型message则是对应的数据负载。签名时底层会按照 EIP-712 的编码规则keccak256拼接domainSeparator、hashStruct等生成待签名的 32 字节摘要再由钱包账户对该摘要做签名。若你的数据包含domainEIP-712 Domain 分隔符常用于区分应用与链 ID也可以传入例如domain: { name: MyApp, version: 1, chainId: 1, verifyingContract: 0x... }。四、ParametersHook 参数4.1 configConfig | undefined指定要使用的 Config由createConfig创建而不是从最近的 WagmiProvider 中获取。适合在 Provider 之外使用 Hook 或需要显式绑定某个独立 config 的场景import { useSignTypedData } from wagmi import { config } from ./config // [!code focus] function App() { const signTypedData useSignTypedData({ config, // [!code focus] }) }4.2 mutationMutationParameters用于透传 TanStack Query 的 mutation 参数完整列表见 mutation-options常用的包括参数类型说明gcTimenumber \| Infinity \| undefinedmutation 缓存数据在内存中保留的毫秒数设为Infinity可禁用垃圾回收metaRecordstring, unknown \| undefined附加到 mutation 缓存条目上的额外信息可在onError/onSuccess中访问networkModeonline \| always \| offlineFirst \| undefined网络模式默认onlineonError((error, variables, context?) Promiseunknown \| unknown) \| undefinedmutation 出错时触发onMutate((variables) Promisecontext \| void \| context \| void) \| undefinedmutation 函数执行前触发常用于乐观更新返回值会传给onError/onSettled以便回滚onSuccess((data, variables, context?) Promiseunknown \| unknown) \| undefinedmutation 成功时触发onSettled((data, error, variables, context?) Promiseunknown \| unknown) \| undefinedmutation 成功或失败后都会触发queryClientQueryClient指定自定义 QueryClient否则使用最近上下文中的实例retryboolean \| number \| ((failureCount, error) boolean) \| undefined重试策略默认0不重试retryDelaynumber \| ((retryAttempt, error) number) \| undefined重试间隔毫秒数可配合指数退避注意官方文档明确说明Wagmi 内部使用mutationFn与mutationKey来实现自身逻辑因此这两个 TanStack Query 参数不允许覆盖。从源码看useSignTypedData.tsHook 内部正是通过signTypedDataMutationOptions(config, parameters)组装 mutation 选项再交给useMutation而 packages/core/src/query/signTypedData.ts 中的实现将mutationKey固定为[signTypedData]并把mutationFn指向底层 action 的调用mutationFn(variables) { return signTypedData(config, variables) }, mutationKey: [signTypedData],五、Return Type返回值import { type UseSignTypedDataReturnType } from wagmiuseSignTypedData返回一个 TanStack Query mutation 结果对象完整说明见 mutation-result核心成员如下mutate(variables, { onSuccess, onSettled, onError }) void触发签名的函数。variables即SignTypedDataVariablestypes、primaryType、message等第二个可选参数可传入本次调用级的回调onSuccess/onError/onSettled。mutateAsync(variables, { onSuccess, onSettled, onError }) PromiseSignTypedDataData与mutate类似但返回一个可await的 Promise适合在异步流程中顺序执行。dataSignTypedDataData | undefined最近一次成功签名的结果即0x${string}格式的签名默认undefined。errorSignTypedDataErrorType | null最近一次 mutation 的错误对象未出错时为null。statusidle | pending | error | successmutation 状态机idle初始、pending执行中、error失败、success成功。另有派生布尔值isError/isIdle/isPending/isSuccess以及isPaused网络模式导致暂停时置true。其他failureCount/failureReason失败计数与最近一次失败原因reset重置 mutation 到初始状态submittedAt提交时间戳默认0variables最近一次传给mutate的变量对象。兼容性说明从 useSignTypedData.ts 的返回类型定义可以看出Hook 还额外暴露了两个已废弃的别名成员/** deprecated use mutate instead */ signTypedData: SignTypedDataMutatecontext /** deprecated use mutateAsync instead */ signTypedDataAsync: SignTypedDataMutateAsynccontext即signTypedData与signTypedDataAsync仍可用但官方标记为 deprecated新代码应统一使用mutate/mutateAsync。六、Type Inference类型推断这是useSignTypedData的亮点之一只要types配置正确TypeScript 就能自动推断出domain、message和primaryType的精确类型并在编译期校验你的数据结构参见官方 TypeScript 文档。6.1 内联类型Inline将types直接内联进mutate参数时类型系统会基于字面量自动推导const signTypedData useSignTypedData() signTypedData.mutate({ types: { Person: [ { name: name, type: string }, { name: wallet, type: address }, ], Mail: [ { name: from, type: Person }, { name: to, type: Person }, { name: contents, type: string }, ], }, primaryType: Mail, // 被推断为 Mail message: { // 被推断为 Mail 结构{ from: Person, to: Person, contents: string } from: { name: Cow, wallet: 0x... }, to: { name: Bob, wallet: 0x... }, contents: Hello, Bob!, }, })6.2 常量断言Const-Asserted当types被抽离为独立变量时需要使用as const保留字面量类型推断效果与内联一致const types { Person: [ { name: name, type: string }, { name: wallet, type: address }, ], Mail: [ { name: from, type: Person }, { name: to, type: Person }, { name: contents, type: string }, ], } as const const signTypedData useSignTypedData() signTypedData.mutate({ types, primaryType: Mail, // 被推断为 Mail message: { // 结构与 types 严格对应 from: { name: Cow, wallet: 0x... }, to: { name: Bob, wallet: 0x... }, contents: Hello, Bob!, }, })6.3 类型推断的源码机制类型推断能力来自 packages/core/src/query/signTypedData.ts 中对SignTypedDataMutate的泛型设计export type SignTypedDataMutatecontext unknown const typedData extends TypedData | Recordstring, unknown, primaryType extends keyof typedData | EIP712Domain, ( variables: SignTypedDataVariablestypedData, primaryType, options?: MutateOptions... | undefined, ) void通过const类型参数捕获字面量types再以primaryType extends keyof typedData将message约束为该类型对应的字段结构。类型级测试见 useSignTypedData.test-d.ts其中对variables的message字段做了toEqualTypeOf{ name: string; wallet:0x${string}}()级别的严格断言同时验证了data、error、context在各回调中的精确类型。七、底层 Action 与执行链路useSignTypedData最终调用的是 core 包中的 signTypedData action。理解其内部逻辑有助于排查问题export async function signTypedDataconst typedData, primaryType( config: Config, parameters: SignTypedDataParameterstypedData, primaryType, ): PromiseSignTypedDataReturnType { const { account, connector, ...rest } parameters let client: Client if (typeof account object account.type local) client config.getClient() else client await getConnectorClient(config, { account, connector }) const action getAction(client, viem_signTypedData, signTypedData) return action({ ...rest, ...(account ? { account } : {}) } as unknown as viem_SignTypedDataParameters) }关键行为账户选择若传入account且其type local本地私钥账户如privateKeyToAccount生成则直接使用config.getClient()无需连接器否则通过getConnectorClient获取当前连接的钱包客户端对应 getConnectorClient.ts。委托 viem最终通过getAction委托给 viem 的signTypedData实现viem/actions完成实际的 EIP-712 编码与签名。错误类型SignTypedDataErrorType由GetConnectorClientErrorType、基类错误与 viem 的签名错误联合而成便于统一错误处理。参数类型SignTypedDataParameters是 viem 参数类型与ConnectorParameter可选connector的交叉signTypedData.ts。八、测试用例验证签名可被恢复验证仓库中的 useSignTypedData.test.ts 提供了两个典型测试展示如何验证签名结果test(default, async () { await connect(config, { connector }) const { result } await renderHook(() useSignTypedData()) result.current.mutate({ types: typedData.basic.types, primaryType: Mail, message: typedData.basic.message, }) await vi.waitUntil(() result.current.isSuccess, { timeout: 10_000 }) await expect( recoverTypedDataAddress({ types: typedData.basic.types, primaryType: Mail, message: typedData.basic.message, signature: result.current.data!, }), ).resolves.toEqual(getConnection(config).address) await disconnect(config, { connector }) })要点解读流程先connect连接钱包 → 触发mutate→vi.waitUntil(isSuccess)等待完成 → 读取result.current.data。验证方式用 viem 的recoverTypedDataAddress从签名中恢复出签名者地址并与当前连接账户地址比对证明签名正确。本地账户场景behavior: local account用例通过privateKeyToAccount(privateKey)构造本地账户并作为account参数传入验证了不依赖连接器的本地签名路径且恢复出的地址与account.address一致。九、常见问题与注意事项必须在WagmiProvider内使用默认情况下 Hook 从最近的 WagmiProvider 读取 config未包裹会报错若确需独立使用请显式传入config参数。签名需连接钱包或提供本地账户使用连接器签名前需先完成connect使用本地账户时传入accounttype: local的对象即可离线签名。mutationFn/mutationKey不可覆盖这是 Wagmi 内部保留参数自定义逻辑请放在onMutate/onSuccess等回调中。优先使用mutate/mutateAsyncsignTypedData/signTypedDataAsync已标记 deprecated。类型推断依赖字面量types抽离为变量时务必加as const否则primaryType与message的推断会退化为宽泛类型。domain的一致性签名时使用的domainchainId、verifyingContract 等必须与链下验证方完全一致否则签名无法通过recoverTypedDataAddress或合约侧ecrecover校验。十、小结useSignTypedData将 EIP-712 类型化数据签名封装为符合 React 心智模型的 mutation Hook通过mutate触发、以data/error/status观察状态、由 TanStack Query 统一管理缓存与生命周期并借助泛型实现配置即类型的编译期安全。本文结合 Hook 实现、core action、query 选项与测试用例完整还原了从调用到签名验证的全链路。如需更底层的命令式调用可进一步阅读 signTypedData action 文档 与 createConfig / WagmiProvider 配置说明。【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考