EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8409: Signed Service Payment Quotes

Provider-signed EVM payment quotes bound to a priced service request.

Authors
Created2026-09-04
Discussion Linkhttps://ethereum-magicians.org/t/erc-8409-signed-service-payment-quotes/29577
Requires

Markdown

https://raw.githubusercontent.com/ethereum/ERCs/re...
Pull Request#1990PR open

EIP-GPT summary

Contents
AbstractMotivationSpecificationDefinitionsQuote structureRequest bindingQuote identifierSingle-use consumptionValidityEIP-712 domainTyped dataSignature verificationPayer bindingVerifier procedureSettlement conformanceTransport bindingsJSON transport encodingRationaleWhy a separate ERCWhy request canonicalization is outside this ERCRequest commitment designQuote identity and offer groupingValidity without issuance timeWhy uint64 timestampsWhy verifyingContract is omittedWhy settlement mechanics are not signedRelationship to x402 Offer and ReceiptRelationship to HTTP Message SignaturesLightweight issuer verificationRelationship to payer authorizationsBackwards CompatibilityTest CasesEIP-712 encodingValidity and submission behaviorSingle-use application behaviorSecurity ConsiderationsProvider identityQuote signatures do not authorize fundsRequest-scheme completenessRequest transformationRequest privacyBearer quotesReplayQuote replacement and revocationSettlement expiryAdditional feesAsset identity and token behaviorCounterfactual issuersERC-1271 mutabilityDelegated accountsCopyright

Abstract

This ERC defines an EIP-712 format for a service provider to sign payment terms for a specific priced request.

A quote authenticates the issuer, optional payer, recipient, payment asset, exact amount, request commitment, quote identifier, validity window, and settlement chain. The request commitment is derived according to a separately identified request scheme, allowing the same quote format to be carried by HTTP payment protocols, agent protocols, wallet APIs, or other transports.

The quote does not authorize payer funds, execute settlement, establish provider identity, or prove service delivery.

Motivation

Machine-to-machine and agentic payment flows commonly separate two decisions:

  1. a provider states the terms under which it will perform a service; and
  2. a payer authorizes a payment that satisfies those terms.

Existing payment authorizations authenticate the second decision. They do not necessarily provide a transport-independent EVM object proving which terms the provider asserted for the concrete service request before the payer authorized payment.

Protocol-specific signed-offer mechanisms, including the x402 Offer and Receipt extension, are prior art for provider-authenticated payment terms. This ERC standardizes a smaller EVM interoperability primitive that can be carried by x402 or by other transports without depending on a particular x402 version or field layout. Its distinguishing properties are:

  • an EIP-712 provider signature;
  • an explicit commitment to the priced request;
  • optional binding to a known payer;
  • EVM settlement-chain and asset semantics; and
  • verification by both externally owned accounts (EOAs) and ERC-1271 contract accounts.

Request serialization is intentionally not standardized by this ERC. HTTP, Model Context Protocol (MCP), Agent2Agent (A2A), JSON-RPC, x402, or another protocol can define a request scheme appropriate to that protocol while reusing the same signed quote object.

This separation keeps the ERC focused on the EVM commercial artifact rather than defining another HTTP or agent-protocol canonicalization standard.

Specification

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

Definitions

  • Issuer: The service-provider account whose signature authenticates a quote.
  • Expected issuer: The issuer account that a verifier has independently associated with the service through an authenticated endpoint, registry, configuration, allowlist, or another trust mechanism.
  • Payer: The account whose assets fund the quoted commercial payment.
  • Recipient: The immediate account or contract designated to receive the quoted commercial payment.
  • Bearer quote: A quote whose payer is the zero address. The surrounding application selects the payer when settlement is authorized.
  • Request scheme: A named and versioned rule that defines the canonical bytes of the priced service request.
  • Request hash: The Keccak-256 commitment to the canonical request bytes defined by the request scheme.
  • Quote ID: An issuer-scoped identifier for one concrete signed quote.
  • Verification time: The time at which quote validity is checked. An on-chain verifier uses block.timestamp; an off-chain verifier uses a trusted local Unix clock.
  • Submission time: The time at which a settlement transaction or authorization is submitted to the selected settlement mechanism, at the submission event defined by the binding.
  • Execution deadline: A time limit enforced by the payer authorization or settlement mechanism when the payment executes. The quote does not itself enforce such a limit.

