ethers.js 中文文档

ethers.js介绍

ethers.js库旨在为以太坊区块链及其生态系统提供一个小而完整的 JavaScript API 库,它最初是与 ethers.io 一起使用,现在已经扩展为更通用的库。

ethers.js 中文文档由登链社区组织翻译, Git 代码库在 https://github.com/lbc-team/ethers.js ,欢迎大家提交 PR 一起贡献。

包含功能

开发手册目录

开始使用
以太坊基础知识
Provider API Keys
应用程序编程接口
命令行接口
Cookbook
迁移指南
测试
Contributing and Hacking
其他资源

旧文档

本版本(v5.4)将保持更新,可通过以下链接到旧版本的文档。

开始使用

安装

以太坊中各种类和函数都可以从@ethersproject下的子库中手动进行导入,但对于大多数项目来说,用完整的总库是最简单的入门方式。

/home/ricmoo> npm install --save ethers

导入

Node.js

node.js 是必须的
const { ethers } = require("ethers");
ES6 或 TypeScript
import { ethers } from "ethers";

Web 浏览器

出于安全,通常较好的方式是将这个库复制到你的web服务器中来进行各种操作。

但若想快速构建实例展示,可以将我们的CDN加载到你的web应用程序中。

在浏览器中通过ES6的方式
<script type="module"> import { ethers } from "https://cdn.ethers.io/lib/ethers-5.2.esm.min.js"; // Your code here... </script>
在浏览器中通过ES3 (UMD)的方式
<script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js" type="application/javascript"></script>

常用术语

这部分将会不断完善...

ProviderProvider(提供者)是一个用于连接以太坊网络的抽象类,提供了只读形式来访问区块链网络和获取链上状态。 
SignerSigner(签名器)通常是以某种方式直接或间接访问私钥,可以签名消息和在已授权网络中管理你账户中的以太币来进行交易。 
ContractContract(合约)是一个运行在以太坊网络上表示现实中特定合约的抽象,应用可以像使用JavaScript对象一样使用它。 
Common Terms 

连接以太坊: MetaMask

在以太坊上去开发和测试的最快、最简单的方法是使用MetaMask, 它是一个基于浏览器的扩展程序,提供了:

连接 MetaMask
// A Web3Provider wraps a standard Web3 provider, which is // what MetaMask injects as window.ethereum into each page const provider = new ethers.providers.Web3Provider(window.ethereum) // The MetaMask plugin also allows signing transactions to // send ether and pay to change state within the blockchain. // For this, you need the account signer... const signer = provider.getSigner()

连接以太坊: RPC

JSON-RPC API 另一种与以太坊交互的比较流行的方式,用在所有主要的以太坊节点 (如 GethParity) 以及许多第三方web服务 (如 INFURA)。 它通常提供了:

连接一个 RPC 客户端
// If you don't specify a //url//, Ethers connects to the default // (i.e. ``http:/\/localhost:8545``) const provider = new ethers.providers.JsonRpcProvider(); // The provider also allows signing transactions to // send ether and pay to change state within the blockchain. // For this, we need the account signer... const signer = provider.getSigner()

查询区块链

当你有了Provider之后, 你就可以通过只读的方式连接区块链, 此外,你还可以使用它来查询当前状态、获取历史日志、查找部署的代码等等。

基础查询
// Look up the current block number await provider.getBlockNumber() // 16383845 // Get the balance of an account (by address or ENS name, if supported by network) balance = await provider.getBalance("ethers.eth") // { BigNumber: "182334002436162568" } // Often you need to format the output to something more user-friendly, // such as in ether (instead of wei) ethers.utils.formatEther(balance) // '0.182334002436162568' // If a user enters a string in an input field, you may need // to convert it from ether (as a string) to wei (as a BigNumber) ethers.utils.parseEther("1.0") // { BigNumber: "1000000000000000000" }

写入区块链

发送 Ether
// Send 1 ether to an ens name. const tx = signer.sendTransaction({ to: "ricmoo.firefly.eth", value: ethers.utils.parseEther("1.0") });

合约

合约(Contract)是以太坊区块链中程序代码的抽象。

合约 (Contract) 对象使得链上的合约可以简单地作为一个普通的JavaScript对象, 通过映射的方法来编码和解码数据。

如果你熟悉数据库, 你会发现这与对象关系映射器 (ORM)是相似的。

为了与链上的合约进行通信,这个类需要知道哪些方法是可用的,以及如何编码和解码数据,这些通过应用程序二进制接口 (ABI)来提供。

这个类是一个 元类(meta-class), 这意味着它的方法是在运行时构造的, 当你将ABI传递给构造函数时,它来确定要添加哪些方法。

尽管链上的合约有许多可用的方法,但是你可以安全地忽略你认为不需要或者不使用的方法,这样合约里面的ABI就是一个较小的子集。

ABI通常来自于Solidity或Vyper编译器,但是你可以在代码中使用人类可读的ABI格式(Human-Readable),如下例所示:

连接 DAI 合约
// You can also use an ENS name for the contract address const daiAddress = "dai.tokens.ethers.eth"; // The ERC-20 Contract ABI, which is a common contract interface // for tokens (this is the Human-Readable ABI format) const daiAbi = [ // Some details about the token "function name() view returns (string)", "function symbol() view returns (string)", // Get the account balance "function balanceOf(address) view returns (uint)", // Send some of your tokens to someone else "function transfer(address to, uint amount)", // An event triggered whenever anyone transfers to someone else "event Transfer(address indexed from, address indexed to, uint amount)" ]; // The Contract object const daiContract = new ethers.Contract(daiAddress, daiAbi, provider);

只读方法

查询 DAI 合约
// Get the ERC-20 token name await daiContract.name() // 'Dai Stablecoin' // Get the ERC-20 token symbol (for tickers and UIs) await daiContract.symbol() // 'DAI' // Get the balance of an address balance = await daiContract.balanceOf("ricmoo.firefly.eth") // { BigNumber: "2413468059122458201631" } // Format the DAI for displaying to the user ethers.utils.formatUnits(balance, 18) // '2413.468059122458201631'

改变状态的方法

发送 DAI
// The DAI Contract is currently connected to the Provider, // which is read-only. You need to connect to a Signer, so // that you can pay to send state-changing transactions. const daiWithSigner = contract.connect(signer); // Each DAI has 18 decimal places const dai = ethers.utils.parseUnits("1.0", 18); // Send 1 DAI to "ricmoo.firefly.eth" tx = daiWithSigner.transfer("ricmoo.firefly.eth", dai);

监听事件

监听事件
// Receive an event when ANY transfer occurs daiContract.on("Transfer", (from, to, amount, event) => { console.log(`${ from } sent ${ formatEther(amount) } to ${ to}`); // The event object contains the verbatim log data, the // EventFragment and functions to fetch the block, // transaction and receipt and event functions }); // A filter for when a specific address receives tokens myAddress = "0x8ba1f109551bD432803012645Ac136ddd64DBA72"; filter = daiContract.filters.Transfer(null, myAddress) // { // address: 'dai.tokens.ethers.eth', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // null, // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' // ] // } // Receive an event when that filter occurs daiContract.on(filter, (from, to, amount, event) => { // The to will always be "address" console.log(`I got ${ formatEther(amount) } from ${ from }.`); });

查询历史事件

过滤历史事件
// Get the address of the Signer myAddress = await signer.getAddress() // '0x8ba1f109551bD432803012645Ac136ddd64DBA72' // Filter for all token transfers from me filterFrom = daiContract.filters.Transfer(myAddress, null); // { // address: 'dai.tokens.ethers.eth', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' // ] // } // Filter for all token transfers to me filterTo = daiContract.filters.Transfer(null, myAddress); // { // address: 'dai.tokens.ethers.eth', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // null, // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' // ] // } // List all transfers sent from me a specific block range await daiContract.queryFilter(filterFrom, 9843470, 9843480) // [ // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // args: [ // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // '0x8B3765eDA5207fB21690874B722ae276B96260E0', // { BigNumber: "4750000000000000000" }, // amount: { BigNumber: "4750000000000000000" }, // from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // to: '0x8B3765eDA5207fB21690874B722ae276B96260E0' // ], // blockHash: '0x8462eb2fbcef5aa4861266f59ad5f47b9aa6525d767d713920fdbdfb6b0c0b78', // blockNumber: 9843476, // data: '0x00000000000000000000000000000000000000000000000041eb63d55b1b0000', // decode: [Function], // event: 'Transfer', // eventSignature: 'Transfer(address,address,uint256)', // getBlock: [Function], // getTransaction: [Function], // getTransactionReceipt: [Function], // logIndex: 69, // removeListener: [Function], // removed: false, // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72', // '0x0000000000000000000000008b3765eda5207fb21690874b722ae276b96260e0' // ], // transactionHash: '0x1be23554545030e1ce47391a41098a46ff426382ed740db62d63d7676ff6fcf1', // transactionIndex: 81 // }, // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // args: [ // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // '0x00De4B13153673BCAE2616b67bf822500d325Fc3', // { BigNumber: "250000000000000000" }, // amount: { BigNumber: "250000000000000000" }, // from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // to: '0x00De4B13153673BCAE2616b67bf822500d325Fc3' // ], // blockHash: '0x8462eb2fbcef5aa4861266f59ad5f47b9aa6525d767d713920fdbdfb6b0c0b78', // blockNumber: 9843476, // data: '0x00000000000000000000000000000000000000000000000003782dace9d90000', // decode: [Function], // event: 'Transfer', // eventSignature: 'Transfer(address,address,uint256)', // getBlock: [Function], // getTransaction: [Function], // getTransactionReceipt: [Function], // logIndex: 70, // removeListener: [Function], // removed: false, // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72', // '0x00000000000000000000000000de4b13153673bcae2616b67bf822500d325fc3' // ], // transactionHash: '0x1be23554545030e1ce47391a41098a46ff426382ed740db62d63d7676ff6fcf1', // transactionIndex: 81 // } // ] // // The following have had the results omitted due to the // number of entries; but they provide some useful examples // // List all transfers sent in the last 10,000 blocks await daiContract.queryFilter(filterFrom, -10000) // List all transfers ever sent to me await daiContract.queryFilter(filterTo)

签名消息

签名消息
// To sign a simple string, which are used for // logging into a service, such as CryptoKitties, // pass the string in. signature = await signer.signMessage("Hello World"); // '0x776d4dbf69ee5ed47b3250c56dbcec7ac3a59fb64447a480dcbfe05e2431547b02cf5200876ea6a9e018680dda31a9283cb0230196782f3d48dba450f0176d141b' // // A common case is also signing a hash, which is 32 // bytes. It is important to note, that to sign binary // data it MUST be an Array (or TypedArray) // // This string is 66 characters long message = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" // This array representation is 32 bytes long messageBytes = ethers.utils.arrayify(message); // Uint8Array [ 221, 242, 82, 173, 27, 226, 200, 155, 105, 194, 176, 104, 252, 55, 141, 170, 149, 43, 167, 241, 99, 196, 161, 22, 40, 245, 90, 77, 245, 35, 179, 239 ] // To sign a hash, you most often want to sign the bytes signature = await signer.signMessage(messageBytes) // '0xc848253c4c267c12d6c039c0a367b9a1c3bc683300bad5f1f9455df67cbc36c17ddf1e28a705420cf19b0a634f86dd13f6eee8eea2b0316f9741785aa31a74d31b'

以太坊基础知识

这是 以太坊 区块链中某些方面的简要概述,开发者应当了解和知晓,以便充分地进行开发。

目前以下的内容较少,但会随着时间不断地进行扩展。

事件
gas
安全
最佳实践

事件

日志和过滤器(Logs and Filtering)

在区块链的应用程序中经常用到日志和过滤器,因为它可以对索引数据进行高效查询,并且针对不在链上的数据可以提供低成本的数据存储。

这些可以结合提供者事件API(Provider Event API) 以及合约事件API(Contract Event API) 一起使用。

合约事件API也提供了高级方法去计算和查询数据,这种方式要优先于低级的过滤器。

过滤器

当合约创建一条日志时,它可以包含了4条可索引的数据。这些索引数据经过哈希加密,且包含在一个Bloom Filter中, Bloom Filter是一种允许高效过滤的数据结构。

因此,过滤器最多可对应4个主题集(topic-set),其中每个主题集指向一个条件,该条件必须匹配该位置的索引日志主题(即每个条件都是AND语句连接在一起的)。

如果一个主题集为 null, 则该位置的日志主题 不过滤 且与 任何值 都匹配。

如果一个主题集是单个主题,则该位置的日志主题 必须 匹配 这个主题

如果主题集是一个数组,则该位置的日志主题必须匹配其中 任何一个 主题(即每个条件都是OR语句连接在一起的)。

虽然一开始听起来有点复杂绕口,但通过以下例子可以很好地帮助你理解。

主题集匹配日志 
[ A ]topic[0] = A 
[ A, null ] 
[ null, B ]topic[1] = B 
[ null, [ B ] ] 
[ null, [ B ], null ] 
[ A, B ](topic[0] = A) AND (topic[1] = B) 
[ A, [ B ] ] 
[ A, [ B ], null ] 
[ [ A, B ] ](topic[0] = A) OR (topic[0] = B) 
[ [ A, B ], null ] 
[ [ A, B ], [ C, D ] ][ (topic[0] = A) OR (topic[0] = B) ] AND [ (topic[1] = C) OR (topic[1] = D) ] 
匹配日志的例子 
ERC-20 转账过滤的例子
// 手动创建ERC-20过滤器的简短实例 // 转账事件 // 大多数用户通常使用过合约的API来过滤计算,因为它更简单,但这是为那些 // 好奇的人提供一个例证 // 如下是等价于合约API的例子 // ERC-20: // Transfer(address indexed src, address indexed dst, uint val) // // -------------------^ // ----------------------------------------^ // // 注意到只有 *src* and *dst* 被 *索引*, 因此只有这些符合过滤条件 // // 并且,注意到在Solidity事件中使用第一个主题识别事件名称,对于转账函数如下: // id("Transfer(address,address,uint256)") // // 其他要注意的地方 // - 一个主题必须是 32 字节; 添加的类型要尽可能短 // 列出所有token *来自于* 我的地址的转账 filter = { address: tokenAddress, topics: [ id("Transfer(address,address,uint256)"), hexZeroPad(myAddress, 32) ] }; // 列出所有token *转给* 我的地址的转账 filter = { address: tokenAddress, topics: [ id("Transfer(address,address,uint256)"), null, hexZeroPad(myAddress, 32) ] }; // 列出所有token *转给* 我的地址或者我的其他地址的转账 filter = { address: tokenAddress, topics: [ id("Transfer(address,address,uint256)"), null, [ hexZeroPad(myAddress, 32), hexZeroPad(myOtherAddress, 32), ] ] };

在这里解释一下,为了简化, 合约的API如下:

ERC-20 合约过滤例子
abi = [ "event Transfer(address indexed src, address indexed dst, uint val)" ]; contract = new Contract(tokenAddress, abi, provider); // 列出所有token *来自于* 我的地址的转账 contract.filters.Transfer(myAddress) // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' // ] // } // 列出所有token *转给* 我的地址的转账 contract.filters.Transfer(null, myAddress) // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // null, // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' // ] // } // 列出所有token *来自于* 我的或者我其他的地址的转账 contract.filters.Transfer(myAddress, otherAddress) // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72', // '0x000000000000000000000000ea517d5a070e6705cc5467858681ed953d285eb9' // ] // } // 列出所有token *转给* 我的地址或者我的其他地址的转账 contract.filters.Transfer(null, [ myAddress, otherAddress ]) // { // address: '0x6B175474E89094C44Da98b954EedeAC495271d0F', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // null, // [ // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72', // '0x000000000000000000000000ea517d5a070e6705cc5467858681ed953d285eb9' // ] // ] // }

Solidity 主题

这是一个在Solidity中关于“事件是如何计算”的简要不全面的概述。

这可能超出了大多数开发人员的了解范围,但对于那些希望了解更多底层技术的人来说,可能会很感兴趣。

Solidity 提供了两种类型的事件,匿名和非匿名。默认值是非匿名的,大多数开发人员不需要关注匿名事件。

对于非匿名事件,最多可以索引3个主题(而不是4个),因为保留第一个主题是事件本身的签名。这允许非匿名事件总是通过其事件签名进行过滤。

这个主题哈希值总是在索引数据的第一个位置中,通过规范化事件签名并取其keccak256哈希来计算。

对于匿名事件,最多可以索引4个主题,并且没有签名的主题哈希,因此此事件不能通过事件签名进行过滤。

每个额外索引属性(主题)的处理取决于其长度是固定的还是动态的。

对于固定长度的类型 (例如uint, bytes5),这些全部都恰好是32字节,较短的类型用零填充,数字值类型(numeric values)填充在左边,数据值类型(data values)填充在右边, 这些直接包含在32个字节的数据里面。

对于动态类型 (例如string, uint256[]) , 这些值通过 keccak256 哈希(hashed),并且使用这个哈希值。

因为动态类型是被哈希加密的,所以在解析事件时应该记录一些重要的结果。主要是其事件丢失了初始值,因此,一个主题(topics)匹配给定的字符串(string)是可能的,但如果两者不匹配,就没有办法确定最初的值是什么。

如果开发人员需要一个字符串值既能被过滤也能被读取,那么这个值必须在签名中被包含两次,一次是索引的,一次是非索引的(例如 someEvent(string indexed searchBy, string clearText))。

有关更详细的描述,请参阅 Solidity事件文档

其他内容? 即将补充

将会详细阐述字符串和事件的细节,如何过滤和保留值。

gas

将阐述“攻击向量”

gas price

gas price是被用来出价的,表明你每执行单位愿意支付的金额,来使得你的交易被处理。

Gas Limit

安全

虽然所有开发人员都应该关注安全性,但在区块链领域,开发人员还必须特别留意哪些可能被利用的漏洞。

一旦一个问题具备经济动机去利用它,就会有更大的风险,而对于区块链应用程序来说,攻击它就变得非常有价值。

除了应用程序开发人员可能需要担心的许多其他安全问题之外,还有一些额外的矢量是JavaScript开发人员应该注意的。

侧信道攻击

当所使用的算法中正交的某些内容被用来了解更多关于安全或私人信息时,就可能会发生侧信道攻击(Side-Channel Attacks)

数据释放 (Strings, Uint8Arrays, Buffers)

在JavaScript中,内存不太可能安全地分配,更重要的是也很难安全地释放。

历史的角度来说,new Buffer(16) 会重用已经释放的旧内存。这意味着稍后运行的代码,可能会访问被丢弃的数据。

这里举一个危险的例子,想象一下,如果你使用Buffer来存储私钥、签名数据,然后从函数返回,允许Buffer被解除分配。未来的函数可能会请求一个新的Buffer,该Buffer仍然保留了剩余的私钥,然后它可以使用该私钥从该帐户中窃取资金。

也有很多调试工具和系统被设计用来帮助开发人员检查JavaScript程序的内存内容。在这些情况下,任何位于内存中的私钥或助记词,都可能被系统上的其他用户或恶意脚本所获得。

时序攻击

时序攻击允许恶意用户或脚本通过分析操作执行多长时间来确定私有的数据。

在JavaScript中,当系统确定需要垃圾回收机制(Garbage Collection)时,垃圾收集会周期性地发生。每种JavaScript实现都是不同的,具有各种各样的方法和能力。

大多数垃圾回收机制需要“停止整个程序”,或者暂停所有正在执行的代码。这将给当前运行的任何代码带来很大的延迟。

攻击者可以利用这种机制来实现“条件导致延迟”。他们会设置一个场景,当系统处于需要进行垃圾收集的边缘时,使用两种路径调用代码,一种是简单路径,一种是复杂路径。 简单的路径不足以触发足够的垃圾收集,而复杂的方式可以。通过计时代码执行的时间,可以了解到是否发生了垃圾收集,从而知道采用的是简单路径还是复杂路径。

高级的时序攻击在任何基于垃圾收集的语言中都很难减缓。大多数有此问题的库都希望能够尽可能地缓解这种情况,因此这是仍有必要了解的。

常见攻击

密钥导出函数

这不是以太坊特有的,但这是一种有用的技术,并且会影响用户体验。

许多人担心加密和解密一个以太坊钱包是非常缓慢的,可能需要相当长的时间。重要的是要理解这是有意为之,因为这可以提供更强的安全性。

这个过程通常使用的算法是scrypt,这是一种内存和CPU密集型算法,它为给定的密码计算一个密钥(固定长度的伪随机字节序列)。

为什么要花这么长时间?

这是为了在用此算法期间需要使用尽可能多的CPU和内存,一台计算机在一段固定的时间内只能计算非常少的结果。为了扩大攻击规模,攻击者需要额外的计算机,这增加了暴力破解密码的代价。

例如,如果用户知道他们的正确密码,这个过程可能需要10秒来解锁他们自己的钱包并使用。

但由于攻击者不知道密码,他们必须猜测;每猜一次也需要花10秒的时间。因此,如果他们想尝试猜测100万个密码,他们的电脑将被完全占用1000万秒,或大约115天。

如果不使用这样的算法,用户将能够立即登录,然而,100万个密码只需要几秒钟的尝试。即使是安全的密码也可能在短时间内被破解。算法是不可能使得攻击者比合法用户更快解锁钱包。

提高用户体验

与其降低安全性(见下文),更好的做法是让用户对等待感觉更好。ether加密和解密API允许开发人员合并一个进度条,方法是传递一个进度回调函数,该函数将定期调用一个0到1之间的数字表示完成百分比。

一般来说,进度条会让玩家感觉更快,也更舒服,因为它清楚地显示了还剩下多少(相对)时间。此外,在进度条中使用"解锁中……"这样的语言会让用户觉得时间没有被无谓地浪费。

其他方案 (不推荐)

有很多方法可以减少以太坊JSON钱包所需的解密时间,但请记住,这样做会放弃该钱包上几乎所有的安全性。

scrypt算法被设计成可调的。这样做的主要目的是考虑到随着时间的推移计算机处理速度的提高而增加难度,但也可以在安全性不那么重要的情况下降低难度。

// 钱包对象 const wallet = Wallet.createRandom(); // 需要解密的密码 const password = "password123"; // 警告: 这种做法会降低钱包安全性,极度不推荐 // 我们重写了 scrypt.N 默认的值,该值是表示破解钱包的难度。 const json = wallet.encrypt(password, { scrypt: { // 这个值必须是2的乘幂 (默认值: 131072) N: 64 } });

最佳实践

网络变化

处理网络中的变化(例如 Görli vs Mainnet)是非常复杂的,一个轻微的故障最多会让你的应用程序看起来很混乱,最坏的情况下会导致资金的损失、隐私数据的泄露或错误传导一个请求要所执行的内容。

幸运的是,正常用户应该永远不会改变他们的网络,除非他们被欺骗或者弄错了。

这是开发人员主要需要担心的问题,大多数开发人员应该理解在应用程序操作期间,会引起页面的重载(这已经是许多客户端的默认行为)和网络的变化,这会引起很多问题。

因此,发生网络变化时,最佳实践是简单地刷新页面。这应该会让你所有的UI组件重置到一个已知的安全状态,包括横幅组件(banners),如果他们在一个不受支持的网络,就可以警告提示你的用户。

这可以通过使用以下函数来完成:

网络变化时自动刷新
// 网络变化时强制页面刷新 { // "any" 参数表示允许任何自发的网络变化 const provider = new ethers.providers.Web3Provider(window.ethereum, "any"); provider.on("network", (newNetwork, oldNetwork) => { // 当 Provider 建立初始连接时,它会 emits 一个 "network" 事件,并且 oldNetwork 为空 // 一旦 oldNetwork 不为空,就表示网络发生了变化 if (oldNetwork) { window.location.reload(); } }); }

Provider API Keys

( 太长不看版: – 使用下面的链接注册来获得您自己的API密钥,以提高您的应用程序性能 )

当使用由API服务商(例如Alchemy,EtherscanINFURA)支持的Provider时, 他们都需要一个API密钥,从而能够跟踪每个项目及其使用和权限。

ethers.js 库 为上述的API服务商提供了默认的API密钥,因此每个Provider都可以轻松地连接————开箱即用。

这些API密钥是由后端服务作为社区资源提供的,用于低流量项目和早期原型开发。

因为这些API键是由所有用户共享的(没有获得自己的API密钥),所以它们被经常被使用,这意味着重新请求发生得更频繁,响应也更慢。

强烈建议你从下列的API服务商注册一个免费的API密钥,这有很多好处(可能有些差别,这取决于具体的API服务商):

Etherscan(以太坊区块浏览器)

Etherscan 是以太坊区块资源管理器,它可能是最有用的构建和调试以太坊应用程序的开发工具。

Etherscan提供了大量的 API endpoints 集合, 包含了所有能够用来与以太坊区块链交互的操作。

免费注册一个 API key

优点:

INFURA

INFURA服务已经存在相当一段时间了,鲁棒性强、可靠性高,强烈推荐。

它们提供了一个标准的JSON-RPC接口和一个WebSocket接口,这使得与标准工具的交互更加通用、简单和直接。

在 INFURA 免费注册一个 Project ID

优点:

Alchemy

Alchemy服务已经有几年的历史了,它也具备强鲁棒性和高可靠性。

它们提供了一个标准的JSON-RPC接口和一个WebSocket接口,以及一组用于与通证(tokens)交互和帮助调试的高级API。

在 Alchemy 免费注册一个 API key

优点:

Pocket Gateway

在 Pocket 免费注册一个 API key

Benefits:

创建默认的 Provider

默认的提供者连接到多个后端,并在内部验证它们的结果,这使得对第三方服务的高度信任变得很简单。

第二个可选参数允许为每个内部创建的Provider指定API keys,任何被遗漏的API keys将返回使用该服务的默认API keys。

极度推荐 您为每个服务提供一个API,让您的应用程序性能实现最大化。

将 API Keys 传到 getDefaultProvider
// 使用主网 const network = "homestead"; // 指定你的API keys // 每个都是可选的,如果省略它,则为默认值 // 该服务的API key将被使用 const provider = ethers.getDefaultProvider(network, { etherscan: YOUR_ETHERSCAN_API_KEY, infura: YOUR_INFURA_PROJECT_ID, // 或者使用项目私钥: // infura: { // projectId: YOUR_INFURA_PROJECT_ID, // projectSecret: YOUR_INFURA_PROJECT_SECRET, // }, alchemy: YOUR_ALCHEMY_API_KEY, pocket: YOUR_POCKET_APPLICATION_KEY // 或者使用应用私钥: // pocket: { // applicationId: , // applicationSecretKey: // } });

应用程序编程接口

应用程序编程接口(API)是库的正式规范。

Providers
Signers
合约交互
实用工具 Utilities
其他的库
试验性的库

Providers

提供者(Provider)是以太坊网络连接的抽象,为标准以太坊节点功能提供简洁、一致的接口。

ethers.js 库 提供了几种选项应该涵盖了绝大多数用例,但如果需要个性化配置,也包括了子类化所需的函数和类。

大多数用户应该使用 默认的 Provider.

默认的 Provider

默认的 provider 是在Ethereum上最简单、最安全的方法方式,而且它也具备足够强的鲁棒性,可以在生产环境中使用。

它创建一个连接到尽可能多的后端服务的FallbackProvider。当发出一个请求时,它会同时发送到多个后端。当从每个后端返回响应时,会检查它们是否同意。 一旦达到规定数量(即足够多的后端同意),你的应用程序就会得到相应。

它会确保如果后端不同步或者被破坏,那么将会放弃这个响应,选择接受而支持大多数认同的响应。

ethers.getDefaultProvider( [ network , [ options ] ] ) Provider

返回一个新的Provider,由多个服务支持连接到 网络。如果没有提供 网络,那么将会使用 homestead (例如mainnet) 。

网络 也可以作为 URL 来连接,比如 http://localhost:8545wss://example.com

options 参数是一个对象,有以下几个属性:

PropertyDescription 
alchemyAlchemy API Token 
etherscanEtherscan API Token 
infuraINFURA Project ID 或 { projectId, projectSecret } 
pocketPocket Network Application ID 或 { applicationId, applicationSecretKey } 
quorum必须满足规定的后端同意数量 (默认: 2 for mainnet, 1 for testnets) 
Option 对象的属性 
注意: API Keys

强烈建议在生产环境下,从每个服务商获取制定一个API Keys。

ethers提供的默认的API Keys是所有用户之间共享的,在大量服务加载的时候可能默认的API key服务会有所被限制,需要提前意识到这一点。

许多服务还具有监视和使用指标,这些指标只有在指定了API Key时才可用。这允许跟踪有多少请求正在被发送,以及哪些方法被使用得最多。

有些服务还提供额外的付费功能,这些功能只有在指定API Key时才可用。

网络

有几个官方的通用以太坊网络,以及自定义网络和其他兼容项目。

任何接收Networkish 的API都可以传递一个通用名称(如 "mainnet" or "ropsten") 或者 chain ID、自定义的参数来定义网络。

ethers.providers.getNetwork( aNetworkish ) Network

通过给定标准规范的aNetworkish Networkish,返回完整的 Network

这对于希望接受Networkish作为输入参数的函数和类非常有用。

// 通过链名 getNetwork("homestead"); // { // chainId: 1, // ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', // name: 'homestead' // } // 通过链ID getNetwork(1); // { // chainId: 1, // ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', // name: 'homestead' // }

自定义的 ENS 合约

这是因为通常需要在为Network 指定自定义属性来覆盖root ENS registry,或者是在一个通用的网络中拦截ENS 方法, 又或者是在一个开发环境的网络中(绝大多数开发环境中的网络中的ENS合约需要你手动部署)指定ENS registry。

例子: 重写 ENS registry
const network = { name: "dev", chainId: 1337, ensAddress: customEnsAddress };

Provider 文档

Provider
JsonRpcProvider
API Providers
其他的 Providers
Types

Provider

在ethers里面,Provider 是访问区块链数据的只读抽象。

使用过 Web3.js吗?

如果你用过Web3.js,以下是与ethers.js最大不同点。

ethers library 在 ProviderSigner可执行的操作之间创建了一个强大的划分,而Web3.js将二者结合在一起。

这种划分的关注点在于Provider严格的操作子集允许更多的后端、更一致的API,并确保其他库可以在不依赖任何潜在假设的情况下运行。

账户的方法

provider.getBalance( address [ , blockTag = latest ] ) Promise< 大数(BigNumber) >

返回给定的blockTag 区块高度下地址的余额。

await provider.getBalance("ricmoo.eth"); // { BigNumber: "36426320346873870455" }
provider.getCode( address [ , blockTag = latest ] ) Promise< string< DataHexString > >

返回给定的blockTag 区块高度下的合约源代码,如果当前没有合约被部署, 将返回 0x

await provider.getCode("registrar.firefly.eth"); // '0x606060405236156100885763ffffffff60e060020a60003504166369fe0e2d81146100fa578063704b6c021461010f57806379502c551461012d578063bed866f614610179578063c37067fa1461019e578063c66485b2146101ab578063d80528ae146101c9578063ddca3f43146101f7578063f2c298be14610219578063f3fef3a314610269575b6100f85b6000808052600760209081527f6d5257204ebe7d88fd91ae87941cb2dd9d8062b64ae5a2bd2d28ec40b9fbf6df80543490810190915560408051918252517fdb7750418f9fa390aaf85d881770065aa4adbe46343bcff4ae573754c829d9af929181900390910190a25b565b005b341561010257fe5b6100f860043561028a565b005b341561011757fe5b6100f8600160a060020a03600435166102ec565b005b341561013557fe5b61013d610558565b60408051600160a060020a0396871681526020810195909552928516848401526060840191909152909216608082015290519081900360a00190f35b341561018157fe5b61018c600435610580565b60408051918252519081900360200190f35b6100f8600435610595565b005b34156101b357fe5b6100f8600160a060020a03600435166105e6565b005b34156101d157fe5b6101d9610676565b60408051938452602084019290925282820152519081900360600190f35b34156101ff57fe5b61018c61068d565b60408051918252519081900360200190f35b6100f8600480803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284375094965061069495505050505050565b005b341561027157fe5b6100f8600160a060020a0360043516602435610ab2565b005b60025433600160a060020a039081169116146102a65760006000fd5b600454604080519182526020820183905280517f854231545a00e13c316c82155f2b8610d638e9ff6ebc4930676f84a5af08a49a9281900390910190a160048190555b50565b60025433600160a060020a039081169116146103085760006000fd5b60025460408051600160a060020a039283168152918316602083015280517fbadc9a52979e89f78b7c58309537410c5e51d0f63a0a455efe8d61d2b474e6989281900390910190a16002805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a038381169190911790915560008054604080516020908101849052815160e060020a6302571be30281527f91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e26004820152915192909416936302571be39360248084019492938390030190829087803b15156103e957fe5b60325a03f115156103f657fe5b50505060405180519050600160a060020a0316631e83409a826000604051602001526040518263ffffffff1660e060020a0281526004018082600160a060020a0316600160a060020a03168152602001915050602060405180830381600087803b151561045f57fe5b60325a03f1151561046c57fe5b50506040805160008054600354602093840183905284517f0178b8bf00000000000000000000000000000000000000000000000000000000815260048101919091529351600160a060020a039091169450630178b8bf9360248082019493918390030190829087803b15156104dd57fe5b60325a03f115156104ea57fe5b505060408051805160035460025460e860020a62d5fa2b0284526004840191909152600160a060020a03908116602484015292519216925063d5fa2b0091604480830192600092919082900301818387803b151561054457fe5b60325a03f1151561055157fe5b5050505b50565b600054600354600254600454600154600160a060020a039485169492831692165b9091929394565b6000818152600760205260409020545b919050565b6000818152600760209081526040918290208054349081019091558251908152915183927fdb7750418f9fa390aaf85d881770065aa4adbe46343bcff4ae573754c829d9af92908290030190a25b50565b60025433600160a060020a039081169116146106025760006000fd5b60015460408051600160a060020a039283168152918316602083015280517f279875333405c968e401e3bc4e71d5f8e48728c90f4e8180ce28f74efb5669209281900390910190a16001805473ffffffffffffffffffffffffffffffffffffffff1916600160a060020a0383161790555b50565b600654600554600160a060020a033016315b909192565b6004545b90565b80516001820190600080808060048510806106af5750601485115b156106ba5760006000fd5b600093505b8484101561072a57855160ff16925060618310806106e05750607a8360ff16115b80156106fc575060308360ff1610806106fc575060398360ff16115b5b801561070d57508260ff16602d14155b156107185760006000fd5b6001909501945b6001909301926106bf565b60045434101561073a5760006000fd5b866040518082805190602001908083835b6020831061076a5780518252601f19909201916020918201910161074b565b51815160209384036101000a60001901801990921691161790526040805192909401829003822060035483528282018190528451928390038501832060008054948401819052865160e060020a6302571be3028152600481018390529651929a509098509650600160a060020a0390921694506302571be393602480820194509192919082900301818787803b15156107ff57fe5b60325a03f1151561080c57fe5b505060405151600160a060020a031691909114905061082b5760006000fd5b60008054600354604080517f06ab5923000000000000000000000000000000000000000000000000000000008152600481019290925260248201869052600160a060020a03308116604484015290519216926306ab59239260648084019382900301818387803b151561089a57fe5b60325a03f115156108a757fe5b505060008054600154604080517f1896f70a00000000000000000000000000000000000000000000000000000000815260048101879052600160a060020a0392831660248201529051919092169350631896f70a9260448084019391929182900301818387803b151561091657fe5b60325a03f1151561092357fe5b50506001546040805160e860020a62d5fa2b02815260048101859052600160a060020a033381166024830152915191909216925063d5fa2b009160448082019260009290919082900301818387803b151561097a57fe5b60325a03f1151561098757fe5b505060008054604080517f5b0fc9c300000000000000000000000000000000000000000000000000000000815260048101869052600160a060020a0333811660248301529151919092169350635b0fc9c39260448084019391929182900301818387803b15156109f357fe5b60325a03f11515610a0057fe5b505060058054349081019091556006805460010190556000838152600760209081526040918290208054840190558151600160a060020a03331681529081019290925280518493507f179ef3319e6587f6efd3157b34c8b357141528074bcb03f9903589876168fa149281900390910190a260408051348152905182917fdb7750418f9fa390aaf85d881770065aa4adbe46343bcff4ae573754c829d9af919081900360200190a25b50505050505050565b60025433600160a060020a03908116911614610ace5760006000fd5b604051600160a060020a0383169082156108fc029083906000818181858888f193505050501515610aff5760006000fd5b60408051600160a060020a03841681526020810183905281517fac375770417e1cb46c89436efcf586a74d0298fee9838f66a38d40c65959ffda929181900390910190a15b50505600a165627a7a723058205c3628c01dc80233f51979d91a76cec2a25d84e86c9838d34672734ca2232b640029'
provider.getStorageAt( addr , pos [ , blockTag = latest ] ) Promise< string< DataHexString > >

在当前的blockTag下,输入address参数addr和position参数pos,返回类型为Bytes32的值。

await provider.getStorageAt("registrar.firefly.eth", 0) // '0x000000000000000000000000314159265dd8dbb310642f98f50c066173c1259b'
provider.getTransactionCount( address [ , blockTag = latest ] ) Promise< number >

在当前的blockTag下,返回已经发送的交易的数量,这个值是用来提供给发送到网络的下一个交易的nonce值。

await provider.getTransactionCount("ricmoo.eth"); // 47

区块的方法

provider.getBlock( block ) Promise< Block >

从网络中得到区块result.transactions 是一串交易集合的哈希值。

await provider.getBlock(100004) // { // _difficulty: { BigNumber: "3849295379889" }, // difficulty: 3849295379889, // extraData: '0x476574682f76312e302e312d39383130306634372f6c696e75782f676f312e34', // gasLimit: { BigNumber: "3141592" }, // gasUsed: { BigNumber: "21000" }, // hash: '0xf93283571ae16dcecbe1816adc126954a739350cd1523a1559eabeae155fbb63', // miner: '0x909755D480A27911cB7EeeB5edB918fae50883c0', // nonce: '0x1a455280001cc3f8', // number: 100004, // parentHash: '0x73d88d376f6b4d232d70dc950d9515fad3b5aa241937e362fdbfd74d1c901781', // timestamp: 1439799168, // transactions: [ // '0x6f12399cc2cb42bed5b267899b08a847552e8c42a64f5eb128c1bcbd1974fb0c' // ] // }
provider.getBlockWithTransactions( block ) Promise< BlockWithTransactions >

从网络中得到区块result.transactions 是 一个TransactionResponse 对象的数组集合。

