从钱包授予权限

  • ethereum
  • 发布于 2025-02-21 14:52
  • 阅读 11

该文档定义了一个新的 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-7679ERC-7710 中定义的 context。 有关详细信息,请参见“原理”部分。

accountMeta 是可选的,但如果存在,则 factoryfactoryData 的字段是必需的,如 ERC-4337 中所定义。 它们要么都指定,要么都不指定。 如果帐户尚未部署,则钱包必须返回 accountMeta,并且 DApp 必须通过使用 factoryData 作为 calldata 调用 factory 合约来部署帐户。

signerMeta 取决于帐户类型。 如果签名者类型为 wallet,则不是必需的。 如果签名者类型为 keykeys,则 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-5792wallet_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-7679

wallet_grantPermissionssignerMeta 字段中回复 permissionsContextuserOpBuilder 地址。 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

ERC-7710

当请求 typeaccount 的权限时,返回的数据可以使用 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 字段旨在成为一个最大限度地灵活的不透明字符串,并且可以为不同的权限方案编码任意信息。 我们特别考虑了三种方案:

  • 如果 DApp 利用 ERC-7679,则它可以将 permissionsContext 用作与 UserOp 构建器交互时的 context 参数。
  • 如果 DApp 利用 ERC-7710,则它可以将 permissionsContext 用作与委托管理器交互时的 _data
  • 如果 DApp 利用应用内会话,则在使用 wallet_sendCalls 时,它会将 permissionContext 用作会话的标识符。

非详尽的签名者和权限列表

随着钱包技术的进步,我们希望开发出新型的签名者和权限。 我们曾考虑强制要求每个签名者和权限都必须具有 UUID,以避免冲突,但最终决定暂时坚持使用更简单的方法,即简单地强制要求这些类型在 ERC 中定义。

向后兼容性

不支持 wallet_grantPermissions 的钱包应该在调用 JSON-RPC 方法时返回错误消息。

安全考虑

有限的权限范围

DApp 应该只请求他们需要的权限,并设置合理的到期时间。

钱包必须正确执行权限。 最终,用户必须相信他们的钱包软件已正确实现,并且权限应被视为钱包实现的一部分。

网络钓鱼攻击

恶意 DApp 可能会冒充合法应用程序并诱骗用户授予广泛的权限。 钱包必须向用户清楚地显示这些权限,并警告他们不要授予危险的权限。

版权

通过 CC0 放弃版权和相关权利。

  • 原文链接: github.com/ethereum/ERCs...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ethereum
ethereum
江湖只有他的大名,没有他的介绍。