Quote structure

A conforming quote MUST contain the following fields in the stated order and with the stated types:

struct ServicePaymentQuote {
    address issuer;
    address payer;
    address recipient;
    address asset;
    uint256 amount;
    string requestScheme;
    bytes32 requestHash;
    bytes32 quoteId;
    uint64 validAfter;
    uint64 validUntil;
}

The fields have the following meanings:

FieldMeaning
issuerAccount whose signature authenticates the quote.
payerAccount permitted to fund the payment, or the zero address for a bearer quote.
recipientImmediate recipient of the commercial payment.
assetPayment token contract, or the zero address for the settlement chain's native asset.
amountExact nominal payment amount in the asset's smallest unit.
requestSchemeIdentifier of the canonical request-binding rules.
requestHashKeccak-256 commitment to the canonical priced request.
quoteIdIssuer-scoped identifier for this concrete quote.
validAfterUnix timestamp before which the quote is not valid.
validUntilUnix timestamp after which new authorization or settlement submission is not permitted.

issuer, recipient, amount, and quoteId MUST be non-zero.

validAfter MUST be less than or equal to validUntil.

The zero address in payer designates a bearer quote.

The zero address in asset designates the native asset of the settlement chain.

The quote defines an exact nominal commercial payment. Gas, relayer fees, facilitator fees, protocol fees, conversion fees, and other additional charges are outside amount and MUST be separately disclosed and authorized.

Request binding

requestScheme MUST identify a deterministic and versioned algorithm for deriving canonical request bytes from the service invocation being priced.

requestScheme MUST contain between 1 and 128 bytes when UTF-8 encoded and MUST consist only of visible ASCII bytes 0x21 through 0x7E.

A request scheme specification MUST define:

  1. the service or operation identity included in the canonical request;
  2. the request fields included in the canonical request;
  3. the exact canonical serialization;
  4. how equivalent and non-equivalent requests are distinguished; and
  5. any transport metadata that can change the meaning or price of the request.

A request scheme MUST bind enough context to distinguish two operations that could interpret the same payload differently.

This requirement does not by itself require distinct canonical bytes for separate invocations of the same operation with identical inputs. Applications using request commitments for single-use consumption MUST additionally satisfy the invocation-identity requirements below.

The request hash MUST be:

requestHash = keccak256(canonicalRequestBytes)

This ERC does not define any built-in request scheme.

Applications MAY define request schemes in separate ERCs, protocol specifications, or application specifications. Globally published schemes SHOULD use an identifier that avoids collisions, such as an absolute URI or a reverse-DNS-based name.

Two implementations have request-binding interoperability only when they support at least one common requestScheme. A transport binding claiming request-bound interoperability MUST select a request scheme understood by both issuer and verifier.

A verifier MUST NOT claim that a quote is bound to a request unless it supports the signed requestScheme, independently derives the canonical request bytes for the invocation being priced, recomputes requestHash, and obtains the signed value.

A component that verifies only the quote signature and payment fields MAY describe the quote as signature-valid, but MUST NOT describe the request binding as verified.

Quote identifier

quoteId identifies one concrete signed quote.

An issuer MUST NOT intentionally assign the same quoteId to two non-identical quote bodies.

An issuer SHOULD generate quoteId from at least 128 bits of entropy or from another issuer-side unique record.

quoteId does not imply one-time use, replacement, revocation, or grouping with other quotes.

A reissued quote MUST use a new quoteId.

This ERC deliberately does not define an offer-group identifier or a consumption protocol. Applications that present mutually exclusive payment alternatives or require single-use consumption MUST define and enforce that state outside this quote.

Single-use consumption

An application claiming single-use consumption MUST define:

  1. a logical invocation identity that remains stable across retries and reissued quotes for that invocation while distinguishing separate intentional invocations;
  2. the scope of shared consumption state, including the relevant service, issuer, request scheme, and payment alternatives;
  3. the component responsible for enforcing consumption and coordinating concurrent attempts; and
  4. whether the guarantee covers payment, service execution, or both, including how pending and failed attempts are handled.

