🖐🏻 免责声明
本教程仅供学习交流使用,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,请各读者自觉遵守相关法律法规。
# 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
# 单位转换

# 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
# 解码
iface.decodeEventLog("Transfer3", res.data, res.topics)
//返回值
Result(3) [
'0x41a2A45041f32396f3b9ac23b43cbadd4B8A0024',
'0x5B38Da6a701c568545dCfcB03FcB875f56beddC4',
100n
]
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字符串0x48656c6c6f3.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,具体可参考这里
- 普通eth转账交易可以附带data
- data也可是calldata,用于合约方法调用,有固定生成规则
- 一笔交易的data,可以通过provider.getTransaction获取到交易返回值,有个子字段
.data - 可以利用
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>;

