基于 ethers.js 的区块链事件处理与钱包管理实践指南

  • 木西
  • 发布于 1天前
  • 阅读 213

前言本文将围绕事件检索与监听、HD钱包批量生成与加密存储、静态调用与callData构造、ERC标准合约识别等关键场景,结合代码示例与最佳实践,展示如何利用ethers.js完成从基础交互到高级功能的完整流程。无论是初学者还是有经验的开发者,都能通过本指南快速掌握ethers.

前言

本文将围绕 事件检索与监听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发给addr2Transfer事件 let rule3 = TokenContract.filters.Transfer(addr1,addr2);//过滤所有从 addr1发给addr2Transfer事件 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](https://github.com/satoshilabs/slips/blob/master/slip-0044.md)
    -   `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`,识别关键通过totalSupply

const 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 进行区块链开发的关键技术,涵盖事件处理、钱包管理、合约交互及标准识别四大核心模块,并通过代码示例与最佳实践提供完整解决方案;

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
木西
木西
江湖只有他的大名,没有他的介绍。