Alert Source Discuss
Standards Track: Interface

EIP-5792: 钱包调用 API

添加 JSON-RPC 方法,用于从用户的钱包发送多个调用,并检查其状态

Authors Moody Salem (@moodysalem), Lukas Rosario (@lukasrosario), Wilson Cusack (@wilsoncusack), Dror Tirosh (@drortirosh), Jake Moxey (@jxom), Derek Rein (@arein), Alex Forshtat (@forshtat), Sam Wilson (@SamWilsn) <sam@binarycake.ca>, Borislav Itskov (@Oxbobby), Joao Tavares (@cryptotavares), Adam Fuller (@azf20), Philip Liao (@phil-ociraptor), bumblefudge (@bumblefudge)
Created 2022-10-17
Requires EIP-1193

摘要

定义新的 JSON-RPC 方法,使应用程序能够请求钱包处理一批链上写入调用,并检查这些调用的状态。 应用程序可以指定执行这些链上调用,同时利用钱包先前表达的特定能力;定义了一个新增的、新颖的钱包 RPC,使应用程序能够查询钱包的这些能力。

动机

当前用于从用户钱包发送交易并检查其状态的方法是 eth_sendTransactioneth_getTransactionReceipt

当前用于从用户钱包发送交易并检查其状态的方法无法满足现代开发者的需求,也无法适应新的交易格式。甚至这个名字——eth_sendTransaction——也是节点充当钱包的时代的产物。

当今,开发者希望在单个 RPC 调用中批量发送多个调用,许多智能账户可以反过来在单个交易中原子地执行这些调用。开发者还希望利用新的交易格式提供的功能,例如 ERC-4337 交易中的 paymaster。eth_sendTransaction 无法实现这些。

在更新到一组新的 wallet_ RPC 时,我们的主要目标是强制执行钱包和应用程序关注点之间的清晰分离,使开发者能够利用诸如 paymaster 和批量交易之类的功能,并创建一个清晰的方式,以便随着时间的推移更安全地添加可发现的功能,并最大限度地减少协调。

规范

添加了四个新的 JSON-RPC 方法:三个用于处理链上调用批次,一个用于查询对钱包能力的支持,例如更好地利用这三个批处理方法。 应用程序可以立即开始使用前三个方法,并在它们不可用时退回到 eth_sendTransactioneth_getTransactionReceipt

wallet_sendCalls

请求钱包提交一批调用。fromchainIdEIP-155 整数标识,以十六进制表示法表示, 带有 0x 前缀,chainId 值没有前导零。 calls 字段中的条目是简单的 {to, data, value} 元组。

capabilities 字段是应用程序如何与钱包通信,了解钱包支持的 capabilities。例如,应用程序可以在这里指定一个 paymaster 服务 URL,ERC-4337 钱包可以从中请求用户操作的 paymasterAndData 输入。

在“capabilities”成员中定义的每个 capability 都可以定义全局或调用特定的字段。 这些字段设置在此 capability 在 capabilities 对象中的条目内。 calls 字段中的每个实体可能包含一个可选的 capabilities 对象。 此对象允许应用程序将 capability 特定的元数据附加到各个调用。

atomicRequired 字段指定是否需要钱包以原子方式处理这批调用。钱包执行原子批量调用的能力通过内置的 atomic capability 公开,可以通过 wallet_getCapabilities 获得。

除非这些要求被某个 capability 显式覆盖,否则钱包必须遵守以下规则。 请注意,这样的 capability 不在本 EIP 的范围内,应该在其自己的 ERC 中定义。

钱包:

  • 必须按照请求中指定的顺序发送调用
  • 必须在由请求的 chainId 属性标识的同一链上发送调用
  • 必须从请求的 from 属性中指定的地址发送调用(如果提供)
    • 如果未提供 from,则钱包应向用户提供一个机会,以便在确认期间查看和选择 from 地址
  • 不得等待任何调用完成以完成批处理
  • 如果用户拒绝请求,则不得发送请求中的任何调用
  • atomicRequired 设置为 true 时:
    • 必须以原子方式执行所有调用,这意味着所有调用要么都成功执行,要么链上不出现任何实质性影响。
    • 必须连续执行所有调用,这意味着不得将其他交易/调用与此批次中的调用交错。
    • 如果钱包能够将其原子性保证从 ready 升级到 supported,但尚未这样做,则必须先升级,然后再继续提交具有上述保证的调用。
  • atomicRequired 设置为 false 时:
    • 可以按顺序执行所有调用,而无需任何原子性/连续性保证。
    • 如果钱包能够提供原子性保证,则可以原子地执行批处理。
    • 如果钱包能够将其原子性保证升级到 supported(请参阅下面的 atomic capability 定义),则可以这样做,然后以原子方式执行批处理。
  • 如果 from 地址与启用的帐户不匹配,则可以拒绝请求
  • 如果在按顺序模拟时,预计批处理中的一个或多个调用将失败,则可以拒绝请求
  • 如果请求包含钱包不支持的 capability(顶级或调用级别),并且该 capability 未明确标记为可选,则必须拒绝该请求。
    • 应用程序可以通过将 optional 设置为 true 来将 capability 标记为可选。有关详细信息,请参见下面的 RPC 规范部分。

