使用 React+ethers.js 开发简单加密钱包(一)

简介: 使用 React+ethers.js 开发简单加密钱包

最近在开发一个 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 更受欢迎。

主要原因有如下两点:

  1. 体积:ethers.js 体积仅有 116.5kb,web3.js 有 590.6kb。相差 5 倍左右。
  2. 设计理念:由于设计理念不同。web3.js 认为用户会在本地部署以太坊节点,私钥和网络连接状态由这个节点管理。但实际上大部分人都不会在本地部署以太坊节点。ethers.js 充分考虑了这一点,它是用 Provider 管理网络连接状态,用 Wallet 管理密钥,更加安全和灵活。而且原生支持 ENS。

ethers.js 基本使用介绍


节点即服务


由于在本地部署一个区块链节点的成本并不低,很少会有人真的部署一个节点,而是选择使用节点即服务。

这类服务有很多,比如老牌的 Alchemy、Infura、Moralis 以及今年估值 102 亿美金的新秀 Tenderly。

在这里我们选择 Alchemy,目前它的市场占有率是最高的。

alchemy 的网址在这:dashboard.alchemy.com/。具体的注册登陆就不讲了。

登陆之后我们创建一个 App。

image.png

Chain 选择 Ethereum,Network 选择 Goerli。

image.png

这样就成功创建了一个 App,点击后面的 view key,就可以查看 key。

image.png

我们把它复制下来,后面会用到。

image.png


构造合约


在对合约进行读取或交互之前,我们首先需要构造一个合约对象。

合约对象有三个构造参数,第一个是合约地址,是一个字符串。第二个是合约的 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

相关文章
|
3天前
|
存储 JavaScript 索引
js开发:请解释什么是ES6的Map和Set,以及它们与普通对象和数组的区别。
ES6引入了Map和Set数据结构。Map的键可以是任意类型且有序,与对象的字符串或符号键不同;Set存储唯一值,无重复。两者皆可迭代,支持for...of循环。Map有get、set、has、delete等方法,Set有add、delete、has方法。示例展示了Map和Set的基本操作。
17 3
|
1月前
|
开发框架 JavaScript 安全
js开发:请解释什么是Express框架,以及它在项目中的作用。
Express是Node.js的Web开发框架,简化路由管理,支持HTTP请求处理。它采用中间件系统增强功能,如日志和错误处理,集成多种模板引擎(EJS、Jade、Pug)用于HTML渲染,并提供安全中间件提升应用安全性。其可扩展性允许选用合适插件扩展功能,加速开发进程。
|
1月前
|
缓存 JavaScript 数据安全/隐私保护
js开发:请解释什么是ES6的Proxy,以及它的用途。
`ES6`的`Proxy`对象用于创建一个代理,能拦截并自定义目标对象的访问和操作,应用于数据绑定、访问控制、函数调用的拦截与修改以及异步操作处理。
18 3
|
1月前
|
JavaScript
js开发:请解释什么是ES6的类(class),并说明它与传统构造函数的区别。
ES6的类提供了一种更简洁的面向对象编程方式,对比传统的构造函数,具有更好的可读性和可维护性。类使用`class`定义,`constructor`定义构造方法,`extends`实现继承,并可直接定义静态方法。示例展示了如何创建`Person`类、`Student`子类以及它们的方法调用。
22 2
|
4天前
|
JavaScript 前端开发
js开发:请解释事件冒泡和事件捕获。
JavaScript中的事件处理有冒泡和捕获两种方式。事件冒泡是从子元素向上级元素传递,而事件捕获则从外层元素向内层传递。`addEventListener`的第三个参数可设定事件模式,`false`或不设为冒泡,`true`为捕获。示例代码展示了如何设置。
19 2
|
2天前
|
前端开发 JavaScript
js开发中的异步处理
JavaScript中的异步处理包括回调函数、Promise和async/await。回调函数是早期方法,将函数作为参数传递给异步操作并在完成后执行。Promise提供链式处理,通过resolve和reject管理异步操作的成功或失败。async/await基于Promise,允许写更简洁的同步风格代码,通过try-catch处理错误。Promise和async/await是现代推荐的异步处理方式。
|
3天前
|
JavaScript 前端开发
js开发:请解释什么是ES6的Generator函数,以及它的用途。
ES6的Generator函数是暂停/恢复功能的特殊函数,利用yield返回多个值,适用于异步编程和流处理,解决了回调地狱问题。例如,一个简单的Generator函数可以这样表示: ```javascript function* generator() { yield 'Hello'; yield 'World'; } ``` 创建实例后,通过`.next()`逐次输出"Hello"和"World",展示其暂停和恢复的特性。
14 0
|
3天前
|
缓存 JavaScript 前端开发
js开发:请解释什么是Webpack,以及它在项目中的作用。
Webpack是开源的JavaScript模块打包器,用于前端项目构建,整合并优化JavaScript、CSS、图片等资源。它实现模块打包、代码分割以提升加载速度,同时进行资源优化和缓存。Webpack的插件机制可扩展功能,支持热更新以加速开发流程。
13 2
|
4天前
|
JavaScript 前端开发
js开发:请解释this关键字在JavaScript中的用法。
【4月更文挑战第23天】JavaScript的this关键字根据执行环境指向不同对象:全局中指向全局对象(如window),普通函数中默认指向全局对象,作为方法调用时指向调用对象;构造函数中指向新实例,箭头函数继承所在上下文的this。可通过call、apply、bind方法显式改变this指向。
7 1
|
4天前
|
JavaScript 前端开发
js开发:请解释同步和异步编程的区别。
同步编程按顺序执行,易阻塞;异步编程不阻塞,提高效率。同步适合简单操作,异步适合并发场景。示例展示了JavaScript中同步和异步函数的使用。
14 0