Alert Source Discuss
⚠️ Draft Standards Track: ERC

ERC-7677: Paymaster Web Service Capability

A way for apps to communicate with smart wallets about paymaster web services

Authors Lukas Rosario (@lukasrosario), Dror Tirosh (@drortirosh), Wilson Cusack (@wilsoncusack), Kristof Gazso (@kristofgazso), Hazim Jumali (@hazim-j)
Created 2024-04-03
Discussion Link https://ethereum-magicians.org/t/erc-7677-paymaster-web-service-capability/19530
Requires EIP-4337, EIP-5792

Abstract

With EIP-5792, apps can communicate with wallets about advanced features via capabilities. This proposal defines a capability that allows apps to request that ERC-4337 wallets communicate with a specified paymaster web service. To support this, we also define a standardized API for paymaster web services.

Motivation

App developers want to start sponsoring their users’ transactions using paymasters. Paymasters are commonly used via web services. However, there is currently no way for apps to tell wallets to communicate with a specific paymaster web service. Similarly, there is no standard for how wallets should communicate with these services. We need both a way for apps to tell wallets to communicate with a specific paymaster web service and a communication standard for wallets to do so.

Specification

One new EIP-5792 wallet capability is defined. We also define a standard interface for paymaster web services as a prerequisite.

Paymaster Web Service Interface

We define two JSON-RPC methods to be implemented by paymaster web services.

pm_getPaymasterStubData

Returns stub values to be used in paymaster-related fields of an unsigned user operation for gas estimation. Accepts an unsigned user operation, entrypoint address, chain id, and a context object. Paymaster service providers can define fields that app developers should use in the context object.

This method MAY return paymaster-specific gas values if applicable to the provided EntryPoint version. For example, if provided with EntryPoint v0.7, this method MAY return paymasterVerificationGasLimit and paymasterPostOpGasLimit values. The wallet SHOULD use these provided gas values when submitting the UserOperation to a bundler for gas estimation.

This method MAY also return a sponsor object with a name field and an optional icon field. The name field is the name of the party sponsoring the transaction, and the icon field is a URI pointing to an image. Wallet developers MAY choose to display sponsor information to users. The icon string MUST be a data URI as defined in [RFC-2397]. The image SHOULD be a square with 96x96px minimum resolution. The image format is RECOMMENDED to be either lossless or vector based such as PNG, WebP or SVG to make the image easy to render on the wallet. Since SVG images can execute Javascript, wallets MUST render SVG images using the <img> tag to ensure no untrusted Javascript execution can occur.

There are cases where a call to just pm_getPaymasterStubData is sufficient to provide valid paymaster-related user operation fields, e.g. when the paymasterData does not contain a signature. In such cases, the second RPC call to pm_getPaymasterData (defined below) MAY be skipped, by returning isFinal: true by this RPC call.

Paymaster web services SHOULD do validations on incoming user operations during pm_getPaymasterStubData execution and reject the request at this stage if it would not sponsor the operation.

pm_getPaymasterStubData RPC Specification

Note that the user operation parameter does not include a signature, as the user signs after all other fields are populated.

// [userOp, entryPoint, chainId, context]
type GetPaymasterStubDataParams = [
  // Below is specific to Entrypoint v0.6 but this API can be used with other entrypoint versions too
  {
    sender: `0x${string}`;
    nonce: `0x${string}`;
    initCode: `0x${string}`;
    callData: `0x${string}`;
    callGasLimit: `0x${string}`;
    verificationGasLimit: `0x${string}`;
    preVerificationGas: `0x${string}`;
    maxFeePerGas: `0x${string}`;
    maxPriorityFeePerGas: `0x${string}`;
  }, // userOp
  `0x${string}`, // EntryPoint
  `0x${string}`, // Chain ID
  Record<string, any> // Context
];

type GetPaymasterStubDataResult = {
  sponsor?: { name: string; icon?: string } // Sponsor info
  paymaster?: string // Paymaster address (entrypoint v0.7)
  paymasterData?: string; // Paymaster data (entrypoint v0.7)
  paymasterVerificationGasLimit?: `0x${string}`; // Paymaster validation gas (entrypoint v0.7)
  paymasterPostOpGasLimit?: `0x${string}`; // Paymaster post-op gas (entrypoint v0.7)
  paymasterAndData?: string; // Paymaster and data (entrypoint v0.6)
  isFinal?: boolean; // Indicates that the caller does not need to call pm_getPaymasterData
}
pm_getPaymasterStubData Example Parameters
[
  {
    "sender": "0x...",
    "nonce": "0x...",
    "initCode": "0x",
    "callData": "0x...",
    "callGasLimit": "0x...",
    "verificationGasLimit": "0x...",
    "preVerificationGas": "0x...",
    "maxFeePerGas": "0x...",
    "maxPriorityFeePerGas": "0x...",
  },
  "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
  "0x2105",
  {
    // Illustrative context field. These should be defined by service providers.
    "policyId": "962b252c-a726-4a37-8d86-333ce0a07299"
  }
]
pm_getPaymasterStubData Example Return Value