requestHash MAY identify an invocation within that scope when the request scheme provides these properties. A request nonce used for this purpose MUST remain stable across retries and reissues for the same invocation and MUST differ for a new intentional invocation. Reissuing a quote does not by itself guarantee that its canonical request bytes remain unchanged.

Neither quoteId nor the pair (quoteId, requestHash) is sufficient by itself to identify shared consumption state across reissues, because a reissued quote has a new quoteId. An application using quote-specific records MUST associate them with the shared invocation state.

An application claiming to prevent duplicate payments MUST enforce that property across independent payer authorizations for the same invocation. Deduplicating service execution alone does not provide this guarantee.

Validity

A quote is valid at verification time t when:

validAfter <= t <= validUntil

validAfter and validUntil bound both creation of a new payer authorization based on the quote and new settlement submission. An integration MUST check the validity window immediately before each action. Creating an authorization within the window does not permit its first submission after validUntil.

A verifier MUST reject a quote before validAfter or after validUntil when accepting it for a new authorization or submission. Expiry does not invalidate historical evidence that the quote was accepted within its validity window.

validUntil does not cancel an already-submitted payment or require its inclusion in a block before expiry. Continued processing of the same submitted payment follows the binding's rules and the payment mechanism's own validity conditions. A retry that creates a new payer authorization or a distinct payment submission requires a currently valid quote.

A settlement mechanism that enforces quote validity at execution time MUST reject execution before validAfter or after validUntil. When the selected payer authorization or settlement mechanism contains its own payment deadline, that deadline MUST NOT exceed validUntil. Such a mechanism MAY impose a narrower execution window. Quote validity alone does not establish authorization validity or guarantee successful execution.

An on-chain component MAY verify only the signature and settlement fields; unless it can independently derive the canonical request according to requestScheme, it MUST NOT claim that the request binding has been verified.

An implementation SHOULD account for clock skew and expected submission and inclusion latency when selecting validity periods. Submitting earlier does not guarantee execution before a deadline.

EIP-712 domain

Quotes MUST use the following EIP-712 domain:

name:    "Signed Service Payment Quotes"
version: "1"
chainId: settlement chain ID

The domain type is:

EIP712Domain(string name,string version,uint256 chainId)

chainId MUST identify the chain on which asset and recipient are interpreted.

The domain intentionally omits verifyingContract because the quote is independent of a particular settlement contract.

A verifier MUST reject a quote whose domain chainId differs from the intended settlement chain.

Typed data

The canonical quote type string is:

ServicePaymentQuote(address issuer,address payer,address recipient,address asset,uint256 amount,string requestScheme,bytes32 requestHash,bytes32 quoteId,uint64 validAfter,uint64 validUntil)

The domain separator MUST be calculated according to EIP-712 using the domain defined above.

The quote struct hash MUST be calculated according to EIP-712. In particular, requestScheme is encoded as the Keccak-256 hash of its UTF-8 bytes as required for EIP-712 dynamic values.

The quote digest is:

quoteDigest = keccak256("\x19\x01" || domainSeparator || quoteStructHash)

Signature verification

A verifier MUST first establish expectedIssuer independently from the quote.

A quote MUST be rejected unless:

quote.issuer == expectedIssuer

For an issuer account with no code on the settlement chain, a verifier MUST validate a secp256k1 signature over quoteDigest and require the recovered address to equal issuer.

A 65-byte ECDSA signature MAY encode recovery parity as either 27/28 or 0/1. A verifier supporting both forms MUST normalize them before recovery.

ECDSA verification MUST reject zero r, zero s, and high-s signatures.

For an issuer account with code on the settlement chain, a verifier MUST call ERC-1271:

isValidSignature(quoteDigest, signature)

and accept only the ERC-1271 magic value:

0x1626ba7e

A verifier MUST NOT fall back to ECDSA recovery when an issuer is classified as an ERC-1271 account.

A verifier whose trust configuration explicitly pins an issuer authentication method MAY use that pinned method instead of classifying by code presence.

A verifier that has neither a pinned authentication method nor authenticated access to settlement-chain state MUST NOT treat the quote as authenticated.

