区块链学习第四期
学习ethers.js【上篇】
[toc] ---web3.js
在说 etherjs 之前呢,先说一下web3js
- Web3.js 是一个库集合,允许您使用 HTTP、IPC 或 WebSocket 与本地或远程以太坊节点进行交互。
- web3js 提供了用于和 geth 通信的 javascipt API,也就是用 web3js 连接 geth,在通过 geth 连接以太坊。
- 内部使用了 json-rpc 与 geth 通信
- json-rpc是一个无状态且轻量级的远程过程调用(rpc)协议。允许使用 socket、http 等协议进行通讯。使用 json 作为数据格式(rfc4627)==》被封装成了 jsapi
- web3js 可以与所有支持 rpc 的节点进行通信。不仅支持以太坊相关的 api,还支持以太坊生态中其它系统的 api
ethers.js
etherjs是一个完整而紧凑的开源库,用于与以太坊区块链及其生态系统进行交互(个人认为就是提供一个方便访问区块链的接口,是压缩版的 web3js)。如果要写 Dapp 前端,则需要用到它。
uer interface(frontend) <–> ethereum api <–> smart contract
对 alchemy 的再理解
某一次实验需要注册 alchemy 获取一个 api key,但一直不懂为什么要。下面我用个对比法解释:
我们一开始访问以太坊时,用的扩展类工具连接的以太坊:Metamask
- 提供连接到以太坊的网络(a Provider)
- 持有你的私钥并且可以进行签名(a Signer)
1 | const provider = new ethers.providers.Web3Provider(window.ethereum); |
但是我们不想下载扩展呢?我还能在不通过网页的形式下查看链上信息吗?
RPC 全称 Remote Procedure Call,即远程过程调用)JSON-RPC API 接口,是另一个连接到以太坊的方法,可以在所有的主要的以太坊节点实现,他主要提供了
- 提供连接到以太坊的网络(a Provider)
- 持有你的私钥并且可以进行签名(a Signer)
查询区块链一旦有了一个 Provider,就有了一个与区块链的连接,可以用它去查询当前状态,获取历史日志,查询部署的代码等
1 | //await 作用使异步方法变同步 |
web3js 和 ethersjs 对比
web3js 认为用户在本地部署以太坊节点,私钥和网络连接状态这个节点管理(事迹并不是这样);ethersjs 中,provider 提供器管理网络连接状态,wallet 钱包类管理密钥,安全且灵活,原生支持 ens
Ethers.js 提供的状态和密钥管理。Web3 的设计场景是 DApp 应该连接到一个本地节点,由这个节点负责保存密钥、签名交易并与以太坊区块链交互。现实并不是这样的,绝大多数用户不会在本地运行一个 geth 节点。Metamask 在浏览器 应用中有效地模拟了这种节点环境,因此绝大多数 web3 应用需要使用 Metamask 来保存密钥、签名交易并完成与以太坊的交互。
Ethers.js 采取了不同的设计思路,它提供给开发者更多的灵活性。Ethers.js 将“节点”拆分为两个不同的角色:
钱包:负责密钥保存和交易签名
提供器:负责以太坊网络的匿名连接、状态检查和交易发送
本地使用 ethersjs,如何配置环境
- 安装环境 nodejs
- 安装 ethersjs 包—npm i –save ethers
- alchemy 申请一个 rpc 复制里面的 apikey
- 查看官方文档
- 当然单个网页也可以直接引入
1
2
3
4
5
6<!-- 会导出一个全局的变量: ethers -->
<script
src="https://cdn.ethers.io/scripts/ethers-v4.min.js"
charset="utf-8"
type="text/javascript"
></script>
本地如何使用使用呢?每一步到底做什么?
本次测试的目录结构
1 | +ethers-template |
- 初始化一个文件夹
1
npm init -y
- 创建一个 config.json 文件保存你的项目配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18{
//账户私钥,将使用这个私钥对应的账户在指定网络上部署智能合约
"private_key": "24C4FE6063E62710EAD956611B71825B778B041B18ED53118CE5DA5F02E494BA",
<!--要接入的以太坊网络,ethers.js支持以下网络:
homestead:主网
rinkeby
ropsten
kovan
goerli -->
"network": "kovan",
//声明要交互的已部署合约,可选
"ERC20": "0x0DEd9F7D82a24099F09AF7831CaB61B31Df10487",
//ERC20合约的参数
"name": "fly",
"symbol": "Zhou",
"total_supply": "1000000000000000000000000",
"decimals": 18
} - 为了编译合约,还需要安装solc和fs-extra
1
npm install fs-extra@8.1.0 solc@0.5.11 --save
- 创建ERC20合约代码
- 编写合约编译脚本,下面代码将读入并编译contracts目录中的所有合约文件,然后将编译得到的abi和字节码保存为json文件运行node compile.js就可以对合约进行编译
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81const path = require('path');
const fs = require('fs-extra');
const solc = require('solc');
//这个读取的就是自己的配置文件
const config = require('./config.json');
const sourceFolderPath = path.resolve(__dirname, 'contracts');
const buildFolderPath = path.resolve(__dirname, 'build');
const getContractSource = contractFileName => {
const contractPath = path.resolve(__dirname, 'contracts', contractFileName);
const source = fs.readFileSync(contractPath, 'utf8');
return source;
};
let sources = {};
fs.readdirSync(sourceFolderPath).forEach(contractFileName => {
sources = {
...sources,
[contractFileName]: {
content: getContractSource(contractFileName)
}
}
});
const input = {
language: 'Solidity',
sources,
settings: {
outputSelection: {
'*': {
'*': [ '*' ]
}
}
}
}
console.log('nCompiling contracts...');
const output = JSON.parse(solc.compile(JSON.stringify(input)));
console.log('Done');
let shouldBuild = true;
if (output.errors) {
console.error(output.errors);
// throw 'nError in compilation please check the contractn';
for(error of output.errors) {
if(error.severity === 'error') {
shouldBuild = false;
throw 'Error found';
break;
}
}
}
if(shouldBuild) {
console.log('nBuilding please wait...');
fs.removeSync(buildFolderPath);
fs.ensureDirSync(buildFolderPath);
for (let contractFile in output.contracts) {
for(let key in output.contracts[contractFile]) {
fs.outputJsonSync(
path.resolve(buildFolderPath, `${key}.json`),
{
abi: output.contracts[contractFile][key]["abi"],
bytecode: output.contracts[contractFile][key]["evm"]["bytecode"]["object"]
},
{
spaces:2,
EOL: "n"
}
);
}
}
console.log('Build finished successfully!n');
} else {
console.log('nBuild failedn');
} - 创建文件deploy.js
- 下面代码中的默认网络是configjs中的测试网
- 在这个测试网中,需要一些以太币来支付部署交易的手续费
- 将使用config.json中的private_key来部署合约
- 运行deploy.js脚本时,需要在命令行传入要部署的合约名称ERC20
1
node deploy.js ERC20
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
36const startTimestamp = Date.now();
const ethers = require('ethers');
const config = require('./config.json');
const fs = require('fs-extra');
const provider = ethers.getDefaultProvider(config["network"]);
const wallet = new ethers.Wallet(config["private_key"], provider);
console.log(`Loaded wallet ${wallet.address}`);
let compiled = require(`./build/${process.argv[2]}.json`);
(async() => {
console.log(`nDeploying ${process.argv[2]} in ${config["network"]}...`);
let contract = new ethers.ContractFactory(
compiled.abi,
compiled.bytecode,
wallet
);
let instance = await contract.deploy(config["decimals"], config["symbol"], config["name"], config["total_supply"]);
console.log(`deployed at ${instance.address}`)
config[`${process.argv[2]}`] = instance.address
console.log("Waiting for the contract to get mined...")
await instance.deployed()
console.log("Contract deployed")
fs.outputJsonSync(
'config.json',
config,
{
spaces:2,
EOL: "n"
}
);
})();
ethers.js 接口文档导读
其实整个文档可以看出四个大类 我把它们按照学习的先后顺序分为:
- provider(提供器:我习惯理解成提供给我信息的网络)
- wallet(钱包)
- contract(合约)
- utils(工具包)