ethers.js库旨在为以太坊区块链及其生态系统提供一个小而完整的 JavaScript API 库,它最初是与 ethers.io 一起使用,现在已经扩展为更通用的库。
ethers.js 中文文档由登链社区组织翻译, Git 代码库在 https://github.com/lbc-team/ethers.js ,欢迎大家提交 PR 一起贡献。
- 将私钥保存在客户端,安全 可信赖
- 可支持导入和导出的 JSON钱包文件 (Geth, Parity and crowdsale)
- 导入和导出 BIP39 助记词 (需备份的12个词或短语) 和 HD(分层确定性)钱包(英语,意大利语,日语,韩语,简体中文,繁体中文; 即将推出更多语言)
- 从任何合约ABI创建 JavaScript 元类对象,包括 ABIv2 和 可读的ABI
- 支持通过 JSON-RPC, INFURA, Etherscan, Alchemy, Cloudflare 或 MetaMask连接到以太坊节点
- ENS 名称 是"一等公民"; 它们可以在任何可以使用以太坊地址的地方使用
- 库非常小 (压缩~88kb; 未压缩~284kb)
- 功能完整 满足所有的以太坊相关需求
- 文档全面:中文文档 及英文文档
- 维护和新增的大量测试用例
- 已经支持 TypeScript ,附带定义文件和完整的TypeScript源文件
- 宽松的 MIT 协议许可 (包括所有依赖); 完全开源可以随意使用
本版本(v5.4)将保持更新,可通过以下链接到旧版本的文档。
以太坊中各种类和函数都可以从@ethersproject下的子库中手动进行导入,但对于大多数项目来说,用完整的总库是最简单的入门方式。
/home/ricmoo> npm install --save ethers
const { ethers } = require("ethers");
import { ethers } from "ethers";
出于安全,通常较好的方式是将这个库复制到你的web服务器中来进行各种操作。
但若想快速构建实例展示,可以将我们的CDN加载到你的web应用程序中。
<script type="module">
import { ethers } from "https://cdn.ethers.io/lib/ethers-5.2.esm.min.js";
// Your code here...
</script>
<script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js"
type="application/javascript"></script>
这部分将会不断完善...
Provider | Provider(提供者)是一个用于连接以太坊网络的抽象类,提供了只读形式来访问区块链网络和获取链上状态。 | |
Signer | Signer(签名器)通常是以某种方式直接或间接访问私钥,可以签名消息和在已授权网络中管理你账户中的以太币来进行交易。 | |
Contract | Contract(合约)是一个运行在以太坊网络上表示现实中特定合约的抽象,应用可以像使用JavaScript对象一样使用它。 | |
Common Terms | |
在以太坊上去开发和测试的最快、最简单的方法是使用MetaMask, 它是一个基于浏览器的扩展程序,提供了:
const provider = new ethers.providers.Web3Provider(window.ethereum)
const signer = provider.getSigner()
JSON-RPC API 另一种与以太坊交互的比较流行的方式,用在所有主要的以太坊节点 (如 Geth 和 Parity) 以及许多第三方web服务 (如 INFURA)。 它通常提供了:
const provider = new ethers.providers.JsonRpcProvider();
const signer = provider.getSigner()
当你有了Provider之后, 你就可以通过只读的方式连接区块链, 此外,你还可以使用它来查询当前状态、获取历史日志、查找部署的代码等等。
await provider.getBlockNumber()
// 16383845
balance = await provider.getBalance("ethers.eth")
// { BigNumber: "182334002436162568" }
ethers.utils.formatEther(balance)
// '0.182334002436162568'
ethers.utils.parseEther("1.0")
// { BigNumber: "1000000000000000000" }
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),如下例所示:
const daiAddress = "dai.tokens.ethers.eth";
const daiAbi = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function balanceOf(address) view returns (uint)",
"function transfer(address to, uint amount)",
"event Transfer(address indexed from, address indexed to, uint amount)"
];
const daiContract = new ethers.Contract(daiAddress, daiAbi, provider);
await daiContract.name()
// 'Dai Stablecoin'
await daiContract.symbol()
// 'DAI'
balance = await daiContract.balanceOf("ricmoo.firefly.eth")
// { BigNumber: "2413468059122458201631" }
ethers.utils.formatUnits(balance, 18)
// '2413.468059122458201631'
const daiWithSigner = contract.connect(signer);
const dai = ethers.utils.parseUnits("1.0", 18);
tx = daiWithSigner.transfer("ricmoo.firefly.eth", dai);
daiContract.on("Transfer", (from, to, amount, event) => {
console.log(`${ from } sent ${ formatEther(amount) } to ${ to}`);
});
myAddress = "0x8ba1f109551bD432803012645Ac136ddd64DBA72";
filter = daiContract.filters.Transfer(null, myAddress)
// {
// address: 'dai.tokens.ethers.eth',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
// }
daiContract.on(filter, (from, to, amount, event) => {
console.log(`I got ${ formatEther(amount) } from ${ from }.`);
});
myAddress = await signer.getAddress()
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72'
filterFrom = daiContract.filters.Transfer(myAddress, null);
// {
// address: 'dai.tokens.ethers.eth',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
// }
filterTo = daiContract.filters.Transfer(null, myAddress);
// {
// address: 'dai.tokens.ethers.eth',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
// }
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
// }
// ]
await daiContract.queryFilter(filterFrom, -10000)
await daiContract.queryFilter(filterTo)
signature = await signer.signMessage("Hello World");
// '0x776d4dbf69ee5ed47b3250c56dbcec7ac3a59fb64447a480dcbfe05e2431547b02cf5200876ea6a9e018680dda31a9283cb0230196782f3d48dba450f0176d141b'
message = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
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 ]
signature = await signer.signMessage(messageBytes)
// '0xc848253c4c267c12d6c039c0a367b9a1c3bc683300bad5f1f9455df67cbc36c17ddf1e28a705420cf19b0a634f86dd13f6eee8eea2b0316f9741785aa31a74d31b'
这是 以太坊 区块链中某些方面的简要概述,开发者应当了解和知晓,以便充分地进行开发。
目前以下的内容较少,但会随着时间不断地进行扩展。
日志和过滤器(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) ] | |
匹配日志的例子 | |
filter = {
address: tokenAddress,
topics: [
id("Transfer(address,address,uint256)"),
hexZeroPad(myAddress, 32)
]
};
filter = {
address: tokenAddress,
topics: [
id("Transfer(address,address,uint256)"),
null,
hexZeroPad(myAddress, 32)
]
};
filter = {
address: tokenAddress,
topics: [
id("Transfer(address,address,uint256)"),
null,
[
hexZeroPad(myAddress, 32),
hexZeroPad(myOtherAddress, 32),
]
]
};
在这里解释一下,为了简化, 合约的API如下:
abi = [
"event Transfer(address indexed src, address indexed dst, uint val)"
];
contract = new Contract(tokenAddress, abi, provider);
contract.filters.Transfer(myAddress)
// {
// address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
// }
contract.filters.Transfer(null, myAddress)
// {
// address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
// }
contract.filters.Transfer(myAddress, otherAddress)
// {
// address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72',
// '0x000000000000000000000000ea517d5a070e6705cc5467858681ed953d285eb9'
// ]
// }
contract.filters.Transfer(null, [ myAddress, otherAddress ])
// {
// address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
// topics: [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// [
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72',
// '0x000000000000000000000000ea517d5a070e6705cc5467858681ed953d285eb9'
// ]
// ]
// }
这是一个在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 price是被用来出价的,表明你每执行单位愿意支付的金额,来使得你的交易被处理。
虽然所有开发人员都应该关注安全性,但在区块链领域,开发人员还必须特别留意哪些可能被利用的漏洞。
一旦一个问题具备经济动机去利用它,就会有更大的风险,而对于区块链应用程序来说,攻击它就变得非常有价值。
除了应用程序开发人员可能需要担心的许多其他安全问题之外,还有一些额外的矢量是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";
const json = wallet.encrypt(password, {
scrypt: {
N: 64
}
});
处理网络中的变化(例如 Görli vs Mainnet)是非常复杂的,一个轻微的故障最多会让你的应用程序看起来很混乱,最坏的情况下会导致资金的损失、隐私数据的泄露或错误传导一个请求要所执行的内容。
幸运的是,正常用户应该永远不会改变他们的网络,除非他们被欺骗或者弄错了。
这是开发人员主要需要担心的问题,大多数开发人员应该理解在应用程序操作期间,会引起页面的重载(这已经是许多客户端的默认行为)和网络的变化,这会引起很多问题。
因此,发生网络变化时,最佳实践是简单地刷新页面。这应该会让你所有的UI组件重置到一个已知的安全状态,包括横幅组件(banners),如果他们在一个不受支持的网络,就可以警告提示你的用户。
这可以通过使用以下函数来完成:
{
const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
provider.on("network", (newNetwork, oldNetwork) => {
if (oldNetwork) {
window.location.reload();
}
});
}
( 太长不看版: – 使用下面的链接注册来获得您自己的API密钥,以提高您的应用程序性能 )
当使用由API服务商(例如Alchemy,Etherscan 或 INFURA)支持的Provider时, 他们都需要一个API密钥,从而能够跟踪每个项目及其使用和权限。
ethers.js 库 为上述的API服务商提供了默认的API密钥,因此每个Provider都可以轻松地连接————开箱即用。
这些API密钥是由后端服务作为社区资源提供的,用于低流量项目和早期原型开发。
因为这些API键是由所有用户共享的(没有获得自己的API密钥),所以它们被经常被使用,这意味着重新请求发生得更频繁,响应也更慢。
强烈建议你从下列的API服务商注册一个免费的API密钥,这有很多好处(可能有些差别,这取决于具体的API服务商):
- 更高的请求速率和并发请求限制
- 更快的响应,更少的重连和超时
- 在用于性能调优和分析客户行为具有更优秀的指标追踪
- 更高级的api,例如归档数据或高级日志查询
Etherscan 是以太坊区块资源管理器,它可能是最有用的构建和调试以太坊应用程序的开发工具。
Etherscan提供了大量的 API endpoints 集合, 包含了所有能够用来与以太坊区块链交互的操作。
免费注册一个 API key
优点:
INFURA服务已经存在相当一段时间了,鲁棒性强、可靠性高,强烈推荐。
它们提供了一个标准的JSON-RPC接口和一个WebSocket接口,这使得与标准工具的交互更加通用、简单和直接。
在 INFURA 免费注册一个 Project ID
优点:
- 更高的速率限制
- 客户使用指标
- 访问归档数据 (需要付费升级)
Alchemy服务已经有几年的历史了,它也具备强鲁棒性和高可靠性。
它们提供了一个标准的JSON-RPC接口和一个WebSocket接口,以及一组用于与通证(tokens)交互和帮助调试的高级API。
在 Alchemy 免费注册一个 API key
优点:
- 更高的速率限制
- 客户使用指标
- 能够访问高级通证余额和元数据API
- 能够进行高级调试跟踪和恢复reason APIs
在 Pocket 免费注册一个 API key
Benefits:
- 客户使用指标
- 使用去中心化区块链基础设施
- 入股而不是按月付费
- 受加密经济激励的高度冗余的全局节点集合
默认的提供者连接到多个后端,并在内部验证它们的结果,这使得对第三方服务的高度信任变得很简单。
第二个可选参数允许为每个内部创建的Provider指定API keys,任何被遗漏的API keys将返回使用该服务的默认API keys。
极度推荐 您为每个服务提供一个API,让您的应用程序性能实现最大化。
将 API Keys 传到 getDefaultProvider
const network = "homestead";
const provider = ethers.getDefaultProvider(network, {
etherscan: YOUR_ETHERSCAN_API_KEY,
infura: YOUR_INFURA_PROJECT_ID,
alchemy: YOUR_ALCHEMY_API_KEY,
pocket: YOUR_POCKET_APPLICATION_KEY
});
应用程序编程接口(API)是库的正式规范。
提供者(Provider)是以太坊网络连接的抽象,为标准以太坊节点功能提供简洁、一致的接口。
ethers.js 库 提供了几种选项应该涵盖了绝大多数用例,但如果需要个性化配置,也包括了子类化所需的函数和类。
大多数用户应该使用 默认的 Provider.
默认的 provider 是在Ethereum上最简单、最安全的方法方式,而且它也具备足够强的鲁棒性,可以在生产环境中使用。
它创建一个连接到尽可能多的后端服务的FallbackProvider。当发出一个请求时,它会同时发送到多个后端。当从每个后端返回响应时,会检查它们是否同意。 一旦达到规定数量(即足够多的后端同意),你的应用程序就会得到相应。
它会确保如果后端不同步或者被破坏,那么将会放弃这个响应,选择接受而支持大多数认同的响应。
ethers.getDefaultProvider( [ network , [ options ] ] ) ⇒ Provider 返回一个新的Provider,由多个服务支持连接到 网络。如果没有提供 网络,那么将会使用 homestead (例如mainnet) 。
网络 也可以作为 URL 来连接,比如 http://localhost:8545
或 wss://example.com
。
options 参数是一个对象,有以下几个属性:
Property | Description | |
alchemy | Alchemy API Token | |
etherscan | Etherscan API Token | |
infura | INFURA Project ID 或 { projectId, projectSecret } | |
pocket | Pocket 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、自定义的参数来定义网络。
getNetwork("homestead");
// {
// chainId: 1,
// ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
// name: 'homestead'
// }
getNetwork(1);
// {
// chainId: 1,
// ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
// name: 'homestead'
// }
这是因为通常需要在为Network 指定自定义属性来覆盖root ENS registry,或者是在一个通用的网络中拦截ENS 方法, 又或者是在一个开发环境的网络中(绝大多数开发环境中的网络中的ENS合约需要你手动部署)指定ENS registry。
const network = {
name: "dev",
chainId: 1337,
ensAddress: customEnsAddress
};
在ethers里面,Provider 是访问区块链数据的只读抽象。
使用过 Web3.js吗?
如果你用过Web3.js,以下是与ethers.js最大不同点。
ethers library 在 Provider和Signer可执行的操作之间创建了一个强大的划分,而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'
// ]
// }
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名称附加到任何一组键和值。
最常见的用法之一是使用一个简单的命名来引用以太坊地址。
在ethers的 API中,几乎任何接受地址的地方都可以使用ENS命名,这可以简化代码,使读取和调试更加简单。
provider 提供了一些基本操作,以帮助解析和处理ENS命名。
返回一个EnsResolver实例,该实例可用于进一步查询ENS命名的特定实体。
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'
resolver.getAddress( [ cointType =
60 ] ) ⇒ Promise< string > 返回一个 解析的类型为EIP-2304 多币地址的 Promise。 默认情况下,返回一个以太坊的地址(Address)(coinType = 60
)。
await resolver.getAddress();
// '0x5555763613a12D8F3e73be831DFf8598089d3dCa'
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'
provider.getLogs( filter ) ⇒ Promise< Array< Log > > 返回匹配筛选器的 Log数组。
请记住,许多后端会丢弃旧的事件,并且请求范围太广可能也会被丢弃,因为它们需要太多的资源来执行查询。
provider.getNetwork( ) ⇒ Promise< Network > 返回这个 Provider 所连接的 Network。
await provider.getNetwork()
// {
// chainId: 1,
// ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
// name: 'homestead'
// }
provider.getBlockNumber( ) ⇒ Promise< number > await provider.getBlockNumber()
// 16383842
gasPrice = await provider.getGasPrice()
// { BigNumber: "16674318809" }
utils.formatUnits(gasPrice, "gwei")
// '16.674318809'
provider.getFeeData( ) ⇒ Promise< FeeData > 返回在一笔交易中当前的recommended FeeData。
对于 EIP-1559 的交易, 应该使用 maxFeePerGas
和 maxPriorityFeePerGas
。
对于不支持 EIP-1559 中被遗留的交易和网络,应该使用 gasPrice
。
feeData = await provider.getFeeData()
// {
// gasPrice: { BigNumber: "16674318809" },
// lastBaseFeePerGas: { BigNumber: "16492712329" },
// maxFeePerGas: { BigNumber: "34485424658" },
// maxPriorityFeePerGas: { BigNumber: "1500000000" }
// }
utils.formatUnits(feeData.maxFeePerGas, "gwei")
// '34.485424658'
provider.ready ⇒ Promise< Network > 返回一个 Promise ,直到网络建立就失效,,忽略由于目标节点还未激活而出现的错误。 这可以用于测试或附上脚本,以等待节点启动并顺利运行。
provider.call( transaction [ , blockTag =
latest ] ) ⇒ Promise< string< DataHexString > > 使用call返回执行交易的结果。调用不需要任何的以太,但不能改变任何状态。这在合约上调用getter方法是非常有用的。
await provider.call({
to: "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41",
data: "0x3b3b57debf074faa138b72c65adbdcfb329847e4f2c04bde7f7dd7fcad5a52d2f395a558"
});
// '0x0000000000000000000000005555763613a12d8f3e73be831dff8598089d3dca'
返回向网络提交交易所需的预估gas值。
估计的gas值可能不准确,因为网络上可能有另一个交易没有被计算在内,但在被挖出来之后就会影响相关状态。
await provider.estimateGas({
to: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
data: "0xd0e30db0",
value: parseEther("1.0")
});
// { BigNumber: "27938" }
返回带有哈希值的交易,如果交易未知,则返回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]
// }
返回交易收据的哈希值,如果交易还没有被挖出则返回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
// }
向在挖区块的网络提交交易。交易 必须 经过签名,并且需要合法(例如,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标识的区块被挖出。
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 Name | Arguments | Description | |
"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) => {
})
filter = {
address: "dai.tokens.ethers.eth",
topics: [
utils.id("Transfer(address,address,uint256)")
]
}
provider.on(filter, (log, event) => {
})
topicSets = [
utils.id("Transfer(address,address,uint256)"),
null,
[
hexZeroPad(myAddress, 32),
hexZeroPad(myOtherAddress, 32)
]
]
provider.on(topicSets, (log, event) => {
})
provider.on("pending", (tx) => {
});
provider.on("error", (tx) => {
});
Provider.isProvider( object ) ⇒ boolean 当且仅当参数object是Provider返回 true 。
JSON-RPC API是与以太坊交互比较流行的方法,在主要的以太坊节点实现(如Geth 和 Parity)以及许多第三方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。请参考他们的文档。
返回一个由该以太坊节点管理的JsonRpcSigner,地址为addressOrIndex。如果没有提供addressOrIndex,则使用第一个帐户(account #0)。
jsonRpcProvider.listAccounts( ) ⇒ Promise< Array< string > > jsonRpcProvider.send( method,
params ) ⇒ Promise< any > 允许向provider发送原始消息。
这可以用于后端特定的调用,比如调试或特定的帐户管理。
JsonRpcSigner是一个简单的Signer,它由一个连接的JsonRpcProvider支持。
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,但如果只需要这些,则可以快速获得交易哈希。
ethers Provider将频繁执行getNetwork
调用,以确保网络调用和网络通信是一致的。
在像MetaMask这样的客户端情况下,这是需要的,因为用户可以随时改变网络,在这种情况下,检查chainId的成本是本地的,因此很便宜。
在像MetaMask这样的客户端情况下是非常有必要的,因为用户可能随时改变网络, 在这种情况下,检查chainId的成本是在本地的,因此很便宜。
众所周知,在某些情况下网络不能改变,如当连接到一个INFURA端点,在这种情况下,可以使用SStaticJsonRpcProvider来将一直缓存链ID,可以减少网络流量和往返查询chain ID的次数。
这个Provider 只应在网络不能更改时使用。
许多方法是不常见的或特定于某些以太坊节点(例如Parity 与 Geth)才有的。这些方法包括帐户和管理员管理、调试、更深层次的区块和交易探索以及其他服务(如Swarm和Whisper)。
可以使用jsonRpcProvider.send 来获得这些方法。
有很多服务都提供了访问以太坊区块链的web API。这些提供商允许连接到它们,因为你不需要运行你自己的实例或以太坊节点集群,这就简化了开发。
然而,这种对第三方服务的依赖会降低弹性和安全性,并增加所需的信任度。为了缓解这些问题,建议您使用 Default 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
provider = new EtherscanProvider();
provider = new EtherscanProvider("goerli");
provider = new EtherscanProvider(5);
network = ethers.providers.getNetwork("goerli");
// {
// chainId: 5,
// ensAddress: '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e',
// name: 'goerli'
// }
provider = new EtherscanProvider(network);
provider = new EtherscanProvider(null, apiKey);
provider = new EtherscanProvider("homestead", apiKey);
provider.getHistory( address ) ⇒ Array< History > InfuraProvider 被流行的INFURA以太坊服务所支持。
new ethers.providers.InfuraProvider( [ network = "
homestead"
, [ apiKey ] ] ) 使用可选的apiKey创建一个新的InfuraProvider连接到网络。
这个网络可以被指定为一个字符串类型的网络名称、或number类型的链ID,或[网络对象]provider-(network)。
apiKey可以是一个string类型的Project ID,也可以是一个带有projectId
和projectSecret
属性对象, 用于指定一个可以在非公共源(如服务器)上使用的Project Secret, 以进一步保护你的API access 和 quotas。
注意: 默认的 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
provider = new InfuraProvider();
provider = new InfuraProvider("goerli");
provider = new InfuraProvider(null, projectId);
provider = new InfuraProvider("homestead", projectId);
provider = new InfuraProvider("homestead", {
projectId: projectId,
projectSecret: projectSecret
});
provider = InfuraProvider.getWebSocketProvider()
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
provider = new AlchemyProvider();
provider = new AlchemyProvider("goerli");
provider = new AlchemyProvider(null, apiKey);
provider = new AlchemyProvider("homestead", apiKey);
provider = AlchemyProvider.getWebSocketProvider()
CloudflareProvider 是被 Cloudflare Ethereum Gateway所支持的。
new ethers.providers.CloudflareProvider( ) 创建一个新的CloudflareProvider 连接到主网(homestead)。
provider = new CloudflareProvider();
FallbackProvider 是ethers中可用的且最高级的Provider。
它使用一个quorum并连接到多个Providers作为后端,每个Providers都配置了一个优先级和权重。
当发出一个请求时,请求被分配给多个随机选择的后端(优先级较低的后端总是被先选中), 并将每个请求的结果与其他请求进行比较。只有达到quorum规定的数量后,该结果才会被接受并返回给调用方。
默认情况下,quorum需要50%(四舍五入)的后端响应(to agree)。权重可用于让一个后端Provider作用更强。
new ethers.providers.FallbackProvider( providers [ , quorum ] ) 创建一个连接到providers的FallbackProvider的新实例。 如果quorum未指定,则设为provider权重总和的一半。
providers可以是Provider 或 FallbackProviderConfig的数组。 如果提供了Provider,默认的优先级为1,权重为1。
在得到结果之前,后端响应的quorum必须达成一致。默认情况下,这个值是权重之和的一半。
fallbackProviderConfig.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 允许JSON-RPC API在文件系统的本地文件名上使用。 Geth, Parity和其他节点都会开放了这个功能。
这只能在node.js中使用,因为它需要访问文件系统,并且由于文件权限可能有增加额外的复杂性。(请参阅相关节点实现的文档)
ipcProvider.path ⇒ string 这个类打算作为子类而不是直接使用。只需要额外生成一个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为了方便一个基于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 ExternalProvider 可以是上面提到的 Web3 Providers (或者是其他兼容的) 又或者是一个EIP-1193 provider。
ExternalProvider必须提供以下签名之一,并且使用第一个匹配的签名:
externalProvider.request( request ) ⇒ Promise< any > 它遵循EIP-1193 API 签名。
request 应该是一个标准的 JSON-RPC payload, 最少指定method
和 params
。
结果应该是实际的结果,它与Web3.js响应不同,后者是一个包装好的JSON-RPC响应。
externalProvider.sendAsync( request , callback ) ⇒ void 它遵循Web3.js Provider Signature.
request 应该是一个标准的 JSON-RPC payload, 最少指定method
和 params
。
回调函数应该使用错误优先调用的语义,比如(error, result),其中result是JSON-RPC包装后的得到的。
externalProvider.send( request , callback ) ⇒ void 这与sendAsync
是相同的。在历史上,这使用了一个同步的web请求,但当前没有浏览器支持这种方式,所以它的这种使用方式在很久以前就被弃用了。
WebSocketProvider 连接到一个JSON-RPC websocket兼容的后端,它允许持久连接、多路复用请求和发布-子事件,以实现更即时的事件调度。
WebSocket API是较新的,如果运行自己的基础设施,请注意WebSockets对服务器资源的占用会很大, 因为它们必须管理和维护每个客户端的状态。由于这个原因,许多服务也可能会为使用他们的WebSocket端点而收取额外的费用。
new ethers.providers.WebSocketProvider( [ url [ , network ] ] ) 通过一个url连接到网络,返回一个新的WebSocketProvider。
如果url未指定,默认值是"ws://localhost:8546"
。 如果network未被指定,它将会从网络中查询。
BlockTag 在区块链中指定了一个特定的区块位置
"latest"
- 最新挖出的区块 "earliest"
- Block #0 "pending"
- 目前正在挖的区块;不是所有的操作和后端都支持这个BlockTag属性 - number - 区块高度
- a negative number - The block this many blocks ago
- hex string - 区块高度(十六进制表示)
Networkish可以是以下任意一种:
- 一个Network 对象
- 字符串形式的常见网络 (如
"homestead"
) - number类型的网络chain ID; 如果chain ID 是常见网络的一种,
name
和 ensAddress
会自动填充, 否则 name 会是"unknown"
且不使用任何的 ensAddress
。
Network 表示一个Ethereum 网络
可读的网络名称,如homestead
。如果网络的name未指定,将会是"unknown"
。
FeeData对象根据最好且可用建议,去封装发送交易所需的费用数据。
gasPrice用于那些不支持EIP-1559的遗留交易或网络。
maxFeePerGas
用于交易。这基于最近产出区块的baseFee
。
The maxPriorityFeePerGas
to use for a transaction. This accounts for the uncle risk and for the majority of current MEV risk.
nonce 用在基于工作量证明(PoW)机制挖取区块。 开发人员通常对这个属性不感兴趣。
block.difficulty ⇒ number 该区块的矿工需要达到的难度指标。 开发人员通常对这个属性不感兴趣。
这个区块允许使用的最大gas数量。这该值的可以由矿工投票决定要变大还是变小,用于自动调整网络的带宽需求。 开发人员通常对这个属性不感兴趣。
当前区块的coinbase 地址,这个地址表示挖出这个区块的矿工想要接受这笔开采奖励的地址。
该数据表示的是当挖出一个区块时,这个矿工可以选择包含的额外数据。 开发人员通常对这个属性不感兴趣。
Block (with transaction hashes)
通常只需要一个区块中包含交易的哈希值,因此默认情况下,一个区块只包含这些信息,这样数据就不会很大。
BlockWithTransactions
inherits Block
如果需要一个块的所有交易,则该对象包含每条交易的完整信息。
想要筛选的地址, null
表示匹配任何用户的地址。
filter.topics ⇒ Array< string< Data< 32 > > | Array< string< Data< 32 > > > > 要筛选的主题,null
示匹配任何用户的主题。
每一个 entry 表示一个AND匹配条件,entry 也可以是null
来匹配所有的内容。 如果给定的 entry 是一个数组,那么这个 entry 是被视为 OR匹配条件来匹配entry里面的内容。 有关指定复杂过滤器的详细信息和示例,请参阅Filters。
用于搜索匹配过滤条件的日志的起始区块(包含在内)。
用于搜索匹配过滤条件的日志的结束区块(包含在内)。
指定区块(按这个区块哈希值)搜索符合过滤条件的日志。
在区块重组期间,如果一个交易是孤立的,它将被设置为true,表示Log entry已被删除; 在不久的将来,当使用包含该日志的交易被另一个区块挖出时,可能会再次触发此日志,但请记住,值可能会更改。
log.transactionLogIndex ⇒ number log.transactionIndex ⇒ number
transaction request 描述了一笔将要被发送到网络或以其他方式处理的交易。
所有字段都是可选的,并且可以是一个能被解析为所需类型的promise。
transactionRequest.to ⇒ string | Promise< string > transactionRequest.nonce ⇒ number | Promise< number > 交易的 nonce 值。 发送这笔交易要设置这个值,是number类型的。
这笔交易允许使用的最大gas值。
如果未指定,ethers将使用estimateGas
来确定要使用的gas值。对于无法预测的gas的交易,可能需要这样做显式地指定。
这笔交易将支付的每个gas的价格(以wei为单位)。
这个属性不适用于这笔交易的type
设置为1
或者 2
的情形,也不适用于交易已指定了maxFeePerGas
或者 maxPriorityFeePerGas
的情形。
这笔交易将支付EIP-1559基础费用的每个gas的价格的最高价格(以wei为单位)。
大多数开发人员不应该指定该参数,而应该使用网络决定的默认值。 这个属性不适用于这笔交易的type
设置为0
的情形,也不适用于交易已指定了gasPrice
的情形。
这笔交易将支付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
。
TransactionResponse
inherits 交易
TransactionResponse包括交易的所有属性,以及等区块被挖出来后就很有用的几个属性。
transaction.blockNumber ⇒ number 这笔交易所在区块被挖出来时的高度(区块数量)。如果区块还没有被挖出,这个值为null
。
这笔交易所在区块被挖出来时的哈希值。如果区块还没有被挖出,这个值为null
。
transaction.timestamp ⇒ number 这笔交易所在区块被挖出来时的时间戳。如果区块还没有被挖出,这个值为null
。
transaction.confirmations ⇒ number 当这笔交易所在的区块被挖出来时,所有已挖出的区块数量(包括初始块)。
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
。
接受这笔交易的地址。如果交易是用于部署合约,则为null
。
receipt.transactionIndex ⇒ number 此交易的EIP-2718类型。如果事务是没有envelope的legacy事务,则其type 为0
。
The intermediate state root of a receipt.
只有在Byzantium Hard Fork之前包含在区块中的交易才有这个属性,因为它被status
属性取代了。
这些属性通常对开发者没什么用处。仅考虑单笔交易的防伪性的时候可以用于验证状态转换;如果没有它,则必须考虑整个区块。
receipt.logs ⇒ Array< Log > receipt.blockNumber ⇒ number receipt.confirmations ⇒ number 当这笔交易所在的区块被挖出来时,所有已挖出的区块数量(包括这笔刚挖出的区块)。
对于包含该交易的块,这是截至(并包括)该交易的有序交易列表中每个交易使用的gas的总和。 开发人员通常对这个属性不感兴趣。
receipt.byzantium ⇒ boolean Access List是可选的,它包含一个地址列表和地址的存储槽,这些地址应该是warmed或pre-fetched的,以便在这个交易的执行中使用。 一个warmed值有一个额外的预先支付访问的成本,但在整个读取和写入代码的执行过程中会被降低。
AccessList的一个更宽松的描述,它将在内部使用accessListify进行转换。
他可以是以下的任意一种:
- 任意的AccessList
- 一个包含两个元素的数组,第一个元素是地址,第二个是数组在storage中的key
- 一个对象, 所以的key表示地址,相应的value表示数组在storage中的key
当使用对象形式(上述最后一个选项)时,地址和存储槽将被排序。 如果需要access list的显式顺序,则必须使用方式。 大多数开发人员不需要明确的顺序。
equivalent to the AccessList example below
accessList = [
[
"0x0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
[
"0x0000000000000000000000000000000000000000000000000000000000000004",
"0x0bcad17ecf260d6506c6b97768bdc2acfb6694445d27ffd3f9c1cfbee4a9bd6d"
]
],
[
"0x5FfC014343cd971B7eb70732021E26C35B744cc4",
[
"0x0000000000000000000000000000000000000000000000000000000000000001"
]
]
];
accessList = {
"0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e": [
"0x0000000000000000000000000000000000000000000000000000000000000004",
"0x0bcad17ecf260d6506c6b97768bdc2acfb6694445d27ffd3f9c1cfbee4a9bd6d"
],
"0x5FfC014343cd971B7eb70732021E26C35B744cc4": [
"0x0000000000000000000000000000000000000000000000000000000000000001"
]
};
一个EIP-2930交易允许一个可选的AccessList,它会导致交易warm(预缓存)另一个地址状态和指定的storage key。
这会使得交易的内在成本变大,但在整个事务执行过程中给存储和状态访问带来了好处。
// Array of objects with the form:
// {
// address: Address,
// storageKey: Array< DataHexString< 32 > >
// }
accessList = [
{
address: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
storageKeys: [
"0x0000000000000000000000000000000000000000000000000000000000000004",
"0x0bcad17ecf260d6506c6b97768bdc2acfb6694445d27ffd3f9c1cfbee4a9bd6d"
]
},
{
address: "0x5FfC014343cd971B7eb70732021E26C35B744cc4",
storageKeys: [
"0x0000000000000000000000000000000000000000000000000000000000000001"
]
}
];
在ethers中Signer是以太坊账户的抽象,可以用来签名消息和交易,如将签名的交易发送到以太坊网络以执行状态更改的操作。
可用的操作里在很大程度上取决于所使用的子类。
例如,一个来自MetaMask的Signer可以发送交易和签名消息,但不能签名一个不广播的交易。
你会遇到的最常见的Signers有:
Signer类是抽象的,不能直接实例化,而是应该使用一个具体的子类,如 Wallet, VoidSigner 或 JsonRpcSigner。
signer.connect( provider ) ⇒ Signer 子类必须实现这个,但是如果更改后的providers是不被支持的话,它们可能仅仅只抛出一个错误。
signer.getAddress( ) ⇒ Promise< string< 地址(Address) > > 返回一个解析为帐户地址的Promise。
这是一个Promise,因此一个Signer可以围绕一个异步源进行设计,如硬钱包。
子类必须实现这个。
Signer.isSigner( object ) ⇒ boolean 当且仅当 object 是一个Signer时返回true。
signer.getBalance( [ blockTag = "
latest"
] ) ⇒ Promise< 大数(BigNumber) > signer.getChainId( ) ⇒ Promise< number > 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) > >
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._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
。
const domain = {
name: 'Ether Mail',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
};
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'
Signer的所有重要属性都是不可变的,这一点非常重要。由于以太坊是异步的,并处理关键数据(如ether和其他潜在有价值的加密资产), 整个Signer的生命周期中保持provider和 address等属性是静态的有助于防止严重的问题的出现, 而且许多其他类和库也是认定provider和 address等属性是静态的。
子类必须扩展Signer,并且必须调用super()
。
这通常不需要重写,但可能需要在子类中提供自定义操作。
这应该返回一个transactionRequest的副本,包含call
、estimateGas
和populateTransaction
(sendTransaction使用的)所需的任何属性。 如果指定了任何未知的key,它会抛出一个错误。
默认的实现只验证有效的TransactionRequest属性是否存在,如果不存在,则将from
添加到交易中。
如果存在from
字段,则必须验证它与 Signer的地址是否相等。
这通常不需要重写,但可能需要在子类中提供自定义操作。
这应该返回一个transactionRequest的副本,遵循与checkTransaction
相同的过程, 并填写发送交易所需的任何属性。返回的结果都是promises,可以使用resolvePropertiesutility函数来解析。
默认实现调用checkTransaction
,如果它是一个ENS name就会解析它,并根据Signer上的相关操作添加gasPrice
, nonce
, gasLimit
和chainId
。
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。
注意
一个钱包实例是不可变的,因此如果您希望更改Provider,您可以使用connect方法创建一个连接到所需的provider的新实例。
wallet.encrypt( password , [ options = {}
, [ progress ] ] ) ⇒ Promise< string > 加密钱包,使用password返回一个解析为JSON钱包的Promise。
如果提供了进度,它将在解密期间被调用,其值介于0到1之间,表示一个完成进度。
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
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'
// }
walletPrivateKey.mnemonic
// null
await walletMnemonic.signMessage("Hello World")
// '0x14280e5885a19f60e536de50097e96e3738c7acae4e9e62d67272d794b8127d31c03d9cd59781d4ee31fb4e1b893bd9b020ec67dfa65cfb51e2bdadbb1de26d91c'
tx = {
to: "0x8ba1f109551bD432803012645Ac136ddd64DBA72",
value: utils.parseEther("1.0")
}
await walletMnemonic.signTransaction(tx)
// '0xf865808080948ba1f109551bd432803012645ac136ddd64dba72880de0b6b3a7640000801ca0918e294306d177ab7bd664f5e141436563854ebe0a3e523b9690b4922bbb52b8a01181612cec9c431c4257a79b8c9f0c980a2c49bb5a0e6ac52949163eeb565dfc'
wallet = walletMnemonic.connect(provider)
await wallet.getBalance();
// { BigNumber: "6846" }
await wallet.getTransactionCount();
// 3
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是一个简单的Signer,它不能签名。
当API需要signer作为参数时,它作为只读的signer是有用的,但它只能携带只读的操作。
比如,在call
函数调用期间会自动传递所提供的地址。
new ethers.VoidSigner( address [ , provider ] ) ⇒ VoidSigner VoidSigner Pre-flight Example
address = "0x8ba1f109551bD432803012645Ac136ddd64DBA72"
signer = new ethers.VoidSigner(address, provider)
abi = [
"function balanceOf(address) view returns (uint)",
"function transfer(address, uint) returns (bool)"
]
contract = new ethers.Contract("dai.tokens.ethers.eth", abi, signer)
tokens = await contract.balanceOf(signer.getAddress())
// { BigNumber: "2413468059122458201631" }
await contract.callStatic.transfer("donations.ethers.eth", tokens)
// true
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'
// }
// }
这个接口包含外部拥有帐户(EOA)所需的最小属性集,可以执行某些操作,比如将其编码为JSON钱包。
可选的。帐户HD的助记词,如果有的话可以打印出来。EOA账户源不编码助记符,如HD extended keys。
Contract对象是部署在以太坊网络上的合约(EVM字节码)的抽象。它允许以一种简单的方式去将链上的合约的调用(calls)和交易(transactions)序列化,并反序列化它们的结果(results)和触发的日志(logs)。
ContractFactory是合约字节码的抽象,可以方便地部署合约。
ContractFactory合约 字节码(bytecode)的抽象,可以方便地部署合约。
Contract是部署到区块链的代码的抽象。
合约可以被用来发送交易,将交易数据作为参数运行代码。
new ethers.Contract( address , abi , signerOrProvider )
返回一个附加到新地址的Contract新实例。 如果网络上有多个类似或相同的合约副本,并且您希望与它们中的每一个进行交互,那么这是非常有用的。
返回合约的一个新实例,但需要连接到providerOrSigner。
通过传入一个Provider,这将返回一个低级的合约实例,它只有只读访问的权限(即常量调用)。
通过传入一个Signer,这将返回一个代表该签名人(signer)的合约实例。
这是一个合约对象将解析合约地址的promise。 如果一个地址(Address)被提供给构造函数,它将解析成这个地址; 如果提供的是ENS名称,这将解析成对应的地址。
如果Contract是ContractFactory部署后返回的对象, 那么contract.deployTransaction返回的数据就是部署这个合约的交易信息(transaction)。
如果构造函数使用的是provider生成的contract合约对象,那么这个结果就是这个provider。 如果使用的是具有Provider的signer,那么这个结果就是provider。
如果构造函数使用的是signer生成的contract合约对象,那么这个结果就是这个signer。
Contract.isIndexed( value ) ⇒ boolean
contract.queryFilter( event [ , fromBlockOrBlockHash [ , toBlock ] ) ⇒ Promise< Array< Event > > contract.listenerCount( [ event ] ) ⇒ number 返回订阅该event的监听器数量。如果没有提供event,则返回所有事件的总数。
contract.listeners( event ) ⇒ Array< Listener > contract.off( event , listener ) ⇒ this contract.on( event , listener ) ⇒ this 监听event事件,当事件发生时,会调用listener函数。
contract.once( event , listener ) ⇒ this 监听event事件,当事件发生时,仅调用一次listener函数。
contract.removeAllListeners( [ event ] ) ⇒ this 取消所有订阅event事件的监听器。如果未提供event事件,则取消订阅所有事件的监听。
元类是在运行时确定其所有属性的类。Contract对象使用合约的ABI来确定可使用方法(methods), 因此下面的部分将描述用一些属性,来与在合约构造函数期间交互的通用方法。
只读的方法 (常量; 如 view 或 pure)
常量方法是只读的,针对当前区块链状态计算少量EVM代码,并可以通过请求单个节点来计算,该节点会返回一个结果。 因此,它是免费的,不需要任何以太币,但不能更改区块链状态。
contract.METHOD_NAME( ...
args [ , overrides ] ) ⇒ Promise< any > 结果的类型取决于ABI。如果方法返回单个值,则将直接返回该值,否则将返回一个Result对象,其中包含每个位置可用的参数, 如果参数被命名,那么就按照命名后的值去执行。
如果返回的值匹配JavaScript中的类型值,如strings字符串类型 和 booleans布尔类型,那么返回的值类型就直接是JavaScript中的类型值。
但对于numbers类型,如果类型在JavaScript的安全范围内(即小于53位,如int24
或uint48
8),则使用标准的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日志)对其进行记录,然后可以从交易收据中查询该事件。
交易被发送到网络后,返回交易的TransactionResponse。 Contract合约对象需要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 method的属性和结果。
contract.estimateGas.METHOD_NAME( ...
args [ , overrides ] ) ⇒ Promise< 大数(BigNumber) > 返回使用 args和overrides执行METHOD_NAME所需的gas的估计单位。
overrides与上面针对只读或写方法的overrides相同,具体取决于 METHOD_NAME调用的类型。
contract.populateTransaction.METHOD_NAME( ...
args [ , overrides ] ) ⇒ Promise< UnsignedTx > 返回一个未签名交易(UnsignedTransaction),它表示需要签名并提交给网络的交易,以执行带有args和overrides的METHOD_NAME。
overrides与上面针对只读或写方法的overrides相同,具体取决于 METHOD_NAME调用的类型。
contract.callStatic.METHOD_NAME( ...
args [ , overrides ] ) ⇒ Promise< any > 比起执行交易状态更改,更可能是会要求节点尝试调用不进行状态的更改并返回结果。
这实际上并不改变任何状态,而是免费的。在某些情况下,这可用于确定交易是失败还是成功。
otherwise函数与Read-Only Method相同。
overrides与上面的只读操作相同。
事件过滤器由主题(topics)组成,这些主题是Bloom Filter中记录的值,允许对匹配过滤器的条目进行有效搜索。
contract.filters.EVENT_NAME( ...
args ) ⇒ Filter 返回EVENT_NAME的过滤器,可以通过增加其他约束进行过滤。
只有indexed
索引的事件参数可以被过滤。如果参数为空(或未提供),则该字段中的任何值都匹配。
去部署一个合约(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的一个新实例,该实例具有相同的接口和字节码,但signer不一样。
这个ContractFactory部署合约是需要用到的字节码(即 initcode)。
contractFactory.signer ⇒ Signer 这个ContractFactory用这个Signer(如果有的话)将合约的实例部署到链上。
返回附有地址的(合约)实例。这与使用带有地址address的Contract constructor构造函数是一样的, 在创建ContractFactory时会传入的interface和signerOrProvider。
返回一个未签名的交易,该交易将会具有部署合约时,传递给Contract的构造函数的args参数。
如果overrides被指定了,它们可以用来用来对某些变量进行覆盖赋值、如value
, 交易的 nonce
, gasLimit
或 gasPrice
。
contractFactory.deploy( ...
args [ , overrides ] ) ⇒ Promise< 合约(Contract) > 使用signer去部署带有args参数传入构造函数的合约,并返回一个Contract,该Contract有address属性,一旦这个交易所在区块被挖出,address属性表示的就是这个合约的地址。
这个交易可以在contract.deployTransaction
中找到,在挖出这个交易之前不应该进行交互。
如果overrides被指定了,它们可以用来用来对某些变量进行覆盖赋值、如value
, 交易的 nonce
, gasLimit
或 gasPrice
。
const abi = [
"constructor(address owner, uint256 initialValue)",
"function value() view returns (uint)"
];
factory = new ContractFactory(abi, bytecode, signer)
contract = await factory.deploy("ricmoo.eth", 42);
contract.address
// '0x308e0b8A8e45F6Afd553D822f9e00CeF49e84e98'
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]
// }
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" }
元类(Meta-Classes)的概念有些令人困惑,因此我们将介绍一个简短的例子。
元类是在运行时(run-time)定义的类。合约是由应用程序二进制接口(ABI)指定的,它描述了它所拥有的方法和事件。 这些描述是合约(Contract)对象处于run-time时传入的,它创建一个新的Class,在run-time时添加了ABI中定义的所有方法。
大多数情况下,您需要与之交互的任何合约都已经部署到区块链上,但对于本例需要先部署合约。
new ethers.ContractFactory( abi , bytecode , signer ) 创建一个新的ContractFactory,它可以将合约部署到区块链。
const bytecode = "0x608060405234801561001057600080fd5b506040516103bc3803806103bc83398101604081905261002f9161007c565b60405181815233906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a333600090815260208190526040902055610094565b60006020828403121561008d578081fd5b5051919050565b610319806100a36000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063313ce5671461005157806370a082311461006557806395d89b411461009c578063a9059cbb146100c5575b600080fd5b604051601281526020015b60405180910390f35b61008e610073366004610201565b6001600160a01b031660009081526020819052604090205490565b60405190815260200161005c565b604080518082018252600781526626bcaa37b5b2b760c91b6020820152905161005c919061024b565b6100d86100d3366004610222565b6100e8565b604051901515815260200161005c565b3360009081526020819052604081205482111561014b5760405162461bcd60e51b815260206004820152601a60248201527f696e73756666696369656e7420746f6b656e2062616c616e6365000000000000604482015260640160405180910390fd5b336000908152602081905260408120805484929061016a9084906102b6565b90915550506001600160a01b0383166000908152602081905260408120805484929061019790849061029e565b90915550506040518281526001600160a01b0384169033907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a350600192915050565b80356001600160a01b03811681146101fc57600080fd5b919050565b600060208284031215610212578081fd5b61021b826101e5565b9392505050565b60008060408385031215610234578081fd5b61023d836101e5565b946020939093013593505050565b6000602080835283518082850152825b818110156102775785810183015185820160400152820161025b565b818111156102885783604083870101525b50601f01601f1916929092016040019392505050565b600082198211156102b1576102b16102cd565b500190565b6000828210156102c8576102c86102cd565b500390565b634e487b7160e01b600052601160045260246000fdfea2646970667358221220d80384ce584e101c5b92e4ee9b7871262285070dbcd2d71f99601f0f4fcecd2364736f6c63430008040033";
const abi = [
"constructor(uint totalSupply)"
];
const factory = new ethers.ContractFactory(abi, bytecode, signer)
const contract = await factory.deploy(parseUnits("100"));
contract.address
// '0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC'
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
// }
new ethers.Contract( address , abi , providerOrSigner ) 创建一个Contract的新实例,是通过在区块链上指定这个合约的地址、以及它的abi(用于填充类的方法)和providerOrSigner来连接到的。
如果给定了Provider,那么合约只有只读访问权限,而Signer则提供了对状态操作方法的访问权限。
const abi = [
"function balanceOf(address owner) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)",
"function transfer(address to, uint amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint amount)"
];
const address = "0xa22aB6748282B3125dC26dAFb79e38B7eb24EAcC";
const erc20 = new ethers.Contract(address, abi, provider);
const erc20_rw = new ethers.Contract(address, abi, signer);
合约被创建的 地址address (或 ENS name)
如果Contract对象是ContractFactory部署的得到的,在这里的返回的结果就是用于部署合约的交易。
如果一个provider被提供给构造函数,这条语句的结果就是provider; 如果提供了的Provider是一个signer,结果就是signer。
如果是一个signer提供给构造函数,结果就是signer。
返回一个附在新地址的合约的新实例。 如果网络上有多个类似或相同的合约副本,并且您希望与它们中的每一个进行交互,那么这是非常有用的。
返回合约的新实例,但需要连接provider或者Signer。
通过传入一个Provider,这将返回一个低级的合约,它只有只读访问(即常量调用)。
通过传入一个Signer。这将返回一个代表该signer的合约。
erc20.deployed( ) ⇒ Promise< Contract >
Contract.isIndexed( value ) ⇒ boolean
关于使用事件的例子请参考Meta-Class Filters。
erc20.queryFilter( event [ , fromBlockOrBlockHash [ , toBlock ] ) ⇒ Promise< Array< Event > > erc20.listenerCount( [ event ] ) ⇒ number 返回订阅该event的监听器数量。如果没有提供event,则返回所有事件的总数。
erc20.listeners( event ) ⇒ Array< Listener > erc20.off( event , listener ) ⇒ this 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 > await erc20.symbol();
// 'MyToken'
从当前的signer将数量为amount的tokens转给接收者target。 在交易处于写入操作时,返回值(布尔类型)是得不到的。 如果需要这个值,则需要其他方法(如事件)。链上合约调用transfer
函数可以得到这个结果。
formatUnits(await erc20_rw.balanceOf(signer.getAddress()));
// '100.0'
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的演练,而不实际签名或发送交易。
这可以用于检查真实的转账前,交易是否能成功。
await erc20_rw.callStatic.transfer("ricmoo.eth", parseUnits("1.23"));
// true
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" }
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'
// ]
// }
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
// }
// ]
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'
// ]
// }
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
// }
// ]
logsTo[0].args
// [
// '0x0000000000000000000000000000000000000000',
// '0x894ed91B666FacCe5a4D2FF8261924b4754A5759',
// { BigNumber: "100000000000000000000" },
// amount: { BigNumber: "100000000000000000000" },
// from: '0x0000000000000000000000000000000000000000',
// to: '0x894ed91B666FacCe5a4D2FF8261924b4754A5759'
// ]
erc20.on(filterFrom, (from, to, amount, event) => {
});
erc20.on(filterTo, (from, to, amount, event) => {
});
erc20.on("Transfer", (from, to, amount, event) => {
});
这些实用工具(utilities)在库中被广泛使用,对DAPP开发人员非常有用。
应用程序二进制接口 (ABI)是一个Fragments的集合,它指定了如何与合约的各种组件交互。
Interface可以按类型组织Fragments,并可以具有对每个组件进行编码、解码等功能。
大多数开发人员可能不需要这种对链上二进制数据进行编码和解码的低级访问,而最有可能使用(Contract),它提供了一个更方便的接口。 一些框架和工具开发者或使用高级技术的开发者可能会发现这些类(classes)和实用工具(utilities)很有帮助。
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将包含一个被访问时将抛出的属性,从而允许更高级别的库从数据错误中恢复。
根据数组中的types对数组values进行编码,每个类型可以是字符串或ParamType。
abiCoder.encode([ "uint", "string" ], [ 1234, "Hello World" ]);
// '0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000'
abiCoder.encode([ "uint[]", "string" ], [ [ 1234, 5678 ] , "Hello World" ]);
// '0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000004d2000000000000000000000000000000000000000000000000000000000000162e000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000'
abiCoder.encode(
[ "uint", "tuple(uint256, string)" ],
[
1234,
[ 5678, "Hello World" ]
]
);
// '0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000162e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000'
abiCoder.encode(
[ "uint a", "tuple(uint256 b, string c) d" ],
[
1234,
{ b: 5678, c: "Hello World" }
]
);
// '0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000162e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000'
abiCoder.decode( types , data ) ⇒ Result 根据数组中的types对data进行编码,每个类型可以是字符串或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'
// ]
data = "0x00000000000000000000000000000000000000000000000000000000000004d20000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000162e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000b48656c6c6f20576f726c64000000000000000000000000000000000000000000";
abiCoder.decode([ "uint", "tuple(uint256, string)" ], data);
// [
// { BigNumber: "1234" },
// [
// { BigNumber: "5678" },
// 'Hello World'
// ]
// ]
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 类型有:
Human-Readable ABI 是早期被 ethers 所描述提出的, 它允许使用一个Solidity式签名(Solidity signature)来描述每个方法(method)、事件(event)和错误(error)。
需要注意的是,一个Solidity式签名完整地描述了ABI需要的全部属性:
- 名称(name)
- 类型 (构造函数, 事件, 函数)
- 输入 (类型、嵌套结构和可选名称)
- 输出 (类型、嵌套结构和可选名称)
- 状态(state mutability)、用于构造函数和方法
- payability、用于构造函数和方法
- 在事件中输入的参数是否需要被检索(indexed)
这样就实现了一种简单的方式既是机器可读的(因为解析器是一台机器),也是人类可读的(至少是开发人员可读的), 而且便于人们编写嵌入到代码中,提高了代码的可读性。 Human-Readable ABI也相当小,有助于减少代码大小。
Huamn-Readable ABI是一个简单的字符串数组,其中每个字符串都是Solidity式签名。
签名可以被最小限度地指定(即输入和输出的名称可以被省略),也可以被完全指定(即所有的属性名称),并且空格会被忽略。
Solidity中的几个修饰符在ethers内部被摒弃了,因为ABI不需要,在Solidity的语义检查系统(semantic checking system)使用的是旧修饰符,比如输入参数数据"calldata"
和"memory"
。
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)",
"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编译器。 有关完整规范,请参阅Solidity compiler documentation。
不同版本的键和值略有不同。例如,早期的编译器只包含一个布尔值"constant"
来表示可变性(mutability), 而新版本包含了一个字符串"mutabilityState"
,它包含了几个旧的属性。
当使用JSON ABI创建一个Fragment实例时,它会自动为new-age ABIs推断出所有的legacy properties, 而对于legacy ABIs将推断出new-age properties。 所有属性都将被填充,因此它与用Human-Readable ABI fragment创建是等价的。
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 }
]
}
]
}
]`;
使用JSON.parse解析Solidity JSON ABI的结果完全与Interface类兼容, 且该对象的每个方法、事件和错误都与Fragment类兼容。
一些开发人员可能更喜欢这种方式,因为它允许将ABI属性作为普通的JavaScript对象访问,并且Solidity ABI与JSON ABI非常匹配。
Fragment对象使重新格式化单个方法、事件或错误变得简单,但是大多数开发人员会对转换整个ABI感兴趣。
对于生产环境下的代码,建议内联Human-Readable ABI 的方式,因为这样可以很容易地看到哪些方法、事件和错误是可用的。 还强烈建议去掉ABI中未使用的部分(如管理方法),以进一步减少代码大小。
转换成完整的 Human-Readable ABI
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
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))'
// ]
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.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
// }
// ]
// }
// ]
// }
// ]`
对一个ABI进行描述解释。
JSON String ABI (Solidity 输出 JSON)
JSON ABI Format 是Solidity 编译器输出的格式.
JSON序列化的对象总是一个字符串,它表示一个对象数组,其中每个对象都有描述ABIFragment 的各种属性。
对JSON字符串(一个普通的JavaScript对象)进行反序列化也可以传递给任何接受JSON string 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)",
]
每个Fragment和ParamType可以使用其format
方法输出。
ethers.utils.FormatTypes.full ⇒ string 这是一个完整的人类可读(human-readable)的字符串,包括所有的参数名,任何可选的修饰符(例如,indexed
,public
等)和空格,以提高代码可读性。
ethers.utils.FormatTypes.json ⇒ string 这将返回一个JavaScript对象,安全地调用JSON.stringify
创建一个JSON字符串。
ethers.utils.FormatTypes.sighash ⇒ string 一个最小的输出格式,Solidity在计算签名哈希或事件主题topic的哈希时使用它。
注意
sighash
格式不足以重新创建原始的Fragment,因为它舍弃了indexed, anonymous, stateMutability等修饰符。
它只用于为Fragment去计算 选择器(selector),不能用于格式化接口。
一个ABI是Fragments的集合,每个fragment指定:
这是一个表示Fragment类型的字符串。如:
constructor
event
function
fragment.format( [ format =
sighash ] ) ⇒ string 使用可用的输出格式创建Fragment的字符串描述。
ethers.utils.Fragment.from( objectOrString ) ⇒ Fragment 从任何兼容的bject或String创建一个新的Fragment子类。
ethers.utils.Fragment.isFragment( object ) ⇒ boolean 如果object 是一个Fragment返回true。
表示部署期间应该使用的gas limit,它可以是null。
fragment.payable ⇒ boolean 表示构造函数在部署期间是否可以接收ether(例如:msg.value != 0)。
fragment.stateMutability ⇒ string 构造函数的state mutability。它可以是:
从任何兼容的object或String创建一个新的ConstructorFragment。
ethers.utils.ConstructorFragment.isConstructorFragment( object ) ⇒ boolean 如果object 是一个 ConstructorFragment返回true。
ethers.utils.ErrorFragment.from( objectOrString ) ⇒ ErrorFragment 从任何兼容的object或String创建一个新的ErrorFragment。
ethers.utils.ErrorFragment.isErrorFragment( object ) ⇒ boolean 如果object 是一个 ErrorFragment返回true。
fragment.anonymous ⇒ boolean 表示事件(event)是否匿名。匿名事件在创建日志时不会将其topic哈希值注入到topic0中。
ethers.utils.EventFragment.from( objectOrString ) ⇒ EventFragment 从任何兼容的object或String创建一个新的EventFragment。
ethers.utils.EventFragment.isEventFragment( object ) ⇒ boolean 如果object 是一个 EventFragment返回true。
fragment.constant ⇒ boolean 表示函数是否为常量(即不改变状态)。如果设为true表示状态可变性是pure
或 view
。
fragment.stateMutability ⇒ string 构造器的状态可变性。它可以是:
nonpayable
payable
pure
view
从任何兼容的object或String创建一个新的FunctionFragment。
ethers.utils.FunctionFragment.isFunctionFragment( object ) ⇒ boolean 如果object 是一个 FunctionFragment 返回true。
下面的例子将表示Solidity中的参数:
string foobar
本地参数名。对于未命名的参数,这个值为null。例如,参数字符串string foobar
会输出foobar
。
参数的完整类型,包括元组和数组符号。对于未命名的参数,这个值可能为null。对于上面的例子,将输出foobar
。
paramType.baseType ⇒ string 参数的基类型(base type)。对于原始类型(例如address
, uint256
等),这等同于type。 对于数组,它将是string array
,对于元组,它将是string tuple
。
数组的children类型。对于任何非数组的参数,这将是null。
paramType.arrayLength ⇒ number 数组的长度,或动态数组的长度-1
。对于不是数组的参数,这个值为null。
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)类是以太坊网络上的与合约交互所需的编码和解码的一种抽象。
许多标准都是随着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)",
"function mint(uint amount) payable",
"function balanceOf(address owner) view returns (uint)",
"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 (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.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))'
// ]
iface.getFunction("transferFrom(address, address, uint256)");
iface.getFunction("transferFrom");
iface.getFunction("0x23b872dd");
iface.getFunction("doesNotExist()");
// [Error: no matching function] {
// argument: 'signature',
// code: 'INVALID_ARGUMENT',
// reason: 'no matching function',
// value: 'doesNotExist()'
// }
iface.getError("AccountLocked(address, uint256)");
iface.getError("AccountLocked");
iface.getError("0xf7c3865a");
iface.getError("DoesNotExist()");
// [Error: no matching error] {
// argument: 'signature',
// code: 'INVALID_ARGUMENT',
// reason: 'no matching error',
// value: 'DoesNotExist()'
// }
iface.getEvent("Transfer(address, address, uint256)");
iface.getEvent("Transfer");
iface.getEvent("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef");
iface.getEvent("DoesNotExist()");
// [Error: no matching event] {
// argument: 'signature',
// code: 'INVALID_ARGUMENT',
// reason: 'no matching event',
// value: 'DoesNotExist()'
// }
签名(Signature) 和 主题(Topic) 的哈希
iface.getSighash("balanceOf");
// '0x70a08231'
iface.getSighash("balanceOf(address)");
// '0x70a08231'
const fragment = iface.getFunction("balanceOf")
iface.getSighash(fragment);
// '0x70a08231'
iface.getEventTopic("Transfer");
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
iface.getEventTopic("Transfer(address, address, uint)");
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
const fragment = iface.getEvent("Transfer")
iface.getEventTopic(fragment);
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
返回已编码的部署数据,这些数据可以连接到合约的部署字节码,以便将值(values)传递到合约构造函数中。
iface.encodeDeploy([ "SYM", "Some Name" ])
// '0x00000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000000353594d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009536f6d65204e616d650000000000000000000000000000000000000000000000'
interface.encodeErrorResult( fragment [ , values ] ) ⇒ string< DataHexString > 返回编码后的错误结果,这通常是对给定值的fragment(参考Specifying Fragments))的reverted调用的响应结果。
大多数开发人员将不需要此方法,但对于那些模拟自己的区块链的开发人员很有用。
iface.encodeErrorResult("AccountLocked", [
"0x8ba1f109551bD432803012645Ac136ddd64DBA72",
parseEther("1.0")
]);
// '0xf7c3865a0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba720000000000000000000000000000000000000000000000000de0b6b3a7640000'
interface.encodeFilterTopics( fragment , values ) ⇒ Array< topic | Array< topic > > 返回已编码的主题过滤器,可以在fragment(参考Specifying Fragments)将values值传递给getLogs函数。
每个主题是一个32字节(64nibble)的DataHexString。
iface.encodeFilterTopics("Transfer", [])
// [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
// ]
iface.encodeFilterTopics("Transfer", [
"0x8ba1f109551bD432803012645Ac136ddd64DBA72"
])
// [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
iface.encodeFilterTopics("Transfer", [
null,
"0x8ba1f109551bD432803012645Ac136ddd64DBA72"
])
// [
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
// null,
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
// ]
iface.encodeFunctionData("transferFrom", [
"0x8ba1f109551bD432803012645Ac136ddd64DBA72",
"0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C",
parseEther("1.0")
])
// '0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000'
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))的调用的响应结果。
大多数开发人员将不需要此方法,但对于那些模拟自己的区块链的开发人员很有用。
iface.encodeFunctionResult("balanceOf", [
"0x8ba1f109551bD432803012645Ac136ddd64DBA72"
])
// '0x0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72'
interface.decodeErrorResult( fragment , data ) ⇒ Result 在对给定数据的fragment(参考Specifying Fragments))revert期间,返回此调用的结果中已解码的值。
大多数开发人员不需要这个数据,因为如果数据表示revert,decodeFunctionResult
将自动解码errors。
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'
// ]
const txData = "0x23b872dd0000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000ab7c8803962c0f2f5bbbe3fa8bf41cd82aa1923c0000000000000000000000000000000000000000000000000de0b6b3a7640000";
iface.decodeFunctionData("transferFrom", txData);
// [
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C',
// { BigNumber: "1000000000000000000" },
// amount: { BigNumber: "1000000000000000000" },
// from: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// to: '0xaB7C8803962c0f2F5BBBe3FA8bf41cd82AA1923C'
// ]
resultData = "0x0000000000000000000000000000000000000000000000000de0b6b3a7640000";
iface.decodeFunctionResult("balanceOf", resultData)
// [
// { BigNumber: "1000000000000000000" }
// ]
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
// }
resultData = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000008ba1f109551bd432803012645ac136ddd64dba72000000000000000000000000000000000000000000000000000000000000000d52696368617264204d6f6f726500000000000000000000000000000000000000";
result = iface.decodeFunctionResult("getUser", resultData);
// [
// [
// 'Richard Moore',
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// addr: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// name: 'Richard Moore'
// ],
// user: [
// 'Richard Moore',
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// addr: '0x8ba1f109551bD432803012645Ac136ddd64DBA72',
// name: 'Richard Moore'
// ]
// ]
result[0][0];
// 'Richard Moore'
result.user.name
// 'Richard Moore'
对于大多数开发人员来说,这些函数通常是最有用的。 它们将自动在ABI中搜索匹配的事件(Event)或函数(Function),并将组件解码为完整的描述信息。
在数据中搜索与错误选择器匹配的错误,并解析出详细信息。
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)'
// }
搜索与日志主题哈希匹配的事件,并解析日志表示的值。
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'
// }
搜索与交易数据签名哈希匹配的函数,并解析出交易的属性。
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" }
// }
Result
inherits Array<any>
一个Result是一个数组,因此每个值都可以作为位置参数访问。
此外,如果值已命名,则可以通过名称访问与其位置值相同的对象。
但是对象中的name为length
是不可用的,因为它是Array的一部分, 所以这个键的任何命名值都被重命名为_length。
如果存在名称冲突,则只有第一个键可用。
errorDescription.args ⇒ Result errorDescription.name ⇒ string error的name。(例如AccountLocked
)
errorDescription.signature ⇒ string error的签名。(例如AccountLocked(address,uint256)
)
errorDescription.sighash ⇒ string
logDescription.name ⇒ string logDescription.signature ⇒ string 事件的签名。(如Transfer(address,address,uint256)
)
logDescription.topic ⇒ string
transactionDescription.args ⇒ Result transactionDescription.name ⇒ string transactionDescription.sighash ⇒ string transactionDescription.signature ⇒ string 函数的签名。(如transfer(address,uint256)
)
当指定一个fragment到接口中的函数时,可以使用以下任意一个:
- 事件或函数的名称(name),如果它在ABI中是唯一确定无歧义(non-ambiguous,例如
transfer
) - 事件或函数的签名。签名是标准化的,例如
uint
和uint256
是等价的(例如transfer(address, uint)
) - 函数的 签名哈希 或 主题哈希 。签名哈希通常指的是Solidity中的函数选择器(例如
0xa9059cbb
) - 一个Fragment
请在此介绍地址、格式和校验和。
还可查看此处: constants.AddressZero
一个地址(Address)是一个20字节(40 nibbles)的DataHexString,混合大小写(mixed case)。
如果大小写混合,它是一个Checksum 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。
getAddress("0x8ba1f109551bd432803012645ac136ddd64dba72");
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72'
getAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36");
// '0x8ba1f109551bD432803012645Ac136ddd64DBA72'
getAddress("0x8Ba1f109551bD432803012645Ac136ddd64DBA72")
// [Error: bad address checksum] {
// argument: 'address',
// code: 'INVALID_ARGUMENT',
// reason: 'bad address checksum',
// value: '0x8Ba1f109551bD432803012645Ac136ddd64DBA72'
// }
getIcapAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK37");
// Error: getIcapAddress is not defined
getIcapAddress("I like turtles!");
// Error: getIcapAddress is not defined
ethers.utils.getIcapAddress( address ) ⇒ string< IcapAddress > getIcapAddress("0x8ba1f109551bd432803012645ac136ddd64dba72");
// 'XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36'
getIcapAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36");
// 'XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36'
ethers.utils.isAddress( address ) ⇒ boolean isAddress("0x8ba1f109551bd432803012645ac136ddd64dba72");
// true
isAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK36");
// true
isAddress("I like turtles.");
// false
ethers.utils.computeAddress( publicOrPrivateKey ) ⇒ string< 地址(Address) > 返回publicOrPrivateKey的地址。公钥可以压缩或不压缩,私钥将自动转换为派生的公钥。
computeAddress("0xb976778317b23a1385ec2d483eda6904d9319135b89f1d8eee9f6d2593e2665d");
// '0x0Ac1dF02185025F65202660F8167210A80dD5086'
computeAddress("0x0376698beebe8ee5c74d8cc50ab84ac301ee8f10af6f28d0ffd6adf4d6d3b9b762");
// '0x0Ac1dF02185025F65202660F8167210A80dD5086'
computeAddress("0x0476698beebe8ee5c74d8cc50ab84ac301ee8f10af6f28d0ffd6adf4d6d3b9b762d46ca56d3dad2ce13213a6f42278dabbb53259f2d92681ea6a0b98197a719be3");
// '0x0Ac1dF02185025F65202660F8167210A80dD5086'
const digest = "0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331";
recoverAddress(digest, {
r: "0x528459e4aec8934dc2ee94c4f3265cf6ce00d47cf42bb106afda3642c72e25eb",
s: "0x42544137118256121502784e5a6425e6183ca964421ecd577db6c66ba9bccdcf",
v: 27
});
// '0x0Ac1dF02185025F65202660F8167210A80dD5086'
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) > const from = "0x8ba1f109551bD432803012645Ac136ddd64DBA72";
const salt = "0x7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331";
const initCode = "0x6394198df16000526103ff60206004601c335afa6040516060f3";
const initCodeHash = keccak256(initCode);
getCreate2Address(from, salt, initCodeHash);
// '0x533ae9d683B10C02EbDb05471642F85230071FC3'
以太坊中的许多操作都是对超出JavaScript安全值范围的数字进行操作。
BigNumber是一个可以安全地对任意大小的数字进行数学运算的对象。
大多数需要返回值的操作将返回一个BigNumber,接受值的参数通常会接收它们。
这个库中的许多函数和方法都接受可以无歧义(non-ambiguously)且安全地转换为BigNumber的值。这些值可以指定为:
number
一个在JavaScript numbers的安全范围内的数字。
BigInt
在支持BigInt的环境中的一个JavaScript BigInt对象。
BigNumber的构造函数不能被直接调用。相反,使用静态BigNumber.from
。
为aBigNumberish返回一个BigNumber的实例。
BigNumber.from("42")
// { BigNumber: "42" }
BigNumber.from("0x2a")
// { BigNumber: "42" }
BigNumber.from("-0x2a")
// { BigNumber: "-42" }
BigNumber.from([ 42 ])
// { BigNumber: "42" }
let one1 = constants.One;
let one2 = BigNumber.from(one1)
one2
// { BigNumber: "1" }
one1 === one2
// true
BigNumber.from(42)
// { BigNumber: "42" }
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 + otherValue的BigNumber。
返回值为 BigNumber - otherValue的BigNumber。
返回值为 BigNumber × otherValue的BigNumber。
返回值为 BigNumber ÷ divisor的BigNumber。
返回值为 BigNumber ÷ divisor余数的BigNumber。
返回值为 BigNumber 指数的幂为exponent的BigNumber。
返回一个BigNumber,其BigNumber的值超出bitcount最低有效位的位则设为0。
Two's Complement是一种优雅的方法, 用于编码和解码固定宽度的有符号值,同时有效地保留数学运算。大多数用户不需要与它们交互。
返回一个BigNumber, BigNumber的值由带位宽(bitwidth)的二进制补码转换而来。
返回一个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.toNumber( ) ⇒ number 将BigNumber的值转换JavaScript值。
如果该值大于Number.MAX_SAFE_INTEGER或小于等于Number.MIN_SAFE_INTEGER, 则会抛出一个错误。
BigNumber.toString( ) ⇒ string
ethers.BigNumber.isBigNumber( object ) ⇒ boolean 当且仅当对象是BigNumber是对象时返回true。
let a = BigNumber.from(42);
let b = BigNumber.from("91");
a.mul(b);
// { BigNumber: "3822" }
这部分是针对一些经常出现的问题。
许多人在处理以太坊时遇到的第一个问题是数字的概念。 大多数常见的货币被划分成非常小的粒度。例如,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之所以在内部被用作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,如Contracts和Providers,但许多低级访问需要通过字节进行操作。
其中许多操作是内部使用的,但各种函数和方法的输出可以用规范化二进制数据表示。
Bytes是一个Array或TypedArray对象, 每个值都在有效字节范围内(例如0到255之间),或者是一个具有length
属性的对象,每个索引的属性都在有效字节范围内。
BytesLike可以是Bytes或DataHexString。
DataHexstring与HexString是相同的,除了它有偶数个nibbles,因此二进制数据作为字符串是有效的。
Hexstring是一个字符串,它有一个0x前缀,后面跟着nibbles number类型(例如,不区分大小写的十六进制字符,0-9
和a-f
)。
- r and s --- The x co-ordinate of r and the s value of the signature
- v --- The parity of the y co-ordinate of r
- _vs --- The compact representation of the s and v
- recoveryParam --- The normalized (i.e. 0 or 1) value of v
原始签名(Raw Signature)是一种常见的签名格式,其中r, s和v被连接成一个65字节(130 nibble)的DataHexString。
SignatureLike类似于一个Signature,除了多余的属性可以被省略或者它也可以是一个 Raw Signature。
例如,如果指定了_vs,则s和v可以省略。同样,如果提供了recoveryParam, 则可以省略v(在这种情况下,可以计算出缺失的值)。
ethers.utils.isBytes( object ) ⇒ boolean 当且仅当object为有效Bytes时返回true。
ethers.utils.isHexString( object , [ length ] ) ⇒ boolean 当且仅当object是一个有效的十六进制字符串时返回true。 如果指定了length并且object 不是一个有效的长度字节的DataHexString,则抛出一个InvalidArgument错误。
ethers.utils.arrayify( DataHexStringOrArrayish [ , options ] ) ⇒ Uint8Array 将DataHexStringOrArrayish转换为Uint8Array。
ethers.utils.hexValue( aBigNumberish ) ⇒ string< HexString > 将aBigNumberish转换为HexString,没有前导零。
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.hexDataLength( aBytesLike ) ⇒ string< DataHexString > ethers.utils.hexDataSlice( aBytesLike , offset [ , endOffset ] ) ⇒ string< DataHexString > 返回一个aBytesLike切片的DataHexString表示,从offset(以字节为单位)到endOffset(以字节为单位)。 如果省略了endOffset,则使用aBytesLike的长度。
ethers.utils.hexStripZeros( aBytesLike ) ⇒ string< HexString > 返回aBytesLike的HexString表示形式,去掉所有前导零。
ethers.utils.hexZeroPad( aBytesLike , length ) ⇒ string< DataHexString > 返回被填充到length字节的aBytesLike的DataHexString表示。
如果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 > 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
// ]
ethers.contants 包含常用的一些值。
ethers.constants.AddressZero ⇒ string< Address > 零地址,它由20字节(40个nibbles)的 0 组成。
零哈希,它由32字节(64个nibbles)的 0 组成。
ethers.constants.EtherSymbol ⇒ string
BigNumber 值表示为"1000000000000000000"
(单位 Wei per Ether)
BigNumber 值表示为 uint256
最大的值
当创建一个应用程序时,在用户友好的字符串(通常显示为ether)和机器可读的值(合约和数学公式所依赖的,通常使用wei)之间进行转换是很有用的
例如,一个钱包通过ether显示余额,在用户界面中用gwei显示gas价格,但是当发送一个交易时,两者都必须用wei表示。
parseUnits是将ether表示的字符串解析成wei表示的字符串, 例如将1.1
解析为wei表示的BigNumber,并且在用户输入值时非常有用,例如发送1.1 ether。
formatUnits是将BigNumberish解析成ether表示的字符串,这在显示余额时非常有用。
Unit可以指定为一个数字,它表示小数点位数。
例子:
- 1 ether 用 wei 表示, 有 18 个小数位 (即 1 ether 表示成 1018 wei)
- 1 bitcoin 用 Satoshi 表示,有 8 个小数位 (即 1 bitcoin 表示成 108 satoshi)
还有几种常见的命名单位,ethers中 可以使用它们的name(string类型)进行操作。
Name | Decimals | |
wei | 0 | |
kwei | 3 | |
mwei | 6 | |
gwei | 9 | |
szabo | 12 | |
finney | 15 | |
ether | 18 | |
ethers.utils.commify( value ) ⇒ string 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" }
等价于调用parseUnits(value, "ether")
。
parseEther("1.0");
// { BigNumber: "1000000000000000000" }
parseEther("-0.5");
// { BigNumber: "-500000000000000000" }
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'
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='
递归长度前缀编码(Recursive Length PrefixRLP编码)在以太坊中被用来序列化数组和数据的嵌套结构。
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'
RLP.decode("0x8412345678");
// '0x12345678'
RLP.decode("0xcac342c1438412345678c0");
// [
// [
// '0x42',
// [
// '0x43'
// ]
// ],
// '0x12345678',
// []
// ]
RLP.decode("0xc0");
// []
数据对象是一种递归结构,用于序列化以太坊中的许多内部结构。每个数据对象可以是:
Examples
"0x1234"
[ "0x1234", [ "0xdead", "0xbeef" ], [ ] ]
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,其值为fixedvalue + otherValue。
返回一个新的FixedNumber,其值为fixedvalue - otherValue。
返回一个新的FixedNumber,其值为fixedvalue × otherValue。
返回一个新的FixedNumber,其值为fixedvalue ÷ otherValue。
返回一个新的FixedNumber,其值为fixedvalue按照decimals进行了四舍五入。
FixedNumber.isZero( ) ⇒ boolean 当且仅当FixedNumber的值为零时返回true。
返回一个新的FixedNumber,其值是fixedvalue按照format进行了格式化后的输出。
fixednumber.toHexString( ) ⇒ string 返回fixednumber的HexString表示形式。
fixednumber.toString( ) ⇒ string 返回fixednumber的字符串(string)表示形式。
fixednumber.toUnsafeFloat( ) ⇒ float 返回一个fixednumber的浮点型JavaScript number 值。由于JavaScript number的四舍五入,该值仅是近似值。
FixedNumber.isFixedNumber( value ) ⇒ boolean 当且仅当value值是FixedNumber时返回true。
通常直接使用这个类是不必要的,因为将格式字符串直接传入FixedNumber时会自动创建。
格式字符串由三个部分组成,包括有符号、位宽和小数位。
有符号格式字符串以fixed
开头,无符号格式字符串以ufixed
开头,然后是位宽和小数位。
宽度必须等于0 mod 8(即(width % 8) == 0
),且不大于256位,小数点的位数不能大于80个。
例如:
- fixed128x18是带符号的,128位宽,有18位小数;这在大多数情况下都是有用的
- fixed32x0是带符号的,32位宽,有0位小数;这和C语言中的
int32_t
是一样的 - ufixed32x0是无符号的,32位宽,有0位小数;这和C语言中的
int32_t
是一样的 - fixed 是
fixed128x18
的缩写 - ufixed 是
ufixed128x18
的缩写
FixedFormat.from( value = "
fixed128x18"
) ⇒ FixedFormat 返回一个由value定义的FixedFormat格式的新实例。 可以传入任何有效的格式字符串,也可以传入任何定义了signed
、width
和decimals
的对象, 包括一个FixedFormat对象。
fixedFormat.signed ⇒ boolean fixedFormat的signed值,如果支持负数则为返回true。
fixedFormat.width ⇒ number fixedFormat.decimals ⇒ number fixedFormat.name ⇒ string fixedFormat的name,可以用来重新创建格式,它是Solidity语言用来表示这种格式的字符串。
在整个区块链空间中使用了许多哈希算法,对于一些复杂的用法可以使用utilities工具来简化这些操作。
加密哈希函数是一个特定的哈希函数家族。
以太坊身份函数(Ethereum Identity function)计算文本字节KECCAK256哈希值。
utils.keccak256([ 0x12, 0x34 ])
// '0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432'
utils.keccak256("0x")
// '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'
utils.keccak256("0x1234")
// '0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432'
utils.keccak256("hello world")
// [Error: invalid arrayify value] {
// argument: 'value',
// code: 'INVALID_ARGUMENT',
// reason: 'invalid arrayify value',
// value: 'hello world'
// }
utils.keccak256(utils.toUtf8Bytes("hello world"))
// '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad'
utils.id("hello world")
// '0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad'
utils.keccak256("0x1234")
// '0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432'
utils.keccak256([ 0x12, 0x34 ])
// '0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432'
bytes = utils.toUtf8Bytes("0x1234")
// Uint8Array [ 48, 120, 49, 50, 51, 52 ]
utils.keccak256(bytes)
// '0x1ac7d1b81b7ba1025b36ccb86723da6ee5a87259f1c2fd5abe69d3200b512ec8'
utils.id("0x1234")
// '0x1ac7d1b81b7ba1025b36ccb86723da6ee5a87259f1c2fd5abe69d3200b512ec8'
utils.ripemd160("0x")
// '0x9c1185a5c5e9fc54612808977ee8f548b2258d31'
utils.ripemd160("0x1234")
// '0xc39867e393cb061b837240862d9ad318c176a96d'
utils.sha256("0x")
// '0xe3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
utils.sha256("0x1234")
// '0x3a103a4e5729ad68c02a678ae39accfbc0ae208096437401b7ceab63cca0622f'
utils.sha512("0x")
// '0xcf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e'
utils.sha512("0x1234")
// '0x4c54886c9821195522d88ff4705c3e0c686b921054421e6ea598739c29c26e1ee75419aaceec94dd2e3c0dbb82ecf895c9f61215f375de6d800d9b99d3d4b816'
HMAC Supported Algorithms
ethers.utils.SupportedAlgorithm.sha256 ⇒ string ethers.utils.SupportedAlgorithm.sha512 ⇒ string const key = "0x0102"
const data = "0x1234"
utils.computeHmac("sha256", key, data)
// '0x7553df81c628815cf569696cad13a37c606c5058df13d9dff4fee2cf5e9b5779'
ethers.utils.hashMessage( message ) ⇒ string< DataHexString< 32 > > 计算message的EIP-191个人信息摘要。 个人消息被转换为UTF-8字节,并以\x19Ethereum Signed Message:
和message的length为前缀。
utils.hashMessage("Hello World")
// '0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2'
utils.hashMessage( [ 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100 ])
// '0xa1de988600a42c4b4ab089b619297c17d53cffae5d5120d82d8a92d0bb3b78f2'
utils.hashMessage("0x42")
// '0xf0d544d6e4a96e1c08adc3efabe2fcb9ec5e28db1ad6c33ace880ba354ab0fce'
utils.hashMessage([ 0x42 ])
// '0xd18c12b87124f9ceb7e1d3a5d06a5ac92ecab15931417e8d1558d9a263f99d63'
utils.hashMessage(utils.arrayify("0x42"))
// '0xd18c12b87124f9ceb7e1d3a5d06a5ac92ecab15931417e8d1558d9a263f99d63'
utils.namehash("")
// '0x0000000000000000000000000000000000000000000000000000000000000000'
utils.namehash("eth")
// '0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae'
utils.namehash("ricmoo.firefly.eth")
// '0x0bcad17ecf260d6506c6b97768bdc2acfb6694445d27ffd3f9c1cfbee4a9bd6d'
utils.namehash("ricmoo.xyz")
// '0x7d56aa46358ba2f8b77d8e05bcabdd2358370dcf34e87810f8cea77ecb3fc57d'
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 > > TypedDataEncoder.hashDomain( domain ) ⇒ string< DataHexString< 32 > > TypedDataEncoder.resolveNames( domain , types , value , resolveName ) ⇒ Promise< any > 返回value的副本,其中任何具有地址(address)
类型的leaf value都将被递归地替换为value的resolveName调用的值。
domain = {
name: 'Ether Mail',
version: '1',
chainId: 1,
verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
};
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 abi.packEncoded(...)
函数时,将使用非标准(non-standard)的紧密打包(tightly packed)的编码版本。 这些函数实现了紧密打包算法(tightly packing algorithm)。
ethers.utils.solidityPack( types , values ) ⇒ string< DataHexString > ethers.utils.solidityKeccak256( types , values ) ⇒ string< DataHexString< 32 > > 返回types中对应类型打包的非标准编码KECCAK256值。
ethers.utils.soliditySha256( types , values ) ⇒ string< DataHexString< 32 > > 返回types中对应类型打包的非标准编码SHA2-256值。
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'
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钱包, Hierarchal Desterministic Wallet) 是为比特币创建的一个标准,但也适用于各种依赖secp256k1私钥的区块链。
更详细的技术理解:
- BIP-32 - 分层确定性描述
- BIP-39 - 用于从人类可读的单词序列(即助记符)派生BIP-32种子的方法
- BIP-44 - 定义了一个标准,使BIP-32易于适应任何未来可兼容的区块链
ethers.utils.defaultPath ⇒ "m/44'/60'/0'/0/0"
助记词的的助记短语,有12、15、18、21或24个单词长,由locale
指定的空格进行分隔。
ethers.HDNode.fromMnemonic( phrase [ , password [ , wordlist ] ] ) ⇒ HDNode ethers.HDNode.fromSeed( aBytesLike ) ⇒ HDNode 为带有aBytesLike的种子返回HDNode。
ethers.HDNode.fromExtendedKey( extendedKey ) ⇒ HDNode 为extendedKey返回HDNode。如果extendedKey被neutered,HDNode将只能计算地址,而不能计算私钥。
fingerprint是用来快速匹配父节点和子节点的索引,但是可能会发生冲突,软件应该验证匹配的节点。
大多数开发者不会用到它。
父节点的fingerprint。请参阅fingerprint了解更多细节。
大多数开发者不会用到它。
HDNode的path(如果已知)。如果助记词也是已知的,这将匹配mnemonic.path
。
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.
大多数开发者不会用到它。
The index of this HDNode. This will match the last component of the path.
大多数开发者不会用到它。
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)的减少。
返回一个新的hdNode实例,其私钥被删除,但所有其他属性保留。 这确保了密钥不会泄露自己或任何派生子密钥,但仍然可以用于计算自己和任何未硬化(non-hardened)子密钥的地址。
hdNode.derivePath( path ) ⇒ HDNode 返回一个新的HDNode,它是通过派生path找到的hdNode的子节点。
ethers.utils.mnemonicToSeed( phrase [ , password ] ) ⇒ string< DataHexString< 64 > > 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
时,可能使用的方式就有所不同。 如果需要使用此功能,请确保你绑定的配置是正确的。
new ethers.utils.Logger( version ) 创建一个新的logger包含所有错误抛出的版本(version)。
Logger.globalLogger( ) ⇒ Logger
logger.debug( ...
args ) ⇒ void logger.info( ...
args ) ⇒ void logger.warn( ...
args ) ⇒ void 这些函数支持当前的审查(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错误。
可以用来确保各种属性(properties)和行为(actions)是安全的。
logger.checkArgumentCount( count , expectedCount [ , message ) ⇒ void logger.checkNew( target , kind ) ⇒ void 如果target不是有效的this
或target
值,则抛出MISSING_NEW错误。 这有助于确保类的调用者是使用new
关键字的。
logger.checkSafeUint53( value [ , message ] ) ⇒ void
Logger.setCensorship( censor [ , permanent =
false ] ) ⇒ void 设置错误审查(error censorship),可选地设置哪些错误可以被审查。
在生产应用程序中,这可以通过屏蔽错误的消息(message)和值(values)来防止错误泄露信息。
这可能会影响调试,使调试变得更加困难。
Logger.setLogLevel( logLevel ) ⇒ void 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.UNKNOWN_ERROR Logger.errors.UNSUPPORTED_OPERATION
Logger.errors.BUFFER_OVERRUN 所需的数据量大于规定的数据量,这将导致数据缓冲区读取超过其末端。
如果合约错误地返回无效的abi编码数据或RLP数据格式不正确,就会发生这种情况。
Logger.errors.INVALID_ARGUMENT 参数的类型或值无效。这通常还包括参数的name
和value
。 任何接受敏感数据(如私钥)的函数将包含string"[[REDACTED]]"
而不是传入的值。
Logger.errors.MISSING_ARGUMENT Logger.errors.MISSING_NEW Logger.errors.UNEXPECTED_ARGUMENT
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 Logger.errors.NONCE_EXPIRED 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
, receipt
和 replacement
。
有关更多详细信息,请参阅wait
方法的TransactionResponse。
Logger.errors.UNPREDICTABLE_GAS_LIMIT 在估计一个交易所需的gas数量时,查询一个节点以获得其最佳的预测值。
如果节点不能(或不愿意)预测成本值,则会发生此错误。
对于这种情况,最好的解决办法是在交易中手动指定一个gas limit。
该错误还会表明,如果一个没有token的帐户试图发送token,交易无论如何都将失败。
这是一组实用的工具函数,以平台安全的方式来处理属性。
下一个主要版本的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 ) 此签名密钥的未压缩公钥。它是65个字节(130个nibbles),并以0x04
作为开头。
此签名密钥的压缩公钥。它是33个字节(66个nibbles),并以0x02
或0x03
作为开头。
signingKey.computeSharedSecret( otherKey ) ⇒ string< DataHexString< 32 > > 使用otherKey计算ECDH共享密钥。otherKey可以是公钥或私钥,但通常是来自另一方的公钥。
非常推荐各方在将其用作对称密钥之前计算其哈希。
SigningKey.isSigningKey( anObject ) ⇒ boolean
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)的内容通常是人可读(human-readable)的。
在处理区块链时,正确处理人可读(human-readable)和人提供(human-provided)的数据 对于防范资金的损失、权限的错误等非常重要。
在Solidity存储一个字符串时,前缀是字符串的长度(256位,即32个字节),接着的才是字符串的内容。 这意味着即使很短的字符串也至少需要2个words(64个字节)的存储空间。
大多数情况下,我们处理的是较短的字符串,所以我们不用字符串的长度作为前缀, 而是通过null-terminate处理字符串并将其放入一个word(32个字节)中。 由于null termination只需要一个字节,因此可以在一个word中存储长度不超过31字节的字符串。
注意
长度为31bytes的字符串可能包含少于31个characters,因为UTF-8需要多个字节来编码国际字符。
ethers.utils.parseBytes32String( aBytesLike ) ⇒ string ethers.utils.formatBytes32String( text ) ⇒ string< DataHexString< 32 > > 返回text的bytes32
字符串表示形式。如果text长度超过31字节,则会抛出错误。
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时会抛出一个错误。
在标准化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错误,可以使用自定义错误处理函数,该函数具有如下签名:
errorFunction( reason , offset , bytes , output [ , badCodepoint ] ) ⇒ number reason是一个utf-8的错误原因,offset是第一次遇到error字节的索引, output已经处理过的(并且可能被修改)的代码点列表和一些错误原因, badCodepoint表示当前计算的代码点,如果它的值是无效的该签名将会被拒绝。
这个函数返回要跳过的字节数,因为offset的值已经被使用过了。
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 unsignedTransaction.chainId ⇒ number 此交易的链ID。如果链ID为0或null,则EIP-155失效并使用遗留签名(legacy signing),除非重写覆盖该签名。
表示交易的通用对象。
交易哈希,可以用作交易的标识符。这是交易的序列化RLP编码的keccak256表示。
transaction.from ⇒ string< Address > transaction.nonce ⇒ number 交易的nonce值。每个从帐户发送到网络的交易都包含这个值,这确保了交易的顺序和不可重复支付的特性。 这个值等于from地址发送到网络的交易数。
交易的gas limit。一个帐户必须有足够的以太(ether)来支付gas(在指定的gasPrice)。 在交易结束时,任何未使用的gas都将被退还,如果没有足够的gas来完成执行, 则交易的效果将被revert,但gas会被完全消耗,并抛出了out-of-gas错误。
交易的每单位gas的价格(单位为wei)。
对于EIP-1559交易,该值为null。
交易的每单位gas的最高价格(单位为wei)。
对于非EIP-1559交易,该值为null。
交易的每单位gas的优先费用价格(单位wei)。
对于非EIP-1559交易,该值为null。
用于交易的数据。在合约中,这是call data。
transaction.chainId ⇒ number 交易的链ID。这是EIP-155的一部分,用于防止不同网络上的重放攻击(replay attacks)。
例如,如果在ropsten链上进行的交易可能会被打包到homestead链上被使用, 那么在ropsten链上签署的交易可能会在homestead链上执行,即使不是用户故意这样操作的。
有些情况下,可能需要重放,但这是非常罕见的,因此建议总是指定链ID。
交易的椭圆曲线签名的r部分,准确地说,是点r(从该点可以计算出y和v)的x坐标。
交易的椭圆曲线签名的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和签名之间不匹配,则抛出错误。
ethers.utils.fetchJson( urlOrConnectionInfo [ , json [ , processFunc ] ] ) ⇒ Promise< any > 从urlOrConnectionInfo中获取并解析JSON内容,body参数json、processFun(在返回前处理result内容)是可选的。
ethers.utils.poll( pollFunc [ , options ] ) ⇒ Promise< any > 使用PollOptions反复调用pollFunc直到它返回一个非undefined的值。
connection.timeout ⇒ number 表示在遇到timeout错误并交易被拒绝之前,需要等待多长的时间。
connection.headers ⇒ {[key:string]:string}
options.retryLimit ⇒ number 在发生错误或返回undefined的情况下进行重试的次数。
如果指定了该值,轮询将等待来自provider的新区块,然后再执行pollFunc。
如果指定了该值,provider的每个轮询周期将会进行轮询,然后再执行pollFunc。
wordlist.getWord( index ) ⇒ string wordlist.getWordIndex( word ) ⇒ number wordlist.split( mnemonic ) ⇒ Array< string > 根据locale设置的有效空白字符集,返回被拆分为单个单词的助记符。
wordlist.join( words ) ⇒ string 通过使用locale设置的标准空格将单词连接在一起,再返回助记符。
检查所有单词双向的映射是否正确,并返回列表的哈希值。 子类应该使用它来验证词表是否与官方词表的哈希值匹配。
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库更加模块化,可以有额外的辅助库,这些库不是核心的部分,但可以选择添加只有在指定的某些情况下才需要的功能库。
这个模块应该被视为试验性的。
本文提供了用于EVM的Ethers ASM Dialect快速、高级的概述, 它是由Ethers ASM Dialect Grammar定义的。
一旦一个程序被高级语言编译成ASM(汇编),或者直接在ASM中进行手动编码,它就需要被编译成字节码。
编译过程执行非常小的操作集,并且有意简化,这与底层EVM字节码密切相关。
操作包括在程序中的嵌入程序(例如,部署辅助程序中嵌入了runtime)和计算跳转操作所需的偏移量。
命令行汇编器可用于编译Ethers ASM Dialect文件或将字节码反汇编为人类可读的操作码和文字。
操作码(Opcodes)可以提供功能性语法(functional syntax)或指导性语法(functional syntax)。 对于需要参数的操作码,推荐使用功能性语法,指导性语法会发出警告。
@TODO: Examples
标签是程序中可以跳转到的位置。一个JUMPDEST
会自动添加到汇编输出中的这个点。
@TODO: Examples
一个字面量(Literals)执行PUSH
操作,表示将数据放入栈中。
提供一个字面量(Literals)能以DataHexString或十进制的字节值的形式。
@TODO: examples
要在Ethers ASM Dialect中输入注释,分号(即 ; )后面的任何文本都会被汇编程序忽略。
以太坊的常见情况是将一个程序嵌入到另一个程序中。
最常见的用法是在部署字节码中嵌入一个合约运行时字节码,这个字节码可以用作初始化代码(init code)。
当程序部署到以太坊时,将使用init transaction。init transaction的to
字段的地址为空, data
的字段数据中包含字节码。 data
字段里的字节码就是程序,执行时返回其他字节码作为结果(result),返回的结果就是要被安装的字节码。
因此,重要的是,嵌入式代码使用相对于自身的跳转,而不是相对于它所嵌入的整个程序, 这也意味着跳转只能针对它自己的作用域,而不能针对父作用域或子作用域。这是由汇编程序强制执行的。
作用域可以访问任何子数据段(Data Segment)或子作用域 的偏移量(相对于自身而言),也可以访问程序中任何位置的数据段(Data Segment)或作用域的长度。
在Ethers ASM Dialect中的每个程序都有一个最高等级的作用域_
。
数据段 允许将任意数据嵌入到程序中,这对于查找表或部署常量很有用。
空的数据段也可以在一个需要带标签的位置中使用,没有JUMPDEST(标签所添加的)。
@TODO: Example
一个 链接 允许访问作用域, 数据段(Data Segment) or 标签.
要访问一个标记项的字节偏移量,使用$foobar
。
对于标签,目标(target)必须在此范围内是直接可达的。 对于数据段(Data Segment)或作用域,它可以位于同一作用域或任何子作用域内。
对于数据段(Data Segment)或标签, 还有一种附加类型的链接,它分别提供数据或字节码的长度。Length Link 可以通过#foobar
获得, 并作为字面量压入栈中。
@TODO: exampl
汇编实用程序允许解析和编译Ethers ASM Dialect源文件。
反汇编程序 可以很容易地将字节码转换为一个对象,方便地去检查程序结构。
asm.disassemble( bytecode ) ⇒ 字节码 asm.formatBytecode( operations ) ⇒ string 每个数组索引代表一个操作,将多字节操作(即PUSH
)分解为单个操作。
通过给定的偏移量在字节码中获得操作(operation)。需要确保偏移处的字节是一个操作, 而不是包含在PUSH
中的数据(如果是这种情况将返回null)。
操作是来自反汇编字节码流的单个命令。
operation.offset ⇒ number 如果操作码是PUSH
,那么这个输出就是push的值
为给定的数值(例如:0x60 is PUSH1)或助记符字符串(例如:"PUSH1")创建一个新的操作码实例。
opcode.isMemory( ) ⇒ "read" | "write" | "full" opcode.isStatic( ) ⇒ boolean 如果操作码不能更改状态(静态的),则返回true。
opcode.isJump( ) ⇒ boolean opcode.isPush( ) ⇒ number 如果操作码不是PUSH*
,则返回0; 如果是,则返回该操作码将push的字节。
抽象语法树(Abstract Syntax Tree)
使用Ethers ASM Dialect去解析文件将生成一个抽象语法树。 根节点将始终是name为_
的ScopeNode。
要将文件解析为抽象语法树,请使用parse函数。
@TODO: 在这里放置一个显示层次结构的图表
此节点所表示的源代码,和该节点在源代码中的位置(location)。
一个 ValueNode(值节点) 是一个可以操作堆栈的节点。
literalNode.verbatim ⇒ boolean 在DataNode上下文中,这是true, 因为在这种情况下,值应该逐字取,不应该添加PUSH
操作,否则为false。
PopNode用于存储栈中隐式弹出的占位符。 它表示隐式占位符(例如$$
)或显式占位符(例如$1
)的代码,它表示要预期要使用的堆栈位置。
literalNode.index ⇒ number 这个PopNode所代表的索引。对于隐式占位符是0
。
LinkNode(链接节点) 表示到另一个节点数据的链接,例如$foo
或#bar
。
linkNode.type ⇒ "offset" | "length"
传递到此节点的所有操作数(operands)的列表。
EvaluationNode用于执行代码和插入结果,但不生成任何输出程序集,使用{{! code here }}
语法。
literalNode.verbatim ⇒ boolean 在DataNode上下文中,这是true, 因为在这种情况下,值应该逐字取,不应该添加PUSH
操作,否则为false。
evaluationNode.script ⇒ string ExecutionNode
inherits 节点
ExecutionNode 用于执行代码,但不生成任何输出程序集,使用{{! code here }}
语法。
evaluationNode.script ⇒ string 要执行的代码。任何结果(result)都会被忽略。
LabelledNode用于任何具有name的节点,因此可以作为LinkNode的目标。
labelledNode.name ⇒ string 通过使用@myLabel:
引用name,LabelNode将被用作要跳转的位置。 自动在字节码偏移处插入一个JUMPDEST
。
DataNode允许数据直接插入到输出程序集中,使用@myData[ ... ]
。 如果需要则对数据进行填充,以确保原本被视为PUSH
值的值不会影响数据以外的任何内容。
ScopeNode允许一个新的引用框架,所有LinkNode在解析偏移位置时都会使用这个引用框架,使用@myScope{ ... }
。
scopeNode.statements ⇒ Array< 节点 >
Ledger Hardware 钱包 是非常受欢迎的。
import { LedgerSigner } from "@ethersproject/hardware-wallets";
new LedgerSigner( [ provider [ , type [ , path ] ] ] ) ⇒ LedgerSigner 连接到Ledger Hardware 钱包。如果未指定类型,则由环境(environment)决定; 在节点中默认是"hid",在浏览器中默认是"u2f"。如果路径(path)不指定,则使用默认的以太坊路径。
试验性的库用于基本库中没有准备好包含的特性。API不是稳定的,而且不遵循semver版本控制, 因此需要它的应用程序应该指定所需的确切版本。
这些特性在ethers核心库中是不可用的,所以要使用它们,你必须安装@ethersproject/experimental
包并从其中导入它们。
Ethers在v4中删除了对BrainWallets的支持,因为它们是不安全的,并且很容易被猜到,从而让攻击者能够窃取资金。 提供这个类是为了确保使用了BrainWallets的旧系统仍然可以恢复其资金和资产。
BrainWallet.generate( username , password [ , progressCallback ] ) ⇒ BrainWallet 生成BrainWallets,体验功能略有改进,其中生成的钱包有一个助记词。
BrainWallet.generateLegacy( username , password [ , progressCallback ] ) ⇒ BrainWallet 生成一个与ether v3及更早版本兼容的BrainWallets。
const { BrainWallet } = require("@ethersproject/experimental");
import { BrainWallet } from "@ethersproject/experimental";
EIP1193Bridge允许将普通的EthersSigner和Provider作为标准的EIP-1193 Provider公开, 这在与其他库交互时可能很有用。
const { Eip1193Bridge } = require("@ethersproject/experimental");
import { Eip1193Bridge } from "@ethersproject/experimental";
NonceManager设计用于为Signer管理nonce,在发送交易时自动增加。
目前NonceManager不处理重复广播(re-broadcast)。 如果您试图向不控制该帐户的节点上的网络发送大量交易,交易池可能会删除您的交易。
将来,如果NonceManager能记住交易并在网络上监听它们,并舍弃重复广播的交易,那就更好不过了。
另一个未来的特性将是某种失败模式。例如,一个交易通常依赖于另一个首先被挖出的交易。
new NonceManager( signer ) nonceManager.setTransactionCount( count ) ⇒ void 为signer设置当前交易计数(nonce)。
在使用该类之外的方法与signer交互时,这可能很有用。
nonceManager.incrementTransactionCount( [ count =
1 ] ) ⇒ void 按count增加当前交易数。
在使用该类之外的方法与signer交互时,这可能很有用。
const { NonceManager } = require("@ethersproject/experimental");
import { NonceManager } from "@ethersproject/experimental";
在学习、调试和管理与以太坊网络的交互过程中,sandbox实用程序提供了一种使用最常用以太坊实用程序的简单方法。
If no command is given, it will enter a REPL interface with many of the ethers utilities already exposed.
用法:
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
/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
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
/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
}
/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命令行工具允许您将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 (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")
}}
]
}
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
# 和上面一样
用法:
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
用法:
ethers-ts FILENAME [ ... ] [ OPTIONS ]
选项
--output FILENAME 将输出写入FILENAME文件(default: stdout)
--force 如果它们存在则覆盖它们
--no-optimize 不运行solc optimizer
--no-bytecode 不包括字节码和Factory方法
其他选项
--debug 显示错误的堆栈跟踪
--help 显示此用法并退出
--version 显示此版本并退出
(*) 因为在命令行中包含助记符或私钥,在你系统上的其他用户可能可以读取它们,并可能存储在bash历史文件中。不推荐这样做。
TODO
cli库的目的是方便您自己创建自己ID命令行工具
一个CLI处理所有命令行标志、选项和参数的解析,并且实例化一个Plugin来处理命令
一个CLI可以支持多个Plugin在这种情况下,第一个参数被用来决定运行哪一个 (如果没有参数,将选择默认的插件) ,或者可以被设计成独立的, 在这种情况下,正好可以使用一个Plugin并且不允许有命令参数。
addPlugin( command , pluginClass ) ⇒ void 为command添加一个plugin类。在所有选项和标志被使用后,第一个参数将被使用,相关的插件类将被实例化并运行。
setPlugin( pluginClass ) ⇒ void 设置一个专门的Plugin类,将处理所有的输入。 它不能与addPlugin一起使用,也不会自动接受参数中的命令。
showUsage( [ message = ""
[ , status =
0 ] ] ) ⇒ never run( args ) ⇒ Promise< void > 通常传入的args数值将会是process.argv.slice(2)
.
每个Plugin管理CLI的每一条命令并且分阶段执行。
如果使用CLI的请求(i.e. 即帮助), 则使用静态方法getHelp
和getOptionHelp
用来生成帮助界面
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.accounts ⇒ Array< Signer > 使用这个插件可以使用--account
,--account-rpc
和--account-void
传递给插件的账户
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.getOptionHelp ⇒ Array< Help > 如果它支持用于生成帮助屏幕的附加选项,每个子类都应该实现这个静态方法。
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 argParser.consumeMultiOptions( names ) ⇒ Array< {name:string,value:string} > 删除所有与names数组中的任何名称相匹配的选项,其值返回值的列表(按顺序)。
argParser.consumeOption( name ) ⇒ string 删除带有name值的选项,并返回该值。如果该选项被多次包含,这将抛出一个UsageError。
argParser.consumeOptions( name ) ⇒ Array< string > 删除所有选项及其对name的值,并返回(按顺序)值的列表。
一个常见的、简单的代码片段的集合(会随着时间的推移而增加),这些代码片段一般来说是有用的
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.
/home/ricmoo/my-react-project> npm install @ethersproject/shims --save
import "@ethersproject/shims"
import { ethers } from "ethers";
React Native环境不包含用于计算随机私钥的安全随机源。这可能会导致其他人可能猜到的私钥,从而导致资金被盗和资产被操纵。
由于这个原因,强烈建议也安装React Native get-random-values,它必须被包含shims的前面。 如果它工作正常,您应该不会在控制台中收到任何关于丢失安全随机源的警告。
Importing with Secure Random Sources
import "react-native-get-random-values"
import "@ethersproject/shims"
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或其他库的旧版本升级时的迁移指南。
本迁移指南的重点是将web3.js 1.2.9版本迁移到ethers.js v5。
在ethers中,Providers为连接到ethers网络提供了一个抽象的概念。 它可以用来发布只读查询,并向ethers网络发送签名的状态变化交易。
var Web3 = require('web3');
var web3 = new Web3('http://localhost:8545');
var ethers = require('ethers');
const url = "http://127.0.0.1:8545";
const provider = new ethers.providers.JsonRpcProvider(url);
const web3 = new Web3(Web3.givenProvider);
const provider = new ethers.providers.Web3Provider(window.ethereum);
在ethers中,签名是ethers账户的一个抽象概念。它可以用来签署消息和交易,并将签署的交易发送到ethers网络。
在web3中,一个账户可以用来签署消息和交易。
const account = web3.eth.accounts.create();
const signer = ethers.Wallet.createRandom();
const signer = provider.getSigner();
signature = web3.eth.accounts.sign('Some data', privateKey)
signature = await signer.signMessage('Some data')
一个合约对象是ethers网络上智能合约的一个抽象。它允许与智能合约简单交互。
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)
});
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();
const contract = new web3.eth.Contract(abi, contractAddress);
contract.methods.getValue().call();
contract.methods.changeValue(42).send({from: ....})
.on('receipt', function(){
...
});
const contract = new ethers.Contract(contractAddress, abi, provider);
const value = await contract.getValue();
const contract = new ethers.Contract(contractAddress, abi, signer);
const tx = await contract.changeValue(33);
const receipt = await tx.wait();
重载函数是具有相同名称但参数类型不同的函数。
在以太网中,调用一个重载合约函数的语法与非重载函数不同。 本节展示了web3 和ethers在调用重载函数时的区别。
查看issue #407更多详情。
message = await contract.methods.getMessage('nice').call();
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类型
web3.utils.toBN('123456');
ethers.BigNumber.from(123456)
ethers.BigNumber.from("123456")
ethers.BigNumber.from("0x1e240")
计算web3和ethers中UTF-8字符串的Keccak256哈希值:
web3.utils.sha3('hello world');
web3.utils.keccak256('hello world');
ethers.utils.id('hello world')
ethers.utils.keccak256('0x4242')
Migration: From Ethers v4
这份文件只涵盖了v4中存在的、在v5中发生了一些重要变化的功能。
它没有涵盖所有已添加的新特性,主要目的是帮助那些更新他们的旧脚本和应用程序的人保持功能的一致性。
如果你遇到任何遗漏的变化,请告诉我,我将更新本指南。
由于大数(BigNumber)使用的相当频繁, 它已经被移到the top level of the umbrella package.
ethers.utils.BigNumber
ethers.utils.BigNumberish
ethers.BigNumber
ethers.BigNumberish
bigNumberify
总是优先于构造函数,因为它可以缩短[[BigNumber]对象的对象实例化(因为它们是不可变的)这已经被移动到了静态的from
类方法中.
new ethers.utils.BigNumber(someValue)
ethers.utils.bigNumberify(someValue);
ethers.BigNumber.from(someValue)
解析的地址的名称已更改。如果传递给构造函数的地址是一个ENS名称,那么在对合约进行任何调用之前,该地址将被解析。
解析地址的属性名称从addressPromise
改为了resolvedAddress
。
contract.addressPromise
contract.resolvedAddress
The only difference in gas estimation is that the bucket has changed its name from estimate
to estimateGas
.
contract.estimate.transfer(toAddress, amount)
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.
const abi = [
"function single() view returns (uint8)",
"function double() view returns (uint8, uint8)",
];
await contract.single()
await contract.functions.single()
await contract.single()
await contract.functions.single()
await contract.double()
await contract.functions.double()
await contract.double()
await contract.functions.double()
所有错误现在属于Logger类,相关的函数已经转移到Logger实例中, 它可以包括每个包的版本字符串
全局错误函数已经被移至Logger类方法中。
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(...)
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(...)
[[接口]]对象经历了最具戏剧性的变化。
它不再是一个元类,现在有一些方法可以简化处理合约接口操作,而不需要进行对象检查和特殊的临界情况
interface.functions.transfer.encode(to, amount)
interface.functions.transfer.decode(callData)
interface.encodeFunctionData("transfer", [ to, amount ])
interface.decodeFunctionResult("transfer", data)
interface.encodeFunctionData("transfer(address,uint)", [ to, amount ])
interface.decodeFunctionResult("transfer(address to, uint256 amount)", data)
interface.events.Transfer.encodeTopics(values)
interface.events.Transfer.decode(data, topics)
interface.encodeFilterTopics("Transfer", values)
interface.decodeEventLog("Transfer", data, topics)
现在(大部分)关于一个函数或者事件的查询可以直接在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
.
wallet.mnemonic
wallet.path
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或更好的环境下工作,并在以下环境下运行测试:
- node.js 8.x
- node.js 10.x
- node.js 12.x
- node.js 13.x
- Web Browsers (using UMD)
- Web Browsers (using ES modules)
如果你觉得有一个环境被忽视了或有建议,请随时打开一个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.gz | Private 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.gz | HDNodes (BIP-32) with mnemonics, entropy, seed and computed nodes with pathes and addresses 带有助记符、entropy、seed和带有路径和地址的计算节点的hdnode (BIP-32) | |
namehash.json.gz | ENS名称与计算的namehashes | |
nameprep.json.gz | IDNA和Nameprep表示法,包括官方矢量 | |
rlp-coder.json.gz | 递归长度前缀(RLP)数据和编码 | |
solidity-hashes.json.gz | 基于Solidity非标准打包形式的哈希值 | |
transactions.json.gz | 带有序列化格式的已签名和未签名交易,包括带有和不带有EIP-155重放保护的交易 | |
units.json.gz | 不同单位之间转换的值 | |
wallets.json.gz | Keystore JSON格式的钱包、密码和解密值 | |
wordlists.json.gz | 完全解压的BIP-39官方词汇表 | |
测试组件套 | |
对于那些直接使用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.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 | 有校验和调整的情况下的地址 | |
icapAddress | ICAP地址 | |
属性 | |
{
"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 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 Algorithm的测试用例
{
"expected": "0x33868cc5c3fd3a9cd3adbc1e868ea133d2218f60dc2660c3bc48d8b1f4961384",
"name": "ViTalIk.WALlet.Eth",
"test": "mixed case"
}
{
"name": "arrayWithNullString3",
"encoded": "0xc3808080",
"decoded": [ "0x", "0x", "0x" ]
}
对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"
}
我编写ethers.js库是出于需要的,并且随着时间的推移已经有组织的发展起来。
很多事情都是有原因的(至少在当时),但我总是欢迎批评,并且完全愿意改变我对事情的看法。
申请拉取代码请求, 不过请记住几点:
- 破坏后向兼容性的改变将不会被接受;它们可能主要考虑的是下一个主要版本
- 安全是很重要的; adding dependencies require fairly convincing arguments as to why
- 这个库的目标是精简,所以在修改之前和之后都要注意dist/ether.min.js文件的大小
- 保持PR简单易读; 只修改
docs.wrm/
和packages/*/src.ts/
文件夹中的文件,因为这样可以方便地验证更改 - 为预期的和意外的输入添加测试用例
- 任何新特性都需要我的支持(未来问题、文档、测试、迁移),所以任何过于复杂或特定的特性都可能不被接受
一般来说, 在开始一个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源代码.
# 开始观察文件并在文件发生变化时重新构建
/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
大多数开发者不应该需要这个步骤,但对于分叉以太坊和创建替代物的人来说(例如,如果你有一个非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的相关性,这样更容易验证修改。
同样,这对大多数开发者来说应该是没有必要的。这个步骤需要使用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的文档而编写的。
风格指南(这一章会有更多内容):
- 尽量保持行的长度不超过大约80个字符
- 避免在源代码中使用内联链接;使用config.js中的
externalLinks
字段 - 用
link-
作为外部链接的前缀 - 更改锚定名称必须合乎情理,因为它将中断该部分的所有链接,flatworm在未来将支持符号链接
- 一般来说,我的目标是一致性;在整个文档中寻找类似的情况
要构建文档,您应该首先遵循以上步骤来构建ethers库.
构建文档将生成几种类型的输出:
- 一套完整的HTML页面,相互链接
- 一个单页HTML页面,所有页面都链接到本地锚点
- 整套的README.md页面,在GitHub中可以浏览和链接。
- 元数据转储,用于工具摄取(仍需更多工作)
- (@TODO;只完成了一半)以LaTeX和生成的PDF格式的文档
/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
在互联网上有很多文档可以帮助您入门,学习更多或者涵盖高级主题。这里有一些资源可以查看。
我不管理或维护这些教程,但我遇到过它们。 如果一个链接已经失效或过时了,请让我知道,我会更新它