Counterfactual contract issuers and wrapped counterfactual signatures are outside the scope of this ERC.

Payer binding

A non-zero payer binds the quote to that payer.

Before settlement, a verifier MUST determine the non-zero account whose assets fund the commercial payment.

For a non-zero quote.payer, the selected payer MUST equal quote.payer.

For a bearer quote, the surrounding application MUST explicitly permit bearer quotes and MUST bind the selected payer through the separate payer authorization or settlement mechanism.

A bearer quote does not authorize any account to spend funds.

Applications using bearer quotes for single-use or non-idempotent requests SHOULD include a client-generated nonce inside the request committed by requestHash.

Verifier procedure

Before treating a quote as authenticated terms for a concrete request, a verifier MUST:

  1. establish the expectedIssuer;
  2. check all structural field requirements;
  3. check the intended settlement chainId;
  4. check validAfter <= verificationTime <= validUntil;
  5. determine the effective payer and apply the payer-binding rules;
  6. support the signed requestScheme;
  7. independently derive the canonical priced request and verify requestHash;
  8. recompute quoteDigest;
  9. verify the issuer signature; and
  10. compare the settlement terms against the quote before payer authorization and before new submission.

The integration MUST repeat the time check immediately before each of these actions; an earlier verification result is insufficient if the quote has since expired.

Settlement conformance

The issuer signature authenticates provider terms. It does not authorize payer funds.

The payer MUST separately authorize the selected settlement mechanism.

A settlement integration claiming conformance with this ERC MUST ensure that the commercial payment matches:

  • the quote domain chainId;
  • the effective payer;
  • quote.asset;
  • quote.recipient; and
  • quote.amount.

For a bearer quote, the effective payer is the non-zero payer selected by the surrounding application and bound by the payer authorization.

A settlement integration MUST NOT replace the signed chain, asset, recipient, or amount merely because another payer authorization would be valid.

If the settlement uses an escrow, router, account-abstraction entry point, payment facilitator, or another mechanism whose behavior affects the payer, the payer authorization or application policy MUST separately authenticate those settlement semantics.

This ERC does not define a payment scheme, settlement contract, fee model, refund rule, or escrow lifecycle.

Transport bindings

This ERC does not define how a quote is transported.

A transport binding that carries this quote MUST define:

  1. how expectedIssuer is authenticated for the service;
  2. the allowed requestScheme values;
  3. where the quote and signature are carried;
  4. the bearer-quote policy;
  5. how the selected payer is bound to the eventual payer authorization;
  6. for each supported settlement path, the submission event and responsible component, including how forwarding or retransmission of an already-submitted payment is distinguished from a new submission; and
  7. whether an execution deadline exists and, if so, how it is enforced, its clock and boundary semantics, and how it relates to the quote validity window.

An integration MUST make the submission cutoff and any enforced execution deadline, or its absence, available to the payer before authorization. User interfaces SHOULD distinguish these limits and explain that pending payments may still execute after quote expiry when no execution deadline exists. Execution-deadline claims MUST be grounded in the authenticated settlement semantics, not inferred from validUntil alone.

An HTTP, x402, MCP, A2A, JSON-RPC, or other binding MAY reuse the same quote format without changing the EIP-712 schema.

JSON transport encoding

When this ERC is carried as a standalone JSON object, the envelope MUST contain the top-level members chainId, quote, and signature. chainId is the EIP-712 domain chain ID. The domain name and version are fixed by this ERC and are not repeated in the envelope.

Additional top-level members MAY be defined by transport bindings. They MUST NOT alter the interpretation of chainId, quote, or signature, and MUST NOT be treated as authenticated by the quote signature unless they are separately cryptographically bound. Generic implementations SHOULD ignore unrecognized top-level members.

The quote object MUST use the field names defined by ServicePaymentQuote exactly as written in the quote structure.

The example below uses the same quote as the canonical test vector in the Test Cases section:

