EtherJS基础

本文会采用几个案例,概述一下 Ethers.js 的常见用法

下面所有案例使用的 Ethers.js 版本均为6.12.0

官方参考文档:https://docs.ethers.org/v6

概述

Ethers.js 主要由下面几个部件组成

Provider

Provider是与区块链的只读连接,允许查询区块链状态,例如帐户、区块或交易详细信息,查询事件日志或使用调用评估只读代码。

Signer

Signer包装与帐户交互的所有操作。每个帐户会有一个私钥,可用于对各种操作进行签名。

私钥可能位于内存中(使用Wallet)或通过某些 IPC 层进行保护,例如 MetaMask,它代理从网站到浏览器插件的交互,从而使私钥远离网站,并且仅在请求用户许可并收到授权后才允许交互。

Transaction

要对区块链进行任何状态更改,需要进行交易,这需要支付费用,其中费用涵盖执行交易(例如读取磁盘和执行数学)和存储更新信息的相关成本。

如果交易恢复,仍然需要支付费用,因为验证者仍然必须花费资源来尝试运行交易以确定它已恢复,并且记录其失败的详细信息。

交易包括从一个用户向另一个用户发送以太币、部署合约或针对合约执行状态更改操作。

Contract

Contract是一个已部署到区块链的程序,其中包含一些代码并分配了可以读取和写入的存储空间。

当它连接到一个Provider或者当连接到一个时可以调用状态改变操作Signer

Receipt

一旦交易被提交到区块链,它就会被放置在内存池(mempool)中,直到验证者决定将其包含在内。

交易只有在被纳入区块链后才会发生更改,此时会收到收据,其中包含有关交易的详细信息,例如它包含在哪个区块中、实际支付的费用、使用的 Gas 以及所有事件它发出了什么以及它是否成功或恢复。

余额查询

本节我们来实现一个查询以太坊余额的脚本,为了实现这个功能,我们需要连接到以太坊节点。当然,我们没必要为了查询余额而真的去部署一个以太坊节点,市面上有许多工具可以用。这里我们使用 INFURA 来获取以太坊节点:https://app.infura.io/

注册好之后可以获取免费的APIKey,找到自己的 Endpoints,就可以直接访问以太坊了

image-20240425161402481

获取到节点之后,就可以通过 Provider 来查询任意地址的以太坊余额

const { ethers } = require("ethers");

const INFURA_API_KEY = "...";
const ADDRESS = "...";

const provider = new ethers.JsonRpcProvider(
  `https://mainnet.infura.io/v3/${INFURA_API_KEY}`
);

const getBalance = async () => {
  const balance = await provider.getBalance(ADDRESS);
  console.log(
    `ETH Balance of ${ADDRESS} --> ${ethers.formatEther(balance)} ETH\n`
  );
};

getBalance();

读取合约

本节我们以 DAI(一个稳定币) 为例,先在 etherscan 上找到 DAI 合约,可以看到它可以列出了 DAI 的所有读取合约信息的函数,并能在上面直接交互

地址:https://etherscan.io/token/0x6b175474e89094c44da98b954eedeac495271d0f#readContract

image-20240425165246420

我们要做的就是使用 Ethers.js 来实现同样的效果

DAI 是一个 ERC20 合约,意味着它实现了实现了一系列函数,比如常见的 totalSupply、balanceOf、name等等

Ethers.js 库可以接收一个 ABI 数组,来得知如何与合约交互

创建完 contract 实例之后,就可以通过它来调用定义在 ERC20_ABI 中的函数了

const { ethers } = require("ethers");

const INFURA_API_KEY = "...";

const DAI_TOKEN_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const provider = new ethers.JsonRpcProvider(
    `https://mainnet.infura.io/v3/${INFURA_API_KEY}`
  );

const ERC20_ABI = [
  "function name() view returns (string)",
  "function symbol() view returns (string)",
  "function totalSupply() view returns (uint256)",
  "function balanceOf(address) view returns (uint256)",
];

const contract = new ethers.Contract(DAI_TOKEN_ADDRESS, ERC20_ABI, provider)

