跳到主要内容

Ethers极简入门: 4. 发送ETH

我最近在重新学ethers.js,巩固一下细节,也写一个WTF Ethers极简入门,供小白们使用。

推特@0xAA_Science

WTF Academy社群: 官网 wtf.academy | WTF Solidity教程 | discord | 微信群申请

所有代码和教程开源在github: github.com/WTFAcademy/WTF-Ethers


这一讲,我们将介绍Signer签名者类和它派生的Wallet钱包类,并利用它来发送ETH

Signer签名者类

Web3.js认为用户会在本地部署以太坊节点,私钥和网络连接状态由这个节点管理(实际并不是这样);而在ethers.js中,Provider提供器类管理网络连接状态,Signer签名者类或Wallet钱包类管理密钥,安全且灵活。

ethers中,Signer签名者类是以太坊账户的抽象,可用于对消息和交易进行签名,并将签名的交易发送到以太坊网络,并更改区块链状态。Signer类是抽象类,不能直接实例化,我们需要使用它的子类:Wallet钱包类。

Wallet钱包类

Wallet类继承了Signer类,并且开发者可以像包含私钥的外部拥有帐户(EOA)一样,用它对交易和消息进行签名。

下面我们介绍创建Wallet实例的几种办法:

方法1:创建随机的wallet对象

我们可以利用ethers.Wallet.createRandom()函数创建带有随机私钥的Wallet对象。该私钥由加密安全的熵源生成,如果当前环境没有安全的熵源,则会引发错误(没法在在线平台playcode使用此方法)。

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

方法2:用私钥创建wallet对象

我们已知私钥的情况下,可以利用ethers.Wallet()函数创建Wallet对象。

// 利用私钥和provider创建wallet对象
const privateKey = '0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b'
const wallet2 = new ethers.Wallet(privateKey, provider)

方法3:从助记词创建wallet对象

我们已知助记词的情况下,可以利用ethers.Wallet.fromPhrase()函数创建Wallet对象。

// 从助记词创建wallet对象
const wallet3 = ethers.Wallet.fromPhrase(mnemonic.phrase)

其他方法:通过JSON文件创建wallet对象

以上三种方法即可满足大部分需求,当然还可以通过ethers.Wallet.fromEncryptedJson解密一个JSON钱包文件创建钱包实例,JSON文件即keystore文件,通常来自Geth, Parity等钱包,通过Geth搭建过以太坊节点的个人对keystore文件不会陌生。

发送ETH

我们可以利用Wallet实例来发送ETH。首先,我们要构造一个交易请求,在里面声明接收地址to和发送的ETH数额value。交易请求TransactionRequest类型可以包含发送方from,nonce值nounce,请求数据data等信息,之后的教程里会更详细介绍。

    // 创建交易请求,参数:to为接收地址,value为ETH数额
const tx = {
to: address1,
value: ethers.parseEther("0.001")
}

然后,我们就可以利用Wallet类的sendTransaction来发送交易,等待交易上链,并获得交易的收据,非常简单。

    //发送交易,获得收据
const txRes = await wallet2.sendTransaction(tx)
const receipt = await txRes.wait() // 等待链上确认交易
console.log(receipt) // 打印交易的收据

代码示例

1. 创建Provider实例

// 利用Wallet类发送ETH
// 由于playcode不支持ethers.Wallet.createRandom()函数,我们只能用VScode运行这一讲代码
import { ethers } from "ethers";

// 利用Alchemy的rpc节点连接以太坊测试网络
// 准备 alchemy API 可以参考https://github.com/AmazingAng/WTFSolidity/blob/main/Topics/Tools/TOOL04_Alchemy/readme.md
const ALCHEMY_GOERLI_URL = 'https://eth-goerli.alchemyapi.io/v2/GlaeWuylnNM3uuOo-SAwJxuwTdqHaY5l';
const provider = new ethers.JsonRpcProvider(ALCHEMY_GOERLI_URL);

2. 用三种不同方法创建Wallet实例

  • 创建随机私钥的Wallet对象。这种方法创建的钱包是单机的,我们需要用connect(provider)函数,连接到以太坊节点。这种方法创建的钱包可以用mnemonic获取助记词。