如果提供,钱包必须遵守 id 字段并在响应中返回它。

标识符,无论是由应用程序提供还是由钱包生成,都必须是唯一的字符串,最大为 4096 字节(包括前导 0x 在内的 8194 个字符)。

应用程序提供的 id 必须对每个应用程序的每个发送者都是唯一的,其中每个“应用程序”都应由其域标识。

钱包必须拒绝具有重复 id 的请求。

在从相应的 wallet_sendCalls 开始的 24 小时内,当使用相同的 id 调用 wallet_getCallsStatus 时,钱包应返回调用批处理状态。

capabilities 响应对象允许钱包将 capability 特定的元数据附加到响应。

wallet_sendCalls RPC 规范

type Capability = {
  [key: string]: unknown;
  optional?: boolean;
}

type SendCallsParams = {
  version: string;
  id?: string;
  from?: `0x${string}`;
  chainId: `0x${string}`; // Hex chain id
  atomicRequired: boolean;
  calls: {
    to?: `0x${string}`;
    data?: `0x${string}`;
    value?: `0x${string}`; // Hex value
    capabilities?: Record<string, Capability>;
  }[];
  capabilities?: Record<string, Capability>;
};

type SendCallsResult = {
  id: string;
  capabilities?: Record<string, any>;
};
wallet_sendCalls 示例参数
[
  {
    "version": "2.0.0",
    "from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
    "chainId": "0x01",
    "atomicRequired": true,
    "calls": [
      {
        "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        "value": "0x9184e72a",
        "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
      },
      {
        "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        "value": "0x182183",
        "data": "0xfbadbaf01"
      }
    ],
    "capabilities": {
      "paymasterService": {
        "url": "https://...",
        "optional": true
      }
    }
  }
]

请注意,由于 paymasterService capability 被标记为可选,因此不支持它的钱包仍将像 capability 不存在一样处理和处理请求。如果此 optional 字段设置为 false 或从请求中删除,则不支持此 capability 的钱包必须拒绝该请求。

wallet_sendCalls 示例返回值
{
  "id": "0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
}

wallet_getCallsStatus

返回通过 wallet_sendCalls 发送的调用批次的状态。 批次的标识符是从 wallet_sendCalls RPC 返回的值。 请注意,此方法响应的 receipts 对象是 eth_getTransactionReceipt 返回的对象的严格子集。

capabilities 对象允许钱包将 capability 特定的元数据附加到响应。

atomic 字段指定钱包如何处理该批次调用,这会影响 receipts 字段的结构。

  • receipts 字段中的收据必须按照它们包含在链上的顺序排列。
  • 如果钱包原子地执行多个调用,则 wallet_getCallsStatus 可以返回一个对象,该对象包含一个 receipts 字段,其中包含单个交易收据或收据数组,对应于链上包含的批量交易的方式。它还必须是明确的并返回设置为 trueatomic 字段。
  • 如果钱包非原子地执行多个调用,则 wallet_getCallsStatus 必须返回一个对象,该对象包含一个 receipts 字段,其中包含所有链上包含的包含批量调用交易的收据数组。这包括包含在链上但最终恢复的批量调用。它还必须是明确的并返回设置为 falseatomic 字段。
  • 收据对象中的 logs 必须仅包括与使用 wallet_sendCalls 提交的调用相关的日志。例如,在 ERC-4337 bundler 在链上提交的交易的情况下,日志必须仅包括与使用通过 wallet_sendCalls 提交的调用构造的用户操作相关的日志。即,日志不应包括来自同一bundle中提交的其他无关用户操作的日志。
    • 类似地,如果用户在从应用程序提交一批调用之前升级他们的钱包以支持原子性,则日志不得包括作为升级过程的一部分发出的日志。

