EIP-1193: Ethereum Provider JavaScript API
在浏览器钱包插件中存储着用户私钥, 当在 dapp
网页发起交易时, 需要告诉钱包帮助我们签名交易和发送交易。因此就需要在 dapp
网页暴露一个对象, 用于 dapp
和 钱包之间的交互。这个对象就是 Provider
。钱包插插件通过向 window
对象添加属性实现Provider
注入。
由于以太坊生态的完善,出现了各种各样的钱包。这些钱包提供的 Provider
对象也大不相同。为了规范 Provider
对象,因此提出了 EIP-1193
, 该标准可以保证各个钱包提供相同的接口。
关联协议
连接
当 Provider
能够为至少一个链提供 RPC
请求时,被称为已连接
当 Provider
无法为任何链提供 RPC
请求时,被称为未连接
API Request
Provider
提供的 RPC
请求参数遵循 RequestArguments
格式, 通过 request
方法发送
interface RequestArguments {
readonly method: string;
readonly params?: readonly unknown[] | object;
}
Provider.request(args: RequestArguments): Promise<unknown>;
支持的 RPC
方法见 EIP-1474
账户相关 EIP-1102
RPC 错误
错误结构
interface ProviderRpcError extends Error {
code: number
data?: unknown
}
code
有以下取值
4001
: User Rejected Request(用户拒绝)4100
: Unauthorized(请求方法未授权)4200
: Unsupported Method(不支持的方法)4900
: Disconnected(Provider 已断开与所有链的连接)4901
: Chain Disconnected(Provider 未连接到目标链)
Events
Provider
需要实现下面的事件处理方法
on
事件监听处理removeListener
移除事件监听
这些方法必须按照 Node.js EventEmitter
API 进行实现。
on
方法可监听的事件有
message
Subscriptions
connect
disconnect
chainChanged
accountsChanged
message
message
为通用型事件,触发时接收参数类型为
interface ProviderMessage {
readonly type: string
readonly data: unknown
}
Provider.on('message', listener: (message: ProviderMessage) => void): Provider;
Subscriptions
如果 Provider
支持以太坊 RPC
订阅,例如 eth_subscribe
,当接收到订阅通知时,须发出消息事件。
如果 Provider
从例如 eth_subscribe
订阅中接收到一个订阅消息,则 Provider
必须发出具有以下形式的 ProviderMessage
对象的消息事件:
interface EthSubscription extends ProviderMessage {
readonly type: 'eth_subscription'
readonly data: {
readonly subscription: string
readonly result: unknown
}
}
connect
如果 Provider
变为已连接状态,则需要发出 connect
的事件。
包括以下情况:
- 在初始化后,
Provider
首次连接到链。 - 在断开事件被发出后,
Provider
重新连接到链。
此事件必须使用以下形式的对象进行发出:
interface ProviderConnectInfo {
readonly chainId: string
}
Provider.on('connect', listener: (connectInfo: ProviderConnectInfo) => void): Provider;
disconnect
如果 Provider
与所有链断开连接,Provider
须按照 RPC
错误部分中定义的接口发出名为 disconnect
的事件,并附带值 error: ProviderRpcError
Provider.on('disconnect', listener: (error: ProviderRpcError) => void): Provider;
chainChanged
如果连接到的链发生变化,Provider
须触发 chainChanged
的事件,并使用值 chainId: string
来指定新链的整数 ID 作为十六进制字符串,符合 eth_chainId
以太坊 RPC 方法
Provider.on('chainChanged', listener: (chainId: string) => void): Provider;
accountsChanged
如果 Provider
可用的账户发生变化,须触发 accountsChanged
的事件,并附带值 accounts: string[]
,其中包含 eth_accounts
以太坊 RPC 方法返回的账户地址。
Provider.on('accountsChanged', listener: (accounts: string[]) => void): Provider;
总的来说 Provider
主要提供两个能力
- 发出以太坊 RPC 请求
- 响应链、客户端和钱包的状态变化
Provider API 规范由一个方法和五个事件组成。仅方法 request 和 message 事件就足以实现一个完整的 Provider。它们旨在分别发出任意 RPC 请求和传送任意消息。
示例
假设 Provider
对象注入在 window.ethereum
中
// 获取 Provider 对象
const ethereum = window.ethereum
// Example 1: 获取 chainId
ethereum
.request({ method: 'eth_chainId' })
.then((chainId) => {
console.log(`hexadecimal string: ${chainId}`)
console.log(`decimal number: ${parseInt(chainId, 16)}`)
})
.catch((error) => {
console.error(`Error fetching chainId: ${error.code}: ${error.message}`)
})
// Example 2: 获取最新的区块号
ethereum
.request({
method: 'eth_getBlockByNumber',
params: ['latest', true]
})
.then((block) => {
console.log(`Block ${block.number}:`, block)
})
.catch((error) => {
console.error(
`Error fetching last block: ${error.message}.
Code: ${error.code}. Data: ${error.data}`
)
})
// Example 3: 获取账户列表
ethereum
.request({ method: 'eth_accounts' })
.then((accounts) => {
console.log(`Accounts:\n${accounts.join('\n')}`)
})
.catch((error) => {
console.error(
`Error fetching accounts: ${error.message}.
Code: ${error.code}. Data: ${error.data}`
)
})
// Example 4: 监听新区块产出
ethereum
.request({
method: 'eth_subscribe',
params: ['newHeads']
})
.then((subscriptionId) => {
ethereum.on('message', (message) => {
if (message.type === 'eth_subscription') {
const { data } = message
if (data.subscription === subscriptionId) {
if ('result' in data && typeof data.result === 'object') {
const block = data.result
console.log(`New block ${block.number}:`, block)
} else {
console.error(`Something went wrong: ${data.result}`)
}
}
}
})
})
.catch((error) => {
console.error(
`Error making newHeads subscription: ${error.message}.
Code: ${error.code}. Data: ${error.data}`
)
})
// Example 5: 监听切换账户
const logAccounts = (accounts) => {
console.log(`Accounts:\n${accounts.join('\n')}`)
}
ethereum.on('accountsChanged', logAccounts)
// 移除监听事件
ethereum.removeListener('accountsChanged', logAccounts)
// Example 6: 监听链接断开
ethereum.on('disconnect', (code, reason) => {
console.log(`Ethereum Provider connection closed: ${reason}. Code: ${code}`)
})