2、Web3.js入门:库安装、Provider配置与事件监听
好,咱们进入实战环节。Web3.js 是连接前端和以太坊的桥梁。说白了,它就是一套 JavaScript 工具包,让你能在浏览器或 Node.js 里跟链上交互。
我个人习惯把 Web3.js 比作「遥控器」。你不需要知道电视内部怎么工作的,按按钮就行。Web3.js 就是那个按钮,帮你调用合约、监听事件、查询余额。
2.1 安装 Web3.js
安装很简单,一行命令搞定。但我建议你注意版本号,不同版本 API 有差异。
npm install web3
# 或者用 yarn
yarn add web3
我在项目中遇到过一个问题:直接装最新版,结果发现有些旧合约的 ABI 格式不兼容。后来我习惯锁定版本:
npm install web3@1.10.0
嗯,这里要注意。Web3.js 1.x 和 2.x 的 Provider 配置方式不太一样。咱们课程基于 1.x 版本,因为目前生产环境用得最多。
2.2 Provider 配置:连接以太坊节点
光装库没用,你得连上一个节点。节点就是以太坊的「入口」。你可以自己跑节点,但太麻烦。一般用 Infura 或 Alchemy 这类第三方服务。
为什么用它们?你想想看,自己维护一个以太坊全节点,硬盘要几个 TB,同步要几天。Infura 和 Alchemy 帮你做了这些脏活累活。
2.2.1 使用 Infura
先去 infura.io 注册,创建一个项目,拿到你的 Project ID。然后这样配置:
const Web3 = require('web3');
// Infura 的 RPC 地址
const infuraUrl = 'https://mainnet.infura.io/v3/YOUR_PROJECT_ID';
const web3 = new Web3(new Web3.providers.HttpProvider(infuraUrl));
// 测试连接
web3.eth.net.isListening()
.then(() => console.log('✅ 连接成功'))
.catch(e => console.error('❌ 连接失败', e));
我曾经犯过一个低级错误:把 Project ID 写死在代码里,结果不小心提交到了 GitHub。几分钟后,有人用我的额度去扫链,账单直接爆了。所以,永远不要把 API Key 硬编码。用环境变量:
const infuraUrl = `https://mainnet.infura.io/v3/${process.env.INFURA_PROJECT_ID}`;
2.2.2 使用 Alchemy
Alchemy 和 Infura 类似,但我觉得它的开发者体验更好。尤其是它的 Dashboard,能看到请求的详细日志,调试时特别方便。
const alchemyUrl = 'https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY';
const web3 = new Web3(new Web3.providers.HttpProvider(alchemyUrl));
两者选哪个?我个人偏好 Alchemy,因为它的免费额度更慷慨,而且 WebSocket 支持更稳定。但 Infura 用户基数大,社区资源多。你都可以试试。
2.2.3 HTTP vs WebSocket Provider
这里有个关键区别。HTTP Provider 只能做「请求-响应」式的调用。比如查询余额、调用合约方法。但如果你想实时监听事件,必须用 WebSocket Provider。
为什么?因为事件是链上主动推给你的,不是你去拉的。HTTP 是单向的,你问一句它答一句。WebSocket 是双向的,链上可以主动通知你。
// WebSocket Provider 配置
const wsUrl = 'wss://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY';
const web3Ws = new Web3(new Web3.providers.WebsocketProvider(wsUrl));
// 注意:WebSocket 连接有超时机制
// 我建议加上重连逻辑
const provider = new Web3.providers.WebsocketProvider(wsUrl, {
clientConfig: {
keepalive: true,
keepaliveInterval: 60000 // 60秒心跳
},
reconnect: {
auto: true,
delay: 5000, // 5秒后重连
maxAttempts: 10
}
});
2.3 监听合约事件的 subscribe 方法
好,现在连上节点了,该监听事件了。Web3.js 提供了 subscribe 方法,专门用来订阅链上事件。
2.3.1 基本用法
假设你有一个 ERC-20 代币合约,它有个 Transfer 事件。你想实时监控所有转账:
// 合约地址和 ABI
const contractAddress = '0x...'; // 你的合约地址
const contractABI = [ /* 合约 ABI 数组 */ ];
const contract = new web3Ws.eth.Contract(contractABI, contractAddress);
// 订阅 Transfer 事件
const subscription = contract.events.Transfer({
filter: {}, // 可以加过滤条件,比如只监听某个地址
fromBlock: 'latest' // 从最新区块开始
})
.on('data', (event) => {
console.log('📦 收到事件:', event);
// event.returnValues 里包含事件参数
console.log(`从 ${event.returnValues.from} 转账到 ${event.returnValues.to}`);
console.log(`金额: ${event.returnValues.value}`);
})
.on('changed', (event) => {
// 当交易被回滚时触发
console.log('🔄 事件状态变更(可能被回滚):', event);
})
.on('error', (error) => {
console.error('❌ 监听出错:', error);
});
// 取消订阅
// subscription.unsubscribe();
这里有个细节:fromBlock: 'latest' 表示只监听新产生的区块。如果你想从某个历史区块开始监听,可以指定区块号:fromBlock: 15000000。
2.3.2 过滤事件
实际项目中,你往往不需要监听所有事件。比如你只关心某个地址的转账:
// 只监听 from 为特定地址的事件
const subscription = contract.events.Transfer({
filter: {
from: '0xYourAddressHere'
},
fromBlock: 'latest'
});
你想想看,如果不加过滤,一个热门合约每秒可能产生几十个事件。你的程序会疯掉的。所以,能用 filter 就用 filter。
2.3.3 监听所有事件
有时候你想监听合约的所有事件,不管是什么类型。可以用 events.allEvents:
const subscription = contract.events.allEvents({
fromBlock: 'latest'
})
.on('data', (event) => {
console.log('事件类型:', event.event);
console.log('事件参数:', event.returnValues);
});
这个方法在调试时特别有用。我记得有一次合约升级后,某个事件突然不触发了。我用 allEvents 一监听,发现是事件名拼写错了。嗯,这种低级错误排查起来最费时间。
2.3.4 处理重连
WebSocket 连接不稳定是常态。网络抖动、节点重启、服务端断开,都可能导致连接中断。所以,必须实现重连机制。
function subscribeWithReconnect(contract, eventName, callback) {
let subscription;
function start() {
subscription = contract.events[eventName]({
fromBlock: 'latest'
})
.on('data', callback)
.on('error', (err) => {
console.error('监听出错,5秒后重连...', err.message);
setTimeout(start, 5000);
});
}
start();
// 返回取消订阅的函数
return () => {
if (subscription) subscription.unsubscribe();
};
}
// 使用
const unsubscribe = subscribeWithReconnect(
contract,
'Transfer',
(event) => {
console.log('收到转账:', event.returnValues);
}
);
2.4 实战:完整的事件监听脚本
把上面的知识点串起来,写一个完整的监听脚本:
const Web3 = require('web3');
require('dotenv').config();
// 配置
const WS_URL = process.env.ALCHEMY_WS_URL;
const CONTRACT_ADDRESS = '0xdAC17F958D2ee523a2206206994597C13D831ec7'; // USDT 合约
const CONTRACT_ABI = [ /* USDT 的 ABI,包含 Transfer 事件 */ ];
// 初始化 WebSocket Provider
const provider = new Web3.providers.WebsocketProvider(WS_URL, {
clientConfig: { keepalive: true, keepaliveInterval: 60000 },
reconnect: { auto: true, delay: 5000, maxAttempts: 20 }
});
const web3 = new Web3(provider);
const contract = new web3.eth.Contract(CONTRACT_ABI, CONTRACT_ADDRESS);
console.log('🚀 开始监听 USDT 转账事件...');
// 订阅 Transfer 事件
const sub = contract.events.Transfer({
filter: { from: '0x...' }, // 可选过滤
fromBlock: 'latest'
});
sub.on('data', (event) => {
const { from, to, value } = event.returnValues;
console.log(`💸 ${from} → ${to} : ${web3.utils.fromWei(value, 'ether')} USDT`);
console.log(`区块: ${event.blockNumber}, 交易: ${event.transactionHash}`);
});
sub.on('error', (err) => {
console.error('监听出错:', err);
});
// 优雅退出
process.on('SIGINT', () => {
console.log('\n👋 正在关闭监听...');
sub.unsubscribe();
provider.disconnect();
process.exit();
});
- Web3.js 是前端与以太坊交互的 SDK,安装时注意版本锁定
- Infura 和 Alchemy 是常用的节点服务商,推荐用 WebSocket 连接
- 监听事件必须用 WebSocket Provider,HTTP 只能做轮询
- subscribe 方法支持 filter 过滤,能减少不必要的数据处理
- 生产环境必须实现重连机制,防止连接中断导致数据丢失
下一章咱们会深入解析事件日志的结构,看看 event.returnValues 背后到底藏着什么。到时候你会明白,为什么有时候事件参数会「对不上号」。