Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7715: Request Permissions from Wallets

Adds JSON-RPC method for requesting permissions from a wallet

Authors Luka Isailovic (@lukaisailovic), Derek Rein (@arein), Dan Finlay (@danfinlay), Derek Chiang (@derekchiang), Fil Makarov (@filmakarov), Pedro Gomes (@pedrouid), Conner Swenberg (@ilikesymmetry), Lukas Rosario (@lukasrosario), Idris Bowman (@V00D00-child), Jeff Smale (@jeffsmale90)
Created 2024-05-24
Discussion Link https://ethereum-magicians.org/t/erc-7715-grant-permissions-from-wallets/20100
Requires EIP-4337, EIP-7710

Abstract

We define a new JSON-RPC method wallet_requestExecutionPermissions for DApp to request a Wallet to grant permissions in order to execute transactions on the user’s behalf. This enables two use cases:

  • Executing transactions for users without a wallet connection.
  • Executing transactions for users with a wallet connection that is scoped with permissions.

Motivation

Currently most DApps implement a flow similar to the following:

Wallet Approve Flow

Each interaction requires the user to sign a transaction with their wallet. The problems are:

  • It can get tedious for the user to manually approve every transaction, especially in highly-interactive applications such as games.
  • It’s impossible to send transactions for users without an active wallet connection. This invalidates use cases such as subscriptions, passive investments, limit orders, and more.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.

Permission Types, Rule Types

This ERC does not specify an exhaustive list of rule or permission types, since we expect more rule and permission types to be developed as wallets get more advanced. A permission type, or rule type is valid as long as both the DApp and the wallet are willing to support it.

However, if two permissions or two rules share the same type name, a DApp could request with one type of permission, or rule while the wallet grants another. Therefore, it’s important that no two permissions, or two rules share the same type. Furthermore, new permission types or rule types should be specified in addition ERCs. In all cases, these new types MUST inherit from the BasePermission or BaseRule scheme.

Permissions

isAdjustmentAllowed defines a boolean value that allows DApp to define whether the Wallet MAY attenuate(reduce or increase) the authority of a “permission” to meet the user’s terms for approval.

For example, a DApp may require an allowance for a specific asset to complete a payment and does not want the user to adjust the requested allowance.

type BasePermission = {
  type: string; // enum defined by ERCs
  isAdjustmentAllowed: boolean; // whether the wallet MAY attenuate the permission
  data: Record<string, any>; // specific to the type, structure defined by ERCs
};

Rules

type BaseRule = {
  type: string; // enum defined by ERCs
  data: Record<string, any>; // specific to the type, structure defined by ERCs
};

// Constrains a permission so that it is only valid until a specified timestamp.
type ExpiryRule = BaseRule & {
  type: "expiry";
  data: {
    timestamp: number; // unix timestamp at which the permission becomes invalid
  };
};

wallet_requestExecutionPermissions

We introduce a wallet_requestExecutionPermissions method for the DApp to request the Wallet to grant permissions.

Request Specification

type PermissionRequest = {
  chainId: Hex; // hex-encoding of uint256
  from?: Address;
  to: Address;
  permission: {
    type: string; // enum defined by ERCs
    isAdjustmentAllowed: boolean; // whether the permission can be adjusted
    data: Record<string, any>; //specific to the type, structure defined by ERCs
  };
  rules?: {
    type: string; // enum defined by ERCs
    data: Record<string, any>; // specific to the type, structure defined by ERCs
  }[];
}[];

chainId defines the chain with EIP-155 which applies to this permission request and all addresses can be found defined by other parameters.

from identifies the account being targeted for this permission request which is useful when a connection has been established and multiple accounts have been exposed. It is optional to let the user choose which account to grant permission for.

to is a field that identifies the DApp session account associated with the permission

permission defines the allowed behavior the to account can do on behalf of the from account. See the “Permission” section for details.

rules define the restrictions or conditions that a to account MUST abide by when using a permission to act on behalf of an account. See the “Rule” section for details.

Request example:

An array of PermissionRequest objects is the final params field expected by the wallet_requestExecutionPermissions RPC.

[
  {
    chainId: "0x01",
    from: "0x...",
    to: "0x016562aA41A8697720ce0943F003141f5dEAe006",
    permission: {
      type: "native-token-allowance",
      isAdjustmentAllowed: false,
      data: {
        allowance: "0x1DCD6500",
      },
    },
    rules: [
      {
        type: "expiry",
        data: {
          timestamp: 1577840461,
        },
      },
    ],
  },
];

Response Specification

type PermissionResponse = PermissionRequest & {
  context: Hex;
  dependencies: {
    factory: `0x${string}`;
    factoryData: `0x${string}`;
  }[];
  delegationManager: `0x${string}`;
};

First note that the response contains all of the parameters of the original request and it is not guaranteed that the values received are equivalent to those requested.

context is a catch-all to identify a permission for revoking permissions or redeeming permissions, and can contain non-identifying data as well. The context is required as defined in ERC-7710. See “Rationale” for details.