{
  "chainId": "8453",
  "quote": {
    "issuer": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
    "payer": "0x2222222222222222222222222222222222222222",
    "recipient": "0x3333333333333333333333333333333333333333",
    "asset": "0x4444444444444444444444444444444444444444",
    "amount": "1000000",
    "requestScheme": "com.example.service/1",
    "requestHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "quoteId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
    "validAfter": "1788372000",
    "validUntil": "1788372300"
  },
  "signature": "0x2051fd043967b356866060fb9bf95e11b735f03cab03d0f7f6c736abbdf55a42311f85b1f7f34e4f1008a494fb10aed2965589f96fca3f99c2bb72994ab345e41c"
}

The JSON encoding MUST satisfy all of the following:

  • chainId and amount MUST be encoded as base-10 strings;
  • validAfter and validUntil MUST be encoded as base-10 strings;
  • base-10 integer strings MUST match 0|[1-9][0-9]* and decode within the range of their corresponding EIP-712 integer type;
  • addresses MUST be encoded as 0x followed by exactly 40 hexadecimal digits;
  • requestHash and quoteId MUST be encoded as 0x followed by exactly 64 hexadecimal digits; and
  • signature MUST be encoded as a 0x-prefixed even-length hexadecimal byte string.

Transport-level JSON key ordering, whitespace, and address letter case do not participate directly in the EIP-712 signature because the signature is computed from the parsed typed values.

Rationale

Why a separate ERC

Provider-signed offers already exist in payment protocols, including the x402 Offer and Receipt extension.

This ERC does not attempt to replace those protocol-specific offers. It standardizes an EVM artifact that can be reused by multiple transports and verified with the same EIP-712 and ERC-1271 semantics.

The interoperability target is therefore not "signed offers" in general. It is the representation and verification of a request-bound provider payment quote for EVM settlement.

An x402 binding can carry this quote, but an MCP, A2A, wallet, or direct application flow can carry the same quote without adopting x402 as its transport.

Why request canonicalization is outside this ERC

Different transports do not share one natural request representation.

HTTP has methods, targets, headers, and content. MCP and A2A have protocol-specific operation and payload structures. JSON-RPC has method and parameter semantics. Attempting to define all of those canonicalization rules inside one ERC would couple the quote format to unrelated transport standards and substantially increase implementation risk.

This ERC therefore signs two request-binding values:

  • requestScheme, identifying the canonicalization rules; and
  • requestHash, committing to the result.

The transport or application binding defines the scheme. Request-binding interoperability therefore requires issuer and verifier to implement at least one common scheme. Widely reusable schemes can be standardized separately without changing the quote format defined by this ERC.

This is analogous to separating a generic signed envelope from the protocol-specific data placed inside that envelope.

Request commitment design

Using separate commitments for the operation identifier and request payload would create two independent canonicalization surfaces and could permit combinations in which one component is bound while another semantically relevant component is omitted.

This ERC requires the request scheme to include the service or operation identity in the canonical request itself. A single hash therefore commits to the complete priced invocation defined by the binding.

Quote identity and offer grouping

A logical offer can contain alternative chains, assets, refreshed prices, or reusable terms. The consumption semantics of such a group are application-specific and can require shared cross-chain state.

A signed identifier alone cannot make alternatives mutually exclusive or revoke older quotes.

This ERC therefore defines only quoteId, which identifies one immutable signed quote. Grouping, replacement, revocation, and one-time consumption are intentionally outside scope.

Validity without issuance time

An issuer-signed issuance timestamp proves only that the issuer asserted a time; it does not independently prove when the quote existed.

The security property required by this ERC is a bounded period for initiating payer authorizations and new settlement submissions based on the quote. validAfter and validUntil provide that property without adding an issuance timestamp. They do not bound the execution time of a payment mechanism that cannot enforce expiry.

Requiring an execution deadline for every integration would exclude ordinary transfers without an expiry check unless an enforcing adapter were used. This ERC leaves that choice to the settlement integration and requires the resulting guarantee to be disclosed.

Settlement time boundaries can differ from quote time boundaries. For example, ERC-3009 uses strict execution-time comparisons against validAfter and validBefore, whereas this quote uses inclusive comparisons. Copying timestamps therefore does not make the validity windows identical; the binding must account for the payment mechanism's conditions.

Applications requiring maximum quote age can use shorter validity periods or define additional metadata in a higher-level protocol.