// 创建随机的wallet对象
const wallet1 = ethers.Wallet.createRandom()
const wallet1WithProvider = wallet1.connect(provider)
const mnemonic = wallet1.mnemonic // 获取助记词
  • 利用私钥和Provider实例创建Wallet对象。这种方法创建的钱包不能获取助记词。
// 利用私钥和provider创建wallet对象
const privateKey = '0x227dbb8586117d55284e26620bc76534dfbd2394be34cf4a09cb775d593b6f2b'
const wallet2 = new ethers.Wallet(privateKey, provider)
  • 利用助记词创建Wallet对象。这里我们使用的是wallet1的助记词,因此创建出钱包的私钥和公钥都和wallet1相同。
// 从助记词创建wallet对象
const wallet3 = ethers.Wallet.fromPhrase(mnemonic.phrase)

3. 获取钱包地址

利用getAddress()函数获取钱包地址。

    const address1 = await wallet1.getAddress()
const address2 = await wallet2.getAddress()
const address3 = await wallet3.getAddress() // 获取地址
console.log(`1. 获取钱包地址`);
console.log(`钱包1地址: ${address1}`);
console.log(`钱包2地址: ${address2}`);
console.log(`钱包3地址: ${address3}`);
console.log(`钱包1和钱包3的地址是否相同: ${address1 === address3}`);

获取钱包地址

4. 获取助记词

利用钱包对象的mnemonic.phrase成员获取助记词:

console.log(`钱包1助记词: ${wallet1.mnemonic.phrase}`)

获取助记词

5. 获取私钥

利用钱包对象的privateKey成员获取私钥:

    console.log(`钱包2私钥: ${wallet2.privateKey}`)

获取私钥

6. 获取钱包在链上的交互次数

利用getTransactionCount()函数获取钱包在链上的交互次数。

    const txCount1 = await provider.getTransactionCount(wallet1WithProvider)
const txCount2 = await provider.getTransactionCount(wallet2)
console.log(`钱包1发送交易次数: ${txCount1}`)
console.log(`钱包2发送交易次数: ${txCount2}`)

获取钱包在链上的交互次数

7. 发送ETH

我们用wallet2wallet1发送0.001 ETH,并打印交易前后的钱包余额。由于wallet1是新建的随机私钥钱包,因此交易前余额为0,而交易后余额为0.001 ETH

    // 5. 发送ETH
// 如果这个钱包没goerli测试网ETH了,去水龙头领一些,钱包地址: 0xe16C1623c1AA7D919cd2241d8b36d9E79C1Be2A2
// 1. chainlink水龙头: https://faucets.chain.link/goerli
// 2. paradigm水龙头: https://faucet.paradigm.xyz/
console.log(`\n5. 发送ETH(测试网)`);
// i. 打印交易前余额
console.log(`i. 发送前余额`)
console.log(`钱包1: ${ethers.formatEther(await provider.getBalance(wallet1WithProvider))} ETH`)
console.log(`钱包2: ${ethers.formatEther(await provider.getBalance(wallet2))} ETH`)
// ii. 构造交易请求,参数:to为接收地址,value为ETH数额
const tx = {
to: address1,
value: ethers.parseEther("0.001")
}
// iii. 发送交易,获得收据
console.log(`\nii. 等待交易在区块链确认(需要几分钟)`)
const receipt = await wallet2.sendTransaction(tx)
await receipt.wait() // 等待链上确认交易
console.log(receipt) // 打印交易详情
// iv. 打印交易后余额
console.log(`\niii. 发送后余额`)
console.log(`钱包1: ${ethers.formatEther(await provider.getBalance(wallet1WithProvider))} ETH`)
console.log(`钱包2: ${ethers.formatEther(await provider.getBalance(wallet2))} ETH`)

发送ETH

总结

这一讲,我们介绍了Signer签名者类和Wallet钱包类,使用钱包实例获取了地址、助记词、私钥、链上交互次数,并发送ETH