dependencies is an array of objects, each containing fields for factory and factoryData as defined in ERC-4337. Either both factory and factoryData must be specified in an entry, or neither. This array is used describe accounts that are not yet deployed but MUST be deployed in order for a permission to be successfully redeemed. If any of the involved accounts have not yet been deployed, the wallet MUST return the corresponding dependencies. If all accounts have already been deployed, the wallet MUST return an empty dependencies array. The DApp MUST deploy each account by calling the factory contract with factoryData as the calldata.

delegationManager is required as defined in ERC-7710.

If the request is malformed or the wallet is unable/unwilling to grant permissions, wallet MUST return an error with a code as defined in ERC-1193.

wallet_requestExecutionPermissions response example:

An array of PermissionResponse objects is the final result field expected by the wallet_requestExecutionPermissions RPC.

[
  {
    // original request with modifications
    chainId: "0x01",
    from: "0x...",
    to: "0x016562aA41A8697720ce0943F003141f5dEAe006",
    permission: {
      type: "native-token-allowance",
      isAdjustmentAllowed: true,
      data: {
        allowance: "0x1DCD65000000",
      },
    },
    // response-specific fields
    context: "0x0x016562aA41A8697720ce0943F003141f5dEAe0060000771577157715",
    dependencies: [
      {
        factory: "0x...",
        factoryData: "0x...",
      },
    ],
    delegationManager: "0x...",
  },
];

wallet_revokeExecutionPermission

Permissions can be revoked by calling this method and the wallet will respond with an empty response when successful.

Request Specification

type RevokeExecutionPermissionRequestParams = {
  permissionContext: "0x{string}";
};

Response Specification

type GetPermissionsInfoResultParams = {
  chainIds: `0x${string}`[];
};

wallet_getSupportedExecutionPermissions

We introduce a wallet_getSupportedExecutionPermissions method for the Wallet to specify the permission types and rules types it supports.

Request Specification

Request example:

window.ethereum.request({
  "method": "wallet_getSupportedExecutionPermissions",
  "params": []
}): Promise<GetSupportedExecutionPermissionsResult>

Response Specification

The wallet SHOULD include an object keyed on supported permission types including ruleTypes (string[]) that can be applied to the permission.

type GetSupportedExecutionPermissionsResult = Record<
  "permission-type",
  {
    chainIds: `0x${string}`[];
    ruleTypes: string[];
  }
>; // Hex chain id

An object keyed on all permission types supported by the Wallet expected by the wallet_getSupportedExecutionPermissions RPC.

{
  "native-token-allowance": {
    "chainIds": ["0x123", "0x345"],
    "rulesTypes": ["expiry"]
  },
  "erc20-token-allowance": {
    "chainIds": ["0x123"],
    "rulesTypes": []
  },
  "erc721-token-allowance": {
    "chainIds": ["0x123"],
    "rulesTypes": ["expiry"]
  }
}

wallet_getGrantedExecutionPermissions

We introduce a wallet_getGrantedExecutionPermissions method for the DApp to retrieve previously granted permissions.

Request Specification

Request example:

window.ethereum.request({
  "method": "wallet_getGrantedExecutionPermissions",
  "params": []
}): Promise<PermissionResponses[]>

Response Specification

The wallet MUST include all granted permissions that are not yet revoked.

type PermissionResponses;

Example:

[
  {
    chainId: "0x01",
    from: "0x...",
    to: "0x016562aA41A8697720ce0943F003141f5dEAe006",
    permission: {
      type: "native-token-allowance",
      isAdjustmentAllowed: true,
      data: {
        allowance: "0x1DCD65000000",
      },
    },
    context: "0x0x016562aA41A8697720ce0943F003141f5dEAe0060000771577157715",
    dependencies: [
      {
        factory: "0x...",
        factoryData: "0x...",
      },
    ],
    delegationManager: "0x...",
  },
];

Sending transaction to redeem permissions

The permission response data will be redeemable by the account defined in the to field, using the interfaces specified in ERC-7710. This allows the recipient of the permissions to use any account type (EOA or contract) to form a transaction or UserOp using whatever payment or relay infrastructure they prefer, by sending an internal message to the returned permissions.delegationManager and calling its function redeemDelegation(bytes[] calldata _permissionContexts, bytes32[] calldata _modes, bytes[] calldata _executionCallData) external; function with the _permissionContexts parameter set to the returned permissions.context, and the _executionCallData data forming the message that the permissions recipient desires the user’s account to emit, as defined by this struct:

struct Execution {
  address target;
  uint256 value;
  bytes callData;
}

A simple pseudocode example of using a permission in this way, where DApp wants to request a permission from bob might be like this:

// Alice requests a permission from Bob
const permissionsResponse = await window.ethereum.request({
  method: 'wallet_requestExecutionPermissions',
  params: [{
    from: bob.address,
    chainId: "0x01",
    to: '0x_dapp_session_account',
    permission: {
      type: 'native-token-allowance',
      isAdjustmentAllowed: true,
      data: {
        allowance: '0x0DE0B6B3A7640000'
      },
    },
    rules: [
      {
        type: 'expiry';
        data: {
          timestamp: Math.floor(Date.now() / 1000) + 3600 // 1 hour from now
        },
      },
    ],
  }]
});

