Abstract

We define a new JSON-RPC method wallet_grantPermissions 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.

In the context of AA, there are multiple vendor-specific implementations of session keys, which are temporary keys imbued with specific permissions. However, since the implementations are vendor-specific, it’s impossible for DApps to “request” session keys from wallets in a unified way, regardless of the specific wallet implementations.

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.

wallet_grantPermissions

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

Permission schema

type PermissionRequest = {
  chainId: Hex; // hex-encoding of uint256
  address?: Address;
  expiry: number; // unix timestamp
  signer: {
    type: string; // enum defined by ERCs
    data: Record<string, any>;
  };
  permissions: {
    type: string; // enum defined by ERCs
    data: Record<string, any>;
  }[];
}[];

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

address identifies the account being targetted 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.

expiry is a UNIX timestamp (in seconds) that specifies the time by which this session MUST expire.

signer is a field that identifies the key or account associated with the permission or alternatively the wallet will manage the session. See the “Signers” section for details.

permissions defines the allowed behavior the signer can do on behalf of the account. See the “Permissions” section for details.

Request example:

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

[
    {
        chainId: 123,
        address: '0x...'
        expiry: 1577840461
        signer: {
            type: 'account',
            data: {
                address:'0x016562aA41A8697720ce0943F003141f5dEAe006',
            }
        },
        permissions: [
          {
            type: 'native-token-transfer',
            data: {
                allowance: '0x1DCD6500'
            }
          }
        ],
    }
]

Response Specification

