读取Event事件,就是读取一些信息吧,不需要gas费,不需要和合约交互
读取Transfer事件
import dotenv from "dotenv";
import { ethers } from "ethers";
dotenv.config();
const provider = new ethers.JsonRpcProvider(process.env.INFURA_ID);
const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY, provider);
// WETH ABI,只包含我们关心的Transfer事件
const abiWETH = [
"event Transfer(address indexed from, address indexed to, uint256 value)",
"event Approval(address indexed owner, address indexed spender, uint256 value)"
];
// 测试网WETH地址
const addressWETH = '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14'
// 声明合约实例
const contract = new ethers.Contract(addressWETH, abiWETH, provider)
async function main() {
// 得到当前block
const block = await provider.getBlockNumber()
console.log(`当前区块高度: ${block}`);
console.log(`打印事件详情:`);
// 查询过去100个区块内的Transfer事件
const fromBlock = block - 100;
console.log(`查询区块范围: 从 ${fromBlock} 到 ${block}`);
try {
// 设置过滤器
const filter = contract.filters.Transfer();
// 查询过滤器内的事件
const transferEvents = await contract.queryFilter(filter, fromBlock, block);
// 打印事件详情
if (transferEvents.length > 0) {
console.log(`找到 ${transferEvents.length} 个 Transfer 事件`);
transferEvents.forEach((event, index) => {
console.log(`事件 ${index + 1}:`, event);
});
} else {
console.log('没有找到任何 Transfer 事件');
}
} catch (error) {
console.error('查询事件时发生错误:', error);
}
}
main();
监听Transfer事件
const WETHAddress = '0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14';
const abi = [
"event Transfer(address indexed from, address indexed to, uint256 value)"
];
// 生成WETH合约对象
const contractETH = new ethers.Contract(WETHAddress, abi, provider);
// 持续监听WETH合约
console.log("\n2. 利用contract.on(),持续监听Transfer事件");
contractETH.on('Transfer', (from, to, value) => {
console.log(
// 打印结果
`${from} -> ${to} ${ethers.formatUnits(value, 18)}`
);
});
创建事件过滤器
example:监听从币安交易所到USDT的合约转账事件
import dotenv from "dotenv";
import { ethers } from "ethers";
dotenv.config();
const provider = new ethers.JsonRpcProvider("https://rpc.ankr.com/eth");
// 合约地址
const addressUSDT = "0xdac17f958d2ee523a2206206994597c13d831ec7";
// 交易所地址
const accountBinance = "0x28C6c06298d514Db089934071355E5743bf21d60";
// 构建ABI
const abi = ["event Transfer(address indexed from, address indexed to, uint value)", "function balanceOf(address) public view returns(uint)"];
// 构建合约对象
const contractUSDT = new ethers.Contract(addressUSDT, abi, provider);
async function main() {
const balanceUSDT = await contractUSDT.balanceOf(accountBinance);
console.log(`USDT余额: ${ethers.formatUnits(balanceUSDT, 6)}\n`);
// 2. 创建过滤器,监听转移USDT进交易所
console.log("\n2. 创建过滤器,监听USDT转进交易所");
let filterBinanceIn = contractUSDT.filters.Transfer(null, accountBinance);
// console.log("过滤器详情:");
console.log(filterBinanceIn);
contractUSDT.on(filterBinanceIn, (res) => {
console.log("---------监听USDT进入交易所--------");
console.log(`${res.args[0]} -> ${res.args[1]} ${ethers.formatUnits(res.args[2], 6)}`);
});
}
main();
代码没问题,但是一到连接主网节点,就因为网络问题,程序运行失败,提供一个节点网络 chainlist.org/
单位转换
我们经常将数值在用户可读的字符串(以
ether
为单位)和机器可读的数值(以wei
为单位)之间转换。例如,钱包可以为用户界面指定余额(以ether
为单位)和gas
价格(以gwei
为单位),但是在发送交易时,两者都必须转换成以wei
为单位的数值
formatUnits
const oneGwei = ethers.getBigInt("1000000000"); // 从十进制字符串生成
//代码参考:https://docs.ethers.org/v6/api/utils/#about-units
console.group('\n2. 格式化:小单位转大单位,formatUnits');
console.log(ethers.formatUnits(oneGwei, 0));
// '1000000000'
console.log(ethers.formatUnits(oneGwei, "gwei"));
// '1.0'
console.log(ethers.formatUnits(oneGwei, 9));
// '1.0'
console.log(ethers.formatUnits(oneGwei, "ether"));
// `0.000000001`
console.log(ethers.formatUnits(1000000000, "gwei"));
// '1.0'
console.log(ethers.formatEther(oneGwei));
// `0.000000001` 等同于formatUnits(value, "ether")
console.groupEnd();
parseUnits
// 3. 解析:大单位转小单位
// 例如将ether转换为wei:parseUnits(变量, 单位),parseUnits默认单位是 ether
// 代码参考:https://docs.ethers.org/v6/api/utils/#about-units
console.group('\n3. 解析:大单位转小单位,parseUnits');
console.log(ethers.parseUnits("1.0").toString());
// { BigNumber: "1000000000000000000" }
console.log(ethers.parseUnits("1.0", "ether").toString());
// { BigNumber: "1000000000000000000" }
console.log(ethers.parseUnits("1.0", 18).toString());
// { BigNumber: "1000000000000000000" }
console.log(ethers.parseUnits("1.0", "gwei").toString());
// { BigNumber: "1000000000" }
console.log(ethers.parseUnits("1.0", 9).toString());
// { BigNumber: "1000000000" }
console.log(ethers.parseEther("1.0").toString());
// { BigNumber: "1000000000000000000" } 等同于parseUnits(value, "ether")
console.groupEnd();
StaticCall
在 ethers.js 中,你可以使用
contract.函数名.staticCall()
方法来模拟执行一个可能会改变状态的函数,但不实际向区块链提交这个状态改变。这相当于调用以太坊节点的eth_call
。这通常用于模拟状态改变函数的结果。如果函数调用成功,它将返回函数本身的返回值;如果函数调用失败,它将抛出异常。
在这里我们使用DAI合约来写demo
- 根据abi,address和provider创建合约实例
const provider = new ethers.JsonRpcProvider("https://rpc.ankr.com/eth");
const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY, provider);
// DAI的ABI
const abiDAI = [
"function balanceOf(address) public view returns(uint)",
"function transfer(address, uint) public returns (bool)",
];
// DAI合约地址(主网)
const addressDAI = '0x6B175474E89094C44Da98b954EedeAC495271d0F' // DAI Contract
// 创建DAI合约实例
const contractDAI = new ethers.Contract(addressDAI, abiDAI, provider)
- 读取钱包中的DAI持仓
const address = await wallet.getAddress()
console.log("\n1. 读取测试钱包的DAI余额")
const balanceDAI = await contractDAI.balanceOf(address)
console.log(`DAI持仓: ${ethers.formatEther(balanceDAI)}\n`)
- 模拟从Vitalik钱包向Vitalik账户转1个DAI,这个会成功,因为Vitalik有这么多钱。
// 发起交易
const tx = await contractDAI.transfer.staticCall("vitalik.eth", ethers.parseEther("1"), { from: await provider.resolveName("vitalik.eth") })
console.log(`交易会成功吗?:`, tx)
4.尝试从自己的钱包转账1DAI给Vitalik,这个不会成功,因为我没有这么多DAI,报错原因应该会是
const tx2 = await contractDAI.transfer.staticCall("vitalik.eth", ethers.parseEther("10000"), { from: address })
console.log(`交易会成功吗?:`, tx2)
合约标准
我们听说过ERC20合约标准,用于创建虚拟币,比如ETH ERC721用于创建NFT等数字收藏品等
- ERC20: 用于创建同质化代币,每个代币是相同的,适用于支付和奖励等场景。
- ERC721: 用于创建非同质化代币,每个代币是独一无二的,适用于数字艺术品和收藏品等场景。
在这里我们尝试调用使用ERC721创建的合约
// 合约abi
const abiERC721 = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function supportsInterface(bytes4) public view returns(bool)",
];
// ERC721的合约地址,这里用的BAYC
const addressBAYC = "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"
// 创建ERC721合约实例
const contractERC721 = new ethers.Contract(addressBAYC, abiERC721, provider)
// 1. 读取ERC721合约的链上信息
const nameERC721 = await contractERC721.name()
const symbolERC721 = await contractERC721.symbol()
console.log("\n1. 读取ERC721合约信息")
console.log(`合约地址: ${addressBAYC}`)
console.log(`名称: ${nameERC721}`)
console.log(`代号: ${symbolERC721}`)
跟我们前面学习到的ERC20标准创建的合约的使用规则一样,也是通过地址、abi和provider创建合约实例,然后读取信息
用ERC165检测合约接口,ERC165是一个标准接口,用于检测智能合约是否实现了某个接口。它允许合约查询另一个合约是否实现了特定的功能。
const selectorERC721 = "0x80ac58cd"
const isERC721 = await contractERC721.supportsInterface(selectorERC721)
console.log("\n2. 利用ERC165的supportsInterface,确定合约是否为ERC721标准")
console.log(`合约是否为ERC721标准: ${isERC721}`)