Ethers.js深入指南

Ethers.js深入指南

Ethers.js是一个完整而紧凑的以太坊开发库,提供了更现代的API设计和更好的类型支持。本章将深入介绍Ethers.js的核心功能和最佳实践。

基础设置

1. 安装

1
2
3
4
5
# 使用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;
}

练习题

  1. 实现一个完整的ERC20代币转账功能:
1
2
3
4
// 练习:实现代币转账
async function transferERC20(tokenAddress, to, amount) {
    // 你的代码
}
  1. 创建一个批量转账合约:
1
2
3
4
// 练习:实现批量转账
async function batchTransferERC20(tokenAddress, recipients, amounts) {
    // 你的代码
}
  1. 实现一个事件监听器:
1
2
3
4
// 练习:实现事件监听
function setupEventListeners(contract) {
    // 你的代码
}
  1. 创建一个Gas价格追踪器:
1
2
3
4
// 练习:实现Gas追踪
async function trackGasPrice() {
    // 你的代码
}
  1. 实现一个多签名钱包:
1
2
3
4
// 练习:实现多签钱包
class MultiSigWallet {
    // 你的代码
}

参考资源

  1. Ethers.js文档
  2. Ethers.js GitHub
  3. EIP-1559说明
  4. 以太坊开发工具
  5. Ethers.js最佳实践