type PermissionResponse = PermissionRequest & {
  context: Hex;
  accountMeta?: {
    factory: `0x${string}`;
    factoryData: `0x${string}`;
  };
  signerMeta?: {
    // 7679 userOp building
    userOpBuilder?: `0x${string}`;
    // 7710 delegation
    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 submitting userOps, and can contain non-identifying data as well. It MAY be the context as defined in ERC-7679 and ERC-7710. See “Rationale” for details.

accountMeta is optional but when present then fields for factory and factoryData are required as defined in ERC-4337. They are either both specified, or none. If the account has not yet been deployed, the wallet MUST return accountMeta, and the DApp MUST deploy the account by calling the factory contract with factoryData as the calldata.

signerMeta is dependent on the account type. If the signer type is wallet then it’s not required. If the signer type is key or keys then userOpBuilder is required as defined in ERC-7679. If the signer type is account then 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_grantPermissions response example:

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

[
    {
        // original request with modifications
        chainId: 123,
        address: '0x...'
        expiry: 1577850000
        signer: {
            type: 'account',
            data: {
                address:'0x016562aA41A8697720ce0943F003141f5dEAe006',
            }
        },
        permissions: [
          {
            type: 'native-token-transfer',
            data: {
                allowance: '0x1DCD65000000'
            }
          },
        ]
        // response-specific fields
        context: "0x0x016562aA41A8697720ce0943F003141f5dEAe0060000771577157715"
    }
]

wallet_revokePermissions

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

Request Specification

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

Response Specification

type RevokePermissionsResponseResult = {};

Signer & Permission Types

In this ERC, we specify a list of signers and permissions that we expect to be commonly used.

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

However, if two signers or two permissions share the same type name, a DApp could request with one type of signer or permission while the wallet grants another. Therefore, it’s important that no two signers or two permissions share the same type. Therefore, new signer or permission types should be specified in ERCs, either in this ERC as an amendment or in another ERC.

Signers

// A wallet is the signer for these permissions
// `data` is not necessary for this signer type as the wallet is both the signer and grantor of these permissions
type WalletSigner = {
  type: "wallet";
  data: {};
};

// The types of keys that are supported for the following `key` and `keys` signer types.
type KeyType = "secp256r1" | "secp256k1" | "ed25519" | "schnorr";

// A signer representing a single key.
// "Key" types are explicitly secp256r1 (p256) or secp256k1, and the public keys are hex-encoded.
type KeySigner = {
  type: "key";
  data: {
    type: KeyType;
    publicKey: `0x${string}`;
  };
};

// A signer representing a multisig signer.
// Each element of `publicKeys` are all explicitly the same `KeyType`, and the public keys are hex-encoded.
type MultiKeySigner = {
  type: "keys";
  data: {
    keys: {
      type: KeyType;
      publicKey: `0x${string}`;
    }[];
  };
};

// An account that can be granted with permissions as in ERC-7710.
type AccountSigner = {
  type: "account";
  data: {
    address: `0x${string}`;
  };
};

Permissions

// Native token transfer, e.g. ETH on Ethereum
type NativeTokenTransferPermission = {
  type: "native-token-transfer";
  data: {
    allowance: "0x..."; // hex value
  };
};

// ERC20 token transfer
type ERC20TokenTransferPermission = {
  type: "erc20-token-transfer";
  data: {
    address: "0x..."; // erc20 contract
    allowance: "0x..."; // hex value
  };
};

// ERC721 token transfer
type ERC721TokenTransferPermission = {
  type: "erc721-token-transfer";
  data: {
    address: "0x..."; // erc721 contract
    tokenIds: "0x..."[]; // hex value array
  };
};

// ERC1155 token transfer
type ERC1155TokenTransferPermission = {
  type: "erc1155-token-transfer";
  data: {
    address: "0x..."; // erc1155 contract
    allowances: {
      [tokenId: string]: "0x..."; // hex value
    };
  };
};

// The maximum gas limit spent in the session in total
type GasLimitPermission = {
  type: "gas-limit";
  data: {
    limit: "0x..."; // hex value
  };
};

// The number of calls the session can make in total
type CallLimitPermission = {
  type: "call-limit";
  data: {
    count: number;
  };
};

// The number of calls the session can make during each interval
type RateLimitPermission = {
  type: "rate-limit";
  data: {
    count: number; // the number of times during each interval
    interval: number; // in seconds
  };
};

Wallet-managed Sessions

If the signer is specified as wallet, then the wallet itself manages the session. If the wallet approves the request, it MUST accept ERC-5792’s wallet_sendCalls with the permissions capability, which MAY include the session with a permissionsContext. For example:

[
  {
    version: "1.0",
    chainId: "0x01",
    from: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
    calls: [
      {
        to: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        value: "0x9184e72a",
        data: "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675",
      },
      {
        to: "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        value: "0x182183",
        data: "0xfbadbaf01",
      },
    ],
    capabilities: {
      permissions: {
        context: "<permissionContext>",
      },
    },
  },
];

Upon receiving this request, the wallet MUST send the calls in accordance with the requested permissions. The wallet SHOULD NOT ask the user for further transaction confirmations.

Capabilities

If the wallet supports ERC-5792, wallet SHOULD respond on wallet_getCapabilities request using the permissions key.

The wallet SHOULD include signerTypes (string[]) and permissionTypes (string[]) in the response, to specify the signer types and permission types it supports. Example:

{
  "0x123": {
    "permissions": {
      "supported": true,
      "signerTypes": ["keys", "account"],
      "keyTypes": ["secp256k1", "secp256r1"],
      "permissionTypes": ["erc20-token-transfer", "erc721-token-transfer"]
    }
  }
}

If the wallet is using CAIP-25 authorization, wallet SHOULD include permissions key in the CAIP-25 sessionProperties object. Additional keys to include are permissionTypes with the comma separated list of supported permission types and signerTypes with the comma separated list of supported signer types.

Example:

{
  //...
  "sessionProperties": {
    "permissions": "true",
    "signerTypes": "keys,account",
    "permissionTypes": "erc20-token-transfer,erc721-token-transfer"
  }
}

Sending transaction with a session

ERC-7679 with Key type signer

wallet_grantPermissions replies with permissionsContext and userOpBuilder address inside the signerMeta field. DApps can use that data with methods provided by ERC-7679 to build the ERC-4337 userOp.

ERC-7679 UserOp Builder contract defines bytes calldata context parameter in all of its methods. It’s equivalent to thepermissionsContext returned by the wallet_grantPermissions call.

Example of formatting userOp signature using the ERC-7679 UserOp Builder

const getSignature = async ({
  address,
  userOp,
  permissionsContext,
}: GetSignatureArgs) => {
  return readContract(config, {
    abi: BUILDER_CONTRACT_ABI,
    address: BUILDER_CONTRACT_ADDRESS,
    functionName: "getSignature",
    args: [address, userOp, permissionsContext],
  });
};

Example of the entire flow:

ERC-7679 Flow

ERC-7710

When requesting permissions with a type of account, the returned data will be redeemable 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.signerMeta.delegationManager and calling its function redeemDelegation(bytes calldata _data, Action calldata _action) external; function with the _data parameter set to the returned permissions.permissionsContext, and the _action data forming the message that the permissions recipient desires the user’s account to emit, as defined by this struct:

struct Action {
    address to;
    uint256 value;
    bytes data;
}

A simple pseudocode example of using a permission in this way, given two ethers signers in the same context, where alice wants to request a permission from bob might be like this:

// Alice requests a permission from 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 hour from now
  }]
});

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

// Alice forms the action she wants Bob's account to take
const action = {
  to: alice.address,
  value: '0x06F05B59D3B20000'
  data: '0x'
};

// Alice sends the transaction by calling redeemDelegation on Bob's account
const tx = await bob.sendTransaction({
  to: delegationManager,
  data: bob.interface.encodeFunctionData('redeemDelegation', [
    permissionsContext,
    action
  ])
});

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. We specifically had three schemes in mind:

  • If a DApp leverages ERC-7679, it could use permissionsContext as the context parameter when interacting with the UserOp builder.
  • If a DApp leverages ERC-7710, it could use permissionsContext as the _data when interacting with the delegation manager.
  • If a DApp leverages in-app sessions, it would use permissionContext as an identifier of the session when using wallet_sendCalls.

Non-exhaustive list of signers and permissions

With the advancement in wallet technologies, we expect new types of signers and permissions to be developed. We considered mandating that each signer and permission 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_grantPermissions SHOULD return an error message if the JSON-RPC method is called.

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.