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:
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.
typeBasePermission={type:string;// enum defined by ERCsisAdjustmentAllowed:boolean;// whether the wallet MAY attenuate the permissiondata:Record<string,any>;// specific to the type, structure defined by ERCs};
Rules
typeBaseRule={type:string;// enum defined by ERCsdata:Record<string,any>;// specific to the type, structure defined by ERCs};// Constrains a permission so that it is only valid until a specified timestamp.typeExpiryRule=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
typePermissionRequest={chainId:Hex;// hex-encoding of uint256from?:Address;to:Address;permission:{type:string;// enum defined by ERCsisAdjustmentAllowed:boolean;// whether the permission can be adjusteddata:Record<string,any>;//specific to the type, structure defined by ERCs};rules?:{type:string;// enum defined by ERCsdata: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.
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.
An array of PermissionResponse objects is the final result field expected by the wallet_requestExecutionPermissions RPC.
[{// original request with modificationschainId:"0x01",from:"0x...",to:"0x016562aA41A8697720ce0943F003141f5dEAe006",permission:{type:"native-token-allowance",isAdjustmentAllowed:true,data:{allowance:"0x1DCD65000000",},},// response-specific fieldscontext:"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.
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:
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 BobconstpermissionsResponse=awaitwindow.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 delegationManagerconstpermissionsContext=permissionsResponse.context;constdelegationManager=permissionsResponse.delegationManager;// DApp forms the execution they want Bob's account to takeconstexecution={target:bob.address,value:'0x06F05B59D3B20000',callData:'0x'};constencodedExecutionCalldata=encodePacked(['address','uint256','bytes'],[execution.target,execution.value,execution.callData],);// Chose execution mode (SingleDefault)constexecutionMode='0x0000000000000000000000000000000000000000000000000000000000000000';// DApp sends the transaction by calling redeemDelegation on with encode execution on Bob's accountconsttx=awaitdapp.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.
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.