// Extract the permissionsContext and delegationManager
const permissionsContext = permissionsResponse.context;
const delegationManager = permissionsResponse.delegationManager;

// DApp forms the execution they want Bob's account to take
const execution = {
  target: bob.address,
  value: '0x06F05B59D3B20000',
  callData: '0x'
};
const encodedExecutionCalldata = encodePacked(
  ['address', 'uint256', 'bytes'],
  [execution.target, execution.value, execution.callData],
);

// Chose execution mode (SingleDefault)
const executionMode = '0x0000000000000000000000000000000000000000000000000000000000000000';

// DApp sends the transaction by calling redeemDelegation on with encode execution on Bob's account
const tx = await dapp.sendTransaction({
  to: delegationManager,
  data: encodeFunctionData({
    abi: DelegationManager.abi,
    functionName: 'redeemDelegations',
    args: [
      [permissionsContext],
      [executionMode],
      [encodedExecutionCalldata],
    ],
  })
});

Example of the entire flow:

sequenceDiagram
  participant DApp
  participant Provider as window.ethereum
  participant Wallet
  participant User
  participant Chain as Relay infrastructure


  Note over DApp: DApp discovers supported permission and rules types

  DApp->>Provider: request({method: "wallet_getSupportedExecutionPermissions", params: []})
  Provider->>Wallet: wallet_getSupportedExecutionPermissions

  Wallet->>DApp: Returns supported permission and rules types

  Note over DApp: DApp triggers permissions request

  DApp->>Provider: request({method: "wallet_requestExecutionPermissions", params: [ PermissionRequest[] ]})
  Provider->>Wallet: wallet_requestExecutionPermissions

  Wallet->>User: Display permission request<br/> (permissions, rules, to = account)
  User-->>Wallet: Approve or reject

  Wallet-->>Provider: PermissionResponse[]<br/>includes context,<br/>delegationManager,<br/>dependencies
  Provider-->>DApp: PermissionResponse[]

  alt Undeployed user account(s)
      DApp->>Chain: Deploy via factory using dependencies
      Chain-->>DApp: Deployment success
  end

  Note over DApp: DApp forms Action calldata<br/>to be executed by user's account

  DApp->>Chain: sendTransaction({<br/> to: delegationManager,<br/> data: redeemDelegations([context], [executionMode], [encodedAction])<br/>})

  Chain-->>DApp: tx receipt

Rationale

The typical transaction flow of suggesting transactions => approving transactions => sending transactions is deeply limiting in several ways:

  • Users must be online to send transactions. DApps cannot send transactions for users when they are offline, which makes use cases such as subscriptions or automated trading impossible.

  • Users must manually approve every transaction, interrupting what could otherwise be a smooth user experience.

With this ERC, DApps can request Wallets to grant permissions and execute transactions on the user’s behalf, therefore circumventing the issues above.

permissionsContext

Since this ERC only specifies the interaction between the wallet and the DApp but not how the wallet enforces permissions, we need a flexible way for the wallet to pass along information to the DApp so that it can construct transactions that imbue the permissions.

The permissionsContext field is meant to be an opaque string that’s maximally flexible and can encode arbitrary information for different permissions schemes.

DApps must submit transactions with the account specified in the to field, using the permissionsContext as the _data when interacting with the delegation manager.

Non-exhaustive list of permissions and rules

With the advancement in wallet technologies, we expect new types of permissions and rules to be developed. We considered mandating that each permission and rule must have a UUID in order to avoid collisions, but ultimately decided to stick with the simpler approach for now of simply mandating that these types be defined in ERCs.

Backwards Compatibility

Wallets that don’t support wallet_requestExecutionPermissions SHOULD return an error message if the JSON-RPC method is called.

Reference Implementation

For a minimal reference implementation focusing on permission granting from a EIP-1193 Ethereum provider, please see Example7715PermissionsRequestHandler.

For a complete reference implementation of a Permissions handler, see the MetaMask Permissions Snap, which includes features such as:

  • Support for commonly used permission and rule types with ability to attenuate(reduce or increase) the requested capability to meet the user’s terms for approval.
  • User encrypted storage for all permissions granted through the Wallet handler to enable revocation mechanisms.

Security Considerations

Limited Permission Scope

DApps should only request the permissions they need, with a reasonable expiration time.

Wallets MUST correctly enforce permissions. Ultimately, users must trust that their wallet software is implemented correctly, and permissions should be considered a part of the wallet implementation.

Phishing Attacks

Malicious DApps could pose as legitimate applications and trick users into granting broad permissions. Wallets MUST clearly display the permissions to users and warn them against granting dangerous permissions.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Luka Isailovic (@lukaisailovic), Derek Rein (@arein), Dan Finlay (@danfinlay), Derek Chiang (@derekchiang), Fil Makarov (@filmakarov), Pedro Gomes (@pedrouid), Conner Swenberg (@ilikesymmetry), Lukas Rosario (@lukasrosario), Idris Bowman (@V00D00-child), Jeff Smale (@jeffsmale90), "ERC-7715: Request Permissions from Wallets [DRAFT]," Ethereum Improvement Proposals, no. 7715, May 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7715.