基于 ethers.js 的区块链事件处理与钱包管理实践指南
前言
本文将围绕 事件检索与监听、HD 钱包批量生成与加密存储、静态调用与 callData 构造、ERC 标准合约识别 等关键场景,结合代码示例与最佳实践,展示如何利用 ethers.js 完成从基础交互到高级功能的完整流程。无论是初学者还是有经验的开发者,都能通过本指南快速掌握 ethers.js 的核心用法,并将其应用于实际项目中
Event事件
检索事件
const { ethers } = require("hardhat");
async function SearchEvent() {
try {
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
const signer = await provider.getSigner();
const TokenAddress = "0xxxxx";//合约地址
const TokenABI =[]//合约的abi;
const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);//创建合约
//读取合约
const name = await TokenContract.name();
console.log("Contract Name:", name);
const symbol = await TokenContract.symbol();
console.log("Contract Symbol:", symbol);
const totalSupply = await TokenContract.totalSupply();
console.log("Total Supply:", totalSupply.toString());
//合约转eth
const arr1="0xxxxxxxx"
await TokenContract.transfer(arr1,10);//给arr1转10;
const block = await provider.getBlockNumber()//得到当前block
const transferEvents = await TokenContract.queryFilter('Transfer', block - x, block);//检索合约Transfer,从block - x,到block之间的解析事件
console.log(`Transfer事件数量: ${transferEvents.length}`);
//transferEvents是个数组,我们可以解析他的参数
console.log(...transferEvents[0].args);//返回form,to ,value
}catch (error) {
console.error("Error:", error);
}
}
监听事件
//以上同上
TokenContract.on("Transfer", (from, to, value, event) => {
console.log(`Transfer事件触发:`);
console.log(`From: ${from}`);
console.log(`To: ${to}`);
console.log(`Value: ${value.toString()}`);
console.log(` 从 ${from}=> 到 ${to} = ${value.toString()}`);
console.log(`Event Details:`, event);
});
过滤事件
设置过滤规则:contract.filters.EVENT_NAME( ...args )说明:EVENT_NAME:过滤事件,...args:过滤规则
基础规则汇总
规则 | 含义 | 示例 |
---|---|---|
null |
该位置不限制,匹配任意值 | contract.filters.Transfer(null, addr) |
单个值 | 必须完全匹配 | contract.filters.Transfer(addr) |
数组 | 至少匹配数组中任意一个值 | contract.filters.Transfer(null, [addr1, addr2]) |
以上代码如上
//设置规则
# 规则1
let addr1="0xf39Fd6e51aad88F6F4ce6axxxxxxx"
let addr2="0x70997970C51812dc3A010C7xxxxxx"
let addr3="0xb0997970C51812dcxxxxxxxxxxxxx"
let rule1 = TokenContract.filters.Transfer(addr1);//过滤来自`addr1`地址的`Transfer`事件
let rule2 = TokenContract.filters.Transfer(null,addr2);//过滤所有发给 addr2`地址的`Transfer`事件
let rule3 = TokenContract.filters.Transfer(addr1,addr2);//过滤所有从 `addr1`发给`addr2`的`Transfer`事件
let rule3 = TokenContract.filters.Transfer(addr1,addr2);//过滤所有从 `addr1`发给`addr2`的`Transfer`事件
let rule4 = TokenContract.filters.Transfer(null,[addr2,addr3]);//过滤所有发给 addr2`地址的或者addr3`的Transfer`事件
# 其他就是各种组合使用了
# 过滤使用
TokenContract.on(rule1, (res) => {
console.log('---------监听开始过滤--------');
console.log(
`${res.args[0]} -> ${res.args[1]} ${res.args[2]}`
)
})
# 其他同上 把过滤规则给监听事件即可
批量生成HD钱包
BIP汇总
BIP编号 | 主要用途 | 典型格式示例 |
---|---|---|
BIP-32 | HD 钱包路径 | m/44'/0'/0'/0/0 |
BIP-39 | 助记词生成种子 | 12/24 个单词 |
BIP-44 | 多币种路径 | m/44'/60'/0'/0/0 |
BIP-49 | 隔离见证兼容地址 | m/49'/0'/0'/0/0 |
BIP-84 | 原生隔离见证地址 | m/84'/0'/0'/0/0 |
BIP-173 | Bech32 地址编码 | bc1q... |
BIP-350 | Taproot 地址编码 | bc1p... |
以BIP-44为例代码实践
- 助记词生成
const mnemonic = ethers.Mnemonic.entropyToPhrase(ethers.randomBytes(32))
-
创建HD基钱包
BIP-44
基路格式:"m / purpose' / coin_type' / account' / change" 参数说明-
m
:主密钥(Master Key) -
purpose'
:固定为44'
(表示遵循 BIP-44 多账户标准) -
coin_type'
:币种标识(如0'
= BTC,60'
= ETH,501'
= SOL)详细可查看SLIP-44 -
account'
:账户编号(从0'
开始) -
change
:比特币专用(0
= 外部地址,1
= 找零地址);其他链通常为0
-
address_index
:地址索引(从0
开始)
-
# BIP-44
// 基路径:
const basePath = "44'/60'/0'/0"
# 生成第一对外的链接
const baseWallet = ethers.HDNodeWallet.fromPhrase(mnemonic, basePath)
- 批量生成
const WalletNumber = 10;//钱包数
for (let i = 0; i < WalletNumber; i++) {
let NewBaseWallet = baseWallet.derivePath(i.toString());
console.log(`第${i+1}个钱包地址: ${baseWalletNew.address}`)
wallets.push(baseWalletNew);//生成10个钱包
}
console.log("钱包地址列表:", wallets.map(wallet => wallet.address));
- 加密JSON保存
async function saveWalletJson() {
const wallet = ethers.Wallet.fromPhrase(mnemonic);//助记词
console.log("通过助记词创建钱包:")
console.log(wallet)
// 加密json用的密码,可以更改成别的
const pwd = "XXXX";
const json = await wallet.encrypt(pwd)
console.log("钱包的加密json:")
console.log(json)
require("fs").writeFileSync("keystoreBatch.json", json);//在当前文件夹下生成一个 keystoreBatch.json文件
}
saveWalletJson();
- 通过加密json读取钱包信息
async function ReadWalletJson() {
console.log("开始读取json文件");
const json=require("fs").readFileSync("keystoreBatch.json", "utf8");
const walletJson =await ethers.Wallet.fromEncryptedJson(json, "xxx");//生成json时设置的密码
console.log("Wallet from JSON:",walletJson);
console.log("Address:", walletJson.address);
console.log("Private Key:", walletJson.privateKey);
console.log("Mnemonic:", walletJson.mnemonic.phrase);
}
ReadWalletJson();
staticCall和callStatic:
名称 | 所属模块 | 作用 | 返回值 | 适用场景 |
---|---|---|---|---|
staticCall |
ethers.Contract 实例方法 |
以 只读方式 调用合约函数,不修改状态 | 函数返回值 | 任何函数(读/写) |
callStatic |
ethers.Contract 实例方法(v6 新增) |
以 只读方式 调用合约函数,不修改状态 | 函数返回值 | 任何函数(读/写) |
# 代码实例
# staticCall
const from="0xf39xxx"
const to="0x70xxx"
const result = await TokenContract.transfer.staticCall(to,10,{
// 可选 overrides
from: from, // 指定调用者(模拟不同账户)
});
console.log('模拟结果:', result);
# callStatic
const result = await TokenContract.transfer.staticCall(to,10,{
// 可选 overrides
from: from, // 指定调用者(模拟不同账户)
});
console.log('模拟结果:', result);
callData
-
接口abi:infce=new ethers.Interface(abi);//两者是一样的功能
-
callData:infce=TokenContract.interface;//两者是一样的功能
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
const signer = await provider.getSigner();
const TokenAddress = "0xxxx";//合约地址
const TokenABI =[];//abi
const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);
const param = TokenContract.interface.encodeFunctionData(
"balanceOf",
["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"]
);
console.log("param:", param);
const tx = {
to: TokenAddress,
data: param
}
// 发起交易,可读操作(view/pure)可以用 provider.call(tx)
const balanceWETH = await provider.call(tx)
console.log(`存款前WETH持仓: ${ethers.formatEther(balanceWETH)}\n`)
encodeFunctionData
const provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545");
const signer = await provider.getSigner();
const TokenAddress = "0xxxxxxx";//合约地址
const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);//构造合约
# 使用合约的transfer 向0x70997970C51812dc3A010C7d01b50e0d17dc79C8 转10n
const calldata = TokenContract.interface.encodeFunctionData('transfer', [
'0x70997970C51812dc3A010C7d01b50e0d17dc79C8', // 收款地址
10n // 转账数量 (BigInt)
]);
console.log(calldata)//生成callData
const wallet = new ethers.Wallet("钱包的私钥", provider);
const tx = await wallet.sendTransaction({
to: "0x5Fxxxxxxx",//合约地址
data: calldata,
});
await tx.wait();
console.log("交易成功生成的txHash:", tx.hash);
//通过交易hash
//交易的详细信息
const hash = await provider.getTransaction(tx.hash);
//交易收据
const receipt = await provider.getTransactionReceipt(tx.hash);
识别ERC20、ERC721、ERC115标准合约
识别关键说明:所有现代标准(ERC721、ERC1155)都实现了 ERC165,通过 supportsInterface(bytes4 interfaceId)
函数声明支持的接口,ERC20 不支持 ERC165
-
ERC20
说明:识别关键ERC20不是基于ERC165,但是ERC20包含totalSupply
,识别关键通过totalSupplyconst provider = new ethers.JsonRpcProvider("http://127.0.0.1:8545"); const signer = await provider.getSigner();// const TokenAddress = "0x5Fbxxxxx";//合约地址 const TokenABI = []//abi const TokenContract = new ethers.Contract(TokenAddress, TokenABI, signer);//创建合约 const totalSupplyValue=await TokenContract.totalSupply(); console.log(totalSupplyValue)//说明是ERC20
-
ERC721
说明:识别关键是ERC721基于ERC165,ERC165标准包含supportsInterface(bytes4 interfaceId)
创建合约如上 const isERC721 = await contract.supportsInterface("0x80ac58cd"); console.log(isERC721); // true 或 false
-
ERC1155
说明:识别关键是ERC721基于ERC165,ERC165标准包含supportsInterface(bytes4 interfaceId)
创建合约如上 const isERC721 = await contract.supportsInterface("0xd9b67a26"); console.log(isERC721); // true 或 false
-
总结
调用函数/方法 返回值 识别结果 备注 supportsInterface(0x80ac58cd)
true
ERC721 NFT 标准接口标识符 supportsInterface(0xd9b67a26)
true
ERC1155 多代币标准接口标识符 totalSupply()
等函数调用成功成功 ERC20 同质化代币标准(无 ERC165)
总结
以上就是系统介绍了使用 ethers.js 进行区块链开发的关键技术,涵盖事件处理、钱包管理、合约交互及标准识别四大核心模块,并通过代码示例与最佳实践提供完整解决方案;