Paymaster services MUST detect which entrypoint version the account is using and return the correct fields.

For example, if using entrypoint v0.6:

{
  "sponsor": {
    "name": "My App",
    "icon": "https://..."
  },
  "paymasterAndData": "0x..."
}

If using entrypoint v0.7:

{
  "sponsor": {
    "name": "My App",
    "icon": "https://..."
  },
  "paymaster": "0x...",
  "paymasterData": "0x..."
}

If using entrypoint v0.7, with paymaster gas estimates:

{
  "sponsor": {
    "name": "My App",
    "icon": "https://..."
  },
  "paymaster": "0x...",
  "paymasterData": "0x...",
  "paymasterVerificationGasLimit": "0x...",
  "paymasterPostOpGasLimit": "0x..."
}

Indicating that the caller does not need to make a pm_getPaymasterData RPC call:

{
  "sponsor": {
    "name": "My App",
    "icon": "https://..."
  },
  "paymaster": "0x...",
  "paymasterData": "0x...",
  "isFinal": true
}

pm_getPaymasterData

Returns values to be used in paymaster-related fields of a signed user operation. These are not stub values and will be used during user operation submission to a bundler. Similar to pm_getPaymasterStubData, accepts an unsigned user operation, entrypoint address, chain id, and a context object.

pm_getPaymasterData RPC Specification

Note that the user operation parameter does not include a signature, as the user signs after all other fields are populated.

// [userOp, entryPoint, chainId, context]
type GetPaymasterDataParams = [
  // Below is specific to Entrypoint v0.6 but this API can be used with other entrypoint versions too
  {
    sender: `0x${string}`;
    nonce: `0x${string}`;
    initCode: `0x${string}`;
    callData: `0x${string}`;
    callGasLimit: `0x${string}`;
    verificationGasLimit: `0x${string}`;
    preVerificationGas: `0x${string}`;
    maxFeePerGas: `0x${string}`;
    maxPriorityFeePerGas: `0x${string}`;
  }, // userOp
  `0x${string}`, // Entrypoint
  `0x${string}`, // Chain ID
  Record<string, any> // Context
];

type GetPaymasterDataResult = {
  paymaster?: string // Paymaster address (entrypoint v0.7)
  paymasterData?: string; // Paymaster data (entrypoint v0.7)
  paymasterAndData?: string; // Paymaster and data (entrypoint v0.6)
}
pm_getPaymasterData Example Parameters
[
  {
    "sender": "0x...",
    "nonce": "0x...",
    "initCode": "0x",
    "callData": "0x...",
    "callGasLimit": "0x...",
    "verificationGasLimit": "0x...",
    "preVerificationGas": "0x...",
    "maxFeePerGas": "0x...",
    "maxPriorityFeePerGas": "0x...",
  },
  "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
  "0x2105",
  {
    // Illustrative context field. These should be defined by service providers.
    "policyId": "962b252c-a726-4a37-8d86-333ce0a07299"
  }
]
pm_getPaymasterData Example Return Value

Paymaster services MUST detect which entrypoint version the account is using and return the correct fields.

For example, if using entrypoint v0.6:

{
  "paymasterAndData": "0x..."
}

If using entrypoint v0.7:

{
  "paymaster": "0x...",
  "paymasterData": "0x..."
}

paymasterService Capability

The paymasterService capability is implemented by both apps and wallets.

App Implementation

Apps need to give wallets a paymaster service URL they can make the above RPC calls to. They can do this using the paymasterService capability as part of an EIP-5792 wallet_sendCalls call.

wallet_sendCalls Paymaster Capability Specification
type PaymasterCapabilityParams = {
  url: string;
  context: Record<string, any>;
}
wallet_sendCalls Example Parameters
[
  {
    "version": "1.0",
    "chainId": "0x01",
    "from": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
    "calls": [
      {
        "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        "value": "0x9184e72a",
        "data": "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"
      },
      {
        "to": "0xd46e8dd67c5d32be8058bb8eb970870f07244567",
        "value": "0x182183",
        "data": "0xfbadbaf01"
      }
    ],
    "capabilities": {
      "paymasterService": {
        "url": "https://...",
        "context": {
          "policyId": "962b252c-a726-4a37-8d86-333ce0a07299"
        }
      }
    }
  }
]

The wallet will then make the above paymaster RPC calls to the URL specified in the paymasterService capability field.

Wallet Implementation