wallet_getCallsStatus RPC 规范

type GetCallsParams = [string];

type GetCallsResult = {
  version: string;
  id: `0x${string}`;
  chainId: `0x${string}`;
  status: number; // See "Status Codes"
  atomic: boolean;
  receipts?: {
    logs: {
      address: `0x${string}`;
      data: `0x${string}`;
      topics: `0x${string}`[];
    }[];
    status: `0x${string}`; // Hex 1 or 0 for success or failure, respectively
    blockHash: `0x${string}`;
    blockNumber: `0x${string}`;
    gasUsed: `0x${string}`;
    transactionHash: `0x${string}`;
  }[];
  capabilities?: Record<string, any>;
};
status 字段的状态码

status 字段的目的是提供批处理的当前状态的简短摘要。 它为内部交易 receipts 数组提供了一些链下上下文。

状态码遵循以下类别:

  • 1xx: 挂起状态
  • 2xx: 已确认状态
  • 4xx: 链下失败
  • 5xx: 链规则失败
代码 描述
100 批处理已由钱包接收,但尚未完成链上执行(挂起)
200 批处理已包含在链上,没有回滚,receipts 数组包含所有调用的信息(已确认)
400 批处理未包含在链上,钱包不会重试(链下失败)
500 批处理完全回滚,链上可能只包含与 gas 费用相关的更改(链规则失败)
600 批处理部分回滚,链上可能包含与批处理调用相关的一些更改(部分链规则失败)

这些类别中更具体的状态代码应在单独的 ERC 中提出和商定。

wallet_getCallsStatus 示例参数

id 批处理标识符是一个唯一的 64 字节,表示为从 wallet_sendCalls 返回的十六进制编码字符串。

[
  "0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
]
wallet_getCallsStatus 示例返回值
{
  "version": "2.0.0",
  "chainId": "0x01",
  "id": "0x00000000000000000000000000000000000000000000000000000000000000000e670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331",
  "status": 200,
  "atomic": true,
  "receipts": [
    {
      "logs": [
        {
          "address": "0xa922b54716264130634d6ff183747a8ead91a40b",
          "topics": [
            "0x5a2a90727cc9d000dd060b1132a5c977c9702bb3a52afe360c9c22f0e9451a68"
          ],
          "data": "0xabcd"
        }
      ],
      "status": "0x1",
      "blockHash": "0xf19bbafd9fd0124ec110b848e8de4ab4f62bf60c189524e54213285e7f540d4a",
      "blockNumber": "0xabcd",
      "gasUsed": "0xdef",
      "transactionHash": "0x9b7bb827c2e5e3c1a0a44dc53e573aa0b3af3bd1f9f5ed03071b100bb039eaff"
    }
  ]
}

wallet_showCallsStatus

请求钱包显示有关使用 wallet_sendCalls 发送的给定调用包的信息。请注意,此方法不返回任何已知的 id 批处理标识符。如果标识符未知,或者在执行 wallet_showCallsStatus 失败的情况下,则返回 RPC 调用错误。

wallet_showCallsStatus RPC 规范

type ShowCallsParams = string; // Call bundle identifier returned by wallet_sendCalls
wallet_showCallsStatus 示例参数

此方法接受由 wallet_sendCalls 调用返回的调用包标识符。

["0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"]

wallet_getCapabilities

此 RPC 允许应用程序从钱包请求 capabilities(例如,批量交易、paymaster 通信),而无需进行不同的发现和权限请求。有关请求 capabilities 和发现功能之间的区别的更多信息,请参阅“隐私注意事项”部分

如果用户尚未授权应用程序和请求的地址之间的连接,则此方法应返回 4100 Unauthorized 错误。

我们希望社区随着时间的推移在单独的 ERC 中对其他 capabilities 的定义达成一致。

请注意,除了或代替直接查询钱包以获取 capabilities 之外,相同的 capability 对象也可以带外公开,例如在 sessionProperties 和/或 scopedProperties 集合中,这些集合持久存储在符合 CAIP-25 的钱包提供程序接口中(有关示例请求和响应,请参阅 CAIP-25 的 ethereum 用法配置文件),或者在其他一些众所周知的位置(例如从 EIP-6963 rdns 标识符派生的 URL)。 提供程序抽象还可以缓存先前请求中的 capabilities,或者以其他方式从带外注入它们,以方便更好的用户体验。 如果这些 capabilities 的任何补充表达与实时钱包 RPC 响应中表达的 capabilities 相矛盾,则后一个值应被视为 capabilities 的规范和当前表达。

