Ethers.js 极简入门

Ethers.js
Blockchain
DAPP

01 RPC

1、infura 注册账号,create API KEY,申请自己的 RPC 节点,避免网络拥堵;

2、连接以太坊

2.1 使用公共 RPC:ethers.getDefaultProvider()

2.2 使用个人 RPC:

const INFURA_MAINNET_URL = "RPC_ADDRESS";
const provider = new ethers.JsonRpcProvider(INFURA_MAINNET_URL);

provider 方法和区块链的交互是异步的;

获取某个地址的 ETH 余额:provider.getBakance('vitalik.eth')

02 Provider

Provider 类是对以太坊账户的抽象,它不接触用户私钥,只能读取链上信息,这一点比 web3.js 更安全。

常用方法:

  • getBalance():查询余额
  • getNetwork():查询 provider 连接到了哪条链
  • getBlockNumber():查询当前区块高度
  • getTransactionCount():查询某个钱包的历史交易次数
  • getFeeData():查询当前建议的 gas 设置
  • getBlock():查询区块信息
  • getCode():查询某个地址的合约 bytecode,参数为合约地址

03 合约交互

Contract 类是以太坊合约(EVM 字节码)的抽象,开发者可以通过 Contract 进行读取和交易(call and transcation),并获得交易的结果和事件。

创建合约变量

1.1 只读合约:const contract = new ethers.Contract(address, abi, provider)

1.2 可读写合约:const contract = new ethers.Contract(address, abi, signer)

2. 读取合约信息

2,1 创建只读 Contract 实例

方法一:直接输入合约 abi,可以直接去 etherscan 的合约代码里复制,也可以从 remix 的编译页面里复制

方法二:ethers 引入了 Human-Readable Abi,开发者可以通过 function signatureevent signature 来写 abi

2.2 读取链上信息

示例代码:

// 方法一
const abiWETH =
	'[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view",...太长后面省略...';
const addressWETH = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"; // WETH Contract
const contractWETH = new ethers.Contract(addressWETH, abiWETH, provider);
// 方法二
const abiERC20 = [
	"function name() view returns (string)",
	"function symbol() view returns (string)",
	"function totalSupply() view returns (uint256)",
	"function balanceOf(address) view returns (uint)",
];
const addressDAI = "0x6B175474E89094C44Da98b954EedeAC495271d0F"; // DAI Contract
const contractDAI = new ethers.Contract(addressDAI, abiERC20, provider);
// 读取链上信息
const main = async () => {
	const nameWETH = await contractWETH.name();
	const symbolWETH = await contractWETH.symbol();
	const totalSupplyWETH = await contractWETH.totalSupply();

	console.log(`合约名称:${nameWETH}`);
	console.log(`代号:${symbolWETH}`);
	const balanceWETH = await contractWETH.balanceOf(myAddress);
	console.log(`我的持仓: ${ethers.formatEther(balanceWETH)}\n`);
};

04 发送 ETH

签名者类(Signer)是以太坊账户的抽象,可用于对消息和交易进行签名,并将签名的交易发送到以太坊网络,并更改区块链状态。

钱包类(Wallet),继承签名者类,开发者可以向包含私钥的 EOA 账户一样,用它对交易和消息进行签名。

创建 Wallet 实例

// 随机创建 wallet 对象
const wallet1 = new ethers.Wallet.createRandom();

// 私钥创建 wallet 对象
const privateKey = PRIVATE_KEY;
const wallet2 = new ethers.Wallet(privateKey, provider);

// 助记词创建 wallet 对象
const wallet3 = new ethers.Wallet.fromMnemonic(mnemonic.phrase);

// JSON 文件创建 wallet 对象(keystore 文件)

发送 ETH

const tx = { to: address1, value: ethers.parseEther("0.001") };
const receipt = await wallet2.sendTransacion(tx); // 发送交易
await receipt.wait(); // 等待链上交易确认
console.log(receipt); // 打印交易详情

05 合约交互

const provider = new ethers.JsonRpcProvider(ALCHEMY_TESTNET_URL);
const dev1_private_key = DEV1_PRIVATE_KEY;
const dev1_wallet = new ethers.Wallet(dev1_private_key, provider);

// 定义合约 abi(需要用哪些合约方法就定义什么)和合约地址
const abiWETH = [
	"function balanceOf(address) public view returns(uint)",
	"function deposit() public payable",
	"function transfer(address, uint) public returns (bool)",
	"function withdraw(uint) public",
];