await provider.getBlockWithTransactions(100004) // { // _difficulty: { BigNumber: "3849295379889" }, // difficulty: 3849295379889, // extraData: '0x476574682f76312e302e312d39383130306634372f6c696e75782f676f312e34', // gasLimit: { BigNumber: "3141592" }, // gasUsed: { BigNumber: "21000" }, // hash: '0xf93283571ae16dcecbe1816adc126954a739350cd1523a1559eabeae155fbb63', // miner: '0x909755D480A27911cB7EeeB5edB918fae50883c0', // nonce: '0x1a455280001cc3f8', // number: 100004, // parentHash: '0x73d88d376f6b4d232d70dc950d9515fad3b5aa241937e362fdbfd74d1c901781', // timestamp: 1439799168, // transactions: [ // { // accessList: null, // blockHash: '0xf93283571ae16dcecbe1816adc126954a739350cd1523a1559eabeae155fbb63', // blockNumber: 100004, // chainId: 0, // confirmations: 16283837, // creates: null, // data: '0x', // from: '0xcf00A85f3826941e7A25BFcF9Aac575d40410852', // gasLimit: { BigNumber: "90000" }, // gasPrice: { BigNumber: "54588778004" }, // hash: '0x6f12399cc2cb42bed5b267899b08a847552e8c42a64f5eb128c1bcbd1974fb0c', // nonce: 25, // r: '0xb23adc880d3735e4389698dddc953fb02f1fa9b57e84d3510a2a4b3597ac2486', // s: '0x4e856f95c4e2828933246fb4765a5bfd2ca5959840643bef0e80b4e3a243d064', // to: '0xD9666150A9dA92d9108198a4072970805a8B3428', // transactionIndex: 0, // type: 0, // v: 27, // value: { BigNumber: "5000000000000000000" }, // wait: [Function] // } // ] // }

以太坊域名服务 (ENS) 方法

以太坊域名服务 (ENS) 允许一个简短且易于记忆的ENS名称附加到任何一组键和值。

最常见的用法之一是使用一个简单的命名来引用以太坊地址

在ethers的 API中,几乎任何接受地址的地方都可以使用ENS命名,这可以简化代码,使读取和调试更加简单。

provider 提供了一些基本操作,以帮助解析和处理ENS命名。

provider.getResolver( name ) Promise< EnsResolver >

返回一个EnsResolver实例,该实例可用于进一步查询ENS命名的特定实体。

// 有关使用此对象的示例,请参见下面(Resolver) const resolver = await provider.getResolver("ricmoo.eth");
provider.lookupAddress( address ) Promise< string >

使用反向注册器对ENS中的地址进行反向查找。如果名称不存在,或者正向查找不匹配,则返回null

ENS名称需要额外的配置来设置反向记录,它们不会自动设置。

await provider.lookupAddress("0x5555763613a12D8F3e73be831DFf8598089d3dCa"); // 'ricmoo.eth'
provider.resolveName( name ) Promise< string< Address > >

查找一个名称的地址。如果这个名称没有被拥有,或者没有配置一个解析器,或者解析器没有配置一个地址,则返回null

await provider.resolveName("ricmoo.eth"); // '0x5555763613a12D8F3e73be831DFf8598089d3dCa'

EnsResolver

resolver.name string

Resolver 的名称

resolver.address string< 地址(Address) >

Resolve 的地址

resolver.getAddress( [ cointType = 60 ] ) Promise< string >

返回一个 解析的类型为EIP-2304 多币地址的 Promise。 默认情况下,返回一个以太坊的地址(Address)(coinType = 60)。

// 默认情况下,查找以太坊地址 // (这个会 match provider.resolveName) await resolver.getAddress(); // '0x5555763613a12D8F3e73be831DFf8598089d3dCa' // 为其他coin地址指定coinType (0 = Bitcoin) await resolver.getAddress(0); // '1RicMooMWxqKczuRCa5D2dnJaUEn9ZJyn'
resolver.getContentHash( ) Promise< string >

返回一个Promise,解析为任何存储的EIP-1577内容哈希。 EIP-1577

await resolver.getContentHash(); // 'ipfs://QmdTPkMMBWQvL8t7yXogo7jq5pAcWg8J7RkLrDsWZHT82y'
resolver.getText( key ) Promise< string >

返回一个Promise,解析为任何存储的EIP-634作为key的文本实体。

await resolver.getText("email"); // 'me@ricmoo.com' await resolver.getText("url"); // 'https://www.ricmoo.com/' await resolver.getText("com.twitter"); // '@ricmoo'

Logs 方法

provider.getLogs( filter ) Promise< Array< Log > >

返回匹配筛选器的 Log数组。

请记住,许多后端会丢弃旧的事件,并且请求范围太广可能也会被丢弃,因为它们需要太多的资源来执行查询。

Network Status Methods

provider.getNetwork( ) Promise< Network >

返回这个 Provider 所连接的 Network

await provider.getNetwork() // { // chainId: 1, // ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', // name: 'homestead' // }
provider.getBlockNumber( ) Promise< number >

返回最近挖出的区块的序号(或高度)。

await provider.getBlockNumber() // 16383842
provider.getGasPrice( ) Promise< 大数(BigNumber) >

返回一个 关于这个交易中的 gas price最准预测

// 燃料价格 (单位: wei)... gasPrice = await provider.getGasPrice() // { BigNumber: "16674318809" } // 通常来说燃料的价格用 gwei 会更好理解 utils.formatUnits(gasPrice, "gwei") // '16.674318809'
provider.getFeeData( ) Promise< FeeData >

返回在一笔交易中当前的recommended FeeData

对于 EIP-1559 的交易, 应该使用 maxFeePerGasmaxPriorityFeePerGas

对于不支持 EIP-1559 中被遗留的交易和网络,应该使用 gasPrice

// 燃料价格 (单位: wei)... feeData = await provider.getFeeData() // { // gasPrice: { BigNumber: "16674318809" }, // lastBaseFeePerGas: { BigNumber: "16492712329" }, // maxFeePerGas: { BigNumber: "34485424658" }, // maxPriorityFeePerGas: { BigNumber: "1500000000" } // } // 通常来说燃料的价格用 gwei 会更好理解 utils.formatUnits(feeData.maxFeePerGas, "gwei") // '34.485424658'
provider.ready Promise< Network >

返回一个 Promise ,直到网络建立就失效,,忽略由于目标节点还未激活而出现的错误。 这可以用于测试或附上脚本,以等待节点启动并顺利运行。

Transactions 方法

provider.call( transaction [ , blockTag = latest ] ) Promise< string< DataHexString > >

使用call返回执行交易的结果。调用不需要任何的以太,但不能改变任何状态。这在合约上调用getter方法是非常有用的。