wallet_getCapabilities RPC 规范

Capabilities 以键/值对返回,其中键命名一个 capability,值符合为该名称定义的形状,在一个以相关的 EIP-155 chainId(以十六进制表示法表示)为键的对象中。 Capabilities 嵌套在每个链的对象中,因为钱包可能会在给定会话中授权的多个链上支持不同的 capabilities。 钱包在所有链上都支持的 Capabilities 应仅包含一次,使用特殊的 chainID"0x0",并且不应在嵌套的每链对象中重复。 如果钱包不支持 wallet_getCapabilities 方法中请求的链,则钱包不得响应错误,而必须不包括响应中不支持的链的键。

type GetCapabilitiesParams = [`0x${string}`, [`0x${string}`]]; // Wallet address, array of queried chain ids (optional)

type GetCapabilitiesResult = Record<`0x${string}`, <Record<string, any>>; // Hex chain id
wallet_getCapabilities 示例参数
["0xd46e8dd67c5d32be8058bb8eb970870f07244567", ["0x2105", "0x14A34"]]
wallet_getCapabilities 示例返回值

以下 capabilities 仅用于说明目的。

{
  "0x0": {
    "flow-control": {
      "supported": true
    }
  },
  "0x2105": {
    "paymasterService": {
      "supported": true
    },
    "sessionKeys": {
      "supported": true
    }
  },
  "0x14A34": {
    "auxiliaryFunds": {
      "supported": true
    }
  }
}

atomic Capability

与上面给出的说明性示例以及未来 EIP 中定义的其他 capabilities 类似,atomic capability 指定钱包将如何执行通过 wallet_sendCalls 方法请求的批量交易。

atomic capability 的唯一属性 status 的有效 JSON-RPC 值为 supportedreadyunsupported

  • supported 表示钱包将以原子且连续的方式执行调用。
  • ready 表示钱包能够升级到 supported,等待用户批准。
  • unsupported 表示钱包不提供任何原子性或连续性保证,也不会向用户建议升级。

此 capability 在每个链上单独表达,应仅解释为对该链上的批量交易的保证。

如果给定的链不存在 atomic capability,并且除非它被另一个 capability(不在本 EIP 的范围内)显式覆盖,否则意味着钱包不支持该给定链的批处理。

atomic Capability 规范

type AtomicCapability = {
  status: 'supported' | 'ready' | 'unsupported';
};