const main = async () => {
  const name = await contract.name();
  const symbol = await contract.symbol();
  const totalSupply = await contract.totalSupply();

  console.log(`Token Name: ${name}`);
  console.log(`Token Symbol: ${symbol}`);
  console.log(`Total Supply: ${totalSupply}`);
  
  const balance = await contract.balanceOf("0xA478c2975Ab1Ea89e8196811F51A7B7Ade33eB11")
  console.log(`Balance: ${ethers.formatUnits(balance)}`);
};

main();

运行结果:

Token Name: Dai Stablecoin
Token Symbol: DAI
Total Supply: 3213933035046622754824652548
Balance: 7288394.450956033361944629

发送交易

本节我们来实现通过 Ethers.js 将一个钱包中的代币发送给另一个钱包

这里使用的是 sepolia 测试网

主要思路是先定义好发送者与接受者的地址,然后获取发送者的私钥,通过这个私钥创建一个 wallet 对象,然后调用 sendTransaction 方法来发送代币

实现逻辑也很简单,具体代码如下

const { ethers } = require("ethers");

const INFURA_API_KEY = "...";

const provider = new ethers.JsonRpcProvider(
  `https://sepolia.infura.io/v3/${INFURA_API_KEY}`
);

const sender = "0x3Ee7EbFb5823c8C8af05BC371873D371085447D1";
const receiver = "0x8cC4f3fBa4b89da6e700D5602e7622CDc0444B62";
const senderPrivateKey = "...";
const wallet = new ethers.Wallet(senderPrivateKey, provider);

const main = async () => {
  const senderBalanceBefore = await provider.getBalance(sender);
  const receiverBalanceBefore = await provider.getBalance(receiver);
  console.log(
    `Sender balance before --> ${ethers.formatEther(senderBalanceBefore)} ETH`
  );
  console.log(
    `Receiver balance before --> ${ethers.formatEther(receiverBalanceBefore)} ETH`
  );

  // Send 0.001 ETH to receiver
  const tx = await wallet.sendTransaction({
    to: receiver,
    value: ethers.parseEther("0.001"),
  });

  // Wait for the transaction to be mined
  await tx.wait();
  console.log(tx);

  const senderBalanceAfter = await provider.getBalance(sender);
  const receiverBalanceAfter = await provider.getBalance(receiver);
  console.log(
    `Sender balance After --> ${ethers.formatEther(senderBalanceAfter)} ETH`
  );
  console.log(
    `Receiver balance After --> ${ethers.formatEther(receiverBalanceAfter)} ETH`
  );
};

main();

运行结果

Sender balance before --> 0.029886093476039388 ETH
Receiver balance before --> 0.1 ETH
TransactionResponse {
  provider: JsonRpcProvider {},
  blockNumber: null,
  blockHash: null,
  index: undefined,
  hash: '0x15b13a89590c9e56fe93b91d610255487f2b2a22a17bf917a87f9d30aad043ad',
  type: 2,
  to: '0x8cC4f3fBa4b89da6e700D5602e7622CDc0444B62',
  from: '0x3Ee7EbFb5823c8C8af05BC371873D371085447D1',
  nonce: 40,
  gasLimit: 21000n,
  gasPrice: undefined,
  maxPriorityFeePerGas: 546176991n,
  maxFeePerGas: 564848631n,
  maxFeePerBlobGas: null,
  data: '0x',
  value: 1000000000000000n,
  chainId: 11155111n,
  signature: Signature { r: "0xf2667f4712bab6df16ff902127c5410c74165886de2941843bca53a28440e5b4", s: "0x09bafe8c0e7f7ab7fa573205c606f490a14654c44c86f8047bcae1d88ee2661a", yParity: 0, networkV: null },
  accessList: [],
  blobVersionedHashes: null
}
Sender balance After --> 0.028874431147816388 ETH
Receiver balance After --> 0.101 ETH

合约转账

本节我们通过合约本身的 transfer 函数来实现转账

还是使用 sepolia 测试网,这次用的是 Link 代币,它也是一个 ERC20 代币,可以通过这个链接来领取一些测试代币:https://faucets.chain.link/sepolia

在 etherscan 中找到这个代币对应的合约地址:https://sepolia.etherscan.io/token/0xdbc1856cbd9553b8f2be31f6e6d5695dc823b47c

因为是 ERC20 代币,所以转账等常用功能的 ABI 是固定的,可以和上一节一样直接定义在数组中使用

