跳到主要内容

Ethers极简入门: 3. 读取合约信息

我最近在重新学ethers.js,巩固一下细节,也写一个WTF Ethers极简入门,供小白们使用。

推特@0xAA_Science

WTF Academy社群: 官网 wtf.academy | WTF Solidity教程 | discord | 微信群申请

所有代码和教程开源在github: github.com/WTFAcademy/WTF-Ethers


这一讲,我们将介绍Contract合约类,并利用它来读取链上的合约信息。

Contract

ethers中,Contract类是部署在以太坊网络上的合约(EVM字节码)的抽象。通过它,开发者可以非常容易的对合约进行读取call和交易transaction,并可以获得交易的结果和事件。以太坊强大的地方正是合约,所以对于合约的操作要熟练掌握。

创建Contract变量

只读和可读写Contract

Contract对象分为两类,只读和可读写。只读Contract只能读取链上合约信息,执行call操作,即调用合约中viewpure的函数,而不能执行交易transaction。创建这两种Contract变量的方法有所不同:

  • 只读Contract:参数分别是合约地址,合约abiprovider变量(只读)。
const contract = new ethers.Contract(`address`, `abi`, `provider`);
  • 可读写Contract:参数分别是合约地址,合约abisigner变量。Signer签名者是ethers中的另一个类,用于签名交易,之后我们会讲到。
const contract = new ethers.Contract(`address`, `abi`, `signer`);

注意 ethers中的call指的是只读操作,与solidity中的call不同。

读取合约信息

1. 创建Provider

我们使用Infura节点的API Key创建Provider(见第2讲:Provider):

import { ethers } from "ethers";
// 利用Infura的rpc节点连接以太坊网络
// 准备Infura API Key, 教程:https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL02_Infura/readme.md
const INFURA_ID = ''
// 连接以太坊主网
const provider = new ethers.JsonRpcProvider(`https://mainnet.infura.io/v3/${INFURA_ID}`)

2. 创建只读Contract实例

创建只读Contract实例需要填入3个参数,分别是合约地址,合约abiprovider变量。合约地址可以在网上查到,provider变量上一步我们已经创建了,那么abi怎么填?

ABI (Application Binary Interface) 是与以太坊智能合约交互的标准,更多内容见WTF Solidity教程第27讲: ABI编码ethers支持两种abi填法:

  • 方法1. 直接输入合约abi。你可以从remix的编译页面中复制,在本地编译合约时生成的artifact文件夹的json文件中得到,或者从etherscan开源合约的代码页面得到。我们用这个方法创建WETH的合约实例:
// 第1种输入abi的方式: 复制abi全文
// WETH的abi可以在这里复制:https://etherscan.io/token/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code
const abiWETH = '[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view",...太长后面省略...';
const addressWETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' // WETH Contract
const contractWETH = new ethers.Contract(addressWETH, abiWETH, provider)

在Etherscan得到abi

  • 方法2. 由于abi可读性太差,ethers创新的引入了Human-Readable Abi(人类可读abi)。开发者可以通过function signatureevent signature来写abi。我们用这个方法创建稳定币DAI的合约实例:
// 第2种输入abi的方式:输入程序需要用到的函数,逗号分隔,ethers会自动帮你转换成相应的abi
// 人类可读abi,以ERC20合约为例
const abiERC20 = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function totalSupply() view returns (uint256)",
"function balanceOf(address) view returns (uint)",
];
const addressDAI = '0x6B175474E89094C44Da98b954EedeAC495271d0F' // DAI Contract
const contractDAI = new ethers.Contract(addressDAI, abiERC20, provider)

3. 读取WETHDAI的链上信息

我们可以利用只读Contract实例调用合约的viewpure函数,获取链上信息:

const main = async () => {
// 1. 读取WETH合约的链上信息(WETH abi)
const nameWETH = await contractWETH.name()
const symbolWETH = await contractWETH.symbol()
const totalSupplyWETH = await contractWETH.totalSupply()
console.log("\n1. 读取WETH合约信息")
console.log(`合约地址: ${addressWETH}`)
console.log(`名称: ${nameWETH}`)
console.log(`代号: ${symbolWETH}`)
console.log(`总供给: ${ethers.formatEther(totalSupplyWETH)}`)
const balanceWETH = await contractWETH.balanceOf('vitalik.eth')
console.log(`Vitalik持仓: ${ethers.formatEther(balanceWETH)}\n`)

// 2. 读取DAI合约的链上信息(IERC20接口合约)
const nameDAI = await contractDAI.name()
const symbolDAI = await contractDAI.symbol()
const totalSupplDAI = await contractDAI.totalSupply()
console.log("\n2. 读取DAI合约信息")
console.log(`合约地址: ${addressDAI}`)
console.log(`名称: ${nameDAI}`)
console.log(`代号: ${symbolDAI}`)
console.log(`总供给: ${ethers.formatEther(totalSupplDAI)}`)
const balanceDAI = await contractDAI.balanceOf('vitalik.eth')
console.log(`Vitalik持仓: ${ethers.formatEther(balanceDAI)}\n`)
}

main()

可以看到,用两种方法创建的合约实例都能成功与链上交互。Vitalik的钱包里有0.05 WETH555508 DAI,见下图。

成功读取VitalikWETH和DAI持仓

说明 我们可以通过以太坊浏览器 验证Vitalik钱包里的WETH余额, 是否与通过Contract读取的一致。 通过ENS 查到Vitalik钱包地址是0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045,然后通过合约方法balanceOf得到余额正好是0.05 WETH, 结论是一致!

VitalikWETH余额

总结

这一讲,我们介绍了ethers中的Contract合约类,并创建了WETHDAI的只读Contract实例,成功读取了Vitalik这两个币的持仓。