前言介绍
前端应该调用哪个库好?
个人推荐etherjs, 因为现在大多数都是使用这个库来跟链上交互, 更新相对比较快, 社区活跃.
如果你还是无法确定使用哪个,可以看看一下官网的介绍以及api, 也可以混搭一起使用
web3js 与 etherjs
web3js: 允许开发人员通过 HTTP、IPC 或 WebSocket 连接与本地或远程以太坊节点(或任何与 EVM 兼容的区块链)进行交互
etherjs: 实现了读写分离, 是目前更流行, 更安全的连接以太坊方式, 查看etherjs v5文档
Web3.js 和 Ethers.js 是两个常用于与以太坊区块链进行交互的JavaScript库
Ethers.js 更加轻量、模块化、易用,适合前端和轻量级应用。
Web3.js 功能更为全面,但相对复杂,适合需要大量以太坊交互功能的场景。
EIP 标准协议
EIP 代表 Ethereum Improvement Proposal(以太坊改进提案)。
它是以太坊平台上推荐使用的标准和协议的统称。 它所包含的具体标准和协议涉及以太坊的核心协议、客户端API、智能合约标准等。 每一个EIP包含对某个标准或协议的定义。
provider
provider: 一个用于连接以太坊网络的抽象类,提供了只读形式来访问区块链网络和获取链上状态, dapp中的一个常见约定是钱包通过网页中一个JavaScript对象公开其API。该对象称为“provider”。
背景: 过去由于不同钱包提供的接口方法各不相同, dapp需要对接不同钱包的api非常麻烦, 比如要签名时, 需要针对不同钱包写多套签名过程
因此, EIP-1193 标准化了钱包Provider API, 使得dapp可以操作不同的钱包, 对钱包抽象化
由于业务需求需要通过应用层数据跟区块链信息进行交互, 在调用provider时不仅跟链上交互, 还需要跟应用层进行数据的交互. 因此我们根据标准重写签名方法, 这样在使用不同provider既可以使用我们的提供的provider, 也不会影响其他提供者的provider
Provider设计方案
大部分provider封装的方法应基于标准化的以太坊json-rpc
常见的 provider 有JsonRpcProvider 和 IpcProvider 允许连接到控制或可以访问的以太坊节点。
综合以上所述, 我们选择JsonRpcProvider进行重写和定制.
重写其中api的思路: 调用eth_signTypedData_v4时, 对多签进行分片, 分片之后的数据需要存储和读取, 这时候就需要嵌入接口来存分片.
etherjs提供了多种provider的基底, 根据情况进行选择
这里帮大家找出各自库提供的provider.
etherjs - provider
web3js - provider
如果你不熟悉如何使用 provider, 这里有个入门教程 WTF - Provoder
代码实现
参考案例: (看看大佬是怎么实现的)
这里有两个例子, 挑个重点代码片段解读一下.
例1: cloud-cryptographic-wallet 是一组用于将加密库与各种云服务的密钥管理系统连接起
按照这个思路, 我们可以找到一个合适的provider作为基底, 再根据我们需要重写的api写入新的provider, 再将这个新的provider丢进基底形成新的provider, 既满足业务场景的需求, 又符合市面上provider规范.
基底: new ethers.providers.Web3Provider( externalProvider [ , network ] ), externalProvider 就是我们需要实现代码逻辑
import { EventEmitter } from 'events'
import { BigNumber, ethers } from 'ethers'
class NewProvider extends EventEmitter {
constructor(rpcUrl) {
super()
this.wallet = null
// Initialize with JsonRpcProvider using the custom RPC URL
this.ethersProvider = new ethers.providers.JsonRpcProvider(rpcUrl)
// Placeholder for the current account and chain ID
this.accounts = []
this.currentAccount = null
this.chainId = null
// Bind this context to methods
this.request = this.request.bind(this)
}
// EIP-1193 request method
// eslint-disable-next-line complexity
async request({ method, params }) {
switch (method) {
case 'eth_accounts':
return this.accounts || []
case 'eth_requestAccounts':
await this.connect()
return this.accounts || []
case 'eth_chainId':
return this.handleEthChainId()
case 'eth_getBalance':
return this.getBalance(params)
case 'eth_signTransaction':
return this.signTransaction(params)
case 'dtt_multiSignTransaction':
return this.multiSignTransaction(params)
case 'eth_sendTransaction':
return this.sendTransaction(params)
case 'eth_signTypedData_v4':
return this.signTypedData(params)
default:
if (this.ethersProvider[method]) {
this.ethersProvider.send(method, params)
} else {
throw new Error(`Unsupported method: ${method}`)
}
}
}
// Method to trigger events
triggerEvent(eventName, data) {
this.emit(eventName, data)
}
// Method to connect to the provider
async connect() {
try {
this.chainId = await this.handleEthChainId()
this.wallet = {} // { ... }
this.accounts = [] // [...]
this.currentAccount = this.accounts[0]
this.triggerEvent('accountsChanged', this.accounts)
this.triggerEvent('chainChanged', this.chainId)
} catch (error) {
this.triggerEvent('disconnect', { error })
}
}
// 返回节点所控制的账户列表
async listAccounts() {
return await this.ethersProvider.listAccounts()
}
// Implementations of various eth_ methods
async handleEthChainId() {
const network = await this.ethersProvider.getNetwork()
const chainId = `0x${network.chainId.toString(16)}`
return chainId
}
async getGasPrice() {
const gasPriceMultiplier = 1
const gasPrice = await this.ethersProvider.getGasPrice()
const baseGasPrice = Math.pow(10, 9)
return Math.ceil(Math.max(baseGasPrice, gasPrice.toNumber()) * gasPriceMultiplier).toString()
}
async getBalance([address, blockTag]) {
const balance = await this.ethersProvider.getBalance(address, blockTag)
return balance.toString()
}
async signTransaction(tx) {}
async signTypedData() {}
}
export default NewProvider
-
gasPrice、estimateGas、chainId 、getBalance、getTransactionCount等与节点交互
这类交互直接复用JsonRpcProvider方法,如有需要在此基础上进行进制转换等简单操作
-
signTransaction: 基础签名方法,对交易进行签名
-
signTypedData: 特有配合多钱使用的签名方法
对typeData进行签名,在Provider层不对typeData的终签及非终签进行区分
ethers已经提供了 Web3Provider 且是EIP-1193, 我们就不用实现更多复杂的逻辑,直接套娃
把定制NewProvider丢进Web3Provider
async connect() {
this.provider = new NewProvider(this.rpcUrl)
this.web3Provider = new ethers.providers.Web3Provider(this.provider)
this.signer = this.web3Provider.getSigner()
await this.provider.connect()
this.provider.on('disconnect', (val) => {
Message.error(val.error.message)
throw val.error
})
}
以上只是一个雏形Provider, 还有类似v, r, s需要根据业务场景来实现.
参考文档