Ethers.js v6的详细用法

🖐🏻 免责声明

本教程仅供学习交流使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,请各读者自觉遵守相关法律法规。

# provider

# 获取当前区块号

/**
 * 异步获取当前区块号
 *
 * 本函数没有参数。
 *
 * @returns {Promise<number>} 返回一个承诺(Promise),该承诺解析为当前的区块号。
 */
async function getBlockNumber() {
  // 通过provider异步获取当前区块号
  let blockNumber = await provider.getBlockNumber();
  console.log(blockNumber); // 打印获取到的区块号
  return blockNumber; // 返回区块号
}

# 通过区块号异步获取区块信息

/**
 * 通过区块号异步获取区块信息
 * @param {number} blockNumber - 需要获取的区块号
 * @returns {Promise<Object>} 返回一个承诺(Promise),该承诺解析为包含指定区块信息的对象
 */
async function getBlockByNumber(blockNumber) {
  // 通过provider异步获取指定区块号的区块信息
  let block = await provider.getBlock(blockNumber);
  console.log(block);
  return block;
}

# 通过区块哈希异步获取区块信息

/**
 * 通过区块哈希异步获取区块信息
 * @param {string} blockHash - 区块的哈希值,用于唯一标识一个区块
 * @returns {Object} block - 返回一个包含区块详细信息的对象
 */
async function getBlockByHash(blockHash) {
  // 通过区块哈希值异步获取区块对象
  let block = await provider.getBlock(blockHash);
  console.log(block);
  return block;
}

# 获取指定地址的余额

/**
 * 异步获取指定地址的余额。
 * @param {string} address - 需要查询余额的地址。
 * @returns {Promise<BigNumber>} 返回一个承诺(Promise),该承诺解析为以太坊地址的余额。
 */
async function getBalanceByAddress(address) {
  // 使用provider获取指定地址的余额
  let balance = await provider.getBalance(address);
  console.log(balance); // 打印原始余额
  console.log(ethers.formatEther(balance)); // 打印格式化后的以太币余额
  return balance; // 返回余额
}

# 通过地址获取交易计数

/**
 * 通过地址获取交易计数
 * @param {string} address - 以太坊地址,用于查询该地址的交易数量。
 * @returns {number} transactionCount - 返回查询地址的交易数量。
 */
async function getTransactionCountByAddress(address) {
    // 异步等待获取指定地址的交易计数
    let transactionCount = await provider.getTransactionCount(address);
    console.log(transactionCount); // 打印交易计数
    return transactionCount; // 返回交易计数
}

# 根据交易哈希异步获取交易信息

/**
 * 根据交易哈希异步获取交易信息
 * @param {string} hash 交易的哈希值,用于唯一标识一笔交易
 * @returns {Promise<object>} 返回一个Promise对象,该对象解析为交易信息的对象
 */
async function getTransactionByHash(hash) {
  // 通过提供的provider异步获取指定hash的交易信息
  let transaction = await provider.getTransaction(hash);
  console.log(transaction);
  return transaction;
}

# 根据交易哈希异步获取交易收据,区别是成功的交易,有消耗多少Gas这些信息

/**
 * 根据交易哈希异步获取交易收据,区别是成功的交易,有消耗多少Gas这些信息
 * @param {string} hash 交易的哈希值
 * @returns {Promise<object>} 返回一个承诺(Promise),该承诺解析为交易收据对象
 */
async function getTransactionReceiptByHash(hash) {
  // 通过提供的provider异步获取指定hash的交易收据
  let receipt = await provider.getTransactionReceipt(hash);
  console.log(receipt);
  return receipt;
}

# 异步获取费用数据

/**
 * 异步获取费用数据
 * 无参数
 * @returns {Promise<Object>} 返回一个承诺(Promise),该承诺解析为费用数据对象
 */
async function getFeeData() {
  // 等待provider.getFeeData()的执行结果,并将结果赋值给feeData
  let feeData = await provider.getFeeData();
  console.log(feeData);
  if (feeData.gasPrice !== null) {
    console.log("gasPrice", ethers.formatUnits(feeData.gasPrice, "gwei"));
  }
  if (feeData.maxFeePerGas !== null) {
    console.log(
      "maxFeePerGas",
      ethers.formatUnits(feeData.maxFeePerGas, "gwei")
    );
  }
  if (feeData.maxPriorityFeePerGas !== null) {
    console.log(
      "maxPriorityFeePerGas",
      ethers.formatUnits(feeData.maxPriorityFeePerGas, "gwei")
    );
  }
  return feeData;
}

# 异步获取指定地址和槽位的存储值

/**
 * 异步获取指定地址和槽位的存储值
 * @param {string} address 指定的合约地址,为字符串格式
 * @param {number} slot 指定的槽位,为数字格式
 * @returns {Promise<string>} 返回一个Promise,解析为存储槽位的16进制字符串值
 */
async function getStorageByAddressAndSlot(address, slot) {
  // 通过提供的provider异步获取指定地址和槽位的存储值
  let storageValue = await provider.getStorage(address, slot);
  console.log("storageValue HexValue", storageValue);
  // 下面两行代码被注释,用于展示存储值的整型和字符串表示形式的解析
  console.log("storageValue IntValue", parseInt(storageValue, 16));
  console.log("storageValue StringValue", ethers.toUtf8String(storageValue));
  return storageValue;
}

# 完整代码

import { ethers } from "ethers";

const RPC_URL =
    "节点rpc地址";
const provider = new ethers.JsonRpcProvider(RPC_URL);
/**
 * 异步获取当前区块号
 *
 * 本函数没有参数。
 *
 * @returns {Promise<number>} 返回一个承诺(Promise),该承诺解析为当前的区块号。
 */
async function getBlockNumber() {
  // 通过provider异步获取当前区块号
  let blockNumber = await provider.getBlockNumber();
  console.log(blockNumber); // 打印获取到的区块号
  return blockNumber; // 返回区块号
}

/**
 * 通过区块号异步获取区块信息
 * @param {number} blockNumber - 需要获取的区块号
 * @returns {Promise<Object>} 返回一个承诺(Promise),该承诺解析为包含指定区块信息的对象
 */
async function getBlockByNumber(blockNumber) {
  // 通过provider异步获取指定区块号的区块信息
  let block = await provider.getBlock(blockNumber);
  console.log(block);
  return block;
}

/**
 * 通过区块哈希异步获取区块信息
 * @param {string} blockHash - 区块的哈希值,用于唯一标识一个区块
 * @returns {Object} block - 返回一个包含区块详细信息的对象
 */
async function getBlockByHash(blockHash) {
  // 通过区块哈希值异步获取区块对象
  let block = await provider.getBlock(blockHash);
  console.log(block);
  return block;
}

/**
 * 异步获取指定地址的余额。
 * @param {string} address - 需要查询余额的地址。
 * @returns {Promise<BigNumber>} 返回一个承诺(Promise),该承诺解析为以太坊地址的余额。
 */
async function getBalanceByAddress(address) {
  // 使用provider获取指定地址的余额
  let balance = await provider.getBalance(address);
  console.log(balance); // 打印原始余额
  console.log(ethers.formatEther(balance)); // 打印格式化后的以太币余额
  return balance; // 返回余额
}

/**
 * 通过地址获取交易计数
 * @param {string} address - 以太坊地址,用于查询该地址的交易数量。
 * @returns {number} transactionCount - 返回查询地址的交易数量。
 */
async function getTransactionCountByAddress(address) {
    // 异步等待获取指定地址的交易计数
    let transactionCount = await provider.getTransactionCount(address);
    console.log(transactionCount); // 打印交易计数
    return transactionCount; // 返回交易计数
}

/**
 * 根据交易哈希异步获取交易信息
 * @param {string} hash 交易的哈希值,用于唯一标识一笔交易
 * @returns {Promise<object>} 返回一个Promise对象,该对象解析为交易信息的对象
 */
async function getTransactionByHash(hash) {
  // 通过提供的provider异步获取指定hash的交易信息
  let transaction = await provider.getTransaction(hash);
  console.log(transaction);
  return transaction;
}

/**
 * 根据交易哈希异步获取交易收据,区别是成功的交易,有消耗多少Gas这些信息
 * @param {string} hash 交易的哈希值
 * @returns {Promise<object>} 返回一个承诺(Promise),该承诺解析为交易收据对象
 */
async function getTransactionReceiptByHash(hash) {
  // 通过提供的provider异步获取指定hash的交易收据
  let receipt = await provider.getTransactionReceipt(hash);
  console.log(receipt);
  return receipt;
}
/**
 * 异步获取费用数据
 * 无参数
 * @returns {Promise<Object>} 返回一个承诺(Promise),该承诺解析为费用数据对象
 */
async function getFeeData() {
  // 等待provider.getFeeData()的执行结果,并将结果赋值给feeData
  let feeData = await provider.getFeeData();
  console.log(feeData);
  if (feeData.gasPrice !== null) {
    console.log("gasPrice", ethers.formatUnits(feeData.gasPrice, "gwei"));
  }
  if (feeData.maxFeePerGas !== null) {
    console.log(
      "maxFeePerGas",
      ethers.formatUnits(feeData.maxFeePerGas, "gwei")
    );
  }
  if (feeData.maxPriorityFeePerGas !== null) {
    console.log(
      "maxPriorityFeePerGas",
      ethers.formatUnits(feeData.maxPriorityFeePerGas, "gwei")
    );
  }
  return feeData;
}

/**
 * 异步获取指定地址和槽位的存储值
 * @param {string} address 指定的合约地址,为字符串格式
 * @param {number} slot 指定的槽位,为数字格式
 * @returns {Promise<string>} 返回一个Promise,解析为存储槽位的16进制字符串值
 */
async function getStorageByAddressAndSlot(address, slot) {
  // 通过提供的provider异步获取指定地址和槽位的存储值
  let storageValue = await provider.getStorage(address, slot);
  console.log("storageValue HexValue", storageValue);
  // 下面两行代码被注释,用于展示存储值的整型和字符串表示形式的解析
  console.log("storageValue IntValue", parseInt(storageValue, 16));
  console.log("storageValue StringValue", ethers.toUtf8String(storageValue));
  return storageValue;
}

# signer

个人理解,signer通过provider直接获取,这种项目里是不需要私钥的,经常结合浏览器钱包插件,比如metamask一起使用,使用场景是面向用户的。

另外,本地节点也是可以直接获取signer的,方便测试,常见的有hardhat启动的节点,ganache等。

# 异步获取签名者对象

/**
 * 异步获取签名者对象。
 *
 * 该函数通过调用provider的getSigner方法,并传入地址(address),来异步获取一个签名者对象。
 * 这个签名者对象可以用于后续的交易签名等操作。
 *
 * @param {Object} provider - 提供者对象,必须有一个getSigner方法。
 * @param {string} address - 需要获取签名者的地址。
 * @returns {Promise<Object>} 返回一个Promise,该Promise解析为签名者对象。
 */
async function getSigner() {
  signer = await provider.getSigner(); // 等待provider的getSigner方法返回签名者对象
}

# 异步获取签名者地址

/**
 * 异步获取签名者地址
 * 该函数没有参数。
 * @returns {Promise<string>} 返回一个承诺(Promise),该承诺解析为签名者的地址字符串。
 */
async function getSignerAddress() {
  // 等待获取签名者地址
  const signerAddress = await signer.getAddress();
  console.log("signerAddress", signerAddress);
  return signerAddress;
}

# 个人签名函数-仅小狐狸支持,本地节点不支持

/**
 * 个人签名函数-仅小狐狸支持,本地节点不支持
 * 使用提供的signer对象对指定消息进行个人签名
 *
 * @param {string} message 需要签名的消息内容
 * @returns {Promise<string>} 返回一个承诺(Promise),该承诺解析为签名字符串
 */
async function personalSign(message) {
  // 使用signer对象签名消息,并等待签名过程完成
  const signature = await signer.signMessage(message);
  console.log("signature", signature);
  return signature;
}

# 异步签名函数-仅本地节点支持

底层是发起eth_sign请求,一些弯弯绕绕的知识点参考这里

/**
 * 异步签名函数-仅本地节点支持
 * @param {string} message 需要签名的消息
 * @returns {Promise<string>} 返回签名后的消息
 */
async function ethSign(message) {
  // 使用提供的signer对象对消息进行签名
  const signature = await signer._legacySignMessage(message);
  console.log("signature", signature);
  return signature;
}

# 验证给定消息的签名是否有效

