wagmi Tempo 系列:Actions.token.watchMint监听 TIP20 代币铸造事件完整指南
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
导读
Actions.token.watchMint是 wagmi 为 Tempo 网络提供的实时事件监听 Action,用于订阅 TIP20 代币的Mint(铸造)事件:一旦链上发生铸造,回调函数会立刻收到铸造参数与事件日志。本指南以 site/tempo/actions/token.watchMint.md 为骨架,结合 packages/core/src/tempo/actions/token.ts 与 React 侧 packages/react/src/tempo/hooks/token.ts 的源码实现,从基本用法、参数全解、轮询机制到测试用例与 React Hook 封装,逐层讲解如何在以太坊应用中接入 TIP20 铸造事件的订阅能力。读完你将能独立完成「创建 Config → 订阅铸造事件 → 处理回调 → 注销监听」的完整闭环,并理解其底层调用链与适用边界。
前置知识:Tempo 与 TIP20
Tempo 是 wagmi 内置支持的网络(在仓库中以 tempo 目录承载全部相关 Action 与 Hook,链定义位于wagmi/chains)。TIP20 是 Tempo 上的代币标准,支持创建(token.create)、铸造(token.mint)、转账(token.transfer)、销毁(token.burn)等操作,并引入了issuer(发行者)等角色概念(见测试中对grantRoles的调用,packages/core/src/tempo/actions/token.test.ts)。
watchMint属于「只读订阅」类 Action:它不签名、不发交易,只持续监听链上事件,并在每次铸造发生时把**参数(args)与日志(log)**传给回调。因此它天然适合用于实时余额刷新、铸造监控面板、通知推送等场景。
基本用法:订阅一次铸造事件
原文档给出的最小可用示例如下:
import { Actions } from 'wagmi/tempo' import { config } from './config' const unwatch = Actions.token.watchMint(config, { token: '0x20c0000000000000000000000000000000000001', onMint(args, log) { console.log('args:', args) }, }) // Later, stop watching unwatch()其中config来自 site/snippets/react/config-tempo.ts 所示的 Tempo 专用配置:
import { createConfig, http } from 'wagmi' import { tempo } from 'wagmi/chains' import { tempoWallet } from 'wagmi/tempo' export const config = createConfig({ connectors: [tempoWallet()], chains: [tempo], multiInjectedProviderDiscovery: false, transports: { [tempo.id]: http(), }, })要点拆解:
- 导入路径:在 wagmi 聚合包下从
wagmi/tempo导入Actions;使用@wagmi/core时则从@wagmi/core/tempo导入(源码注释中的写法,见 packages/core/src/tempo/actions/token.ts)。 token参数:传 TIP20 代币地址(Address)或代币 ID(bigint),两种形式都支持。- 返回的
unwatch:类型为() => void,调用后即停止监听并释放内部资源;建议在组件卸载或不再需要监听时调用,避免事件泄漏。
从源码看,watchMint的实现非常薄(packages/core/src/tempo/actions/token.ts):
export function watchMint<config extends Config>( config: config, parameters: watchMint.Parameters<config>, ) { const { chainId, ...rest } = parameters const client = config.getClient({ chainId }) return Actions.token.watchMint(client, rest) }即:先从 Config 中取出对应chainId的 viem Client,再把剩余参数原样转发给 viem/tempo 的Actions.token.watchMint。这说明wagmi 层负责「配置与客户端解析」,真正的事件订阅逻辑由 viem 的 Tempo 实现承担(文档底部也给出了对应的 viem 参考页)。
参数详解:onMint、token、过滤与轮询
原文档完整列出了全部参数,下面逐一展开说明。
onMint(必填)
类型为function,签名:
declare function onMint(args: Args, log: Log): void type Args = { /** Address that received the tokens */ to: Address /** Amount minted */ amount: bigint }- 第一个参数
args是铸造事件的核心数据:to为收款地址,amount为铸造数量(bigint类型,注意与parseUnits('100', 6)等小数位换算配合使用,见下文测试用例)。 - 第二个参数
log是对应的事件日志对象(包含区块、交易、日志索引等链上元信息)。
token
- 类型:
Address | bigint - 指定要监听的 TIP20 代币,可以是合约地址(如文档示例中的
0x20c0...0001),也可以是代币 ID(数值形式)。
args(可选)
类型为object,当前支持按收款地址过滤:
type Args = { /** Filter by recipient address */ to?: Address | Address[] | null }传入单个地址或地址数组时,只有to匹配的铸造事件才会触发回调;不传则监听该代币的全部铸造事件。
fromBlock(可选)
- 类型:
bigint - 指定从哪个区块高度开始监听。默认从最新区块开始;设置历史区块后,事件扫描将从该高度起,适用于「上线后补拉遗漏事件」的场景。
onError(可选)
- 类型:
function - 签名
declare function onError(error: Error): void。 - 当「获取新区块」过程中发生错误时调用,用于日志上报与异常兜底。文档原话是 "The callback to call when an error occurred when trying to get for a new block",即它捕获的是轮询/扫描过程中拉取新区块失败的错误,而非事件本身的业务错误。
poll(可选)
- 类型:
true - 置为
true时启用轮询模式。Tempo 的实时事件默认走订阅通道;若当前环境(如某些 RPC 提供方)不支持订阅,可退回轮询模式。
pollingInterval(可选)
- 类型:
number - 轮询频率,单位为毫秒(ms)。默认取 Client 的
pollingInterval配置(即 Config 中 transport 层设置的值),显式传入则可覆盖默认值,例如高频铸造场景下可缩短到 1000ms 以内。
参数速查表
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
onMint | function | 是 | 铸造事件回调,接收(args, log) |
token | Address \| bigint | 是 | TIP20 代币地址或 ID |
args.to | Address \| Address[] \| null | 否 | 按收款地址过滤 |
fromBlock | bigint | 否 | 起始监听区块高度 |
onError | function | 否 | 拉取新区块出错时的回调 |
poll | true | 否 | 启用轮询模式 |
pollingInterval | number | 否 | 轮询间隔(ms),默认取 Client 配置 |
从源码看调用链:Config 到 viem Client
watchMint之所以能跨框架复用,是因为它在 wagmi 层的实现只有「取 Client + 转发」两步:
config.getClient({ chainId })依据链 ID 解析出 viem Client(transport、链配置均来自createConfig);- 将
chainId剥离后的参数(token、onMint、poll等)直接传给Actions.token.watchMint(client, rest)(packages/core/src/tempo/actions/token.ts)。
类型定义方面,watchMint.Parameters是ChainIdParameter<config> & Actions.token.watchMint.Parameters的交集(packages/core/src/tempo/actions/token.ts),即它额外继承了chainId参数——当 Config 配置了多条链时,可用chainId显式指定在哪个链上监听,未指定则使用默认链。
真实工作流:测试用例如何验证订阅闭环
仓库中的测试用例完整演示了「创建代币 → 授权发行者 → 订阅 → 触发铸造 → 断言事件 → 注销」的全流程(packages/core/src/tempo/actions/token.test.ts):
// 1. 连接第一个 connector await connect(config, { connector: config.connectors[0]! }) // 2. 创建一个新代币 const { token: tokenAddr } = await token.createSync(config, { currency: 'USD', name: 'Watch Mint Token', symbol: 'WATCHMINT', }) // 3. 给自己授予 issuer 角色(铸造权限) await token.grantRolesSync(config, { token: tokenAddr, roles: ['issuer'], to: account.address, }) // 4. 订阅铸造事件 const events: any[] = [] const unwatch = token.watchMint(config, { token: tokenAddr, onMint: (args) => { events.push(args) }, }) // 5. 触发一次铸造 await token.mintSync(config, { token: tokenAddr, to: account.address, amount: parseUnits('100', 6), }) // 6. 等待事件到达并断言 await vi.waitFor(() => { expect(events.length).toBeGreaterThan(0) }) unwatch() // 7. 校验事件内容 expect(events[0]?.to).toBe(account.address) expect(events[0]?.amount).toBe(parseUnits('100', 6))该测试同时验证了几个关键事实:
- 事件参数结构与文档一致:
args.to对应收款地址,args.amount对应铸造数量;金额是bigint,用parseUnits('100', 6)按 6 位小数构造。 - 异步到达:监听回调是异步触发的,测试用
vi.waitFor轮询等待事件数组非空。 unwatch()幂等可安全调用:事件断言完成后立即注销。- 前置依赖:铸造需要
issuer角色,订阅事件前需先完成授权,否则铸造不会成功、事件也不会产生。
React 封装:Hooks.token.useWatchMint
在 React 应用中,不必手动管理 Config 与useEffect,仓库提供了现成的 Hook 封装(packages/react/src/tempo/hooks/token.ts):
import { Hooks } from 'wagmi/tempo' function App() { Hooks.token.useWatchMint({ onMint(args) { console.log('Mint:', args) }, }) return <div>Watching for mints...</div> }Hook 的源码实现揭示了几点设计细节:
export function useWatchMint<config extends Config = ResolvedRegister['config']>( parameters: useWatchMint.Parameters<config> = {}, ) { const { enabled = true, onMint, token, ...rest } = parameters const config = useConfig({ config: parameters.config }) const configChainId = useChainId({ config }) const chainId = parameters.chainId ?? configChainId useEffect(() => { if (!enabled) return if (!onMint) return if (!token) return return Actions.token.watchMint(config, { ...rest, chainId, onMint, token, }) }, [ config, enabled, chainId, token, onMint, rest.fromBlock, rest.onError, rest.poll, rest.pollingInterval, ]) }- 自动生命周期管理:Hook 在
useEffect中调用Actions.token.watchMint,并把其返回的unwatch作为 effect 清理函数返回——组件卸载或依赖变化时自动注销,无需手动清理。 - 守卫条件:
enabled、onMint、token任一不满足时直接跳过订阅,避免无效监听。 - 依赖数组精确:
fromBlock、onError、poll、pollingInterval变化都会重建订阅,保证参数最新。 chainId智能推导:未显式传入chainId时,使用useChainId得到当前链。- 参数类型:
useWatchMint.Parameters为ExactPartial<Actions.token.watchMint.Parameters> & ConfigParameter & { enabled?: boolean },即所有参数可省略(onMint、token可在后续渲染中再提供),并额外支持config覆盖与enabled开关。
边界与最佳实践
- 监听起点:默认从最新区块开始,历史事件不会补发;需要历史数据时显式传
fromBlock,或将事件落库后再用getContractEvents类查询接口回溯。 - 网络容错:订阅通道不可用时启用
poll: true,并通过pollingInterval控制频率与 RPC 压力;onError用于捕获扫描过程中的异常并做重连/告警。 - 金额处理:
amount是bigint,展示前需按代币小数位格式化(如parseUnits('100', 6)对应的 6 位小数)。 - 资源释放:非 React 场景务必在合适时机调用返回的
unwatch();React 场景交给 Hook 自动处理。 - 多链场景:Config 配置多链时,通过
chainId参数指定监听目标链,配合 config-tempo.ts 中transports[tempo.id]的配置方式理解链与 transport 的绑定关系。
延伸阅读
- 同系列事件监听:
token.watchTransfer、token.watchCreate、token.watchBurn、token.watchRole等均位于 site/tempo/actions 目录,参数结构与本文一致; - React Hook 完整列表见 packages/react/src/tempo/hooks/token.ts;
- Tempo 的完整 Action 与 Hook 索引可参考 site/tempo/actions/index.md 与 site/tempo/hooks/index.md;
- 若需自定义轮询间隔,可在 site/snippets/react/config-tempo.ts 的
createConfig中为 transport 配置pollingInterval,作为watchMint的默认值。
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考