wagmi Tempodex.useWatchOrderPlacedHook 实战:监听 Stablecoin DEX 挂单事件
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
dex.useWatchOrderPlaced是 wagmi Tempo 模块为 React 应用提供的声明式 Hook,用于在 Stablecoin DEX 上实时监听"订单已挂出(Order Placed)"事件。本文以该 Hook 的官方文档为核心,结合 packages/react/src/tempo/hooks/dex.ts、packages/core/src/tempo/actions/dex.ts 及对应测试源码,完整讲解它的用法、参数、事件回调结构与底层实现原理,帮助你在组件中像订阅普通状态一样订阅链上挂单事件。
为什么需要dex.useWatchOrderPlaced
在去中心化交易所(DEX)中,挂单(place order)是最核心的用户行为之一。构建订单簿类 UI(如实时行情、成交提示、订单流看板)时,前端需要第一时间感知"新订单已挂出"这一链上事件。wagmi Tempo 将这一能力封装成两层 API:
- 命令式 Action:
dex.watchOrderPlaced,直接调用并手动管理退订; - 声明式 Hook:
dex.useWatchOrderPlaced,在 React 组件中自动订阅、自动清理,与WagmiProvider的配置体系天然打通。
Hook 的核心价值在于"以 React 的方式做链上事件订阅":你只需要声明"我要监听什么",订阅生命周期(挂载时开始、卸载时结束)由 Hook 内部通过useEffect管理,无需手写退订逻辑。
基本用法
最小示例
在组件中调用Hooks.dex.useWatchOrderPlaced,并传入onOrderPlaced回调即可开始监听:
import { Hooks } from 'wagmi/tempo' function App() { Hooks.dex.useWatchOrderPlaced({ onOrderPlaced: (args, log) => { console.log('args:', args) }, }) return <div>Watching for order placements...</div> }该用法与官方文档示例一致(见 site/tempo/hooks/dex.useWatchOrderPlaced.md)。onOrderPlaced回调会收到两个参数:args(解析后的事件参数对象)与log(原始事件日志)。
组件级监听的正确姿势
Hook 通常应在组件内部使用(而非模块顶层),因为它的订阅生命周期绑定组件挂载/卸载:
import { Hooks } from 'wagmi/tempo' export function OrderFeed() { const [orders, setOrders] = useState<Args[]>([]) Hooks.dex.useWatchOrderPlaced({ onOrderPlaced(args) { setOrders((prev) => [...prev, args]) }, }) return <ul>{orders.map((o, i) => <li key={i}>{String(o.amount)}</li>)}</ul> }注意:在onOrderPlaced中直接调用setState更新状态即可驱动 UI 刷新,这正是声明式 Hook 相比命令式 Action 的便捷之处。
需要的配置环境
Tempo 相关 Hook 依赖一个配置了tempo链与tempoWallet连接器的 wagmiConfig,并通过WagmiProvider注入。官方文档代码组中引用的配置文件如下(site/snippets/react/config-tempo.ts):
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(), }, })然后在应用根部用WagmiProvider提供该配置,Hook 即可从最近的 Provider 中自动获取config。
参数详解
dex.useWatchOrderPlaced的参数类型定义如下(packages/react/src/tempo/hooks/dex.ts):
type Parameters<config> = ExactPartial<Actions.dex.watchOrderPlaced.Parameters<config>> & ConfigParameter<config> & { enabled?: boolean | undefined }即:底层 Action 的全部参数(全部可选)+ 可选的config+ Hook 特有的enabled开关。
config
- 类型:
Config | undefined - 作用:指定要使用的 wagmi
Config(即 createConfig 创建的配置),替代从最近的WagmiProvider中获取。
一般情况下无需传该参数,只有在需要绕开 Provider 上下文、显式指定配置时才使用。
enabled(Hook 独有)
- 类型:
boolean,默认true
这是 Hook 层特有的开关。当enabled为false时,Hook 不会发起订阅。从实现看(packages/react/src/tempo/hooks/dex.ts):
const { enabled = true, onOrderPlaced, ...rest } = parameters ... useEffect(() => { if (!enabled) return if (!onOrderPlaced) return return Actions.dex.watchOrderPlaced(config, { ...rest, chainId, onOrderPlaced, }) }, [...])enabled用于条件订阅场景:例如仅在用户已连接钱包或某个交易对处于激活状态时才开启监听。
onOrderPlaced(必传回调)
- 类型:
(args: Args, log: Log) => void
挂单事件触发时被调用的回调。官方文档(site/tempo/actions/dex.watchOrderPlaced.md)给出了完整的Args结构:
type Args = { /** ID of the placed order */ orderId: bigint /** Address that placed the order */ maker: Address /** Address of the base token */ token: Address /** Amount of tokens in the order */ amount: bigint /** Whether this is a buy order */ isBid: boolean /** Price tick for the order */ tick: number }各字段含义:
| 字段 | 类型 | 说明 |
|---|---|---|
orderId | bigint | 被挂出的订单 ID |
maker | Address | 挂单者的地址 |
token | Address | 基础代币地址 |
amount | bigint | 订单中的代币数量 |
isBid | boolean | 是否为买单(true 为买,false 为卖) |
tick | number | 订单的价格 tick |
注意amount与orderId为bigint类型,直接进行字符串拼接会得到十进制大整数表示,建议使用BigInt相关的格式化工具(如formatUnits)处理展示。
过滤参数:args、maker、token
Hook 支持按订单 ID、maker 地址、token 地址过滤事件,避免无关事件触发回调:
args(可选,object):订阅过滤器对象,包含三个可选字段:orderId?: bigint | bigint[] | null—— 按订单 ID 过滤;maker?: Address | Address[] | null—— 按挂单地址过滤;token?: Address | Address[] | null—— 按代币地址过滤;
maker(可选,Address):单独传入时等价于按 maker 地址过滤事件;token(可选,Address):单独传入时等价于按 token 地址过滤事件。
args过滤器与单独的maker/token参数在类型上均映射到底层 Action 的过滤配置(见 packages/core/src/tempo/actions/dex.ts 中watchOrderPlaced.Parameters对 viem 参数的透传),可传入数组实现多值匹配。
fromBlock(可选)
- 类型:
bigint - 作用:指定开始监听的区块高度,用于从历史区块起回放事件(例如页面刷新后补全错过的挂单记录)。
轮询相关:poll与pollingInterval
poll(可选):类型固定为true,开启轮询模式(默认采用订阅模式,即通过链的过滤器推送事件);pollingInterval(可选):类型为number,轮询频率(毫秒),未指定时默认使用 Client 的pollingInterval配置。
在无法使用 WebSocket 订阅推送的环境(如某些 HTTP RPC 场景)下,可通过poll: true降级为定时拉取。
onError(可选)
- 类型:
(error: Error) => void - 作用:当获取新区块/新事件出错时调用(例如网络抖动或 RPC 不可用),可用于错误上报或 UI 提示。
chainId(可选)
虽然官方文档参数列表未单独列出,但从实现(packages/react/src/tempo/hooks/dex.ts)可以看到 Hook 内部通过useConfig与useChainId解析链 ID:
const config = useConfig({ config: parameters.config }) const configChainId = useChainId({ config }) const chainId = parameters.chainId ?? configChainId即:未显式传入chainId时,默认使用当前 Provider 所激活的链。
底层原理:Hook 如何桥接 Action
dex.useWatchOrderPlaced的实现非常精简——它本质上是命令式 Action 的 React 封装。调用链为:
useWatchOrderPlaced (React Hook) └─> Actions.dex.watchOrderPlaced(config, parameters) // @wagmi/core Action └─> config.getClient({ chainId }) // 获取链客户端 └─> viem Actions.dex.watchOrderPlaced(client, rest) // 底层链交互对应源码(packages/react/src/tempo/hooks/dex.ts 与 packages/core/src/tempo/actions/dex.ts):
// React 层:useEffect 内调用 Action 并返回退订函数 useEffect(() => { if (!enabled) return if (!onOrderPlaced) return return Actions.dex.watchOrderPlaced(config, { ...rest, chainId, onOrderPlaced, }) }, [config, enabled, chainId, onOrderPlaced, rest.fromBlock, ...]) // Core 层:解析出 chainId 对应的 client 后转发给 viem export function watchOrderPlaced<config extends Config>( config: config, parameters: watchOrderPlaced.Parameters<config>, ) { const { chainId, ...rest } = parameters const client = config.getClient({ chainId }) return Actions.dex.watchOrderPlaced(client, rest) }几个值得注意的实现细节:
- 退订即清理:
Actions.dex.watchOrderPlaced返回一个() => void的退订函数(官方文档 Return Type 亦标注为() => void),Hook 将其作为useEffect的清理函数返回,因此组件卸载时订阅会被自动取消,不会造成内存泄漏。 - 依赖数组精确控制重订阅:
useEffect的依赖数组显式列出了rest.fromBlock、rest.maker、rest.onError、rest.poll、rest.pollingInterval、rest.token等参数。当这些参数变化时,旧订阅会被取消并重新建立;onOrderPlaced也在依赖中,确保回调引用更新后立即生效。 enabled前置短路:enabled === false或未传onOrderPlaced时直接不建立订阅,避免无效的链上连接。
测试验证:过滤与事件数据完整性
仓库中的测试(packages/react/src/tempo/hooks/dex.test.ts)从两个维度验证了该 Hook 的行为:
默认监听(default):先渲染 Hook,再通过Actions.dex.placeSync依次挂出买单与卖单,随后用vi.waitUntil(() => events.length >= 2)等待两个事件到达,并断言:
expect(events[0]?.isBid).toBe(true) expect(events[0]?.amount).toBe(parseUnits('100', 6)) expect(events[1]?.isBid).toBe(false) expect(events[1]?.amount).toBe(parseUnits('100', 6))即:买单事件isBid为true、卖单事件isBid为false,且amount精确等于挂单时解析后的数量,验证了事件数据的完整性与方向标记的正确性。
按 token 过滤(filter by token):为 Hook 传入token: base过滤器,同时在base与另一个交易对(base2)上分别挂单,只有匹配base的事件被回调捕获,验证过滤参数确实生效。
与 Action 的关系:何时用 Hook、何时用 Action
官方文档在 Hook 文档末尾列出了两个关联 Action:
dex.place:挂单 Action,与watchOrderPlaced形成"写-读"配对——dex.place产生挂单事件,useWatchOrderPlaced消费该事件;dex.watchOrderPlaced:命令式事件监听 Action,useWatchOrderPlaced的底层实现。
选择建议:
- 在 React 组件内需要监听挂单事件、且希望自动管理订阅生命周期,首选
Hooks.dex.useWatchOrderPlaced; - 在组件外(如事件总线、服务端逻辑、非 React 上下文)需要监听,或需要手动控制退订时机,使用
Actions.dex.watchOrderPlaced(config, { onOrderPlaced })并保存其返回的退订函数。
命令式版本的典型用法(见 site/tempo/actions/dex.watchOrderPlaced.md):
import { Actions } from 'wagmi/tempo' import { config } from './config' const unwatch = Actions.dex.watchOrderPlaced(config, { onOrderPlaced(args, log) { console.log('args:', args) }, }) // Later, stop watching unwatch()实战示例:带过滤的订单流看板
综合以上知识点,一个监听指定交易对买单/卖单的完整组件如下:
import { useMemo, useState } from 'react' import { Hooks } from 'wagmi/tempo' import { formatUnits } from 'viem' type OrderEvent = { orderId: bigint maker: `0x${string}` token: `0x${string}` amount: bigint isBid: boolean tick: number } export function OrderBookFeed({ token }: { token: `0x${string}` }) { const [orders, setOrders] = useState<OrderEvent[]>([]) const [error, setError] = useState<Error | null>(null) Hooks.dex.useWatchOrderPlaced({ token, // 只监听该交易对的挂单 enabled: Boolean(token), fromBlock: undefined, // 默认从当前区块开始 onOrderPlaced(args) { setOrders((prev) => [args, ...prev].slice(0, 50)) }, onError(err) { setError(err) }, }) const bids = useMemo(() => orders.filter((o) => o.isBid), [orders]) const asks = useMemo(() => orders.filter((o) => !o.isBid), [orders]) return ( <div> {error && <p style={{ color: 'red' }}>{error.message}</p>} <h3>买单({bids.length})</h3> <ul>{bids.map((o) => <li key={String(o.orderId)}>{formatUnits(o.amount, 6)} @ tick {o.tick}</li>)}</ul> <h3>卖单({asks.length})</h3> <ul>{asks.map((o) => <li key={String(o.orderId)}>{formatUnits(o.amount, 6)} @ tick {o.tick}</li>)}</ul> </div> ) }要点回顾:
- 用
token过滤避免无关交易对事件干扰; - 用
enabled控制未传入 token 时不建立订阅; - 在
onOrderPlaced中更新 React state,驱动 UI 实时刷新; - 通过
isBid区分买卖方向,用formatUnits处理bigint数量展示。
小结
dex.useWatchOrderPlaced是 wagmi Tempo 中"事件驱动 UI"的代表性 Hook:它以最小的心智负担(一个回调 + 可选过滤器)将 Stablecoin DEX 的挂单事件接入 React 组件,底层通过useEffect桥接到@wagmi/core的dex.watchOrderPlacedAction,再透传到 viem 完成真实的链上事件订阅,并在组件卸载时自动退订。配合dex.place挂单、token/maker过滤器与fromBlock历史回放,你可以快速构建订单流看板、成交提示、实时行情等去中心化交易应用的核心功能。
进一步阅读:Tempo Hooks 总览、dex.placeAction、dex.watchOrderPlacedAction。
【免费下载链接】wagmiReactive primitives for Ethereum apps项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考