// WETH Contract
const addressWETH = "0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9";
// 构建合约对象
const contractWETH = new ethers.Contract(addressWETH, abiWETH, dev1_wallet);

const main = async () => {
	const dev1_address = await dev1_wallet.getAddress();
	const balance = await contractWETH.balanceOf(dev1_address);
	console.log(ethers.formatEther(balance));

	// 调用 deposit 方法:和合约方法里接收的参数相同
	const tx = await contractWETH.deposit({ value: ethers.parseEther("0.001") });
	await tx.wait(); // 等待交易完成,tx 是交易详情
	const balanceWETH_deposit = await contractWETH.balanceOf(dev1_address);
	console.log(balanceWETH_deposit);
};
main();

06 检索事件

事件(Event):相当于合约向外部世界发送通知的机制。事件会在合约的不同的函数或操作中被触发,当事件被触发时,相关数据会被记录在区块链上(可以在 etherscan 里查询到),供外部应用程序监听和处理。

function vote(unit256 candidateId) public {
	// 投票逻辑
	// 触发投票事件
	emit VoteCast(msg.sender, candidateId)
}

代码示例:

const provider = new ethers.JsonRpcProvider(ALCHEMY_TESTNET_URL);
// 要检索的事件必须包含在合约的 abi 中
const abiWETH = [
	"event Transfer(address indexed from, address indexed to, uint amount)",
];
const addressWETH = WETH_SEPOLIA_ADDRESS;
const contract = new ethers.Contract(addressWETH, abiWETH, provider);

const main = async () => {
	const block = await provider.getBlockNumber();
	console.log(`当前区块高度${block}`); // 获取过去10个区块内的Transfer事件,并打印出第1个
	//  contract.queryFilter('事件名',起始区块,结束区块)
	const transferEvents = await contract.queryFilter(
		"Transfer",
		block - 10,
		block + 10
	);
	const amount = ethers.formatUnits(
		ethers.getBigInt(transferEvents[0].args["amount"]),
		"ether"
	); // 读取事件的解析结果。
	console.log(
		`地址 ${transferEvents[0].args["from"]} 转账${amount} WETH 到地址 ${transferEvents[0].args["to"]}`
	);
};
main();

pic

08 监听智能合约

// 监听 USDT 的实时交易数据
const provider = new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);
const abi = [
	"event Transfer(address indexed from, address indexed to, uint value)",
];
const contractUSDT = new ethers.Contract(USDT_MAIN_ADDRESS, abi, provider);

const main = async () => {
	console.log("\n2. 利用contract.on(),持续监听Transfer事件");
	//  contract.on('eventName', function) - 持续监听
	//  contract.once - 监听一次
	contractUSDT.on("Transfer", (from, to, value) => {
		console.log(
			// 打印结果
			`${from} -> ${to} ${ethers.formatUnits(ethers.getBigInt(value), 6)}`
		);
	});
};

09 事件过滤

构建过滤器:const filter = contract.filters.EVENT_NAME(...args)

// 监听交易所的 USDT 转账
const provider = new ethers.JsonRpcProvider(ALCHEMY_MAINNET_URL);
// USDT 合约地址
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);
const balanceUSDT = await contractUSDT.balanceOf(accountBinance);
console.log(`USDT余额: ${ethers.formatUnits(balanceUSDT, 6)}\n`);
// 创建过滤器,监听转移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)}`
	);
});
// 创建过滤器,监听交易所转出USDT
let filterToBinanceOut = contractUSDT.filters.Transfer(accountBinance);
console.log("\n3. 创建过滤器,监听USDT转出交易所");
console.log("过滤器详情:");
console.log(filterToBinanceOut);
contractUSDT.on(filterToBinanceOut, (res) => {
	console.log("---------监听USDT转出交易所--------");
	console.log(
		`${res.args[0]} -> ${res.args[1]} ${ethers.formatUnits(res.args[2], 6)}`
	);
});

10 单位转换

NameDecimals
wei0
kwei3
mwei6
gwei9
szabo12
finney15
ether18

formatUnits(变量, 单位):小单位转大单位,比如wei -> ether,在显示余额时很有用

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(变量, 单位):大单位转小单位,比如 ether -> wei,在将用户输入的值转为 wei 为单位的数值很有用

// 例如将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();