该文档定义了一个新的 JSON-RPC 方法 wallet_grantPermissions
,允许 DApp 请求钱包授予执行交易的权限,无需用户手动批准每个交易,并支持在没有钱包连接的情况下执行交易。该方法通过定义权限请求和响应的模式,以及支持不同类型的签名者和权限,为 DApp 提供了一种统一的方式来请求和管理用户授权,从而实现诸如订阅、被动投资和限价订单等用例。
我们定义了一个新的 JSON-RPC 方法 wallet_grantPermissions
,供 DApp 请求钱包授予权限,以便代表用户执行交易。这实现了两个用例:
目前,大多数 DApp 都实现了类似于以下流程:
sequenceDiagram
User ->>+ DApp: 建立连接
loop 针对每个调用
DApp ->> User: 提议调用 <br> (交易)
User ->> DApp: 批准调用
end
每次交互都需要用户使用他们的钱包签署交易。问题是:
在 AA 的上下文中,存在会话密钥的多个特定于供应商的实现,这些会话密钥是具有特定权限的临时密钥。但是,由于实现是特定于供应商的,因此无论具体的钱包实现如何,DApp 都不可能以统一的方式从钱包“请求”会话密钥。
本文档中的关键词“必须”、“不得”、“必需”、“应”、“不应”、“应该”、“不应该”、“建议”、“可以”和“可选”应按照 RFC 2119 中的描述进行解释。
wallet_grantPermissions
我们引入了一个 wallet_grantPermissions
方法,供 DApp 请求钱包授予权限。
type PermissionRequest = {
chainId: Hex; // uint256 的十六进制编码
address?: Address;
expiry: number; // unix 时间戳
signer: {
type: string; // ERC 定义的枚举
data: Record<string, any>;
};
permissions: {
type: string; // ERC 定义的枚举
data: Record<string, any>;
}[];
}[];
chainId
使用 EIP-155 定义了链,该链适用于此权限请求,并且可以通过其他参数找到定义的所有地址。
address
标识了此权限请求的目标帐户,当已建立连接并且已公开多个帐户时,这很有用。 它是可选的,允许用户选择要为其授予权限的帐户。
expiry
是一个 UNIX 时间戳(以秒为单位),用于指定此会话必须到期的时间。
signer
是一个字段,用于标识与权限关联的密钥或帐户,或者钱包将管理会话。 有关详细信息,请参见“签名者”部分。
permissions
定义了签名者可以代表帐户执行的允许行为。 有关详细信息,请参见“权限”部分。
请求示例:
PermissionRequest
对象的数组是 wallet_grantPermissions
RPC 期望的最终 params
字段。
[
{
chainId: 123,
address: '0x...'
expiry: 1577840461
signer: {
type: 'account',
data: {
address:'0x016562aA41A8697720ce0943F003141f5dEAe006',
}
},
permissions: [
{
type: 'native-token-transfer',
data: {
allowance: '0x1DCD6500'
}
}
],
}
]
type PermissionResponse = PermissionRequest & {
context: Hex;
accountMeta?: {
factory: `0x${string}`;
factoryData: `0x${string}`;
};
signerMeta?: {
// 7679 userOp 构建
userOpBuilder?: `0x${string}`;
// 7710 委托
delegationManager?: `0x${string}`;
};
};
首先请注意,响应包含原始请求的所有参数,并且不能保证收到的值与请求的值等效。
context
是一个用于标识撤销权限或提交 userOp 的通用字段,并且还可以包含非标识数据。 它可以是 ERC-7679 和 ERC-7710 中定义的 context
。 有关详细信息,请参见“原理”部分。
accountMeta
是可选的,但如果存在,则 factory
和 factoryData
的字段是必需的,如 ERC-4337 中所定义。 它们要么都指定,要么都不指定。 如果帐户尚未部署,则钱包必须返回 accountMeta
,并且 DApp 必须通过使用 factoryData
作为 calldata 调用 factory
合约来部署帐户。
signerMeta
取决于帐户类型。 如果签名者类型为 wallet
,则不是必需的。 如果签名者类型为 key
或 keys
,则 userOpBuilder
是必需的,如 ERC-7679 中所定义。 如果签名者类型为 account
,则 delegationManager
是必需的,如 ERC-7710 中所定义。
如果请求格式不正确或钱包无法/不愿意授予权限,则钱包必须返回一个错误,其代码如 ERC-1193 中所定义。
wallet_grantPermissions
响应示例:
PermissionResponse
对象的数组是 wallet_grantPermissions
RPC 期望的最终 result
字段。
[
{
// 带有修改的原始请求
chainId: 123,
address: '0x...'
expiry: 1577850000
signer: {
type: 'account',
data: {
address:'0x016562aA41A8697720ce0943F003141f5dEAe006',
}
},
permissions: [
{
type: 'native-token-transfer',
data: {
allowance: '0x1DCD65000000'
}
},
]
// 响应特定字段
context: "0x0x016562aA41A8697720ce0943F003141f5dEAe0060000771577157715"
}
]
wallet_revokePermissions
可以通过调用此方法来撤销权限,并且成功后钱包将以空响应作为回应。
type RevokePermissionsRequestParams = {
permissionContext: "0x{string}";
};
type RevokePermissionsResponseResult = {};
在此 ERC 中,我们指定了我们期望常用的签名者和权限列表。
此 ERC 未指定签名者或权限的详尽列表,因为我们希望随着钱包变得更加高级,会开发出更多的签名者和权限类型。 只要 DApp 和钱包都愿意支持,签名者或权限类型便是有效的。
但是,如果两个签名者或两个权限共享相同的类型名称,则 DApp 可能会请求一种类型的签名者或权限,而钱包会授予另一种。 因此,重要的是没有两个签名者或两个权限共享相同的类型。 因此,新的签名者或权限类型应在 ERC 中指定,可以作为对此 ERC 的修订,也可以在另一个 ERC 中指定。
// 钱包是这些权限的签名者
// 对于这种签名者类型,`data` 不是必需的,因为钱包既是签名者又是授权者
type WalletSigner = {
type: "wallet";
data: {};
};
// 以下 `key` 和 `keys` 签名者类型支持的密钥类型。
type KeyType = "secp256r1" | "secp256k1" | "ed25519" | "schnorr";
// 表示单个密钥的签名者。
// “Key”类型明确为 secp256r1 (p256) 或 secp256k1,并且公钥采用十六进制编码。
type KeySigner = {
type: "key";
data: {
type: KeyType;
publicKey: `0x${string}`;
};
};
// 表示多重签名者的签名者。
// `publicKeys` 的每个元素都明确地是相同的 `KeyType`,并且公钥采用十六进制编码。
type MultiKeySigner = {
type: "keys";
data: {
keys: {
type: KeyType;
publicKey: `0x${string}`;
}[];
};
};
// 可以使用 ERC-7710 中的权限授予的帐户。
type AccountSigner = {
type: "account";
data: {
address: `0x${string}`;
};
};
// 本机代币转账,例如以太坊上的 ETH
type NativeTokenTransferPermission = {
type: "native-token-transfer";
data: {
allowance: "0x..."; // 十六进制值
};
};
// ERC20 代币转账
type ERC20TokenTransferPermission = {
type: "erc20-token-transfer";
data: {
address: "0x..."; // erc20 合约
allowance: "0x..."; // 十六进制值
};
};
// ERC721 代币转账
type ERC721TokenTransferPermission = {
type: "erc721-token-transfer";
data: {
address: "0x..."; // erc721 合约
tokenIds: "0x..."[]; // 十六进制值数组
};
};
// ERC1155 代币转账
type ERC1155TokenTransferPermission = {
type: "erc1155-token-transfer";
data: {
address: "0x..."; // erc1155 合约
allowances: {
[tokenId: string]: "0x..."; // 十六进制值
};
};
};
// 会话中花费的总 gas 限制
type GasLimitPermission = {
type: "gas-limit";
data: {
limit: "0x..."; // 十六进制值
};
};
// 会话总共可以进行的调用次数
type CallLimitPermission = {
type: "call-limit";
data: {
count: number;
};
};
// 会话在每个时间间隔内可以进行的调用次数
type RateLimitPermission = {
type: "rate-limit";
data: {
count: number; // 每个时间间隔内的次数
interval: number; // 以秒为单位
};
};
如果签名者被指定为 wallet
,则钱包本身管理会话。 如果钱包批准该请求,则它必须接受具有 permissions
功能的 ERC-5792 的 wallet_sendCalls
,其中可以包含具有 permissionsContext
的会话。 例如:
[
{
version: "1.0",
chainId: "0x01",
from: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
calls: [
{
to: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
value: "0x9184e72a",
data: "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675",
},
{
to: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
value: "0x182183",
data: "0xfbadbaf01",
},
],
capabilities: {
permissions: {
context: "<permissionContext>",
},
},
},
];
收到此请求后,钱包必须根据请求的权限发送调用。 钱包不应要求用户进行进一步的交易确认。
如果钱包支持 ERC-5792,则钱包应该使用 permissions
密钥响应 wallet_getCapabilities
请求。
钱包应该在响应中包含 signerTypes
(string[]
) 和 permissionTypes
(string[]
),以指定其支持的签名者类型和权限类型。
示例:
{
"0x123": {
"permissions": {
"supported": true,
"signerTypes": ["keys", "account"],
"keyTypes": ["secp256k1", "secp256r1"],
"permissionTypes": ["erc20-token-transfer", "erc721-token-transfer"]
}
}
}
如果钱包正在使用 CAIP-25 授权,则钱包应该在 CAIP-25 sessionProperties
对象中包含 permissions
密钥。 要包含的其他密钥是带有逗号分隔的支持权限类型列表的 permissionTypes
和带有逗号分隔的支持签名者类型列表的 signerTypes
。
示例:
{
//...
"sessionProperties": {
"permissions": "true",
"signerTypes": "keys,account",
"permissionTypes": "erc20-token-transfer,erc721-token-transfer"
}
}
Key
类型签名者的 ERC-7679wallet_grantPermissions
在 signerMeta
字段中回复 permissionsContext
和 userOpBuilder
地址。 DApp 可以将该数据与 ERC-7679 提供的方法一起使用来构建 ERC-4337 userOp。
ERC-7679 UserOp Builder 合约在其所有方法中定义了 bytes calldata context
参数。 它等效于 wallet_grantPermissions
调用返回的 permissionsContext
。
使用 ERC-7679 UserOp Builder 格式化 userOp 签名的示例
const getSignature = async ({
address,
userOp,
permissionsContext,
}: GetSignatureArgs) => {
return readContract(config, {
abi: BUILDER_CONTRACT_ABI,
address: BUILDER_CONTRACT_ADDRESS,
functionName: "getSignature",
args: [address, userOp, permissionsContext],
});
};
整个流程的示例:
sequenceDiagram
participant D as DApp
participant W as 钱包
actor U as 用户
participant SA as 智能账户
D ->>+ W: wallet_grantPermissions
W ->>+ W: 构建权限对象
W ->>+ U: 显示权限 UI
U ->>+ W: 批准并签署权限对象 <br> 以授予权限
W ->>+ D: 返回 wallet_grantPermissions 响应
D ->>+ D: 存储 permissionsContext
Note left of D: DApp 具有权限上下文 <br> 并且正在寻找发送 UserOp
D ->>+ D: 构建 UserOp 并使用会话密钥签名
alt 利用 ERC-7679 UserOpBuilder
D ->>+ SA: getNonce (带有 permissionsContext)
D ->>+ D: 使用 nonce 更新 UserOp
D ->>+ SA: getCallData (带有 permissionsContext)
D ->>+ D: 使用 callData 更新 UserOp
D ->>+ SA: getSignature (带有 permissionsContext)
D ->>+ D: 使用签名更新 UserOp
end
D ->>+ D: 发送 UserOperation
当请求 type
为 account
的权限时,返回的数据可以使用 ERC-7710 中指定的接口进行兑换。 这允许权限的接收者使用任何帐户类型(EOA 或合约)来形成交易或 UserOp,并使用他们喜欢的任何付款或中继基础设施,方法是向返回的 permissions.signerMeta.delegationManager
发送内部消息并调用其 function redeemDelegation(bytes calldata _data, Action calldata _action) external;
函数,其中 _data
参数设置为返回的 permissions.permissionsContext
,并且 _action
数据形成权限接收者希望用户的帐户发出的消息,如以下结构体所定义:
struct Action {
address to;
uint256 value;
bytes data;
}
以下是一个以这种方式使用权限的简单伪代码示例,给定同一上下文中的两个 ethers 签名者,其中 alice
想要向 bob
请求权限:
// Alice 向 Bob 请求权限
const permissionsResponse = await bob.request({
method: 'wallet_grantPermissions',
params: [{
address: bob.address,
chainId: 123,
signer: {
type: 'account',
data: {
id: alice.address
}
},
permissions: [
{
type: 'native-token-transfer',
data: {
allowance: '0x0DE0B6B3A7640000'
},
},
{
type: 'gas-limit';
data: {
limit: '0x0186A0',
},
},
],
expiry: Math.floor(Date.now() / 1000) + 3600 // 从现在开始 1 小时
}]
});
// 提取 permissionsContext 和 delegationManager
const permissionsContext = permissionsResponse.permissionsContext;
const delegationManager = permissionsResponse.signerMeta.delegationManager;
// Alice 形成她希望 Bob 的帐户采取的操作
const action = {
to: alice.address,
value: '0x06F05B59D3B20000'
data: '0x'
};
// Alice 通过调用 Bob 的帐户上的 redeemDelegation 来发送交易
const tx = await bob.sendTransaction({
to: delegationManager,
data: bob.interface.encodeFunctionData('redeemDelegation', [
permissionsContext,
action
])
});
建议交易 => 批准交易 => 发送交易
的典型交易流程在几个方面具有很大的局限性:
用户必须在线才能发送交易。 当用户离线时,DApp 无法为用户发送交易,这使得订阅或自动交易等用例成为不可能。
用户必须手动批准每笔交易,这会中断原本可以流畅的用户体验。
通过此 ERC,DApp 可以请求钱包授予权限并代表用户执行交易,从而规避上述问题。
permissionsContext
由于此 ERC 仅指定了钱包和 DApp 之间的交互,而没有指定钱包如何强制执行权限,因此我们需要一种灵活的方式让钱包将信息传递给 DApp,以便它可以构建赋予权限的交易。
permissionsContext
字段旨在成为一个最大限度地灵活的不透明字符串,并且可以为不同的权限方案编码任意信息。 我们特别考虑了三种方案:
permissionsContext
用作与 UserOp 构建器交互时的 context
参数。permissionsContext
用作与委托管理器交互时的 _data
。wallet_sendCalls
时,它会将 permissionContext
用作会话的标识符。随着钱包技术的进步,我们希望开发出新型的签名者和权限。 我们曾考虑强制要求每个签名者和权限都必须具有 UUID,以避免冲突,但最终决定暂时坚持使用更简单的方法,即简单地强制要求这些类型在 ERC 中定义。
不支持 wallet_grantPermissions
的钱包应该在调用 JSON-RPC 方法时返回错误消息。
DApp 应该只请求他们需要的权限,并设置合理的到期时间。
钱包必须正确执行权限。 最终,用户必须相信他们的钱包软件已正确实现,并且权限应被视为钱包实现的一部分。
恶意 DApp 可能会冒充合法应用程序并诱骗用户授予广泛的权限。 钱包必须向用户清楚地显示这些权限,并警告他们不要授予危险的权限。
通过 CC0 放弃版权和相关权利。
- 原文链接: github.com/ethereum/ERCs...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!