To conform to this specification, smart wallets that wish to leverage app-sponsored transactions:

  1. MUST indicate to apps that they can communicate with paymaster web services via their response to an EIP-5792 wallet_getCapabilities call.
  2. SHOULD make calls to and use the values returned by the paymaster service specified in the capabilities field of an EIP-5792 wallet_sendCalls call. An example of an exception is a wallet that allows users to select a paymaster provided by the wallet. Since there might be cases in which the provided paymaster is ultimately not used—either due to service failure or due to a user selecting a different, wallet-provided paymaster—applications MUST NOT assume that the paymaster it provides to a wallet is the entity that pays for transaction fees.
wallet_getCapabilities Response Specification
type PaymasterServiceCapability = {
  supported: boolean;
}
wallet_getCapabilities Example Response
{
  "0x2105": {
    "paymasterService": {
      "supported": true
    },
  },
  "0x14A34": {
    "paymasterService": {
      "supported": true
    }
  }
}

Below is a diagram illustrating the full wallet_sendCalls flow, including how a wallet might implement the interaction.

flow

Rationale

Gas Estimation

The current loose standard for paymaster services is to implement pm_sponsorUserOperation. This method returns values for paymaster-related user operation fields and updated gas values. The problem with this method is that paymaster service providers have different ways of estimating gas, which results in different estimated gas values. Sometimes these estimates can be insufficient. As a result we believe it’s better to leave gas estimation up to the wallet, as the wallet has more context on how user operations will get submitted (e.g. which bundler they will get submitted to). Then wallets can ask paymaster services to sponsor given the estimates defined by the wallet.

The above reason is also why we specify that the pm_getPaymasterStubData method MAY also return paymaster-specific gas estimates. I.e., bundlers are prone to insufficiently estimating the paymaster-specific gas values, and the paymaster servies themselves are ultimately better equipped to provide them.

Chain ID Parameter

Currently, paymaster service providers typically provide developers with a URL per chain. That is, paymaster service URLs are not typically multichain. So why do we need a chain ID parameter? We recognize that we must specify some constraint so that wallets can communicate with paymaster services about which chain their requests are for. As we see it, there are two options:

  1. Formalize the current loose standard and require that paymaster service URLs are 1:1 with chains.
  2. Require a chain ID parameter as part of paymaster service requests.

We feel that option (2) is the better abstraction here. This allows service providers to offer multichain URLs if they wish at essentially no downside to providers who offer a URL per chain. Providers who offer a URL per chain would just need to accept an additional parameter that they can ignore. When an app developer who uses a URL-per-chain provider wants to submit a request to a different chain, they can just swap out the URL accordingly.

Challenges With Stub Data

Enabling a workflow with greater flexibility in gas estimations will nonetheless come with some known challenges that paymaster services must be aware of in order to ensure reliable gas estimates are generated during the process.

preVerificationGas

The preVerificationGas value is largely influenced by the size of the user operation and it’s ratio of zero to non-zero bytes. This can cause a scenario where pm_getPaymasterStubData returns values that results in upstream gas estimations to derive a lower preVerificationGas compared to what pm_getPaymasterData would require. If this occurs then bundlers will return an insufficient preVerificationGas error during eth_sendUserOperation.

To avoid this scenario, a paymaster service MUST return stub data that:

  1. Is of the same length as the final data.
  2. Has an amount of zero bytes (0x00) that is less than or equal to the final data.

Consistent Code Paths

In the naive case, a stub value of repeating non-zero bytes (e.g. 0x01) that is of the same length as the final value will generate a usable preVerificationGas. Although this would immediately result in a gas estimation error given that the simulation will likely revert due to an invalid paymaster data.

In a more realistic case, a valid stub can result in a successful simulation but still return insufficient gas limits. This can occur if the stub data causes validatePaymasterUserOp or postOp functions to simulate a different code path compared to the final value. For example, if the simulated code was to return early, the estimated gas limits would be less than expected which would cause upstream out of gas errors once a user operation is submitted to the bundler.

Therefore, a paymaster service MUST also return a stub that can result in a simulation executing the same code path compared to what is expected of the final user operation.

Security Considerations

The URLs paymaster service providers give to app developers commonly have API keys in them. App developers might not want to pass these API keys along to wallets. To remedy this, we recommend that app developers provide a URL to their app’s backend, which can then proxy calls to paymaster services. Below is a modified diagram of what this flow might look like.

flowWithAPI

This flow would allow developers to keep their paymaster service API keys secret. Developers might also want to do additional simulation / validation in their backends to ensure they are sponsoring a transaction they want to sponsor.

Copyright and related rights waived via CC0.

Citation

Please cite this document as:

Lukas Rosario (@lukasrosario), Dror Tirosh (@drortirosh), Wilson Cusack (@wilsoncusack), Kristof Gazso (@kristofgazso), Hazim Jumali (@hazim-j), "ERC-7677: Paymaster Web Service Capability [DRAFT]," Ethereum Improvement Proposals, no. 7677, April 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7677.