最近在开发一个 NFT 二创平台,其中包含了很多概念和技术。我会更新一个系列的文章来总结和沉淀在这个过程中的一些知识与思考。
本文是对 ethers.js 进行一个全方位介绍,非常适合 web3 入门学习。
什么是 ethers.js?
Web3 中的各类 DApp,都需要与智能合约进行交互。如果用原生 JS 来做这些事会很麻烦。这时就需要使用专属的 SDK。
目前 JS 环境中有两个主流的库可以用来和智能合约进行交互,一个是 web3.js,另一个是 ethers.js。
ethers.js VS web3.js
web3.js 比 ethers.js 出现的更早。但是目前 ethers.js 更受欢迎。
主要原因有如下两点:
- 体积:ethers.js 体积仅有 116.5kb,web3.js 有 590.6kb。相差 5 倍左右。
- 设计理念:由于设计理念不同。web3.js 认为用户会在本地部署以太坊节点,私钥和网络连接状态由这个节点管理。但实际上大部分人都不会在本地部署以太坊节点。ethers.js 充分考虑了这一点,它是用 Provider 管理网络连接状态,用 Wallet 管理密钥,更加安全和灵活。而且原生支持 ENS。
ethers.js 基本使用介绍
节点即服务
由于在本地部署一个区块链节点的成本并不低,很少会有人真的部署一个节点,而是选择使用节点即服务。
这类服务有很多,比如老牌的 Alchemy、Infura、Moralis 以及今年估值 102 亿美金的新秀 Tenderly。
在这里我们选择 Alchemy,目前它的市场占有率是最高的。
alchemy 的网址在这:dashboard.alchemy.com/。具体的注册登陆就不讲了。
登陆之后我们创建一个 App。
Chain 选择 Ethereum,Network 选择 Goerli。
这样就成功创建了一个 App,点击后面的 view key,就可以查看 key。
我们把它复制下来,后面会用到。
构造合约
在对合约进行读取或交互之前,我们首先需要构造一个合约对象。
合约对象有三个构造参数,第一个是合约地址,是一个字符串。第二个是合约的 abi,可以是一个 JSON,第三个参数是 provider 对象,它用于管理网络连接状态。
下面的例子中就是使用 alchemy 提供的 JSON RPC 接口作为网络连接。
const rpc = `https://eth-goerli.g.alchemy.com/v2/${process.env.NEXT_PUBLIC_ALCHEMY_API_KEY}`; const provider = new ethers.providers.JsonRpcProvider(rpc); const contract = new ethers.Contract(contractAddress, abi, provider)
除了 JsonRpcProvider 以外,ethers 还有 IpcProvider、InfuraProvider、AlchemyProvider、Web3Provider 等多种 Provider。
读取合约信息
abi 中的方法会直接挂载到 contract 对象上,我们可以直接调用。
不过需要注意,所有的操作都是异步的。
(async () => { const owner = await contract.owner(); console.log(owner); })();
连接钱包
由于和合约交互需要支付 gas 费用,所以必须有一个数字钱包。
钱包有很多种,比如 MetaMask、Rainbow、Coinbase Wallet 等。其中 MetaMask 是最常用的一种数字钱包。
这里主要介绍如何连接到 MetaMask。
MetaMask 有一个浏览器插件,如果用户安装了该插件,在 window 对象下会有一个 ethereum 对象。我们可以调用 ethereum.request 方法发起请求,参数是一个对象,对象的 method 描述该次请求的操作。
ethereum.request 方法是异步的,会返回一个数组,该数组是所有登陆钱包的账户地址字符串,第一个账户就是当前激活的账户。如果返回的数组长度为 0,则意味着没有登陆任何账户。
(async ()=> { const accounts = await ethereum.request({ method: 'eth_requestAccounts' }) if(accounts.length === 0) { throw Error('未登录任何账户') } const activeAccount = accounts[0] console.log(activeAccount) })()
我们还可以通过 ethereum.request 方法获取当前的网络状态。
(async ()=> { const chainId = await ethereum.request({ method: 'eth_chainId' }) console.log(chainId) })()
它会返回一个字符串。0x1 表示以太网主网;0x5 表示 Goerli 测试网,更多网络的 chainId 可以在这个网站查看:chainlist.org/。
下面是使用 ethers.js 来连接 MetaMask 的代码。
(async ()=> { const provider = new ethers.providers.Web3Provider(window.ethereum) const accounts = await provider.send("eth_requestAccounts", []) const activeAccount = accounts[0] })()
如果使用 MetaMask 作为 provider,那么就不需要再使用 alchemy 了。
钱包
在转账交易之前,我们需要创建一个 Wallet 实例,它的作用是对交易和消息进行签名。
创建 Wallet 对象的方法有三种。
通过 Wallet.createRandom 创建随机钱包
这种方式创建的是一个单机钱包,需要连接网络。
const wallet = ethers.Wallet.createRandom() wallet.connect(provider)
通过助记词创建
const wallet = new ethers.Wallet.fromMnemonic(mnenonic.phrase)
通过私钥和 provider 创建
const wallet = new ethers.Wallet(privateKey, provider)
钱包信息
我们可以在创建好的钱包上面获取很多有用的信息,比如钱包地址、助记词、私钥、交易次数等。
console.log(wallet.address) console.log(await wallet.getAddress()) console.log(wallet.mnemonic) console.log(wallet.privateKey) console.log(wallet.getTransactionCount())
转账
一旦又了钱包,我们就可以向其他人发起转账交易。
创建一个 tx 对象,它最少需要两个属性,to 和 value,分别表示接受钱包地址和转账额度。
然后使用 wallet.sendTransaction 方法发送转账,它会返回一个 receipt 对象。这个对象有一个异步的 wait 方法,当交易上链后会返回。
const tx = { to: address, value: ethers.utils.parseEther("0.1"), } console.log('开始转账') const receipt = await wallet.sendTransaction(tx) await receipt.wait() console.log('完成转账')
通过合约转账交易
交易需要使用 Wallet 对象。再通过 wallet 作为合约的第三个构造参数创建 Contract 对象。
调用合约的 transfer 方法,进行转账交易。该方法需要两个参数,转入的钱包地址字符串和转入的数量。
transfer 会返回一个 tx 对象,该对象有一个异步的 wait 方法,会在交易完成后执行。
const wallet = new ethers.Wallet(privateKey, provider) const contract = new ethers.Contract(contractAddress, abi, wallet) (async ()=> { const tx = await contract.transfer(toAddress, ethers.utils.parseEther("1")) await tx.wait() })()
通过 signer 进行转账
通常我们无法直接拿到 privateKey,但是可以通过 signer 对象间接使用 privateKey。只需要进行签名就可以进行交易。这也是最常用的交易方式。
const signer = walletProvider.getSigner(); const tx = { to, value, }; const receipt = await signer.sendTransaction(tx); await receipt.wait();
使用 React 和 ethers.js 开发加密钱包
接下来我们开发一个最简单的加密钱包,具备最基础的转账功能和查询余额功能。
创建项目
我们首先创建一个 Next.js 项目。
npx create-next-app
需要选择 TypeScript。
安装依赖
安装 ethers.js
npm i ethers
安装 tailwindcss
npm install -D tailwindcss postcss autoprefixer npx tailwindcss init -p
修改 tailwind.cinfig.js。
/** @type {import('tailwindcss').Config} */ module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], }
修改 styles/globals.css。
@tailwind base; @tailwind components; @tailwind utilities;
安装 headless-ui
npm install @headlessui/react