# Ethers.js 简明使用教程

By [xyyme.eth](https://paragraph.com/@xyyme) · 2022-05-25

---

`Ethers.js` 是一个与 evm-compitable 区块链交互的 JavaScript 库。用户通过编写 JavaScript 代码就可以与区块链进行交互，包括读写等操作。同时还有一个名为 `web3.js` 的库也可以与区块链交互，与之相比，`Ethers.js` 更加轻量级，上手更加简单。

### 安装

> npm install --save ethers

### 基本使用方法

    // 需要导入 ethers 包
    const { ethers } = require("ethers");
    
    // 构建 provider，可以理解为与区块链交互的桥梁
    // 1. 可使用最基础的 JsonRpcProvider 进行构建 provider
    const INFURA_ID = 'xxx..';
    const provider = new ethers.providers.JsonRpcProvider(`https://mainnet.infura.io/v3/${INFURA_ID}`);
    
    // 2. 也可使用封装好的特定 provider，例如 AlchemyProvider，InfuraProvider
    // 使用 alchemy 的时候，第一个参数若为 null 则代表主网
    const ALCHEMY_ID = `xxx...`;
    const provider = new ethers.providers.AlchemyProvider('null', ALCHEMY_ID);
    

接下来我们看看一些常用的函数：

    // 获取当前网络最新的区块号 
    let blockNumber = await provider.getBlockNumber(); 
    
    // 获取余额，参数为地址，返回值是 bignumber 格式，单位为 wei
    let balance = await provider.getBalance("0x...");
    
    // 将 wei 格式转化为 ether 格式，bignumber 和 toString 格式都可以作为参数
    let balance_in_ether = ethers.utils.formatEther(balance);
    
    // 将 ether 转换为 wei 格式，返回值为 bignumber 格式
    let result = ethers.utils.parseEther("1.0");
    
    // 获取 gas price，返回值为 bignumber 格式
    let gasPrice = await provider.getGasPrice();
    
    // 获取内存 storage slot
    // 第一个参数是合约地址，第二个参数是插槽位置
    // 插槽位置可以使用十进制或者十六进制，十六进制需要加引号
    await provider.getStorageAt("0x...", 3);
    await provider.getStorageAt("0x...", "0x121");
    
    // 获取地址的 nonce
    await provider.getTransactionCount("0x...");
    

注意上面返回值为 `bignumber` 格式的数据，如果要在控制台打印，需要使用 `toString()` 进行转换。同时，由于 JavaScript 中的语法限制，所有 `await` 的语句都要放在 `async` 函数中。也就是说，要执行带有 `await` 的语句，必须为：

    const main = async () => {
        let gasPrice = await provider.getGasPrice();
    }
    
    main();
    

### 构建钱包信息

    // 构建钱包有两种方法
    // 1. 直接通过 provider 和 私钥 构建
    const privateKey1 = `0x...` // Private key of account 1
    const wallet = new ethers.Wallet(privateKey1, provider);
    
    // 2. 先通过 私钥 构建钱包，然后连接 provider
    wallet = new ethers.Wallet(privateKey1);
    walletSigner = wallet.connect(provider);
    
    // 获取钱包地址
    let address = await wallet.getAddress();
    

### 发送链上原生货币（例如 ETH，BNB）

    // eth 转账
    const tx = await wallet.sendTransaction({
        to: wallet2.getAddress(),
        value: ethers.utils.parseEther("100")
    });
    
    // 等待交易上链
    await tx.wait();
    // 打印交易信息
    console.log(tx);
    

### 构建合约

    // 可以直接通过 函数签名 构建 abi
    // 需要用到哪个函数就写哪个，不需要写出全部的函数签名
    const ERC20_ABI = [
        "function name() public view returns (string)",
        "function symbol() view returns (string)",
        "function totalSupply() view returns (uint256)",
        "function balanceOf(address) view returns (uint)",
    
        "event Transfer(address indexed from, address indexed to, uint amount)"
    ];
    
    // 合约地址
    const address = '0x...';
    // 通过 地址，abi，provider 构建合约对象
    const contract = new ethers.Contract(address, ERC20_ABI, provider);
    

### 调用合约只读方法

    // 读取 name() 的值
    const name = await contract.name();
    console.log(name.toString());
    
    // 读取 symbol() 的值
    const symbol = await contract.symbol();
    console.log(symbol);
    
    // 读取 ERC20 合约中 balanceOf 的值
    const balance = await contract.balanceOf('0x....');
    console.log(balance.toString());
    

### 调用合约的写方法

    // 获取钱包的 ERC20 余额
    const balance = await contract.balanceOf(wallet.getAddress());
    
    // 合约连接钱包对象
    const contractWithWallet = contract.connect(wallet);
    
    // 调用合约的 transfer 方法向其他账户转账
    // 注意这里是调用 ERC20 合约的 transfer 函数，而不是原生货币转账
    // 如果要调用 approve 函数，则为 contractWithWallet.approve
    const tx = await contractWithWallet.transfer('0x...', balance);
    // 等待交易上链
    await tx.wait();
    
    console.log(tx);
    

### 实时监听合约事件

    // Transfer 事件要在 abi 中声明
    // 括号中的参数分别对应 Transfer 事件的参数
    contract.on("Transfer", (from, to, amount, event) => {
        console.log(`${from} sent ${ethers.utils.formatEther(amount)} to ${to}`);
    });
    
    // 这里是指定参数的监听行为
    // 例如，我只想监听接收人是 `0x1234....` 的事件，那么就这样指定地址
    // 构造一个 filter，然后通过 filter 筛选
    filter = contract.filters.Transfer(null, '0x1234....');
    
    // Receive an event when that filter occurs
    contract.on(filter, (from, to, amount, event) => {
        // The to will always be "address"
        console.log(`I got ${ethers.utils.formatEther(amount)} from ${from}.`);
    });
    

### 设置范围，扫描指定区块范围的事件

    // 创建 指定发送人是 myAddress 的 filter
    const myAddress = `0x...`;
    filterFrom = contract.filters.Transfer(myAddress, null);
    
    // 创建 指定接收人是 myAddress 的 filter
    filterTo = contract.filters.Transfer(null, myAddress);
    // 扫描指定区块的范围
    
    console.log(await contract.queryFilter(filterFrom, 14692250, 14693250))
    
    // 扫描最近 1000 个区块
    console.log(await contract.queryFilter(filterFrom, -1000))
    
    // 扫描所有区块
    await daiContract.queryFilter(filterTo)
    

### 签名

    // 签名 utf-8
    const signature = await wallet.signMessage('hello world');
    console.log(signature);
    
    // 更常见的 case 是签名 hash，长度为 32 字节
    // 注意签名十六进制时，必须要将其转换为数组格式
    
    // This string is 66 characters long
    message = "0x4c8f18581c0167eb90a761b4a304e009b924f03b619a0c0e8ea3adfce20aee64";
    // This array representation is 32 bytes long
    messageBytes = ethers.utils.arrayify(message);
    
    // To sign a hash, you most often want to sign the bytes
    const signature2 = await wallet.signMessage(messageBytes);
    console.log(signature2);
    

### 指定调用合约时的一些参数

    // 我们上面在做一些写操作，例如转 ETH，写合约等操作时
    // 使用的 gasLimit、gasPrice，都是由链上获取的默认数值
    // 如果想要手动指定，需要添加一个额外的参数
    // 声明一个对象，填写需要覆盖的字段，例如
    let overrides = {
        gasLimit: 230000,
        maxFeePerGas: ethers.utils.parseUnits('12', 'gwei'),
        maxPriorityFeePerGas: ethers.utils.parseUnits('3', 'gwei'),
        nonce: (await provider.getTransactionCount(wallet.getAddress()))
    }
    // 将 overrides 作为最后一个参数
    await walletWithSigner.transfer(wallet2.getAddress(), amount, overrides)
    

### 总结

本文列出了一些常用的用法，作为基本的使用基本够用了。查询更多方法可以查询 `Ethers.js` 的[官方文档](https://docs.ethers.io/v5/)。总的来说，`Ethers.js` 还是比较简单的，上手也比较快。

### 关于我

欢迎[和我交流](https://linktr.ee/xyymeeth)

### 参考

[

Documentation
-------------

Documentation for ethers, a complete, tiny and simple Ethereum library.

https://docs.ethers.org

![](https://storage.googleapis.com/papyrus_images/8d250b70269d5cc279fc8f5d00fda99086933fe57d001fc87efbd30595cc0097.jpg)

](https://docs.ethers.io/v5/)

---

*Originally published on [xyyme.eth](https://paragraph.com/@xyyme/ethers-js)*