await provider.call({ // ENS public resovler address to: "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41", // `function addr(namehash("ricmoo.eth")) view returns (address)` data: "0x3b3b57debf074faa138b72c65adbdcfb329847e4f2c04bde7f7dd7fcad5a52d2f395a558" }); // '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca'
provider.estimateGas( transaction ) Promise< 大数(BigNumber) >

返回向网络提交交易所需的预估gas值。

估计的gas值可能不准确,因为网络上可能有另一个交易没有被计算在内,但在被挖出来之后就会影响相关状态。

await provider.estimateGas({ // Wrapped ETH address to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // `function deposit() payable` data: "0xd0e30db0", // 1 ether value: parseEther("1.0") }); // { BigNumber: "27938" }
provider.getTransaction( hash ) Promise< TransactionResponse >

返回带有哈希值的交易,如果交易未知,则返回null。

如果一个交易还没有被挖出,这个方法将搜索交易池。各种后端可能有更多的限制交易池访问(例如,燃料价格太低或交易最近才发送,还没有索引),在这种情况下,这个方法也可能返回null。

await provider.getTransaction("0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0"); // { // accessList: null, // blockHash: '0x8a179bc6cb299f936c4fd614995e62d597ec6108b579c23034fb220967ceaa94', // blockNumber: 12598244, // chainId: 1, // confirmations: 3785600, // creates: '0x733aF852514e910E2f8af40d61E00530377889E9', // data: '0x608060405234801561001057600080fd5b5060405161062438038061062483398101604081905261002f916100cd565b60405163c47f002760e01b815260206004820152600d60248201526c0daead8e8d2c6c2d8d85ccae8d609b1b60448201526001600160a01b0382169063c47f002790606401602060405180830381600087803b15801561008e57600080fd5b505af11580156100a2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100c691906100fb565b5050610113565b6000602082840312156100de578081fd5b81516001600160a01b03811681146100f4578182fd5b9392505050565b60006020828403121561010c578081fd5b5051919050565b610502806101226000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80634c0770b914610030575b600080fd5b61004361003e366004610309565b61005b565b60405161005293929190610389565b60405180910390f35b600060608085841461006c57600080fd5b8567ffffffffffffffff81111561009357634e487b7160e01b600052604160045260246000fd5b6040519080825280602002602001820160405280156100bc578160200160208202803683370190505b5091508567ffffffffffffffff8111156100e657634e487b7160e01b600052604160045260246000fd5b60405190808252806020026020018201604052801561011957816020015b60608152602001906001900390816101045790505b50905060005b86811015610235576101cd8a8a8a8a8581811061014c57634e487b7160e01b600052603260045260246000fd5b905060200201602081019061016191906102db565b89898681811061018157634e487b7160e01b600052603260045260246000fd5b90506020028101906101939190610460565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061024592505050565b8483815181106101ed57634e487b7160e01b600052603260045260246000fd5b6020026020010184848151811061021457634e487b7160e01b600052603260045260246000fd5b6020908102919091010191909152528061022d816104a5565b91505061011f565b5043925096509650969350505050565b6000606060405190506000815260208101604052600080845160208601878afa9150843d101561028857603f3d01601f191681016040523d81523d6000602083013e5b94509492505050565b60008083601f8401126102a2578182fd5b50813567ffffffffffffffff8111156102b9578182fd5b6020830191508360208260051b85010111156102d457600080fd5b9250929050565b6000602082840312156102ec578081fd5b81356001600160a01b0381168114610302578182fd5b9392505050565b60008060008060008060808789031215610321578182fd5b8635955060208701359450604087013567ffffffffffffffff80821115610346578384fd5b6103528a838b01610291565b9096509450606089013591508082111561036a578384fd5b5061037789828a01610291565b979a9699509497509295939492505050565b60006060820185835260206060818501528186518084526080860191508288019350845b818110156103c9578451835293830193918301916001016103ad565b5050848103604086015285518082528282019350600581901b82018301838801865b8381101561045057601f1980868503018852825180518086528a5b81811015610421578281018a01518782018b01528901610406565b81811115610431578b8a83890101525b5098880198601f019091169390930186019250908501906001016103eb565b50909a9950505050505050505050565b6000808335601e19843603018112610476578283fd5b83018035915067ffffffffffffffff821115610490578283fd5b6020019150368190038213156102d457600080fd5b60006000198214156104c557634e487b7160e01b81526011600452602481fd5b506001019056fea264697066735822122083b5dc25b3c9256aa4244eddaf9e4b5fccd09a45ec4e0174f2c900de7144602d64736f6c63430008040033000000000000000000000000084b1c3c81545d370f3634392de611caabff8148', // from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // gasLimit: { BigNumber: "443560" }, // gasPrice: { BigNumber: "10100000000" }, // hash: '0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0', // nonce: 745, // r: '0xaf2b969de6dfb234fb8843f47a029636abb1ef52f26bb8bb615bbabcf23808e9', // s: '0x3dd61cd8df015e0af5689a249dd3224ee71f2b04917b7b4c14f7e68bb3a4ec17', // to: null, // transactionIndex: 315, // type: 0, // v: 38, // value: { BigNumber: "0" }, // wait: [Function] // }
provider.getTransactionReceipt( hash ) Promise< TransactionReceipt >

返回交易收据的哈希值,如果交易还没有被挖出则返回null。

如果需要等待交易被挖出,请考虑下面的waitForTransaction方法。

await provider.getTransactionReceipt("0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0"); // { // blockHash: '0x8a179bc6cb299f936c4fd614995e62d597ec6108b579c23034fb220967ceaa94', // blockNumber: 12598244, // byzantium: true, // confirmations: 3785600, // contractAddress: '0x733aF852514e910E2f8af40d61E00530377889E9', // cumulativeGasUsed: { BigNumber: "12102324" }, // effectiveGasPrice: { BigNumber: "10100000000" }, // from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // gasUsed: { BigNumber: "443560" }, // logs: [ // { // address: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', // blockHash: '0x8a179bc6cb299f936c4fd614995e62d597ec6108b579c23034fb220967ceaa94', // blockNumber: 12598244, // data: '0x000000000000000000000000084b1c3c81545d370f3634392de611caabff8148', // logIndex: 160, // topics: [ // '0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82', // '0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2', // '0x4774f6d3b3d08b5ec00115f0e2fddb92604b39e52b0dc908c6f8fcb7aa5d2a9a' // ], // transactionHash: '0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0', // transactionIndex: 315 // }, // { // address: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', // blockHash: '0x8a179bc6cb299f936c4fd614995e62d597ec6108b579c23034fb220967ceaa94', // blockNumber: 12598244, // data: '0x000000000000000000000000a2c122be93b0074270ebee7f6b7292c7deb45047', // logIndex: 161, // topics: [ // '0x335721b01866dc23fbee8b6b2c7b1e14d6f05c28cd35a2c934239f94095602a0', // '0x790fdce97f7b2b1c4c5a709fb6a49bf878feffcaa85ed0245f6dff09abcefda7' // ], // transactionHash: '0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0', // transactionIndex: 315 // } // ], // logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000004000000000000010000000000000020000000000000000000040000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000040000000000000000100004000000000000008000040000000000000000000000000000000000005000000000041000000000000000000000000000000000000000000000000100000000000000001000000000000000000000', // status: 1, // to: null, // transactionHash: '0x5b73e239c55d790e3c9c3bbb84092652db01bb8dbf49ccc9e4a318470419d9a0', // transactionIndex: 315, // type: 0 // }
provider.sendTransaction( transaction ) Promise< TransactionResponse >

向在挖区块的网络提交交易交易 必须 经过签名,并且需要合法(例如,nonce值需要正确并且账户要有足够的余额来进行该笔交易的支付)。

const signedTx = "0x02f874827a69048459682f00845e558eb4825209945555763613a12d8f3e73be831dff8598089d3dca882b992b75cbeb600080c080a0c8a8f1bed41823367b739625e724e8ff89bd0caff38e5747cf76384c3ae3e26da03893b6aaefa8337cd67fbd4ceaf4aec44dfc928d78c040118a6cc8f9ea94367e"; await provider.sendTransaction(signedTx); // { // accessList: [], // chainId: 31337, // confirmations: 0, // data: '0x', // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // gasLimit: { BigNumber: "21001" }, // gasPrice: null, // hash: '0xabee48ad39a96ee6e018afd801d34b23eae384cbdc7304b5089f2ebc0c126b18', // maxFeePerGas: { BigNumber: "1582665396" }, // maxPriorityFeePerGas: { BigNumber: "1500000000" }, // nonce: 4, // r: '0xc8a8f1bed41823367b739625e724e8ff89bd0caff38e5747cf76384c3ae3e26d', // s: '0x3893b6aaefa8337cd67fbd4ceaf4aec44dfc928d78c040118a6cc8f9ea94367e', // to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa', // type: 2, // v: 0, // value: { BigNumber: "3141590000000000000" }, // wait: [Function] // }
provider.waitForTransaction( hash [ , confirms = 1 [ , timeout ] ] ) Promise< TxReceipt >

返回一个Promise,直到transactionHash被挖出来,才能够被解析。

如果confirms参数为0,这个方法是非阻塞的,如果交易还没有被挖出,则返回null。 否则,该方法是阻塞的,直到这个包含该交易的、被confirms标识的区块被挖出。

Event Emitter 方法

EventEmitter API允许应用程序使用Obeserver Pattern来注册各种事件发生时的回调函数。

这与其他JavaScript库提供的Event Emitter密切相关,除开那些事件名称支持一些更复杂的对象,而不仅仅是字符串。对象在内部是标准化的。

provider.on( eventName , listener ) this

为每一个参数为eventName事件添加监听器

provider.once( eventName , listener ) this

为参数为eventName事件添加监听器,监听使用过后将会被移除。

provider.emit( eventName , ...args ) boolean

通知所有的eventName event监听器,并把参数传递给它们。这通常只在内部使用。

provider.off( eventName [ , listener ] ) this

移除一个参数为eventName事件监听器,如果没有提供listener参数,则移除所有关于eventName的监听器。

provider.removeAllListeners( [ eventName ] ) this

移除所有参数为eventName事件监听器,如果没有提供eventName参数,则移除所有事件。

provider.listenerCount( [ eventName ] ) number

返回所有参数为eventName事件的监听器数量。如果没有提供eventName参数,返回所有监听器的数量。

provider.listeners( eventName ) Array< Listener >

返回参数为eventName事件监听器的list集合。

事件

以下任何一个方法都可以作为上述方法中的eventName参数。

Log Filter

一个过滤器是一个对象,表示合约log Filter它具有可选的address (合约地址) 和 topics (一个要匹配的主题集)。

如果参数address未被指定,则过滤器匹配任何合约地址。

有关于过滤事件的更多信息,请参阅 EventFilters

Topic-Set Filter

主题集过滤器的值是一个主题集数组。

此事件与日志过滤器相同,但参数address可以不用填(匹配任何合约)。

有关于过滤事件的更多信息,请参阅 EventFilters

Transaction Filter

交易过滤器的值是交易的哈希值。

这个事件会在任何给定的交易挖出的链上区块中触发。 使用once方法比使用on方法要普遍地多。

除了交易和过滤事件外,还有几个命名事件。

Event NameArgumentsDescription 
"block"blockNumber当一个区块被挖出时触发 
"error"error只要有错误就触发 
"pending"pendingTransaction当一个新交易进入内存池时触发;只有特定的providers提供此事件,从而在运行在自己的节点上获得可靠的数据 
"willPoll"pollId在一个polling loop开始之前触发;(大多数开发者很少使用) 
"poll"pollId, blockNumber在每个poll cycle中,`blockNumber`更新之后(如果改变了),以及与在poll loop中任何其他的事件(如果有)之前触发; (大多数开发者很少使用) 
"didPoll"pollId在polling loop中的所有事件被触发后触发;(大多数开发者很少使用) 
"debug"provider dependent每个Provider可以使用它来发出有用的调试信息,格式由开发者决定;(大多数开发者很少使用) (very rarely used by most developers) 
Named Provider Events 
事件例子
provider.on("block", (blockNumber) => { // 只有有一个区块改变就会触发 }) provider.once(txHash, (transaction) => { // 当含有交易的区块被挖出就会触发 }) // 这个过滤器也可以通过Contract或接口API生成。 // 如果不指定address,则默认匹配任何地址, // 如果不指定topics,则默认匹配任何日志。 filter = { address: "dai.tokens.ethers.eth", topics: [ utils.id("Transfer(address,address,uint256)") ] } provider.on(filter, (log, event) => { // 当 DAI token 发生转账时触发 }) // 注意,这是一个主题集数组,与使用没有address参数的过滤器相同(即匹配任何地址) topicSets = [ utils.id("Transfer(address,address,uint256)"), null, [ hexZeroPad(myAddress, 32), hexZeroPad(myOtherAddress, 32) ] ] provider.on(topicSets, (log, event) => { // 当任何一个token 发送到其他地址时触发 }) provider.on("pending", (tx) => { // 当有一笔pending交易被捕获时触发 }); provider.on("error", (tx) => { // 只要有错误就会触发 });

校验方法

Provider.isProvider( object ) boolean

当且仅当参数object是Provider返回 true 。

JsonRpcProvider inherits Provider

JSON-RPC API是与以太坊交互比较流行的方法,在主要的以太坊节点实现(如GethParity)以及许多第三方web服务(如INFURA)中都可以用。

new ethers.providers.JsonRpcProvider( [ urlOrConnectionInfo [networkish ] ] )

使用 URL 或者 ConnectionInfo urlOrConnectionInfo 作为一个一个 JSON-RPC HTTP API 连接到 networkish 网络。

如果 urlOrConnectionInfo 没有被指定, 默认值是http://localhost:8545。 如果网络没有被指定, 它将会自动使用 eth_chaindId查询网络节点并返回 eth_networkId从而确定网络。

注意: 连接到本地节点

每个节点的实现略有不同,可能需要特定的命令行标志、配置或UI设置,以启用JSON-RPC、解锁帐户或公开特定的api。请参考他们的文档。

jsonRpcProvider.getSigner( [ addressOrIndex ] ) JsonRpcSigner

返回一个由该以太坊节点管理的JsonRpcSigner,地址为addressOrIndex。如果没有提供addressOrIndex,则使用第一个帐户(account #0)。

jsonRpcProvider.getUncheckedSigner( [ addressOrIndex ] ) JsonRpcUncheckedSigner
jsonRpcProvider.listAccounts( ) Promise< Array< string > >

返回此provider管理的所有帐户地址的列表。

jsonRpcProvider.send( methodparams ) Promise< any >

允许向provider发送原始消息。

这可以用于后端特定的调用,比如调试或特定的帐户管理。

JsonRpcSigner inherits Signer

JsonRpcSigner是一个简单的Signer,它由一个连接的JsonRpcProvider支持。

signer.provider JsonRpcProvider

创建这个signer的provider

signer.connectUnchecked( ) JsonRpcUncheckedSigner

返回一个新的Signer对象,该对象在发送交易时不执行额外的检查。详情请参阅getUncheckedSigner

signer.sendUncheckedTransaction( transaction ) Promise< string< DataHexString< 32 > > >

发送交易并返回一个解析为不透明交易哈希的Promise。

signer.unlock( password ) Promise< boolean >

使用密码请求节点解除锁定(如果被锁了)。

JsonRpcUncheckedSigner inherits Signer

JSON-RPC API仅在发送交易时提供交易哈希作为响应,但ether Provider要求在返回交易之前填充交易的所有细节。 例如,燃料价格(gas price)和燃料限制(gas limit)可能由节点或自动包含在内的nonce调整,在这种情况下,不透明的交易哈希已经丢弃了这一点。

为了解决这个问题,JsonRpcSigner立即查询provider的详细信息,用返回的交易哈希来填充TransactionResponse对象。

一些后端不立即响应,直到区块被挖出,才发布它负责签署的交易的细节。

UncheckedSigner不填充任何附加信息,并将一个模拟的类似TransactionResponse的对象作为立即返回的结果, 其中大部分属性设置为null,但如果只需要这些,则可以快速获得交易哈希。

StaticJsonRpcProvider inherits JsonRpcProvider

ethers Provider将频繁执行getNetwork调用,以确保网络调用和网络通信是一致的。

在像MetaMask这样的客户端情况下,这是需要的,因为用户可以随时改变网络,在这种情况下,检查chainId的成本是本地的,因此很便宜。

在像MetaMask这样的客户端情况下是非常有必要的,因为用户可能随时改变网络, 在这种情况下,检查chainId的成本是在本地的,因此很便宜。

众所周知,在某些情况下网络不能改变,如当连接到一个INFURA端点,在这种情况下,可以使用SStaticJsonRpcProvider来将一直缓存链ID,可以减少网络流量和往返查询chain ID的次数。

这个Provider 应在网络不能更改时使用。

节点特定的方法

许多方法是不常见的或特定于某些以太坊节点(例如ParityGeth)才有的。这些方法包括帐户和管理员管理、调试、更深层次的区块和交易探索以及其他服务(如Swarm和Whisper)。

可以使用jsonRpcProvider.send 来获得这些方法。

API Providers

有很多服务都提供了访问以太坊区块链的web API。这些提供商允许连接到它们,因为你不需要运行你自己的实例或以太坊节点集群,这就简化了开发。

然而,这种对第三方服务的依赖会降低弹性和安全性,并增加所需的信任度。为了缓解这些问题,建议您使用 Default Provider

EtherscanProvider inherits Provider

EtherscanProvider是由各种Etherscan APIs的组合所支持。

new ethers.providers.EtherscanProvider( [ network = "homestead" , [ apiKey ] ] )

使用可选的apiKey创建一个新的EtherscanProvider连接到网络

这个网络可以被指定为一个字符串类型的网络名称、或number类型的链ID,或[网络对象]provider-(network)。

如果没有提供apiKey,将使用一个共享的API key,这可能会导致性能降低和请求受限。 强烈推荐在生产环境中使用您在Etherscan上注册的API密钥。

注意: 默认的 API keys

如果没有提供apiKey,将使用一个共享的API key,这可能会导致性能降低和请求受限。

强烈推荐在生产环境中使用您在Etherscan上注册的API密钥。

支持的网络

  • homestead - Homestead (Mainnet)
  • goerli - Görli (clique testnet)
  • sepolia - Sepolia (proof-of-authority testnet)
  • arbitrum - Arbitrum Optimistic L2
  • arbitrum-goerli - Arbitrum Optimistic L2 testnet
  • matic - Polgon mainnet
  • maticmum - Polgon testnet
  • optimism - Optimism Optimistic L2
  • optimism-goerli - Optimism Optimistic L2 testnet

Etherscan 例子
// 连接主网 (homestead) provider = new EtherscanProvider(); // 连接 rinkeby 测试网(以下这两种方式是等价的) provider = new EtherscanProvider("goerli"); provider = new EtherscanProvider(5); network = ethers.providers.getNetwork("goerli"); // { // chainId: 5, // ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e', // name: 'goerli' // } provider = new EtherscanProvider(network); // 通过API key 连接主网(homestead) provider = new EtherscanProvider(null, apiKey); provider = new EtherscanProvider("homestead", apiKey);
provider.getHistory( address ) Array< History >

@TODO... Explain

InfuraProvider inherits UrlJsonRpcProvider

InfuraProvider 被流行的INFURA以太坊服务所支持。

new ethers.providers.InfuraProvider( [ network = "homestead" , [ apiKey ] ] )

使用可选的apiKey创建一个新的InfuraProvider连接到网络

这个网络可以被指定为一个字符串类型的网络名称、或number类型的链ID,或[网络对象]provider-(network)。

apiKey可以是一个string类型的Project ID,也可以是一个带有projectIdprojectSecret属性对象, 用于指定一个可以在非公共源(如服务器)上使用的Project Secret, 以进一步保护你的API access 和 quotas。

InfuraProvider.getWebSocketProvider( [ network [ , apiKey ] ] ) WebSocketProvider

Create a new WebSocketProvider 使用 INFURA web-socket 端点创建一个新的WebSocketProvider,并使用可选的apiKey连接到网络。 网络和apiKey的设定与构造函数相同。

网络apiKey 的设定与构造函数相同。

注意: 默认的 API keys

如果没有提供apiKey,将使用一个共享的API key,这可能会导致性能降低和请求受限。

强烈推荐在生产环境中使用您通过INFURA注册的API密钥。

支持的网络

  • homestead - Homestead (Mainnet)
  • goerli - Görli (clique testnet)
  • sepolia - Sepolia (proof-of-authority testnet)
  • arbitrum - Arbitrum Optimistic L2
  • arbitrum-goerli - Arbitrum Optimistic L2 testnet
  • matic - Polgon mainnet
  • maticmum - Polgon testnet
  • optimism - Optimism Optimistic L2
  • optimism-goerli - Optimism Optimistic L2 testnet

INFURA 例子
// 连接到主网 (homestead) provider = new InfuraProvider(); // 连接到 goerli 测试网 // (see EtherscanProvider above for other network examples) provider = new InfuraProvider("goerli"); // 使用一个Project ID连接到主网(以下这两种方式是等价的) provider = new InfuraProvider(null, projectId); provider = new InfuraProvider("homestead", projectId); // 使用Project ID 和 Project Secret 连接到主网 provider = new InfuraProvider("homestead", { projectId: projectId, projectSecret: projectSecret }); // 使用一个 WebSocketProvider 连接到 INFURA WebSocket 端点 provider = InfuraProvider.getWebSocketProvider()

AlchemyProvider inherits UrlJsonRpcProvider

AlchemyProvider 是被Alchemy所支持的。

new ethers.providers.AlchemyProvider( [ network = "homestead" , [ apiKey ] ] )

通过可选的apiKey创建一个新的AlchemyProvider 连接到网络

这个网络可以被指定为一个字符串类型的网络名称、或number类型的链ID,或[网络对象]provider-(network)。

注意: 默认的 API keys

如果没有提供apiKey,将使用一个共享的API key,这可能会导致性能降低和请求受限。

强烈推荐在生产环境中使用您通过Alchemy 注册的API密钥。

支持的网络

  • homestead - Homestead (Mainnet)
  • goerli - Görli (clique testnet)
  • sepolia - Sepolia (proof-of-authority testnet)
  • arbitrum - Arbitrum Optimistic L2
  • arbitrum-goerli - Arbitrum Optimistic L2 testnet
  • matic - Polgon mainnet
  • maticmum - Polgon testnet
  • optimism - Optimism Optimistic L2
  • optimism-goerli - Optimism Optimistic L2 testnet

Alchemy Examples
// 连接到主网(homestead) provider = new AlchemyProvider(); // 连接到 goerli 测试网 // (see EtherscanProvider above for other network examples) provider = new AlchemyProvider("goerli"); // 使用 apiKey 连接到主网(以下这两种方式是等价的) provider = new AlchemyProvider(null, apiKey); provider = new AlchemyProvider("homestead", apiKey); // 使用一个 WebSocketProvider 连接到 Alchemy WebSocket 端点 provider = AlchemyProvider.getWebSocketProvider()

CloudflareProvider inherits UrlJsonRpcProvider

CloudflareProvider 是被 Cloudflare Ethereum Gateway所支持的。

new ethers.providers.CloudflareProvider( )

创建一个新的CloudflareProvider 连接到主网(homestead)。

支持的网络

  • Homestead (主网)

Cloudflare 例子
// 连接到主网(homestead) provider = new CloudflareProvider();

其他的 Providers

FallbackProvider inherits Provider

FallbackProvider 是ethers中可用的且最高级的Provider

它使用一个quorum并连接到多个Providers作为后端,每个Providers都配置了一个优先级权重

当发出一个请求时,请求被分配给多个随机选择的后端(优先级较低的后端总是被先选中), 并将每个请求的结果与其他请求进行比较。只有达到quorum规定的数量后,该结果才会被接受并返回给调用方。

默认情况下,quorum需要50%(四舍五入)的后端响应(to agree)。权重可用于让一个后端Provider作用更强。

new ethers.providers.FallbackProvider( providers [ , quorum ] )

创建一个连接到providers的FallbackProvider的新实例。 如果quorum未指定,则设为provider权重总和的一半。

providers可以是ProviderFallbackProviderConfig的数组。 如果提供了Provider,默认的优先级为1,权重为1。

provider.providerConfigs Array< FallbackProviderConfig >

描述后端的Provider配置列表。

provider.quorum number

在得到结果之前,后端响应的quorum必须达成一致。默认情况下,这个值是权重之和的一半

FallbackProviderConfig

fallbackProviderConfig.provider Provider

该配置的provider。

fallbackProviderConfig.priority number

表示provider使用的优先级。Lower-value优先级比higher-value优先级更受欢迎。 如果多个providers共享相同的优先级,则随机选择它们。

fallbackProviderConfig.stallTimeout number

超时后(以ms为单位)将会尝试其他的Provider。这不会影响到当前的Provider; 如果它返回一个结果,将会计数,并算入到quorum的一部分。

Lower values的Provider会造成更多的网络流量,但可能会减少响应的时间。

fallbackProviderConfig.weight number

表示这个provider响应的权重。比如,你认为某个Provider更值得信任,就可以用这个参数去设置。

IpcProvider inherits JsonRpcProvider

IpcProvider 允许JSON-RPC API在文件系统的本地文件名上使用。 Geth, Parity和其他节点都会开放了这个功能。

这只能在node.js中使用,因为它需要访问文件系统,并且由于文件权限可能有增加额外的复杂性。(请参阅相关节点实现的文档)

ipcProvider.path string

这个Provider要连接的路径。

UrlJsonRpcProvider inherits JsonRpcProvider

这个类打算作为子类而不是直接使用。只需要额外生成一个SON-RPCURL后,就能通过JsonRpcProvider创建一个Provider

new ethers.providers.UrlJsonRpcProvider( [ network [ , apiKey ] ] )

子类通常不需要重写这一部分。相反应该重写静态的方法 getUrl 和可选的getApiKey

urlJsonRpcProvider.apiKey any

InheritedClass.getApiKey返回的apiKey的值。

InheritingClass.getApiKey( apiKey ) any

这个函数应该检查apiKey以确保它是有效的,并返回一个(可能修改过的)值在getUrl函数中使用。

InheritingClass.getUrl( network , apiKey ) string

这个URL在JsonRpcProvider 实例中使用。

Web3Provider inherits JsonRpcProvider

Web3Provider为了方便一个基于web3.js应用程序迁移到ethers,主要是通过将现有的Web3-compatible (比如一个 Web3HttpProvider, Web3IpcProvider 或者 Web3WsProvider) 打包进ethers.js作为ethers.js Provider,并将相应的接口暴露出去,它可以和ethers.js库的其他部分一起使用。

这也可以将一个标准的EIP-1193 Provider打包进去。

new ethers.providers.Web3Provider( externalProvider [ , network ] )

创建一个Web3Provider,它能将EIP-1193 Provider 或者 Web3Provider-compatible Provider打包进去。

web3Provider.provider Web3CompatibleProvider

这个provider用于创建一个实例。

ExternalProvider

ExternalProvider 可以是上面提到的 Web3 Providers (或者是其他兼容的) 又或者是一个EIP-1193 provider。

ExternalProvider必须提供以下签名之一,并且使用第一个匹配的签名:

externalProvider.request( request ) Promise< any >

它遵循EIP-1193 API 签名。

request 应该是一个标准的 JSON-RPC payload, 最少指定methodparams

结果应该是实际的结果,它与Web3.js响应不同,后者是一个包装好的JSON-RPC响应。

externalProvider.sendAsync( request , callback ) void

它遵循Web3.js Provider Signature.

request 应该是一个标准的 JSON-RPC payload, 最少指定methodparams

回调函数应该使用错误优先调用的语义,比如(error, result),其中result是JSON-RPC包装后的得到的。

externalProvider.send( request , callback ) void

这与sendAsync是相同的。在历史上,这使用了一个同步的web请求,但当前没有浏览器支持这种方式,所以它的这种使用方式在很久以前就被弃用了。

WebSocketProvider inherits JsonRpcProvider

WebSocketProvider 连接到一个JSON-RPC websocket兼容的后端,它允许持久连接、多路复用请求和发布-子事件,以实现更即时的事件调度。

WebSocket API是较新的,如果运行自己的基础设施,请注意WebSockets对服务器资源的占用会很大, 因为它们必须管理和维护每个客户端的状态。由于这个原因,许多服务也可能会为使用他们的WebSocket端点而收取额外的费用。

new ethers.providers.WebSocketProvider( [ url [ , network ] ] )

通过一个url连接到网络,返回一个新的WebSocketProvider

如果url未指定,默认值是"ws://localhost:8546"。 如果network未被指定,它将会从网络中查询。

Types

BlockTag

BlockTag 在区块链中指定了一个特定的区块位置

Networkish

Networkish可以是以下任意一种:

Network

Network 表示一个Ethereum 网络

network.name string

可读的网络名称,如homestead。如果网络的name未指定,将会是"unknown"

network.chainId number

网络的Chain ID。

network.ensAddress string< 地址(Address) >

部署在这个网络上的ENS注册表的地址。

FeeData

FeeData对象根据最好且可用建议,去封装发送交易所需的费用数据。

feeData.gasPrice 大数(BigNumber)

gasPrice用于那些不支持EIP-1559的遗留交易或网络。

feeData.maxFeePerGas 大数(BigNumber)

maxFeePerGas用于交易。这基于最近产出区块的baseFee

feeData.maxPriorityFeePerGas 大数(BigNumber)

The maxPriorityFeePerGas to use for a transaction. This accounts for the uncle risk and for the majority of current MEV risk.

Block

block.hash string< DataHexString< 32 > >

这个区块的哈希。

block.parentHash string< DataHexString< 32 > >

上个区块的哈希。

block.number number

当前区块的高度(数量)。

block.timestamp number

当前区块的时间戳。

block.nonce string< DataHexString >

nonce 用在基于工作量证明(PoW)机制挖取区块。 开发人员通常对这个属性不感兴趣。

block.difficulty number

该区块的矿工需要达到的难度指标。 开发人员通常对这个属性不感兴趣。

block.gasLimit 大数(BigNumber)

这个区块允许使用的最大gas数量。这该值的可以由矿工投票决定要变大还是变小,用于自动调整网络的带宽需求。 开发人员通常对这个属性不感兴趣。

block.gasUsed 大数(BigNumber)

当前区块所有的交易已使用的gas总数量。

block.miner string

当前区块的coinbase 地址,这个地址表示挖出这个区块的矿工想要接受这笔开采奖励的地址。

block.extraData string

该数据表示的是当挖出一个区块时,这个矿工可以选择包含的额外数据。 开发人员通常对这个属性不感兴趣。

Block (with transaction hashes)

通常只需要一个区块中包含交易的哈希值,因此默认情况下,一个区块只包含这些信息,这样数据就不会很大。

block.transactions Array< string< DataHexString< 32 > > >

一个当前区块包含的每一条交易哈希的列表。

BlockWithTransactions inherits Block

如果需要一个块的所有交易,则该对象包含每条交易的完整信息。

block.transactions Array< TransactionResponse >

当前区块包含的交易列表。

Events and Logs

EventFilter

filter.address string< 地址(Address) >

想要筛选的地址, null 表示匹配任何用户的地址。

filter.topics Array< string< Data< 32 > > | Array< string< Data< 32 > > > >

要筛选的主题,null示匹配任何用户的主题。

每一个 entry 表示一个AND匹配条件,entry 也可以是null来匹配所有的内容。 如果给定的 entry 是一个数组,那么这个 entry 是被视为 OR匹配条件来匹配entry里面的内容。 有关指定复杂过滤器的详细信息和示例,请参阅Filters

Filter inherits EventFilter

filter.fromBlock BlockTag

用于搜索匹配过滤条件的日志的起始区块(包含在内)。

filter.toBlock BlockTag

用于搜索匹配过滤条件的日志的结束区块(包含在内)。

FilterByBlockHash inherits EventFilter

filter.blockHash string< DataHexString< 32 > >

指定区块(按这个区块哈希值)搜索符合过滤条件的日志。

Log

log.blockNumber number

包含该日志的交易的区块高度(区块数)。

log.blockHash string< DataHexString< 32 > >

包含该日志的交易的区块的哈希值。

log.removed boolean

在区块重组期间,如果一个交易是孤立的,它将被设置为true,表示Log entry已被删除; 在不久的将来,当使用包含该日志的交易被另一个区块挖出时,可能会再次触发此日志,但请记住,值可能会更改。

log.transactionLogIndex number

该日志在交易中的索引。

log.address string< 地址(Address) >

生成这条日志的合约地址。

log.data string< DataHexString >

日志的数据。

log.topics Array< string< DataHexString< 32 > > >

日志的主题(索引属性)列表。

log.transactionHash string< DataHexString< 32 > >

包含这条日志的交易的哈希值。

log.transactionIndex number

区块中包含这条日志的交易的索引。

log.logIndex number

在整个区块中所有的日志集合里,该条日志的索引。

Transactions

TransactionRequest

transaction request 描述了一笔将要被发送到网络或以其他方式处理的交易。

所有字段都是可选的,并且可以是一个能被解析为所需类型的promise。

transactionRequest.to string | Promise< string >

接受这笔交易的地址(or ENS name)。

transactionRequest.from string< 地址(Address) > | Promise< string< 地址(Address) > >

发送这笔交易的地址。

transactionRequest.nonce number | Promise< number >

交易的 nonce 值。 发送这笔交易要设置这个值,是number类型的。

transactionRequest.data DataHexString | Promise< DataHexString >

交易的数据。

transactionRequest.value 大数(BigNumber) | Promise< 大数(BigNumber) >

这笔交易要发送的数量(以wei为单位)。

transactionRequest.gasLimit 大数(BigNumber) | Promise< 大数(BigNumber) >

这笔交易允许使用的最大gas值。

如果未指定,ethers将使用estimateGas来确定要使用的gas值。对于无法预测的gas的交易,可能需要这样做显式地指定。

transactionRequest.gasPrice 大数(BigNumber) | Promise< 大数(BigNumber) >

这笔交易将支付的每个gas的价格(以wei为单位)。

这个属性不适用于这笔交易的type设置为1 或者 2的情形,也不适用于交易已指定了maxFeePerGas 或者 maxPriorityFeePerGas的情形。

transactionRequest.maxFeePerGas 大数(BigNumber) | Promise< 大数(BigNumber) >

这笔交易将支付EIP-1559基础费用的每个gas的价格的最高价格(以wei为单位)。

大多数开发人员不应该指定该参数,而应该使用网络决定的默认值。 这个属性不适用于这笔交易的type设置为0的情形,也不适用于交易已指定了gasPrice的情形。

transactionRequest.maxPriorityFeePerGas 大数(BigNumber) | Promise< 大数(BigNumber) >

这笔交易将支付EIP-1559优先费用的每个gas的价格的价格(以wei为单位)。 这包含在maxFeePerGass中,所以这不会影响maxFeePerGas设置的总最大价格。

大多数开发人员应该不指定该参数,而应该使用网络决定的默认值。 这个属性不适用于这笔交易的type设置为0的情形,也不适用于交易已指定了gasPrice的情形。

transactionRequest.chainId number | Promise< number >

已被授权交易的chain ID,由EIP-155指定。 如果chain ID为0将禁用EIP-155,交易将在任何网络上有效。这可能很危险,应该小心,因为它允许交易在可能不是你想指定的网络上复现。 在最新版本的Geth中,有意重现的交易在默认情况下是禁用的,需要配置才能启用。

transactionRequest.type null | number

The EIP-2718 type of this transaction envelope, or null for to use the network default. To force using a lagacy transaction without an envelope, use type 0. 这笔交易envelope的EIP-2718类型,在网络上这个默认值是null。要强制使用没有envelope的lagacy交易,请使用type 0

transactionRequest.accessList AccessListish

要包含的AccessList;仅适用于EIP-2930EIP-1559的交易。

TransactionResponse inherits 交易

TransactionResponse包括交易的所有属性,以及等区块被挖出来后就很有用的几个属性。

transaction.blockNumber number

这笔交易所在区块被挖出来时的高度(区块数量)。如果区块还没有被挖出,这个值为null

transaction.blockHash string< DataHexString< 32 > >

这笔交易所在区块被挖出来时的哈希值。如果区块还没有被挖出,这个值为null

transaction.timestamp number

这笔交易所在区块被挖出来时的时间戳。如果区块还没有被挖出,这个值为null

transaction.confirmations number

当这笔交易所在的区块被挖出来时,所有已挖出的区块数量(包括初始块)。

transaction.raw string< DataHexString >

序列化交易。如果一些后端不重新填充它时可能是null。 如果你需要这个属性,可以使用this cookbook recipeTransactionResponse对象中计算。

transaction.wait( [ confirms = 1 ] ) Promise< TransactionReceipt >

confirms属性表示你愿意等待的挖出区块数量,一旦满足这个值将返回一个待解析的TransactionReceipt。 如果confirms为0,并且交易还没有被挖出,则返回null

如果交易执行失败(即接收状态为0),将会生成一个CALL_EXCEPTION,其属性如下:

  • error.transaction - 这笔交易
  • error.transactionHash - 这笔交易的哈希值
  • error.receipt - the actual receipt, 状态是 0

如果这笔交易被另一个交易替换,将会生成一个TRANSACTION_REPLACED,其属性如下:

  • error.hash - 被替换的交易的哈希值
  • error.reason - string类型的错误原因; 是 "repriced""cancelled""replaced"其中一个
  • error.cancelled - boolean类型; "repriced"表示不视为cancelled, 但"cancelled""replaced"视为cancelled。
  • error.replacement - the replacement transaction (a TransactionResponse)
  • error.receipt - replacement transaction的收据 (a TransactionReceipt)

当用户在客户端相同的帐户中发送一笔新的交易,这笔交易与之前一笔交易的nonce相同时,之前的那笔交易将被替换。 这通常是为了加快交易或取消交易,新交易可以给矿工更多的费用,让他们更喜欢新交易,而不是原来的交易。

transactionRequest.type number

这笔交易的EIP-2718类型。如果事务是没有envelope的legacy交易,则其类型为0

transactionRequest.accessList AccessList

包含的AccessList,对于不支持访问列表的交易类型时该值为空。

TransactionReceipt

receipt.to string< 地址(Address) >

接受这笔交易的地址。如果交易是用于部署合约,则为null

receipt.from string< 地址(Address) >

发起这笔交易的地址。

receipt.contractAddress string< 地址(Address) >

如果这笔交易是转去一个null的地址,它是表示一个init transaction用于部署合约。在这种情况下,这个属性就代表所创建的合约的地址。 要计算合约地址,getContractAddress的utility function还可以与TransactionResponse对象一起使用,该对象需要交易的nonce值和发送人的地址。

receipt.transactionIndex number

当前区块包含所有交易列表中,此交易的索引。

receipt.type number

此交易的EIP-2718类型。如果事务是没有envelope的legacy事务,则其type 为0

receipt.root string

The intermediate state root of a receipt.

只有在Byzantium Hard Fork之前包含在区块中的交易才有这个属性,因为它被status属性取代了。

这些属性通常对开发者没什么用处。仅考虑单笔交易的防伪性的时候可以用于验证状态转换;如果没有它,则必须考虑整个区块。

receipt.gasUsed 大数(BigNumber)

该交易实际使用的gas值。

receipt.logsBloom string< DataHexString >

一个bloom-filter,包含此交易中所有日志中包含的全部地址和主题。

receipt.blockHash string< DataHexString< 32 > >

包含该交易的区块的区块哈希值。

receipt.transactionHash string< DataHexString< 32 > >

这笔交易的哈希值。

receipt.logs Array< Log >

这笔交易触发的所有日志。

receipt.blockNumber number

包含这笔交易的区块的高度(区块数量)。

receipt.confirmations number

当这笔交易所在的区块被挖出来时,所有已挖出的区块数量(包括这笔刚挖出的区块)。

receipt.cumulativeGasUsed 大数(BigNumber)

对于包含该交易的块,这是截至(并包括)该交易的有序交易列表中每个交易使用的gas的总和。 开发人员通常对这个属性不感兴趣。

receipt.byzantium boolean

如果区块是在post-Byzantium Hard Fork区块,这个值是true。

receipt.status boolean

如果交易成功,这个值为1;如果交易被reverted,这个值为为0。 只有post-Byzantium Hard Fork中包含的交易才有这个属性。

Access Lists

Access List是可选的,它包含一个地址列表和地址的存储槽,这些地址应该是warmed或pre-fetched的,以便在这个交易的执行中使用。 一个warmed值有一个额外的预先支付访问的成本,但在整个读取和写入代码的执行过程中会被降低。

AccessListish

AccessList的一个更宽松的描述,它将在内部使用accessListify进行转换。

他可以是以下的任意一种:

当使用对象形式(上述最后一个选项)时,地址和存储槽将被排序。 如果需要access list的显式顺序,则必须使用方式。 大多数开发人员不需要明确的顺序。

equivalent to the AccessList example below
// Option 1: // AccessList // see below // Option 2: // Array< [ Address, Array<Bytes32> ] > accessList = [ [ "0x0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", [ "0x0000000000000000000000000000000000000000000000000000000000000004", "0x0bcad17ecf260d6506c6b97768bdc2acfb6694445d27ffd3f9c1cfbee4a9bd6d" ] ], [ "0x5FfC014343cd971B7eb70732021E26C35B744cc4", [ "0x0000000000000000000000000000000000000000000000000000000000000001" ] ] ]; // Option 3: // Record<Address, Array<Bytes32>> accessList = { "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e": [ "0x0000000000000000000000000000000000000000000000000000000000000004", "0x0bcad17ecf260d6506c6b97768bdc2acfb6694445d27ffd3f9c1cfbee4a9bd6d" ], "0x5FfC014343cd971B7eb70732021E26C35B744cc4": [ "0x0000000000000000000000000000000000000000000000000000000000000001" ] };

AccessList

一个EIP-2930交易允许一个可选的AccessList,它会导致交易warm(预缓存)另一个地址状态和指定的storage key。

这会使得交易的内在成本变大,但在整个事务执行过程中给存储和状态访问带来了好处。

example access list
// Array of objects with the form: // { // address: Address, // storageKey: Array< DataHexString< 32 > > // } accessList = [ { address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", storageKeys: [ "0x0000000000000000000000000000000000000000000000000000000000000004", "0x0bcad17ecf260d6506c6b97768bdc2acfb6694445d27ffd3f9c1cfbee4a9bd6d" ] }, { address: "0x5FfC014343cd971B7eb70732021E26C35B744cc4", storageKeys: [ "0x0000000000000000000000000000000000000000000000000000000000000001" ] } ];

Signers

ethersSigner是以太坊账户的抽象,可以用来签名消息和交易,如将签名的交易发送到以太坊网络以执行状态更改的操作。

可用的操作里在很大程度上取决于所使用的子类。

例如,一个来自MetaMask的Signer可以发送交易和签名消息,但不能签名一个不广播的交易。

你会遇到的最常见的Signers有:

Signer

Signer类是抽象的,不能直接实例化,而是应该使用一个具体的子类,如 Wallet, VoidSignerJsonRpcSigner

signer.connect( provider ) Signer

子类必须实现这个,但是如果更改后的providers是不被支持的话,它们可能仅仅只抛出一个错误。

signer.getAddress( ) Promise< string< 地址(Address) > >

返回一个解析为帐户地址的Promise。

这是一个Promise,因此一个Signer可以围绕一个异步源进行设计,如硬钱包。

子类必须实现这个。

Signer.isSigner( object ) boolean

当且仅当 object 是一个Signer时返回true。

Blockchain Methods

signer.getBalance( [ blockTag = "latest" ] ) Promise< 大数(BigNumber) >

在指定的blockTag下返回这个钱包的余额。

signer.getChainId( ) Promise< number >

返回这个钱包连接的链 ID。

signer.getGasPrice( ) Promise< 大数(BigNumber) >

返回当前的gas price。

signer.getTransactionCount( [ blockTag = "latest" ] ) Promise< number >

返回此帐户曾经发送的交易数量,交易中的中nonce依赖这个值。

signer.call( transactionRequest ) Promise< string< DataHexString > >

返回transactionRequest调用的结果,此帐户地址用作from 字段。

signer.estimateGas( transactionRequest ) Promise< 大数(BigNumber) >

返回发送transactionRequest的估算费用,在使用这个方法之前先用账户地址填充from字段。

signer.resolveName( ensName ) Promise< string< 地址(Address) > >

返回与ensName关联的地址。

Signing

signer.signMessage( message ) Promise< string< RawSignature > >

这将返回一个解析为消息Raw SignaturePromise。

由于使用的是hashMessage方法,因此它是EIP-191兼容的。 如果在Solidity中恢复地址,则需要这个前缀来创建一个匹配的哈希。

子类必须实现这个方法。 如果不支持签名消息可能会抛出错误,比如在基于合约的钱包或基于元交易的钱包中使用时。

Note

如果message是一个字符串,它将被视为一个字符串并转换为UTF8字节的表示形式。

当且仅当消息是Bytes时,它将被视为二进制数据。

例如,字符串"0x1234"是6个字符长(在本例中是6字节长)。 这与数组[ 0x12, 0x34 ]不同,数组长度为2字节。

一种常见的情况是对哈希签名。在本例中,如果哈希是一个字符串,则必须首先使用arrayifyutility函数将其转换为数组。

signer.signTransaction( transactionRequest ) Promise< string< DataHexString > >

返回一个解析为transactionRequest中已签名的交易的Promise。 此方法不填充任何缺少的字段。

子类必须实现这个,如果不支持签名消息可能会抛出错误,出于安全这在许多客户端中是常见的。

signer.sendTransaction( transactionRequest ) Promise< TransactionResponse >

该方法使用populateTransaction填充缺少字段的transactionRequest,并返回一个解析成交易的Promise。

子类必须实现这个,如果不支持发送交易可能会抛出错误,比如VoidSigner或者如果钱包没有联网且没有连接Provider

signer._signTypedData( domain , types , value ) Promise< string< RawSignature > >

<<<<<<< Updated upstream 签名使用EIP-712规范,在作用域中采用类型数据结构作为类型化的数据值。 ======= 使用EIP-712规范,采用类型数据结构作为领域签署类型化的数据值。 >>>>>>> Stashed changes

Experimental feature (方法名称会做更改)

这仍然是一个实验中的功能。如果使用它,请指定您正在使用的ethers的确切版本(例如指定"5.0.18",而不是"^5.0.18"), 因为方法名将从_signTypedData重命名为signTypedData

Typed Data Example
// 作用域中的所有属性都是可选的 const domain = { name: 'Ether Mail', version: '1', chainId: 1, verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' }; // 所有类型定义的name列表 const types = { Person: [ { name: 'name', type: 'string' }, { name: 'wallet', type: 'address' } ], Mail: [ { name: 'from', type: 'Person' }, { name: 'to', type: 'Person' }, { name: 'contents', type: 'string' } ] }; // 待签名的数据 const value = { from: { name: 'Cow', wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' }, to: { name: 'Bob', wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' }, contents: 'Hello, Bob!' }; signature = await signer._signTypedData(domain, types, value); // '0x463b9c9971d1a144507d2e905f4e98becd159139421a4bb8d3c9c2ed04eb401057dd0698d504fd6ca48829a3c8a7a98c1c961eae617096cb54264bbdd082e13d1c'

Sub-Classes

Signer的所有重要属性都是不可变的,这一点非常重要。由于以太坊是异步的,并处理关键数据(如ether和其他潜在有价值的加密资产), 整个Signer的生命周期中保持provideraddress等属性是静态的有助于防止严重的问题的出现, 而且许多其他类和库也是认定provideraddress等属性是静态的。

子类必须扩展Signer,并且必须调用super()

signer.checkTransaction( transactionRequest ) TransactionRequest

这通常不需要重写,但可能需要在子类中提供自定义操作。

这应该返回一个transactionRequest的副本,包含callestimateGaspopulateTransaction (sendTransaction使用的)所需的任何属性。 如果指定了任何未知的key,它会抛出一个错误。

默认的实现只验证有效的TransactionRequest属性是否存在,如果不存在,则将from添加到交易中。

如果存在from字段,则必须验证它与 Signer的地址是否相等。

signer.populateTransaction( transactionRequest ) Promise< TransactionRequest >

这通常不需要重写,但可能需要在子类中提供自定义操作。

这应该返回一个transactionRequest的副本,遵循与checkTransaction相同的过程, 并填写发送交易所需的任何属性。返回的结果都是promises,可以使用resolvePropertiesutility函数来解析。

默认实现调用checkTransaction,如果它是一个ENS name就会解析它,并根据Signer上的相关操作添加gasPrice, nonce, gasLimitchainId

Wallet inherits ExternallyOwnedAccount and Signer

Wallet类继承了Signer,可以使用私钥作为外部拥有帐户(EOA)的标准对交易和消息进行签名。

new ethers.Wallet( privateKey [ , provider ] )

privateKey创建一个新的钱包实例,并可选地连接到provider

ethers.Wallet.createRandom( [ options = {} ] ) Wallet

返回一个带有随机私钥的新钱包,由加密安全的熵源生成。如果当前环境没有安全的熵源,则会抛出错误。

使用此方法创建的钱包将具有助记词。

ethers.Wallet.fromEncryptedJson( json , password [ , progress ] ) Promise< Wallet >

从加密的JSON钱包创建一个实例。

如果提供了进度,它将在解密期间被调用,其值介于0到1之间,表示一个完成进度。

ethers.Wallet.fromEncryptedJsonSync( json , password ) Wallet

从加密的JSON钱包创建一个实例。

此操作将同步操作,从而锁定用户界面一段时间。 大多数应用程序应该使用异步的fromEncryptedJson

ethers.Wallet.fromMnemonic( mnemonic [ , path , [ wordlist ] ] ) Wallet

从助记短语中创建实例。

如果没有指定path,则使用以太坊的默认path路径(如m/44'/60'/0'/0/0).

如果不指定wordlist,则使用English Wordlist。

Properties

wallet.address string< 地址(Address) >

此钱包表示的帐户的地址。

wallet.provider Provider

这个钱包所连接的provider,它将用于任何Blockchain Methods的方法,它也可以是null。

注意

一个钱包实例是不可变的,因此如果您希望更改Provider,您可以使用connect方法创建一个连接到所需的provider的新实例。

wallet.publicKey string< DataHexString< 65 > >

此钱包的未压缩的公钥。

Methods

wallet.encrypt( password , [ options = {} , [ progress ] ] ) Promise< string >

加密钱包,使用password返回一个解析为JSON钱包的Promise。

如果提供了进度,它将在解密期间被调用,其值介于0到1之间,表示一个完成进度。

Wallet Examples
// 通过助记词创建一个钱包实例 mnemonic = "announce room limb pattern dry unit scale effort smooth jazz weasel alcohol" walletMnemonic = Wallet.fromMnemonic(mnemonic) // 或者通过私钥创建一个钱包实例 walletPrivateKey = new Wallet(walletMnemonic.privateKey) walletMnemonic.address === walletPrivateKey.address // true // 有着每个Signer API的地址,是一个Promise对象 await walletMnemonic.getAddress() // '0x71CB05EE1b1F506fF321Da3dac38f25c0c9ce6E1' // 一个钱包地址,也可用同步的方式获得 walletMnemonic.address // '0x71CB05EE1b1F506fF321Da3dac38f25c0c9ce6E1' // 内部加密组件 walletMnemonic.privateKey // '0x1da6847600b0ee25e9ad9a52abbd786dd2502fa4005dd5af9310b7cc7a3b25db' walletMnemonic.publicKey // '0x04b9e72dfd423bcf95b3801ac93f4392be5ff22143f9980eb78b3a860c4843bfd04829ae61cdba4b3b1978ac5fc64f5cc2f4350e35a108a9c9a92a81200a60cd64' // 钱包助记词 walletMnemonic.mnemonic // { // locale: 'en', // path: "m/44'/60'/0'/0/0", // phrase: 'announce room limb pattern dry unit scale effort smooth jazz weasel alcohol' // } // Note: 通过私钥创建的钱包实例没有助记词,因为从数学上无法推导 walletPrivateKey.mnemonic // null // 签名消息 await walletMnemonic.signMessage("Hello World") // '0x14280e5885a19f60e536de50097e96e3738c7acae4e9e62d67272d794b8127d31c03d9cd59781d4ee31fb4e1b893bd9b020ec67dfa65cfb51e2bdadbb1de26d91c' tx = { to: "0x8ba1f109551bD432803012645Ac136ddd64DBA72", value: utils.parseEther("1.0") } // 签名交易 await walletMnemonic.signTransaction(tx) // '0xf865808080948ba1f109551bd432803012645ac136ddd64dba72880de0b6b3a7640000801ca0918e294306d177ab7bd664f5e141436563854ebe0a3e523b9690b4922bbb52b8a01181612cec9c431c4257a79b8c9f0c980a2c49bb5a0e6ac52949163eeb565dfc' // connect方法返回一个连接到provider的新钱包实例 wallet = walletMnemonic.connect(provider) // 从网络中查询 await wallet.getBalance(); // { BigNumber: "6846" } await wallet.getTransactionCount(); // 3 // 发送 ether await wallet.sendTransaction(tx) // { // accessList: [], // chainId: 31337, // confirmations: 0, // data: '0x', // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // gasLimit: { BigNumber: "21001" }, // gasPrice: null, // hash: '0x1b4a95e9d23bd96d5429c535148eb3eaed326d89118b118e44cc05c26703224e', // maxFeePerGas: { BigNumber: "1572346688" }, // maxPriorityFeePerGas: { BigNumber: "1500000000" }, // nonce: 5, // r: '0x5e32c2741700a9120cfa186bc74e88b3d9488393be796a5124fddf08ffdbfdc6', // s: '0x2431f3e3274cd22d6de63ed6e23a5c6839c1fabeb97b6683fb15584b9bf1f29d', // to: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // type: 2, // v: 0, // value: { BigNumber: "1000000000000000000" }, // wait: [Function] // }

VoidSigner inherits Signer

一个VoidSigner是一个简单的Signer,它不能签名。

当API需要signer作为参数时,它作为只读的signer是有用的,但它只能携带只读的操作。

比如,在call函数调用期间会自动传递所提供的地址。

new ethers.VoidSigner( address [ , provider ] ) VoidSigner

为一个地址创建VoidSigner实例。

voidSigner.address string< 地址(Address) >

VoidSigner的地址。

VoidSigner Pre-flight Example
address = "0x8ba1f109551bD432803012645Ac136ddd64DBA72" signer = new ethers.VoidSigner(address, provider) // The DAI token contract abi = [ "function balanceOf(address) view returns (uint)", "function transfer(address, uint) returns (bool)" ] contract = new ethers.Contract("dai.tokens.ethers.eth", abi, signer) // 获取此帐户的token数量 tokens = await contract.balanceOf(signer.getAddress()) // { BigNumber: "2413468059122458201631" } // // Pre-flight (check for revert) on DAI from the signer // // 注意: 我们现在还没有私钥,这仅仅允许我们检查这样做时会发生什么。这是前端界面中发出请求进行检查是很有用的。 // 当 token 余额可用时将会正确执行转账 await contract.callStatic.transfer("donations.ethers.eth", tokens) // true // 当转账的数量大于 token 余额时将会失败 await contract.callStatic.transfer("donations.ethers.eth", tokens.add(1)) // [Error: call revert exception; VM Exception while processing transaction: reverted with reason string "Dai/insufficient-balance" [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ]] { // address: 'dai.tokens.ethers.eth', // args: [ // 'donations.ethers.eth', // { BigNumber: "2413468059122458201632" } // ], // code: 'CALL_EXCEPTION', // data: '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000184461692f696e73756666696369656e742d62616c616e63650000000000000000', // errorArgs: [ // 'Dai/insufficient-balance' // ], // errorName: 'Error', // errorSignature: 'Error(string)', // method: 'transfer(address,uint256)', // reason: 'Dai/insufficient-balance', // transaction: { // data: '0xa9059cbb000000000000000000000000643aa0a61eadcc9cc202d1915d942d35d005400c000000000000000000000000000000000000000000000082d598fcab89948e20', // from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // to: '0x6B175474E89094C44Da98b954EedeAC495271d0F' // } // }

ExternallyOwnedAccount

这个接口包含外部拥有帐户(EOA)所需的最小属性集,可以执行某些操作,比如将其编码为JSON钱包。

eoa.address string< 地址(Address) >

地址(Address)EOA的地址

eoa.privateKey string< DataHexString< 32 > >

EOA的私钥

eoa.mnemonic 助记词

可选的。帐户HD的助记词,如果有的话可以打印出来。EOA账户源不编码助记符,如HD extended keys。

合约交互

Contract对象是部署在以太坊网络上的合约(EVM字节码)的抽象。它允许以一种简单的方式去将链上的合约的调用(calls)和交易(transactions)序列化,并反序列化它们的结果(results)和触发的日志(logs)。

ContractFactory是合约字节码的抽象,可以方便地部署合约。

ContractFactory合约 字节码(bytecode)的抽象,可以方便地部署合约。

合约(Contract)
ContractFactory
Example: ERC-20 Contract

合约(Contract)

Contract是部署到区块链的代码的抽象。

合约可以被用来发送交易,将交易数据作为参数运行代码。

创建实例

new ethers.Contract( address , abi , signerOrProvider )
contract.attach( addressOrName ) 合约(Contract)

返回一个附加到新地址的Contract新实例。 如果网络上有多个类似或相同的合约副本,并且您希望与它们中的每一个进行交互,那么这是非常有用的。

contract.connect( providerOrSigner ) 合约(Contract)

返回合约的一个新实例,但需要连接到providerOrSigner

通过传入一个Provider,这将返回一个低级的合约实例,它只有只读访问的权限(即常量调用)。

通过传入一个Signer,这将返回一个代表该签名人(signer)的合约实例。

属性

contract.address string< 地址(Address) >

这是构建合约时使用的地址(或ENS名称)。

contract.resolvedAddress string< 地址(Address) >

这是一个合约对象将解析合约地址的promise。 如果一个地址(Address)被提供给构造函数,它将解析成这个地址; 如果提供的是ENS名称,这将解析成对应的地址。

contract.deployTransaction TransactionResponse

如果Contract是ContractFactory部署后返回的对象, 那么contract.deployTransaction返回的数据就是部署这个合约的交易信息(transaction)。

contract.interface Interface

这是作为Interface接口的ABI。

contract.provider Provider

如果构造函数使用的是provider生成的contract合约对象,那么这个结果就是这个provider。 如果使用的是具有Provider的signer,那么这个结果就是provider。

contract.signer Signer

如果构造函数使用的是signer生成的contract合约对象,那么这个结果就是这个signer。

方法

contract.deployed( ) Promise< 合约(Contract) >
Contract.isIndexed( value ) boolean

事件(Events)

contract.queryFilter( event [ , fromBlockOrBlockHash [ , toBlock ] ) Promise< Array< Event > >

返回与event匹配的事件。

contract.listenerCount( [ event ] ) number

返回订阅该event的监听器数量。如果没有提供event,则返回所有事件的总数。

contract.listeners( event ) Array< Listener >

返回订阅该event的监听器列表。

contract.off( event , listener ) this

监听器取消订阅event事件。

contract.on( event , listener ) this

监听event事件,当事件发生时,会调用listener函数。

contract.once( event , listener ) this

监听event事件,当事件发生时,仅调用一次listener函数。

contract.removeAllListeners( [ event ] ) this

取消所有订阅event事件的监听器。如果未提供event事件,则取消订阅所有事件的监听。

元类(Meta-Class)

元类是在运行时确定其所有属性的类。Contract对象使用合约的ABI来确定可使用方法(methods), 因此下面的部分将描述用一些属性,来与在合约构造函数期间交互的通用方法。

只读的方法 (常量; 如 view 或 pure)

常量方法是只读的,针对当前区块链状态计算少量EVM代码,并可以通过请求单个节点来计算,该节点会返回一个结果。 因此,它是免费的,不需要任何以太币,但不能更改区块链状态。

contract.METHOD_NAME( ...args [ , overrides ] ) Promise< any >

结果的类型取决于ABI。如果方法返回单个值,则将直接返回该值,否则将返回一个Result对象,其中包含每个位置可用的参数, 如果参数被命名,那么就按照命名后的值去执行。

如果返回的值匹配JavaScript中的类型值,如strings字符串类型 和 booleans布尔类型,那么返回的值类型就直接是JavaScript中的类型值。

但对于numbers类型,如果类型在JavaScript的安全范围内(即小于53位,如int24uint488),则使用标准的JavaScript number类型。 否则返回大数(BigNumber)类型。

对于字节bytes类型(包括固定长度和动态),将返回一个DataHexString

如果call reverts(或runs out of gas),将抛出一个CALL_EXCEPTION,其中包括:

  • error.address - 合约地址
  • error.args - 合约方法中传入的参数
  • error.transaction - 交易

只读方法的overrides对象可以包括以下任何一个:

  • overrides.from - 代码执行期间使用的msg.sender (或 CALLER)
  • overrides.value - 代码执行期间使用的msg.value (或 CALLVALUE)
  • overrides.gasPrice - 每个gas的价格(理论上);因为没有交易,所以不会收取任何费用,但EVM仍然需要向tx.gasprice(或GASPRICE)传递value值; 大多数开发人员不需要这样做

  • overrides.gasLimit - 在执行代码期间允许节点使用的gas数量(理论上);因为没有交易,所以不会有任何费用,但EVM仍然估计gas总数量,因此会向gasleft (或 GAS) 传递这些值。

  • overrides.blockTag - 一个用来模拟在哪里执行的块标签,可以用于假设性历史分析;注意,许多后端不支持这一点,或者可能需要付费访问,因为节点数据库存储和处理需求费用要高得多

contract.functions.METHOD_NAME( ...args [ , overrides ] ) Promise< Result >

结果将始终是Result,即使只有一个返回值类型。

这简化了期望使用合约(Contract)对象的框架,因为它们不需要检查返回类型来展开简化函数。

此方法的另一个用途是用于错误恢复。例如,如果一个函数的结果是一个无效的UTF-8字符串,使用上述元类函数的普通调用将抛出一个异常。 这允许使用Result access error来访问低级字节以及错误的原因,从而允许使用另一种UTF-8错误策略。

大多数开发人员不需要这样做。

overrides与上面的只读操作相同。

Write Methods (non-constant)

非常量方法要求签名一笔交易,并需要向矿工支付费用。这个交易将由整个网络上的所有节点以及矿工进行验证, 矿工会根据当前状态执行,接着在区块链计算新状态。

它不能返回结果。如果需要一个结果,那么应该使用Solidity事件(或EVM日志)对其进行记录,然后可以从交易收据中查询该事件。

contract.METHOD_NAME( ...args [ , overrides ] ) Promise< TransactionResponse >

交易被发送到网络后,返回交易的TransactionResponseContract合约对象需要signer。

重写方法的overrides对象可以是以下任何一个:

  • overrides.gasPrice - 每个gas的价格
  • overrides.gasLimit - 该笔交易允许使用的gas的最大数量,未被使用的gas按照gasPrice退还
  • overrides.value - 调用中转账的ether (wei格式)数量
  • overrides.nonce - Signer使用的nonce值

如果返回的TransactionResponse使用了wait()方法,那么在收据上将会有额外的属性:

  • receipt.events - 带有附加属性的日志数组(如果ABI包含事件的描述)
  • receipt.events[n].args - 解析后的参数
  • receipt.events[n].decode - 可以用来解析日志主题(topics)和数据的方法(用于计算args)
  • receipt.events[n].event - 事件的名称
  • receipt.events[n].eventSignature - 这个事件完整的签名
  • receipt.removeListener() - 用于移除触发此事件的监听器的方法
  • receipt.getBlock() - 返回发生触发此事件的Block
  • receipt.getTransaction() - 返回发生触发此事件的Transaction
  • receipt.getTransactionReceipt() - 返回发生触发此事件的Transaction Receipt

Write Methods Analysis

在不实际执行的情况下,有几个选项可以分析write method的属性和结果。

contract.estimateGas.METHOD_NAME( ...args [ , overrides ] ) Promise< 大数(BigNumber) >

返回使用 argsoverrides执行METHOD_NAME所需的gas的估计单位。

overrides与上面针对只读或写方法的overrides相同,具体取决于 METHOD_NAME调用的类型。

contract.populateTransaction.METHOD_NAME( ...args [ , overrides ] ) Promise< UnsignedTx >

返回一个未签名交易(UnsignedTransaction),它表示需要签名并提交给网络的交易,以执行带有argsoverridesMETHOD_NAME

overrides与上面针对只读或写方法的overrides相同,具体取决于 METHOD_NAME调用的类型。

contract.callStatic.METHOD_NAME( ...args [ , overrides ] ) Promise< any >

比起执行交易状态更改,更可能是会要求节点尝试调用不进行状态的更改并返回结果。

这实际上并不改变任何状态,而是免费的。在某些情况下,这可用于确定交易是失败还是成功。

otherwise函数与Read-Only Method相同。

overrides与上面的只读操作相同。

Event Filters

事件过滤器由主题(topics)组成,这些主题是Bloom Filter中记录的值,允许对匹配过滤器的条目进行有效搜索。

contract.filters.EVENT_NAME( ...args ) Filter

返回EVENT_NAME的过滤器,可以通过增加其他约束进行过滤。

只有indexed索引的事件参数可以被过滤。如果参数为空(或未提供),则该字段中的任何值都匹配。

ContractFactory

去部署一个合约(Contract),需要附加一些不作用于Contract对象本身上的信息。

要注意,合约的字节码(更确切地说是初始化代码)是必需的。

Contract Factory发送一个特殊类型的交易,一个initcode交易(即,to字段是空的,data字段是initcode), 其中initcode将被分析计算,结果成为新代码部署为一个新的合约。

创建实例

new ethers.ContractFactory( interface , bytecode [ , signer ] )

为接口和字节码的初始化代码所描述的合约创建一个ContractFactory的新实例。

ContractFactory.fromSolidity( compilerOutput [ , signer ] ) ContractFactory

使用Solidity编译器的输出,从中提取ABI和字节码,允许solc编译器在其生命周期内输出任何格式。

contractFactory.connect( signer ) ContractFactory

返回ContractFactory的一个新实例,该实例具有相同的接口和字节码,但signer不一样。

属性

contractFactory.interface Interface
contractFactory.bytecode string< DataHexString >

这个ContractFactory部署合约是需要用到的字节码(即 initcode)。

contractFactory.signer Signer

这个ContractFactory用这个Signer(如果有的话)将合约的实例部署到链上。

方法

contractFactory.attach( address ) 合约(Contract)

返回附有地址的(合约)实例。这与使用带有地址addressContract constructor构造函数是一样的, 在创建ContractFactory时会传入的interface和signerOrProvider。

contractFactory.getDeployTransaction( ...args [ , overrides ] ) 未签名交易(UnsignedTransaction)

返回一个未签名的交易,该交易将会具有部署合约时,传递给Contract的构造函数的args参数。

如果overrides被指定了,它们可以用来用来对某些变量进行覆盖赋值、如value, 交易的 nonce, gasLimitgasPrice

contractFactory.deploy( ...args [ , overrides ] ) Promise< 合约(Contract) >

使用signer去部署带有args参数传入构造函数的合约,并返回一个Contract,该Contract有address属性,一旦这个交易所在区块被挖出,address属性表示的就是这个合约的地址。

这个交易可以在contract.deployTransaction中找到,在挖出这个交易之前不应该进行交互。

如果overrides被指定了,它们可以用来用来对某些变量进行覆盖赋值、如value, 交易的 nonce, gasLimitgasPrice

部署合约
// 如果合约构造函数需要参数,ABI必须包含构造函数constructor const abi = [ "constructor(address owner, uint256 initialValue)", "function value() view returns (uint)" ]; // 我们使用factory来部署合约 factory = new ContractFactory(abi, bytecode, signer) // 部署合约的实例 contract = await factory.deploy("ricmoo.eth", 42); // 地址直接可以得到,但是记住合约还没有被部署 contract.address // '0x308e0b8A8e45F6Afd553D822f9e00CeF49e84e98' // signer发送的部署合约的交易 contract.deployTransaction // { // accessList: [], // chainId: 31337, // confirmations: 0, // data: '0x608060405234801561001057600080fd5b5060405161012e38038061012e8339818101604052604081101561003357600080fd5b81019080805190602001909291908051906020019092919050505081600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508060008190555050506088806100a66000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80633fa4f24514602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000805490509056fea2646970667358221220926465385af0e8706644e1ff3db7161af699dc063beaadd55405f2ccd6478d7564736f6c634300070400330000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca000000000000000000000000000000000000000000000000000000000000002a', // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // gasLimit: { BigNumber: "129862" }, // gasPrice: null, // hash: '0xa989298cda1e2e6f2ccf6233a8fc44c6307cae804abaecdd9ea0fa8ca8ce6745', // maxFeePerGas: { BigNumber: "1640416584" }, // maxPriorityFeePerGas: { BigNumber: "1500000000" }, // nonce: 0, // r: '0x23c9f76e74fd69faece058562df4713c912b40432d3ed8eae6f8fb051575b54c', // s: '0x5cda1af84f57ad7fea9ffd13aa8563c517a6c9d8eb9dbd6a62240bd54f5f5d7a', // to: null, // type: 2, // v: 0, // value: { BigNumber: "0" }, // wait: [Function] // } // 在交易被挖出来之前(即合约被部署)等待(Wait) // - 返回receipt // - throws on failure (reciept发生错误) await contract.deployTransaction.wait() // { // blockHash: '0x56cbcf7135f587427d79cbf66b5eaf603e11a4feefd19d801e04920ca9fc5482', // blockNumber: 21, // byzantium: true, // confirmations: 1, // contractAddress: '0x308e0b8A8e45F6Afd553D822f9e00CeF49e84e98', // cumulativeGasUsed: { BigNumber: "129862" }, // effectiveGasPrice: { BigNumber: "1561444542" }, // events: [], // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // gasUsed: { BigNumber: "129862" }, // logs: [], // logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', // status: 1, // to: null, // transactionHash: '0xa989298cda1e2e6f2ccf6233a8fc44c6307cae804abaecdd9ea0fa8ca8ce6745', // transactionIndex: 0, // type: 2 // } // 现在合约时可以用来交互了 await contract.value() // { BigNumber: "42" }

Example: ERC-20 Contract

元类(Meta-Classes)的概念有些令人困惑,因此我们将介绍一个简短的例子。

元类是在运行时(run-time)定义的类。合约是由应用程序二进制接口(ABI)指定的,它描述了它所拥有的方法和事件。 这些描述是合约(Contract)对象处于run-time时传入的,它创建一个新的Class,在run-time时添加了ABI中定义的所有方法。

部署合约

大多数情况下,您需要与之交互的任何合约都已经部署到区块链上,但对于本例需要先部署合约。

new ethers.ContractFactory( abi , bytecode , signer )

创建一个新的ContractFactory,它可以将合约部署到区块链。

const bytecode = "0x608060405234801561001057600080fd5b506040516103bc3803806103bc83398101604081905261002f9161007c565b60405181815233906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a333600090815260208190526040902055610094565b60006020828403121561008d578081fd5b5051919050565b610319806100a36000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063313ce5671461005157806370a082311461006557806395d89b411461009c578063a9059cbb146100c5575b600080fd5b604051601281526020015b60405180910390f35b61008e610073366004610201565b6001600160a01b031660009081526020819052604090205490565b60405190815260200161005c565b604080518082018252600781526626bcaa37b5b2b760c91b6020820152905161005c919061024b565b6100d86100d3366004610222565b6100e8565b604051901515815260200161005c565b3360009081526020819052604081205482111561014b5760405162461bcd60e51b815260206004820152601a60248201527f696e73756666696369656e7420746f6b656e2062616c616e6365000000000000604482015260640160405180910390fd5b336000908152602081905260408120805484929061016a9084906102b6565b90915550506001600160a01b0383166000908152602081905260408120805484929061019790849061029e565b90915550506040518281526001600160a01b0384169033907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a350600192915050565b80356001600160a01b03811681146101fc57600080fd5b919050565b600060208284031215610212578081fd5b61021b826101e5565b9392505050565b60008060408385031215610234578081fd5b61023d836101e5565b946020939093013593505050565b6000602080835283518082850152825b818110156102775785810183015185820160400152820161025b565b818111156102885783604083870101525b50601f01601f1916929092016040019392505050565b600082198211156102b1576102b16102cd565b500190565b6000828210156102c8576102c86102cd565b500390565b634e487b7160e01b600052601160045260246000fdfea2646970667358221220d80384ce584e101c5b92e4ee9b7871262285070dbcd2d71f99601f0f4fcecd2364736f6c63430008040033"; // 一个人类可读(Human-Readable)的ABI;我们只需要指定相关的部分内容,在这里我们用到了构造函数 const abi = [ "constructor(uint totalSupply)" ]; const factory = new ethers.ContractFactory(abi, bytecode, signer) // 部署,设置token的total supply为100,分配给部署人(deployer) const contract = await factory.deploy(parseUnits("100")); //链上现在虽然我们还没有这个合约,但是这个合约地址我们是预先经过计算后得到的 contract.address // '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC' // 在交易被挖出来之前(即合约被部署)等待(Wait) // - 部署后,交易会返回receipt await contract.deployTransaction.wait(); // { // blockHash: '0xe9b244958d066490c46a826a9733c1a43210316777185353b3ecbc2ec362ea87', // blockNumber: 22, // byzantium: true, // confirmations: 1, // contractAddress: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // cumulativeGasUsed: { BigNumber: "250842" }, // effectiveGasPrice: { BigNumber: "1553830469" }, // events: [ // { // address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // blockHash: '0xe9b244958d066490c46a826a9733c1a43210316777185353b3ecbc2ec362ea87', // blockNumber: 22, // data: '0x0000000000000000000000000000000000000000000000056bc75e2d63100000', // getBlock: [Function], // getTransaction: [Function], // getTransactionReceipt: [Function], // logIndex: 0, // removeListener: [Function], // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000000000000000000000000000000000000000000000', // '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759' // ], // transactionHash: '0x03164a2299c8334178d5a2c731149910d5c21db0309bfbc8aae89a34c8069ad4', // transactionIndex: 0 // } // ], // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // gasUsed: { BigNumber: "250842" }, // logs: [ // { // address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // blockHash: '0xe9b244958d066490c46a826a9733c1a43210316777185353b3ecbc2ec362ea87', // blockNumber: 22, // data: '0x0000000000000000000000000000000000000000000000056bc75e2d63100000', // logIndex: 0, // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000000000000000000000000000000000000000000000', // '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759' // ], // transactionHash: '0x03164a2299c8334178d5a2c731149910d5c21db0309bfbc8aae89a34c8069ad4', // transactionIndex: 0 // } // ], // logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000080000000000000000000000000000000000000000020000000000000000000800000000000000000002000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000002000000000000000000000000000000000000000000000000000020000000800008000000000000000000000000000000200000000000000000000000', // status: 1, // to: null, // transactionHash: '0x03164a2299c8334178d5a2c731149910d5c21db0309bfbc8aae89a34c8069ad4', // transactionIndex: 0, // type: 2 // }

连接到合约

ERC20Contract inherits 合约(Contract)

new ethers.Contract( address , abi , providerOrSigner )

创建一个Contract的新实例,是通过在区块链上指定这个合约的地址、以及它的abi(用于填充类的方法)和providerOrSigner来连接到的。

如果给定了Provider,那么合约只有只读访问权限,而Signer则提供了对状态操作方法的访问权限。

// 一个人类可读(Human-Readable)的ABI;用来与合约交互,必须要包含我们要具体使用的函数内容 const abi = [ // Read-Only Functions "function balanceOf(address owner) view returns (uint256)", "function decimals() view returns (uint8)", "function symbol() view returns (string)", // Authenticated Functions "function transfer(address to, uint amount) returns (bool)", // Events "event Transfer(address indexed from, address indexed to, uint amount)" ]; // 可以是 address 也可以是 一个 ENS name const address = "0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC"; // 只读; 通过 Provider连接,允许以下操作: // - 访问常量函数 constant function // - 查询过滤器 // - 对于 non-constant methods 填充未签名的交易 // - 对于non-constant估算Gas(作为匿名发送者) // - 对于non-constant methods静态调用 (作为匿名发送者) const erc20 = new ethers.Contract(address, abi, provider); // 可读写; 通过Signer连接,允许以下操作: // - 任何只读的操作 (作为Signer而非匿名者) // - 为non-constant functions发送交易 const erc20_rw = new ethers.Contract(address, abi, signer);

属性(inheritted from 合约(Contract))

erc20.address string< 地址(Address) >

合约被创建的 地址address (或 ENS name)

erc20.resolvedAddress string< 地址(Address) >

这是一个解析到合约对象的地址的promise。 如果是地址(Address)提供给构造函数,结果就是这个地址(Address); 如果提供的是ENS name,结果就是解析后地址。

erc20.deployTransaction TransactionResponse

如果Contract对象是ContractFactory部署的得到的,在这里的返回的结果就是用于部署合约的交易。

erc20.interface Interface

这是作为Interface的ABI。

erc20.provider Provider

如果一个provider被提供给构造函数,这条语句的结果就是provider; 如果提供了的Provider是一个signer,结果就是signer。

erc20.signer Signer

如果是一个signer提供给构造函数,结果就是signer。

Methods(inheritted from 合约(Contract))

erc20.attach( addressOrName ) 合约(Contract)

返回一个附在新地址的合约的新实例。 如果网络上有多个类似或相同的合约副本,并且您希望与它们中的每一个进行交互,那么这是非常有用的。

erc20.connect( providerOrSigner ) 合约(Contract)

返回合约的新实例,但需要连接provider或者Signer。

通过传入一个Provider,这将返回一个低级的合约,它只有只读访问(即常量调用)。

通过传入一个Signer。这将返回一个代表该signer的合约

erc20.deployed( ) Promise< Contract >
Contract.isIndexed( value ) boolean

事件(Events)(inheritted from 合约(Contract))

关于使用事件的例子请参考Meta-Class Filters

erc20.queryFilter( event [ , fromBlockOrBlockHash [ , toBlock ] ) Promise< Array< Event > >

返回匹配event参数的事件。

erc20.listenerCount( [ event ] ) number

返回订阅该event的监听器数量。如果没有提供event,则返回所有事件的总数。

erc20.listeners( event ) Array< Listener >

返回订阅该event的监听器列表。

erc20.off( event , listener ) this

返回订阅该event的监听器列表。

erc20.on( event , listener ) this

监听event事件,当事件发生时,会调用listener函数。

erc20.once( event , listener ) this

监听event事件,当事件发生时,仅调用一次listener函数。

erc20.removeAllListeners( [ event ] ) this

取消所有订阅event事件的监听器。如果未提供event事件,则取消订阅所有事件的监听。

Meta-Class Methods(added at Runtime)

因为合约是元类,这里面可用的方法取决于传入合约的ABI。

erc20.decimals( [ overrides ] ) Promise< number >

返回此ERC-20 token所使用的小数位数。在前端界面,当从用户获取输入时,可以使用parseUnits转化后传入合约; 从合约获得token后可以通过[formatUnits](utils-formatunits]转化后再显示给用户。

await erc20.decimals(); // 18
erc20.balanceOf( owner [ , overrides ] ) Promise< 大数(BigNumber) >

返回持有这个ERC-20 token的持有者(owner)的余额。

await erc20.balanceOf(signer.getAddress()) // { BigNumber: "100000000000000000000" }
erc20.symbol( [ overrides ] ) Promise< string >

返回token的symbol。

await erc20.symbol(); // 'MyToken'
erc20_rw.transfer( target , amount [ , overrides ] ) Promise< TransactionResponse >

从当前的signer将数量为amount的tokens转给接收者target。 在交易处于写入操作时,返回值(布尔类型)是得不到的。 如果需要这个值,则需要其他方法(如事件)。链上合约调用transfer函数可以得到这个结果。

// 在这之前先转化格式 formatUnits(await erc20_rw.balanceOf(signer.getAddress())); // '100.0' // 转 1.23 tokens 到 ENS name 为 "ricmoo.eth" 的地址 tx = await erc20_rw.transfer("ricmoo.eth", parseUnits("1.23")); // { // accessList: [], // chainId: 31337, // confirmations: 0, // data: '0xa9059cbb0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca0000000000000000000000000000000000000000000000001111d67bb1bb0000', // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // gasLimit: { BigNumber: "51558" }, // gasPrice: null, // hash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600', // maxFeePerGas: { BigNumber: "1607660938" }, // maxPriorityFeePerGas: { BigNumber: "1500000000" }, // nonce: 2, // r: '0x33284b92f1197507d7bd48aee7ccf4f7386527e72b6bf3fe23245026cf0a7eac', // s: '0x64eb0ca43292a98695f702ed0d59ec1d97618905c3366ac1d4ff50841f4cd0fb', // to: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // type: 2, // v: 1, // value: { BigNumber: "0" }, // wait: [Function] // } // 等待交易所在的区块被挖出打包 await tx.wait(); // { // blockHash: '0x95ac5d8669e6c73c1784296c0ad09c3ccf3e06055c78361056d971c6498171f1', // blockNumber: 23, // byzantium: true, // confirmations: 1, // contractAddress: null, // cumulativeGasUsed: { BigNumber: "51558" }, // effectiveGasPrice: { BigNumber: "1547214185" }, // events: [ // { // address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // args: [ // '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // '0x5555763613a12D8F3e73be831DFf8598089d3dCa', // { BigNumber: "1230000000000000000" }, // amount: { BigNumber: "1230000000000000000" }, // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa' // ], // blockHash: '0x95ac5d8669e6c73c1784296c0ad09c3ccf3e06055c78361056d971c6498171f1', // blockNumber: 23, // data: '0x0000000000000000000000000000000000000000000000001111d67bb1bb0000', // decode: [Function], // event: 'Transfer', // eventSignature: 'Transfer(address,address,uint256)', // getBlock: [Function], // getTransaction: [Function], // getTransactionReceipt: [Function], // logIndex: 0, // removeListener: [Function], // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759', // '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca' // ], // transactionHash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600', // transactionIndex: 0 // } // ], // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // gasUsed: { BigNumber: "51558" }, // logs: [ // { // address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // blockHash: '0x95ac5d8669e6c73c1784296c0ad09c3ccf3e06055c78361056d971c6498171f1', // blockNumber: 23, // data: '0x0000000000000000000000000000000000000000000000001111d67bb1bb0000', // logIndex: 0, // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759', // '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca' // ], // transactionHash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600', // transactionIndex: 0 // } // ], // logsBloom: '0x00000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000080000000000000000000000000000000000000000000000000000000000000000000000000000000002000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000002000000000000000000000000000000000000000000000000000000000000800008000001000000000200000000000000200000000000000000000000', // status: 1, // to: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // transactionHash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600', // transactionIndex: 0, // type: 2 // } // 成功后转化格式 formatUnits(await erc20_rw.balanceOf(signer.getAddress())); // '98.77' formatUnits(await erc20_rw.balanceOf("ricmoo.eth")); // '1.23'
erc20.callStatic.transfer( target , amount [ , overrides ] ) Promise< boolean >

执行一次从当前signer向target地址转移amount数量的token的演练,而不实际签名或发送交易。

这可以用于检查真实的转账前,交易是否能成功。

// signer有足够的tokens发送的话,结果会返回true await erc20_rw.callStatic.transfer("ricmoo.eth", parseUnits("1.23")); // true // 一个随机的地址不会有足够的tokens用于发送,在这个例子里会抛出错误 erc20_random = erc20_rw.connect(randomWallet); await erc20_random.callStatic.transfer("ricmoo.eth", parseUnits("1.23")); // [Error: call revert exception; VM Exception while processing transaction: reverted with reason string "insufficient token balance" [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ]] { // address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // args: [ // 'ricmoo.eth', // { BigNumber: "1230000000000000000" } // ], // code: 'CALL_EXCEPTION', // data: '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001a696e73756666696369656e7420746f6b656e2062616c616e6365000000000000', // errorArgs: [ // 'insufficient token balance' // ], // errorName: 'Error', // errorSignature: 'Error(string)', // method: 'transfer(address,uint256)', // reason: 'insufficient token balance', // transaction: { // data: '0xa9059cbb0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca0000000000000000000000000000000000000000000000001111d67bb1bb0000', // from: '0x3744A6A188e46e3f2A10f918126d13F73E3C58D9', // to: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC' // } // }
erc20.estimateGas.transfer( target , amount [ , overrides ] ) Promise< 大数(BigNumber) >

返回估计的“将amount数量的tokens发送target地址”所需的多少gas值。

await erc20_rw.estimateGas.transfer("ricmoo.eth", parseUnits("1.23")); // { BigNumber: "34458" }
erc20.populateTransaction.transfer( target , amount [ , overrides ] ) Promise< UnsignedTx >

返回一个未签名交易(UnsignedTransaction),它可以被签名并提交给网络,达成“将amount数量的tokens发送target地址”的目的。

await erc20_rw.populateTransaction.transfer("ricmoo.eth", parseUnits("1.23")); // { // data: '0xa9059cbb0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca0000000000000000000000000000000000000000000000001111d67bb1bb0000', // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // to: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC' // }
关于估算和静态调用的注意事项

当你执行一个静态调用时,以太坊会尽可能地考虑当前状态。 在许多情况下,这可能会提供假阳性和假阴性。 The eventually consistent model of the blockchain also means there are certain consistency modes that cannot be known until an actual transaction is attempted.

Meta-Class Filters(added at Runtime)

因为合约是元类,这里面可用的方法取决于传入合约的ABI。

erc20.filters.Transfer( [ fromAddress [ , toAddress ] ] ) Filter

返回一个新的Filter用于查询或者去subscribe/unsubscribe to events

如果fromAddress 是空的或者没有填写,将匹配任何发送人的地址。 如果 toAddress是空的或者没有填写,将匹配任何接受人的地址。

query filter *from* events
filterFrom = erc20.filters.Transfer(signer.address); // { // address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759' // ] // } // 在最新的10个区块内查询我发送的交易 logsFrom = await erc20.queryFilter(filterFrom, -10, "latest"); // [ // { // address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // args: [ // '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // '0x5555763613a12D8F3e73be831DFf8598089d3dCa', // { BigNumber: "1230000000000000000" }, // amount: { BigNumber: "1230000000000000000" }, // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa' // ], // blockHash: '0x95ac5d8669e6c73c1784296c0ad09c3ccf3e06055c78361056d971c6498171f1', // blockNumber: 23, // data: '0x0000000000000000000000000000000000000000000000001111d67bb1bb0000', // decode: [Function], // event: 'Transfer', // eventSignature: 'Transfer(address,address,uint256)', // getBlock: [Function], // getTransaction: [Function], // getTransactionReceipt: [Function], // logIndex: 0, // removeListener: [Function], // removed: false, // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759', // '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca' // ], // transactionHash: '0xceb49c9db1d35d18db9b3c56d285e691f006d62529dec397eec004337206e600', // transactionIndex: 0 // } // ] // 请注意,args提供了事件的详细信息,每个参数都是可用的,这是因为我们的ABI包含了所有参数的名称 logsFrom[0].args // [ // '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // '0x5555763613a12D8F3e73be831DFf8598089d3dCa', // { BigNumber: "1230000000000000000" }, // amount: { BigNumber: "1230000000000000000" }, // from: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // to: '0x5555763613a12D8F3e73be831DFf8598089d3dCa' // ]
query filter with *to* events
filterTo = erc20.filters.Transfer(null, signer.address); // { // address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // null, // '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759' // ] // } // 在最新的10个区块内查询发送给我的交易 // 注意: 这是因为合约在部署时,构造函数写了将totalSupply tokens转给我 logsTo = await erc20.queryFilter(filterTo, -10, "latest"); // [ // { // address: '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC', // args: [ // '0x0000000000000000000000000000000000000000', // '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // { BigNumber: "100000000000000000000" }, // amount: { BigNumber: "100000000000000000000" }, // from: '0x0000000000000000000000000000000000000000', // to: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759' // ], // blockHash: '0xe9b244958d066490c46a826a9733c1a43210316777185353b3ecbc2ec362ea87', // blockNumber: 22, // data: '0x0000000000000000000000000000000000000000000000056bc75e2d63100000', // decode: [Function], // event: 'Transfer', // eventSignature: 'Transfer(address,address,uint256)', // getBlock: [Function], // getTransaction: [Function], // getTransactionReceipt: [Function], // logIndex: 0, // removeListener: [Function], // removed: false, // topics: [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000000000000000000000000000000000000000000000', // '0x000000000000000000000000894ed91b666facce5a4d2ff8261924b4754a5759' // ], // transactionHash: '0x03164a2299c8334178d5a2c731149910d5c21db0309bfbc8aae89a34c8069ad4', // transactionIndex: 0 // } // ] // 请注意,args提供了事件的详细信息,每个参数都是可用的,这是因为我们的ABI包含了所有参数的名称 logsTo[0].args // [ // '0x0000000000000000000000000000000000000000', // '0x894ed91B666FacCe5a4D2FF8261924b4754A5759', // { BigNumber: "100000000000000000000" }, // amount: { BigNumber: "100000000000000000000" }, // from: '0x0000000000000000000000000000000000000000', // to: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759' // ]
listen for events
// 监听signer的发送的event事件: erc20.on(filterFrom, (from, to, amount, event) => { // The `from` will always be the signer address }); // 监听signer的接受转账的event事件: erc20.on(filterTo, (from, to, amount, event) => { // The `to` will always be the signer address }); // 监听所有的转账交易event事件: erc20.on("Transfer", (from, to, amount, event) => { // ... });

实用工具 Utilities

这些实用工具(utilities)在库中被广泛使用,对DAPP开发人员非常有用。

应用程序二进制接口
地址(Addresses)
大数(BigNumber)
字节处理
常量(contants)
显示逻辑和输入
编码工具
FixedNumber
哈希算法
HD 钱包
日志
Property Utilities
签名密钥
字符串(Strings)
交易
Web Utilities
Wordlists(词表)

应用程序二进制接口

应用程序二进制接口 (ABI)是一个Fragments的集合,它指定了如何与合约的各种组件交互。

Interface可以按类型组织Fragments,并可以具有对每个组件进行编码、解码等功能。

大多数开发人员可能不需要这种对链上二进制数据进行编码和解码的低级访问,而最有可能使用(Contract),它提供了一个更方便的接口。 一些框架和工具开发者或使用高级技术的开发者可能会发现这些类(classes)和实用工具(utilities)很有帮助。

AbiCoder
ABI 格式
Fragments
Interface

AbiCoder

AbiCoder是编码器的集合,可用于在EVM和更高级别库之间通过二进制数据格式进行编码和解码的操作。

大多数开发人员不需要直接使用这个类,因为Interface类极大地简化了这些操作。

创建实例

在大多数情况下,都不需要手动创建AbiCoder的实例,因为当库被加载时,一个实例是用默认的强制函数创建的,它可以被普遍使用。

这可能只有那些有特殊需求的人才需要,通过二进制格式解码后将值进行强制更改。

This is likely only needed by those with specific needs to override how values are coerced after they are decoded from their binary format.

new ethers.utils.AbiCoder( [ coerceFunc ] )

创建一个新的AbiCoder实例,在每一个需要解码的地方都会调用coerceFunc,调用的结果将在Result中使用。

函数签名是`(type, value)`,type字段是是描述类型的字符串,value 字段是来自底层Coder的处理过的值。

如果回调抛出错误,Result将包含一个被访问时将抛出的属性,从而允许更高级别的库从数据错误中恢复。

ethers.utils.defaultAbiCoder AbiCoder

Interface使用的库在被导入时将创建一个AbiCoder

Coding Methods

abiCoder.encode( types , values ) string< DataHexString >

根据数组中的types对数组values进行编码,每个类型可以是字符串或ParamType

// 对简单的类型进行编码 abiCoder.encode([ "uint", "string" ], [ 1234, "Hello World" ]); // '0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000' // 对数组的类型进行编码 abiCoder.encode([ "uint[]", "string" ], [ [ 1234, 5678 ] , "Hello World" ]); // '0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004d2000000000000000000000000000000000000000000000000000000000000162e000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000' // 对复杂的结构体进行编码 (使用 positional 属性) abiCoder.encode( [ "uint", "tuple(uint256, string)" ], [ 1234, [ 5678, "Hello World" ] ] ); // '0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000162e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000' // 对复杂的结构体进行编码 (使用 keyword 属性) abiCoder.encode( [ "uint a", "tuple(uint256 b, string c) d" ], [ 1234, { b: 5678, c: "Hello World" } ] ); // '0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000162e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000'
abiCoder.decode( types , data ) Result

根据数组中的typesdata进行编码,每个类型可以是字符串或ParamType

// 对简单的类型进行解码 data = "0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000"; abiCoder.decode([ "uint", "string" ], data); // [ // { BigNumber: "1234" }, // 'Hello World' // ] // 对数组的类型进行解码 data = "0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004d2000000000000000000000000000000000000000000000000000000000000162e000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000"; abiCoder.decode([ "uint[]", "string" ], data); // [ // [ // { BigNumber: "1234" }, // { BigNumber: "5678" } // ], // 'Hello World' // ] // 对复杂的结构体进行解码; 参数如果未命名,只允许按照位置(positional)方式去访问值(values) data = "0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000162e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000"; abiCoder.decode([ "uint", "tuple(uint256, string)" ], data); // [ // { BigNumber: "1234" }, // [ // { BigNumber: "5678" }, // 'Hello World' // ] // ] // 对复杂的结构体进行解码; 参数如果已命名,可通过位置(positional)方式和关键词(keyword)方式去访问值(values) abiCoder.decode([ "uint a", "tuple(uint256 b, string c) d" ], data); // [ // { BigNumber: "1234" }, // [ // { BigNumber: "5678" }, // 'Hello World', // b: { BigNumber: "5678" }, // c: 'Hello World' // ], // a: { BigNumber: "1234" }, // d: [ // { BigNumber: "5678" }, // 'Hello World', // b: { BigNumber: "5678" }, // c: 'Hello World' // ] // ]

ABI 格式

有几种格式可以用来为智能合约指定ABI,它将向底层库指定存在哪些方法、事件和错误, 以便库可以处理网络上接受的和发送出去的数据的编码和解码。

所支持的 ABI 类型有:

Human-Readable ABI

Human-Readable ABI 是早期被 ethers 所描述提出的, 它允许使用一个Solidity式签名(Solidity signature)来描述每个方法(method)、事件(event)和错误(error)。

需要注意的是,一个Solidity式签名完整地描述了ABI需要的全部属性:

这样就实现了一种简单的方式既是机器可读的(因为解析器是一台机器),也是人类可读的(至少是开发人员可读的), 而且便于人们编写嵌入到代码中,提高了代码的可读性。 Human-Readable ABI也相当小,有助于减少代码大小。

Huamn-Readable ABI是一个简单的字符串数组,其中每个字符串都是Solidity式签名。

签名可以被最小限度地指定(即输入和输出的名称可以被省略),也可以被完全指定(即所有的属性名称),并且空格会被忽略。

Solidity中的几个修饰符在ethers内部被摒弃了,因为ABI不需要,在Solidity的语义检查系统(semantic checking system)使用的是旧修饰符,比如输入参数数据"calldata""memory"

Human-Readable ABI 例子
const humanReadableAbi = [ // 简易的类型 "constructor(string symbol, string name)", "function transferFrom(address from, address to, uint value)", "function balanceOf(address owner) view returns (uint balance)", "event Transfer(address indexed from, address indexed to, address value)", "error InsufficientBalance(account owner, uint balance)", // 在一些带结构体类型的例子中,我们使用元组关键字(tuple keyword): // 注意,元组关键字是可选的,简单地使用花括号就可以完成 // struct Person { // string name; // uint16 age; // } "function addPerson(tuple(string name, uint16 age) person)", "function addPeople(tuple(string name, uint16 age)[] person)", "function getPerson(uint id) view returns (tuple(string name, uint16 age))", "event PersonAdded(uint indexed id, tuple(string name, uint16 age) person)" ];

Solidity JSON ABI

Solidity JSON ABI是许多工具导出的标准格式,包括Solidity编译器。 有关完整规范,请参阅Solidity compiler documentation

不同版本的键和值略有不同。例如,早期的编译器只包含一个布尔值"constant"来表示可变性(mutability), 而新版本包含了一个字符串"mutabilityState",它包含了几个旧的属性。

当使用JSON ABI创建一个Fragment实例时,它会自动为new-age ABIs推断出所有的legacy properties, 而对于legacy ABIs将推断出new-age properties。 所有属性都将被填充,因此它与用Human-Readable ABI fragment创建是等价的。

The same ABI as JSON ABI
const jsonAbi = `[ { "type": "constructor", "payable": false, "inputs": [ { "type": "string", "name": "symbol" }, { "type": "string", "name": "name" } ] }, { "type": "function", "name": "transferFrom", "constant": false, "payable": false, "inputs": [ { "type": "address", "name": "from" }, { "type": "address", "name": "to" }, { "type": "uint256", "name": "value" } ], "outputs": [ ] }, { "type": "function", "name": "balanceOf", "constant":true, "stateMutability": "view", "payable":false, "inputs": [ { "type": "address", "name": "owner"} ], "outputs": [ { "type": "uint256"} ] }, { "type": "event", "anonymous": false, "name": "Transfer", "inputs": [ { "type": "address", "name": "from", "indexed":true}, { "type": "address", "name": "to", "indexed":true}, { "type": "address", "name": "value"} ] }, { "type": "error", "name": "InsufficientBalance", "inputs": [ { "type": "account", "name": "owner"}, { "type": "uint256", "name": "balance"} ] }, { "type": "function", "name": "addPerson", "constant": false, "payable": false, "inputs": [ { "type": "tuple", "name": "person", "components": [ { "type": "string", "name": "name" }, { "type": "uint16", "name": "age" } ] } ], "outputs": [] }, { "type": "function", "name": "addPeople", "constant": false, "payable": false, "inputs": [ { "type": "tuple[]", "name": "person", "components": [ { "type": "string", "name": "name" }, { "type": "uint16", "name": "age" } ] } ], "outputs": [] }, { "type": "function", "name": "getPerson", "constant": true, "stateMutability": "view", "payable": false, "inputs": [ { "type": "uint256", "name": "id" } ], "outputs": [ { "type": "tuple", "components": [ { "type": "string", "name": "name" }, { "type": "uint16", "name": "age" } ] } ] }, { "type": "event", "anonymous": false, "name": "PersonAdded", "inputs": [ { "type": "uint256", "name": "id", "indexed": true }, { "type": "tuple", "name": "person", "components": [ { "type": "string", "name": "name", "indexed": false }, { "type": "uint16", "name": "age", "indexed": false } ] } ] } ]`;

Solidity Object ABI

使用JSON.parse解析Solidity JSON ABI的结果完全与Interface类兼容, 且该对象的每个方法、事件和错误都与Fragment类兼容。

一些开发人员可能更喜欢这种方式,因为它允许将ABI属性作为普通的JavaScript对象访问,并且Solidity ABI与JSON ABI非常匹配。

在格式之间相互转换

Fragment对象使重新格式化单个方法、事件或错误变得简单,但是大多数开发人员会对转换整个ABI感兴趣。

对于生产环境下的代码,建议内联Human-Readable ABI 的方式,因为这样可以很容易地看到哪些方法、事件和错误是可用的。 还强烈建议去掉ABI中未使用的部分(如管理方法),以进一步减少代码大小。

转换成完整的 Human-Readable ABI
// 使用"full"格式将确保Result对象具有named属性,这将提高代码的可读性 const iface = new Interface(jsonAbi); iface.format(FormatTypes.full); // [ // 'constructor(string symbol, string name)', // 'function transferFrom(address from, address to, uint256 value)', // 'function balanceOf(address owner) view returns (uint256)', // 'event Transfer(address indexed from, address indexed to, address value)', // 'error InsufficientBalance(account owner, uint256 balance)', // 'function addPerson(tuple(string name, uint16 age) person)', // 'function addPeople(tuple(string name, uint16 age)[] person)', // 'function getPerson(uint256 id) view returns (tuple(string name, uint16 age))', // 'event PersonAdded(uint256 indexed id, tuple(string name, uint16 age) person)' // ]
Converting to Minimal Human-Readable ABI
//使用"minimal"格式将节省少量的空间,但通常不值得,因为在Results上的named属性将不可用 const iface = new Interface(jsonAbi); iface.format(FormatTypes.minimal); // [ // 'constructor(string,string)', // 'function transferFrom(address,address,uint256)', // 'function balanceOf(address) view returns (uint256)', // 'event Transfer(address indexed,address indexed,address)', // 'error InsufficientBalance(account,uint256)', // 'function addPerson(tuple(string,uint16))', // 'function addPeople(tuple(string,uint16)[])', // 'function getPerson(uint256) view returns (tuple(string,uint16))', // 'event PersonAdded(uint256 indexed,tuple(string,uint16))' // ]
转换成 JSON ABI
// 有时你可能需要将Human-Readable ABI 导出成JSON,因为有些工具不支持Human-Readable // 为了简洁紧密凑合,JSON保留了最少的空格 const iface = new Interface(humanReadableAbi); jsonAbi = iface.format(FormatTypes.json); // '[{"type":"constructor","payable":false,"inputs":[{"type":"string","name":"symbol"},{"type":"string","name":"name"}]},{"type":"function","name":"transferFrom","constant":false,"payable":false,"inputs":[{"type":"address","name":"from"},{"type":"address","name":"to"},{"type":"uint256","name":"value"}],"outputs":[]},{"type":"function","name":"balanceOf","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"owner"}],"outputs":[{"type":"uint256","name":"balance"}]},{"type":"event","anonymous":false,"name":"Transfer","inputs":[{"type":"address","name":"from","indexed":true},{"type":"address","name":"to","indexed":true},{"type":"address","name":"value"}]},{"type":"error","name":"InsufficientBalance","inputs":[{"type":"account","name":"owner"},{"type":"uint256","name":"balance"}]},{"type":"function","name":"addPerson","constant":false,"payable":false,"inputs":[{"type":"tuple","name":"person","components":[{"type":"string","name":"name"},{"type":"uint16","name":"age"}]}],"outputs":[]},{"type":"function","name":"addPeople","constant":false,"payable":false,"inputs":[{"type":"tuple[]","name":"person","components":[{"type":"string","name":"name"},{"type":"uint16","name":"age"}]}],"outputs":[]},{"type":"function","name":"getPerson","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"id"}],"outputs":[{"type":"tuple","components":[{"type":"string","name":"name"},{"type":"uint16","name":"age"}]}]},{"type":"event","anonymous":false,"name":"PersonAdded","inputs":[{"type":"uint256","name":"id","indexed":true},{"type":"tuple","name":"person","components":[{"type":"string","name":"name","indexed":false},{"type":"uint16","name":"age","indexed":false}]}]}]' // 结合格式化的参数去使用 JSON.parse 和 JSON.stringify可提高可读性 JSON.stringify(JSON.parse(jsonAbi), null, 2); // `[ // { // "type": "constructor", // "payable": false, // "inputs": [ // { // "type": "string", // "name": "symbol" // }, // { // "type": "string", // "name": "name" // } // ] // }, // { // "type": "function", // "name": "transferFrom", // "constant": false, // "payable": false, // "inputs": [ // { // "type": "address", // "name": "from" // }, // { // "type": "address", // "name": "to" // }, // { // "type": "uint256", // "name": "value" // } // ], // "outputs": [] // }, // { // "type": "function", // "name": "balanceOf", // "constant": true, // "stateMutability": "view", // "payable": false, // "inputs": [ // { // "type": "address", // "name": "owner" // } // ], // "outputs": [ // { // "type": "uint256", // "name": "balance" // } // ] // }, // { // "type": "event", // "anonymous": false, // "name": "Transfer", // "inputs": [ // { // "type": "address", // "name": "from", // "indexed": true // }, // { // "type": "address", // "name": "to", // "indexed": true // }, // { // "type": "address", // "name": "value" // } // ] // }, // { // "type": "error", // "name": "InsufficientBalance", // "inputs": [ // { // "type": "account", // "name": "owner" // }, // { // "type": "uint256", // "name": "balance" // } // ] // }, // { // "type": "function", // "name": "addPerson", // "constant": false, // "payable": false, // "inputs": [ // { // "type": "tuple", // "name": "person", // "components": [ // { // "type": "string", // "name": "name" // }, // { // "type": "uint16", // "name": "age" // } // ] // } // ], // "outputs": [] // }, // { // "type": "function", // "name": "addPeople", // "constant": false, // "payable": false, // "inputs": [ // { // "type": "tuple[]", // "name": "person", // "components": [ // { // "type": "string", // "name": "name" // }, // { // "type": "uint16", // "name": "age" // } // ] // } // ], // "outputs": [] // }, // { // "type": "function", // "name": "getPerson", // "constant": true, // "stateMutability": "view", // "payable": false, // "inputs": [ // { // "type": "uint256", // "name": "id" // } // ], // "outputs": [ // { // "type": "tuple", // "components": [ // { // "type": "string", // "name": "name" // }, // { // "type": "uint16", // "name": "age" // } // ] // } // ] // }, // { // "type": "event", // "anonymous": false, // "name": "PersonAdded", // "inputs": [ // { // "type": "uint256", // "name": "id", // "indexed": true // }, // { // "type": "tuple", // "name": "person", // "components": [ // { // "type": "string", // "name": "name", // "indexed": false // }, // { // "type": "uint16", // "name": "age", // "indexed": false // } // ] // } // ] // } // ]`

Fragments

对一个ABI进行描述解释。

Formats

JSON String ABI (Solidity 输出 JSON)

JSON ABI FormatSolidity 编译器输出的格式.

JSON序列化的对象总是一个字符串,它表示一个对象数组,其中每个对象都有描述ABIFragment 的各种属性。

对JSON字符串(一个普通的JavaScript对象)进行反序列化也可以传递给任何接受JSON string ABI的函数。

Humanb-Readable ABI

Human-Readable ABI本文中由ethers引入的,并依据得到了广泛的采用。

ABI是通过使用字符串数组来描述的,其中每个字符串都是构造函数函数事件错误的solididity式签名。

当解析一个片段(fragment) 时,所有的填写的属性都将被注入(例如,一个payable的函数方法将其constant属性设置为false)。

元组可以通过使用tuple(...)语法或使用空括号(...)来指定。

Example Human-Readable ABI
const ABI = [ // Constructor "constructor(address ens)", // Constant functions (pure or view) "function balanceOf(address owner) view returns (uint)", // State-mutating functions (payable or non-payable) "function mint(uint amount) payable", "function transfer(address to, uint amount) returns (bool)", // Events "event Transfer(address indexed from, address indexed to, uint amount)", // Errors "error InsufficientFunds(address from, uint balance)", ]

Output Formats

每个FragmentParamType可以使用其format方法输出。

ethers.utils.FormatTypes.full string

这是一个完整的人类可读(human-readable)的字符串,包括所有的参数名,任何可选的修饰符(例如,indexedpublic等)和空格,以提高代码可读性。

ethers.utils.FormatTypes.minimal string

这类似于full,除了没有不必要的空白或参数名。这对于存储最小的字符串非常有用,该字符串仍然可以使用Fragment&thinsp;.&thinsp;from, 从完全重构原始的Fragment。

ethers.utils.FormatTypes.json string

这将返回一个JavaScript对象,安全地调用JSON.stringify创建一个JSON字符串。

ethers.utils.FormatTypes.sighash string

一个最小的输出格式,Solidity在计算签名哈希或事件主题topic的哈希时使用它。

注意

sighash 格式不足以重新创建原始的Fragment,因为它舍弃了indexed, anonymous, stateMutability等修饰符。

它只用于为Fragment去计算 选择器(selector),不能用于格式化接口。

Fragment

一个ABI是Fragments的集合,每个fragment指定:

Properties

fragment.name string

事件或函数的name。如果是ConstructorFragment时为null。

fragment.type string

这是一个表示Fragment类型的字符串。如:

  • constructor
  • event
  • function

fragment.inputs Array< ParamType >

构造函数、事件等输入参数的ParamType的数组。

Methods

fragment.format( [ format = sighash ] ) string

使用可用的输出格式创建Fragment的字符串描述。

ethers.utils.Fragment.from( objectOrString ) Fragment

从任何兼容的bject或String创建一个新的Fragment子类。

ethers.utils.Fragment.isFragment( object ) boolean

如果object 是一个Fragment返回true。

ConstructorFragment inherits Fragment

Properties

fragment.gas 大数(BigNumber)

表示部署期间应该使用的gas limit,它可以是null。

fragment.payable boolean

表示构造函数在部署期间是否可以接收ether(例如:msg.value != 0)。

fragment.stateMutability string

构造函数的state mutability。它可以是:

  • nonpayable
  • payable

Methods

ethers.utils.ConstructorFragment.from( objectOrString ) ConstructorFragment

从任何兼容的object或String创建一个新的ConstructorFragment

ethers.utils.ConstructorFragment.isConstructorFragment( object ) boolean

如果object 是一个 ConstructorFragment返回true。

ErrorFragment inherits Fragment

Methods

ethers.utils.ErrorFragment.from( objectOrString ) ErrorFragment

从任何兼容的object或String创建一个新的ErrorFragment

ethers.utils.ErrorFragment.isErrorFragment( object ) boolean

如果object 是一个 ErrorFragment返回true。

EventFragment inherits Fragment

Properties

fragment.anonymous boolean

表示事件(event)是否匿名。匿名事件在创建日志时不会将其topic哈希值注入到topic0中。

Methods

ethers.utils.EventFragment.from( objectOrString ) EventFragment

从任何兼容的object或String创建一个新的EventFragment

ethers.utils.EventFragment.isEventFragment( object ) boolean

如果object 是一个 EventFragment返回true。

FunctionFragment inherits ConstructorFragment

Properties

fragment.constant boolean

表示函数是否为常量(即不改变状态)。如果设为true表示状态可变性是pureview

fragment.stateMutability string

构造器的状态可变性。它可以是:

  • nonpayable
  • payable
  • pure
  • view

fragment.outputs Array< ParamType >

函数输出参数的列表。

Methods

ethers.utils.FunctionFragment.from( objectOrString ) FunctionFragment

从任何兼容的object或String创建一个新的FunctionFragment

ethers.utils.FunctionFragment.isFunctionFragment( object ) boolean

如果object 是一个 FunctionFragment 返回true。

ParamType

下面的例子将表示Solidity中的参数:

string foobar

Properties

paramType.name string

本地参数名。对于未命名的参数,这个值为null。例如,参数字符串string foobar会输出foobar

paramType.type string

参数的完整类型,包括元组和数组符号。对于未命名的参数,这个值可能为null。对于上面的例子,将输出foobar

paramType.baseType string

参数的基类型(base type)。对于原始类型(例如address, uint256等),这等同于type。 对于数组,它将是string array,对于元组,它将是string tuple

paramType.indexed boolean

参数是否被标记为索引。这适用于参数是EventFragment的部分。

paramType.arrayChildren ParamType

数组的children类型。对于任何非数组的参数,这将是null。

paramType.arrayLength number

数组的长度,或动态数组的长度-1。对于不是数组的参数,这个值为null。

paramType.components Array< ParamType >

元组的组成部分。这对于非元组参数是null。

Methods

paramType.format( [ outputType = sighash ] )

使用可用的output formats创建Fragment的字符串描述。

ethers.utils.ParamType.from( objectOrString ) ParamType

从任何兼容的object或String创建一个新的ParamType

ethers.utils.ParamType.isParamType( object ) boolean

如果object 是一个ParamType返回true。

Interface

接口(Interface)类是以太坊网络上的与合约交互所需的编码和解码的一种抽象。

许多标准都是随着Solidity语言而发展的,其他语言也采用这种方式去与现有部署的合约的保持兼容。

EVM本身并不理解ABI是什么。它只是一组商定的格式,用于编码合约所需的各种类型的数据,以便它们可以相互交互。

创建实例

new ethers.utils.Interface( abi )

从表示abi的JSON字符串或对象创建一个新的接口

abi可以是JSON字符串或Solidity 编译器(或其他兼容语言)使用JSON.parse生产的对象。

abi 也可以是Human-Readable Abi, 它是Ethers创建的一种格式,以简化将abi手动输入到源文件中的操作, 这样就可以在同一个源文件中简单地引用Contract abi。

创建接口实例
// 接口用法示例如下 const iface = new Interface([ // 构造函数 "constructor(string symbol, string name)", // 会改变状态的方法 "function transferFrom(address from, address to, uint amount)", // 会改变状态的方法,是 payable 的(可接收以太币) "function mint(uint amount) payable", // 常量方法 (如 "view" 或 "pure") "function balanceOf(address owner) view returns (uint)", // 事件 "event Transfer(address indexed from, address indexed to, uint256 amount)", // 自定义的 Solidity Error "error AccountLocked(address owner, uint256 balance)", // 带有结构体类型的例子 "function addUser(tuple(string name, address addr) user) returns (uint id)", "function addUsers(tuple(string name, address addr)[] user) returns (uint[] id)", "function getUser(uint id) view returns (tuple(string name, address addr) user)" ]);

属性

interface.fragments Array< Fragment >

接口中所有的Fragments

interface.errors Array< ErrorFragment >

接口中所有的Error Fragments

interface.events Array< EventFragment >

接口中所有的Event Fragments

interface.functions Array< FunctionFragment >

接口中所有的Function Fragments

interface.deploy ConstructorFragment

接口中所有的Constructor Fragments

格式化

interface.format( [ format ] ) string | Array< string >

返回格式化的接口。如果格式类型是json,则返回一个字符串,否则返回一个由human-readable(人类可读)字符串组成的数组。

const FormatTypes = ethers.utils.FormatTypes; iface.format(FormatTypes.json) // '[{"type":"constructor","payable":false,"inputs":[{"type":"string","name":"symbol"},{"type":"string","name":"name"}]},{"type":"function","name":"transferFrom","constant":false,"payable":false,"inputs":[{"type":"address","name":"from"},{"type":"address","name":"to"},{"type":"uint256","name":"amount"}],"outputs":[]},{"type":"function","name":"mint","constant":false,"stateMutability":"payable","payable":true,"inputs":[{"type":"uint256","name":"amount"}],"outputs":[]},{"type":"function","name":"balanceOf","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"address","name":"owner"}],"outputs":[{"type":"uint256"}]},{"type":"event","anonymous":false,"name":"Transfer","inputs":[{"type":"address","name":"from","indexed":true},{"type":"address","name":"to","indexed":true},{"type":"uint256","name":"amount"}]},{"type":"error","name":"AccountLocked","inputs":[{"type":"address","name":"owner"},{"type":"uint256","name":"balance"}]},{"type":"function","name":"addUser","constant":false,"payable":false,"inputs":[{"type":"tuple","name":"user","components":[{"type":"string","name":"name"},{"type":"address","name":"addr"}]}],"outputs":[{"type":"uint256","name":"id"}]},{"type":"function","name":"addUsers","constant":false,"payable":false,"inputs":[{"type":"tuple[]","name":"user","components":[{"type":"string","name":"name"},{"type":"address","name":"addr"}]}],"outputs":[{"type":"uint256[]","name":"id"}]},{"type":"function","name":"getUser","constant":true,"stateMutability":"view","payable":false,"inputs":[{"type":"uint256","name":"id"}],"outputs":[{"type":"tuple","name":"user","components":[{"type":"string","name":"name"},{"type":"address","name":"addr"}]}]}]' iface.format(FormatTypes.full) // [ // 'constructor(string symbol, string name)', // 'function transferFrom(address from, address to, uint256 amount)', // 'function mint(uint256 amount) payable', // 'function balanceOf(address owner) view returns (uint256)', // 'event Transfer(address indexed from, address indexed to, uint256 amount)', // 'error AccountLocked(address owner, uint256 balance)', // 'function addUser(tuple(string name, address addr) user) returns (uint256 id)', // 'function addUsers(tuple(string name, address addr)[] user) returns (uint256[] id)', // 'function getUser(uint256 id) view returns (tuple(string name, address addr) user)' // ] iface.format(FormatTypes.minimal) // [ // 'constructor(string,string)', // 'function transferFrom(address,address,uint256)', // 'function mint(uint256) payable', // 'function balanceOf(address) view returns (uint256)', // 'event Transfer(address indexed,address indexed,uint256)', // 'error AccountLocked(address,uint256)', // 'function addUser(tuple(string,address)) returns (uint256)', // 'function addUsers(tuple(string,address)[]) returns (uint256[])', // 'function getUser(uint256) view returns (tuple(string,address))' // ]

Fragment Access

interface.getFunction( fragment ) FunctionFragment

返回fragmentFunctionFragment(参考Specifying Fragments)。

// 通过方法(method)的签名,这是经过标准化后的,因此空格和多余的属性会被舍去 iface.getFunction("transferFrom(address, address, uint256)"); // 通过name的方式;这只在方法名称是唯一确定的情况下才有效 iface.getFunction("transferFrom"); // 通过函数选择器 iface.getFunction("0x23b872dd"); // 如果方法不存在将抛出异常 iface.getFunction("doesNotExist()"); // [Error: no matching function] { // argument: 'signature', // code: 'INVALID_ARGUMENT', // reason: 'no matching function', // value: 'doesNotExist()' // }
interface.getError( fragment ) ErrorFragment

返回fragment的ErrorFragment(参考Specifying Fragments)。

// 通过错误(error)的签名,这是经过标准化后的,因此空格和多余的属性会被舍去 iface.getError("AccountLocked(address, uint256)"); // 通过name的方式;这只在方法名称是唯一确定的情况下才有效 iface.getError("AccountLocked"); // 通过error的选择器 iface.getError("0xf7c3865a"); // 如果方法不存在将抛出异常 iface.getError("DoesNotExist()"); // [Error: no matching error] { // argument: 'signature', // code: 'INVALID_ARGUMENT', // reason: 'no matching error', // value: 'DoesNotExist()' // }
interface.getEvent( fragment ) EventFragment

返回fragmentEventFragment(参考Specifying Fragments)。

// 通过事件(event)的签名,这是经过标准化后的,因此空格和多余的属性会被舍去 iface.getEvent("Transfer(address, address, uint256)"); // 通过name的方式;这只在方法名称是唯一确定的情况下才有效 iface.getEvent("Transfer"); // 通过事件topic哈希 iface.getEvent("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"); // 如果方法不存在将抛出异常 iface.getEvent("DoesNotExist()"); // [Error: no matching event] { // argument: 'signature', // code: 'INVALID_ARGUMENT', // reason: 'no matching event', // value: 'DoesNotExist()' // }

签名(Signature) 和 主题(Topic) 的哈希

interface.getSighash( fragment ) string< DataHexString< 4 > >

返回fragment的签名哈希(sighash)或函数选择器(Function Selector)(参考Specifying Fragments)。

iface.getSighash("balanceOf"); // '0x70a08231' iface.getSighash("balanceOf(address)"); // '0x70a08231' const fragment = iface.getFunction("balanceOf") iface.getSighash(fragment); // '0x70a08231'
interface.getEventTopic( fragment ) string< DataHexString< 32 > >

返回fragment的主题哈希(topic hash)(参考Specifying Fragments)。

iface.getEventTopic("Transfer"); // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' iface.getEventTopic("Transfer(address, address, uint)"); // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' const fragment = iface.getEvent("Transfer") iface.getEventTopic(fragment); // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'

编码数据

interface.encodeDeploy( [ values ] ) string< DataHexString >

返回已编码的部署数据,这些数据可以连接到合约的部署字节码,以便将值(values)传递到合约构造函数中。

// 这些数据表示的是在部署期间应该追加到字节码形式的构造函数的参数 iface.encodeDeploy([ "SYM", "Some Name" ]) // '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000353594d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009536f6d65204e616d650000000000000000000000000000000000000000000000'
interface.encodeErrorResult( fragment [ , values ] ) string< DataHexString >

返回编码后的错误结果,这通常是对给定值的fragment(参考Specifying Fragments))的reverted调用的响应结果。

大多数开发人员将不需要此方法,但对于那些模拟自己的区块链的开发人员很有用。

// 编码结果数据(像在revert期间被eth_call返回) iface.encodeErrorResult("AccountLocked", [ "0x8ba1f109551bD432803012645Ac136ddd64DBA72", parseEther("1.0") ]); // '0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000'
interface.encodeFilterTopics( fragment , values ) Array< topic | Array< topic > >

返回已编码的主题过滤器,可以在fragment(参考Specifying Fragments)将values值传递给getLogs函数。

每个主题是一个32字节(64nibble)的DataHexString

// 匹配的Transfer事件的过滤器 iface.encodeFilterTopics("Transfer", []) // [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' // ] // 匹配的sender的过滤器 iface.encodeFilterTopics("Transfer", [ "0x8ba1f109551bD432803012645Ac136ddd64DBA72" ]) // [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' // ] // 匹配receiver的过滤器 iface.encodeFilterTopics("Transfer", [ null, "0x8ba1f109551bD432803012645Ac136ddd64DBA72" ]) // [ // '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', // null, // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72' // ]
interface.encodeFunctionData( fragment [ , values ] ) string< DataHexString >

返回编码后的数据,可以在fragment(参考Specifying Fragments)指定values值,这个数据用作交易的data中。

// 编码数据,用于交易或调用的tx.data iface.encodeFunctionData("transferFrom", [ "0x8ba1f109551bD432803012645Ac136ddd64DBA72", "0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C", parseEther("1.0") ]) // '0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000' // 使用 positional 数组编码结构体数据 user = [ "Richard Moore", "0x8ba1f109551bD432803012645Ac136ddd64DBA72" ]; iface.encodeFunctionData("addUser", [ user ]); // '0x43967833000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000000000000000000000000000000000000000000d52696368617264204d6f6f726500000000000000000000000000000000000000' // 使用对象的方式编码结构体数据,只有参数被命名才可用 user = { name: "Richard Moore", addr: "0x8ba1f109551bD432803012645Ac136ddd64DBA72" }; iface.encodeFunctionData("addUser", [ user ]); // '0x43967833000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000000000000000000000000000000000000000000d52696368617264204d6f6f726500000000000000000000000000000000000000'
interface.encodeFunctionResult( fragment [ , values ] ) string< DataHexString >

Most developers will not need this method, but may be useful for authors of a mock blockchain. 返回编码后的结果,这通常是对给定值的fragment(参考Specifying Fragments))的调用的响应结果。

大多数开发人员将不需要此方法,但对于那些模拟自己的区块链的开发人员很有用。

// 编码 result 数据 (像被eth_call后的返回结果数据) iface.encodeFunctionResult("balanceOf", [ "0x8ba1f109551bD432803012645Ac136ddd64DBA72" ]) // '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'

解码数据

interface.decodeErrorResult( fragment , data ) Result

在对给定数据的fragment(参考Specifying Fragments))revert期间,返回此调用的结果中已解码的值。

大多数开发人员不需要这个数据,因为如果数据表示revert,decodeFunctionResult将自动解码errors。

// 解码Result数据(例如eth_call) errorData = "0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000"; iface.decodeErrorResult("AccountLocked", errorData) // [ // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // { BigNumber: "1000000000000000000" }, // balance: { BigNumber: "1000000000000000000" }, // owner: '0x8ba1f109551bD432803012645Ac136ddd64DBA72' // ]
interface.decodeEventLog( fragment , data [ , topics ] ) Result

从事件日志中为带有可选主题(topics)的给定数据fragment(参考Specifying Fragments))返回已解码的事件值。

如果没有指定主题(topics),将在结果中插入占位符。

大多数开发人员会发现parsing methods更便于解码事件数据,因为它们会自动检测匹配的事件。

// 解码日志和主题数据 (在整个收据中) const data = "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"; const topics = [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72", "0x000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c" ]; iface.decodeEventLog("Transfer", data, topics); // [ // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C', // { BigNumber: "1000000000000000000" }, // amount: { BigNumber: "1000000000000000000" }, // from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // to: '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C' // ]
interface.decodeFunctionData( fragment , data ) Result

对于给定数据fragment(参考Specifying Fragments)),从交易数据中返回已解码的值。

大多数开发人员不需要这个方法,但是对于调试或检查交易可能很有用。

大多数开发人员还会发现parsing methods对于解码交易数据更方便,因为它们会自动检测匹配的函数。

// 解码函数数据(tx.data的值) const txData = "0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000"; iface.decodeFunctionData("transferFrom", txData); // [ // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C', // { BigNumber: "1000000000000000000" }, // amount: { BigNumber: "1000000000000000000" }, // from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // to: '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C' // ]
interface.decodeFunctionResult( fragment , data ) Result

返回给定数据的fragment(参考Specifying Fragments))调用结果的解码值。

// 解码result数据(例如,来自eth_call) resultData = "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"; iface.decodeFunctionResult("balanceOf", resultData) // [ // { BigNumber: "1000000000000000000" } // ] // 解码由revert触发的result数据 // 抛出一个CALL_EXCEPTION,具有额外的信息 errorData = "0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000"; iface.decodeFunctionResult("balanceOf", errorData) // [Error: call revert exception [ See: https://links.ethers.org/v5-errors-CALL_EXCEPTION ]] { // code: 'CALL_EXCEPTION', // data: '0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000', // errorArgs: [ // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // { BigNumber: "1000000000000000000" }, // balance: { BigNumber: "1000000000000000000" }, // owner: '0x8ba1f109551bD432803012645Ac136ddd64DBA72' // ], // errorName: 'AccountLocked', // errorSignature: 'AccountLocked(address,uint256)', // method: 'balanceOf(address)', // reason: null // } // 解码结构体数据将返回一个Result对象,该对象将按位置包含所有值,如果ABI包含names,则值还将通过names使用。 resultData = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000000000000000000000000000000000000000000d52696368617264204d6f6f726500000000000000000000000000000000000000"; result = iface.decodeFunctionResult("getUser", resultData); // [ // [ // 'Richard Moore', // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // addr: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // name: 'Richard Moore' // ], // user: [ // 'Richard Moore', // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // addr: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // name: 'Richard Moore' // ] // ] // 位置访问:下标为0的输出参数,结构体的第一个属性 result[0][0]; // 'Richard Moore' // 通过name访问:(仅当因为参数已命名才可用) result.user.name // 'Richard Moore'

解析(Parsing)

对于大多数开发人员来说,这些函数通常是最有用的。 它们将自动在ABI中搜索匹配的事件(Event)或函数(Function),并将组件解码为完整的描述信息。

interface.parseError( data ) ErrorDescription

数据中搜索与错误选择器匹配的错误,并解析出详细信息。

const data = "0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000"; iface.parseError(data); // ErrorDescription { // args: [ // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // { BigNumber: "1000000000000000000" }, // balance: { BigNumber: "1000000000000000000" }, // owner: '0x8ba1f109551bD432803012645Ac136ddd64DBA72' // ], // errorFragment: [Function: ErrorFragment], // name: 'AccountLocked', // sighash: '0xf7c3865a', // signature: 'AccountLocked(address,uint256)' // }
interface.parseLog( log ) LogDescription

搜索与日志主题哈希匹配的事件,并解析日志表示的值。

const data = "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000"; const topics = [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72", "0x000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c" ]; iface.parseLog({ data, topics }); // LogDescription { // args: [ // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C', // { BigNumber: "1000000000000000000" }, // amount: { BigNumber: "1000000000000000000" }, // from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // to: '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C' // ], // eventFragment: [Function: EventFragment], // name: 'Transfer', // signature: 'Transfer(address,address,uint256)', // topic: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' // }
interface.parseTransaction( transaction ) TransactionDescription

搜索与交易数据签名哈希匹配的函数,并解析出交易的属性。

const data = "0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000"; const value = parseEther("1.0"); iface.parseTransaction({ data, value }); // TransactionDescription { // args: [ // '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C', // { BigNumber: "1000000000000000000" }, // amount: { BigNumber: "1000000000000000000" }, // from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72', // to: '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C' // ], // functionFragment: [Function: FunctionFragment], // name: 'transferFrom', // sighash: '0x23b872dd', // signature: 'transferFrom(address,address,uint256)', // value: { BigNumber: "1000000000000000000" } // }

类型(Types)

Result inherits Array<any>

一个Result是一个数组,因此每个值都可以作为位置参数访问。

此外,如果值已命名,则可以通过名称访问与其位置值相同的对象。

但是对象中的name为length是不可用的,因为它是Array的一部分, 所以这个键的任何命名值都被重命名为_length。

如果存在名称冲突,则只有第一个键可用。

ErrorDescription

errorDescription.args Result

error的输入参数的值。

errorDescription.errorFragment ErrorFragment

与数据中选择器匹配的ErrorFragment

errorDescription.name string

error的name。(例如AccountLocked)

errorDescription.signature string

error的签名。(例如AccountLocked(address,uint256))

errorDescription.sighash string

error的选择器(selector)。

LogDescription

logDescription.args Result

事件的输入参数的值。

logDescription.eventFragment EventFragment

与日志中的主题匹配的EventFragment

logDescription.name string

事件的名称。(例如Transfer)

logDescription.signature string

事件的签名。(如Transfer(address,address,uint256))

logDescription.topic string

主题的哈希。(topic hash)

TransactionDescription

transactionDescription.args Result

从交易数据的输入参数中解码出来的值。

transactionDescription.functionFragment FunctionFragment

交易数据中的与签名哈希匹配的FunctionFragment

transactionDescription.name string

函数的名称。(如transfer)

transactionDescription.sighash string

匹配交易数据的签名哈希(或函数选择器)。

transactionDescription.signature string

函数的签名。(如transfer(address,uint256))

transactionDescription.value 大数(BigNumber)

交易的value值(以太币).

Specifying Fragments

当指定一个fragment到接口中的函数时,可以使用以下任意一个:

地址(Addresses)

请在此介绍地址、格式和校验和。

还可查看此处: constants.AddressZero

地址格式

地址(Address)

一个地址(Address)是一个20字节(40 nibbles)的DataHexString,混合大小写(mixed case)。

如果大小写混合,它是一个Checksum Address,它在给定地址中使用特定的大写和小写字母模式, 以减少输入地址或剪切和粘贴问题带来的错误风险。

所有返回地址的函数将返回一个校验和地址。

ICAP Address

The ICAP Address Format was an early attempt to introduce a checksum into Ethereum addresses using the popular banking industry's IBAN format with the country code specified as XE.

Due to the way IBAN encodes address, only addresses that fit into 30 base-36 characters are actually compatible, so the format was adapted to support 31 base-36 characters which is large enough for a full Ethereum address, however the preferred method was to select a private key whose address has a 0 as the first byte, which allows the address to be formatted as a fully compatibly standard IBAN address with 30 base-36 characters.

In general this format is no longer widely supported anymore, however any function that accepts an address can receive an ICAP address, and it will be converted internally.

要将地址转换为ICAP格式,请参见getIcapAddress

转换和验证

ethers.utils.getAddress( address ) string< 地址(Address) >

返回一个校验和地址

如果address是一个无效的40-nibble HexString,或者它包含大小写混合且校验和无效,则抛出INVALID_ARGUMENT错误。

address的值可以是任何所支持的地址格式。

// 注入校验和(通过大写字母) getAddress("0x8ba1f109551bd432803012645ac136ddd64dba72"); // '0x8ba1f109551bD432803012645Ac136ddd64DBA72' // 转换并注入校验和 getAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36"); // '0x8ba1f109551bD432803012645Ac136ddd64DBA72' // 如果提供了一个校验和地址,但字母是错误的情况,则抛出错误 // ------------B 应为小写字母 getAddress("0x8Ba1f109551bD432803012645Ac136ddd64DBA72") // [Error: bad address checksum] { // argument: 'address', // code: 'INVALID_ARGUMENT', // reason: 'bad address checksum', // value: '0x8Ba1f109551bD432803012645Ac136ddd64DBA72' // } // 如果 ICAP/IBAN 校验和失败抛出错误 getIcapAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK37"); // Error: getIcapAddress is not defined // 如果地址不合法抛出错误 getIcapAddress("I like turtles!"); // Error: getIcapAddress is not defined
ethers.utils.getIcapAddress( address ) string< IcapAddress >

返回一个ICAP address地址。 与getAddress具有相同的限制条件。

getIcapAddress("0x8ba1f109551bd432803012645ac136ddd64dba72"); // 'XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36' getIcapAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36"); // 'XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36'
ethers.utils.isAddress( address ) boolean

如果地址有效(任何支持的格式)则返回true。

isAddress("0x8ba1f109551bd432803012645ac136ddd64dba72"); // true isAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36"); // true isAddress("I like turtles."); // false

Derivation

ethers.utils.computeAddress( publicOrPrivateKey ) string< 地址(Address) >

返回publicOrPrivateKey的地址。公钥可以压缩或不压缩,私钥将自动转换为派生的公钥。

// 私钥 computeAddress("0xb976778317b23a1385ec2d483eda6904d9319135b89f1d8eee9f6d2593e2665d"); // '0x0Ac1dF02185025F65202660F8167210A80dD5086' // 公钥 (压缩过后的) computeAddress("0x0376698beebe8ee5c74d8cc50ab84ac301ee8f10af6f28d0ffd6adf4d6d3b9b762"); // '0x0Ac1dF02185025F65202660F8167210A80dD5086' // 公钥 (未被压缩的) computeAddress("0x0476698beebe8ee5c74d8cc50ab84ac301ee8f10af6f28d0ffd6adf4d6d3b9b762d46ca56d3dad2ce13213a6f42278dabbb53259f2d92681ea6a0b98197a719be3"); // '0x0Ac1dF02185025F65202660F8167210A80dD5086'
ethers.utils.recoverAddress( digest , signature ) string< 地址(Address) >

使用ECDSA Public Key Recovery来确定摘要(digest)生成签名的公钥地址,

const digest = "0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331"; // 使用一个拓展的签名 recoverAddress(digest, { r: "0x528459e4aec8934dc2ee94c4f3265cf6ce00d47cf42bb106afda3642c72e25eb", s: "0x42544137118256121502784e5a6425e6183ca964421ecd577db6c66ba9bccdcf", v: 27 }); // '0x0Ac1dF02185025F65202660F8167210A80dD5086' // Using a flat Signature const signature = "0x528459e4aec8934dc2ee94c4f3265cf6ce00d47cf42bb106afda3642c72e25eb42544137118256121502784e5a6425e6183ca964421ecd577db6c66ba9bccdcf1b"; recoverAddress(digest, signature); // '0x0Ac1dF02185025F65202660F8167210A80dD5086'

合约地址

ethers.utils.getContractAddress( transaction ) string< 地址(Address) >

如果一个交易用于部署合约,则返回部署合约后的合约地址。

const from = "0x8ba1f109551bD432803012645Ac136ddd64DBA72"; const nonce = 5; getContractAddress({ from, nonce }); // '0x082B6aC9e47d7D83ea3FaBbD1eC7DAba9D687b36'
ethers.utils.getCreate2Address( from , salt , initCodeHash ) string< 地址(Address) >

返回给定CREATE2调用后的合约地址。

const from = "0x8ba1f109551bD432803012645Ac136ddd64DBA72"; const salt = "0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331"; const initCode = "0x6394198df16000526103ff60206004601c335afa6040516060f3"; const initCodeHash = keccak256(initCode); getCreate2Address(from, salt, initCodeHash); // '0x533ae9d683B10C02EbDb05471642F85230071FC3'

大数(BigNumber)

以太坊中的许多操作都是对超出JavaScript安全值范围的数字进行操作。

BigNumber是一个可以安全地对任意大小的数字进行数学运算的对象。

大多数需要返回值的操作将返回一个BigNumber,接受值的参数通常会接收它们。

类型

BigNumberish

这个库中的许多函数和方法都接受可以无歧义(non-ambiguously)且安全地转换为BigNumber的值。这些值可以指定为:

string

HexString或decimal string,两者都可以为负数。

BytesLike

一个BytesLike对象,例如数组或Uint8Array。

BigNumber

一个大数(BigNumber)实例。

number

一个在JavaScript numbers的安全范围内的数字。

BigInt

在支持BigInt的环境中的一个JavaScript BigInt对象。

创建实例

BigNumber的构造函数不能被直接调用。相反,使用静态BigNumber.from

ethers.BigNumber.from( aBigNumberish ) 大数(BigNumber)

aBigNumberish返回一个BigNumber的实例。

例子:

// 来自于十进制字符串 BigNumber.from("42") // { BigNumber: "42" } // 来自于十六进制 BigNumber.from("0x2a") // { BigNumber: "42" } // 来自于负数的十六进制 BigNumber.from("-0x2a") // { BigNumber: "-42" } // 来自于 数组 或 Uint8Array BigNumber.from([ 42 ]) // { BigNumber: "42" } // 来自于BigNumber let one1 = constants.One; let one2 = BigNumber.from(one1) one2 // { BigNumber: "1" } // 返回相同的实例 one1 === one2 // true // 来自于(安全) 的数字... BigNumber.from(42) // { BigNumber: "42" } // 来自于 ES2015 BigInt (只能在支持BigInt的平台上使用) BigNumber.from(42n) // { BigNumber: "42" } // 安全范围外的数字将会失效: BigNumber.from(Number.MAX_SAFE_INTEGER); // [Error: overflow [ See: https://links.ethers.org/v5-errors-NUMERIC_FAULT-overflow ]] { // code: 'NUMERIC_FAULT', // fault: 'overflow', // operation: 'BigNumber.from', // reason: 'overflow', // value: 9007199254740991 // }

方法

BigNumber类是不可变的,所以没有任何操作可以改变它所表示的值。

数学运算

BigNumber.add( otherValue ) 大数(BigNumber)

返回值为BigNumber + otherValue的BigNumber。

BigNumber.sub( otherValue ) 大数(BigNumber)

返回值为 BigNumber - otherValue的BigNumber。

BigNumber.mul( otherValue ) 大数(BigNumber)

返回值为 BigNumber × otherValue的BigNumber。

BigNumber.div( divisor ) 大数(BigNumber)

返回值为 BigNumber ÷ divisor的BigNumber。

BigNumber.mod( divisor ) 大数(BigNumber)

返回值为 BigNumber ÷ divisor余数的BigNumber。

BigNumber.pow( exponent ) 大数(BigNumber)

返回值为 BigNumber 指数的幂为exponent的BigNumber。

BigNumber.abs( ) 大数(BigNumber)

返回值为绝对值的BigNumber。

BigNumber.mask( bitcount ) 大数(BigNumber)

返回一个BigNumber,其BigNumber的值超出bitcount最低有效位的位则设为0。

Two's Complement

Two's Complement是一种优雅的方法, 用于编码和解码固定宽度的有符号值,同时有效地保留数学运算。大多数用户不需要与它们交互。

BigNumber.fromTwos( bitwidth ) 大数(BigNumber)

返回一个BigNumber, BigNumber的值由带位宽(bitwidth)的二进制补码转换而来。

BigNumber.toTwos( bitwidth ) 大数(BigNumber)

返回一个BigNumber, BigNumber的值转换为带位宽的二进制补码。

比较和相等

BigNumber.eq( otherValue ) boolean

当且仅当BigNumber的值等于otherValue时返回true。

BigNumber.lt( otherValue ) boolean

当且仅当BigNumber的值<otherValue时返回true。

BigNumber.lte( otherValue ) boolean

当且仅当BigNumber的值otherValue时返回true。

BigNumber.gt( otherValue ) boolean

当且仅当BigNumber的值>otherValue时返回true。

BigNumber.gte( otherValue ) boolean

当且仅当BigNumber的值otherValue时返回true。

BigNumber.isZero( ) boolean

当且仅当BigNumber的值为0时返回true。

转换

BigNumber.toBigInt( ) bigint

在支持BigInt的平台上以JavScript BigInt值返回BigNumber的值。

BigNumber.toNumber( ) number

将BigNumber的值转换JavaScript值。

如果该值大于Number.MAX_SAFE_INTEGER或小于等于Number.MIN_SAFE_INTEGER, 则会抛出一个错误

BigNumber.toString( ) string

以十进制字符串的形式返回BigNumber的值。

BigNumber.toHexString( ) string< DataHexString >

返回BigNumber的值为十六进制的值,0x是前缀DataHexString.。

检查

ethers.BigNumber.isBigNumber( object ) boolean

当且仅当对象是BigNumber是对象时返回true。

例子

let a = BigNumber.from(42); let b = BigNumber.from("91"); a.mul(b); // { BigNumber: "3822" }

注意

这部分是针对一些经常出现的问题。

为什么我不能只使用 numbers?

许多人在处理以太坊时遇到的第一个问题是数字的概念。 大多数常见的货币被划分成非常小的粒度。例如,1美元只有100美分。然而,一个ether等于1018 wei

JavaScript使用IEEE 754 double-precision binary floating point](link-wiki-ieee754)来表示数值。 因此,在9,007,199,254,740,991之后的整数集就存在漏洞了; 这对以太坊来说是个问题,即使是9,007,199,254,740,991数值也才只有0.009以太(单位:wei), 这意味着超过这个值将开始产生舍入误差。

用代码来说明这个问题,如下:

(Number.MAX_SAFE_INTEGER + 2 - 2) == (Number.MAX_SAFE_INTEGER) // false

为了修复这一点,所有的数字(可以很大)都被存储和操作为Big Numbers](BigNumber)。

函数parseEther( etherString )formatEther( wei ) 可以用字符串(用户可以查看或输入)和Big Number(可以安全地进行数学操作)之间进行转换。

为什么不用 BigNumber.js, BN.js, BigDecimal, 这一类?

每个人都有自己最喜欢的Big Number库,一千个读者就有一千个哈姆雷特, 就像他们的编辑器都有vi vs emacs。在npm上有超过100个Big Number库。

Ethers 大数(BigNumber)对象和其他库之间最大的区别之一是它是不可变的,这在处理区块链的异步特性时非常重要。

在异步函数中捕获值是不安全的,所以不可变性可以让我们不容易犯错误,这在支持大量就地操作的低级库上是不可能得到保证的。

其次,Ethers 大数(BigNumber)提供了内部所需的所有功能,通常对大多数开发人员来说应该足够了, 而不暴露一些更高级和罕见的功能。因此,在不影响用户的情况下,交换底层库将更加容易。

例如,如果BN.js被公开,有人可能会使用最大的公分母函数, 这将是替换库也应该提供的功能,以确保不会影响依赖于该功能的开发人员。

为什么是BN.js??

BN.js之所以在内部被用作big number,是因为它是elliptic使用的库。

因此,无论如何都必须包含它,所以我们利用这个库,而不是添加另一个Big Number库,添加了就意味着两个不同的库提供相同的功能。

与其他库(包括用于各种目的的单独的Big Number库)相比,这节省了大约85kb(该库大小的80%)的库大小。

允许我们设置一个全局的 Big Number 库吗?

有些人经常提到希望指定一个用户定义的全局 Big Number库,所有函数都将返回这个库。

这是有问题的,因为您的代码可能与其他使用Ethers的库或代码共存。事实上,甚至Ethers也在内部使用了许多公共函数。

例如,如果你使用的库使用的是a.plus(b)而不是a.add(b),这将在尝试内部计算fees时Ethers会被破坏,而其他库可能有类似的逻辑。

但是,大数(BigNumber)原型是公开的,所以你总是可以添加一个toMyCustomBigNumber()方法到所有全局的大数(BigNumber)会更安全。

字节处理

虽然有许多用于与以太坊交互的高级api,如ContractsProviders,但许多低级访问需要通过字节进行操作。

其中许多操作是内部使用的,但各种函数和方法的输出可以用规范化二进制数据表示。

类型

Bytes

Bytes是一个ArrayTypedArray对象, 每个值都在有效字节范围内(例如0到255之间),或者是一个具有length属性的对象,每个索引的属性都在有效字节范围内。

BytesLike

BytesLike可以是BytesDataHexString

DataHexString

DataHexstringHexString是相同的,除了它有偶数个nibbles,因此二进制数据作为字符串是有效的。

HexString

Hexstring是一个字符串,它有一个0x前缀,后面跟着nibbles number类型(例如,不区分大小写的十六进制字符,0-9a-f)。

Signature

Raw Signature inherits string<DataHexString<65>>

原始签名(Raw Signature)是一种常见的签名格式,其中r, s和v被连接成一个65字节(130 nibble)的DataHexString

SignatureLike

SignatureLike类似于一个Signature,除了多余的属性可以被省略或者它也可以是一个 Raw Signature

例如,如果指定了_vs,则sv可以省略。同样,如果提供了recoveryParam, 则可以省略v(在这种情况下,可以计算出缺失的值)。

检查

ethers.utils.isBytes( object ) boolean

当且仅当object为有效Bytes时返回true。

ethers.utils.isBytesLike( object ) boolean

当且仅当objectBytesDataHexString时返回true。

ethers.utils.isHexString( object , [ length ] ) boolean

当且仅当object是一个有效的十六进制字符串时返回true。 如果指定了length并且object 不是一个有效的长度字节的DataHexString,则抛出一个InvalidArgument错误。

数组和十六进制字符串之间的转换

ethers.utils.arrayify( DataHexStringOrArrayish [ , options ] ) Uint8Array

DataHexStringOrArrayish转换为Uint8Array。

ethers.utils.hexlify( hexstringOrArrayish ) string< DataHexString >

hexstringOrArrayish转换为DataHexString

ethers.utils.hexValue( aBigNumberish ) string< HexString >

aBigNumberish转换为HexString,没有前导零。

Examples
// 将十六进制字符串转换为Uint8Array arrayify("0x1234") // Uint8Array [ 18, 52 ] // 将数组转换为十六进制字符串 hexlify([1, 2, 3, 4]) // '0x01020304' // 将对象转换为十六进制字符串 hexlify({ length: 2, "0": 1, "1": 2 }) // '0x0102' // 将数组转换为十六进制字符串 hexlify([ 1 ]) // '0x01' // 将数字转换成无前导零的十六进制值 hexValue(1) // '0x1' // 将数组转换成无前导零的十六进制值 hexValue([ 1, 2 ]) // '0x102'

数组处理

ethers.utils.concat( arrayOfBytesLike ) Uint8Array

arrayOfBytesLike中的所有BytesLike连接到一个单独的Uint8Array中。

ethers.utils.stripZeros( aBytesLike ) Uint8Array

返回一个无前导零aBtyesLike字节的Uint8Array

ethers.utils.zeroPad( aBytesLike , length ) Uint8Array

Returns a Uint8Array of the data in aBytesLike with 0 bytes prepended to length bytes long.

如果aBytesLike的长度已经超过长度字节,则会抛出InvalidArgument错误。

十六进制字符串处理

ethers.utils.hexConcat( arrayOfBytesLike ) string< DataHexString >

arrayOfBytesLike中的所有BytesLike连接成一个单一的DataHexString

ethers.utils.hexDataLength( aBytesLike ) string< DataHexString >

返回aBytesLike的长度(以字节为单位)。

ethers.utils.hexDataSlice( aBytesLike , offset [ , endOffset ] ) string< DataHexString >

返回一个aBytesLike切片的DataHexString表示,从offset(以字节为单位)到endOffset(以字节为单位)。 如果省略了endOffset,则使用aBytesLike的长度。

ethers.utils.hexStripZeros( aBytesLike ) string< HexString >

返回aBytesLikeHexString表示形式,去掉所有前导零。

ethers.utils.hexZeroPad( aBytesLike , length ) string< DataHexString >

返回被填充到length字节的aBytesLikeDataHexString表示。

如果aBytesLike的长度已经超过length字节,则会抛出InvalidArgument错误。

签名转换

ethers.utils.joinSignature( aSignatureLike ) string< RawSignature >

返回aSignaturelike的原始格式,它是65字节(130个nibbles)长,连接签名的r, s和(标准化后))v

ethers.utils.splitSignature( aSignatureLikeOrBytesLike ) Signature

返回aSignaturelike的完整扩展格式或原始格式DataHexString,将自动计算所有缺失的属性。

随机字节

ethers.utils.randomBytes( length ) Uint8Array

返回一个新的Uint8Array 长度为length的随机字节。

ethers.utils.shuffled( array ) Array< any >

返回一个使用Fisher-Yates Shuffle打乱后的数组副本。

Examples
utils.randomBytes(8) // Uint8Array [ 30, 216, 23, 134, 85, 198, 30, 109 ] const data = [ 1, 2, 3, 4, 5, 6, 7 ]; // 返回一个新数组 utils.shuffled(data); // [ // 6, // 4, // 1, // 7, // 5, // 2, // 3 // ] // 原数据不会有任何变化 data // [ // 1, // 2, // 3, // 4, // 5, // 6, // 7 // ]

常量(contants)

ethers.contants 包含常用的一些值。

字节(Bytes)

ethers.constants.AddressZero string< Address >

零地址,它由20字节(40个nibbles)的 0 组成。

ethers.constants.HashZero string< DataHexString< 32 > >

零哈希,它由32字节(64个nibbles)的 0 组成。

字符串(Strings)

ethers.constants.EtherSymbol string

Ether symbol, Ξ.

大数(BigNumber)

ethers.constants.NegativeOne 大数(BigNumber)

BigNumber 值表示为"-1"

ethers.constants.Zero 大数(BigNumber)

BigNumber 值表示为"0"

ethers.constants.One 大数(BigNumber)

BigNumber 值表示为"1"

ethers.constants.Two 大数(BigNumber)

BigNumber 值表示为"2"

ethers.constants.WeiPerEther 大数(BigNumber)

BigNumber 值表示为"1000000000000000000"(单位 Wei per Ether)

ethers.constants.MaxUint256 大数(BigNumber)

BigNumber 值表示为 uint256 最大的值

显示逻辑和输入

当创建一个应用程序时,在用户友好的字符串(通常显示为ether)和机器可读的值(合约和数学公式所依赖的,通常使用wei)之间进行转换是很有用的

例如,一个钱包通过ether显示余额,在用户界面中用gwei显示gas价格,但是当发送一个交易时,两者都必须用wei表示。

parseUnits是将ether表示的字符串解析成wei表示的字符串, 例如将1.1解析为wei表示的BigNumber,并且在用户输入值时非常有用,例如发送1.1 ether。

formatUnits是将BigNumberish解析成ether表示的字符串,这在显示余额时非常有用。

单位(Units)

十进制数(Decimal Count)

Unit可以指定为一个数字,它表示小数点位数。

例子:

命名单位

还有几种常见的命名单位,ethers中 可以使用它们的name(string类型)进行操作。

NameDecimals 
wei0 
kwei3 
mwei6 
gwei9 
szabo12 
finney15 
ether18 

功能函数

格式化

ethers.utils.commify( value ) string

返回一个由3位数字组成的,并且用,分隔的字符串.

commify("-1000.3000"); // '-1,000.3'

转换

ethers.utils.formatUnits( value [ , unit = "ether" ] ) string

返回格式化后字符串形式表示的值, 如果是数字number指定,则格式化为对应的小数位数; 如果是字符串string指定,则格式化为 string name 对应的单位;。

const oneGwei = BigNumber.from("1000000000"); const oneEther = BigNumber.from("1000000000000000000"); formatUnits(oneGwei, 0); // '1000000000' formatUnits(oneGwei, "gwei"); // '1.0' formatUnits(oneGwei, 9); // '1.0' formatUnits(oneEther); // '1.0' formatUnits(oneEther, 18); // '1.0'
ethers.utils.formatEther( value ) string

等价于调用formatUnits(value, "ether")

const value = BigNumber.from("1000000000000000000"); formatEther(value); // '1.0'
ethers.utils.parseUnits( value [ , unit = "ether" ] ) BigNumber

返回BigNumber表示的值, 如果是数字number指定,则解析为对应的小数位数; 如果是字符串string指定,则解析为 string name 对应的单位。

parseUnits("1.0"); // { BigNumber: "1000000000000000000" } parseUnits("1.0", "ether"); // { BigNumber: "1000000000000000000" } parseUnits("1.0", 18); // { BigNumber: "1000000000000000000" } parseUnits("121.0", "gwei"); // { BigNumber: "121000000000" } parseUnits("121.0", 9); // { BigNumber: "121000000000" }
ethers.utils.parseEther( value ) BigNumber

等价于调用parseUnits(value, "ether")

parseEther("1.0"); // { BigNumber: "1000000000000000000" } parseEther("-0.5"); // { BigNumber: "-500000000000000000" }

编码工具

Base58

ethers.utils.base58.decode( textData ) Uint8Array

返回textData(经过base-58编码)解码成Uint8Array类型的数据。

base58.decode("TzMhH"); // Uint8Array [ 18, 52, 86, 120 ]
ethers.utils.base58.encode( aBytesLike ) string

返回aBytesLike(经过base-58编码)解码成字符串类型的数据。

base58.encode("0x12345678"); // 'TzMhH' base58.encode([ 0x12, 0x34, 0x56, 0x78 ]); // 'TzMhH'

Base64

ethers.utils.base64.decode( textData ) Uint8Array

返回textData(经过base-64编码)解码成Uint8Array类型的数据。

base64.decode("EjQ="); // Uint8Array [ 18, 52 ]
ethers.utils.base64.encode( aBytesLike ) string

返回aBytesLike(经过base-64编码)解码成字符串类型的数据。

base64.encode("0x1234"); // 'EjQ=' base64.encode([ 0x12, 0x34 ]); // 'EjQ='

递归长度前缀编码(RLP编码)

递归长度前缀编码(Recursive Length PrefixRLP编码)在以太坊中被用来序列化数组和数据的嵌套结构。

ethers.utils.RLP.encode( dataObject ) string< DataHexString >

将结构化数据对象编码到RLP编码的形式。

RLP.encode("0x12345678"); // '0x8412345678' RLP.encode([ "0x12345678" ]); // '0xc58412345678' RLP.encode([ new Uint8Array([ 0x12, 0x34, 0x56, 0x78 ]) ]); // '0xc58412345678' RLP.encode([ [ "0x42", [ "0x43" ] ], "0x12345678", [ ] ]); // '0xcac342c1438412345678c0' RLP.encode([ ]); // '0xc0'
ethers.utils.RLP.decode( aBytesLike ) DataObject

将RLP编码的aBytesLike解码为其结构化数据对象

所有的Data组件将作为一个DataHexString返回。

RLP.decode("0x8412345678"); // '0x12345678' RLP.decode("0xcac342c1438412345678c0"); // [ // [ // '0x42', // [ // '0x43' // ] // ], // '0x12345678', // [] // ] RLP.decode("0xc0"); // []

数据对象(Data Object)

数据对象是一种递归结构,用于序列化以太坊中的许多内部结构。每个数据对象可以是:

Examples

  • "0x1234"
  • [ "0x1234", [ "0xdead", "0xbeef" ], [ ] ]

FixedNumber

FixedNumber是一个内部具有十进制除数的固定宽度(以位为单位)的number,这使得它能表示十进制小数部分。

创建实例

FixedNumber构造函数不能被直接调用。有几个静态方法可以创建FixedNumber。

FixedNumber.from( value [ , format = "fixed" ] ) FixedNumber

format返回value的一个FixedNumber的实例。

FixedNumber.fromBytes( aBytesLike [ , format = "fixed" ] ) FixedNumber

format返回value的一个FixedNumber的实例。

FixedNumber.fromString( value [ , format = "fixed" ] ) FixedNumber

format返回value的一个FixedNumber的实例。value所包含的值不能超过format允许的位数。

FixedNumber.fromValue( value [ , decimals = 0 [ , format = "fixed" ] ] ) FixedNumber

format的格式为value返回具有decimals位数的FixedNumber实例。

属性

fixednumber.format

关于fixednumberFixedFormat

方法(Methods)

数学运算

fixednumber.addUnsafe( otherValue ) FixedNumber

返回一个新的FixedNumber,其值为fixedvalue + otherValue

fixednumber.subUnsafe( otherValue ) FixedNumber

返回一个新的FixedNumber,其值为fixedvalue - otherValue

fixednumber.mulUnsafe( otherValue ) FixedNumber

返回一个新的FixedNumber,其值为fixedvalue × otherValue

fixednumber.divUnsafe( otherValue ) FixedNumber

返回一个新的FixedNumber,其值为fixedvalue ÷ otherValue

fixednumber.round( [ decimals = 0 ] ) FixedNumber

返回一个新的FixedNumber,其值为fixedvalue按照decimals进行了四舍五入。

比较和等价

FixedNumber.isZero( ) boolean

当且仅当FixedNumber的值为零时返回true。

转换

fixednumber.toFormat( format ) FixedNumber

返回一个新的FixedNumber,其值是fixedvalue按照format进行了格式化后的输出。

fixednumber.toHexString( ) string

返回fixednumberHexString表示形式。

fixednumber.toString( ) string

返回fixednumber的字符串(string)表示形式。

fixednumber.toUnsafeFloat( ) float

返回一个fixednumber的浮点型JavaScript number 值。由于JavaScript number的四舍五入,该值仅是近似值。

检查

FixedNumber.isFixedNumber( value ) boolean

当且仅当value值是FixedNumber时返回true。

FixedFormat

通常直接使用这个类是不必要的,因为将格式字符串直接传入FixedNumber时会自动创建。

格式字符串

格式字符串由三个部分组成,包括有符号、位宽和小数位。

有符号格式字符串以fixed开头,无符号格式字符串以ufixed开头,然后是位宽和小数位。

宽度必须等于0 mod 8(即(width % 8) == 0),且不大于256位,小数点的位数不能大于80个。

例如:

创建实例

FixedFormat.from( value = "fixed128x18" ) FixedFormat

返回一个由value定义的FixedFormat格式的新实例。 可以传入任何有效的格式字符串,也可以传入任何定义了signedwidthdecimals的对象, 包括一个FixedFormat对象。

属性

fixedFormat.signed boolean

fixedFormat的signed值,如果支持负数则为返回true。

fixedFormat.width number

fixedFormat的位宽。

fixedFormat.decimals number

fixedFormat的小数点位数。

fixedFormat.name string

fixedFormat的name,可以用来重新创建格式,它是Solidity语言用来表示这种格式的字符串。

"fixed"

表示的是fixed128x80的缩写。

哈希算法

在整个区块链空间中使用了许多哈希算法,对于一些复杂的用法可以使用utilities工具来简化这些操作。

加密哈希函数

加密哈希函数是一个特定的哈希函数家族。

ethers.utils.id( text ) string< DataHexString< 32 > >

以太坊身份函数(Ethereum Identity function)计算文本字节KECCAK256哈希值。

ethers.utils.keccak256( aBytesLike ) string< DataHexString< 32 > >

返回aBytesLikeKECCAK256摘要(digest)。

ethers.utils.ripemd160( aBytesLike ) string< DataHexString< 20 > >

返回aBytesLikeRIPEMD-160摘要(digest)。

ethers.utils.sha256( aBytesLike ) string< DataHexString< 32 > >

返回aBytesLikeSHA2-256摘要(digest)。

ethers.utils.sha512( aBytesLike ) string< DataHexString< 64 > >

返回aBytesLikeSHA2-512摘要(digest)。

KECCAK256
utils.keccak256([ 0x12, 0x34 ]) // '0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432' utils.keccak256("0x") // '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' utils.keccak256("0x1234") // '0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432' // value 必须是 data,比如: // - numbers数组 // - 十六进制字符串 (如 "0x1234") // - Uint8Array // 不要使用非十六进制的 UTF-8 字符串 utils.keccak256("hello world") // [Error: invalid arrayify value] { // argument: 'value', // code: 'INVALID_ARGUMENT', // reason: 'invalid arrayify value', // value: 'hello world' // } // 如果确实要使用非十六进制的 UTF-8 字符串,请先转换成字节(bytes): utils.keccak256(utils.toUtf8Bytes("hello world")) // '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad' // 等价于使用身份函数(identity function): utils.id("hello world") // '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad' // 请记住字符串 "0x1234" 表示两个字节(即 [ 0x12, 0x34 ]. If you wish to compute the // 如果你想要计算6个字符("0x1234")的哈希,请先通过utils.toUtf8Bytes转换成 UTF-8 字节 // 参考下述例子: // 2个字节的哈希 utils.keccak256("0x1234") // '0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432' // 2个字节的哈希,与上结果相同 utils.keccak256([ 0x12, 0x34 ]) // '0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432' bytes = utils.toUtf8Bytes("0x1234") // Uint8Array [ 48, 120, 49, 50, 51, 52 ] // 6个字符的哈希,与上结果不同 utils.keccak256(bytes) // '0x1ac7d1b81b7ba1025b36ccb86723da6ee5a87259f1c2fd5abe69d3200b512ec8' // 6个字符的哈希 utils.id("0x1234") // '0x1ac7d1b81b7ba1025b36ccb86723da6ee5a87259f1c2fd5abe69d3200b512ec8'
RIPEMD160
utils.ripemd160("0x") // '0x9c1185a5c5e9fc54612808977ee8f548b2258d31' utils.ripemd160("0x1234") // '0xc39867e393cb061b837240862d9ad318c176a96d'
SHA-2
utils.sha256("0x") // '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' utils.sha256("0x1234") // '0x3a103a4e5729ad68c02a678ae39accfbc0ae208096437401b7ceab63cca0622f' utils.sha512("0x") // '0xcf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e' utils.sha512("0x1234") // '0x4c54886c9821195522d88ff4705c3e0c686b921054421e6ea598739c29c26e1ee75419aaceec94dd2e3c0dbb82ecf895c9f61215f375de6d800d9b99d3d4b816'

HMAC

ethers.utils.computeHmac( algorithm , key , data ) string< DataHexString >

使用Algorithm算法返回 带有key的 HMACdata数据。

HMAC Supported Algorithms

ethers.utils.SupportedAlgorithm.sha256 string

使用SHA2-256哈希算法。

ethers.utils.SupportedAlgorithm.sha512 string

使用SHA2-512哈希算法。

HMAC
const key = "0x0102" const data = "0x1234" utils.computeHmac("sha256", key, data) // '0x7553df81c628815cf569696cad13a37c606c5058df13d9dff4fee2cf5e9b5779'

Hashing Helpers

ethers.utils.hashMessage( message ) string< DataHexString< 32 > >

计算messageEIP-191个人信息摘要。 个人消息被转换为UTF-8字节,并以\x19Ethereum Signed Message:message的length为前缀。

Hashing Messages
// 对字符串信息进行哈希 utils.hashMessage("Hello World") // '0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2' // 对二进制数据进行哈希 (以下是字节类型的"Hello World") utils.hashMessage( [ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100 ]) // '0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2' // 注意:理解字符串和二进制数据的不同处理方式是很重要的 // 字符串总是作为字符串形式的字节进行处理,所以必须首先将十六进制字符串转换为类似数组的对象 //首先转换为类数组对象。 // 对一个十六进制字符串哈希等同于对字符串直接哈希 // 注意: 这是对4个字符 [ '0', 'x', '4', '2' ] 进行哈希 utils.hashMessage("0x42") // '0xf0d544d6e4a96e1c08adc3efabe2fcb9ec5e28db1ad6c33ace880ba354ab0fce' // 对二进制数据进行哈希 // 注意: 这是对一个字节 [ 0x42 ] 进行哈希 utils.hashMessage([ 0x42 ]) // '0xd18c12b87124f9ceb7e1d3a5d06a5ac92ecab15931417e8d1558d9a263f99d63' // 对二进制数据进行哈希 // 注意: 这也等同于对一个字节 [ 0x42 ] 进行哈希 utils.hashMessage(utils.arrayify("0x42")) // '0xd18c12b87124f9ceb7e1d3a5d06a5ac92ecab15931417e8d1558d9a263f99d63'
ethers.utils.namehash( name ) string< DataHexString< 32 > >

返回ENS Namehashname

Namehash
utils.namehash("") // '0x0000000000000000000000000000000000000000000000000000000000000000' utils.namehash("eth") // '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae' utils.namehash("ricmoo.firefly.eth") // '0x0bcad17ecf260d6506c6b97768bdc2acfb6694445d27ffd3f9c1cfbee4a9bd6d' utils.namehash("ricmoo.xyz") // '0x7d56aa46358ba2f8b77d8e05bcabdd2358370dcf34e87810f8cea77ecb3fc57d'

Typed Data Encoder

TypedDataEncoder用于计算EIP-712签名的各种编码数据。

签名数据需要域(domain)、结构成员列表和数据本身。

domain是一个具有任何标准域属性值的对象。

types是一个对象,每个属性都是一个结构的名称,映射到一个字段描述数组。 它不应该包含EIP712Domain属性,除非它需要作为另一个签名数据的子结构。

实验特性 (导出的类名将会改变)

这是一个实验特性。如果你要使用,请指定 ethers 确切的版本 (例如指定"5.0.18", 而不是 "^5.0.18") 一旦它在字段中被使用,导出的类名将_TypedDataEncoder重命名为TypedDataEncoder

ethers.utils._TypedDataEncoder.from( types ) [TypedDataEncoder]

types创建一个新的TypedDataEncoder。 这个对象是一个相当低级的对象,大多数开发人员不应该直接使用实例。

大多数开发人员会发现下面的静态类方法更有用。

TypedDataEncoder.encode( domain , types , values ) string

编码并返回经过哈希处理的EIP-712 domain。

TypedDataEncoder.getPayload( domain , types , value ) any

返回各种JSON-RPC eth_signTypedData*调用所使用的标准payload。

domain和value的值都会被标准化(normalized),并对types的内容进行验证。

TypedDataEncoder.getPrimaryType( types ) string

构造一个types的有向无环图并返回root type,root type可以用作EIP-712payloads的primaryType

TypedDataEncoder.hash( domain , types , values ) string< DataHexString< 32 > >

返回计算后的EIP-712哈希。

TypedDataEncoder.hashDomain( domain ) string< DataHexString< 32 > >

返回EIP-712 domain的哈希。

TypedDataEncoder.resolveNames( domain , types , value , resolveName ) Promise< any >

返回value的副本,其中任何具有地址(address)类型的leaf value都将被递归地替换为value的resolveName调用的值。

Typed Data 例子
domain = { name: 'Ether Mail', version: '1', chainId: 1, verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC' }; // 所有types定义的name列表 types = { Person: [ { name: 'name', type: 'string' }, { name: 'wallet', type: 'address' } ], Mail: [ { name: 'from', type: 'Person' }, { name: 'to', type: 'Person' }, { name: 'contents', type: 'string' } ] }; // 待签名的数据 value = { from: { name: 'Cow', wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826' }, to: { name: 'Bob', wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB' }, contents: 'Hello, Bob!' }; TypedDataEncoder.encode(domain, types, value) // '0x1901f2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090fc52c0ee5d84264471806290a3f2c4cecfc5490626bf912d01f240d7a274b371e' TypedDataEncoder.getPayload(domain, types, value) // { // domain: { // chainId: '1', // name: 'Ether Mail', // verifyingContract: '0xcccccccccccccccccccccccccccccccccccccccc', // version: '1' // }, // message: { // contents: 'Hello, Bob!', // from: { // name: 'Cow', // wallet: '0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826' // }, // to: { // name: 'Bob', // wallet: '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb' // } // }, // primaryType: 'Mail', // types: { // EIP712Domain: [ // { // name: 'name', // type: 'string' // }, // { // name: 'version', // type: 'string' // }, // { // name: 'chainId', // type: 'uint256' // }, // { // name: 'verifyingContract', // type: 'address' // } // ], // Mail: [ // { // name: 'from', // type: 'Person' // }, // { // name: 'to', // type: 'Person' // }, // { // name: 'contents', // type: 'string' // } // ], // Person: [ // { // name: 'name', // type: 'string' // }, // { // name: 'wallet', // type: 'address' // } // ] // } // } TypedDataEncoder.getPrimaryType(types) // 'Mail' TypedDataEncoder.hash(domain, types, value) // '0xbe609aee343fb3c4b28e1df9e632fca64fcfaede20f02e86244efddf30957bd2' TypedDataEncoder.hashDomain(domain) // '0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f'

Solidity 哈希算法

当使用Solidity abi.packEncoded(...)函数时,将使用非标准(non-standard)的紧密打包(tightly packed)的编码版本。 这些函数实现了紧密打包算法(tightly packing algorithm)。

ethers.utils.solidityPack( types , values ) string< DataHexString >

返回types中对应类型打包的非标准编码值。

ethers.utils.solidityKeccak256( types , values ) string< DataHexString< 32 > >

返回types中对应类型打包的非标准编码KECCAK256值。

ethers.utils.soliditySha256( types , values ) string< DataHexString< 32 > >

返回types中对应类型打包的非标准编码SHA2-256值。

Solidity 哈希
utils.solidityPack([ "int16", "uint48" ], [ -1, 12 ]) // '0xffff00000000000c' utils.solidityPack([ "string", "uint8" ], [ "Hello", 3 ]) // '0x48656c6c6f03' utils.solidityKeccak256([ "int16", "uint48" ], [ -1, 12 ]) // '0x81da7abb5c9c7515f57dab2fc946f01217ab52f3bd8958bc36bd55894451a93c' utils.soliditySha256([ "int16", "uint48" ], [ -1, 12 ]) // '0xa5580fb602f6e2ba9c588011dc4e6c2335e0f5d970dc45869db8f217efc6911a' // 作为一个简单的例子,说明了solid 紧密打包不具有区分度(这就是为什么从安全的角度来看它对很多事情都不合适), // 下面的例子打包输出的结果是相同的,尽管它们表示的值和布局都不同。 utils.solidityPack([ "string", "string" ], [ "hello", "world01" ]) // '0x68656c6c6f776f726c643031' utils.solidityPack([ "string", "string" ], [ "helloworld", "01" ]) // '0x68656c6c6f776f726c643031' utils.solidityPack([ "string", "string", "uint16" ], [ "hell", "oworld", 0x3031 ]) // '0x68656c6c6f776f726c643031' utils.solidityPack([ "uint96" ], [ "32309054545061485574011236401" ]) // '0x68656c6c6f776f726c643031'

HD 钱包

分层确定性钱包(HD钱包, Hierarchal Desterministic Wallet) 是为比特币创建的一个标准,但也适用于各种依赖secp256k1私钥的区块链。

更详细的技术理解:

类型

常量

ethers.utils.defaultPath "m/44'/60'/0'/0/0"

以太坊在HD钱包中的默认路径

助记词

mnemonic.phrase string

助记词的的助记短语,有12、15、18、21或24个单词长,由locale指定的空格进行分隔。

mnemonic.path string

助记词的HD路径。

mnemonic.locale string

这个助记词所使用的单词表语言。

HDNode

创建实例

ethers.HDNode.fromMnemonic( phrase [ , password [ , wordlist ] ] ) HDNode

为带有可选密码和单词列表的短语返回HDNode

ethers.HDNode.fromSeed( aBytesLike ) HDNode

为带有aBytesLike的种子返回HDNode

ethers.HDNode.fromExtendedKey( extendedKey ) HDNode

extendedKey返回HDNode。如果extendedKey被neutered,HDNode将只能计算地址,而不能计算私钥。

属性

hdNode.privateKey string< DataHexString< 32 > >

HDNode的私钥。

hdNode.publicKey string< DataHexString< 33 > >

HDNode压缩后的公钥。

hdNode.fingerprint string< DataHexString< 4 > >

fingerprint是用来快速匹配父节点和子节点的索引,但是可能会发生冲突,软件应该验证匹配的节点。

大多数开发者不会用到它。

hdNode.parentFingerprint string< DataHexString< 4 > >

父节点的fingerprint。请参阅fingerprint了解更多细节。

大多数开发者不会用到它。

hdNode.address string< 地址(Address) >

HDNode的地址。

hdNode.mnemonic 助记词

HDNode的助记符(如果已知)。

hdNode.path string

HDNode的path(如果已知)。如果助记词也是已知的,这将匹配mnemonic.path

hdNode.chainCode string< DataHexString< 32 > >

The chain code is used as a non-secret private key which is then used with EC-multiply to provide the ability to derive addresses without the private key of child non-hardened nodes.

大多数开发者不会用到它。

hdNode.index number

The index of this HDNode. This will match the last component of the path.

大多数开发者不会用到它。

hdNode.depth number

The depth of this HDNode. This will match the number of components (less one, the m/) of the path.

大多数开发者不会用到它。

hdNode.extendedKey string

此HDNode的序列化字符串表示形式。 并不是所有属性都包含在序列化中,例如助记符和路径,因此序列化和反序列化(使用fromExtendedKey类方法)将造成信息(information)的减少。

Methods

hdNode.neuter( ) HDNode

返回一个新的hdNode实例,其私钥被删除,但所有其他属性保留。 这确保了密钥不会泄露自己或任何派生子密钥,但仍然可以用于计算自己和任何未硬化(non-hardened)子密钥的地址。

hdNode.derivePath( path ) HDNode

返回一个新的HDNode,它是通过派生path找到的hdNode的子节点。

其他的函数

ethers.utils.mnemonicToSeed( phrase [ , password ] ) string< DataHexString< 64 > >

根据BIP-39,将助记词短语转换成种子。

ethers.utils.mnemonicToEntropy( phrase [ , wordlist ] ) string< DataHexString >

根据BIP-39,将助记词短语转换成熵(entropy)。

ethers.utils.isValidMnemonic( phrase [ , wordlist ] ) boolean

通过测试校验和,如果短语是有效的助记短语,则返回true。

日志

这有一些简便的logging(日志)工具,用于简化和标准化Ethers库中的error facilities。

Logger库没有依赖关系,并且非常轻量级,因此可以很容易地包含在每个库中。

审查(Censorship)功能依赖于包含的ether库的一个实例。在大型绑定包中或使用npm link时,可能使用的方式就有所不同。 如果需要使用此功能,请确保你绑定的配置是正确的。

Logger

new ethers.utils.Logger( version )

创建一个新的logger包含所有错误抛出的版本(version)。

Logger.globalLogger( ) Logger

返回单例全局的logger。

Logging Output

logger.debug( ...args ) void

打印 debugging 信息。

logger.info( ...args ) void

打印通用的信息。

logger.warn( ...args ) void

打印 warnings 信息。

Errors

这些函数支持当前的审查(Censorship),还可以帮助创建一个用于检测和处理Ethers中的错误的标准错误模型。

logger.makeError( message [ , code = UNKNOWN_ERROR [ , params ] ] ) Error

创建一个带有消息(message)、可选代码(code)和附加参数集(params)的Error对象。用于需要拒绝错误而不是抛出错误时。

logger.throwError( message [ , code = UNKNOWN_ERROR [ , params ] ] ) never

抛出一个带有消息(message)、可选代码(code)和附加参数集(params)的错误(Error)。

logger.throwArgumentError( message , name , value ) never

抛出一个带有名称(name )和值(value)的INVALID_ARGUMENT错误。

Usage Validation

可以用来确保各种属性(properties)和行为(actions)是安全的。

logger.checkAbstract( target , kind ) void

如果targetkind,则抛出UNSUPPORTED_OPERATION错误, 否则执行与checkNew相同的操作。

这可以用来确保抽象类没有被实例化。

logger.checkArgumentCount( count , expectedCount [ , message ) void

如果count不等于expectedCount,则抛出MISSING_ARGUMENTUNEXPECTED_ARGUMENT错误。

logger.checkNew( target , kind ) void

如果target不是有效的thistarget值,则抛出MISSING_NEW错误。 这有助于确保类的调用者是使用new关键字的。

logger.checkNormalize( message ) void

检查运行环境(environment)是否具有正常运行String.normalize的功能。 如果没有,则抛出UNSUPPORTED_OPERATION错误。

logger.checkSafeUint53( value [ , message ] ) void

如果value作为JavaScript number是不安全的,则抛出NUMERIC_FAULT错误。

Censorship

Logger.setCensorship( censor [ , permanent = false ] ) void

设置错误审查(error censorship),可选地设置哪些错误可以被审查。

在生产应用程序中,这可以通过屏蔽错误的消息(message)和值(values)来防止错误泄露信息。

这可能会影响调试,使调试变得更加困难。

Logger.setLogLevel( logLevel ) void

设置日志级别,使日志输出低于特定的日志级别

Errors

Ethers中的每个错误都有一个代码(code)值,它是一个字符串,将匹配以下错误代码之一。

通用的错误代码

Logger.errors.NOT_IMPLEMENTED

该操作还没有实现。一般发生在:调用尚未完全实现其抽象超类的子类的方法时。

Logger.errors.SERVER_ERROR

与服务器通信时发生错误。

出现这种情况的原因有很多,例如:

  • CORS的问题;大多数都是这种问题,属于最难诊断和修复的,所以熟悉CORS对你是很有帮助的; 一些后端允许你配置你的CORS,例如geth命令行或配置文件,或者是INFURA和Alchemy的通过指定Origins或者methods配置的dashboards等。
  • SSL的问题;例如,如果你试图通过HTTP连接到本地节点,但节点提供的是HTTPS网站的内容。
  • 链接的问题;防火墙正在阻止进入服务器的流量。
  • 服务器的问题;服务器已关闭,或返回500的错误码。
  • 后端DDoS mitigation proxy问题;例如,Etherscan在Cloudflare代理后运行,如果请求是通过特定的用户代理发送的,或者在某些情况下,客户端fingerprint被检测为机器人,Cloudflare代理将阻塞流量

Logger.errors.TIMEOUT

发生超时错误。

Logger.errors.UNKNOWN_ERROR

一个通用的未知错误。

Logger.errors.UNSUPPORTED_OPERATION

不支持该操作。

这种情况可能有多种原因,例如:

Safety Error Codes

Logger.errors.BUFFER_OVERRUN

所需的数据量大于规定的数据量,这将导致数据缓冲区读取超过其末端。

如果合约错误地返回无效的abi编码数据或RLP数据格式不正确,就会发生这种情况。

Logger.errors.NUMERIC_FAULT

对数值进行了无效的操作。

这种情况通常发生在overflow、固定数值类型中的arithmetic underflow、或除零时。

Usage Error Codes

Logger.errors.INVALID_ARGUMENT

参数的类型或值无效。这通常还包括参数的namevalue。 任何接受敏感数据(如私钥)的函数将包含string"[[REDACTED]]"而不是传入的值。

Logger.errors.MISSING_ARGUMENT

未指定预期参数。

Logger.errors.MISSING_NEW

对象是一个类,但没有通过new调用。

Logger.errors.UNEXPECTED_ARGUMENT

传递给函数的参数过多。

Ethereum Error Codes

Logger.errors.CALL_EXCEPTION

尝试调用区块链合约(getter)会导致revert或其他错误,例如gas不足(out-of-gas)或无效的操作码。 这也可能发生在gas估计或等待TransactionReceipt执行过程中失败。

通过合约来确定原因,例如require语句中的失败条件。reason属性可以为此错误的原因提供更多上下文。

Logger.errors.INSUFFICIENT_FUNDS

该帐户正试图进行一个费用超过自身可用余额的交易。

发送帐户必须有足够的以太币来对value、gas limit、以及数据的内在成本进行支付。 数据的内在成本是每个零字节4个gas,每个非零字节68个gas,如果交易不包含to属性,表示要部署一个新合约,则为35000个gas。

Logger.errors.NETWORK_ERROR

以太坊网络验证错误,例如无效的链ID。

Logger.errors.NONCE_EXPIRED

指定的nonce已经被挖出的区块中的交易所使用。

Logger.errors.REPLACEMENT_UNDERPRICED

当替换一个交易(已经发送到网络但尚未被挖出)时,通过相同nonce,新的交易必须比指定替换的的gas price更高。

当天然气价格不足以贿赂(bribe)交易池(期望使其选择新的交易而不是旧的交易时)就会发生这种错误。 一般情况下,新的gas price应该是比原来多50% + 1 wei,即如果使用10 gwei的gas price, 更换的应该是15.000000001gwei。协议没有强制执行这一点,因为它处理未被挖出的交易可以由每个节点自由配置, 但是要确保将交易传播到一个矿工手里,最好是遵循大多数节点启用的默认值。

Logger.errors.TRANSACTION_REPLACED

当一个交易被用户替换时,即广播的新交易替换了节点内存池中相同正在等待被打包的相同nonce的旧交易,就会发生此错误,因为 正在等待被打包的旧交易已经失效了。

这种情况可能有多种原因,但最常见的原因是用户提高了gas price(这改变了交易哈希)去"加速"交易, 或者用户在其客户端"取消"了交易。在任何一种情况下,这通常是通过以更高的gas price去贿赂矿工来实现的。

此错误将具有附加属性:cancelled, hash, reason, receiptreplacement

有关更多详细信息,请参阅wait方法的TransactionResponse

Logger.errors.UNPREDICTABLE_GAS_LIMIT

在估计一个交易所需的gas数量时,查询一个节点以获得其最佳的预测值。

如果节点不能(或不愿意)预测成本值,则会发生此错误。

对于这种情况,最好的解决办法是在交易中手动指定一个gas limit。

该错误还会表明,如果一个没有token的帐户试图发送token,交易无论如何都将失败。

Log Levels

Logger.levels.DEBUG

记录所有输出信息,包括调试信息。

Logger.levels.INFO

仅记录信息、警告和错误的输出。

Logger.levels.WARNING

仅记录警告和错误的输出。

Logger.levels.ERROR

仅记录错误的输出。

Logger.levels.OFF

不输出日志。

Property Utilities

这是一组实用的工具函数,以平台安全的方式来处理属性。

下一个主要版本的ethers将不再与ES3兼容,所以它们中的许多特性将被移除,以支持ES2015及以上版本的内置选项。

ethers.utils.checkProperties( object , check ) void

检查对象是否有包含check参数中的属性,如果不满足,则抛出INVALID_ARGUMENT

ethers.utils.deepCopy( anObject ) any

创建anObject的递归副本。该副本是一个冻结的对象,通过引用复制生成的。

ethers.utils.defineReadOnly( anObject , name , value ) void

使用Object.defineProperty方法在对象上设置只读属性。

ethers.utils.getStatic( aConstructor , key ) any

在一条从aConstructor到所有的祖先的继承链上递归地检查静态方法的key

这用于参考了其他的语言中,在静态方法中的this也会搜索其祖先。

ethers.utils.resolveProperties( anObject ) Promise< any >

返回一个Promise,它解析一个anObject上的所有子值。

ethers.utils.shallowCopy( anObject ) any

返回一个anObject的浅拷贝。这与使用Object.assign({ }, anObject)是一样的。

签名密钥

new ethers.utils.SigningKey( privateKey )

私钥创建一个新的签名密钥。

signingKey.privateKey string< DataHexString< 32 > >

签名密钥的私钥。

signingKey.publicKey string< DataHexString< 65 > >

此签名密钥的未压缩公钥。它是65个字节(130个nibbles),并以0x04作为开头。

signingKey.compressedPublicKey string< DataHexString< 33 > >

此签名密钥的压缩公钥。它是33个字节(66个nibbles),并以0x020x03作为开头。

signingKey.signDigest( digest ) Signature

摘要上签名并返回签名。

signingKey.computeSharedSecret( otherKey ) string< DataHexString< 32 > >

使用otherKey计算ECDH共享密钥。otherKey可以是公钥或私钥,但通常是来自另一方的公钥。

非常推荐各方在将其用作对称密钥之前计算其哈希。

SigningKey.isSigningKey( anObject ) boolean

如果anObject是签名密钥则返回true。

Other Functions

ethers.utils.verifyMessage( message , signature ) string< 地址(Address) >

返回生成签名消息的地址。签名可能有一个不规范的v(即不需要是27或28), 在这种情况下,它将被标准化去计算`recoveryParam`,然后将用于计算地址; 这允许使用v编码额外数据(如EIP-155)的系统使用,因为v参数是完全明确的。

ethers.utils.verifyTypedData( domain , types , value , signature ) string< 地址(Address) >

返回为domain和生成签名的types签名EIP-712后的value的地址。

ethers.utils.recoverPublicKey( digest , signature ) string< DataHexString< 65 > >

返回对给出签名的摘要进行签名的私钥的未压缩公钥(即第一个字节将为0x04)。

ethers.utils.computePublicKey( key [ , compressed = false ] ) string< DataHexString >

计算key的公钥,压缩选项是可选的。key可以是任何形式的公钥(压缩或未压缩)或私钥。

字符串(Strings)

字符串(Strings)的内容通常是人可读(human-readable)的。

在处理区块链时,正确处理人可读(human-readable)和人提供(human-provided)的数据 对于防范资金的损失、权限的错误等非常重要。

Bytes32String

在Solidity存储一个字符串时,前缀是字符串的长度(256位,即32个字节),接着的才是字符串的内容。 这意味着即使很短的字符串也至少需要2个words(64个字节)的存储空间。

大多数情况下,我们处理的是较短的字符串,所以我们不用字符串的长度作为前缀, 而是通过null-terminate处理字符串并将其放入一个word(32个字节)中。 由于null termination只需要一个字节,因此可以在一个word中存储长度不超过31字节的字符串。

注意

长度为31bytes的字符串可能包含少于31个characters,因为UTF-8需要多个字节来编码国际字符。

ethers.utils.parseBytes32String( aBytesLike ) string

返回对Bytes32编码数据进行解码的字符串。

ethers.utils.formatBytes32String( text ) string< DataHexString< 32 > >

返回textbytes32字符串表示形式。如果text长度超过31字节,则会抛出错误。

UTF-8 字符串

ethers.utils.toUtf8Bytes( text [ , form = current ] ) Uint8Array

返回text的UTF-8字节表示,也可通过UnicodeNormalizationForm form对其进行标准化。

ethers.utils.toUtf8CodePoints( text [ , form = current ] ) Array< number >

返回text的codepoints数组,也可通过UnicodeNormalizationForm form对其进行标准化。

Note

This function correctly splits each user-perceived character into its codepoint, accounting for surrogate pairs. This should not be confused with string.split(""), which destroys surrogate pairs, splitting between each UTF-16 codeunit instead.

ethers.utils.toUtf8String( aBytesLike [ , onError = error ] ) string

返回由aBytesLike的UTF-8字节表示的字符串。

onError 是一个自定义的UTF-8 error函数, 如果没有指定它默认为error函数,在发生UTF-8 error时会抛出一个错误。

UnicodeNormalizationForm

在标准化UTF-8数据时,有几种常用的形式, 它们能够以稳定的方式对字符串进行比较或哈希。

ethers.utils.UnicodeNormalizationForm.current

保持当前的 normalization form。

ethers.utils.UnicodeNormalizationForm.NFC

The Composed Normalization Form. This form uses single codepoints which represent the fully composed character.

For example, the é is a single codepoint, 0x00e9.

ethers.utils.UnicodeNormalizationForm.NFD

The Decomposed Normalization Form. This form uses multiple codepoints (when necessary) to compose a character.

For example, the é is made up of two codepoints, "0x0065" (which is the letter "e") and "0x0301" which is a special diacritic UTF-8 codepoint which indicates the previous character should have an acute accent.

ethers.utils.UnicodeNormalizationForm.NFKC

The Composed Normalization Form with Canonical Equivalence. The Canonical representation folds characters which have the same syntactic representation but different semantic meaning.

For example, the Roman Numeral I, which has a UTF-8 codepoint "0x2160", is folded into the capital letter I, "0x0049".

ethers.utils.UnicodeNormalizationForm.NFKD

The Decomposed Normalization Form with Canonical Equivalence. See NFKC for more an example.

Note

Only certain specified characters are folded in Canonical Equivalence, and thus it should not be considered a method to achieve any level of security from homoglyph attacks.

自定义 UTF-8 错误处理

在将字符串转换为其代码点时,可能会出现无效的字节序列。 由于某些情况下可能需要特定的方法来处理UTF-8错误,可以使用自定义错误处理函数,该函数具有如下签名:

errorFunction( reason , offset , bytes , output [ , badCodepoint ] ) number

reason是一个utf-8的错误原因offset是第一次遇到error字节的索引, output已经处理过的(并且可能被修改)的代码点列表和一些错误原因, badCodepoint表示当前计算的代码点,如果它的值是无效的该签名将会被拒绝。

这个函数返回要跳过的字节数,因为offset的值已经被使用过了。

UTF-8 错误原因

ethers.utils.Utf8ErrorReason.BAD_PREFIX

遇到一个字节,该字节开头的UTF-8字节序列是无效的。

ethers.utils.Utf8ErrorReason.MISSING_CONTINUE

A UTF-8 sequence was begun, but did not have enough continuation bytes for the sequence. For this error the ofset is the index at which a continuation byte was expected.

ethers.utils.Utf8ErrorReason.OUT_OF_RANGE

The computed codepoint is outside the range for valid UTF-8 codepoints (i.e. the codepoint is greater than 0x10ffff). This reason will pass the computed badCountpoint into the custom error function.

ethers.utils.Utf8ErrorReason.OVERLONG

Due to the way UTF-8 allows variable length byte sequences to be used, it is possible to have multiple representations of the same character, which means overlong sequences allow for a non-distinguished string to be formed, which can impact security as multiple strings that are otherwise equal can have different hashes.

Generally, overlong sequences are an attempt to circumvent some part of security, but in rare cases may be produced by lazy libraries or used to encode the null terminating character in a way that is safe to include in a char*.

This reason will pass the computed badCountpoint into the custom error function, which is actually a valid codepoint, just one that was arrived at through unsafe methods.

ethers.utils.Utf8ErrorReason.OVERRUN

The string does not have enough characters remaining for the length of this sequence.

ethers.utils.Utf8ErrorReason.UNEXPECTED_CONTINUE

This error is similar to BAD_PREFIX, since a continuation byte cannot begin a valid sequence, but many may wish to process this differently. However, most developers would want to trap this and perform the same operation as a BAD_PREFIX.

ethers.utils.Utf8ErrorReason.UTF16_SURROGATE

The computed codepoint represents a value reserved for UTF-16 surrogate pairs. This reason will pass the computed surrogate half badCountpoint into the custom error function.

Provided UTF-8 Error Handling Functions

There are already several functions available for the most common situations.

ethers.utils.Utf8ErrorFuncs.error

The will throw an error on any error with a UTF-8 sequence, including invalid prefix bytes, overlong sequences, UTF-16 surrogate pairs.

ethers.utils.Utf8ErrorFuncs.ignore

This will drop all invalid sequences (by consuming invalid prefix bytes and any following continuation bytes) from the final string as well as permit overlong sequences to be converted to their equivalent string.

ethers.utils.Utf8ErrorFuncs.replace

This will replace all invalid sequences (by consuming invalid prefix bytes and any following continuation bytes) with the UTF-8 Replacement Character, (i.e. U+FFFD).

交易

类型

未签名交易(UnsignedTransaction)

An unsigned transaction represents a transaction that has not been signed and its values are flexible as long as they are not ambiguous.

unsignedTransaction.to string< Address >

交易的目标地址。

unsignedTransaction.nonce number

交易的nonce值。

unsignedTransaction.gasLimit BigNumberish

交易的 gas limit。

unsignedTransaction.gasPrice BigNumberish

交易的 gas price。

unsignedTransaction.maxFeePerGas BigNumberish

本次交易每单位gas的最高费用。

unsignedTransaction.maxPriorityFeePerGas BigNumberish

本次交易每单位gas的最高优先费用。

unsignedTransaction.data BytesLike

交易的data。

unsignedTransaction.value BigNumberish

交易的value (单位为wei)。

unsignedTransaction.chainId number

此交易的链ID。如果链ID为0或null,则EIP-155失效并使用遗留签名(legacy signing),除非重写覆盖该签名。

交易

表示交易的通用对象。

transaction.hash string< DataHexString< 32 > >

交易哈希,可以用作交易的标识符。这是交易的序列化RLP编码的keccak256表示。

transaction.to string< Address >

交易的目标地址。

transaction.from string< Address >

交易的发起地址。

transaction.nonce number

交易的nonce值。每个从帐户发送到网络的交易都包含这个值,这确保了交易的顺序和不可重复支付的特性。 这个值等于from地址发送到网络的交易数。

transaction.gasLimit 大数(BigNumber)

交易的gas limit。一个帐户必须有足够的以太(ether)来支付gas(在指定的gasPrice)。 在交易结束时,任何未使用的gas都将被退还,如果没有足够的gas来完成执行, 则交易的效果将被revert,但gas会被完全消耗,并抛出了out-of-gas错误。

transaction.gasPrice null | 大数(BigNumber)

交易的每单位gas的价格(单位为wei)。

对于EIP-1559交易,该值为null。

transaction.maxFeePerGas 大数(BigNumber)

交易的每单位gas的最高价格(单位为wei)。

对于非EIP-1559交易,该值为null。

transaction.maxPriorityFeePerGas 大数(BigNumber)

交易的每单位gas的优先费用价格(单位wei)。

对于非EIP-1559交易,该值为null。

transaction.data BytesLike

用于交易的数据。在合约中,这是call data。

transaction.value 大数(BigNumber)

交易的value (单位为wei)。

transaction.chainId number

交易的链ID。这是EIP-155的一部分,用于防止不同网络上的重放攻击(replay attacks)。

例如,如果在ropsten链上进行的交易可能会被打包到homestead链上被使用, 那么在ropsten链上签署的交易可能会在homestead链上执行,即使不是用户故意这样操作的。

有些情况下,可能需要重放,但这是非常罕见的,因此建议总是指定链ID。

transaction.r string< DataHexString< 32 > >

交易的椭圆曲线签名的r部分,准确地说,是点r(从该点可以计算出y和v)的x坐标。

transaction.s string< DataHexString< 32 > >

交易的椭圆曲线签名的s部分。

transaction.v number

交易的椭圆曲线签名的v部分。 This is used to refine which of the two possible points a given x-coordinate can have, 并且在EIP-155中还用于将链ID编码到序列化的交易中。

函数

ethers.utils.accessListify( anAcceslistish ) AccessList

AccessListishanAccessListish标准化为一个AccessList

这对于希望在访问列表的输入参数方面保持灵活性的其他实用程序函数中非常有用, such as when creating a Signer which needs to manipulate a possibly typed transaction envelope.

ethers.utils.parseTransaction( aBytesLike ) 交易

解析经过序列化的交易的属性。

ethers.utils.serializeTransaction( tx [ , signature ] ) string< DataHexString >

计算序列化的交易,可选地使用签名进行序列化。如果没有签名,则返回未签名的序列化交易,可用于计算签名的hash。

如果提供了chainId,则此函数将使用EIP-155,否则将使用legacy serialization。 强烈建议总是指定chainId。

如果签名包含链ID(通过显式地或隐式地使用EIP-155的一个 v_vs),它将被用于计算链ID。

如果交易的链ID和签名之间不匹配,则抛出错误。

Web Utilities

ethers.utils.fetchJson( urlOrConnectionInfo [ , json [ , processFunc ] ] ) Promise< any >

urlOrConnectionInfo中获取并解析JSON内容,body参数json、processFun(在返回前处理result内容)是可选的。

ethers.utils.poll( pollFunc [ , options ] ) Promise< any >

使用PollOptions反复调用pollFunc直到它返回一个非undefined的值。

ConnectionInfo

connection.url string

要连接的URL。

connection.user string

用于Basic Authentication的用户名。默认为null(即不使用基本身份验证)

connection.password string

用于Basic Authentication的密码。默认为null(即不使用基本身份验证)

connection.allowInsecureAuthentication boolean

允许对不安全的HTTP进行Basic Authentication。默认值为false。

connection.timeout number

表示在遇到timeout错误并交易被拒绝之前,需要等待多长的时间。

connection.headers {[key:string]:string}

包含在连接中的额外的头文件。

PollOptions

options.timeout number

表示经过连接多长的时间,才会触发超时错误。

options.floor number

允许Exponential Backoff的最小时间限制。

默认值是0s。

options.ceiling number

允许Exponential Backoff的最大时间限制。

默认值是10s。

options.interval number

Exponential Backoff计算期间所用到的interval。

默认值是250ms。

options.retryLimit number

在发生错误或返回undefined的情况下进行重试的次数。

options.onceBlock Provider

如果指定了该值,轮询将等待来自provider的新区块,然后再执行pollFunc

options.oncePoll Provider

如果指定了该值,provider的每个轮询周期将会进行轮询,然后再执行pollFunc

Wordlists(词表)

词表

wordlist.locale string

词表的locale设置。

wordlist.getWord( index ) string

根据索引返回词表中单词。

wordlist.getWordIndex( word ) number

返回词表中单词的索引。

wordlist.split( mnemonic ) Array< string >

根据locale设置的有效空白字符集,返回被拆分为单个单词的助记符。

wordlist.join( words ) string

通过使用locale设置的标准空格将单词连接在一起,再返回助记符。

Wordlist.check( wordlists ) string< DataHexString< 32 > >

检查所有单词双向的映射是否正确,并返回列表的哈希值。 子类应该使用它来验证词表是否与官方词表的哈希值匹配。

Wordlist.register( wordlist [ , name ] ) void

用词表的列表注册一个wordlist,可以将已注册的name重写。

语言

官方词表可在`ethers.wordlists`找到。 在浏览器中,默认只有英文可用;要包含其他文件(这会增加库的大小),请参阅`ethers`包中的dist文件夹。

ethers.wordlists.cz Wordlist

捷克语 词表.

ethers.wordlists.en Wordlist

英语 词表.

ethers.wordlists.es Wordlist

西班牙语 词表.

ethers.wordlists.fr Wordlist

法语 词表.

ethers.wordlists.it Wordlist

意大利语 词表.

ethers.wordlists.ja Wordlist

日语 词表.

ethers.wordlists.ko Wordlist

韩语 词表.

ethers.wordlists.zh_cn Wordlist

简体中文 词表.

ethers.wordlists.zh_tw Wordlist

繁体中文 词表.

其他的库

现在的ethers库更加模块化,可以有额外的辅助库,这些库不是核心的部分,但可以选择添加只有在指定的某些情况下才需要的功能库。

汇编
硬件钱包

汇编

这个模块应该被视为试验性的。

Ethers ASM Dialect
实用工具
抽象语法树(Abstract Syntax Tree)

Ethers ASM Dialect

本文提供了用于EVM的Ethers ASM Dialect快速、高级的概述, 它是由Ethers ASM Dialect Grammar定义的。

一旦一个程序被高级语言编译成ASM(汇编),或者直接在ASM中进行手动编码,它就需要被编译成字节码。

编译过程执行非常小的操作集,并且有意简化,这与底层EVM字节码密切相关。

操作包括在程序中的嵌入程序(例如,部署辅助程序中嵌入了runtime)和计算跳转操作所需的偏移量。

命令行汇编器可用于编译Ethers ASM Dialect文件或将字节码反汇编为人类可读的操作码和文字。

操作码(Opcodes)

操作码(Opcodes)可以提供功能性语法(functional syntax)或指导性语法(functional syntax)。 对于需要参数的操作码,推荐使用功能性语法,指导性语法会发出警告。

@TODO: Examples

标签

标签是程序中可以跳转到的位置。一个JUMPDEST会自动添加到汇编输出中的这个点。

@TODO: Examples

字面量(Literals)

一个字面量(Literals)执行PUSH操作,表示将数据放入栈中。

提供一个字面量(Literals)能以DataHexString或十进制的字节值的形式。

@TODO: examples

注释

要在Ethers ASM Dialect中输入注释,分号(即 ; )后面的任何文本都会被汇编程序忽略。

作用域

以太坊的常见情况是将一个程序嵌入到另一个程序中。

最常见的用法是在部署字节码中嵌入一个合约运行时字节码,这个字节码可以用作初始化代码(init code)

当程序部署到以太坊时,将使用init transactioninit transactionto 字段的地址为空, data的字段数据中包含字节码。 data字段里的字节码就是程序,执行时返回其他字节码作为结果(result),返回的结果就是要被安装的字节码。

因此,重要的是,嵌入式代码使用相对于自身的跳转,而不是相对于它所嵌入的整个程序, 这也意味着跳转能针对它自己的作用域,而不能针对父作用域或子作用域。这是由汇编程序强制执行的。

作用域可以访问任何子数据段(Data Segment)或子作用域 的偏移量(相对于自身而言),也可以访问程序中任何位置的数据段(Data Segment)作用域的长度。

Ethers ASM Dialect中的每个程序都有一个最高等级的作用域_

数据段(Data Segment)

数据段 允许将任意数据嵌入到程序中,这对于查找表或部署常量很有用。

空的数据段也可以在一个需要带标签的位置中使用,没有JUMPDEST(标签所添加的)。

@TODO: Example

链接

一个 链接 允许访问作用域, 数据段(Data Segment) or 标签.

要访问一个标记项的字节偏移量,使用$foobar

对于标签,目标(target)必须在此范围内是直接可达的。 对于数据段(Data Segment)作用域,它可以位于同一作用域或任何子作用域内。

对于数据段(Data Segment)标签, 还有一种附加类型的链接,它分别提供数据或字节码的长度。Length Link 可以通过#foobar获得, 并作为字面量压入栈中。

堆栈占位符

@TODO: exampl

评价与执行

实用工具

汇编程序

汇编实用程序允许解析和编译Ethers ASM Dialect源文件。

asm.parse( code ) 节点

解析ethers格式的程序集文件并返回抽象语法树(Abstract Syntax Tree)

asm.assemble( node ) string< DataHexString >

执行抽象语法树(Abstract Syntax Tree)节点的汇编,并返回结果(字节码表示)。

反汇编程序

反汇编程序 可以很容易地将字节码转换为一个对象,方便地去检查程序结构。

asm.disassemble( bytecode ) 字节码

返回给定字节码的操作数组。

asm.formatBytecode( operations ) string

创建操作(Operation)数组的格式化输出。

字节码 inherits Array<操作(Operation)>

每个数组索引代表一个操作,将多字节操作(即PUSH)分解为单个操作。

bytecode.getOperation( offset ) 操作(Operation)

通过给定的偏移量在字节码中获得操作(operation)。需要确保偏移处的字节是一个操作, 而不是包含在PUSH中的数据(如果是这种情况将返回null)。

操作(Operation)

操作是来自反汇编字节码流的单个命令。

operation.opcode 操作码(Opcode)

该操作的操作码。

operation.offset number

该操作进入字节码的偏移量。

operation.pushValue string< DataHexString >

如果操作码是PUSH,那么这个输出就是push的值

操作码(Opcode)

asm.Opcode.from( valueOrMnemonic ) 操作码(Opcode)

为给定的数值(例如:0x60 is PUSH1)或助记符字符串(例如:"PUSH1")创建一个新的操作码实例。

属性

opcode.value number

该操作码的值(字节码作为数字)。

opcode.mnemonic string

该操作码的助记符字符串。

opcode.delta number

该操作码将从堆栈中消耗的项的数量。

opcode.alpha number

该操作码将压入栈中的项数。

opcode.doc string

该操作码功能的简短描述。

opcode.isMemory( ) "read" | "write" | "full"

如果操作码访问内存则返回true。

opcode.isStatic( ) boolean

如果操作码不能更改状态(静态的),则返回true。

opcode.isJump( ) boolean

如果操作码是跳转操作,则返回true。

opcode.isPush( ) number

如果操作码不是PUSH*,则返回0; 如果是,则返回该操作码将push的字节。

抽象语法树(Abstract Syntax Tree)

使用Ethers ASM Dialect去解析文件将生成一个抽象语法树。 根节点将始终是name为_ScopeNode

要将文件解析为抽象语法树,请使用parse函数。

类型

位置(Location)

offset number

源代码中到此节点的开始处的偏移量。

length number

源代码中到此节点末尾的字符长度。

source string

此节点的源代码。

节点(Nodes)

@TODO: 在这里放置一个显示层次结构的图表

节点

node.tag string

程序生命周期中此节点的唯一标记。

node.location 位置(Location)

此节点所表示的源代码,和该节点在源代码中的位置(location)。

ValueNode inherits 节点

一个 ValueNode(值节点) 是一个可以操作堆栈的节点。

LiteralNode inherits ValueNode

literalNode.value string

此节点的文字值,可以是DataHexString或十进制数的字符串。

literalNode.verbatim boolean

DataNode上下文中,这是true, 因为在这种情况下,值应该逐字取,不应该添加PUSH操作,否则为false。

PopNode inherits ValueNode

PopNode用于存储栈中隐式弹出的占位符。 它表示隐式占位符(例如$$)或显式占位符(例如$1)的代码,它表示要预期要使用的堆栈位置。

literalNode.index number

这个PopNode所代表的索引。对于隐式占位符是0

LinkNode inherits ValueNode

LinkNode(链接节点) 表示到另一个节点数据的链接,例如$foo#bar

linkNode.label string

目标节点的名称。

linkNode.type "offset" | "length"

表示此节点是用于目标节点的偏移值还是长度值。

OpcodeNode inherits ValueNode

opcodeNode.opcode 操作码(Opcode)

此节点的操作码。

opcodeNode.operands Array< ValueNode >

传递到此节点的所有操作数(operands)的列表。

EvaluationNode inherits ValueNode

EvaluationNode用于执行代码和插入结果,但不生成任何输出程序集,使用{{! code here }}语法。

literalNode.verbatim boolean

DataNode上下文中,这是true, 因为在这种情况下,值应该逐字取,不应该添加PUSH操作,否则为false。

evaluationNode.script string

一段用于计算和生成结果以作为文字使用的代码。

ExecutionNode inherits 节点

ExecutionNode 用于执行代码,但不生成任何输出程序集,使用{{! code here }}语法。

evaluationNode.script string

要执行的代码。任何结果(result)都会被忽略。

LabelledNode inherits 节点

LabelledNode用于任何具有name的节点,因此可以作为LinkNode的目标。

labelledNode.name string

节点的名称。

LabelNode inherits LabelledNode

通过使用@myLabel:引用name,LabelNode将被用作要跳转的位置。 自动在字节码偏移处插入一个JUMPDEST

DataNode inherits LabelledNode

DataNode允许数据直接插入到输出程序集中,使用@myData[ ... ]。 如果需要则对数据进行填充,以确保原本被视为PUSH值的值不会影响数据以外的任何内容。

dataNode.data Array< ValueNode >

子节点,代表逐字插入的一段数据。

ScopeNode inherits LabelledNode

ScopeNode允许一个新的引用框架,所有LinkNode在解析偏移位置时都会使用这个引用框架,使用@myScope{ ... }

scopeNode.statements Array< 节点 >

此作用域的子节点列表。

硬件钱包

LedgerSigner inherits Signer

Ledger Hardware 钱包 是非常受欢迎的。

在 ES6 或 TypeScript 中导入
import { LedgerSigner } from "@ethersproject/hardware-wallets";

API

new LedgerSigner( [ provider [ , type [ , path ] ] ] ) LedgerSigner

连接到Ledger Hardware 钱包。如果未指定类型,则由环境(environment)决定; 在节点中默认是"hid",在浏览器中默认是"u2f"。如果路径(path)不指定,则使用默认的以太坊路径。

试验性的库

试验性的库用于基本库中没有准备好包含的特性。API不是稳定的,而且不遵循semver版本控制, 因此需要它的应用程序应该指定所需的确切版本

这些特性在ethers核心库中是不可用的,所以要使用它们,你必须安装@ethersproject/experimental包并从其中导入它们。

BrainWallet inherits Wallet

Ethers在v4中删除了对BrainWallets的支持,因为它们是不安全的,并且很容易被猜到,从而让攻击者能够窃取资金。 提供这个类是为了确保使用了BrainWallets的旧系统仍然可以恢复其资金和资产。

BrainWallet.generate( username , password [ , progressCallback ] ) BrainWallet

生成BrainWallets,体验功能略有改进,其中生成的钱包有一个助记词。

BrainWallet.generateLegacy( username , password [ , progressCallback ] ) BrainWallet

生成一个与ether v3及更早版本兼容的BrainWallets。

Importing
// Node const { BrainWallet } = require("@ethersproject/experimental"); // ESM/TypeScript import { BrainWallet } from "@ethersproject/experimental";

EIP1193Bridge inherits EventEmitter

EIP1193Bridge允许将普通的EthersSignerProvider作为标准的EIP-1193 Provider公开, 这在与其他库交互时可能很有用。

Importing
// Node const { Eip1193Bridge } = require("@ethersproject/experimental"); // ESM/TypeScript import { Eip1193Bridge } from "@ethersproject/experimental";

NonceManager inherits Signer

NonceManager设计用于为Signer管理nonce,在发送交易时自动增加。

目前NonceManager不处理重复广播(re-broadcast)。 如果您试图向不控制该帐户的节点上的网络发送大量交易,交易池可能会删除您的交易。

将来,如果NonceManager能记住交易并在网络上监听它们,并舍弃重复广播的交易,那就更好不过了。

另一个未来的特性将是某种失败模式。例如,一个交易通常依赖于另一个首先被挖出的交易。

new NonceManager( signer )

创建一个新的NonceManager。

nonceManager.signer Signer

signer管理的nonce值。

nonceManager.provider Provider

与signer关联的provider。

nonceManager.setTransactionCount( count ) void

为signer设置当前交易计数(nonce)。

在使用该类之外的方法与signer交互时,这可能很有用。

nonceManager.incrementTransactionCount( [ count = 1 ] ) void

count增加当前交易数。

在使用该类之外的方法与signer交互时,这可能很有用。

Importing
// Node const { NonceManager } = require("@ethersproject/experimental"); // ESM/TypeScript import { NonceManager } from "@ethersproject/experimental";

命令行接口

Sandbox Utility
Assembler
Ethereum Naming Service
TypeScript
Making Your Own

Sandbox Utility

在学习、调试和管理与以太坊网络的交互过程中,sandbox实用程序提供了一种使用最常用以太坊实用程序的简单方法。

If no command is given, it will enter a REPL interface with many of the ethers utilities already exposed.

Help

用法: ethers [ COMMAND ] [ ARGS ] [ OPTIONS ] 指令:(默认: sandbox) sandbox 运行一个带有ethers的REPL VM环境 init FILENAME 创建一个新的JSON钱包 [ --force ] 覆盖任何现有文件 fund TARGET Fund TARGET with testnet ether info [ TARGET ... ] 到处账户、地址和ENS名称 send TARGET ETHER 从账户accounts[0]向目标TARGET发送ether [ --allow-zero ] 允许发送到零地址 [ --data DATA ] 在交易中包含数据DATA sweep TARGET 从账户accounts[0]向目标TARGET发送所有ether sign-message MESSAGE 用账户accounts[0]签署信息 [ --hex ] 消息内容是十六进制编码 eval CODE 在一个有ethers的虚拟环境运行代码 run FILENAME 在一个有ethers的虚拟环境运行文件 wait HASH 等待交易哈希完成 wrap-ether VALUE Deposit VALUE into Wrapped Ether (WETH) unwrap-ether VALUE 从Wrapped Ether (WETH)赎回VALUE send-token TOKEN ADDRESS VALUE Send VALUE tokens (at TOKEN) to ADDRESS compile FILENAME 编译Solidity合约 [ --no-optimize ] Do not optimize the compiled output [ --warnings ] 警告错误 deploy FILENAME 编译并部署Solidity合约 [ --no-optimize ] Do not optimize the compiled output [ --contract NAME ] 指定要部署的合约 账户选项 --account FILENAME 从(JSON, RAW or mnemonic)文件加载 --account RAW_KEY 使用私钥(insecure *) --account 'MNEMONIC' 使用助记词(insecure *) --account - 对原始密钥或者助记词使用安全入口 --account-void ADDRESS 使用地址作为无效签名者 --account-void ENS_NAME 使用解析地址添加为无效签名者 --account-rpc ADDRESS 从JSON-RPC provider程序添加地址 --account-rpc INDEX 从JSON-RPC provider程序添加索引 --mnemonic-password 提示输入助记密码 --xxx-mnemonic-password Prompt for a (experimental) hard password PROVIDER 选项 (默认: all + homestead) --alchemy 包含Alchemy --etherscan 包含Etherscan --infura 包含INFURA --nodesmith 包含nodesmith --rpc URL 包含一个自定义的JSON-RPC --offline 转储已签署的交易(不发送) --network NETWORK 要连接的网络(默认: homestead) 交易选项 (默认: query network) --gasPrice GWEI 交易的默认gas限额(以wei为单位) --gasLimit GAS 交易的默认gas限额 --nonce NONCE 第一笔交易的初始nonce --yes 始终接受签名和发送 OTHER OPTIONS --wait Wait until transactions are mined --debug 显示错误的堆栈跟踪 --help 显示此用法并退出 --version 显示此版本并退出 (*) 因为在命令行中包含助记符或私钥,在你系统上的其他用户可能可以读取它们,并可能存储在bash历史文件中。不推荐这样做。

示例

创建新的选项
/home/ethers> ethers init wallet.json 创建新的json钱包 - wallet.json 保持密码和文件安全!如果丢失或者遗失,任何人都不能恢复 选择密码: ****** 确认密码: ****** 加密中... 100% 新的账户地址: 0x485bcC23ae2E5038ec7ec9b8DCB2A6A6291cC003 Saved: wallet.json # 如果你打算尝试试用Ropsten 测试网... /home/ethers> ethers --network ropsten fund 0x485bcC23ae2E5038ec7ec9b8DCB2A6A6291cC003 交易哈希: 0x8dc55b8f8dc8076acded97f9e3ed7d6162460c0221e2769806006b6d7d1156e0
发送 Ether and Tokens
# 发送 ether /home/ricmoo> ethers --account wallet.json send ricmoo.firefly.eth 0.123 密码 (wallet.json): ****** 加密... 100% Transaction: To: 0x8ba1f109551bD432803012645Ac136ddd64DBA72 From: 0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C Value: 0.123 ether Nonce: 96 Data: 0x Gas Limit: 21000 Gas Price: 1.2 gwei Chain ID: 1 Network: homestead Send Transaction? (y/N/a) y Response: Hash: 0xc4adf8b379033d7ab679d199aa35e6ceee9a802ca5ab0656af067e911c4a589a # Sending a token (SAI) # NOTE: 合同地址也可以使用,但常用的token合同地址也由以太坊管理 /home/ricmoo> ethers --account wallet.json send-token sai.tokens.ethers.eth ricmoo.firefly.eth 1.0 Sending Tokens: To: 0x8ba1f109551bD432803012645Ac136ddd64DBA72 Token Contract: 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359 Value: 1.0 Password (wallet.json): ****** Decrypting... 100% Transaction: To: 0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359 From: 0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C Value: 0.0 ether Nonce: 95 Data: 0xa9059cbb0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000 Gas Limit: 37538 Gas Price: 1.0 gwei Chain ID: 1 Network: homestead Send Transaction? (y/N/a) y Response: Hash: 0xd609ecb7e3b5e8d36fd781dffceede3975ece6774b6322ea56cf1e4d0a17e3a1
签名信息
/home/ethers> ethers --account wallet.json sign-message 'Hello World' Password (wallet.json): ****** Decrypting... 100% Message: Message: "Hello World" Message (hex): 0x48656c6c6f20576f726c64 Sign Message? (y/N/a) y Signature Flat: 0xca3f0b32a22a5ab97ca8be7e4a36b1e81d565c6822465d769f4faa4aa24539fb122ee5649c8a37c9f5fc8446593674159e3a7b039997cd6ee697a24b787b1a161b r: 0xca3f0b32a22a5ab97ca8be7e4a36b1e81d565c6822465d769f4faa4aa24539fb s: 0x122ee5649c8a37c9f5fc8446593674159e3a7b039997cd6ee697a24b787b1a16 vs: 0x122ee5649c8a37c9f5fc8446593674159e3a7b039997cd6ee697a24b787b1a16 v: 27 recid: 0

Scripting

eval命令可用于从命令行执行简单的单行脚本,这些脚本将传递给其他命令或存储在脚本环境变量中。

获取一个账户的格式化余额
/home/ethers> ethers --network ropsten \ --account wallet.json \ eval \ 'accounts[0].getBalance().then(b => formatEther(b))' 3.141592653589793238
获取当前区块数
/home/ethers> ethers --network rinkeby \ eval "provider.getBlockNumber()" 5761009
将Solidity签名转换为JSON
/home/ethers> ethers eval 'utils.Fragment.from( "function balanceOf(address) view returns (uint)" ).format("json")' | json_pp { "inputs" : [ { "type" : "address", "name" : "owner" } ], "type" : "function", "payble" : false, "stateMutability" : "view", "ouputs" : [ { "type" : "uint256" } ], "name" : "balanceOf", "constant" : true }
Compute a topic hash
/home/ricmoo> ethers eval 'id("Transfer(address,address,uint256")' 0xd99659a21de82e379975ce8df556f939a4ccb95e92144f38bb0dd35730ffcdd5
随机创建一个助记符
/home/ricmoo> ethers eval 'Wallet.createRandom().mnemonic' useful pond inch knock ritual matrix giggle attend dilemma convince coach amazing

使用助记符(带密码)

所有助记符都有一个密码,但默认是使用空字符串(i.e. "")作为密码。如果你的助记符有密码,--mnemonic-password将会提示你输入密码以解密该账户。

/home/ricmoo> ethers --account mnemonic.txt --mnemonic-password 密码 (助记符): ****** 网络: homestead (chainId: 1) homestead> accounts[0].getAddress() <Promise id=0 resolved> '0x6d3F723EC1B73141AA4aC248c3ab34A5a1DAD776' homestead>

使用助记符 (with experimental memory-hard passwords)

--xxx-mnemonic-password类似于--mnemonic-password选项,它使用密码对助记符的帐户进行解密, 然而它首先通过scrypt基于密码的密钥派生函数传递密码,which is intentionally slow and makes a brute-force attack far more difficult.

/home/ricmoo> ethers --account mnemonic.txt --xxx-mnemonic-password Password (mnemonic; experimental - hard): ****** Decrypting... 100% network: homestead (chainId: 1) homestead> accounts[0].getAddress() <Promise id=0 resolved> '0x56FC8792cC17971C19bEC4Ced978beEA44711EeD' homestead>
Note

这仍然是一个实验性的功能(hence the xxx).

Assembler

assembler命令行工具允许您将Ethers ASM Dialect汇编成可部署的EVM bytecode,并将EVM字节码反汇编成人类可读的助记符。

帮助

用法: ethers-asm [ FILENAME ] [ OPTIONS ] 选项 --define KEY=VALUE provide assembler defines --disassemble 反汇编输入字节码 --ignore-warnings 忽略警告 --pic 生成独立位置代码 --target LABEL 输出LABEL字节码 (默认: _) 其他选项 --debug 显示错误的堆栈跟踪 --help 显示此用法并退出 --version 显示此版本并退出

示例输入文件

SimpleStore.asm
; SimpleStore (uint) ; Set the initial value of 42 sstore(0, 42) ; Init code to deploy myContract codecopy(0, $myContract, #myContract) return(0, #myContract) @myContract { ; Non-payable jumpi($error, callvalue) ; Get the Sighash shr({{= 256 - 32 }}, calldataload(0)) ; getValue() dup1 {{= sighash("getValue()") }} jumpi($getValue, eq) ; setValue(uint) dup1 {{= sighash("setValue(uint)") }} jumpi($setValue, eq) ; No matching signature @error: revert(0, 0) @getValue: mstore(0, sload(0)) return (0, 32) @setValue: ; Make sure we have exactly a uint jumpi($error, iszero(eq(calldatasize, 36))) ; Store the value sstore(0, calldataload(4)) return (0, 0) ; There is no *need* for the PUSH32, it just makes ; decompiled code look nicer @checksum[ {{= (defines.checksum ? concat([ Opcode.from("PUSH32"), id(myContract.source) ]): "0x") }} ] }
SimpleStore.bin
0x602a6000556044601160003960446000f334601e5760003560e01c8063209652 0x5514602457806355241077146030575b60006000fd5b60005460005260206000 0xf35b6024361415601e5760043560005560006000f3
Note: 字节码文件语法

一个bin文件可以由多个字节码块组成,每个字节可以选择以0x前缀开头,所有的字节必须为偶数长度(因为字节是必需的,每个字节有2个nibbles)。

所有空白都会被忽略。

汇编例子

汇编器通过汇编阶段的多次遍历,将Ethers ASM Dialect成字节码,每次🏪都接近最终结果。

这允许对字节码的一小部分进行修改和调整,直到字节码稳定。This allows for more compact jump destinations,并允许代码包含更高级的元编程技术。

/home/ethers> ethers-asm SimpleStore.asm 0x602a6000556044601160003960446000f334601e5760003560e01c80632096525514602457806355241077146030575b60006000fd5b60005460005260206000f35b6024361415601e5760043560005560006000f3 # Piping in ASM source code /home/ethers> cat SimpleStore.asm | ethers-asm # 和上面一样 # 设置一个ASM文件检查并添加校验和的定义 /home/ethers> ethers-asm --define checksum SimpleStore.asm 0x602a6000556065601160003960656000f334601e5760003560e01c80632096525514602457806355241077146030575b60006000fd5b60005460005260206000f35b6024361415601e5760043560005560006000f37f10358310d664c9aeb4bf4ce7a10a6a03176bd23194c8ccbd3160a6dac90774d6

选项

--define KEY=VALUE or --define FLAG

这允许键/值对(值是一个字符串)和flags (值是true)传递给汇编程序,这可以在Scripting Blocks中访问,例如{{= defined.someKey }}.

--ignore-warnings

默认情况下,任何警告都将被视为一个错误。This enabled by-passing warnings.

--pic

当程序在汇编的时候,标签通常以绝对字节位置给出,可以跳转到for循环和控制流。这就意味着程序必须安装在特定的位置。

通过指定Position Independent Code flag,代码将以所有偏移量都是相对的方式生成,允许程序在不影响其逻辑的情况下移动。

不过这确实会产生额外的gas成本,即每个偏移量需要8个gas。

--target LABEL

所有程序都有一个名为_ 的作用域,默认情况下是进行汇编的。这个选项允许其他被标记了的目标( 作用域或者数据段(Data Segment)进行汇编 。整个程序仍然按照常规汇编,因此这只影响输出的一部分。

反汇编程序例子

反汇编程序显示给定字节码的偏移量和助记符。这个格式在未来可能会改变,以使人类更易于阅读。

/home/ethers> ethers-asm --disassemble SimpleStore.bin 0000 : 0x2a ; #1 0002 : 0x00 ; #1 0004 : SSTORE 0005 : 0x44 ; #1 0007 : 0x11 ; #1 0009 : 0x00 ; #1 000b : CODECOPY 000c : 0x44 ; #1 000e : 0x00 ; #1 0010 : RETURN 0011 : CALLVALUE 0012 : 0x1e ; #1 0014 : JUMPI 0015 : 0x00 ; #1 0017 : CALLDATALOAD 0018 : 0xe0 ; #1 001a : SHR 001b : DUP1 001c : 0x20965255 ; #4 0021 : EQ 0022 : 0x24 ; #1 0024 : JUMPI 0025 : DUP1 0026 : 0x55241077 ; #4 002b : EQ 002c : 0x30 ; #1 002e : JUMPI 002f*: JUMPDEST 0030 : 0x00 ; #1 0032 : 0x00 ; #1 0034 : REVERT 0035*: JUMPDEST 0036 : 0x00 ; #1 0038 : SLOAD 0039 : 0x00 ; #1 003b : MSTORE 003c : 0x20 ; #1 003e : 0x00 ; #1 0040 : RETURN 0041*: JUMPDEST 0042 : 0x24 ; #1 0044 : CALLDATASIZE 0045 : EQ 0046 : ISZERO 0047 : 0x1e ; #1 0049 : JUMPI 004a : 0x04 ; #1 004c : CALLDATALOAD 004d : 0x00 ; #1 004f : SSTORE 0050 : 0x00 ; #1 0052 : 0x00 ; #1 0054 : RETURN /home/ethers> cat SimpleStore.bin | ethers-asm --disassemble # 和上面一样

Ethereum Naming Service

帮助

用法: ethers-ens COMMAND [ ARGS ] [ OPTIONS ] 命令 lookup [ NAME | ADDRESS [ ... ] ] 查找名称或者地址 commit NAME Submit a pre-commitment [ --duration DAYS ] 注册期限 (默认:365天) [ --salt SALT ] SALT to blind the commit with [ --secret SECRET ] 使用id(SECRET)作为salt [ --owner OWNER ] 目标所有者 (默认:当前账户) reveal NAME Reveal a previous pre-commitment [ --duration DAYS ] 注册期限 (默认:365天) [ --salt SALT ] SALT to blind the commit with [ --secret SECRET ] 使用id(SECRET)作为salt [ --owner OWNER ] 目标所有者 (默认:当前账户) set-controller NAME 设置controller (默认:当前账户) [ --address ADDRESS ] 指定另一个地址 set-subnode NAME 设置一个字节点所有者(默认:当前账户) [ --address ADDRESS ] 指定另一个地址 set-resolver NAME 设置解析器(默认: resolver.eth) [ --address ADDRESS ] 指定另一个地址 set-addr NAME 设置地址记录(默认:当前账户) [ --address ADDRESS ] 指定另一个地址 set-text NAME KEY VALUE 设置一个文本记录 set-email NAME EMAIL 设置电子邮箱文本记录 set-website NAME URL 设置网站文本记录 set-content NAME HASH 设置IPFS内容哈希值 migrate-registrar NAME Migrate from the Legacy to the Permanent Registrar transfer NAME NEW_OWNER 转移注册人所有权 reclaim NAME 由注册人重置controller [ --address ADDRESS ] 指定另一个地址 账户选项 --account FILENAME 从(JSON, RAW or mnemonic)文件加载 --account RAW_KEY 使用私钥(insecure *) --account 'MNEMONIC' 使用助记词(insecure *) --account - 对原始密钥或者助记词使用安全入口 --account-void ADDRESS 使用地址作为无效签名者 --account-void ENS_NAME 使用解析地址添加为无效签名者 --account-rpc ADDRESS 从JSON-RPC provider程序添加地址 --account-rpc INDEX 从JSON-RPC provider程序添加索引 --mnemonic-password 提示输入助记密码 --xxx-mnemonic-password Prompt for a (experimental) hard password PROVIDER 选项 (默认: all + homestead) --alchemy 包含Alchemy --etherscan 包含Etherscan --infura 包含INFURA --nodesmith 包含nodesmith --rpc URL 包含一个自定义的JSON-RPC --offline 转储已签署的交易(不发送) --network NETWORK 要连接的网络(默认: homestead) 交易选项 (默认: query network) --gasPrice GWEI 交易的默认gas限额(以wei为单位) --gasLimit GAS 交易的默认gas限额 --nonce NONCE 第一笔交易的初始nonce --yes 始终接受签名和发送 其他选项 --wait 等待交易完成 --debug 显示错误的堆栈跟踪 --help 显示此用法并退出 --version 显示此版本并退出 (*) 因为在命令行中包含助记符或私钥,在你系统上的其他用户可能可以读取它们,并可能存储在bash历史文件中。不推荐这样做。

示例

TODO examples

TypeScript

帮助

用法: ethers-ts FILENAME [ ... ] [ OPTIONS ] 选项 --output FILENAME 将输出写入FILENAME文件(default: stdout) --force 如果它们存在则覆盖它们 --no-optimize 不运行solc optimizer --no-bytecode 不包括字节码和Factory方法 其他选项 --debug 显示错误的堆栈跟踪 --help 显示此用法并退出 --version 显示此版本并退出 (*) 因为在命令行中包含助记符或私钥,在你系统上的其他用户可能可以读取它们,并可能存储在bash历史文件中。不推荐这样做。

示例

TODO

Making Your Own

cli库的目的是方便您自己创建自己ID命令行工具

CLI

一个CLI处理所有命令行标志、选项和参数的解析,并且实例化一个Plugin来处理命令

一个CLI可以支持多个Plugin在这种情况下,第一个参数被用来决定运行哪一个 (如果没有参数,将选择默认的插件) ,或者可以被设计成独立的, 在这种情况下,正好可以使用一个Plugin并且不允许有命令参数。

addPlugin( command , pluginClass ) void

command添加一个plugin类。在所有选项和标志被使用后,第一个参数将被使用,相关的插件类将被实例化并运行。

setPlugin( pluginClass ) void

设置一个专门的Plugin类,将处理所有的输入。 它不能与addPlugin一起使用,也不会自动接受参数中的命令。

showUsage( [ message = "" [ , status = 0 ] ] ) never

显示CLI的使用帮助屏幕并终止。

run( args ) Promise< void >

通常传入的args数值将会是process.argv.slice(2).

Plugin

每个Plugin管理CLI的每一条命令并且分阶段执行。

如果使用CLI的请求(i.e. 即帮助), 则使用静态方法getHelpgetOptionHelp用来生成帮助界面

Otherwise, a plugin is instantiated and the prepareOptions is called. Each plugin must call super.prepareOptions, otherwise the basic options are not yet processed. During this time a Plugin should consume all the flags and options it understands, since any left over flags or options will cause the CLI to bail and issue an unknown option error. This should throw if a value for a given option is invalid or some combination of options and flags is not allowed.

一旦prepareOptions完成(the returned promise is resolved), 就会调用prepareArguments。这应该验证预期参数的数量,如果有太多或者太少,或者任何参数没有意义,则抛出一个错误。

一旦prepareArguments完成(the returned promise is resolved), 则会调用 run

plugin.network Network

这个插件运行的网络

plugin.provider Provider

这个插件运行的provider对象

plugin.accounts Array< Signer >

使用这个插件可以使用--account,--account-rpc--account-void 传递给插件的账户

plugin.gasLimit 大数(BigNumber)

这个插件应该使用的gas限额。如果未指定则为空。

plugin.gasPrice 大数(BigNumber)

这个插件应该使用的gas价格。如果未指定则为空。

plugin.nonce number

这个插件用户应该使用的初始nonce。

方法

plugin.prepareOptions( argParser [ , verifyOnly = false ] ) Promise< void >
plugin.prepareArgs( args ) Promise< void >
plugin.run( ) Promise< void >
plugin.getAddress( addressOrName [ , message = "" , [ allowZero = false ] ] ) Promise< string >

插件应该使用这种方法来解析地址。如果解析的地址是零地址,并且allowZero不是真值, 则会引发错误。

plugin.dump( header , info ) void

info以良好的格式风格转储到console控制台,并带有header。以后的话,插件可能会支持自动使用该方法的JSON输出格式。

plugin.throwUsageError( [ message = "" ] ) never

停止插件的执行,显示插件的帮助屏幕,带有可选的信息.

plugin.throwError( message ) never

停止插件的执行,显示信息.

静态方法

Plugin.getHelp Help

每个子类都应该实现这个静态方法用于生成帮助屏幕

Plugin.getOptionHelp Array< Help >

如果它支持用于生成帮助屏幕的附加选项,每个子类都应该实现这个静态方法。

ArgParser

ArgParser is used to 用来将一条命令行解析成标志, 选项和参数。

/home/ethers> ethers --account wallet.json --yes send ricmoo.eth 1.0 # An Option ----------^ ^ ^ # - name = "account" | | # - value = "wallet.json" | | # A Flag -----------------------------------+ | # - name = "yes" | # - value = true | # Arguments ------------------------------------+ # - count = 3 # - [ "send", "ricmoo.eth", "1.0" ]

Flags 是一个简单的二进制选项(例如--yes), 如果存在则为真,否则的话为假

选项在一个命令行中需要带一个参数 (例如--account wallet.json, 它有名字account 和它的值wallet.json)

参数是命令行中所有其他值,而并不是通过ArgParser直接访问的.

当一个CLI在运行的时候, 一个ArgParser通过使用prepareOptions来验证命令行, which consumes all flags and options leaving only the arguments behind, which are then passed into prepareArgs.

argParser.consumeFlag( name ) boolean

删除标签name,如果存在,返回true

argParser.consumeMultiOptions( names ) Array< {name:string,value:string} >

删除所有与names数组中的任何名称相匹配的选项,其值返回值的列表(按顺序)。

argParser.consumeOption( name ) string

删除带有name值的选项,并返回该值。如果该选项被多次包含,这将抛出一个UsageError。

argParser.consumeOptions( name ) Array< string >

删除所有选项及其对name的值,并返回(按顺序)值的列表。

Cookbook

一个常见的、简单的代码片段的集合(会随着时间的推移而增加),这些代码片段一般来说是有用的

React Native (and ilk)
交易

React Native (and ilk)

The React Native框架已经变得相当流行并且具有许多流行的分支, 例如Expo.

React Native是基于JavaScriptCore (WebKit的部分) 并且不使用Node.js或常见的Web和DOM API。因此,丢失了许多正常的网络环境或者Node.js实例所能提供的操作。

由于这个原因, 这里提供一个Shims模块来填补这个漏洞

安装

要在React Native中使用ether, 你必须要为所需且缺少的功能提供ether shims,或者使用ethers.js shim.

强烈建议您查看下面的security部分获得关于安装可能影响你的应用程序安全的软件包

安装软件包后, 你可能需要重启你的软件包和company.

Installing
/home/ricmoo/my-react-project> npm install @ethersproject/shims --save
Importing
// Pull in the shims (BEFORE importing ethers) import "@ethersproject/shims" // Import the ethers library import { ethers } from "ethers";

安全

React Native环境不包含用于计算随机私钥的安全随机源。这可能会导致其他人可能猜到的私钥,从而导致资金被盗和资产被操纵。

由于这个原因,强烈建议也安装React Native get-random-values,它必须被包含shims的前面。 如果它工作正常,您应该不会在控制台中收到任何关于丢失安全随机源的警告。

Importing with Secure Random Sources
// 导入crypto getRandomValues shim(在shims**之前**) import "react-native-get-random-values" // 导入ethers shims (在导入ethers**之前**) import "@ethersproject/shims" // 导入ethers库 import { ethers } from "ethers";

交易

计算原始交易

function getRawTransaction(tx) { function addKey(accum, key) { if (tx[key]) { accum[key] = tx[key]; } return accum; } // 提取交易和签名的相关部分 const txFields = "accessList chainId data gasPrice gasLimit maxFeePerGas maxPriorityFeePerGas nonce to type value".split(" "); const sigFields = "v r s".split(" "); // 对已签名的交易进行验证 const raw = utils.serializeTransaction(txFields.reduce(addKey, { }), sigFields.reduce(addKey, { })); // 再次检查一切是否正确能够顺利运行 if (utils.keccak256(raw) !== tx.hash) { throw new Error("serializing failed!"); } return raw; }

迁移指南

这里有一些从Ethers或其他库的旧版本升级时的迁移指南。

Migration: From Web3.js
Migration: From Ethers v4

Migration: From Web3.js

本迁移指南的重点是将web3.js 1.2.9版本迁移到ethers.js v5。

Providers

在ethers中,Providers为连接到ethers网络提供了一个抽象的概念。 它可以用来发布只读查询,并向ethers网络发送签名的状态变化交易。

连接到ethers

// web3 var Web3 = require('web3'); var web3 = new Web3('http://localhost:8545'); // ethers var ethers = require('ethers'); const url = "http://127.0.0.1:8545"; const provider = new ethers.providers.JsonRpcProvider(url);

连接到ethers: Metamask

// web3 const web3 = new Web3(Web3.givenProvider); // ethers const provider = new ethers.providers.Web3Provider(window.ethereum);

签名者

在ethers中,签名是ethers账户的一个抽象概念。它可以用来签署消息和交易,并将签署的交易发送到ethers网络。

在web3中,一个账户可以用来签署消息和交易。

创建签名

// web3 const account = web3.eth.accounts.create(); // ethers (随机创建新账户) const signer = ethers.Wallet.createRandom(); // ethers (连接到JSON-RPC账户) const signer = provider.getSigner();

签署信息

// web3 (使用私钥) signature = web3.eth.accounts.sign('Some data', privateKey) // web3 (使用JSON-RPC账户) // @TODO // ethers signature = await signer.signMessage('Some data')

合约

一个合约对象是ethers网络上智能合约的一个抽象。它允许与智能合约简单交互。

部署合约

// web3 const contract = new web3.eth.Contract(abi); contract.deploy({ data: bytecode, arguments: ["my string"] }) .send({ from: "0x12598d2Fd88B420ED571beFDA8dD112624B5E730", gas: 150000, gasPrice: "30000000000000" }), function(error, transactionHash){ ... }) .then(function(newContract){ console.log('new contract', newContract.options.address) }); // ethers const signer = provider.getSigner(); const factory = new ethers.ContractFactory(abi, bytecode, signer); const contract = await factory.deploy("hello world"); console.log('contract address', contract.address); // 等待合约创建交易完成 await contract.deployTransaction.wait();

与合约交互

// web3 const contract = new web3.eth.Contract(abi, contractAddress); // 只读查询 contract.methods.getValue().call(); // 状态变化的操作 contract.methods.changeValue(42).send({from: ....}) .on('receipt', function(){ ... }); // ethers // 在初始化只读查询合约的时候传递一个provider const contract = new ethers.Contract(contractAddress, abi, provider); const value = await contract.getValue(); // 传递一个signer,为改变状态的操作创建一个合同实例。 const contract = new ethers.Contract(contractAddress, abi, signer); const tx = await contract.changeValue(33); // 等待交易完成 const receipt = await tx.wait();

重载函数

重载函数是具有相同名称但参数类型不同的函数。

在以太网中,调用一个重载合约函数的语法与非重载函数不同。 本节展示了web3 和ethers在调用重载函数时的区别。

查看issue #407更多详情。

// web3 message = await contract.methods.getMessage('nice').call(); // ethers const abi = [ "function getMessage(string) public view returns (string)", "function getMessage() public view returns (string)" ] const contract = new ethers.Contract(address, abi, signer); // 对于不明确的函数(两个具有相同名称的函数名称) // ,也必须指定签名 message = await contract['getMessage(string)']('nice');

数字

BigNumber

转换为BigNumber类型

// web3 web3.utils.toBN('123456'); // ethers (一个数字;必须是在可靠安全的范围内) ethers.BigNumber.from(123456) // ethers (10进制字符串) ethers.BigNumber.from("123456") // ethers (十六进制字符串) ethers.BigNumber.from("0x1e240")

Utilities

哈希

计算web3和ethers中UTF-8字符串的Keccak256哈希值:

// web3 web3.utils.sha3('hello world'); web3.utils.keccak256('hello world'); // ethers (字符串的哈希值) ethers.utils.id('hello world') // ethers (二进制数据的哈希值) ethers.utils.keccak256('0x4242')

Migration: From Ethers v4

这份文件只涵盖了v4中存在的、在v5中发生了一些重要变化的功能。

它没有涵盖所有已添加的新特性,主要目的是帮助那些更新他们的旧脚本和应用程序的人保持功能的一致性。

如果你遇到任何遗漏的变化,请告诉我,我将更新本指南。

BigNumber

命名空间

由于大数(BigNumber)使用的相当频繁, 它已经被移到the top level of the umbrella package.

// v4 ethers.utils.BigNumber ethers.utils.BigNumberish // v5 ethers.BigNumber ethers.BigNumberish

创建实例

bigNumberify总是优先于构造函数,因为它可以缩短[[BigNumber]对象的对象实例化(因为它们是不可变的)这已经被移动到了静态的from类方法中.

// v4 new ethers.utils.BigNumber(someValue) ethers.utils.bigNumberify(someValue); // v5 // - Constructor 是私有的 // - 移除 `bigNumberify` ethers.BigNumber.from(someValue)

合约

ENS 名称解析

解析的地址的名称已更改。如果传递给构造函数的地址是一个ENS名称,那么在对合约进行任何调用之前,该地址将被解析。

解析地址的属性名称从addressPromise改为了resolvedAddress

解析 ENS 名称
// v4 contract.addressPromise // v5 contract.resolvedAddress

Gas 估算

The only difference in gas estimation is that the bucket has changed its name from estimate to estimateGas.

Gas Estimation
// v4 contract.estimate.transfer(toAddress, amount) // v5 contract.estimateGas.transfer(toAddress, amount)

函数

In a contract in ethers, there is a functions bucket, which exposes all the methods of a contract.

All these functions are available on the root contract itself as well and historically there was no difference between contact.foo and contract.functions.foo. The original reason for the functions bucket was to help when there were method names that collided with other buckets, which is rare.

In v5, the functions bucket is now intended to help with frameworks and for the new error recovery API, so most users should use the methods on the root contract.

The main difference will occur when a contract method only returns a single item. The root method will dereference this automatically while the functions bucket will preserve it as an Result.

如果一个方法返回多个项,则没有区别。

This helps when creating a framework, since the result will always be known to have the same number of components as the Fragment outputs, without having to handle the special case of a single return value.

Functions Bucket
const abi = [ // 返回一个值 "function single() view returns (uint8)", // 返回两个值 "function double() view returns (uint8, uint8)", ]; // v4 await contract.single() // 123 await contract.functions.single() // 123 // v5 (notice the change in the .function variant) await contract.single() // 123 await contract.functions.single() // [ 123 ] // v4 await contract.double() // [ 123, 5 ] await contract.functions.double() // [ 123, 5 ] // v5 (no difference from v4) await contract.double() // [ 123, 5 ] await contract.functions.double() // [ 123, 5 ]

错误

命名空间

所有错误现在属于Logger类,相关的函数已经转移到Logger实例中, 它可以包括每个包的版本字符串

全局错误函数已经被移至Logger类方法中。

// v4 ethers.errors.UNKNOWN_ERROR ethers.errors.* errors.setCensorship(censorship, permanent) errors.setLogLevel(logLevel) errors.checkArgumentCount(count, expectedCount, suffix) errors.checkNew(self, kind) errors.checkNormalize() errors.throwError(message, code, params) errors.warn(...) errors.info(...) // v5 ethers.utils.Logger.errors.UNKNOWN_ERROR ethers.utils.Logger.errors.* Logger.setCensorship(censorship, permanent) Logger.setLogLevel(logLevel) const logger = new ethers.utils.Logger(version); logger.checkArgumentCount(count, expectedCount, suffix) logger.checkNew(self, kind) logger.checkNormalize() logger.throwError(message, code, params) logger.warn(...) logger.info(...)

接口

[[接口]]对象经历了最具戏剧性的变化。

它不再是一个元类,现在有一些方法可以简化处理合约接口操作,而不需要进行对象检查和特殊的临界情况

函数

// v4 (example: "transfer(address to, uint amount)") interface.functions.transfer.encode(to, amount) interface.functions.transfer.decode(callData) // v5 interface.encodeFunctionData("transfer", [ to, amount ]) interface.decodeFunctionResult("transfer", data) // 或者你可以使用任何兼容的签名或Fragment对象。 // Notice that signature normalization is performed for you, // e.g. "uint"和"uint256"将被自动转换 interface.encodeFunctionData("transfer(address,uint)", [ to, amount ]) interface.decodeFunctionResult("transfer(address to, uint256 amount)", data)

事件

// v4 (example: Transfer(address indexed, address indexed, uint256) interface.events.Transfer.encodeTopics(values) interface.events.Transfer.decode(data, topics) // v5 interface.encodeFilterTopics("Transfer", values) interface.decodeEventLog("Transfer", data, topics)

Inspection

现在(大部分)关于一个函数或者事件的查询可以直接在Fragment对象上完成

// v4 interface.functions.transfer.name interface.functions.transfer.inputs interface.functions.transfer.outputs interface.functions.transfer.payable interface.functions.transfer.gas // v5 const functionFragment = interface.getFunction("transfer") functionFragment.name functionFragment.inputs functionFragment.outputs functionFragment.payable functionFragment.gas // v4; type is "call" or "transaction" interface.functions.transfer.type // v5; constant is true (i.e. "call") or false (i.e. "transaction") functionFragment.constant // v4 interface.events.Transfer.anonymous interface.events.Transfer.inputs interface.events.Transfer.name // v5 const eventFragment = interface.getEvent("Transfer"); eventFragment.anonymous eventFragment.inputs eventFragment.name // v4 const functionSig = interface.functions.transfer.signature const sighash = interface.functions.transfer.sighash const eventSig = interface.events.Transfer.signature const topic = interface.events.Transfer.topic // v5 const functionSig = functionFragment.format() const sighash = interface.getSighash(functionFragment) const eventSig = eventFragment.format() const topic = interface.getTopic(eventFragment)

钱包

助记短语

mnemonic短语和相关属性已经合并为一个mnemonic对象,它现在也包括locale.

// v4 wallet.mnemonic wallet.path // v5 // - 助记符语法和路径是一个助记符对象 // - 注意: 如果没有助记符,wallet.mnemonic为空 wallet.mnemonic.phrase wallet.mnemonic.path

测试

测试是任何希望保持安全、安全和可靠的库的关键部分。

Ethers目前有超过23000个测试在它的测试套件中, 这些测试都可以作为简单的GZIP-JSON导出文件供其他项目使用。。

每次签入都要运行测试并且结果可以在GitHub CI Action上查看

我们还努力不断地添加新的测试用例,特别是当问题出现时,以确保问题出现在修复之前,在修复之后得到纠正,并包括进来以防止未来的变更导致回退。

大量测试用例是通过使用来自不同的资源(例如Geth)已知正确的实现程序创建的,并且用不同的语言编写,使用多个库进行验证

例如, ABI测试套件是通过程序生成的类型列表来生成的, 对于每个选择随机(有效)值, 然后将其转换成一个Solidity源文件, 使用solc编译,部署到一个运行的Parity节点并且执行,然后捕获它的输出。类似于创建了多少的哈希、事件和选择器测试用例。 cases were created.

测试平台

虽然网络技术发展很快,特别是在Web3领域,但我们尽量保持ether的可访问性。

目前ether应该可以在几乎所有ES3或更好的环境下工作,并在以下环境下运行测试:

如果你觉得有一个环境被忽视了或有建议,请随时打开一个issue on Github.

我们想为Expo和React添加一个测试版本 因为那些开发人员在使用ethers时经常遇到麻烦,所以你对此有经验或者想法,bug us.

下一个主要版本(可能在2021年夏天)将可能回取消对node 8.x的支持,并且将需要ES2015的代理.

Certain features in JavaScript are also avoided, such as look-behind tokens in regular expressions, since these have caused conflicts (at import time) with certain JavaScript environments such as Otto.

Basically, the moral of the story is "be inclusive and don't drop people needlessly".

测试套件

测试套件在@ethersproject/testcases中以GZIP格式提供, 这使得安装和导入比较容易(GZIP和JSON在大多数语言中都很容易使用)。每个测试套件在这个包中也有其可用的模式。

文件名称测试用例 
accounts.json.gzPrivate Keys and addresses in checksum and ICAP formats 
contract-events.json.gz已经编译的Solidity,ABI接口, 输入类型/值与输出类型/值的emitted事件;所有测试都是针对真实的Ethereum节点执行的。 
contract-interface.json.gz已经编译的Solidity, ABI接口, 输入类型/值与输出类型/值、编码和解码的二进制数据以及针对真实Ethereum节点执行的函数调用的标准化值。 
contract-interface-abi2.json.gz合约-接口一样, except with emphasis on the ABIv2 coder which supports nested dynami types and structured data 
contract-signatures.json.gz合同签名和匹配的选择器 
hashes.json.gz针对各种哈希函数的数据和各自的哈希值 
hdnode.json.gzHDNodes (BIP-32) with mnemonics, entropy, seed and computed nodes with pathes and addresses 带有助记符、entropy、seed和带有路径和地址的计算节点的hdnode (BIP-32) 
namehash.json.gzENS名称与计算的namehashes 
nameprep.json.gzIDNA和Nameprep表示法,包括官方矢量 
rlp-coder.json.gz递归长度前缀(RLP)数据和编码 
solidity-hashes.json.gz基于Solidity非标准打包形式的哈希值 
transactions.json.gz带有序列化格式的已签名和未签名交易,包括带有和不带有EIP-155重放保护的交易 
units.json.gz不同单位之间转换的值 
wallets.json.gzKeystore JSON格式的钱包、密码和解密值 
wordlists.json.gz完全解压的BIP-39官方词汇表 
测试组件套 

测试API套件

对于那些直接使用Typescript进行开发的人来说,这里有一些方便的功能

testcases.loadTests( tag ) Array< TestCase >

加载所有给定tag的测试用例.

标签是上述测试用例名称列表中的字符串,不包括任何扩展名(e.g. "solidity-hashes")

testcases.TestCase.TEST_NAME

大多数测试案例都有它的模式,可作为TypeScript类型,使测试每个属性更容易。

Deterministic Random Numbers (DRNG)

当创建测试用例时,我们通常希望从我们不确定使用了什么值的角度获得随机数据,但是我们希望这些值在运行时保持一致。否则就很难重现问题。

在下面的每一个示例中,seed用于控制返回的随机值。确保正确地调整seed,例如在每次迭代中更改值,并在递归函数中连接到seed。

testcases.randomBytes( seed , lower [ , upper ] ) Uint8Array

Return at least lower random bytes, up to upper (exclusive) if specified, given seed. If upper is omitted, exactly /lower bytes are returned.

testcases.randomHexString( seed , lower [ , upper ] ) string< DataHexString >

和randomBytes相同,只是返回值时DataHexString而不是Uint8Array.

testcases.randomNumber( seed , lower , upper ) number

Returns a random number of at least lower and less than upper given seed.

模式

本节仍在进行中,但将概述测试用例及其值的一些更微妙的方面。

在下一个主要版本中,可能会对测试用例进行彻底的检查,使代码覆盖测试更直接,并消除一些冗余。

例如,不再需要分离ABI和ABIv2测试用例,帐户和交易套件可以合并到一个大型集合中。

账户

使用私钥的基本账户信息和计算各种地址形式。

测试时通过[EthereumJS](https://github.com/ethereumjs)和创建的自定义脚本来直接与Geth和cpp实现进行交互来验证的。

请查看: accounts.json.gz

属性意义 
name测试案例的名称 
privateKey私钥 
address地址(消协) 
checksumAddress有校验和调整的情况下的地址 
icapAddressICAP地址 
属性 
示例
{ "name": "random-1023", "address": "0x53bff74b9af2e3853f758a8d2bd61cd115d27782", "privateKey": "0x8ab0e165c2ea461b01cdd49aec882d179dccdbdb5c85c3f9c94c448aa65c5ace", "checksumAddress": "0x53bFf74b9Af2E3853f758A8D2Bd61CD115d27782", "icapAddress": "XE709S6NUSJR6SXQERCMYENAYYOZ2Y91M6A" }

合约接口

程序化生成测试用例,用于测试ABI编码。

示例
{ "name": "random-1999", "source": "contract Test {\n function test() constant returns (address, bool, bytes14[1]) {\n address a = address(0x061C7F399Ee738c97C7b7cD840892B281bf772B5);\n bool b = bool(true);\n bytes14[1] memory c;\n c[0] = bytes14(0x327621c4abe12d4f21804ed40455);\n return (a, b, c);\n }\n}\n", "types": "[\"address\",\"bool\",\"bytes14[1]\"]", "interface": "[{\"constant\":true,\"inputs\":[],\"name\":\"test\",\"outputs\":[{\"name\":\"\",\"type\":\"address\"},{\"name\":\"\",\"type\":\"bool\"},{\"name\":\"\",\"type\":\"bytes14[1]\"}],\"type\":\"function\"}]\n", "bytecode": "0x6060604052610175806100126000396000f360606040526000357c010000000000000000000000000000000000000000000000000000000090048063f8a8fd6d1461003957610037565b005b610046600480505061009d565b604051808473ffffffffffffffffffffffffffffffffffffffff1681526020018315158152602001826001602002808383829060006004602084601f0104600f02600301f150905001935050505060405180910390f35b600060006020604051908101604052806001905b60008152602001906001900390816100b157905050600060006020604051908101604052806001905b60008152602001906001900390816100da5790505073061c7f399ee738c97c7b7cd840892b281bf772b59250600191506d327621c4abe12d4f21804ed404557201000000000000000000000000000000000000028160006001811015610002579090602002019071ffffffffffffffffffffffffffffffffffff191690818152602001505082828295509550955061016d565b50505090919256", "result": "0x000000000000000000000000061c7f399ee738c97c7b7cd840892b281bf772b50000000000000000000000000000000000000000000000000000000000000001327621c4abe12d4f21804ed40455000000000000000000000000000000000000", "values": "[{\"type\":\"string\",\"value\":\"0x061C7F399Ee738c97C7b7cD840892B281bf772B5\"},{\"type\":\"boolean\",\"value\":true},[{\"type\":\"buffer\",\"value\":\"0x327621c4abe12d4f21804ed40455\"}]]", "normalizedValues": "[{\"type\":\"string\",\"value\":\"0x061C7F399Ee738c97C7b7cD840892B281bf772B5\"},{\"type\":\"boolean\",\"value\":true},[{\"type\":\"buffer\",\"value\":\"0x327621c4abe12d4f21804ed40455\"}]]", "runtimeBytecode": "0x60606040526000357c010000000000000000000000000000000000000000000000000000000090048063f8a8fd6d1461003957610037565b005b610046600480505061009d565b604051808473ffffffffffffffffffffffffffffffffffffffff1681526020018315158152602001826001602002808383829060006004602084601f0104600f02600301f150905001935050505060405180910390f35b600060006020604051908101604052806001905b60008152602001906001900390816100b157905050600060006020604051908101604052806001905b60008152602001906001900390816100da5790505073061c7f399ee738c97c7b7cd840892b281bf772b59250600191506d327621c4abe12d4f21804ed404557201000000000000000000000000000000000000028160006001811015610002579090602002019071ffffffffffffffffffffffffffffffffffff191690818152602001505082828295509550955061016d565b50505090919256" }

合约签名

计算的ABI签名和选择器哈希值。

示例
{ "name": "random-1999", "sigHash": "0xf51e9244", "abi": "[{\"constant\":false,\"inputs\":[{\"name\":\"r0\",\"type\":\"string[2]\"},{\"name\":\"r1\",\"type\":\"uint128\"},{\"components\":[{\"name\":\"a\",\"type\":\"bytes\"},{\"name\":\"b\",\"type\":\"bytes\"},{\"name\":\"c\",\"type\":\"bytes\"}],\"name\":\"r2\",\"type\":\"tuple\"},{\"name\":\"r3\",\"type\":\"bytes\"}],\"name\":\"testSig\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"test\",\"outputs\":[{\"name\":\"r0\",\"type\":\"string[2]\"},{\"name\":\"r1\",\"type\":\"uint128\"},{\"components\":[{\"name\":\"a\",\"type\":\"bytes\"},{\"name\":\"b\",\"type\":\"bytes\"},{\"name\":\"c\",\"type\":\"bytes\"}],\"name\":\"r2\",\"type\":\"tuple\"},{\"name\":\"r3\",\"type\":\"bytes\"}],\"payable\":false,\"stateMutability\":\"pure\",\"type\":\"function\"}]", "signature": "testSig(string[2],uint128,(bytes,bytes,bytes),bytes)" }

哈希

示例
{ "data": "0x3718a88ceb214c1480c32a9d", "keccak256": "0x82d7d2dc3d384ddb289f41917b8280675bb1283f4fe2b601ac7c8f0a2c2824fa", "sha512": "0xe93462bb1de62ba3e6a980c3cb0b61728d3f771cea9680b0fa947b6f8fb2198a2690a3a837495c753b57f936401258dfe333a819e85f958b7d786fb9ab2b066c", "sha256": "0xe761d897e667aa72141dd729264c393c4ddda5c62312bbd21b0f4d954eba1a8d" }

等级确定的节点 (BIP-32)

BIP-32 HD Wallets的测试用例

示例
{ "name": "trezor-23", "entropy": "0xf585c11aec520db57dd353c69554b21a89b20fb0650966fa0a9d6f74fd989d8f", "mnemonic": "void come effort suffer camp survey warrior heavy shoot primary clutch crush open amazing screen patrol group space point ten exist slush involve unfold", "locale": "en", "password": "TREZOR", "hdnodes": [ { "path": "m", "address": "0xfd8eb95169ce57eab52fb69bc6922e9b6454d9aa", "privateKey": "0x679bf92c04cf16307053cbed33784f3c4266b362bf5f3d7ee13bed6f2719743c" }, { "address": "0xada964e9f10c4fc9787f9e17f00c63fe188722b0", "privateKey": "0xdcbcb48a2b11eef0aab93a8f88d83f60a3aaabb34f9ffdbe939b8f059b30f2b7", "path": "m/8'/8'/2/3/4" }, { "privateKey": "0x10fd3776145dbeccb3d6925e4fdc0d58b452fce40cb8760b12f8b4223fafdfa6", "address": "0xf3f6b1ef343d5f5f231a2287e801a46add43eb06", "path": "m/1'/3'" }, { "address": "0xb7b0fdb6e0f79f0529e95400903321e8a601b411", "privateKey": "0x093a8ff506c95a2b79d397aed59703f6212ff3084731c2f03089b069ae76e69d", "path": "m/8'/4'/7'" }, { "path": "m/7'/5'/11", "privateKey": "0x6bd79da4dfa7dd0abf566a011bdb7cba0d28bba9ca249ba25880d5dabf861b42", "address": "0x1b3ad5fa50ae32875748107f4b2160829cc10536" }, { "path": "m/9'/6'/2'/7'/3'", "address": "0x42eb4bed59f3291d02387cf0fb23098c55d82611", "privateKey": "0xfc173acba7bc8bb2c434965d9e99f5a221f81add421bae96a891d08d60be11dd" } ], "seed": "0x01f5bced59dec48e362f2c45b5de68b9fd6c92c6634f44d6d40aab69056506f0e35524a518034ddc1192e1dacd32c1ed3eaa3c3b131c88ed8e7e54c49a5d0998" }

ENS Namehash

ENS Namehash Algorithm的测试用例

Examples
{ "expected": "0x33868cc5c3fd3a9cd3adbc1e868ea133d2218f60dc2660c3bc48d8b1f4961384", "name": "ViTalIk.WALlet.Eth", "test": "mixed case" }

RLP Coder

示例
{ "name": "arrayWithNullString3", "encoded": "0xc3808080", "decoded": [ "0x", "0x", "0x" ] }

Solidity 哈希

对Solidity哈希函数非标准打包形式的测试

这些测试是通过程序化地生成随机签名和与这些签名相匹配的值,构建等价的Soldity,将其编译并部署到一个Parity节点,然后评估响应。

示例
{ "name": "random-1999", "keccak256": "0x7d98f1144a0cd689f720aa2f11f0a73bd52a2da1117175bc4bacd93c130966a1", "ripemd160": "0x59384617f8a06efd57ab106c9e0c20c3e64137ac000000000000000000000000", "sha256": "0xf9aeea729ff39f8d372d8552bca81eb2a3c5d433dc8f98140040a03b7d81ac92", "values": [ "0xcdffcb5242e6", "0xc1e101b60ebe4688", "0x5819f0ef5537796e43bdcd48309f717d6f7ccffa", "0xec3f3f9f", false, true ], "types": [ "int184", "int176", "address", "int64", "bool", "bool" ] }

交易

启用和禁用EIP-155的序列化已签名和未签名交易

示例
{ "name": "random-998", "privateKey": "0xd16c8076a15f7fb583f05dc12686fe526bc59d298f1eb7b9a237b458133d1dec", "signedTransactionChainId5": "0xf8708391d450848517cfba8736fcf36da03ee4949577303fd4e0acbe72c6c116acab5bf63f0b1e9c8365fdc7827dc82ea059891894eb180cb7c6c45a52f62d2103420d3ad0bc3ba518d0a25ed910842522a0155c0ea2aee2ea82e75843aab297420bad907d46809d046b13d692928f4d78aa", "gasLimit": "0x36fcf36da03ee4", "to": "0x9577303fd4e0acbe72c6c116acab5bf63f0b1e9c", "data": "0x7dc8", "accountAddress": "0x6d4a6aff30ca5ca4b8422eea0ebcb669c7d79859", "unsignedTransaction": "0xed8391d450848517cfba8736fcf36da03ee4949577303fd4e0acbe72c6c116acab5bf63f0b1e9c8365fdc7827dc8", "nonce": "0x91d450", "gasPrice": "0x8517cfba", "signedTransaction": "0xf8708391d450848517cfba8736fcf36da03ee4949577303fd4e0acbe72c6c116acab5bf63f0b1e9c8365fdc7827dc81ba05030832331e6be48c95e1569a1ca9505c495486f72d6009b3a30fadfa05d9686a05cd3116b416d2362da1e9b0ca7fb1856c4e591cc22e63b395bd881ce2d3735e6", "unsignedTransactionChainId5": "0xf08391d450848517cfba8736fcf36da03ee4949577303fd4e0acbe72c6c116acab5bf63f0b1e9c8365fdc7827dc8058080", "value": "0x65fdc7" }

单位

单位转换。

示例
{ "name": "one-two-three-3", "gwei_format": "-1234567890123456.789012345", "ether_format": "-1234567.890123456789012345", "gwei": "-1234567890123456.789012345", "ether": "-1234567.890123456789012345", "finney": "-1234567890.123456789012345", "wei": "-1234567890123456789012345", "finney_format": "-1234567890.123456789012345" }

钱包

测试JSON密钥库格式。

示例
{ "mnemonic": null, "name": "secretstorage_password", "type": "secret-storage", "password": "foo", "privateKey": "0xf03e581353c794928373fb0893bc731aefc4c4e234e643f3a46998b03cd4d7c5", "hasAddress": true, "json": "{\"address\":\"88a5c2d9919e46f883eb62f7b8dd9d0cc45bc290\",\"Crypto\":{\"cipher\":\"aes-128-ctr\",\"ciphertext\":\"10adcc8bcaf49474c6710460e0dc974331f71ee4c7baa7314b4a23d25fd6c406\",\"cipherparams\":{\"iv\":\"1dcdf13e49cea706994ed38804f6d171\"},\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":262144,\"p\":1,\"r\":8,\"salt\":\"bbfa53547e3e3bfcc9786a2cbef8504a5031d82734ecef02153e29daeed658fd\"},\"mac\":\"1cf53b5ae8d75f8c037b453e7c3c61b010225d916768a6b145adf5cf9cb3a703\"},\"id\":\"fb1280c0-d646-4e40-9550-7026b1be504a\",\"version\":3}\n", "address": "0x88a5c2d9919e46f883eb62f7b8dd9d0cc45bc290" }

Contributing and Hacking

我编写ethers.js库是出于需要的,并且随着时间的推移已经有组织的发展起来。

很多事情都是有原因的(至少在当时),但我总是欢迎批评,并且完全愿意改变我对事情的看法。

申请拉取代码请求, 不过请记住几点:

一般来说, 在开始一个pull请求之前请先提出一个问题, 这样我们可以进行公开讨论,找出解决问题/功能的最佳办法:)

构建

不巧的是,以太坊的构建过程并不是超级简单的,但我已经试图使它尽可能的简单化。

这是一个mono repo,它试图与大量环境、构建工具和平台兼容,这就是为什么它必须做一些奇怪的事情

misc/admin文件夹中有几个自定义脚本可以帮助管理monorepo。开发人员通常不需要担心这些问题,因为 它们都是被npm 运行脚本 操作包裹封装的。

安装
# Clone the repository /home/ricmoo> git clone https://github.com/ethers-io/ethers.js.git /home/ricmoo> cd ethers.js # Install all dependencies: # - Hoists all sub-package dependencies in the package.json (preinstall) # - Installs all the (hoisted) dependencies and devDependencies (install) # - Build the rat-nests (in .package_node_modules) (postinstall) # - Create a dependency graph for the TypeScript (postinstall) # - Link the rat-nets into each project (postinstall) /home/ricmoo/ethers.js> npm install

进行更改

一旦你的环境设置好, 你应该能够简单地开启自动构建特性,并且能够修改TypeScript源代码.

Watching and Building
# 开始观察文件并在文件发生变化时重新构建 /home/ricmoo/ethers.js> npm run auto-build # 或者如果你不想观察而只是构建 /home/ricmoo/ethers.js> npm run build

创建浏览器适用的软件

要创建可以直接在浏览器中使用的文件,需要建立分布式的文件(位于packages/ethers/dist)需要被构建,这需要几个中间的构建、脚本和对于各种rollup脚本的执行.

Building Distribution Files
# 如果你需要重构所有的库(esm + cjs)和dist文件 # 注意:这需要node 10或者更新的 /home/ricmoo/ethers.js> npm run build-all

测试

测试
# 重构所有文件(npm run build-all)同时捆绑测试用例进行测试 /home/ricmoo/ethers.js> npm test # Often you don't need the full CI experience /home/ricmoo/ethers.js> npm run test-node

Distribution

大多数开发者不应该需要这个步骤,但对于分叉以太坊和创建替代物的人来说(例如,如果你有一个非EVM兼容链,但试图重新使用这个包)。

这个脚本将重建整个ether项目,将它与npm进行比较,重写包版本,更新内部哈希值,重写各种TypeScript文件(以绕过一些ES+TS对shake和链接的限制),重写映射文件,打包去除依赖的版本,基本上就是一堆东西。

如果你使用这个并且陷入了困境, 请给我发消息.

Preparing the Distribution
# Prepare all the distribution files # - Remove all generated files (i.e. npm run clean) # - Re-install all dependencies, hoisting, etc. (npm install) # - Spell check all strings in every TypeScript files # - Build everything from scratch with this clean install # - Compare local with npm, bumping the version if changed # - Build everything again (with the updated versions) # - Update the CHANGELOG.md with the git history since the last change /home/ricmoo/ethers.js> npm run update-version
Do NOT check in dist files in a PR

对于拉取代码的请求,请只提交docs.wrm/ andpackages/*/src.ts/文件夹下的文件。 我将自己准备发布构建 并且保持PR的相关性,这样更容易验证修改。

Publishing

同样,这对大多数开发者来说应该是没有必要的。这个步骤需要使用misc/admin/cmds/config-set脚本来设置一些值, 包括私钥、NPM会话密钥、AWS访问密钥。GitHub API令牌,等等

配置文件是用大约30秒的基于scrypt密码的,密钥推导功能进行加密,因此对该文件进行暴力破解代价是相当昂贵的

配置文件还包含一个纯文本的助记符。这是个money-pot(钱罐子)。在这个账户上放置一笔一定数量的以太币或比特币,并为这个账户设置一个 为这个账户设置电子邮件警报。

任何攻击者都会在你的加密配置中遇到,他们可以立即访问明文助记符,因此他们可以选择立即窃取以太网(i.e. the responsible-disclosure bond)。

如果你看到这个以太坊被盗,你的加密文件就讲会被破坏。Rotate all your AWS keys, NPM session keys, etc. immedately.

@TODO: document all the keys that need to be set for each step

Preparing the Distribution
# Publish # - Update any changed packages to NPM # - Create a release on GitHub with the latest CHANGELOG.md description # - Upload the bundled files the the CDN # - Flush the CDN edge caches /home/ricmoo/ethers.js> npm run publish-all

文档

这些文件是用Flatworm文档生成工具生成的,该工具是为编写ethers的文档而编写的。

风格指南(这一章会有更多内容):

构建

要构建文档,您应该首先遵循以上步骤来构建ethers库.

构建文档将生成几种类型的输出:

构建文档
/home/ricmoo/ethers.js> npm run build-docs

评估

在构建文档时,所有的代码样本都会通过一个JavaScript虚拟机运行,以确保示例代码中没有错别字,同时也会将结果的准确输出注入到输出中,所以不需要保持结果和代码的同步。

然而,在做许多小的改动时,这可能是一个有点头痛的问题,所以为了更快地建立文档,你可以跳过评估步骤,这将直接注入代码。

创建跳过评估的文档
/home/ricmoo/ethers.js> npm run build-docs -- --skip-eval

预览更改

要在本地预览更改,你可以使用任何标准网络服务器并且从/docs/文件夹中运行, 或者使用内置的Web服务器

与普通网络开发一样的注意事项也适用,比如在修改(和重新构建)文档后,要刷新浏览器的缓存。在改变(和重新构建)文档后,要刷新浏览器缓存。

运行一个网络服务器
/home/ricmoo/ethers.js> npm run serve-docs

其他资源

在互联网上有很多文档可以帮助您入门,学习更多或者涵盖高级主题。这里有一些资源可以查看。

Ethereum 相关

相关教程

我不管理或维护这些教程,但我遇到过它们。 如果一个链接已经失效或过时了,请让我知道,我会更新它