具体转账代码如下

const { ethers } = require("ethers");

const INFURA_API_KEY = "...";

const provider = new ethers.JsonRpcProvider(
  `https://sepolia.infura.io/v3/${INFURA_API_KEY}`
);

const sender = "0x3Ee7EbFb5823c8C8af05BC371873D371085447D1";
const receiver = "0x8cC4f3fBa4b89da6e700D5602e7622CDc0444B62";
const senderPrivateKey = "...";
const wallet = new ethers.Wallet(senderPrivateKey, provider);

const ERC20_ABI = [
  "function balanceOf(address) view returns (uint256)",
  "function transfer(address to, uint256 amount) returns (bool)",
];

const ChainLinkTokenAddress = "0x779877A7B0D9E8603169DdbD7836e478b4624789";
const contract = new ethers.Contract(
  ChainLinkTokenAddress,
  ERC20_ABI,
  provider
);

const main = async () => {
  const senderBalanceBefore = await contract.balanceOf(sender);
  const receiverBalanceBefore = await contract.balanceOf(receiver);
  console.log(
    `Sender balance --> ${ethers.formatEther(senderBalanceBefore)} Link`
  );
  console.log(
    `Receiver balance --> ${ethers.formatEther(receiverBalanceBefore)} Link`
  );

  const contractWithWallect = contract.connect(wallet);
  const txResponse = await contractWithWallect.transfer(
    receiver,
    ethers.parseEther("1")
  );

  const receipt = await txResponse.wait();
  console.log(receipt);

  const senderBalanceAfter = await contract.balanceOf(sender);
  const receiverBalanceAfter = await contract.balanceOf(receiver);
  console.log(
    `Sender balance --> ${ethers.formatEther(senderBalanceAfter)} Link`
  );
  console.log(
    `Receiver balance --> ${ethers.formatEther(receiverBalanceAfter)} Link`
  );
};

main()

运行结果

Sender balance --> 40.0 Link
Receiver balance --> 0.0 Link
ContractTransactionReceipt {
  provider: JsonRpcProvider {},
  to: '0x779877A7B0D9E8603169DdbD7836e478b4624789',
  from: '0x3Ee7EbFb5823c8C8af05BC371873D371085447D1',
  contractAddress: null,
  hash: '0x8d6ecd27959f86d87b06ae30184f2c97d78064af5d20b49dd4c338990d176b31',
  index: 63,
  blockHash: '0xce05ec859ecc91dd060756ba1680a28f2940e57a7a12aca631ff8741fe2bea46',
  blockNumber: 5775997,
  logsBloom: '0x00000004000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000010000000000000000000000000000400000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000002004000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000002000000000',
  gasUsed: 51646n,
  blobGasUsed: null,
  cumulativeGasUsed: 12901369n,
  gasPrice: 663138827n,
  blobGasPrice: null,
  type: 2,
  status: 1,
  root: undefined
}
Sender balance --> 39.0 Link
Receiver balance --> 1.0 Link

事件查询

Ethers.js 也可以查询特定的事件,比如 ERC20 标准下的代币,每次转账的时候都会触发 Transfer 事件,这些事件都会在区块链中记录下来,我们就可以通过 Ethers.js 来查询对应的事件

const { ethers } = require("ethers");

const INFURA_API_KEY = "...";

const DAI_TOKEN_ADDRESS = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const provider = new ethers.JsonRpcProvider(
  `https://mainnet.infura.io/v3/${INFURA_API_KEY}`
);

const ERC20_ABI = [
  "function name() view returns (string)",
  "function symbol() view returns (string)",
  "function totalSupply() view returns (uint256)",
  "function balanceOf(address) view returns (uint256)",

  "event Transfer(address indexed from, address indexed to, uint256 value)",
];

const contract = new ethers.Contract(DAI_TOKEN_ADDRESS, ERC20_ABI, provider);

const main = async () => {
  const block = await provider.getBlockNumber();
  const transferEvents = await contract.queryFilter(
    "Transfer",
    block - 1,
    block
  );
  console.log(transferEvents);
};

main();

作者:加密鲸拓

版权:此文章版权归 加密鲸拓 所有,如有转载,请注明出处!