Viem监听合约事件完整指南目录基本概念监听方法概览watchContractEvent详解watchEvent详解过滤器配置错误处理性能优化实战示例基本概念什么是合约事件?Solidity合约中通过event关键字声明用于记录链上发生的特定操作比直接查
<!--StartFragment-->
event关键字声明Viem 提供两种主要的事件监听方式:
| 方法 | 适用场景 | 特点 |
|---|---|---|
watchContractEvent |
已知 ABI 的合约 | 类型安全,开发便捷 |
watchEvent |
原始事件监听 | 更底层,灵活性高 |
import { createPublicClient, http, parseAbi } from 'viem'
import { mainnet } from 'viem/chains'
const publicClient = createPublicClient({
chain: mainnet,
transport: http()
})
// 定义合约 ABI
const erc20Abi = parseAbi([
'event Transfer(address indexed from, address indexed to, uint256 value)',
'event Approval(address indexed owner, address indexed spender, uint256 value)'
])
// 监听 Transfer 事件
const unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984', // UNI 合约
abi: erc20Abi,
eventName: 'Transfer',
onLogs: (logs) => {
logs.forEach((log) => {
console.log('Transfer 事件:', {
发送方: log.args.from,
接收方: log.args.to,
金额: log.args.value,
交易哈希: log.transactionHash,
区块号: log.blockNumber
})
})
}
})
// 停止监听
// unwatch()
// 只监听特定地址的转账
const unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
abi: erc20Abi,
eventName: 'Transfer',
args: {
from: '0x742d35Cc6634C0532925a3b8D5C9eA6A2A3b8D5C' // 只监听从这个地址转出的
},
onLogs: (logs) => {
console.log('特定地址转出:', logs)
}
})
// 监听多个事件类型
const unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
abi: erc20Abi,
onLogs: (logs) => {
logs.forEach((log) => {
if (log.eventName === 'Transfer') {
console.log('转账事件:', log.args)
} else if (log.eventName === 'Approval') {
console.log('授权事件:', log.args)
}
})
}
})
import { parseAbiItem } from 'viem'
// 使用 watchEvent 进行更底层的监听
const unwatch = publicClient.watchEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
event: parseAbiItem('event Transfer(address indexed, address indexed, uint256)'),
onLogs: (logs) => {
logs.forEach((log) => {
console.log('原始事件数据:', {
topics: log.topics,
data: log.data,
完整日志: log
})
})
}
})
// 监听特定主题的所有事件(不指定地址)
const unwatch = publicClient.watchEvent({
event: parseAbiItem('event Transfer(address indexed, address indexed, uint256)'),
onLogs: (logs) => {
// 会收到所有合约的 Transfer 事件
logs.forEach((log) => {
console.log('全网 Transfer 事件:', log)
})
}
})
const unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
abi: erc20Abi,
eventName: 'Transfer',
fromBlock: 18000000n, // 从特定区块开始
toBlock: 'latest', // 监听最新区块
onLogs: (logs) => {
console.log('区块范围内的事件:', logs)
}
})
// 复杂的参数过滤
const unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
abi: erc20Abi,
eventName: 'Transfer',
args: {
from: ['0x742d35Cc6634C0532925a3b8D5C9eA6A2A3b8D5C', '0x另外一个地址'],
to: '0x特定接收地址'
},
onLogs: (logs) => {
console.log('复杂过滤结果:', logs)
}
})
const unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
abi: erc20Abi,
eventName: 'Transfer',
onLogs: (logs) => {
// 正常处理逻辑
},
onError: (error) => {
console.error('监听事件时发生错误:', error)
// 根据错误类型处理
if (error.message.includes('connection')) {
console.log('网络连接问题,尝试重连...')
// 这里可以添加重连逻辑
}
},
poll: true, // 启用轮询模式(WebSocket 失败时回退)
pollingInterval: 4000 // 轮询间隔 4 秒
})
class EventWatcher {
private unwatch: (() => void) | null = null
private retryCount = 0
private maxRetries = 5
startWatching() {
this.unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
abi: erc20Abi,
eventName: 'Transfer',
onLogs: this.handleLogs.bind(this),
onError: this.handleError.bind(this)
})
}
private handleLogs(logs: any[]) {
// 重置重试计数
this.retryCount = 0
console.log('收到事件:', logs)
}
private handleError(error: Error) {
console.error('监听错误:', error)
this.retryCount++
if (this.retryCount <= this.maxRetries) {
console.log(`尝试重连... (${this.retryCount}/${this.maxRetries})`)
setTimeout(() => this.startWatching(), 2000 * this.retryCount)
} else {
console.error('达到最大重试次数,停止监听')
}
}
stopWatching() {
if (this.unwatch) {
this.unwatch()
this.unwatch = null
}
}
}
let batchQueue: any[] = []
let batchTimer: NodeJS.Timeout | null = null
const unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
abi: erc20Abi,
eventName: 'Transfer',
onLogs: (logs) => {
// 批量处理,减少频繁操作
batchQueue.push(...logs)
if (batchTimer) clearTimeout(batchTimer)
batchTimer = setTimeout(() => {
this.processBatch(batchQueue)
batchQueue = []
}, 1000) // 1秒批量处理一次
}
})
private processBatch(logs: any[]) {
console.log(`批量处理 ${logs.length} 个事件`)
// 批量插入数据库或其他操作
}
import { createPublicClient, webSocket } from 'viem'
import { mainnet } from 'viem/chains'
// 使用 WebSocket 实现实时监听(推荐)
const publicClient = createPublicClient({
chain: mainnet,
transport: webSocket('wss://eth-mainnet.g.alchemy.com/v2/your-api-key')
})
// WebSocket 连接更高效,支持服务端推送
const unwatch = publicClient.watchContractEvent({
address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
abi: erc20Abi,
eventName: 'Transfer',
onLogs: (logs) => {
console.log('实时事件:', logs)
}
})
import { parseAbi } from 'viem'
const uniswapV3PoolAbi = parseAbi([
'event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)',
'event Mint(address indexed sender, address indexed owner, int24 tickLower, int24 tickUpper, uint128 amount, uint256 amount0, uint256 amount1)',
'event Burn(address indexed owner, int24 tickLower, int24 tickUpper, uint128 amount, uint256 amount0, uint256 amount1)'
])
class DexMonitor {
private unwatchFunctions: (() => void)[] = []
startMonitoring(poolAddress: string) {
// 监听交易事件
const swapUnwatch = publicClient.watchContractEvent({
address: poolAddress,
abi: uniswapV3PoolAbi,
eventName: 'Swap',
onLogs: (logs) => {
logs.forEach(log => {
console.log('交易发生:', {
交易对: poolAddress,
输入数量: log.args.amount0,
输出数量: log.args.amount1,
价格: log.args.sqrtPriceX96,
交易哈希: log.transactionHash
})
})
}
})
// 监听流动性事件
const liquidityUnwatch = publicClient.watchContractEvent({
address: poolAddress,
abi: uniswapV3PoolAbi,
eventName: ['Mint', 'Burn'],
onLogs: (logs) => {
logs.forEach(log => {
if (log.eventName === 'Mint') {
console.log('添加流动性:', log.args)
} else {
console.log('移除流动性:', log.args)
}
})
}
})
this.unwatchFunctions.push(swapUnwatch, liquidityUnwatch)
}
stopMonitoring() {
this.unwatchFunctions.forEach(unwatch => unwatch())
this.unwatchFunctions = []
}
}
const seaportAbi = parseAbi([
'event OrderFulfilled(bytes32 orderHash, address offerer, address zone, address recipient, tuple(uint8 itemType, address token, uint256 identifier, uint256 amount)[] offer, tuple(uint8 itemType, address token, uint256 identifier, uint256 amount, address recipient)[] consideration)'
])
class NFTMarketMonitor {
constructor(private marketAddress: string) {}
startWatching() {
return publicClient.watchContractEvent({
address: this.marketAddress,
abi: seaportAbi,
eventName: 'OrderFulfilled',
onLogs: (logs) => {
logs.forEach(log => {
const { offerer, recipient, offer, consideration } = log.args
console.log('NFT 交易完成:', {
卖家: offerer,
买家: recipient,
出售的NFT: offer.filter(item => item.itemType === 2), // ERC721
支付代币: consideration.filter(item => item.itemType === 0), // 原生代币
交易哈希: log.transactionHash
})
})
}
})
}
}
const bridgeAbi = parseAbi([
'event Deposit(uint256 chainId, address token, address from, address to, uint256 amount, uint256 nonce)',
'event Withdraw(bytes32 depositHash, address token, address to, uint256 amount)'
])
class CrossChainBridgeMonitor {
private unwatch: (() => void) | null = null
startBridgeMonitoring(bridgeAddress: string) {
this.unwatch = publicClient.watchContractEvent({
address: bridgeAddress,
abi: bridgeAbi,
onLogs: (logs) => {
logs.forEach(log => {
if (log.eventName === 'Deposit') {
this.handleDeposit(log.args)
} else if (log.eventName === 'Withdraw') {
this.handleWithdraw(log.args)
}
})
}
})
}
private handleDeposit(args: any) {
console.log('跨链存款:', {
目标链: args.chainId,
代币: args.token,
存款人: args.from,
接收人: args.to,
金额: args.amount
})
}
private handleWithdraw(args: any) {
console.log('跨链取款:', {
存款哈希: args.depositHash,
代币: args.token,
接收人: args.to,
金额: args.amount
})
}
stop() {
if (this.unwatch) {
this.unwatch()
this.unwatch = null
}
}
}
<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!