--- eip: 8409 title: Signed Service Payment Quotes description: Provider-signed EVM payment quotes bound to a priced service request. author: Dmitry Sergeev (@SergeevDmitry) discussions-to: https://ethereum-magicians.org/t/erc-8409-signed-service-payment-quotes/29577 status: Draft type: Standards Track category: ERC created: 2026-09-04 requires: 712, 1271 --- ## Abstract This ERC defines an [EIP-712](./eip-712.md) 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](./eip-1271.md) 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: ```solidity 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: | Field | Meaning | | --------------- | ------------------------------------------------------------------------------------ | | `issuer` | Account whose signature authenticates the quote. | | `payer` | Account permitted to fund the payment, or the zero address for a bearer quote. | | `recipient` | Immediate recipient of the commercial payment. | | `asset` | Payment token contract, or the zero address for the settlement chain's native asset. | | `amount` | Exact nominal payment amount in the asset's smallest unit. | | `requestScheme` | Identifier of the canonical request-binding rules. | | `requestHash` | Keccak-256 commitment to the canonical priced request. | | `quoteId` | Issuer-scoped identifier for this concrete quote. | | `validAfter` | Unix timestamp before which the quote is not valid. | | `validUntil` | Unix 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: ```text 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: ```text 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: ```text name: "Signed Service Payment Quotes" version: "1" chainId: settlement chain ID ``` The domain type is: ```text 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: ```text 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: ```text quoteDigest = keccak256("\x19\x01" || domainSeparator || quoteStructHash) ``` ### Signature verification A verifier MUST first establish `expectedIssuer` independently from the quote. A quote MUST be rejected unless: ```text 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: ```solidity isValidSignature(quoteDigest, signature) ``` and accept only the ERC-1271 magic value: ```text 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: ```json { "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](./eip-3009.md) 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](./eip-20.md) 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. ```text privateKey = 0x0000000000000000000000000000000000000000000000000000000000000001 issuer = 0x7e5f4552091a69125d5dfcb7b8c2659029395bdf ``` The domain is: ```text name = "Signed Service Payment Quotes" version = "1" chainId = 8453 ``` The quote is: ```json { "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: ```text 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. ```text 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: | Mutation | Expected `quoteDigest` | | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------- | | `chainId = 1` | `0xa98027809bff313cfbd26b41c33c594b70b255eb0cdf8f70b560ee5a178a14f8` | | `amount = 1000001` | `0x9a5f35f98fadf41d26713cb396231f44a6269d9c2a30521a416a691bc03840ff` | | `requestScheme = "com.example.service/2"` | `0x6c1f9e241a2cb2a1c400cd68e9506ff0ae1868f64fcfbbb303b757967f960f7c` | | `requestHash = 0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc` | `0x8624bbaa1e7b897b3bb2d313ca2349b695c8ef9a9d4e6e77c0a20a5fcc22425a` | ### 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: | Case | Expected result | | ---- | --------------- | | Quote verification at `t = 99` or `t = 201` for a new payment | Reject. | | Quote verification at `t = 100` or `t = 200` | Pass the quote time check; settlement may have stricter conditions. | | Authorization created at `t = 190`, first submission attempted at `t = 201` | Reject the new submission. | | Quote checked at `t = 190`, new authorization attempted at `t = 201` | Reject after repeating the time check. | | Payment submitted at `t = 190`, execution at `t = 201`, no enforced execution deadline | Quote expiry alone does not prevent execution. | | Payment submitted at `t = 190`, execution at `t = 201`, enforced execution deadline of `200` | Reject execution. | | Payment authorization has a deadline of `201` | Reject as incompatible with the quote. | | ERC-3009 execution at `t = 200` with `validBefore = 200` | The authorization rejects execution even though the quote time check passes. | | Original payment remains pending after `t = 200` | Do 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. | Case | Expected result | | ---- | --------------- | | Retry and reissued quote for the same invocation, with a new `quoteId` | Resolve to the same shared invocation state. | | Two intentional invocations with identical operation arguments | Resolve to distinct invocation identities. | | A nonce identifies an invocation in the canonical request | Retain it across retries and reissues; use a different nonce for a new invocation. | | Two concurrent attempts using different quotes or payer authorizations for one invocation | Enforce the claimed single-use property across both attempts. | | Replacement quote obtained while the original payment remains pending | Do not treat replacement or expiry as release of the original consumption state. | | Service execution is deduplicated but both independent payments succeed | The 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](./eip-6492.md) 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](./eip-7702.md) 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 Copyright and related rights waived via [CC0](../LICENSE.md).