Why uint64 timestamps

Unix timestamps do not require the range of uint256.

Using uint64 gives a fixed and more than sufficient range while avoiding arithmetic edge cases when settlement protocols derive nearby time bounds.

EIP-712 still encodes the value deterministically according to its declared uint64 type.

Why verifyingContract is omitted

The provider quote can be consumed by different settlement mechanisms.

Binding it to one verifyingContract would prevent the same quote from being used with a direct transfer, an authorization-based transfer, an escrow, or another adapter.

The settlement chain remains in the EIP-712 domain because token and account addresses are chain-scoped commercial terms.

Why settlement mechanics are not signed

This quote authenticates the provider's commercial payment leg: payer policy, chain, asset, recipient, and exact amount.

It does not claim that two settlement mechanisms are semantically equivalent.

When a particular escrow, router, facilitator, or execution path matters to the payer, that behavior is authenticated by the separate payer authorization or enforced by the surrounding application.

A protocol that requires the provider itself to commit to settlement-program semantics can define a higher-level order object rather than changing the meaning of this minimal quote.

Relationship to x402 Offer and Receipt

Protocol-specific signed offers, including the x402 Offer and Receipt extension, are prior art for provider-authenticated payment terms.

Such artifacts are defined as part of their respective payment protocols. This ERC instead standardizes an EVM quote representation that can be carried by multiple protocols and does not depend on a particular x402 version or field layout.

The main additional semantic provided here is a signed commitment to the exact priced request under an identified request scheme. It also defines ERC-1271 verification and optional pre-payment payer binding.

An implementation that uses both protocols can define an x402 binding for this quote. The signed artifacts are not intended to be wire-compatible.

Relationship to HTTP Message Signatures

HTTP Message Signatures can authenticate selected components of an HTTP message and are appropriate for HTTP-specific request or response authentication.

This ERC does not replace them.

An HTTP request scheme can be defined using HTTP Message Signature component derivation or another deterministic HTTP representation, while the resulting commitment is carried inside this EVM-specific commercial quote.

The distinction is the signed object: this ERC authenticates EVM payment terms and an opaque request commitment, rather than defining a general HTTP-message signature format.

Lightweight issuer verification

General-purpose off-chain verifiers may classify an issuer by authenticated settlement-chain code state. Deployments that require offline or lightweight verification can instead pin the issuer authentication method as part of the independently authenticated issuer configuration.

Relationship to payer authorizations

Payer authorization standards prove that a payer approved a transfer.

This ERC proves a complementary statement: an authenticated provider asserted the quoted terms for the committed request.

A payment flow can therefore validate the provider quote before constructing the payer authorization, and then ensure that the authorization matches the quote.

Backwards Compatibility

This ERC introduces a new off-chain typed-data format and does not modify Ethereum consensus, transaction validity, ERC-20 behavior, ERC-1271, or existing payment protocols.

Existing systems can ignore the quote.

Protocol bindings can introduce this quote alongside their existing payment messages without changing the EIP-712 schema defined here.

Test Cases

EIP-712 encoding

The following canonical vector tests EIP-712 encoding of ServicePaymentQuote. It deliberately treats requestHash as a fixed input and does not define the semantics or canonical request bytes of com.example.service/1.

The private key is a public test-only key and must not be used with real assets.

privateKey = 0x0000000000000000000000000000000000000000000000000000000000000001
issuer     = 0x7e5f4552091a69125d5dfcb7b8c2659029395bdf

The domain is:

name    = "Signed Service Payment Quotes"
version = "1"
chainId = 8453

The quote is:

{
  "issuer": "0x7e5f4552091a69125d5dfcb7b8c2659029395bdf",
  "payer": "0x2222222222222222222222222222222222222222",
  "recipient": "0x3333333333333333333333333333333333333333",
  "asset": "0x4444444444444444444444444444444444444444",
  "amount": "1000000",
  "requestScheme": "com.example.service/1",
  "requestHash": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "quoteId": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
  "validAfter": "1788372000",
  "validUntil": "1788372300"
}

The expected intermediate values are:

domainTypeHash =
0xc2f8787176b8ac6bf7215b4adcc1e069bf4ab82d9ab1df05a57a91d425935b6e

