Overview
The escrow scheme for x402 v2 uses the Commerce Payments Protocol contract stack to enable secure, conditional fund handling. The client signs a single ERC-3009 authorization. The facilitator submits it to an operator, which handles token collection, escrow locking, and fee distribution in one transaction.This spec is based on the escrow scheme proposal (Issue #1011) and the spec PR (#1425). It uses audited on-chain escrow contracts from the Commerce Payments Protocol.
Settlement Methods
The scheme supports two settlement paths:| Method | Behavior |
|---|---|
authorize (default) | Funds held in escrow. Can be captured, voided, reclaimed, or refunded. |
charge | Funds sent directly to receiver. Refundable post-settlement. |
Authorize (Default)
Authorize
Client authorization is submitted — funds locked in escrow via
operator.authorize(). The operator calls the token collector to execute receiveWithAuthorization with the client’s ERC-3009 signature, then routes funds into the escrow contract.Capture or Void
The operator can capture (release funds to receiver via
operator.release()) or void (return escrowed funds to client). Capture conditions are configurable per operator (time-locked, arbiter-approved, etc.).Reclaim
If
authorizationExpiry passes without capture, the client can reclaim funds directly from escrow without operator approval.Charge
Charge
Client authorization is submitted — funds sent directly to receiver via
operator.charge(). No escrow hold.Visual Flow
Exact Payment (Immediate Settlement)
Escrow Payment (Deferred Settlement)
Key Differences
| Aspect | Exact | Escrow |
|---|---|---|
| Settlement | Immediate on request | Deferred until conditions met |
| Payer Protection | None (payment final) | Refundable until capture |
| Resource Delivery | After payment clears | Immediately after authorization |
| Recourse | No recourse | Reclaim after expiry, refund via operator |
| Fee System | None | Configurable (min/max bounds, client-signed) |
| Use Case | Trusted, low-value, instant | High-value, variable cost, disputes |
Operator Flexibility
The operator is the key abstraction. Different implementations enable different payment patterns:| Use Case | Operator Behavior |
|---|---|
| Session billing | Track usage off-chain, capture periodically |
| Time-locked escrow | Release after period expires |
| Dispute resolution | Arbiter decides release vs refund |
| Immediate (exact-like) | Use charge() for instant settlement |
| Streaming payments | Time-proportional captures |
Message Format
PaymentRequirements (402 Response)
Server sends this to request payment:PaymentPayload (Client Response)
Client sends this with signed authorization:Field Reference
Required Extra Fields
| Field | Type | Description |
|---|---|---|
name | string | EIP-712 domain name for the token (e.g., "USDC") |
version | string | EIP-712 domain version (e.g., "2") |
escrowAddress | address | AuthCaptureEscrow contract address on the specified network |
operatorAddress | address | Operator contract address (stored in PaymentInfo.operator) |
tokenCollector | address | ERC-3009 token collector contract address |
Optional Extra Fields
| Field | Type | Description | Default |
|---|---|---|---|
settlementMethod | "authorize" | "charge" | Settlement path | "authorize" |
minFeeBps | uint16 | Minimum fee in basis points | 0 |
maxFeeBps | uint16 | Maximum fee in basis points | 0 |
feeReceiver | address | Address receiving fees | address(0) (flexible) |
preApprovalExpirySeconds | uint48 | ERC-3009 signature validity / pre-approval deadline (seconds from now) | type(uint48).max |
authorizationExpirySeconds | uint48 | Deadline for capturing escrowed funds (seconds from now) | type(uint48).max |
refundExpirySeconds | uint48 | Deadline for refund requests (seconds from now) | type(uint48).max |
Fee Configuration: Fees are enforced on-chain in the PaymentInfo struct. The operator contract cannot charge more than
maxFeeBps or less than minFeeBps. If feeReceiver is set, the actual fee recipient at capture/charge must match.Nonce Derivation
The ERC-3009 nonce is deterministically derived from the payment parameters:Verification Logic
The facilitator performs these checks in order:- Type guard — Verify
payloadcontainsauthorization,signature, andpaymentInfofields - Scheme match — Verify
scheme === "escrow" - Network match — Verify network format is
eip155:<chainId>and matches between requirements and payload - Extra validation — Verify
extracontains required fields (escrowAddress,operatorAddress,tokenCollector) - Time window — Verify
validBefore > now + 6s(not expired) andvalidAfter <= now(active) - ERC-3009 signature — Recover signer from EIP-712 typed data (
ReceiveWithAuthorizationprimary type) and verify matchesauthorization.from - Amount — Verify
authorization.value === requirements.amount - Recipient match — Verify
authorization.to === extra.tokenCollector - Token match — Verify
paymentInfo.token === requirements.asset - Receiver match — Verify
paymentInfo.receiver === requirements.payTo - Simulate — Call
operator.authorize(...)oroperator.charge(...)viaeth_callto verify success
EIP-6492 Support
For smart wallet clients, the signature may be EIP-6492 wrapped (containing deployment bytecode). The facilitator extracts the inner ECDSA signature for verification. The on-chainERC6492SignatureHandler in the token collector handles wallet deployment during settlement.
Settlement Logic
Settlement is performed by the facilitator calling the operator:- Re-verify the payload (catch expired/invalid payloads before spending gas)
- Determine function —
settlementMethod === "charge" ? "charge" : "authorize" - Call operator —
operator.<fn>(paymentInfo, amount, tokenCollector, collectorData) - Wait for receipt — Confirm transaction success (60s timeout)
- Return result — Transaction hash, network, and payer address
- Calling the token collector to execute
receiveWithAuthorizationwith the client’s ERC-3009 signature - Routing funds to escrow (authorize) or directly to receiver (charge)
- Validating fee bounds against the client-signed
PaymentInfo
PaymentInfo Struct
This is the on-chain Solidity struct. Thepayer field is not included in the JSON payload — it is derived from authorization.from at settlement time.
Expiry Ordering
The contract enforces:preApprovalExpiry <= authorizationExpiry <= refundExpiry
| Expiry | Enforced At | Effect |
|---|---|---|
preApprovalExpiry | authorize() / charge() | Blocks settlement after this time |
authorizationExpiry | capture() | Blocks capture; enables reclaim() |
refundExpiry | refund() | Blocks refund requests |
Safety Guarantees
The escrow contract enforces invariants on-chain:No Overcharging
Settlement amount is capped by client-signed
maxAmount. Attempting to exceed the limit reverts the transaction.Replay Prevention
Each payment has a unique nonce derived from
(chainId, escrowAddress, paymentInfoHash). The nonce is consumed on-chain at settlement.Payer Reclaim
After
authorizationExpiry, payer can reclaim escrowed funds directly without operator approval.Fee Bounds
Min/max fee bounds in PaymentInfo are client-signed and enforced on-chain. The operator must respect these limits.
Error Codes
Verification Errors
| Error Code | Description |
|---|---|
invalid_payload_format | Payload missing authorization, signature, or paymentInfo |
unsupported_scheme | Scheme is not escrow |
network_mismatch | Payload network does not match requirements |
invalid_network | Network format is not eip155:<chainId> |
invalid_escrow_extra | Missing required extra fields (escrowAddress, operatorAddress, tokenCollector) |
authorization_expired | validBefore <= now + 6s |
authorization_not_yet_valid | validAfter > now |
invalid_escrow_signature | ERC-3009 signature verification failed |
amount_mismatch | authorization.value !== requirements.amount |
token_collector_mismatch | authorization.to !== extra.tokenCollector |
token_mismatch | paymentInfo.token !== requirements.asset |
receiver_mismatch | paymentInfo.receiver !== requirements.payTo |
insufficient_balance | Payer balance is less than required amount |
simulation_failed | Settlement simulation via eth_call failed |
Settlement Errors
| Error Code | Description |
|---|---|
verification_failed | Re-verification before settlement failed |
transaction_reverted | On-chain transaction reverted after confirmation |
vs Exact Scheme
Theescrow scheme adds an authorization step before settlement. For simple immediate payments where trust is not a concern, the exact scheme remains more efficient.
See Comparison for detailed trade-offs.
Next Steps
Overview
Understand why escrow is needed for HTTP payments.
Comparison
Compare escrow vs exact schemes in detail.
Smart Contracts
Learn about escrow and operator contracts.
SDK Installation
Build your first escrow-based payment flow.
