:2026-02-23 13:36 点击:3
在区块链应用开发中,实时监听以太坊网络上的事件(如交易确认、合约日志、代币转账等)是一项核心需求,Go 语言凭借其高性能、并发特性和简洁的语法,成为开发以太坊应用的热门选择,本文将详细介绍如何使用 Go 语言来监听以太坊事件,涵盖环境准备、连接节点、监听合约日志以及处理事件等关键步骤。
在深入技术细节之前,我们先了解一下为何 Go 语言适合这项任务:
go-ethereum(也称为 geth 的客户端库),它提供了完整的以太坊交互功能,包括连接节点、发送交易、调用合约以及监听事件。在开始编码之前,我们需要准备以下环境:
geth 或 Parity 节点,优点是数据完全可控,缺点是同步区块数据需要较多时间和存储空间。go-ethereum 库,打开终端,执行以下命令:go get -u github.com/ethereum/go-ethereum go get -u github.com/ethereum/go-ethereum/crypto go get -u github.com/ethereum/go-ethereum/common go get -u github.com/ethereum/go-ethereum/ethclient go get -u github.com/ethereum/go-ethereum/accounts/abi go get -u github.com/ethereum/go-ethereum/accounts/abi/bind go get -u github.com/ethereum/go-ethereum/event
这些包提供了与以太坊交互所需的核心功能。
监听事件的第一步是与以太坊节点建立连接,我们可以使用 ethclient 包来实现。
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/ethereum/go-ethereum/ethclient"
)
func main() {
// 替换为你的以太坊节点地址
nodeURL := "https://mainnet.infura.io/v3/YOUR_PROJECT_ID" // Infura 节点地址
// 或者本地节点: "http://localhost:8545"
client, err := ethclient.Dial(nodeURL)
if err != nil {
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
}
defer client.Close()
fmt.Println("Successfully connected to the Ethereum client")
// 验证连接,获取最新区块号
blockNumber, err := client.BlockNumber(context.Background())
if err != nil {
log.Fatalf("Failed to get block number: %v", err)
}
fmt.Printf("Latest block number: %d\n", blockNumber)
// 保持程序运行,以便后续监听
select {}
}
将 YOUR_PROJECT_ID 替换为你自己的 Infura 项目 ID(或其他节点服务的认证信息),运行此代码,如果成功打印出最新区块号,则表示连接成功。
监听合约日志是 Go 语言监听以太坊事件最常见的需求,我们需要合约的 ABI(Application Binary Interface)和合约地址。
假设我们有一个简单的 ERC20 代币合约,我们想监听它的 Transfer 事件,我们需要获取该合约的 ABI JSON 字符串和合约地址。
使用 abi 包解析 ABI,然后使用 event 包创建事件过滤器。
// 假设我们已经有了一个 ERC20 合约的 ABI 字符串
erc20ABI := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]`
// 合约地址,USDT 的主网地址 (示例,请替换为实际地址)
contractAddress := "0xdAC17F958D2ee523a2206206994597C13D831ec7"
// 解析 ABI
parsedABI, err := abi.JSON(strings.NewReader(erc20ABI))
if err != nil {
log.Fatalf("Failed to parse ABI: %v", err)
}
// 创建 Transfer 事件的主题
transferSig := parsedABI.Events["Transfer"].ID
使用 ethclient 的 FilterLogs 方法或 SubscribeFilterLogs 来监听事件。SubscribeFilterLogs 是订阅模式,能够实时接收新事件。
package main
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/event"
)
func main() {
nodeURL := "https://mainnet.infura.io/v3/YOUR_PROJECT_ID"
client, err := ethclient.Dial(nodeURL)
if err != nil {
log.Fatalf("Failed to connect to the Ethereum client: %v", err)
}
defer client.Close()
fmt.Println("Successfully connected to the Ethereum client")
// ERC20 Transfer Event ABI
erc20ABI := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]`
parsedABI, err := abi.JSON(strings.NewReader(erc20ABI))
if err != nil {
log.Fatalf("Failed to parse ABI: %v", err)
}
// USDT Contract Address on Mainnet
contractAddress := common.HexToAddress("0xdAC17F958D2ee523a2206206994597C13D831ec7")
transferSig := parsedABI.Events["Transfer"].ID
// 创建查询条件
query := ethereum.FilterQuery{
Addresses: []common.Address{contractAddress},
Topics: [][]common.Hash{[]common.Hash{transferSig}},
}
// 使用 event subscription 监听新事件
logs := make(chan ethereum.Log)
sub, err := client.SubscribeFilterLogs(context.Background(), query, logs)
if err != nil {
log.Fatalf("Failed to subscribe to filter logs: %v", err)
}
defer sub.Unsubscribe() // 确保在退出时取消订阅
fmt.Println("Subscribed to Transfer events. Waiting for logs...")
// 启动一个 goroutine 来处理日志
go func() {
for {
select {
case err := <-sub.Err():
log.Fatalf("Subscription error: %v", err)
case vLog := <-logs:
fmt.Printf("New Transfer Log:\n")
fmt.Printf(" Address: %s\n", vLog.Address.Hex())
fmt.Printf(" Topics: %v\n", vLog.Topics)
fmt.Printf(" Data: %x\n", vLog.Data)
// 解析事件数据
var transferEvent struct {
From common.Address
To common.Address
Value *big.Int
}
err := parsedABI.UnpackIntoInterface(&transferEvent, "Transfer", vLog.Data)
if err != nil {
log.Printf("Failed to unpack event data: %v\n", err)
continue
}
fmt.Printf(" Parsed Event:\n")
fmt.Printf(" From: %s\n", transferEvent.From.Hex())
fmt.Printf(" To: %s\n", transferEvent.To.Hex())
fmt.Printf(" Value: %s\n", transferEvent.Value.String())
fmt.Println("-------------------------------------")
}
}
}()
// 保持主 goroutine 运行
select {}
}
上面的例子主要监听从订阅时刻开始的新事件,如果你需要监听历史事件,可以在创建 FilterQuery 时
本文由用户投稿上传,若侵权请提供版权资料并联系删除!