quoteTypeHash =
0xca99e2c0129de0b5f7079659a6d0212de62a30b5a49cf106d3e9261f6db945a3

requestSchemeHash =
0x17e1eec6f92dae5bde1713ec0a389a57dbcd572bdfc3f524d01aac30f498fa71

domainSeparator =
0x4e6b3484521d4bcf7d0aafb9025dd4cc61163bf1c417e7d0fad98a2a2002c590

structHash =
0x6487a5ffb72d1a1cb9d2b3ff0c1e8258d1cc6ac4a88893e66e110552bd89a931

quoteDigest =
0xc80f6a3da4ad8d3636a7e4de909f46180ef8c3af67cf46f55736632c4a93fc65

The following is one valid 65-byte low-s secp256k1 signature over quoteDigest, produced with deterministic RFC 6979 nonce generation using HMAC-SHA256. Signers are not required to reproduce this exact signature; verification of this signature recovers the issuer above.

r =
0x2051fd043967b356866060fb9bf95e11b735f03cab03d0f7f6c736abbdf55a42

s =
0x311f85b1f7f34e4f1008a494fb10aed2965589f96fca3f99c2bb72994ab345e4

v = 28

signature =
0x2051fd043967b356866060fb9bf95e11b735f03cab03d0f7f6c736abbdf55a42311f85b1f7f34e4f1008a494fb10aed2965589f96fca3f99c2bb72994ab345e41c

recoveredIssuer =
0x7e5f4552091a69125d5dfcb7b8c2659029395bdf

As mutation checks, changing one signed input while leaving the remaining vector unchanged produces different digests:

MutationExpected quoteDigest
chainId = 10xa98027809bff313cfbd26b41c33c594b70b255eb0cdf8f70b560ee5a178a14f8
amount = 10000010x9a5f35f98fadf41d26713cb396231f44a6269d9c2a30521a416a691bc03840ff
requestScheme = "com.example.service/2"0x6c1f9e241a2cb2a1c400cd68e9506ff0ae1868f64fcfbbb303b757967f960f7c
requestHash = 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc0x8624bbaa1e7b897b3bb2d313ca2349b695c8ef9a9d4e6e77c0a20a5fcc22425a

Validity and submission behavior

For an otherwise valid quote with validAfter = 100 and validUntil = 200, the following cases exercise time semantics independently of the encoding vector above:

CaseExpected result
Quote verification at t = 99 or t = 201 for a new paymentReject.
Quote verification at t = 100 or t = 200Pass the quote time check; settlement may have stricter conditions.
Authorization created at t = 190, first submission attempted at t = 201Reject the new submission.
Quote checked at t = 190, new authorization attempted at t = 201Reject after repeating the time check.
Payment submitted at t = 190, execution at t = 201, no enforced execution deadlineQuote expiry alone does not prevent execution.
Payment submitted at t = 190, execution at t = 201, enforced execution deadline of 200Reject execution.
Payment authorization has a deadline of 201Reject as incompatible with the quote.
ERC-3009 execution at t = 200 with validBefore = 200The authorization rejects execution even though the quote time check passes.
Original payment remains pending after t = 200Do not infer failure or cancellation from quote expiry.

Bindings additionally test their submission event and retransmission rules, including forwarding through intermediaries. Forwarding an existing payment does not relax the selected mechanism's execution deadline.

Single-use application behavior

These cases apply to applications claiming the corresponding single-use guarantee; reusable quotes are not required to acquire single-use semantics.

CaseExpected result
Retry and reissued quote for the same invocation, with a new quoteIdResolve to the same shared invocation state.
Two intentional invocations with identical operation argumentsResolve to distinct invocation identities.
A nonce identifies an invocation in the canonical requestRetain it across retries and reissues; use a different nonce for a new invocation.
Two concurrent attempts using different quotes or payer authorizations for one invocationEnforce the claimed single-use property across both attempts.
Replacement quote obtained while the original payment remains pendingDo not treat replacement or expiry as release of the original consumption state.
Service execution is deduplicated but both independent payments succeedThe implementation does not satisfy a duplicate-payment prevention claim.

