Ethers.js深入指南
Ethers.js是一个完整而紧凑的以太坊开发库,提供了更现代的API设计和更好的类型支持。本章将深入介绍Ethers.js的核心功能和最佳实践。
基础设置
1. 安装
| # 使用npm安装
npm install ethers
# 使用yarn安装
yarn add ethers
|
2. Provider配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | // 引入ethers
const { ethers } = require('ethers');
// 使用默认provider
const provider = ethers.getDefaultProvider('mainnet');
// 使用Infura
const provider = new ethers.providers.InfuraProvider(
'mainnet',
'YOUR-PROJECT-ID'
);
// 使用Alchemy
const provider = new ethers.providers.AlchemyProvider(
'mainnet',
'YOUR-API-KEY'
);
// 使用JsonRpcProvider
const provider = new ethers.providers.JsonRpcProvider(
'http://localhost:8545'
);
// 使用WebSocketProvider
const provider = new ethers.providers.WebSocketProvider(
'wss://mainnet.infura.io/ws/v3/YOUR-PROJECT-ID'
);
|
Provider功能
1. 网络信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 | async function getNetworkInfo() {
// 获取网络信息
const network = await provider.getNetwork();
console.log('Network name:', network.name);
console.log('Chain ID:', network.chainId);
// 获取区块号
const blockNumber = await provider.getBlockNumber();
console.log('Current block:', blockNumber);
// 获取Gas价格
const gasPrice = await provider.getGasPrice();
console.log('Gas price:', ethers.utils.formatUnits(gasPrice, 'gwei'), 'gwei');
}
// 获取区块信息
async function getBlockInfo(blockNumber) {
const block = await provider.getBlock(blockNumber);
console.log('Block:', {
number: block.number,
timestamp: block.timestamp,
miner: block.miner,
transactions: block.transactions.length
});
}
// 获取交易信息
async function getTransactionInfo(txHash) {
const tx = await provider.getTransaction(txHash);
const receipt = await provider.getTransactionReceipt(txHash);
return {
hash: tx.hash,
from: tx.from,
to: tx.to,
value: ethers.utils.formatEther(tx.value),
gasPrice: ethers.utils.formatUnits(tx.gasPrice, 'gwei'),
gasUsed: receipt.gasUsed.toString(),
status: receipt.status === 1 ? 'Success' : 'Failed'
};
}
|
2. 事件监听
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | // 监听新区块
provider.on('block', async (blockNumber) => {
const block = await provider.getBlock(blockNumber);
console.log('New block:', blockNumber, 'with', block.transactions.length, 'txs');
});
// 监听待定交易
provider.on('pending', (tx) => {
console.log('Pending transaction:', tx);
});
// 监听特定地址
provider.on(address, (balance) => {
console.log('Balance changed:', ethers.utils.formatEther(balance));
});
|
钱包操作
1. 创建和导入钱包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | // 创建随机钱包
const randomWallet = ethers.Wallet.createRandom();
console.log('Address:', randomWallet.address);
console.log('Private key:', randomWallet.privateKey);
console.log('Mnemonic:', randomWallet.mnemonic.phrase);
// 从私钥导入
const privateKey = '0x...';
const wallet = new ethers.Wallet(privateKey, provider);
// 从助记词导入
const mnemonic = 'word1 word2 ... word12';
const walletFromMnemonic = ethers.Wallet.fromMnemonic(mnemonic);
// 从JSON导入
const json = '{"address": "0x...", "privateKey": "0x..."}';
const password = 'your-password';
const walletFromJson = await ethers.Wallet.fromEncryptedJson(json, password);
|
2. 钱包操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | // 连接provider
const connectedWallet = wallet.connect(provider);
// 获取余额
async function getWalletBalance(address) {
const balance = await provider.getBalance(address);
return ethers.utils.formatEther(balance);
}
// 发送交易
async function sendTransaction(to, value) {
// 创建交易
const tx = await wallet.sendTransaction({
to: to,
value: ethers.utils.parseEther(value)
});
// 等待确认
const receipt = await tx.wait();
return receipt;
}
// 签名消息
async function signMessage(message) {
const signature = await wallet.signMessage(message);
return signature;
}
// 验证签名
function verifyMessage(message, signature) {
const recoveredAddress = ethers.utils.verifyMessage(message, signature);
return recoveredAddress;
}
|
合约交互
1. 合约部署
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | // 部署合约
async function deployContract(contractFactory, args = []) {
const factory = new ethers.ContractFactory(
contractFactory.abi,
contractFactory.bytecode,
wallet
);
const contract = await factory.deploy(...args);
await contract.deployed();
return contract;
}
// 部署示例
const MyContract = {
abi: [...],
bytecode: '0x...'
};
const contract = await deployContract(MyContract, ['Constructor Arg']);
console.log('Contract deployed to:', contract.address);
|
2. 合约调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | // 创建合约实例
const contract = new ethers.Contract(address, abi, provider);
// 连接钱包
const contractWithSigner = contract.connect(wallet);
// 调用只读方法
async function readContract() {
const value = await contract.getValue();
console.log('Value:', value.toString());
// 带参数的调用
const balance = await contract.balanceOf(address);
console.log('Balance:', ethers.utils.formatEther(balance));
}
// 调用写入方法
async function writeContract() {
// 发送交易
const tx = await contractWithSigner.setValue(123);
// 等待确认
const receipt = await tx.wait();
console.log('Transaction confirmed:', receipt.transactionHash);
}
// 监听事件
contract.on('Transfer', (from, to, value, event) => {
console.log('Transfer:', {
from,
to,
value: ethers.utils.formatEther(value),
blockNumber: event.blockNumber
});
});
|
3. 合约工厂
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | // 创建合约工厂
const factory = new ethers.ContractFactory(abi, bytecode, wallet);
// 估算部署Gas
async function estimateDeployment(args = []) {
const deployTx = factory.getDeployTransaction(...args);
const estimatedGas = await provider.estimateGas(deployTx);
return estimatedGas;
}
// 批量部署
async function batchDeploy(count, args = []) {
const contracts = [];
for (let i = 0; i < count; i++) {
const contract = await factory.deploy(...args);
await contract.deployed();
contracts.push(contract.address);
}
return contracts;
}
|
高级特性
1. BigNumber处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | // BigNumber操作
const { BigNumber } = ethers;
function bigNumberOperations() {
// 创建BigNumber
const a = BigNumber.from('1000000000000000000'); // 1 ETH
const b = BigNumber.from('500000000000000000'); // 0.5 ETH
// 算术运算
const sum = a.add(b);
const diff = a.sub(b);
const product = a.mul(2);
const quotient = a.div(2);
// 比较
const isGreater = a.gt(b);
const isEqual = a.eq(b);
// 格式化
console.log('Sum:', ethers.utils.formatEther(sum));
}
|
2. 工具函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 | // 常用工具函数
function utilityFunctions() {
// 地址检查
const isAddress = ethers.utils.isAddress('0x...');
// 计算合约地址
const futureAddress = ethers.utils.getContractAddress({
from: deployer.address,
nonce: await provider.getTransactionCount(deployer.address)
});
// 计算keccak256哈希
const hash = ethers.utils.keccak256('0x1234');
// 打包参数
const packed = ethers.utils.solidityPack(
['address', 'uint256'],
[address, amount]
);
// ABI编码
const encoded = ethers.utils.defaultAbiCoder.encode(
['string', 'uint256'],
['Hello', 123]
);
}
|
3. 错误处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | // 错误处理模式
async function handleContractErrors() {
try {
const tx = await contract.riskyFunction();
await tx.wait();
} catch (error) {
if (error.code === 'UNPREDICTABLE_GAS_LIMIT') {
console.error('Transaction will fail');
} else if (error.code === 'INSUFFICIENT_FUNDS') {
console.error('Not enough funds');
} else if (error.code === 'CALL_EXCEPTION') {
console.error('Contract call failed:', error.reason);
} else {
console.error('Unknown error:', error);
}
}
}
// 交易错误恢复
async function recoverFailedTransaction(tx) {
try {
const receipt = await tx.wait();
return receipt;
} catch (error) {
if (error.code === 'TRANSACTION_REPLACED') {
if (error.replacement) {
console.log('Transaction was replaced by:', error.replacement.hash);
return error.replacement.wait();
} else if (error.cancelled) {
console.log('Transaction was cancelled');
}
}
throw error;
}
}
|
最佳实践
1. Gas优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | // Gas优化策略
async function optimizeGas() {
// 估算Gas限制
const gasEstimate = await contract.estimateGas.transfer(to, amount);
// 添加安全边际
const safeGasLimit = gasEstimate.mul(120).div(100); // 增加20%
// 获取EIP-1559 Gas费用
const feeData = await provider.getFeeData();
// 发送交易
const tx = await contract.transfer(to, amount, {
gasLimit: safeGasLimit,
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas
});
return tx;
}
|
2. 批量操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | // 批量转账优化
async function batchTransfer(recipients, amounts) {
// 创建多调用合约
const multicall = new ethers.Contract(
multicallAddress,
multicallAbi,
wallet
);
// 准备调用数据
const calls = recipients.map((to, i) => ({
target: tokenAddress,
callData: contract.interface.encodeFunctionData('transfer', [to, amounts[i]])
}));
// 执行批量调用
const tx = await multicall.aggregate(calls);
return await tx.wait();
}
|
3. 事件过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | // 高效的事件过滤
async function filterEvents() {
// 创建过滤器
const filter = contract.filters.Transfer(wallet.address);
// 获取历史事件
const events = await contract.queryFilter(filter, -1000); // 最近1000个区块
// 处理事件
const transfers = events.map(event => ({
from: event.args.from,
to: event.args.to,
value: ethers.utils.formatEther(event.args.value),
blockNumber: event.blockNumber
}));
return transfers;
}
|
练习题
- 实现一个完整的ERC20代币转账功能:
| // 练习:实现代币转账
async function transferERC20(tokenAddress, to, amount) {
// 你的代码
}
|
- 创建一个批量转账合约:
| // 练习:实现批量转账
async function batchTransferERC20(tokenAddress, recipients, amounts) {
// 你的代码
}
|
- 实现一个事件监听器:
| // 练习:实现事件监听
function setupEventListeners(contract) {
// 你的代码
}
|
- 创建一个Gas价格追踪器:
| // 练习:实现Gas追踪
async function trackGasPrice() {
// 你的代码
}
|
- 实现一个多签名钱包:
| // 练习:实现多签钱包
class MultiSigWallet {
// 你的代码
}
|
参考资源
- Ethers.js文档
- Ethers.js GitHub
- EIP-1559说明
- 以太坊开发工具
- Ethers.js最佳实践