/**
 * 验证给定消息的签名是否有效。
 * @param message 需要验证签名的消息内容。
 * @param signature 消息对应的签名。
 * @returns 返回通过签名验证的地址。
 */
async function verifySignature(message, signature) {
  // 验证消息和签名,并恢复签名者的地址
  const recoveredAddress = await ethers.verifyMessage(message, signature);
  console.log("recoveredAddress", recoveredAddress);

  // 获取签名者的地址进行比对
  const signerAddress = await getSignerAddress();

  // 比对恢复的地址和签名者的地址是否一致
  if (recoveredAddress !== signerAddress) {
    console.log("签名验证失败");
  } else {
    console.log("签名验证成功");
  }

  return recoveredAddress;
}

# 异步签名并发送交易

/**
 * 异步签名并发送交易
 * @param {string} from 发起交易的地址
 * @param {string} to 接收交易的地址
 * @param {string} amount 转账金额,以 Ether 为单位
 * @returns {Promise<object>} 返回交易签名结果的Promise
 */
async function signTransaction(from, to, amount) {
  // 从provider获取from地址的签名器
  const fromSigner = await provider.getSigner(from);
  // 将转账金额转换为wei单位
  const weiAmount = ethers.parseUnits(amount + "");

  // 创建交易对象,设置发起方、接收方和转账金额
  let tx = {
    from: from,
    to: to,
    value: weiAmount,
  };

  // 获取当前网络的Gas费用信息,并设置到交易对象中
  const feeData = await provider.getFeeData();
  //   console.log("feeData", feeData);
  tx.maxFeePerGas = feeData.maxFeePerGas;
  const gas = await provider.estimateGas(tx);
  tx.gasLimit = gas;

  // 签名交易并打印结果
  const result = await fromSigner.signTransaction(tx);
  console.log("result", result);
  return result;
}

# 异步广播交易

/**
 * 异步广播交易。
 * @param {string} signedTx 已签名的交易数据,准备发送到网络进行确认。
 * @returns {Promise<object>} 返回一个承诺(Promise),解决为交易的详细信息对象。
 */
async function broadcastTransaction(signedTx) {
  // 通过provider发送已签名的交易,并等待广播结果
  const tx = await provider.broadcastTransaction(signedTx);
  console.log("tx", tx);
  return tx;
}

# 异步发送以太坊交易

/**
 * 异步发送以太坊交易(ethers把该做的封装都做了,基本只要传个to,value,data等,其它参数内部都替我们处理好了)
 * @param {string} from 发起交易的地址
 * @param {string} to 接收交易的地址
 * @param {string} amount 转账金额,单位为ether
 * @returns {Promise<object>} 返回交易执行结果的Promise
 */
async function sendTransaction(from, to, amount) {
  // 获取from地址的签名器
  const fromSigner = await provider.getSigner(from);
  // 将转账金额转换为wei单位
  const weiAmount = ethers.parseUnits(amount + "");
  // 将memo文本转换为hex格式,用于交易数据部分
  const memo = ethers.hexlify(ethers.toUtf8Bytes("Hello"));
  
  // 创建交易对象,包括发起方、接收方、转账金额和数据
  let tx = {
    from: from,
    to: to,
    value: weiAmount,
    data: memo,
  };

  // 注释掉的部分是获取和设置Gas费用的代码,以及估算Gas用量的逻辑
  // // 获取当前网络的Gas费用信息,并设置到交易对象中
  // const feeData = await provider.getFeeData();
  // //   console.log("feeData", feeData);
  // tx.maxFeePerGas = feeData.maxFeePerGas;
  // const gas = await provider.estimateGas(tx);
  // tx.gasLimit = gas;

  // 签名交易并发送
  const result = await fromSigner.sendTransaction(tx);
  console.log("result", result);
  return result;
}

# 完整代码

import { ethers } from "ethers";

const RPC_URL = "http://localhost:7545";
const provider = new ethers.JsonRpcProvider(RPC_URL); // 本地节点new出来的provider或者根据钱包插件得到的provider,才可以用signer,alchemy这种节点rpc是没有的,或者就用metamask的provider

let signer;
async function main() {
  await getSigner();
  //   getSignerAddress();
  //   let msg = "Hello";
  //   let sig = await ethSign(msg);
  //   verifySignature(msg, sig);

  //   let signedTx = await signTransaction(
  //     "from",
  //     "to",
  //     1
  //   );
  //   broadcastTransaction(signedTx);

  sendTransaction(
    "from",
    "to",
    1
  );
}

/**
 * 异步获取签名者对象。
 *
 * 该函数通过调用provider的getSigner方法,并传入地址(address),来异步获取一个签名者对象。
 * 这个签名者对象可以用于后续的交易签名等操作。
 *
 * @param {Object} provider - 提供者对象,必须有一个getSigner方法。
 * @param {string} address - 需要获取签名者的地址。
 * @returns {Promise<Object>} 返回一个Promise,该Promise解析为签名者对象。
 */
async function getSigner() {
  signer = await provider.getSigner(); // 等待provider的getSigner方法返回签名者对象
}

/**
 * 异步获取签名者地址
 * 该函数没有参数。
 * @returns {Promise<string>} 返回一个承诺(Promise),该承诺解析为签名者的地址字符串。
 */
async function getSignerAddress() {
  // 等待获取签名者地址
  const signerAddress = await signer.getAddress();
  console.log("signerAddress", signerAddress);
  return signerAddress;
}

/**
 * 个人签名函数-仅小狐狸支持,本地节点不支持
 * 使用提供的signer对象对指定消息进行个人签名
 *
 * @param {string} message 需要签名的消息内容
 * @returns {Promise<string>} 返回一个承诺(Promise),该承诺解析为签名字符串
 */
async function personalSign(message) {
  // 使用signer对象签名消息,并等待签名过程完成
  const signature = await signer.signMessage(message);
  console.log("signature", signature);
  return signature;
}

/**
 * 异步签名函数-仅本地节点支持
 * @param {string} message 需要签名的消息
 * @returns {Promise<string>} 返回签名后的消息
 */
async function ethSign(message) {
  // 使用提供的signer对象对消息进行签名
  const signature = await signer._legacySignMessage(message);
  console.log("signature", signature);
  return signature;
}

/**
 * 验证给定消息的签名是否有效。
 * @param message 需要验证签名的消息内容。
 * @param signature 消息对应的签名。
 * @returns 返回通过签名验证的地址。
 */
async function verifySignature(message, signature) {
  // 验证消息和签名,并恢复签名者的地址
  const recoveredAddress = await ethers.verifyMessage(message, signature);
  console.log("recoveredAddress", recoveredAddress);

  // 获取签名者的地址进行比对
  const signerAddress = await getSignerAddress();

  // 比对恢复的地址和签名者的地址是否一致
  if (recoveredAddress !== signerAddress) {
    console.log("签名验证失败");
  } else {
    console.log("签名验证成功");
  }

  return recoveredAddress;
}

/**
 * 异步签名并发送交易
 * @param {string} from 发起交易的地址
 * @param {string} to 接收交易的地址
 * @param {string} amount 转账金额,以 Ether 为单位
 * @returns {Promise<object>} 返回交易签名结果的Promise
 */
async function signTransaction(from, to, amount) {
  // 从provider获取from地址的签名器
  const fromSigner = await provider.getSigner(from);
  // 将转账金额转换为wei单位
  const weiAmount = ethers.parseUnits(amount + "");

  // 创建交易对象,设置发起方、接收方和转账金额
  let tx = {
    from: from,
    to: to,
    value: weiAmount,
  };

  // 获取当前网络的Gas费用信息,并设置到交易对象中
  const feeData = await provider.getFeeData();
  //   console.log("feeData", feeData);
  tx.maxFeePerGas = feeData.maxFeePerGas;
  const gas = await provider.estimateGas(tx);
  tx.gasLimit = gas;

  // 签名交易并打印结果
  const result = await fromSigner.signTransaction(tx);
  console.log("result", result);
  return result;
}

/**
 * 异步广播交易。
 * @param {string} signedTx 已签名的交易数据,准备发送到网络进行确认。
 * @returns {Promise<object>} 返回一个承诺(Promise),解决为交易的详细信息对象。
 */
async function broadcastTransaction(signedTx) {
  // 通过provider发送已签名的交易,并等待广播结果
  const tx = await provider.broadcastTransaction(signedTx);
  console.log("tx", tx);
  return tx;
}

/**
 * 异步发送以太坊交易(ethers把该做的封装都做了,基本只要传个to,value,data等,其它参数内部都替我们处理好了)
 * @param {string} from 发起交易的地址
 * @param {string} to 接收交易的地址
 * @param {string} amount 转账金额,单位为ether
 * @returns {Promise<object>} 返回交易执行结果的Promise
 */
async function sendTransaction(from, to, amount) {
  // 获取from地址的签名器
  const fromSigner = await provider.getSigner(from);
  // 将转账金额转换为wei单位
  const weiAmount = ethers.parseUnits(amount + "");
  // 将memo文本转换为hex格式,用于交易数据部分
  const memo = ethers.hexlify(ethers.toUtf8Bytes("Hello"));
  
  // 创建交易对象,包括发起方、接收方、转账金额和数据
  let tx = {
    from: from,
    to: to,
    value: weiAmount,
    data: memo,
  };

  // 注释掉的部分是获取和设置Gas费用的代码,以及估算Gas用量的逻辑
  // // 获取当前网络的Gas费用信息,并设置到交易对象中
  // const feeData = await provider.getFeeData();
  // //   console.log("feeData", feeData);
  // tx.maxFeePerGas = feeData.maxFeePerGas;
  // const gas = await provider.estimateGas(tx);
  // tx.gasLimit = gas;

  // 签名交易并发送
  const result = await fromSigner.sendTransaction(tx);
  console.log("result", result);
  return result;
}

main();

# Utils

# 单位转换

image-20240417100058441

# wei2Any

console.log(ethers.formatUnits(1000000024, "gwei")); // 左边是wei的数量,右边进行转换成对应单位,比如这里9个0,变成gwei就是2,该方法是把bigint转sting

# Any2Wei

console.log(ethers.parseUnits(1 + "", "gwei")); // 把指定数量+单位,比如这里的1+gwei转为wei的数量,string转bigint

# wei2Ether

console.log(ethers.formatEther(2)); // 传入wei的数量,转为ether计数法,把bigint转string

# Ether2Wei

console.log(ethers.parseEther(2 + "")); // 传入ether的数量,转为wei,把string转bigint

# 完整代码

import { ethers } from "ethers";

console.log(ethers.formatUnits(1000000024, "gwei")); // 左边是wei的数量,右边进行转换成对应单位,比如这里9个0,变成gwei就是2,该方法是把bigint转sting
console.log(ethers.parseUnits(1 + "", "gwei")); // 把指定数量+单位,比如这里的1+gwei转为wei的数量,string转bigint
console.log(ethers.formatEther(2)); // 传入wei的数量,转为ether计数法,把bigint转string
console.log(ethers.parseEther(2 + "")); // 传入ether的数量,转为wei,把string转bigint

# Encode 编码与 Decode 解码

const abiCoder = AbiCoder.defaultAbiCoder();

# 编码

# 字符串