Security Considerations

Provider identity

A valid signature proves control of the issuer account. It does not prove that the account belongs to the service the payer intended to use.

The verifier must obtain expectedIssuer from an independently authenticated trust mechanism.

A malicious intermediary that can replace both the service identity and the quote remains outside the protection of this ERC.

Quote signatures do not authorize funds

The issuer signature is not a payer signature.

Any implementation that treats the provider quote as authority to transfer payer assets creates an independent asset-theft vulnerability.

Request-scheme completeness

The security of request binding depends on the request scheme.

If a field can change the operation, price, recipient behavior, or requested result but is omitted by the scheme, the quote does not authenticate that field.

Published request schemes should therefore be narrow, deterministic, versioned, and explicit about all semantically relevant components.

Request transformation

The issuer and verifier must derive the same canonical request.

If an intermediary transforms a request between the verifier and issuer, the request scheme must be defined at a layer observed consistently by both parties or the transformer becomes part of the trusted boundary.

The quote does not prove the semantics of data after an uncommitted transformation.

Request privacy

requestHash is a commitment, not encryption.

Low-entropy requests can be guessed by dictionary attack if an observer obtains the quote.

Applications requiring confidentiality should include unpredictable committed entropy or use a separate privacy-preserving mechanism.

Bearer quotes

A bearer quote can be used by any payer permitted by the surrounding application.

For single-use or non-idempotent operations, interception can allow another payer to race the intended payer.

A client-generated nonce inside the committed request and delivery bound to the actual settling payer reduce this risk.

Replay

quoteId, payer binding, request binding, chain binding, and the validity window limit replay contexts but do not create global consumption state.

A quote may be presented more than once while valid.

Applications that require exactly-once execution must maintain consumption state separately. A nonce identifies an invocation but does not consume it. A payer authorization nonce can prevent reuse of that authorization without preventing another authorization from paying for the same invocation.

Consumption tracking must handle concurrent attempts and payment uncertainty according to the application's claimed guarantee. Preventing a second service execution does not prevent a second transfer of funds.

Quote replacement and revocation

Issuing a new quote does not revoke an earlier quote.

Each validly signed quote remains eligible for new authorization and submission within its own validity window unless a higher-level protocol defines revocation. Reissuing a quote or allowing it to expire does not cancel a payment already submitted using it.

Applications should use short validity periods when rapid repricing or revocation is required.

Settlement expiry

validUntil governs new authorization or submission, not blockchain inclusion time.

A transaction submitted while the quote is valid can be included after expiry.

Applications requiring an on-chain execution deadline must use a settlement mechanism or adapter that enforces one. A local timer or earlier submission cannot provide that guarantee.

Quote expiry is not evidence that a pending payment failed. Retrying with a new authorization can cause duplicate payments if the earlier payment later succeeds. Applications must resolve pending payment state or enforce shared consumption before treating a replacement payment as safe under a single-use policy.

Additional fees

The signed amount covers only the quoted commercial payment.

Additional gas, facilitator, relayer, conversion, or protocol fees are not authenticated by the provider quote.

A payer or autonomous agent must apply its own policy to those separately authorized costs.

Asset identity and token behavior

The signed asset identity is the pair of settlement chainId and asset address.

Token symbols, names, and decimals are not signed identifiers.

Fee-on-transfer, rebasing, callback-enabled, upgradeable, or malicious token contracts can behave differently from a simple nominal transfer. Settlement integrations remain responsible for token-specific execution risk.

Counterfactual issuers

This ERC does not define verification for counterfactual contract issuers or ERC-6492 wrapped signatures. Higher-level bindings can add such support without changing the signed quote structure.

ERC-1271 mutability

ERC-1271 validity can change over time because contract ownership, code, delegation, or policy can change.

A verifier requiring durable historical evidence should retain the quote, signature, verification result, and relevant chain context at the time of acceptance.

Delegated accounts

An account with code is verified through ERC-1271 under the default classification rule.

This includes an account whose code is an EIP-7702 delegation designator.

A delegated account whose implementation does not support ERC-1271 cannot authenticate quotes through the contract-account path unless the verifier trust configuration explicitly pins another supported method.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth