ethers.js_day01
1. ethers.js的安装
- 参考文档
- 在终端执行命令:
1 | npm install --save ethers |
- 出现的问题:
a. 按照 B站教程来的话,需要修改package.js中的配置,如:
1
2
3
4
5
6 {
"type": "module",
"dependencies": {
"ethers": "^5.6.9"
}
}在这里:加入了
''type": "module"
之后,require()
这种引包的方式就不能使用了,网上搜了一下说是 ES6模块(没系统学过JavaScript,不是很懂,只是记录一下,避免继续犯错),得需要使用import
的方式导入ethers
包
1 const { ethers } = require("ethers");
1 import { ethers } from "ethers";b. B站视频中在vscode上安装的ethers包的命令为:
1 npm install ethers@5.6.9这行代码会让代码无法运行,报错如下:
2. 编写 Hello Vitalik
2.1 编译器:
- 在线编译器 —– playcode
- 本地编译器 —– VSCode
2.2 第一段代码
- 示例代码:
1 | import { ethers } from "ethers"; |
- 解读代码
1
2
3
4 const main = async () => {
const balance = await provider.getBalance(`vitalik.eth`);
console.log(`ETH Balance of vitalik: ${ethers.utils.formatEther(balance)} ETH`);
}代码的意思:
这是一段使用 JavaScript 编写的代码,使用了 ethers.js 库与以太坊节点进行交互。
这段代码定义了一个异步函数
main()
,在函数中:
- 使用
provider.getBalance()
函数获取vitalik.eth
地址上的以太币余额,并将结果存储在balance
变量中。这里的provider
是一个 ethers.js 库中提供的以太坊节点对象,它用于与以太坊网络进行通信。- 使用
ethers.utils.formatEther()
函数将以太币余额从 wei 单位转换为以太币单位,并将结果打印到控制台中。因为
getBalance()
函数是异步函数,它会从以太坊网络中读取数据,因此需要使用await
关键字等待它完成读取操作。在等待getBalance()
函数完成后,balance
变量将包含返回的余额值,可以对其进行处理并打印到控制台中。
ethers.js_day02
3. Provider 提供器
3.1 运行如下代码报错:
1 | // 导入ethers包 |
报错结果如下:
报错原因是说
JsonRpcProvider
不是一个构造器,到官方文档上查看,JsonRpcProvider
的用法是:1
2
3
4// 连接以太坊主网
const providerETH = new ethers.providers.JsonRpcProvider(ALCHEMY_MAINNET_URL)
// 连接Goerli测试网
const providerGoerli = new ethers.providers.JsonRpcProvider(ALCHEMY_GOERLI_URL)
3.2 与本地的ganache获取连接
- 代码:
1 | import { ethers } from "ethers"; |
3.3 与 metamask(小狐狸🦊钱包)建立连接
3.3.1 需要到 Alchemy 上获取RPC 的节点
1 | const provider = ethers.getDefaultProvider(""); // 从Alchemy获取 |
3.4 利用Provider
读取链上数据
tips:const provider = new ethers.providers.JsonRpcProvider("HTTP://127.0.0.1:8545");
这个的连接速度要比const provider = ethers.getDefaultProvider("HTTP://127.0.0.1:8545");
的连接速度更快
3.5 获取指定地址的余额 – provider.getBalance(address)
代码:
1
2
3
4
5 >provider.getBalance(address).then((balance) => {
console.log(`Account balance: ${ethers.utils.formatEther(balance)} ETH`);
>}).catch((err) => {
console.error('Error:', err);
>});解释:
这段代码是使用以太坊 JavaScript 库 Ethers.js 来查询指定以太坊地址的余额,并将其以以太币(ETH)的形式打印到控制台上。
具体来说,这段代码执行了以下步骤:
- 调用
provider.getBalance(address)
方法,其中provider
是一个以太坊节点提供商,address
是要查询的以太坊地址。getBalance()
方法返回一个 Promise,该 Promise 在查询完成后返回以太坊地址的余额。- 当 Promise 被解析时,
then()
方法中的回调函数会被执行。该回调函数使用ethers.utils.formatEther()
方法将余额从 wei 转换为以太,并将结果打印到控制台上。- 如果 Promise 被拒绝(即查询失败),则
catch()
方法中的回调函数会被执行,该回调函数将错误信息打印到控制台上
3.6 查询provider连接到那条链 – provider.getNetwork()
代码:
1 const netWork = await provider.getNetwork();查询结果:
3.7 查询当前 gas price — provider.getGasPrice()
tips:返回的数据格式为BigNumber,可以用BigNumber类的toNumber()或toString() 方法转换成数字或者字符串
代码:
1
2 >const gas_price = await provider.getGasPrice();
>console.log(gas_price.toString());运行结果:
3.8 查询区块信息 – provider.getBlock()
代码:
1 const block = await provider.getBlock(2);查询结果:
3.9 查询地址的合约bytecode — provider.getCode(address)
代码:
1
2 const address = "0x44f2A5d2CFf45111E5FdcBE600CfA62Ea0386E7f"; //合约地址
const code = await providerETH.getCode(address);查询结果:
4.读取合约信息
4.1 创建Contract变量
4.1.1 只读Contract
tips:参数分别是合约地址,合约的abi和provider变量(只读)
代码:
1 const contract = new ethers.Contract(`address`,`abi`,`provider`)
4.1.2 可读写 Contract
tips: 参数分别是合约地址,合约的abi和singer变量。Singer签名者是ethers中另一个类,用于签名交易。
代码:
1 const contract = new ethers.Contract(`address`, `abi`, `signer`);
4.2 创建只读 Contract实例
tips:创建Contract实例需要填入三个参数,分别是合约地址,合约abi和provider变量。
tips:ehters支持两种abi填法
方法一:直接输出合约的abi。也可以像之前web3中那样使用
fs
包读取.abi
文件,我觉得这种可读性更强一些。
1
2
3
4 var fs = require("fs");
var path = "";//这里是你本地abi的文件地址
var contractABI = JSON.parse(fs.readFileSync(path),toString());参考代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 // SPDX-License-Identifier: MIT
pragma solidity ^0.4.21;
contract CallMeChallenge {
int public value = 5;
bool public isComplete = false;
function callme() public {
isComplete = true;
}
function getValue() external view returns(int) {
return value;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 import { Contract, ethers } from "ethers"; // ES6模块下引入ethers包
// var fs = require("fs")
import fs from "fs"; // ES6模块下引入fs包
const provider = new ethers.providers.JsonRpcProvider("HTTP://127.0.0.1:8545");
// 读取文件的abi
let con_ABI = JSON.parse(fs.readFileSync("D:\\VSCode\\VSCode_Data\\ethersjsWorkspace\\LearningSelf\\abi\\CallMeChallenge.abi").toString())
// 合约地址
let con_Address = "0xCfD32B1C6C1b2Fb425F3d6a86F8E2ebB363b5d96";
// 创建合约
let contract = new ethers.Contract(con_Address,con_ABI,provider);
async function main() {
let slot0 = await provider.getStorageAt(con_Address,0);
console.log("slot0 = >" + slot0);
let slot1 = await provider.getStorageAt(con_Address,0);
console.log("slot1 = >" + slot1);
}
// 调用函数
main()
// 调用合约的只读函数
let value = await contract.getValue();
console.log("value => " + value);
let address = "0xfDc84b042c01F01C7cC6eF08452BA156844c3A5C";
provider.getBalance(address).then((balance)=>{
console.log(`Account balance: ${ethers.utils.formatEther(balance)} ETH`);
})运行结果:
注: 在Solidity中,布尔类型(bool)的默认值是false。在你的智能合约中,存储槽(slot)的位置为0,存储的变量是布尔类型(isComplete),因此默认值为false。
当你部署合约时,存储槽的初始值会被设置为false。如果在之后合约的执行过程中,调用了
callme()
函数,isComplete变量的值会被设置为true,存储槽的值也会相应地被更新为true。在使用ethers查询存储槽的值时,如果存储槽的值为false,查询结果会显示为0x0。如果存储槽的值为true,查询结果会显示为0x1。
因此,如果你在查询智能合约的存储槽时得到的结果是0x0,这意味着存储槽的值为false,即isComplete变量还没有被设置为true。如果你想要获取存储槽的实际值,可以将查询结果转换为布尔类型,如下所示:
1
2
3 const slot0 = await provider.getStorageAt(contractAddress, 0);
const isComplete = ethers.utils.bigNumberify(slot0).toNumber() !== 0;
console.log(`isComplete: ${isComplete}`);这段代码将查询结果转换为BigNumber类型,并将其转换为数字类型。如果数字不为0,isComplete变量的值为true,否则为false。
方法二:由于abi可读性太差,ethers创新的引入了人类可读ABI。开发者可以通过function signature和event signature来写ABI。
参考代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 import { Contract, ethers } from "ethers"; // ES6模块下引入ethers包
// var fs = require("fs")
import fs from "fs"; // ES6模块下引入fs包
const provider = new ethers.providers.JsonRpcProvider("HTTP://127.0.0.1:8545");
// 读取文件的abi
let con_ABI = JSON.parse(fs.readFileSync("D:\\VSCode\\VSCode_Data\\ethersjsWorkspace\\LearningSelf\\abi\\CallMeChallenge.abi").toString())
// 合约地址
let con_Address = "0xCfD32B1C6C1b2Fb425F3d6a86F8E2ebB363b5d96";
// 创建合约
let contract = new ethers.Contract(con_Address,con_ABI,provider);
let abi = [
"function callme() public",
"function getValue() external view returns(int)",
]
let contract1 = new ethers.Contract(con_Address,abi,provider);
async function demo1(){
// 调用合约的只读函数
let value = await contract1.getValue();
console.log("demo1's value => " + value);
}
demo1()运行结果:
4.3 更多的读取合约信息可到 官方文档 查看
5.发送ETH
5.1 Signer签名类
在ethers中,Signer签名者类是以太坊账户的抽象类,可用于对消息和交易进行签名,并将签名的交易发送到以太坊网络,并更改区块链状态。Signer类是抽象类,不能直接实例化,我们需要使用它的子类:Wallet钱包类。
5.2 Wallet钱包类
Wallet类继承了Signe类,并且开发者可以像包含私钥的外部拥有者账户(EOA)一样,用它对交易和消息进行签名。
5.2.1方法一:创建随机的wallet对象
我们可以使用 ethers.Wallet.createRandom()
函数创建带有随机私钥的Wallet对象。对该私钥由加密安全的熵源生成,如果当前环境没有安全的熵源,则会引发错误。
tips:在密码学中,熵(entropy)是指随机性的度量。在智能合约中,熵源(entropy source)是指用于生成随机数或随机种子的源头。
在智能合约中,为了确保随机数的安全性和不可预测性,需要使用熵源来生成随机数或随机种子。通常情况下,熵源可以是外部的数据源,例如区块哈希或者时间戳等,也可以是合约内部的数据源,例如合约的存储槽或者合约的执行结果等。
需要注意的是,熵源的质量和安全性对于密码学应用非常重要。如果熵源不够随机或者不够安全,可能会导致随机数的可预测性,从而破坏密码学应用的安全性。因此,在使用熵源生成随机数或随机种子时,需要选择合适的熵源,并对其进行充分的测试和验证,以确保其安全性和不可预测性。
1 | // 创建随机的wallet对象 |
5.2.2方法二:用私钥创建wallet对象
我们已知私钥的情况下,可以利用ethers.Wallet()
函数创建 Wallet对象。
从Ganache
中获取privateKey
1 | // 利用私钥和provider创建wallet对象 |
5.2.3方法三:从助记词创建wallet对象
我们已知助记词的情况下,可以利用 ethers.Wallet.fromMnemonic()
函数创建Wallet对象。
1 | // 从助记词创建wallet对象 |
5.2.4 其他方法:通过JSON文件创建wallet对象
通过ethers.Wallet.fromEncryptedJson
解密一个JSON
钱包文件创建钱包实例,JSON
文件即keystore
文件,通常来自Geth
, Parity
等钱包
5.3 发送ETH
我们可以利用Wallet实例来发送ETH。首先,我们需要构造一个交易请求,在里面声明接收地址to和发送的ETH数额value。交易请求TransactionRequest
类型可以包含发送方 from,nonce值 nounce,请求数据data等信息。
1 | // 创建交易请求,参数:to为接收地址,value为ETH数额 |
然后,我们就可以利用Wallet类的sendTransaction来发送交易,等待交易上链,并获得交易的数据。
1 | //发送交易,获得收据 |
5.4 代码实例
5.4.1创建Wallet实例
三种方法的示例代码:
1 | import { ethers } from "ethers"; |
运行结果:
5.4.2 获取钱包地址
代码:
1
2
3
4
5
6
7
8
9
10
11
12 /**
* 获取钱包地址
*/
const address1 = await wallet1.getAddress()
const address2 = await wallet2.getAddress()
const address3 = await wallet3.getAddress() // 获取地址
console.log(`1. 获取钱包地址`);
console.log(`钱包1地址: ${address1}`);
console.log(`钱包2地址: ${address2}`);
console.log(`钱包3地址: ${address3}`);
console.log(`钱包1和钱包3的地址是否相同: ${address1 === address3}`);运行结果:
5.4.3 获取助记词
利用钱包对象的mnemonic
成员获取助记词:
1 | console.log(`钱包1助记词: ${wallet1.mnemonic.phrase}`) |
结果:
5.4.4 利用钱包对象的 privateKey
成员获取私钥:
1 | console.log(`钱包2私钥: ${wallet2.privateKey}`) |
运行结果:
到ganache上查看:
结果是相同的。
5.4.5 获取钱包在链上的交互次数
利用 getTransactionCount()
函数获取钱包在链上的交互次数
1 | const txCount1 = await wallet1WithProvider.getTransactionCount() |
运行结果:
到ganache
上查看:
5.4.6 发送ETH
我们用wallet2
给wallet1
发送0.001 ETH
,并打印交易前后的钱包余额。由于wallet1
是新建的随机私钥钱包,因此交易前余额为0
,而交易后余额为0.001 ETH
。
1 | const wallet1 = ethers.Wallet.createRandom(); |
运行结果:
ethers.js_day03
6. 合约交互
6.1 创建可写Contract变量
声明可写的Contract变量的规则:
1 const contract = new ethers.Contract(address, abi, signer)解读:其中 address 是合约地址,abi是合约的abi接口,singer是wallet对象。声明可写contract需要提供signer,声明可读contract只需要provider
将可读合约转换为可写合约:
1 const contract2 = contract.connect(signer)
6.2 合约交互
读合约信息不需要gas。写入合约需要接入合约信息,构建交易,并支付gas。该交易将由整个网络的每个节点以及矿工验证,并改变区块链状态。
合约交互的方法如下:
1
2
3
4 // 发送交易
const tx = await contract.METHOD_NAME(args [, overrides])
// 等待链上确认交易
await tx.wait()解读代码:其中METHOD_NAME为调用的函数名,args为函数参数,
[, overrides]
是可以选择传入的数据,包括:
- gasPrice:gas价格
- gasLimit:gas上限
- value:调用时传入的ether(单位是wei)
- nonce:nonce(随机数?)
注:此方法不能获取合约运行的返回值,如若需要获取合约运行你的返回值,要使用Solidity事件记录,然后利用交易收据去查询。
6.3 例子:与本地ganache
合约交互
- 创建provider,wallet变量
1
2
3
4
5
6
7
8
9 >import { ethers } from "ethers";
>// 获取本地的provider
>const provider = new ethers.providers.JsonRpcProvider("HTTP://127.0.0.1:8545");
>// 根据私钥创建 wallet对象
>const privateKey = "0x9b139483dc6f6c8782fda2301e9b65a75b3d084d79e79e04c64683bebfa1741d";
>const wallet = new ethers.Wallet(privateKey, provider);
- 创建可写合约变量,在我自己
domo1.sol
中调用4个函数
- getSlot0():查找插槽0位置的值;
- getSlot1():查找插槽1位置的值;
- getSlot2():查找插槽2位置的值;
- depoist():转账;
- balanceOf():查询地址余额。
tips:这里我使用的是方法是传入remix上获取的abi
1
2
3
4
5
6
7
8
9
10
11
12
13 /**
* 创建可写的合约变量
*/
// 读取文件的abi
const ABI = JSON.parse(fs.readFileSync("LearningSelf\\day03\\demo1.abi").toString());
// 在remix上部署的合约地址
const contractAddress = "0x5B1557A5AD3cfAC88F3624743BF67baB28723899";
// 声明可写合约
const constract = new ethers.Contract(contractAddress,ABI,wallet);3.调用getSlot0、1、2()三个函数,查看其值
1
2
3
4
5
6
7
8
9
10 // let slot0 = await provider.getStorageAt(contractAddress,0);
let slot0 = await constract.getSlot0();
// console.log(slot0);
console.log(`slot0's value is => ${slot0}`);
let slot1 = await constract.getSlot1();
// console.log(slot1);
console.log(`slot1's value is => ${slot1}`);
let slot2 = await constract.getSlot2();
// console.log(slot2);
console.log(`slot2's value is => ${slot2}`)运行结果:
4.查询账户的余额
1
2
3
4
5
6 // 需要查询余额的账户
let test_address = "0x753f06cD09C531Ef83b84C5147E812519BB65efd";
let balance = await constract.balanceOf(test_address);
console.log(`ETH balance is => ${ethers.utils.formatEther(balance)}`)运行结果:
5.转账操作,打印交易详情和余额。
1
2
3
4
5
6 //转账
let balance1 = await provider.getBalance(test_address);
console.log(`转账前的余额为:=> ${ethers.utils.formatEther(balance1)}`)
constract.depoist(1, test_address);
console.log(`转账后的余额为:=> ${ethers.utils.formatEther(balance1)}`)运行结果:
由于vscode显示数位的问题,使用在remix上查询显示的位数更多
注:对于非pure/view函数的调用,会返回交易的信息。如果想知道函数执行过程中合约变量的变化,可以在合约中使用emit输出事件,并在返回的transaction信息中读取事件信息来获取对应的值
完整的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 import { ethers } from "ethers";
import fs from "fs";
// 获取本地的provider
const provider = new ethers.providers.JsonRpcProvider("HTTP://127.0.0.1:8545");
// 根据私钥创建 wallet对象
const privateKey = "0x9b139483dc6f6c8782fda2301e9b65a75b3d084d79e79e04c64683bebfa1741d";
const wallet = new ethers.Wallet(privateKey, provider);
/**
* 创建可写的合约变量
*/
// 读取文件的abi
const ABI = JSON.parse(fs.readFileSync("LearningSelf\\day03\\demo1.abi").toString());
// 在remix上部署的合约地址
const contractAddress = "0xf8fe22cA93bD2416a106dFB62168C0D7eBF557FE";
// 声明可写合约
const constract = new ethers.Contract(contractAddress,ABI,wallet);
/**
* 调用合约的函数
*/
// let slot0 = await provider.getStorageAt(contractAddress,0);
let slot0 = await constract.getSlot0();
// console.log(slot0);
console.log(`slot0's value is => ${slot0}`);
let slot1 = await constract.getSlot1();
// console.log(slot1);
console.log(`slot1's value is => ${slot1}`);
let slot2 = await constract.getSlot2();
// console.log(slot2);
console.log(`slot2's value is => ${slot2}`);
// 需要查询余额的账户
let test_address = "0x753f06cD09C531Ef83b84C5147E812519BB65efd";
let balance = await constract.balanceOf(test_address);
console.log(`ETH balance is => ${ethers.utils.formatEther(balance)}`)
//转账
let balance1 = await provider.getBalance(test_address);
console.log(`转账前的余额为:=> ${ethers.utils.formatEther(balance1)}`)
constract.depoist(1, test_address);
console.log(`转账后的余额为:=> ${ethers.utils.formatEther(balance1)}`)
7.部署合约
7.1 合约工厂
ethers.js 创造了合约工厂 ContractFactory类型,方便开发者部署合约。你可以利用合约abi,编译得到的字节码bytecode和签名者变量 singer 来创建合约工厂实例,为部署合约做准备。
1 const contractFactory = new ethers.ContractFactory(abi, bytecode, signer);注:如果合约的构造函数有参数,那么在abi中必须包含构造函数。
在创建好合约工厂之后,可以调用它的deploy函数,并传入合约构造函数的参数args来部署并得到合约实例:
1 const contract = await contractFactory.deploy(args)可以使用两种命令,等待合约部署在链上确认,然后再进行交互
1
2 await contractERC20.deployed()
//或者 await contract.deployTransaction.wait()
7.2 例子:部署我自己编写的demo1.sol合约
demo2.sol 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 // SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract demo2 {
uint slot0;
address slot1;
string public slot2;
// 初始化一些变量
constructor(uint _slot0, address _slot1, string memory _slot2) payable{
slot0 = _slot0;
slot1 = _slot1;
slot2 = _slot2;
}
function setSlot0(uint num) public {
slot0 = num;
}
function getSlot0() external view returns(uint){
return slot0;
}
function setSlot1(address _address) public {
slot1 = _address;
}
function getSlot1() external view returns(address){
return slot1;
}
function setSlot2(string memory _str) public {
slot2 = _str;
}
function getSlot2() external view returns(string memory){
return slot2;
}
function depoist(uint _amount, address payable _to) external {
uint balance = address(this).balance;
require(balance > _amount, "You balance is less than your address(this).balance");
_to.transfer(_amount);
}
function balanceOf(address _address) external view returns(uint) {
return address(_address).balance;
}
}
创建provider和wallet变量
1
2
3
4
5
6
7
8
9 import { ethers } from "ethers";
import fs from "fs";
// 获取本地的provider
const provider = new ethers.providers.JsonRpcProvider("HTTP://127.0.0.1:8545");
// 根据私钥创建 wallet对象
const privateKey = "0x9b139483dc6f6c8782fda2301e9b65a75b3d084d79e79e04c64683bebfa1741d";
const wallet = new ethers.Wallet(privateKey, provider);准备demo1合约的字节码和ABI(这里我使用remix生成的ABI)。因为demo2的构造函数含有参数,因此我们需要把它包含再ABI中。合约的字节码可以从remix的编译面板中点击bytecode按钮拷贝下来(为了代码的可读性,将拷贝的字节码新建一个
.code
文件存放,获取的时候 使用fs
来读取),其中”object”字段对应的数据就是字节码。如果部署在链上的合约,你可以在etherscan的Contract页面的Contract Creation Code中找到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 // demo2人类可读abi
/**
const demo2ABI = [
"constructor(uint _slot0, address _slot1, string memory _slot2) payable",
.......
]
*/
// 读取文件的abi
const ABI = JSON.parse(fs.readFileSync("LearningSelf\\day03\\demo2.abi").toString());
// 读取文件的bytecode
const byteCode = fs.readFileSync("LearningSelf\\day03\\demo2.code").toString();
3. 创建合约工厂ContractFactory实例
```js
// 创建合约工厂 ContractFactory 实例
const factoryDemo2 = new ethers.ContractFactory(ABI, byteCode, wallet);
4. 调用工厂合约的deploy() 函数并填入构造函数的参数,部署demo2合约并获取合约实例。有写常用方法:
- `contract.address`获取合约地址,
- `contract.deployTransaction`获取部署详情,
- `contractERC20.deployed()`等待合约部署在链上确认。
```js
// 利用 factoryDemo2 部署demo2 合约
const gasPrice = await provider.getGasPrice();
const transaction = { value: ethers.utils.parseEther('1'), gasPrice };
const constract = await factoryDemo2.deploy(9,"0xfDc84b042c01F01C7cC6eF08452BA156844c3A5C","biyou",transaction);
console.log(`部署得到的合约地址:${constract.address}`);
console.log("部署合约的交易详情");
console.log(constract.deployTransaction);调用合约函数
1
2
3
4
5
6
7
8
9
10
11
12
13 /**
* 调用合约的函数
*/
let slot0 = await constract.getSlot0();
console.log(`slot0's value is => ${slot0}`);
let slot1 = await constract.getSlot1();
console.log(`slot1's value is => ${slot1}`);
let slot2 = await constract.getSlot2();
console.log(`slot2's value is => ${slot2}`);
let balance = await constract.balanceOf("0x643bB521371F66E271937C6365F5812118e58a6D");
console.log(`ETH balance is => ${balance}`);
执行结果:
完整的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41 import { ethers } from "ethers";
import fs from "fs";
// 获取本地的provider
const provider = new ethers.providers.JsonRpcProvider("HTTP://127.0.0.1:8545");
// 根据私钥创建 wallet对象
const privateKey = "0x9b139483dc6f6c8782fda2301e9b65a75b3d084d79e79e04c64683bebfa1741d";
const wallet = new ethers.Wallet(privateKey, provider);
// 读取文件的abi
const ABI = JSON.parse(fs.readFileSync("LearningSelf\\day03\\demo2.abi").toString());
// 读取文件的bytecode
const byteCode = fs.readFileSync("LearningSelf\\day03\\demo2.code").toString();
// 创建合约工厂 ContractFactory 实例
const factoryDemo2 = new ethers.ContractFactory(ABI, byteCode, wallet);
// 利用 factoryDemo2 部署demo2 合约
const gasPrice = await provider.getGasPrice();
const transaction = { value: ethers.utils.parseEther('1'), gasPrice };
const constract = await factoryDemo2.deploy(9,"0xfDc84b042c01F01C7cC6eF08452BA156844c3A5C","biyou",transaction);
console.log(`部署得到的合约地址:${constract.address}`);
console.log("部署合约的交易详情");
console.log(constract.deployTransaction);
/**
* 调用合约的函数
*/
let slot0 = await constract.getSlot0();
console.log(`slot0's value is => ${slot0}`);
let slot1 = await constract.getSlot1();
console.log(`slot1's value is => ${slot1}`);
let slot2 = await constract.getSlot2();
console.log(`slot2's value is => ${slot2}`);
let balance = await constract.balanceOf("0x643bB521371F66E271937C6365F5812118e58a6D");
console.log(`ETH balance is => ${balance}`);
8.检索事件 — 很有用
8.1 参考博客:请移步 这里
8.2 检索事件
可以利用Ethers中合约类型的queryFilter()函数读取合约释放的事件
1 const transferEvents = await contract.queryFilter('事件名',起始区块,结束区块)queryFilter() 包含3个参数,分别是事件名(必填),起始区块(选填),和结束区块(选填)。检索结果会以数组的形式返回。
注:要检索的事件必须包含在合约的abi中。
9. 监听合约事件
9.1 监听合约事件
contract.on
在ethersjs中,合约对象有一个contract.on 的监听方法,让我们持续监听合约的事件:
1 contract.on("eventName", function)contract.on 有两个参数,一个是要监听的事件名称”eventName”,需要包含合约abi中;另一个是我们在事件发生时调用的函数
contract.once
合约对象有一个contract.once的监听方法,让我们只监听一次合约释放事件,它的参数与contract.on一样:
1 contract.once("eventName",function)
9.2 监听合约示例
在这里 —-> here
…………………
10. 事件过滤
现阶段的我还不适合考虑这些,日后再学 博客链接
…………………
11. BigNumber 和 单位转换
11.1 BigNumber
在以太坊中,许多计算都对JavaScript整数的安全值(js中最大安全数为9007199254740991)。因此,ethers.js使用BigNumber 类安全地对任何数量级的数字进行数学运算。在ethers.js中,大多数需要返回值的操作将返回BigNumber,而接受值的参数也会接受他们。
11.2 创建BigNumber实例
可以利用
ethers.BigNumber.from()
函数将string,number,BigNumber等类型转换为BigNumber。注:超过js最大安全整数的数值将不能转换
1
2
3
4
5
6
7 const oneGwei = ethers.BigNumber.from("1000000000"); // 从十进制字符串生成
console.log(oneGwei)
console.log(ethers.BigNumber.from("0x3b9aca00")) // 从hex字符串生成
console.log(ethers.BigNumber.from(1000000000)) // 从数字生成
// 不能从js最大的安全整数之外的数字生成BigNumber,下面代码会报错
// ethers.BigNumber.from(Number.MAX_SAFE_INTEGER);
console.log("js中最大安全整数:", Number.MAX_SAFE_INTEGER)运行结果:
11.3 BigNumber运算
BigNumber
支持很多运算,例如加减乘除、取模mod
,幂运算pow
,绝对值abs
等运算:
1
2
3
4
5
6
7 // 运算
console.log("加法:", oneGwei.add(1).toString())
console.log("减法:", oneGwei.sub(1).toString())
console.log("乘法:", oneGwei.mul(2).toString())
console.log("除法:", oneGwei.div(2).toString())
// 比较
console.log("是否相等:", oneGwei.eq("1000000000"))运行结果:
11. 4 单位转换
以太坊中,1 ethers 等于 10^18wei。下面是一些常用的单位:
在应用中,我们经常将数值在用户可读的字符串(以
ether
为单位)和机器可读的数值(以wei
为单位)之间转换。例如,钱包可以为用户界面指定余额(以ether
为单位)和gas
价格(以gwei
为单位),但是在发送交易时,两者都必须转换成以wei
为单位的数值。ethers.js
提供了一些功能函数,方便这类转换。
formatUnits(变量, 单位)
:格式化,小单位转大单位,比如wei
->ether
,在显示余额时很有用。参数中,单位填位数(数字)或指定的单位(字符串)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 //代码参考:https://docs.ethers.io/v5/api/utils/display-logic/#utils-parseUnits
console.group('\n2. 格式化:小单位转大单位,formatUnits');
console.log(ethers.utils.formatUnits(oneGwei, 0));
// '1000000000'
console.log(ethers.utils.formatUnits(oneGwei, "gwei"));
// '1.0'
console.log(ethers.utils.formatUnits(oneGwei, 9));
// '1.0'
console.log(ethers.utils.formatUnits(oneGwei, "ether"));
// `0.000000001`
console.log(ethers.utils.formatUnits(1000000000, "gwei"));
// '1.0'
console.log(ethers.utils.formatEther(oneGwei));
// `0.000000001` 等同于formatUnits(value, "ether")
console.groupEnd();运行结果:
parseUnits
:解析,大单位转小单位,比如ether
->wei
,在将用户输入的值转为wei
为单位的数值很有用。参数中,单位填位数(数字)或指定的单位(字符串)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 // 3. 解析:大单位转小单位
// 例如将ether转换为wei:parseUnits(变量, 单位),parseUnits默认单位是 ether
// 代码参考:https://docs.ethers.io/v5/api/utils/display-logic/#utils-parseUnits
console.group('\n3. 解析:大单位转小单位,parseUnits');
console.log(ethers.utils.parseUnits("1.0").toString());
// { BigNumber: "1000000000000000000" }
console.log(ethers.utils.parseUnits("1.0", "ether").toString());
// { BigNumber: "1000000000000000000" }
console.log(ethers.utils.parseUnits("1.0", 18).toString());
// { BigNumber: "1000000000000000000" }
console.log(ethers.utils.parseUnits("1.0", "gwei").toString());
// { BigNumber: "1000000000" }
console.log(ethers.utils.parseUnits("1.0", 9).toString());
// { BigNumber: "1000000000" }
console.log(ethers.utils.parseEther("1.0").toString());
// { BigNumber: "1000000000000000000" } 等同于parseUnits(value, "ether")
console.groupEnd();运行结果:
12. CallStatic
callStatic方法是属于ethers.Contract类的编写方法分析,同类的还有populateTransaction和estimateGas方法
12.1 可能失败的交易
在以太坊上交易需要支付昂贵的gas,并且有失败的风险,发送失败的交易并不会把gas返还给你。因此,在发送交易前知道哪些交易可能会失败非常重要。以小狐狸钱包(metamask)为例。
如果交易将失败,小狐狸会告诉你 this transaction may fail,翻译过来就是”这笔交易可能会失败”。当用户看到提示的时候,就可以取消交易了。
实现的原理:以太坊节点有一个eth_call方法,让用户可以模拟一笔交易,并返回可能的交易结果,但不是真正的在区块链上执行(交易不上链)。
12.2 callStatic
在
ethers.js
中你可以利用contract
对象的callStatic()
来调用以太坊节点的eth_call
。如果调用成功,则返回ture
;如果失败,则报错并返回失败原因。方法:
1
2 const tx = await contract.callStatic.函数名( 参数, {override})
console.log(`交易会成功吗?:`, tx)
- 函数名:为模拟调用的函数名。
- 参数:调用函数的参数。
- {override}:选填,可包含一下参数:
from
:执行时的msg.sender
,也就是你可以模拟任何一个人的调用,比如V神。value
:执行时的msg.value
。blockTag
:执行时的区块高度。gasPrice
gasLimit
nonce
12.3 用callStatic 模拟DAI转账
演示过程—-》博客
13. 编码 calldata
13.1 接口类Interface
ethers.js 的接口类抽象了与以太坊网络的合约交互所需的ABI编码和解码。ABI与API类似,是一格式,用于对合约可以处理的各种类型的数据进行编码,以便它们可以交互。
可以利用abi生成或者直接从合约中获取interface变量:
1
2
3
4 // 利用abi生成
const interface = new ethers.utils.Interface(abi)
// 直接从contract中获取
const interface2 = contract.interface接口类封装了一些编码解码的方法。与一些特殊的合约交互时(比如代理 合约),你需要编码参数、解码返回值:
注:相关函数必须包含在abi中。
getSighash():获取函数选择器(function selector),参数为函数名或函数签名。
至于函数签名如何获取,在remix上使用keccak256()或的和ethersjs使用keccak256()获取的结果完全不一样,日后空闲了再仔细深入学习
1
2 interface.getSighash("balanceOf");
// '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
encodeDeploy()
:编码构造器的参数,然后可以附在合约字节码的后面。
1 interface.encodeDeploy("Wrapped ETH", "WETH");
encodeFunctionData()
:编码函数的calldata
。
1 interface.encodeFunctionData("balanceOf", ["0xc778417e063141139fce010982780140aa0cd5ab"]);
decodeFunctionResult()
:解码函数的返回值。
1 interface.decodeFunctionResult("balanceOf", resultData)