wallet_getCapabilities 示例返回值(包括 atomic

{
  "0x2105": {
    "atomic": {
      "status": "supported"
    }
  },
  "0x14A34": {
    "atomic": {
      "status": "unsupported"
    }
  }
}

错误代码

以下错误代码是为本 EIP 中指定的方法定义的。这些错误遵循 JSON-RPC 2.0 规范的错误对象,该规范要求 codemessage 字段。

代码 消息 描述 相关 RPC
-32602 无效参数 钱包无法解析此请求(例如,缺少 0x 前缀、链 id 中的前导零、请求未通过模式验证) wallet_sendCalls, wallet_getCallsStatus, wallet_showCallsStatus, wallet_getCapabilities
4001 用户拒绝请求 用户拒绝提交这批调用(来自 EIP-1193 wallet_sendCalls
4100 未授权 指定的地址未连接或不在钱包中(来自 EIP-1193 wallet_sendCalls, wallet_getCapabilities
5700 不支持的非可选 capability 此钱包不支持未标记为可选的 capability wallet_sendCalls
5710 不支持的链 id 此钱包不支持指定的链 id wallet_sendCalls
5720 重复 ID 已经提交了具有此 id 的包 wallet_sendCalls
5730 未知的包 id 此包 id 未知/尚未提交 wallet_getCallsStatus, wallet_showCallsStatus
5740 包太大 调用包对于钱包来说太大了,无法处理 wallet_sendCalls
5750 原子性就绪钱包拒绝升级 钱包可以在升级后支持原子性,但用户拒绝了升级 wallet_sendCalls
5760 不支持原子性 钱包不支持原子执行,但请求需要原子执行 wallet_sendCalls

理由

关于命名

我们考虑修改 eth_sendTransaction 以添加对这些新 capabilities 的支持,但该方法最终是节点用于签名交易的时代的产物。我们决定最好继续使用 wallet_- 命名空间的方法,这些方法更好地描述了它们的用途。

我们还争论了这些方法是否应该被称为 wallet_sendTransactionwallet_sendCalls 或其他名称。我们最终决定使用 wallet_sendCalls,因为在 EOA 钱包的情况下,wallet_send* 方法可能会发送多个交易。类似地,我们反对 wallet_sendTransactions,因为在其他钱包实现(例如 ERC-4337)的情况下,多个调用可能会导致单个交易。

调用执行原子性

wallet_sendCalls 方法接受一个 calls 数组。但是,此提案并不要求将这些调用作为单个交易的一部分执行。它使 EOA 钱包能够表达其将调用作为单个交易的一部分或作为多个交易的一部分执行的能力。它还使应用程序能够表达其对于调用必须如何执行的最小原子性要求。

atomic 特殊 capability 已成为此规范不可或缺的一部分,以便提高表达性并促进采用(来自钱包和应用程序)。并且由于其对于减少应用程序和钱包之间的歧义的重要性,因此 capability 在 wallet_sendCalls 请求(通过 atomicRequired 参数)和 wallet_getCallsStatus 响应(通过 atomic 字段)中表示为一个顶级字段。

我们最初建议必须以原子方式执行多个调用,但在经过一番争论后,我们最终认为这过于主观。相反,我们包括了一个 atomic capability 的规范。这允许 EOA 钱包接受多个调用,并且仍然允许开发者选择仅在以原子方式执行批量调用时才提交它们。

调用 Gas Limit

我们最初的提案包括一个可选的 gas 字段,用于 walletSendCalls 方法接受的 calls 字段中的每个调用。但是,我们意识到这可能会产生误导,因为在 ERC-4337 钱包的情况下,您不能为每个调用指定 gas limit,只能为用户操作中的所有调用指定单个 gas limit。然后我们提出了一个适用于所有调用的单个 gas 值。这适用于 ERC-4337 钱包,但不适用于 EOA 钱包。当我们决定 EOA 钱包应该能够处理多个调用时,对于各种用例来说,通用的 gas 字段变得站不住脚,我们完全删除了它。

向后兼容性

不支持此处定义的方法的钱包在调用这些新的 JSON-RPC 方法时应返回错误响应。当由于缺少钱包支持而导致对这些方法的调用失败时,应用程序可以尝试通过 eth_sendTransaction 连续发送同一批调用,或者可以向用户指示其钱包不受支持并且未处理请求。

安全注意事项

无论指定的 atomic 值如何,App 开发者都不得假设所有调用都将在单个交易中发送。一个例子可能是对抵抗重组的 L2 实现 sendBundle 或类似功能。

钱包必须确保 wallet_sendCalls 返回的批处理标识符是不可预测的,以防止恶意应用程序推断有关其他用户交易的信息。

钱包不得在 wallet_getCallsStatus capabilities 响应中泄漏敏感信息。

隐私注意事项

渐进式授权和渐进式同意范例对于现代用户体验以及保护用户代理的匿名性非常重要。为了保护这些模式免受促进更好用户体验的功能发现的交叉激励,capability 语义被使用,并且在用于查询 capabilities 的 wallet_ RPC 的设计中,明确地掩盖了缺少功能支持和缺少功能权限之间的差异。

此外,建议钱包避免向不受信任的调用者或比必要的更多调用者公开 capabilities,因为这可能会允许对其“用户代理”(即客户端软件)进行“指纹识别”或概率识别,这与 Web 平台固有的其他去匿名化向量相结合,可能会导致个别用户的去匿名化或给定客户端的所有用户的聚合去匿名化。类似地,过度查询 capabilities 或激励 capability 过度共享(包括第三方 capability 过度共享)的应用程序是在服务于发现功能的能力交换的实现中要避免的反模式。

版权

CC0 下放弃版权和相关权利。

Citation

Please cite this document as:

Moody Salem (@moodysalem), Lukas Rosario (@lukasrosario), Wilson Cusack (@wilsoncusack), Dror Tirosh (@drortirosh), Jake Moxey (@jxom), Derek Rein (@arein), Alex Forshtat (@forshtat), Sam Wilson (@SamWilsn) <sam@binarycake.ca>, Borislav Itskov (@Oxbobby), Joao Tavares (@cryptotavares), Adam Fuller (@azf20), Philip Liao (@phil-ociraptor), bumblefudge (@bumblefudge), "EIP-5792: 钱包调用 API," Ethereum Improvement Proposals, no. 5792, October 2022. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-5792.