abiCoder.encode(["string"], ["Hello"]

# 整数

abiCoder.encode(["uint256"], [100])

# address

abiCoder.encode(["address"], ["0x7d8BB2Ded76d6Bd89410b3b56a1c503612E05a35"])

# bool

abiCoder.encode(["bool"], [true])

# 1字节

abiCoder.encode(["bytes1"], ["0xff"])

# 数组

abiCoder.encode(["uint256[]"], [[1, 2, 3]])

# struct

abiCoder.encode(["tuple(string,uint256[])"], [["chenkai", [30, 31]]]

# 组合

abiCoder.encode( ["uint256[]", "string", "tuple(string,uint256[])"], [[1, 2], "Hello", ["chenkai", [30, 31]]] )

# 解码

abiCoder.decode(["string"],"0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000")

abiCoder.decode(["uint256[]", "string", "tuple(string,uint256[])"], r1)r1是encode后的结果

# 完整代码

const abiCoder = AbiCoder.defaultAbiCoder();

//字符串
console.log(abiCoder.encode(["string"], ["Hello"]));
console.log(
abiCoder.decode(
  ["string"],
  "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000548656c6c6f000000000000000000000000000000000000000000000000000000"
)
);
//整数
console.log(abiCoder.encode(["uint256"], [100]));
//address
console.log(
abiCoder.encode(["address"], ["0x7d8BB2Ded76d6Bd89410b3b56a1c503612E05a35"])
);
//bool
console.log(abiCoder.encode(["bool"], [true]));
//字节
console.log(abiCoder.encode(["bytes1"], ["0xff"]));
//数组
console.log(abiCoder.encode(["uint256[]"], [[1, 2, 3]]));
//struct
console.log(
abiCoder.encode(["tuple(string,uint256[])"], [["chenkai", [30, 31]]])
);
//组合
const r1 = abiCoder.encode(
["uint256[]", "string", "tuple(string,uint256[])"],
[[1, 2], "Hello", ["chenkai", [30, 31]]]
);
console.log(r1);
console.log(abiCoder.decode(["uint256[]", "string", "tuple(string,uint256[])"], r1));

# 方法编码与解码

const iface = new ethers.Interface(abi);

# 编码

encode1 为方法名,["Hello"]为参数,方法名必须是初始化传入的abi中有的

iface.encodeFunctionData("encode1", ["Hello"]);

# 解码

encode1 为方法名,data为编码后的值

iface.decodeFunctionData("encode1", data)

# 完整代码

const abi = [
{
  inputs: [
    {
      internalType: "string",
      name: "_str",
      type: "string",
    },
  ],
  name: "encode1",
  outputs: [
    {
      internalType: "bytes",
      name: "",
      type: "bytes",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [
    {
      internalType: "uint256",
      name: "_str",
      type: "uint256",
    },
  ],
  name: "encode2",
  outputs: [
    {
      internalType: "bytes",
      name: "",
      type: "bytes",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [
    {
      internalType: "address",
      name: "_str",
      type: "address",
    },
  ],
  name: "encode3",
  outputs: [
    {
      internalType: "bytes",
      name: "",
      type: "bytes",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [
    {
      internalType: "bool",
      name: "_str",
      type: "bool",
    },
  ],
  name: "encode4",
  outputs: [
    {
      internalType: "bytes",
      name: "",
      type: "bytes",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [
    {
      internalType: "bytes1",
      name: "_str",
      type: "bytes1",
    },
  ],
  name: "encode5",
  outputs: [
    {
      internalType: "bytes",
      name: "",
      type: "bytes",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [
    {
      internalType: "uint256[]",
      name: "_str",
      type: "uint256[]",
    },
  ],
  name: "encode6",
  outputs: [
    {
      internalType: "bytes",
      name: "",
      type: "bytes",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [
    {
      components: [
        {
          internalType: "string",
          name: "name",
          type: "string",
        },
        {
          internalType: "uint256[]",
          name: "ages",
          type: "uint256[]",
        },
      ],
      internalType: "struct AbiDemo.People",
      name: "_str",
      type: "tuple",
    },
  ],
  name: "encode7",
  outputs: [
    {
      internalType: "bytes",
      name: "",
      type: "bytes",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [
    {
      internalType: "uint256[]",
      name: "_p1",
      type: "uint256[]",
    },
    {
      internalType: "string",
      name: "_p2",
      type: "string",
    },
    {
      components: [
        {
          internalType: "string",
          name: "name",
          type: "string",
        },
        {
          internalType: "uint256[]",
          name: "ages",
          type: "uint256[]",
        },
      ],
      internalType: "struct AbiDemo.People",
      name: "_str",
      type: "tuple",
    },
  ],
  name: "encode8",
  outputs: [
    {
      internalType: "bytes",
      name: "",
      type: "bytes",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
];

const iface = new ethers.Interface(abi);
const data = iface.encodeFunctionData("encode1", ["Hello"]);
console.log(data);
console.log(iface.decodeFunctionData("encode1", data));

# 事件编码与解码

事件可以参考这篇文章

const iface = new ethers.Interface(abi);

# 编码

Transfer3为事件名,必须在Abi中有,后面数组传参数

iface.encodeEventLog("Transfer3,["0x41a2A45041f32396f3b9ac23b43cbadd4B8A0024","0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",100]);

返回值包含data和topics

# 解码

  1. iface.decodeEventLog("Transfer3", res.data, res.topics)
//返回值
Result(3) [
   '0x41a2A45041f32396f3b9ac23b43cbadd4B8A0024',
   '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4',
   100n
]
  1. iface.parseLog({ topics: res.topics, data: res.data })
// LogDescription {
//   fragment: EventFragment {
//     type: 'event',
//     inputs: [ [ParamType], [ParamType], [ParamType] ],
//     name: 'Transfer3',
//     anonymous: false
//   },
//   name: 'Transfer3',
//   signature: 'Transfer3(address,address,uint256)',
//   topic: '0x2411965a414db56a3afdcb174d6049ed9467924617e5455ab694e45e8f69fd80',
//   args: Result(3) [
//     '0x41a2A45041f32396f3b9ac23b43cbadd4B8A0024',
//     '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4',
//     100n
//   ]
// }

# 完整代码

const abi = [
{
  anonymous: false,
  inputs: [],
  name: "Transfer",
  type: "event",
},
{
  anonymous: false,
  inputs: [
    {
      indexed: true,
      internalType: "address",
      name: "from",
      type: "address",
    },
  ],
  name: "Transfer1",
  type: "event",
},
{
  anonymous: false,
  inputs: [
    {
      indexed: true,
      internalType: "address",
      name: "from",
      type: "address",
    },
    {
      indexed: true,
      internalType: "address",
      name: "to",
      type: "address",
    },
  ],
  name: "Transfer2",
  type: "event",
},
{
  anonymous: false,
  inputs: [
    {
      indexed: true,
      internalType: "address",
      name: "from",
      type: "address",
    },
    {
      indexed: true,
      internalType: "address",
      name: "tp",
      type: "address",
    },
    {
      indexed: false,
      internalType: "uint256",
      name: "amount",
      type: "uint256",
    },
  ],
  name: "Transfer3",
  type: "event",
},
{
  anonymous: false,
  inputs: [
    {
      indexed: true,
      internalType: "address",
      name: "from",
      type: "address",
    },
    {
      indexed: true,
      internalType: "address",
      name: "to",
      type: "address",
    },
    {
      indexed: true,
      internalType: "address",
      name: "spender",
      type: "address",
    },
    {
      indexed: false,
      internalType: "uint256",
      name: "amount",
      type: "uint256",
    },
  ],
  name: "Transfer4",
  type: "event",
},
{
  inputs: [],
  name: "emitTransfer",
  outputs: [],
  stateMutability: "nonpayable",
  type: "function",
},
{
  inputs: [],
  name: "emitTransfer1",
  outputs: [],
  stateMutability: "nonpayable",
  type: "function",
},
{
  inputs: [],
  name: "emitTransfer2",
  outputs: [],
  stateMutability: "nonpayable",
  type: "function",
},
{
  inputs: [],
  name: "emitTransfer3",
  outputs: [],
  stateMutability: "nonpayable",
  type: "function",
},
{
  inputs: [],
  name: "emitTransfer4",
  outputs: [],
  stateMutability: "nonpayable",
  type: "function",
},
{
  inputs: [],
  name: "spender",
  outputs: [
    {
      internalType: "address",
      name: "",
      type: "address",
    },
  ],
  stateMutability: "view",
  type: "function",
},
{
  inputs: [],
  name: "to",
  outputs: [
    {
      internalType: "address",
      name: "",
      type: "address",
    },
  ],
  stateMutability: "view",
  type: "function",
},
{
  inputs: [],
  name: "transferTopic",
  outputs: [
    {
      internalType: "bytes32",
      name: "",
      type: "bytes32",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [],
  name: "transferTopic1",
  outputs: [
    {
      internalType: "bytes32",
      name: "",
      type: "bytes32",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [],
  name: "transferTopic2",
  outputs: [
    {
      internalType: "bytes32",
      name: "",
      type: "bytes32",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [],
  name: "transferTopic3",
  outputs: [
    {
      internalType: "bytes32",
      name: "",
      type: "bytes32",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
{
  inputs: [],
  name: "transferTopic4",
  outputs: [
    {
      internalType: "bytes32",
      name: "",
      type: "bytes32",
    },
  ],
  stateMutability: "pure",
  type: "function",
},
];
const iface = new ethers.Interface(abi);
const res = iface.encodeEventLog("Transfer3", [
"0x41a2A45041f32396f3b9ac23b43cbadd4B8A0024",
"0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
100,
]);
console.log(res);
console.log(iface.decodeEventLog("Transfer3", res.data, res.topics));
// Result(3) [
//   '0x41a2A45041f32396f3b9ac23b43cbadd4B8A0024',
//   '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4',
//   100n
// ]
console.log(iface.parseLog({ topics: res.topics, data: res.data }));
// LogDescription {
//   fragment: EventFragment {
//     type: 'event',
//     inputs: [ [ParamType], [ParamType], [ParamType] ],
//     name: 'Transfer3',
//     anonymous: false
//   },
//   name: 'Transfer3',
//   signature: 'Transfer3(address,address,uint256)',
//   topic: '0x2411965a414db56a3afdcb174d6049ed9467924617e5455ab694e45e8f69fd80',
//   args: Result(3) [
//     '0x41a2A45041f32396f3b9ac23b43cbadd4B8A0024',
//     '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4',
//     100n
//   ]
// }

# Abi格式化

const abi = [
    {
      anonymous: false,
      inputs: [],
      name: "Transfer",
      type: "event",
    },
    {
      anonymous: false,
      inputs: [
        {
          indexed: true,
          internalType: "address",
          name: "from",
          type: "address",
        },
      ],
      name: "Transfer1",
      type: "event",
    },
    {
      anonymous: false,
      inputs: [
        {
          indexed: true,
          internalType: "address",
          name: "from",
          type: "address",
        },
        {
          indexed: true,
          internalType: "address",
          name: "to",
          type: "address",
        },
      ],
      name: "Transfer2",
      type: "event",
    },
    {
      anonymous: false,
      inputs: [
        {
          indexed: true,
          internalType: "address",
          name: "from",
          type: "address",
        },
        {
          indexed: true,
          internalType: "address",
          name: "tp",
          type: "address",
        },
        {
          indexed: false,
          internalType: "uint256",
          name: "amount",
          type: "uint256",
        },
      ],
      name: "Transfer3",
      type: "event",
    },
    {
      anonymous: false,
      inputs: [
        {
          indexed: true,
          internalType: "address",
          name: "from",
          type: "address",
        },
        {
          indexed: true,
          internalType: "address",
          name: "to",
          type: "address",
        },
        {
          indexed: true,
          internalType: "address",
          name: "spender",
          type: "address",
        },
        {
          indexed: false,
          internalType: "uint256",
          name: "amount",
          type: "uint256",
        },
      ],
      name: "Transfer4",
      type: "event",
    },
    {
      inputs: [],
      name: "emitTransfer",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function",
    },
    {
      inputs: [],
      name: "emitTransfer1",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function",
    },
    {
      inputs: [],
      name: "emitTransfer2",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function",
    },
    {
      inputs: [],
      name: "emitTransfer3",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function",
    },
    {
      inputs: [],
      name: "emitTransfer4",
      outputs: [],
      stateMutability: "nonpayable",
      type: "function",
    },
    {
      inputs: [],
      name: "spender",
      outputs: [
        {
          internalType: "address",
          name: "",
          type: "address",
        },
      ],
      stateMutability: "view",
      type: "function",
    },
    {
      inputs: [],
      name: "to",
      outputs: [
        {
          internalType: "address",
          name: "",
          type: "address",
        },
      ],
      stateMutability: "view",
      type: "function",
    },
    {
      inputs: [],
      name: "transferTopic",
      outputs: [
        {
          internalType: "bytes32",
          name: "",
          type: "bytes32",
        },
      ],
      stateMutability: "pure",
      type: "function",
    },
    {
      inputs: [],
      name: "transferTopic1",
      outputs: [
        {
          internalType: "bytes32",
          name: "",
          type: "bytes32",
        },
      ],
      stateMutability: "pure",
      type: "function",
    },
    {
      inputs: [],
      name: "transferTopic2",
      outputs: [
        {
          internalType: "bytes32",
          name: "",
          type: "bytes32",
        },
      ],
      stateMutability: "pure",
      type: "function",
    },
    {
      inputs: [],
      name: "transferTopic3",
      outputs: [
        {
          internalType: "bytes32",
          name: "",
          type: "bytes32",
        },
      ],
      stateMutability: "pure",
      type: "function",
    },
    {
      inputs: [],
      name: "transferTopic4",
      outputs: [
        {
          internalType: "bytes32",
          name: "",
          type: "bytes32",
        },
      ],
      stateMutability: "pure",
      type: "function",
    },
  ];
//借助ethers.Interface,取决于一个参数传true或false
  const iface = new ethers.Interface(abi);
  const abi3 = iface.format(true); //最精简,形如'event Transfer4(address indexed,address indexed,address indexed,uint256)'
  const abi4 = iface.format(false); //形如'event Transfer4(address indexed from, address indexed to, address indexed spender, uint256 amount)'
  console.log(abi3);
  console.log(abi4);

# 字符串与bytes数组的关系

我们以Hello为例,他有好几种表现形式,1.Hello本身 2.Hex字符串0x48656c6c6f 3.Uint8数组形式

# 字符串2Bytes数组

const str = "Hello";
// 将字符串转换为UTF-8字节码数组
const bytesArr = ethers.toUtf8Bytes(str);

# Bytes数组2Hex字符串

// 将字节码数组转换为16进制字符串
const hexStr = ethers.hexlify(bytesArr);

# 十六进制数字转十进制

// 48是十六进制,将为转为10进制,这里结果是72
console.log(parseInt(48, 16));

# Hex字符串2字符串

// 将16进制字符串转换回UTF-8字符串
console.log(ethers.toUtf8String(hexStr));

# Bytes数组2字符串

// 将字节码数组直接转换回UTF-8字符串
console.log(ethers.toUtf8String(bytesArr));

# Hex字符串2Bytes数组

// 获取与之前转换的16进制字符串对应的字节码数组
console.log(ethers.getBytes(hexStr));

# 对Bytes数组进行SHA-256哈希计算

// 对字节码数组进行SHA-256哈希计算
const hash1 = ethers.keccak256(bytesArr);
console.log(hash1);

# 完整代码

/**
 * 将字符串转换为UTF-8字节码,然后转换为16进制字符串,并进行哈希计算。
 * 主要演示了ethers.js库中与字符串和字节操作相关的功能。
 */
const str = "Hello";
// 将字符串转换为UTF-8字节码数组
const bytesArr = ethers.toUtf8Bytes(str);
console.log(bytesArr);
// 将字节码数组转换为16进制字符串
const hexStr = ethers.hexlify(bytesArr);
console.log(hexStr);
// 48是十六进制,将为转为10进制,这里结果是72
console.log(parseInt(48, 16));
// 将16进制字符串转换回UTF-8字符串
console.log(ethers.toUtf8String(hexStr));
// 将字节码数组直接转换回UTF-8字符串
console.log(ethers.toUtf8String(bytesArr));
// 获取与之前转换的16进制字符串对应的字节码数组
console.log(ethers.getBytes(hexStr));

// 对字节码数组进行SHA-256哈希计算
const hash1 = ethers.keccak256(bytesArr);
console.log(hash1);

# Hash

# keccak256

const str = "Hello";
// 将字符串转换为UTF-8字节码数组
const bytesArr = ethers.toUtf8Bytes(str);
// 对字节码数组进行SHA-256哈希计算
const hash1 = ethers.keccak256(bytesArr);
console.log(hash1);

# ethers.id

const hash1 = ethers.id("Hello");
console.log(hash1);

# 获取插槽数据

合约代码

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.19;

contract SlotStringExample {
    
    //0x48656c6c6f48656c6c6f48656c6c6f48656c6c6f48656c6c6f48656c6c6f003c
    //0x48656c6c6f48656c6c6f48656c6c6f48656c6c6f48656c6c6f48656c6c6f303e
    //0x0000000000000000000000000000000000000000000000000000000000000043
    string private hello = "HelloHelloHelloHelloHelloHello012"; //0x00 slot
    
    //获取小于或等于32个字节插槽的数据
    function getBytes32BySlot(uint256 slot) public view returns (bytes32) {
        bytes32 valueBytes32;
        assembly {
            valueBytes32 := sload(slot)
        }
        return valueBytes32;
    }

    //获取大于32个字节的第一个插槽数据
    //0x48656c6c6f48656c6c6f48656c6c6f48656c6c6f48656c6c6f48656c6c6f3031
    function getFirstSlot(uint256 slot) public view returns (bytes32) {
        uint256 firstDataSlot = calculateBaseSlot(slot);
        bytes32 valueBytes32;
        assembly {
            valueBytes32 := sload(firstDataSlot)
        }
        return valueBytes32;
    }

    //获取大于32个字节的第二个插槽数据
    //0x3200000000000000000000000000000000000000000000000000000000000000
    function getSecondSlot(uint256 slot) public view returns (bytes32) {
        uint256 firstDataSlot = calculateBaseSlot(slot);
        ++firstDataSlot;
        bytes32 valueBytes32;
        assembly {
            valueBytes32 := sload(firstDataSlot)
        }
        return valueBytes32;
    }

     //计算插槽 
    function calculateBaseSlot(uint256 slot) internal pure returns (uint256) {
        //0x0000000000000000000000000000000000000000000000000000000000000000;
        bytes32 paddedSlot = bytes32(slot);
        bytes32 baseSlot = keccak256(abi.encodePacked(paddedSlot));
        uint256 iBaseSlot = uint256(baseSlot);
        return iBaseSlot;
    }

}

js代码


import { expect } from "chai";
import { ethers } from "hardhat";

describe("SlotStringExample", function () {
  it("slot_string", async function () {
    const provider = new ethers.providers.JsonRpcProvider();
    const SlotStringExample = await ethers.getContractFactory("SlotStringExample");
    const slotStringExample = await SlotStringExample.deploy();
    
    //大于32个字节的字符串
    const hello = await getGreaterThan32String(slotStringExample.address, 0x00);
    //小于或等于32个字节的字符串
    //const hello = await getLessEqual32String(slotStringExample.address, 0x00);
  });

  //右移1位,相当于除以2
  it("BigNumber shr ", async function () {
    const shr = ethers.BigNumber.from(16).shr(2).toNumber();
    console.log(" BigNumber shr  ->", shr);
  });

 
});

//大于32个字节的字符串
async function getGreaterThan32String(contractAddress: any, slot: any) {
  //把插槽转换成32字节的16进制,如: 0x0000000000000000000000000000000000000000000000000000000000000000
  const slot0Hex = ethers.utils.hexZeroPad(slot, 32);
  console.log("slot0Hex ->",slot0Hex);
  //获取字符串的长度
  const slot0DataLength = await ethers.provider.getStorageAt(contractAddress, slot0Hex);
  console.log("slot0DataLength ->",slot0DataLength);
  //真正储存数据的起始插槽
  const baseSlotData = ethers.utils.keccak256(slot0Hex);
  console.log("baseSlotData ->",baseSlotData);
  const dataByteLength = ethers.BigNumber.from(slot0DataLength).shr(1).toNumber();
  console.log("dataByteLength ->",dataByteLength);
  //计算字符串占用多少个插槽
  const totalSlots = Math.ceil(dataByteLength / 32);
  console.log("totalSlots ->",totalSlots);
  //将插槽转换成16进制
  let slotDataLocation = baseSlotData;
  console.log("slotDataLocation ->",slotDataLocation);
  let helloData = "";

  for (let i = 1; i <= totalSlots; i++) {
    //获取插槽里面的值
    const eachSlotData = await ethers.provider.getStorageAt(contractAddress, slotDataLocation);
    const eachSlotString = ethers.utils.toUtf8String(eachSlotData);
    console.log("eachSlotString ->",i,"->",eachSlotString);
    helloData = helloData.concat(eachSlotString);
    //获取连续插槽地址
    slotDataLocation = ethers.BigNumber.from(baseSlotData).add(i).toHexString();
  }

  console.log("helloData ->",helloData);
  //return str.replace(/\x00/g, '');
  return helloData;
}

//小于或等于32个字节的字符串
async function getLessEqual32String(contractAddress: any, slot: any) {
  //0x0000000000000000000000000000000000000000000000000000000000000000
  const slot0Hex = ethers.utils.hexZeroPad(slot, 32);
  const slot0Data = await ethers.provider.getStorageAt(contractAddress, slot0Hex);
  console.log("slot0Data ->",slot0Data);
  const slot0BigNumberData = ethers.BigNumber.from(slot0Data);
  console.log("slot0BigNumberData ->",slot0BigNumberData);
  const helloData = ethers.utils.toUtf8String(
    slot0BigNumberData.and(ethers.constants.MaxUint256.sub(255)).toHexString()
  );
 
  console.log("helloData ->",helloData);
  return helloData;
}

# Wallet

wallet 更多是在代码中直接给私钥,更多给脚本自动化使用

# 根据私钥创建钱包对象

/**
 * 根据私钥创建钱包对象。
 * @param {string} privateKey 私钥,用于创建钱包。
 * @return {Wallet} 返回创建的钱包对象。
 */
function createWalletByPrivateKey(privateKey, wprovider) {
  // 使用私钥创建Wallet实例
  const wallet = new Wallet(privateKey, wprovider);

  // 打印钱包地址和私钥
  console.log("address", wallet.address);
  console.log("privateKey", wallet.privateKey);

  return wallet;
}

# 连接到指定的区块链服务提供者

/**
 * 连接到指定的区块链服务提供者
 * @param {Object} wallet 钱包对象,支持连接到外部服务提供者
 * @param {Object} provider 区块链服务提供者,例如以太坊的Infura等
 * @returns {Object} 连接后的钱包对象,包含了服务提供者的信息
 */
function connectProvider(wallet, provider) {
  // 通过钱包对象的connect方法连接到提供的服务提供者
  let newWallet = wallet.connect(provider);
  // console.log("provider", newWallet.provider); // 打印连接成功后的服务提供者信息
  return newWallet; // 返回连接后的钱包对象
}

# 异步加密钱包函数

/**
 * 异步加密钱包函数
 * @param {Object} wallet 钱包对象,需要具有encrypt方法用于加密
 * @param {String} password 用于加密钱包的密码
 * @returns {Promise<Object>} 返回一个Promise对象,解析为加密后钱包对象
 */
async function encryptWallet(wallet, password) {
  // 使用钱包对象的encrypt方法,异步加密钱包
  const encryptedWallet = await wallet.encrypt(password);
  console.log("encryptedWallet", encryptedWallet);
  return encryptedWallet;
}

# 异步解密钱包函数

/**
 * 异步解密钱包函数
 * @param {string} encryptedWallet 加密的钱包对象字符串
 * @param {string} password 解密钱包所需的密码
 * @returns {Promise<Object>} 解密后的钱包对象
 */
async function decryptWallet(encryptedWallet, password) {
  // 使用Wallet.fromEncryptedJson方法异步解密钱包
  const wallet = await Wallet.fromEncryptedJson(encryptedWallet, password);
  // 打印解密后钱包的地址和私钥
  console.log("address", wallet.address);
  console.log("privateKey", wallet.privateKey);
  return wallet;
}

# 创建一个随机钱包

/**
 * 创建一个随机钱包
 * 该函数不接受任何参数,并返回一个包含地址、私钥和(可选)助记词的钱包对象。
 *
 * @returns {Object} 返回一个随机生成的钱包对象,包含地址、私钥和助记词(如果有的话)。
 */
async function createRandomWallet() {
  // 创建一个随机钱包
  const wallet = Wallet.createRandom();
  // 打印钱包地址和私钥
  console.log("address", wallet.address);
  console.log("privateKey", wallet.privateKey);
  // 如果存在助记词,则打印助记词
  if (wallet.mnemonic) {
    console.log("mnemonic", wallet.mnemonic);
    console.log("mnemonic phrase", wallet.mnemonic.phrase);
  }
  return wallet;
}

# 根据助记词创建钱包

/**
 * 根据助记词创建钱包
 * @param {string} mnemonic 助记词,用于创建钱包
 * @returns {Wallet} 返回创建的钱包实例
 */
async function createWalletFromMnemonic(mnemonic) {
  // 使用助记词创建钱包
  const wallet = Wallet.fromPhrase(mnemonic);
  // 打印钱包地址和私钥
  console.log("address", wallet.address);
  console.log("privateKey", wallet.privateKey);
  return wallet;
}

# 获取0x00靓号

// 获取0x00靓号
function isStartWithZero() {
  let address = "";
  do {
    let wallet = Wallet.createRandom();
    address = wallet.address;
  } while (!address.startsWith("0x00"));
  console.log("address", address);
}

# 使用指定的钱包对消息进行个人签名

ethers的签名与验签纯本地实现,一些弯弯绕绕的知识点参考这里

/**
 * 使用指定的钱包对消息进行个人签名
 * @param {Object} wallet - 钱包对象,必须具有 signMessage 方法用于签名。
 * @param {String} message - 需要签名的消息。
 * @returns {Promise<String>} 返回一个承诺(Promise),该承诺解析为签名的结果。
 */
async function personalSign(wallet, message) {
  // 使用钱包对象的 signMessage 方法对消息进行签名,并等待签名过程完成
  const signature = await wallet.signMessage(message);
  console.log("signature", signature);
  return signature;
}

# 异步验证消息签名函数

/**
 * 异步验证消息签名函数
 * @param {Object} walleet 钱包对象,用于比对签名者的地址
 * @param {string} message 被签名的消息
 * @param {string} signature 签名信息
 * @returns {string} 返回通过签名恢复的地址
 */
async function verifySignature(walleet, message, signature) {
  // 使用ethers.js库验证消息和签名,并恢复签名者的地址
  const recoveredAddress = await ethers.verifyMessage(message, signature);
  console.log("recoveredAddress", recoveredAddress);

  // 获取签名者的地址进行比对
  const signerAddress = walleet.address;

  // 比对恢复的地址和签名者的地址是否一致
  if (recoveredAddress !== signerAddress) {
    console.log("签名验证失败");
  } else {
    console.log("签名验证成功");
  }

  return recoveredAddress;
}

# 异步发送以太坊交易

/**
 * 异步发送以太坊交易
 * @param {Object} wallet 钱包对象,用于发送交易
 * @param {string} from 发起方地址
 * @param {string} to 接收方地址
 * @param {string} amount 转账金额(以太坊)
 * @returns {Promise<void>} 不返回任何内容,但会在控制台打印交易信息
 */
async function sendTransaction(wallet, from, to, amount) {
  // 将转账金额转换为wei单位,以适应以太坊网络的交易单位要求
  const weiAmount = ethers.parseUnits(amount + "");

  // 创建一个简单的memo,将其编码为hex,通常用于附带简单信息或指令
  const memo = ethers.hexlify(ethers.toUtf8Bytes("Hello"));

  // 准备交易对象,包含交易的基本信息
  let tx = {
    from: from,
    to: to,
    value: weiAmount,
    data: memo, // 包含memo数据
  };

  // 发送交易并等待其完成,然后打印交易信息
  let resTx = await wallet.sendTransaction(tx);
  console.log(resTx);
}

# 异步签名交易函数

/**
 * 异步签名交易函数
 * @param {Object} wallet 用于签名交易的钱包对象。
 * @param {string} from 起始地址,交易发起方的地址。
 * @param {string} to 目标地址,接收方的地址。
 * @param {string} amount 转账金额,以太坊单位。
 * @returns {Promise<string>} 返回交易的签名信息。
 */
async function signTransaction(wallet, from, to, amount) {
  //send的底层就是先sign再broadcast,所以别自己折腾了,直接send吧
  // 将金额转换为wei单位
  const weiAmount = ethers.parseUnits(amount + "");

  // 创建并编码memo,用于交易中携带简单信息
  const memo = ethers.hexlify(ethers.toUtf8Bytes("Hello"));

  let txCount = await provider.getTransactionCount(from);
  // 准备交易信息
  let tx = {
    from: from,
    to: to,
    value: weiAmount,
    chainId: (await provider.getNetwork()).chainId, //这个很重要,wallet.signTransaction()必须要chainId
    data: memo, // 包含memo数据
    nonce: txCount + 1, // 这个也必须要
  };
  // 获取当前网络的Gas费用信息,并设置到交易对象中
  // tx.maxPriorityFeePerGas = ethers.parseUnits(1 + "", "gwei"); // 小费给1gwei
  // let baseFeePerGas = (await provider.getBlock(await provider.getBlockNumber()))
  //   .baseFeePerGas;
  // console.log("baseFeePerGas", baseFeePerGas);
  // tx.maxFeePerGas = baseFeePerGas * BigInt(Math.floor(Math.pow(1.25, 4))); // 基础费*(1.25^4) 往后至少推算4个区块
  let feeData = await provider.getFeeData();
  tx.maxFeePerGas = feeData.maxFeePerGas;
  tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
  const gas = await provider.estimateGas(tx);
  tx.gasLimit = gas;

  // // 下面的代码块用于处理特定版本的以太坊钱包签名问题,可能在新版本中不需要
  // const pop = await wallet.populateTransaction(tx);
  // delete pop.from;
  // const tx1 = Transaction.from(pop);

  // 签名交易
  const sig = await wallet.signTransaction(tx); //总结,这种方式,tx该有的参数基本都要有,不然会报错
  console.log("sig", sig);
  return sig;
}

# 异步广播交易

/**
 * 异步广播交易。
 * @param {string} signedTx 已签名的交易数据,准备发送到网络进行确认。
 * @returns {Promise<object>} 返回一个承诺(Promise),解决为交易的详细信息对象。
 */
async function broadcastTransaction(signedTx) {
  // 通过provider发送已签名的交易
  const tx = await provider.broadcastTransaction(signedTx);
  console.log("tx", tx);
  return tx;
}

# 完整代码

import { ethers, Transaction, Wallet } from "ethers";

const mykey =
  "";

// const RPC_URL =
//   "";

const RPC_URL =
  "";

// const RPC_URL = "http://localhost:7545";

const myEncryptedKey = {
  address: "",
  id: "",
  version: 3,
  Crypto: {
    cipher: "aes-128-ctr",
    cipherparams: { iv: "" },
    ciphertext:
      "",
    kdf: "scrypt",
    kdfparams: {
      salt: "",
      n: 131072,
      dklen: 32,
      p: 1,
      r: 8,
    },
    mac: "",
  },
};
const mymnemonic =
  "";
const provider = new ethers.JsonRpcProvider(RPC_URL);
async function main() {
  console.log((await provider.getNetwork()).chainId);
  let wallet = createWalletByPrivateKey(mykey);
  let newWallet = connectProvider(wallet, provider);
  let balance = await newWallet.provider.getBalance(newWallet.address);
  console.log("balance", ethers.formatEther(balance));
  //   encryptWallet(wallet, "rje187600");
  // 通过加密json获得钱包对象
  // let wallet = await decryptWallet(JSON.stringify(myEncryptedKey), "rje187600");
  // createRandomWallet();
  // createWalletFromMnemonic(mymnemonic);
  // isStartWithZero();
  // let sig = await personalSign(newWallet, "Hello");
  // verifySignature(newWallet, "Hello", sig);
  sendTransaction(
    newWallet,
    newWallet.address,
    "to",
    1.999
  );
  // let tx = await signTransaction(
  //   newWallet,
  //   newWallet.address,
  //   "to",
  //   2
  // );
  // broadcastTransaction(tx);
}

/**
 * 根据私钥创建钱包对象。
 * @param {string} privateKey 私钥,用于创建钱包。
 * @return {Wallet} 返回创建的钱包对象。
 */
function createWalletByPrivateKey(privateKey, wprovider) {
  // 使用私钥创建Wallet实例
  const wallet = new Wallet(privateKey, wprovider);

  // 打印钱包地址和私钥
  console.log("address", wallet.address);
  console.log("privateKey", wallet.privateKey);

  return wallet;
}

/**
 * 连接到指定的区块链服务提供者
 * @param {Object} wallet 钱包对象,支持连接到外部服务提供者
 * @param {Object} provider 区块链服务提供者,例如以太坊的Infura等
 * @returns {Object} 连接后的钱包对象,包含了服务提供者的信息
 */
function connectProvider(wallet, provider) {
  // 通过钱包对象的connect方法连接到提供的服务提供者
  let newWallet = wallet.connect(provider);
  // console.log("provider", newWallet.provider); // 打印连接成功后的服务提供者信息
  return newWallet; // 返回连接后的钱包对象
}

/**
 * 异步加密钱包函数
 * @param {Object} wallet 钱包对象,需要具有encrypt方法用于加密
 * @param {String} password 用于加密钱包的密码
 * @returns {Promise<Object>} 返回一个Promise对象,解析为加密后钱包对象
 */
async function encryptWallet(wallet, password) {
  // 使用钱包对象的encrypt方法,异步加密钱包
  const encryptedWallet = await wallet.encrypt(password);
  console.log("encryptedWallet", encryptedWallet);
  return encryptedWallet;
}

/**
 * 异步解密钱包函数
 * @param {string} encryptedWallet 加密的钱包对象字符串
 * @param {string} password 解密钱包所需的密码
 * @returns {Promise<Object>} 解密后的钱包对象
 */
async function decryptWallet(encryptedWallet, password) {
  // 使用Wallet.fromEncryptedJson方法异步解密钱包
  const wallet = await Wallet.fromEncryptedJson(encryptedWallet, password);
  // 打印解密后钱包的地址和私钥
  console.log("address", wallet.address);
  console.log("privateKey", wallet.privateKey);
  return wallet;
}

/**
 * 创建一个随机钱包
 * 该函数不接受任何参数,并返回一个包含地址、私钥和(可选)助记词的钱包对象。
 *
 * @returns {Object} 返回一个随机生成的钱包对象,包含地址、私钥和助记词(如果有的话)。
 */
async function createRandomWallet() {
  // 创建一个随机钱包
  const wallet = Wallet.createRandom();
  // 打印钱包地址和私钥
  console.log("address", wallet.address);
  console.log("privateKey", wallet.privateKey);
  // 如果存在助记词,则打印助记词
  if (wallet.mnemonic) {
    console.log("mnemonic", wallet.mnemonic);
    console.log("mnemonic phrase", wallet.mnemonic.phrase);
  }
  return wallet;
}

/**
 * 根据助记词创建钱包
 * @param {string} mnemonic 助记词,用于创建钱包
 * @returns {Wallet} 返回创建的钱包实例
 */
async function createWalletFromMnemonic(mnemonic) {
  // 使用助记词创建钱包
  const wallet = Wallet.fromPhrase(mnemonic);
  // 打印钱包地址和私钥
  console.log("address", wallet.address);
  console.log("privateKey", wallet.privateKey);
  return wallet;
}

// 获取0x00靓号
function isStartWithZero() {
  let address = "";
  do {
    let wallet = Wallet.createRandom();
    address = wallet.address;
  } while (!address.startsWith("0x00"));
  console.log("address", address);
}

/**
 * 使用指定的钱包对消息进行个人签名
 * @param {Object} wallet - 钱包对象,必须具有 signMessage 方法用于签名。
 * @param {String} message - 需要签名的消息。
 * @returns {Promise<String>} 返回一个承诺(Promise),该承诺解析为签名的结果。
 */
async function personalSign(wallet, message) {
  // 使用钱包对象的 signMessage 方法对消息进行签名,并等待签名过程完成
  const signature = await wallet.signMessage(message);
  console.log("signature", signature);
  return signature;
}

/**
 * 异步验证消息签名函数
 * @param {Object} walleet 钱包对象,用于比对签名者的地址
 * @param {string} message 被签名的消息
 * @param {string} signature 签名信息
 * @returns {string} 返回通过签名恢复的地址
 */
async function verifySignature(walleet, message, signature) {
  // 使用ethers.js库验证消息和签名,并恢复签名者的地址
  const recoveredAddress = await ethers.verifyMessage(message, signature);
  console.log("recoveredAddress", recoveredAddress);

  // 获取签名者的地址进行比对
  const signerAddress = walleet.address;

  // 比对恢复的地址和签名者的地址是否一致
  if (recoveredAddress !== signerAddress) {
    console.log("签名验证失败");
  } else {
    console.log("签名验证成功");
  }

  return recoveredAddress;
}

/**
 * 异步发送以太坊交易
 * @param {Object} wallet 钱包对象,用于发送交易
 * @param {string} from 发起方地址
 * @param {string} to 接收方地址
 * @param {string} amount 转账金额(以太坊)
 * @returns {Promise<void>} 不返回任何内容,但会在控制台打印交易信息
 */
async function sendTransaction(wallet, from, to, amount) {
  // 将转账金额转换为wei单位,以适应以太坊网络的交易单位要求
  const weiAmount = ethers.parseUnits(amount + "");

  // 创建一个简单的memo,将其编码为hex,通常用于附带简单信息或指令
  const memo = ethers.hexlify(ethers.toUtf8Bytes("Hello"));

  // 准备交易对象,包含交易的基本信息
  let tx = {
    from: from,
    to: to,
    value: weiAmount,
    data: memo, // 包含memo数据
  };

  // 发送交易并等待其完成,然后打印交易信息
  let resTx = await wallet.sendTransaction(tx);
  console.log(resTx);
}

/**
 * 异步签名交易函数
 * @param {Object} wallet 用于签名交易的钱包对象。
 * @param {string} from 起始地址,交易发起方的地址。
 * @param {string} to 目标地址,接收方的地址。
 * @param {string} amount 转账金额,以太坊单位。
 * @returns {Promise<string>} 返回交易的签名信息。
 */
async function signTransaction(wallet, from, to, amount) {
  //send的底层就是先sign再broadcast,所以别自己折腾了,直接send吧
  // 将金额转换为wei单位
  const weiAmount = ethers.parseUnits(amount + "");

  // 创建并编码memo,用于交易中携带简单信息
  const memo = ethers.hexlify(ethers.toUtf8Bytes("Hello"));

  let txCount = await provider.getTransactionCount(from);
  // 准备交易信息
  let tx = {
    from: from,
    to: to,
    value: weiAmount,
    chainId: (await provider.getNetwork()).chainId, //这个很重要,wallet.signTransaction()必须要chainId
    data: memo, // 包含memo数据
    nonce: txCount + 1, // 这个也必须要
  };
  // 获取当前网络的Gas费用信息,并设置到交易对象中
  // tx.maxPriorityFeePerGas = ethers.parseUnits(1 + "", "gwei"); // 小费给1gwei
  // let baseFeePerGas = (await provider.getBlock(await provider.getBlockNumber()))
  //   .baseFeePerGas;
  // console.log("baseFeePerGas", baseFeePerGas);
  // tx.maxFeePerGas = baseFeePerGas * BigInt(Math.floor(Math.pow(1.25, 4))); // 基础费*(1.25^4) 往后至少推算4个区块
  let feeData = await provider.getFeeData();
  tx.maxFeePerGas = feeData.maxFeePerGas;
  tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
  const gas = await provider.estimateGas(tx);
  tx.gasLimit = gas;

  // // 下面的代码块用于处理特定版本的以太坊钱包签名问题,可能在新版本中不需要
  // const pop = await wallet.populateTransaction(tx);
  // delete pop.from;
  // const tx1 = Transaction.from(pop);

  // 签名交易
  const sig = await wallet.signTransaction(tx); //总结,这种方式,tx该有的参数基本都要有,不然会报错
  console.log("sig", sig);
  return sig;
}

/**
 * 异步广播交易。
 * @param {string} signedTx 已签名的交易数据,准备发送到网络进行确认。
 * @returns {Promise<object>} 返回一个承诺(Promise),解决为交易的详细信息对象。
 */
async function broadcastTransaction(signedTx) {
  // 通过provider发送已签名的交易
  const tx = await provider.broadcastTransaction(signedTx);
  console.log("tx", tx);
  return tx;
}

main();

# Contract

# 创建并返回一个与智能合约交互的ethers.Contract实例。

/**
 * 创建并返回一个与智能合约交互的ethers.Contract实例。
 *
 * @合同地址 {string} contractAddress - 智能合约的地址。
 * @ABI {array} abi1 - 智能合约的ABI(应用程序二进制接口)。
 * @钱包 {object} wallet - 用于与智能合约交互的钱包对象。
 * @返回 {ethers.Contract} - 返回一个配置好的ethers.Contract实例,可用于调用智能合约的功能。
 */
function getContract() {
  // 使用提供的合约地址、ABI和钱包创建一个新的ethers.Contract实例
  const contract = new ethers.Contract(contractAddress, abi1, wallet);
  return contract;
}

# abi形式有四种

// abi形式有四种
function abiFormat() {
  // 1.导入编译后的json文件,取里面的abi节点内容
  // 2.自己建一个数组,把方法除去方法体,把名称原封不动拷过来就行
  const abi2 = [
    "function greet() public view returns (string memory)",
    "function setGreeting(string memory _greeting) public",
  ];
  // 3和4都借助ethers.Interface,取决于一个参数传true或false
  const iface = new ethers.Interface(GreeterABI.abi);
  const abi3 = iface.format(true);
  const abi4 = iface.format(false);
  console.log(abi3);
  console.log(abi4);
}

# 部署合约

/**
 * 异步部署智能合约到以太坊网络。
 * @param {Object[]} contractAbi 合约的ABI(应用二进制接口),用于与智能合约进行交互。
 * @param {string} contractByteCode 合约的字节码,是编译后的合约代码。
 * @param {Object} myWallet 用户的钱包对象,用于签名交易和支付 gas 费用。
 * @param {...any} params 合约初始化时需要传递的参数。
 * @returns {Object} 返回部署后的合约实例。
 */
async function deployContract(
  contractAbi,
  contractByteCode,
  myWallet,
  ...params
) {
  // 创建合约工厂,用于生成合约实例
  const factory = new ethers.ContractFactory(
    contractAbi,
    contractByteCode,
    myWallet
  );

  // 使用合约工厂部署合约,并传入初始化参数
  const contract_by_factory = await factory.deploy(...params);

  // 打印部署后的合约地址
  console.log("contractAddress", await contract_by_factory.getAddress());

  // 获取部署交易的信息
  const tx = await contract_by_factory.deploymentTransaction();
  console.log("tx", tx);

  // 等待部署交易被确认上链
  const receipt = await tx.wait();
  console.log("receipt", receipt);

  // 返回部署后的合约实例
  return contract_by_factory;
}

# 更换合约账户

/**
 * 异步函数,用于更换合约的账户。
 * @param {Object} contract 需要进行账户更换的合约对象。
 * @param {string} newkey 新账户的私钥。
 * 该函数没有返回值。
 */
async function changeAccount(contract, newkey) {
  // 使用新的私钥创建一个 ethers.Wallet 对象
  const newWallet = new ethers.Wallet(newkey, provider);
  // 获取新钱包地址的余额并打印
  console.log(await provider.getBalance(newWallet.address));
  // 连接到合约并使用新钱包
  const newcontract = contract.connect(newWallet);
  // 调用合约的 greet 方法,并打印结果
  newcontract.greet().then((result) => {
    console.log(result);
  });
}

# 更换合约地址

/**
 * 通过attach方式更改智能合约的地址-前提是合约的网络必须相同
 * @param {Object} contract 原始合约对象
 * @param {string} newAddress 新的合约地址
 * @returns {Object} 返回新合约对象,该对象与原始合约关联但地址已更新
 */
function changeContractAddress(contract, newAddress) {
  // 通过新地址关联原始合约,创建新的合约实例
  const newContract = contract.attach(newAddress);
  return newContract;
}

# 更换provider

/**
 * 更改网络配置并初始化智能合约实例
 * @param {string} newRpcUrl 新的RPC服务URL,用于更换网络提供者
 * @param {string} contractAddress 智能合约的地址,用于实例化合约
 * @param {string} neWalletKey 钱包密钥,用于创建钱包实例
 * @returns {Object} 返回新实例化的智能合约对象
 */
function changeNetWork(newRpcUrl, contractAddress, neWalletKey) {
  // 创建一个新的JSON-RPC提供者实例,用于连接到指定的网络
  const newProvider = new ethers.JsonRpcProvider(newRpcUrl);
  // 使用提供的密钥和网络提供者创建一个新的钱包实例
  const newWallet = new ethers.Wallet(neWalletKey, newProvider);
  // 实例化一个新的智能合约对象,准备进行合约交互
  const newContract = new ethers.Contract(
    contractAddress,
    GreeterABI.abi, // 假设GreeterABI是合约ABI的定义
    newWallet
  );
  return newContract;
}

# 评估Gas

/**
 * 异步估计合约中setGreeting函数调用的gas消耗。
 * @param {Object} contract 合约对象,应包含getFunction和estimateGas方法。
 * @returns {Promise} 无返回值,但会在控制台打印出gas消耗量。
 */
async function estimateGas(contract) {
  // 调用合约的setGreeting函数,传入字符串参数"Hello, World111!",并预估gas消耗
  const gas = await contract
    .getFunction("setGreeting(string)")
    .estimateGas("Hello, World111!");
  console.log(gas);
}

# 执行合约方法

/**
 * 异步执行合约操作。
 * @param {Object} contract - 需要执行的智能合约对象。
 * 此函数无返回值。
 */
async function excuteContract(contract) {
  // 设置合约的问候语并打印交易信息
  const tx = await contract.setGreeting("Hello, World111!");
  // 可以动态调整gas费参数
  // const tx = await contract.setGreeting("Hello, World111!", {
  //    gasLimit: eGas,
  //    maxFeePerGas: ethers.parseUnits('5', 'gwei'),
  //    maxPriorityFeePerGas: ethers.parseUnits('0.000000001', 'gwei'),
  //  });
  console.log(tx);
  // 等待交易完成并打印收据
  const receipt = await tx.wait();
  console.log(receipt);
}

# 覆盖Pending交易

/**
 * 异步函数,用于覆盖处于pending状态的交易。
 * 有些交易可能因为给的小费过低而一直未被确认,此函数重新构造并发送一个交易,以相同的nonce值确保交易的唯一性。
 * @param {string} txHash 原始交易的哈希值,用于查询该交易的状态。
 * @returns {undefined} 若未找到待处理的交易,则函数不返回任何内容。
 */
async function coverTransaction(txHash) {
  // 尝试获取指定交易信息
  const pendingTx = await provider.getTransaction(txHash);
  // 如果交易不存在,则直接返回
  if (!pendingTx) {
    return;
  }
  // 获取当前网络的费用信息
  let feeData = await provider.getFeeData();
  // 构造并发送新的交易,采用较高的费用以期望交易能被更快确认
  const newTx = await wallet.sendTransaction({
    from: pendingTx.from, // 原始交易的发送地址
    to: pendingTx.to, // 原始交易的接收地址
    value: pendingTx.value, // 原始交易的价值
    data: pendingTx.data, // 原始交易的数据部分
    nonce: pendingTx.nonce, // 使用与原始交易相同的nonce值
    maxFeePerGas: feeData.maxFeePerGas, // 最高gas费用
    maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, // 最高优先级gas费用
  });
  // 等待新交易被确认
  await newTx.wait();
}

# 监听合约

/**
 * 监听指定合约的特定事件。
 * @param {Object} contract 合约对象,必须具有监听事件的方法 on。
 * @param {String} eventName 需要监听的事件名称。
 * 当事件触发时,会打印出事件触发时传递的所有参数。
 */
function onContract(contract, eventName) {
  console.log("开始监听事件", eventName);
  // 注册事件监听器,当事件发生时,打印出所有参数
  contract.on(eventName, (...params) => {
    console.log(...params);

    //监听到一次就取消监听,这里主要为了测试取消
    offContract(contract, eventName);
  });
}

/**
 * 移除合约的特定事件监听器。
 * @param {Object} contract - 合约对象,需要具有 off 方法用于移除事件监听器。
 * @param {String} eventName - 要移除的事件名称。
 * 该函数没有返回值。
 */
function offContract(contract, eventName) {
  console.log("结束监听事件", eventName);
  contract.off(eventName); // 移除合约上的特定事件监听器
}

# Function形式调用合约方法

/**
 * 根据指定函数名和调用类型,异步调用智能合约中的函数。
 * @param {Object} contract 智能合约对象,用于调用合约中的函数。
 * @param {String} functionName 需要调用的合约函数名。
 * @param {String} functionType 调用类型,决定是静态调用(staticCall)还是发送交易(send)。
 * @param {...any} params 传递给合约函数的参数。
 * @returns 返回合约函数调用的结果,具体类型依赖于调用方式(staticCall 或 send)。
 */
async function callContractByFunction(
  contract,
  functionName,
  functionType,
  ...params
) {
  // 根据调用类型选择不同的调用方法
  if (functionType === "staticCall") {
    // 执行静态调用
    return await contract.getFunction(functionName).staticCall(...params);
  } else if (functionType === "send") {
    // 执行发送交易调用
    return await contract.getFunction(functionName).send(...params);
  }
}
await callContractByFunction(
    token,
    "transfer",
    "send",
    "0xE001c1DADA0BE9356bAE0120413F3C9D01eD3a50",
    ethers.parseUnits(10 + "", "ether")
);
console.log(
    await callContractByFunction(
      token,
      "balanceOf",
      "staticCall",
      "0xE001c1DADA0BE9356bAE0120413F3C9D01eD3a50"
    )
);

# 伪造签名

通过任意钱包地址伪造签名者,该功能只适用于hardhat 本地节点网络

/**
 * 异步函数,用于模拟指定账户的签名。
 *
 * @param {string} aimAccount 需要模拟签名的目标账户地址。
 * @returns {Promise<ethers.Signer>} 返回一个承诺(Promise),该承诺解析为模拟签名者的对象。
 */
async function impersonatedSomeAccount(aimAccount) {
  // 获取模拟目标账户的签名者
  const impersonatedSigner = await ethers.getImpersonatedSigner(aimAccount);
  return impersonatedSigner;
}

# Contract 查询事件日志

建议先学习RPCFilter日志过滤

  • Filter,只能查询该合约里面的事件日志
  • Topics,根据topics组合查询事件日志
/**
 * 通过事件名称查询合约事件
 * @param {Object} myContract 合约对象,用于执行查询操作
 * @param {Number} fromBlock 起始区块号
 * @param {Number} toBlock 终止区块号
 * @param {String} eventName 事件名称,用于过滤查询结果
 * @returns {Promise<Object[]>} 返回一个Promise,解析为符合条件的事件对象数组
 */
async function queryFilterByEvent(myContract, fromBlock, toBlock, eventName) {
  // 使用合约的queryFilter方法查询指定事件在指定区块范围内的发生情况
  const eve = await myContract.queryFilter(
    eventName,
    "0x" + fromBlock.toString(16), // 将区块号转换为16进制字符串格式,作为查询参数
    "0x" + toBlock.toString(16)
  );
  console.log(JSON.stringify(eve, null, 2)); // 格式化输出查询结果
  return eve;
}

/**
 * 根据指定的主题(topics)查询智能合约的事件日志。
 * @param {Object} myContract 智能合约对象,用于执行查询操作。
 * @param {Number} fromBlock 起始区块号,查询的起始位置。
 * @param {Number} toBlock 终止区块号,查询的结束位置。
 * @param {Array} topics 主题数组,用于过滤查询结果。
 * @returns {Promise<Array>} 返回查询到的事件日志数组。
 */
async function queryFilterByTopics(myContract, fromBlock, toBlock, topics) {
  // 使用提供的智能合约对象、起始区块号和终止区块号,以及主题数组进行查询
  const eve = await myContract.queryFilter(
    topics,
    "0x" + fromBlock.toString(16), // 将起始区块号转换为16进制字符串
    "0x" + toBlock.toString(16) // 将终止区块号转换为16进制字符串
  );
  console.log(JSON.stringify(eve, null, 2)); // 格式化并打印查询结果
  return eve; // 返回查询结果
}

function mian(){
  queryFilterByEvent(usdt, 19666610, 19666610, "Transfer");
  queryFilterByTopics(usdt, 19666610, 19666610, [
    null,
    "0x000000000000000000000000879639d1018b787b95380224dd0b63739f4567ab",
  ]);
}

# 完整代码

import { ethers } from "ethers";
//abi
import GreeterABI from "./Greeter.json" assert { "type": "json" };

// provider
const RPC_URL =
  "";
const provider = new ethers.JsonRpcProvider(RPC_URL);
// wallet
const mykey =
  "";
const wallet = new ethers.Wallet(mykey, provider);

const address = wallet.address;
console.log(address);
let balance = await provider.getBalance(address);
console.log(ethers.formatUnits(balance, "ether"));

const abi1 = GreeterABI.abi;
const contractAddress = "";

/**
 * 创建并返回一个与智能合约交互的ethers.Contract实例。
 *
 * @合同地址 {string} contractAddress - 智能合约的地址。
 * @ABI {array} abi1 - 智能合约的ABI(应用程序二进制接口)。
 * @钱包 {object} wallet - 用于与智能合约交互的钱包对象。
 * @返回 {ethers.Contract} - 返回一个配置好的ethers.Contract实例,可用于调用智能合约的功能。
 */
function getContract() {
  // 使用提供的合约地址、ABI和钱包创建一个新的ethers.Contract实例
  const contract = new ethers.Contract(contractAddress, abi1, wallet);
  return contract;
}

// abi形式有四种
function abiFormat() {
  // 1.导入编译后的json文件,取里面的abi节点内容
  // 2.自己建一个数组,把方法除去方法体,把名称原封不动拷过来就行
  const abi2 = [
    "function greet() public view returns (string memory)",
    "function setGreeting(string memory _greeting) public",
  ];
  // 3和4都借助ethers.Interface,取决于一个参数传true或false
  const iface = new ethers.Interface(GreeterABI.abi);
  const abi3 = iface.format(true);
  const abi4 = iface.format(false);
  console.log(abi3);
  console.log(abi4);
}

/**
 * 异步部署智能合约到以太坊网络。
 * @param {Object[]} contractAbi 合约的ABI(应用二进制接口),用于与智能合约进行交互。
 * @param {string} contractByteCode 合约的字节码,是编译后的合约代码。
 * @param {Object} myWallet 用户的钱包对象,用于签名交易和支付 gas 费用。
 * @param {...any} params 合约初始化时需要传递的参数。
 * @returns {Object} 返回部署后的合约实例。
 */
async function deployContract(
  contractAbi,
  contractByteCode,
  myWallet,
  ...params
) {
  // 创建合约工厂,用于生成合约实例
  const factory = new ethers.ContractFactory(
    contractAbi,
    contractByteCode,
    myWallet
  );

  // 使用合约工厂部署合约,并传入初始化参数
  const contract_by_factory = await factory.deploy(...params);

  // 打印部署后的合约地址
  console.log("contractAddress", await contract_by_factory.getAddress());

  // 获取部署交易的信息
  const tx = await contract_by_factory.deploymentTransaction();
  console.log("tx", tx);

  // 等待部署交易被确认上链
  const receipt = await tx.wait();
  console.log("receipt", receipt);

  // 返回部署后的合约实例
  return contract_by_factory;
}

/**
 * 异步函数,用于更换合约的账户。
 * @param {Object} contract 需要进行账户更换的合约对象。
 * @param {string} newkey 新账户的私钥。
 * 该函数没有返回值。
 */
async function changeAccount(contract, newkey) {
  // 使用新的私钥创建一个 ethers.Wallet 对象
  const newWallet = new ethers.Wallet(newkey, provider);
  // 获取新钱包地址的余额并打印
  console.log(await provider.getBalance(newWallet.address));
  // 连接到合约并使用新钱包
  const newcontract = contract.connect(newWallet);
  // 调用合约的 greet 方法,并打印结果
  newcontract.greet().then((result) => {
    console.log(result);
  });
}

/**
 * 通过attach方式更改智能合约的地址-前提是合约的网络必须相同
 * @param {Object} contract 原始合约对象
 * @param {string} newAddress 新的合约地址
 * @returns {Object} 返回新合约对象,该对象与原始合约关联但地址已更新
 */
function changeContractAddress(contract, newAddress) {
  // 通过新地址关联原始合约,创建新的合约实例
  const newContract = contract.attach(newAddress);
  return newContract;
}

/**
 * 更改网络配置并初始化智能合约实例
 * @param {string} newRpcUrl 新的RPC服务URL,用于更换网络提供者
 * @param {string} contractAddress 智能合约的地址,用于实例化合约
 * @param {string} neWalletKey 钱包密钥,用于创建钱包实例
 * @returns {Object} 返回新实例化的智能合约对象
 */
function changeNetWork(newRpcUrl, contractAddress, neWalletKey) {
  // 创建一个新的JSON-RPC提供者实例,用于连接到指定的网络
  const newProvider = new ethers.JsonRpcProvider(newRpcUrl);
  // 使用提供的密钥和网络提供者创建一个新的钱包实例
  const newWallet = new ethers.Wallet(neWalletKey, newProvider);
  // 实例化一个新的智能合约对象,准备进行合约交互
  const newContract = new ethers.Contract(
    contractAddress,
    GreeterABI.abi, // 假设GreeterABI是合约ABI的定义
    newWallet
  );
  return newContract;
}

/**
 * 异步估计合约中setGreeting函数调用的gas消耗。
 * @param {Object} contract 合约对象,应包含getFunction和estimateGas方法。
 * @returns {Promise} 无返回值,但会在控制台打印出gas消耗量。
 */
async function estimateGas(contract) {
  // 调用合约的setGreeting函数,传入字符串参数"Hello, World111!",并预估gas消耗
  const gas = await contract
    .getFunction("setGreeting(string)")
    .estimateGas("Hello, World111!");
  console.log(gas);
}

/**
 * 异步执行合约操作。
 * @param {Object} contract - 需要执行的智能合约对象。
 * 此函数无返回值。
 */
async function excuteContract(contract) {
  // 设置合约的问候语并打印交易信息
  const tx = await contract.setGreeting("Hello, World111!");
  // 可以动态调整gas费参数
  // const tx = await contract.setGreeting("Hello, World111!", {
  //    gasLimit: eGas,
  //    maxFeePerGas: ethers.parseUnits('5', 'gwei'),
  //    maxPriorityFeePerGas: ethers.parseUnits('0.000000001', 'gwei'),
  //  });
  console.log(tx);
  // 等待交易完成并打印收据
  const receipt = await tx.wait();
  console.log(receipt);
}

/**
 * 异步函数,用于覆盖处于pending状态的交易。
 * 有些交易可能因为给的小费过低而一直未被确认,此函数重新构造并发送一个交易,以相同的nonce值确保交易的唯一性。
 * @param {string} txHash 原始交易的哈希值,用于查询该交易的状态。
 * @returns {undefined} 若未找到待处理的交易,则函数不返回任何内容。
 */
async function coverTransaction(txHash) {
  // 尝试获取指定交易信息
  const pendingTx = await provider.getTransaction(txHash);
  // 如果交易不存在,则直接返回
  if (!pendingTx) {
    return;
  }
  // 获取当前网络的费用信息
  let feeData = await provider.getFeeData();
  // 构造并发送新的交易,采用较高的费用以期望交易能被更快确认
  const newTx = await wallet.sendTransaction({
    from: pendingTx.from, // 原始交易的发送地址
    to: pendingTx.to, // 原始交易的接收地址
    value: pendingTx.value, // 原始交易的价值
    data: pendingTx.data, // 原始交易的数据部分
    nonce: pendingTx.nonce, // 使用与原始交易相同的nonce值
    maxFeePerGas: feeData.maxFeePerGas, // 最高gas费用
    maxPriorityFeePerGas: feeData.maxPriorityFeePerGas, // 最高优先级gas费用
  });
  // 等待新交易被确认
  await newTx.wait();
}

/**
 * 监听指定合约的特定事件。
 * @param {Object} contract 合约对象,必须具有监听事件的方法 on。
 * @param {String} eventName 需要监听的事件名称。
 * 当事件触发时,会打印出事件触发时传递的所有参数。
 */
function onContract(contract, eventName) {
  console.log("开始监听事件", eventName);
  // 注册事件监听器,当事件发生时,打印出所有参数
  contract.on(eventName, (...params) => {
    console.log(...params);

    //监听到一次就取消监听,这里主要为了测试取消
    offContract(contract, eventName);
  });
}

/**
 * 移除合约的特定事件监听器。
 * @param {Object} contract - 合约对象,需要具有 off 方法用于移除事件监听器。
 * @param {String} eventName - 要移除的事件名称。
 * 该函数没有返回值。
 */
function offContract(contract, eventName) {
  console.log("结束监听事件", eventName);
  contract.off(eventName); // 移除合约上的特定事件监听器
}

/**
 * 根据指定函数名和调用类型,异步调用智能合约中的函数。
 * @param {Object} contract 智能合约对象,用于调用合约中的函数。
 * @param {String} functionName 需要调用的合约函数名。
 * @param {String} functionType 调用类型,决定是静态调用(staticCall)还是发送交易(send)。
 * @param {...any} params 传递给合约函数的参数。
 * @returns 返回合约函数调用的结果,具体类型依赖于调用方式(staticCall 或 send)。
 */
async function callContractByFunction(
  contract,
  functionName,
  functionType,
  ...params
) {
  // 根据调用类型选择不同的调用方法
  if (functionType === "staticCall") {
    // 执行静态调用
    return await contract.getFunction(functionName).staticCall(...params);
  } else if (functionType === "send") {
    // 执行发送交易调用
    return await contract.getFunction(functionName).send(...params);
  }
}

/**
 * 异步函数,用于模拟指定账户的签名。
 *
 * @param {string} aimAccount 需要模拟签名的目标账户地址。
 * @returns {Promise<ethers.Signer>} 返回一个承诺(Promise),该承诺解析为模拟签名者的对象。
 */
async function impersonatedSomeAccount(aimAccount) {
  // 获取模拟目标账户的签名者
  const impersonatedSigner = await ethers.getImpersonatedSigner(aimAccount);
  return impersonatedSigner;
}

/**
 * 通过事件名称查询合约事件
 * @param {Object} myContract 合约对象,用于执行查询操作
 * @param {Number} fromBlock 起始区块号
 * @param {Number} toBlock 终止区块号
 * @param {String} eventName 事件名称,用于过滤查询结果
 * @returns {Promise<Object[]>} 返回一个Promise,解析为符合条件的事件对象数组
 */
async function queryFilterByEvent(myContract, fromBlock, toBlock, eventName) {
  // 使用合约的queryFilter方法查询指定事件在指定区块范围内的发生情况
  const eve = await myContract.queryFilter(
    eventName,
    "0x" + fromBlock.toString(16), // 将区块号转换为16进制字符串格式,作为查询参数
    "0x" + toBlock.toString(16)
  );
  console.log(JSON.stringify(eve, null, 2)); // 格式化输出查询结果
  return eve;
}

/**
 * 根据指定的主题(topics)查询智能合约的事件日志。
 * @param {Object} myContract 智能合约对象,用于执行查询操作。
 * @param {Number} fromBlock 起始区块号,查询的起始位置。
 * @param {Number} toBlock 终止区块号,查询的结束位置。
 * @param {Array} topics 主题数组,用于过滤查询结果。
 * @returns {Promise<Array>} 返回查询到的事件日志数组。
 */
async function queryFilterByTopics(myContract, fromBlock, toBlock, topics) {
  // 使用提供的智能合约对象、起始区块号和终止区块号,以及主题数组进行查询
  const eve = await myContract.queryFilter(
    topics,
    "0x" + fromBlock.toString(16), // 将起始区块号转换为16进制字符串
    "0x" + toBlock.toString(16) // 将终止区块号转换为16进制字符串
  );
  console.log(JSON.stringify(eve, null, 2)); // 格式化并打印查询结果
  return eve; // 返回查询结果
}

async function main() {
  // 获取合约实例
  const contract = getContract();
  // 调用合约的greet函数并打印返回值
  contract.greet().then((result) => {
    console.log(result);
  });
  estimateGas(contract);
  excuteContract(contract);
  contract.greet().then((result) => {
    console.log(result);
  });
  //   abiFormat();
  //   deployContract();
  // changeAccount(
  //   contract,
  //   ""
  // );
    const token = await deployContract(
    GLDTokenABI.abi,
    GLDTokenABI.bytecode,
    wallet,
    ethers.parseUnits(10 ** 8 + "", "ether")
  );
  // console.log(await token.balanceOf(wallet.address));
  // 监听
  // onContract(token, "Transfer");
  // 给别人10个币
  // token.transfer(
  //   "0xE001c1DADA0BE9356bAE0120413F3C9D01eD3a50",
  //   ethers.parseUnits(10 + "", "ether")
  // );

  await callContractByFunction(
    token,
    "transfer",
    "send",
    "0xE001c1DADA0BE9356bAE0120413F3C9D01eD3a50",
    ethers.parseUnits(10 + "", "ether")
  );
  console.log(
    await callContractByFunction(
      token,
      "balanceOf",
      "staticCall",
      "0xE001c1DADA0BE9356bAE0120413F3C9D01eD3a50"
    )
  );
    
  queryFilterByEvent(usdt, 19666610, 19666610, "Transfer");
  queryFilterByTopics(usdt, 19666610, 19666610, [
    null,
    "0x000000000000000000000000879639d1018b787b95380224dd0b63739f4567ab",
  ]);
}

main();

# contract总结

target, 属性, 获取合约地址
interface, 属性, 处理ABI对象
runner,属性, 钱包/签名者
filters,属性, 查询日志过滤器条件
connect,方法, 切换钱包/签名者
attach,方法, 切换合约
getAddress,方法, 获取合约地址
getDeployedCode,方法, 获取合约部署字节码
waitForDeployment,方法, 等待部署完成
deploymentTransaction,方法, 部署合约
getFunction,方法, 获取合约里的方法
getEvent,方法, 获取合约里的事件
queryFilter,方法, 查询历史事件日志
on,方法, 实时监听某事件日志
once,方法, 只监听某事件日志1次
emit,方法, 获取某事件是否已经被触发
listenerCount,方法, 获取监听事件数量
off,方法, 删除监听事件

# Logs

这里不单指Event日志的查询,还有一些技巧,比如交易data分析,区块监听等

# 关于calldata,具体可参考这里

  1. 普通eth转账交易可以附带data
  2. data也可是calldata,用于合约方法调用,有固定生成规则
  3. 一笔交易的data,可以通过provider.getTransaction获取到交易返回值,有个子字段.data
  4. 可以利用decodeFunctionData方法解析calldata
import { ethers } from "ethers";
// 方法4字节逆向https://openchain.xyz/signatures
const RPC_URL =
  "";
const provider = new ethers.JsonRpcProvider(RPC_URL);

const abi = [
  "function swapETH(uint16 _dstChainId, address _refundAddress, bytes _toAddress, uint256 _amountLD, uint256 _minAmountLD)",
];
// 通过提供的provider异步获取指定hash的交易信息
let transaction = await provider.getTransaction(
"0x34c4f24f91275daa177674be7bd04bb58d9cc352da648380af8c9ec8dbce5a30"
);
console.log(transaction.data);
const iface = new ethers.Interface(abi);
//   Logs 解码方法数据 calldata
const decodeDataResult = iface.decodeFunctionData(
"swapETH",
transaction.data
);
console.log(decodeDataResult);
// Result(3) [
//   '0x41a2A45041f32396f3b9ac23b43cbadd4B8A0024',
//   '0x5B38Da6a701c568545dCfcB03FcB875f56beddC4',
//   100n
// ]

# 监听区块

provider.on("block", async (blockNumber) => {})

# 监听交易

其实就是监听区块,然后遍历区块里的所有交易

// 监听每个新块
provider.on("block", async (blockNumber) => {
    // 只能监听线上
    console.log(`New block number: ${blockNumber}`); // 输出新块号
    const block = await provider.getBlock(blockNumber); // 获取块详情

    // 遍历块中的每笔交易
    for (const transactionHash of block.transactions) {
      const tx = await provider.getTransaction(transactionHash); // 获取交易详情
    }
})

# 监听特定函数调用

就是遍历区块里的交易,然后取出方法id,和指定函数id对比,符合就打印出来

/**
 * 监听特定函数的调用
 * @param {string} funcStr 以字符串形式表示的函数签名
 * 监听以太坊网络上的每个新块,并检查块中的交易是否调用了指定的函数。
 * 不返回任何值,但会在控制台输出匹配的函数调用信息。
 */
async function listenOnFunc(funcStr) {
  // 从函数签名生成一个10字符长度的方法ID
  const methodId = ethers.id(funcStr).slice(0, 10);
  console.log(methodId);

  // 监听每个新块
  provider.on("block", async (blockNumber) => {
    // 只能监听线上
    console.log(`New block number: ${blockNumber}`); // 输出新块号
    const block = await provider.getBlock(blockNumber); // 获取块详情

    // 遍历块中的每笔交易
    for (const transactionHash of block.transactions) {
      const tx = await provider.getTransaction(transactionHash); // 获取交易详情
      const callMethodId = tx.data.slice(0, 10); // 从交易数据中提取方法ID

      // 检查交易是否调用了指定的函数
      if (methodId === callMethodId) {
        console.log(`callMethodId:${callMethodId}`); // 输出调用的方法ID
        const callData = tx.data; // 获取调用的数据
        console.log(`callData:${callData}`); // 输出调用的数据
      }
    }
  });
}
//调用
listenOnFunc("transfer(address,uint256)");

# 监听特定合约的事件

/**
 * 监听特定合约的事件。
 * @param {string} contractAddress 合约地址,默认为 "0xdac17f958d2ee523a2206206994597c13d831ec7"。
 * @param {Array} contractAbi 合约的ABI,默认为 ["event Transfer(address indexed, address indexed, uint256)"]。
 * @param {string} topicName 事件名称,默认为 "Transfer(address,address,uint256)"。
 */
async function listenEvent(
  contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7",
  contractAbi = ["event Transfer(address indexed, address indexed, uint256)"],
  topicName = "Transfer(address,address,uint256)"
) {
  // 创建过滤参数,用于监听特定合约和事件
  const filterParam = {
    address: contractAddress,
    topics: [ethers.id(topicName)], // 使用ethers.js的id方法处理主题名,可加入更多主题过滤
  };

  // 解析合约ABI,以便于后续解码事件日志
  const iface = new ethers.Interface(contractAbi);

  // 注册事件监听器,当监听到事件时,解码并打印事件信息
  provider.on(filterParam, (log) => {
    // 解码并打印事件日志,提供了更细粒度的事件解析控制
    console.log(iface.parseLog({ topics: log.topics, data: log.data }));
  });
}

listenEvent()

# 监听待处理的交易(Pending)

/**
 * 监听待处理的交易。
 * 此函数会监听区块链网络上新的待处理交易,并在接收到交易哈希时打印出来。
 * 进一步的,它会尝试获取该交易的详细信息并打印。
 *
 * @param {Object} provider - 提供监听功能的对象,必须支持 'on' 方法来注册事件监听器。
 */
async function listenPendingTransaction() {
  // 注册一个监听器,以便在有新的待处理交易时收到通知
  provider.on("pending", (txHash) => {
    console.log(txHash); // 打印接收到的交易哈希
    // 尝试获取交易的详细信息
    provider.getTransaction(txHash).then((tx) => {
      console.log(tx); // 打印交易的详细信息
    });
  });
}

# Logs 查询事件日志

具体原理参考Rpc-GetLogs

Logs,获取历史事件Event

  • BlockNumber,根据区块高度 + 合约 + Topics 查找
  • BlockHash,根据区块Hash + 合约 + Topics 查找
const RPC_URL1 = process.env.REACT_APP_RPC_URL1!;

const CONTRACT_ADDRESS = "0x3dee31B29e9899c55F4C8b33Ee6400E3BDd3C868";
const ABI = ['event Transfer(address indexed from, address indexed to, uint256 value)',];
const iface = new ethers.Interface(ABI);
//创建交易对事件topic
//注意,是uint256 不是uint
//0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
const topicTransfer = ethers.id("Transfer(address,address,uint256)");
const formAddress = AbiCoder.defaultAbiCoder().encode(["address"], ["0x22E1701aB03212c62C9404be2ddC53608CCf78aA"]);
//创建provider
const providerWeb3 = new ethers.JsonRpcProvider(RPC_URL1);

//创建交易对过滤器1
const filterTransfer1 = {
    //合约地址
    address: CONTRACT_ADDRESS,
    //开始区块,可为空,此时它默认查询最新的区块
    fromBlock: fromBlock,
    //结束区块,可为空
    toBlock: toBlock,
    topics: [topicTransfer, formAddress],
}

const logs = await providerWeb3.getLogs(filterTransfer1) as Array<Log>;
const eventTransfer = iface.getEvent("Transfer") as EventFragment;
//打印日志
logs.forEach((item => {
    const eventTransferResult = iface.decodeEventLog(eventTransfer, item.data, item.topics);
    const eventPrintln = ` blockHash -> ${item.blockHash} \n from -> ${eventTransferResult[0]} \n to -> ${eventTransferResult[1]} \n value -> ${eventTransferResult[2]} \n`;
    console.log(`eventPrintln : ${eventPrintln}`);
}));
const providerWeb3 = new ethers.JsonRpcProvider(RPC_URL1);
//创建交易对过滤器1
const filterTransfer2 = {
    //合约地址
    address: CONTRACT_ADDRESS,
    topics: [topicTransfer, formAddress],
    blockHash: hashBlock
}
const logs = await providerWeb3.getLogs(filterTransfer2) as Array<Log>;

# ☕ 请我喝咖啡

如果本文章对您有所帮助,不妨请作者我喝杯咖啡 :)

pay


# ☀️ 广告时间

现承接以下业务,欢迎大家支持:)

  • Web 2.0 & Web 3.0应用定制
  • Web 3.0专项脚本定制与优化
  • 数据爬虫需求快速响应
  • 网站/公众号/小程序一站式开发
  • 毕业设计与科研项目支持
  • 企业管理软件定制:ERP, MES, CRM, 进销存系统等

联系方式:

X:@motuoka

V:ck742931485

wx