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:
- a provider states the terms under which it will perform a service; and
- 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
payeris 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:
| 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:
- the service or operation identity included in the canonical request;
- the request fields included in the canonical request;
- the exact canonical serialization;
- how equivalent and non-equivalent requests are distinguished; and
- 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:
- a logical invocation identity that remains stable across retries and reissued quotes for that invocation while distinguishing separate intentional invocations;
- the scope of shared consumption state, including the relevant service, issuer, request scheme, and payment alternatives;
- the component responsible for enforcing consumption and coordinating concurrent attempts; and
- 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 <= validUntilvalidAfter 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 IDThe 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 == expectedIssuerFor 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:
0x1626ba7eA 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:
- establish the
expectedIssuer; - check all structural field requirements;
- check the intended settlement
chainId; - check
validAfter <= verificationTime <= validUntil; - determine the effective payer and apply the payer-binding rules;
- support the signed
requestScheme; - independently derive the canonical priced request and verify
requestHash; - recompute
quoteDigest; - verify the issuer signature; and
- 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; andquote.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:
- how
expectedIssueris authenticated for the service; - the allowed
requestSchemevalues; - where the quote and signature are carried;
- the bearer-quote policy;
- how the selected payer is bound to the eventual payer authorization;
- 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
- 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:
chainIdandamountMUST be encoded as base-10 strings;validAfterandvalidUntilMUST 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
0xfollowed by exactly 40 hexadecimal digits; requestHashandquoteIdMUST be encoded as0xfollowed by exactly 64 hexadecimal digits; andsignatureMUST be encoded as a0x-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; andrequestHash, 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 = 0x7e5f4552091a69125d5dfcb7b8c2659029395bdfThe domain is:
name = "Signed Service Payment Quotes"
version = "1"
chainId = 8453The 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 =
0xc80f6a3da4ad8d3636a7e4de909f46180ef8c3af67cf46f55736632c4a93fc65The 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 =
0x7e5f4552091a69125d5dfcb7b8c2659029395bdfAs 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 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
Copyright and related rights waived via CC0.
