区块链学习第四期

myBlog

学习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

  1. 提供连接到以太坊的网络(a Provider)
  2. 持有你的私钥并且可以进行签名(a Signer)
1
2
3
4
const provider = new ethers.providers.Web3Provider(window.ethereum);
//Metamask插件支持签名交易和信息,来发送交易信息改变区块链的状态
//因此需要一个账户联名
const signer = provider.getSigner();

但是我们不想下载扩展呢?我还能在不通过网页的形式下查看链上信息吗?

RPC 全称 Remote Procedure Call,即远程过程调用)JSON-RPC API 接口,是另一个连接到以太坊的方法,可以在所有的主要的以太坊节点实现,他主要提供了

  1. 提供连接到以太坊的网络(a Provider)
  2. 持有你的私钥并且可以进行签名(a Signer)
    查询区块链一旦有了一个 Provider,就有了一个与区块链的连接,可以用它去查询当前状态,获取历史日志,查询部署的代码等
1
2
3
4
5
6
7
8
9
10
11
12
//await 作用使异步方法变同步
await provider.getBlockNumber();
ethers.utils.formatEther(balance);
ethers.utils.parseEther("1.0");

//写入区块
// Send 1 ether to an ens name.发送1以太到以太坊域名
const tx = signer.sendTransaction({
//签名,发送交易
to: "ricmoo.firefly.eth", //发送至以太坊域名
value: ethers.utils.parseEther("1.0"), //格式化
});

web3js 和 ethersjs 对比

  • web3js 认为用户在本地部署以太坊节点,私钥和网络连接状态这个节点管理(事迹并不是这样);ethersjs 中,provider 提供器管理网络连接状态,wallet 钱包类管理密钥,安全且灵活,原生支持 ens

  • Ethers.js 提供的状态和密钥管理。Web3 的设计场景是 DApp 应该连接到一个本地节点,由这个节点负责保存密钥、签名交易并与以太坊区块链交互。现实并不是这样的,绝大多数用户不会在本地运行一个 geth 节点。Metamask 在浏览器 应用中有效地模拟了这种节点环境,因此绝大多数 web3 应用需要使用 Metamask 来保存密钥、签名交易并完成与以太坊的交互。

  • Ethers.js 采取了不同的设计思路,它提供给开发者更多的灵活性。Ethers.js 将“节点”拆分为两个不同的角色:

    钱包:负责密钥保存和交易签名
    提供器:负责以太坊网络的匿名连接、状态检查和交易发送

本地使用 ethersjs,如何配置环境

  1. 安装环境 nodejs
  2. 安装 ethersjs 包—npm i –save ethers
  3. alchemy 申请一个 rpc 复制里面的 apikey
  4. 查看官方文档
  5. 当然单个网页也可以直接引入
    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
2
3
4
5
6
7
8
9
10
+ethers-template
+compile.js
+contracts
-erc20.sol
+build
-ERC.json
-Context.json
-IERC20.sjon
-SafeMath.json
-package.json
  1. 初始化一个文件夹
    1
    npm init -y
  2. 创建一个 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
    }
  3. 为了编译合约,还需要安装solc和fs-extra
    1
    npm install fs-extra@8.1.0 solc@0.5.11 --save
  4. 创建ERC20合约代码
  5. 编写合约编译脚本,下面代码将读入并编译contracts目录中的所有合约文件,然后将编译得到的abi和字节码保存为json文件
    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
    81
    const 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');
    }
    运行node compile.js就可以对合约进行编译
  6. 创建文件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
      36
      const 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(工具包)