Skip to content

[Proposal] escrow scheme using Base Commerce Payments Protocol #834

@PancheI

Description

@PancheI

Summary

Agentokratia is building a marketplace for AI agent services and is looking for community input on a proposed x402 scheme. We plan to offer session-based payments that enable agents to authorize funds once and use them across multiple API calls - reducing signatures and gas costs for high-frequency interactions.

Built on the Base Commerce Payments Protocol, this solution uses audited on-chain escrow contracts with the auth/capture pattern. We propose adding an escrow scheme to x402 v2 that decouples authorization from settlement, enabling session-based payments, batched settlement, variable pricing, and exact settlements through a single primitive.

Problem

The exact scheme works well for one-off payments, but creates friction for high-frequency use cases:

AI agents making hundreds of API calls must sign every request. Micropayments become impractical when gas costs exceed the payment itself. Interactive sessions like chatbots or code execution require constant wallet interaction.

Consider an agent making 1,000 API calls at $0.01 each. With exact, that's 1,000 signatures and 1,000 on-chain transactions. The overhead defeats the purpose.

Solution

Credit cards solved this decades ago with the auth/capture pattern: authorize funds upfront, track usage, capture what's earned, return what's unused. The Commerce Payments Protocol brings this pattern on-chain with audited escrow contracts on Base.

We propose wrapping this in an x402 scheme called escrow:

  1. Authorize - Client signs once, funds locked in escrow contract
  2. Use - Client makes requests using a session ID (off-chain, no gas)
  3. Capture - Facilitator settles earned amount (batched)
  4. Void - Unused funds returned to client

For that same 1,000 API calls: 1 signature, 2 transactions.

How It Works

sequenceDiagram
    participant Client
    participant Server
    participant Facilitator
    participant Escrow

    Client->>Server: GET /resource
    Server-->>Client: 402 + PaymentRequired

    Note over Client: Signs ERC-3009 (once)

    Client->>Server: PaymentPayload
    Server->>Facilitator: authorize
    Facilitator->>Escrow: authorize()
    Facilitator-->>Server: sessionId
    Server-->>Client: 200 + X-SESSION-ID

    loop Off-chain usage
        Client->>Server: X-SESSION-ID + X-SESSION-SIG + X-SESSION-TS
        Server->>Facilitator: validate + debit
        Facilitator-->>Server: OK + balance
        Server-->>Client: 200 OK
    end

    Facilitator->>Escrow: capture(earned)
    Facilitator->>Escrow: void(unused)
Loading

Funds flow through the escrow contract - never held by the Facilitator. The client can call reclaim() after expiry if the Facilitator disappears.

Session ID

The session ID is the on-chain hash of the payment info, computed by the escrow contract:

sessionId = escrowContract.getHash(paymentInfo)

Where paymentInfo contains:

  • operator - Facilitator's address
  • payer - Client's wallet
  • receiver - Final recipient (payTo)
  • token - USDC address
  • amount - Authorized amount
  • authorizationExpiry - When client can reclaim
  • salt - Client-provided entropy for uniqueness

This design ties the off-chain session directly to on-chain escrow state. The client provides the salt in their PaymentPayload; the Facilitator constructs the full paymentInfo and calls the contract to get the canonical session ID.

After authorization, the client includes session headers in subsequent requests:

  • X-SESSION-ID - the session identifier
  • X-SESSION-SIG - signature proving wallet ownership (optional)
  • X-SESSION-TS - timestamp for replay protection (required if sig provided)

The server validates these against the Facilitator to confirm the session is active and has sufficient balance. Alternatively, servers can implement session tracking themselves - the Facilitator role is optional if the server handles escrow operations directly.

Expanding the Facilitator Role

Today, Facilitators are stateless - they verify signatures and settle exact payments immediately. The escrow scheme expands this role to be stateful:

  • Session state - track active sessions, balances, usage
  • Escrow management - authorize, capture, void
  • Off-chain usage tracking - validate requests, debit balances, no per-request gas
  • Batched settlement - amortize gas across sessions

This makes the Facilitator role economically viable. Off-chain tracking eliminates per-request costs, and batched captures amortize gas across many sessions.

Token Support

The Commerce Payments Protocol supports multiple authorization methods via its token collectors:

  • ERC-3009 - For tokens with native transferWithAuthorization (e.g., USDC)
  • Permit2 - For tokens without ERC-3009 support

This means the escrow scheme can work with a broader range of ERC-20 tokens, not just those implementing ERC-3009.

Relationship to Other Proposals

vs exact - The escrow contract also supports a charge() method for one-time immediate payments, equivalent to exact behavior. This means escrow can implement exact semantics when needed, while also supporting session-based patterns.

vs upto - The proposed upto scheme handles variable pricing (e.g., LLM tokens) by charging actual usage after a request. escrow can implement this: authorize a maximum, capture actual usage, void the remainder. The difference is escrow works across multiple requests, not just one.

vs deferred (Circle) - Circle's proposal batches settlements through their Gateway smart contracts. Both approaches use on-chain escrow. The difference: deferred is built around Circle's Gateway infrastructure, while escrow uses the Base Commerce contracts which are designed for any Facilitator to operate.

Specification

The escrow scheme uses standard x402 v2 structures with additional fields in extra:

PaymentRequired with escrow scheme:

{
  "x402Version": 2,
  "accepts": [
    {
      "scheme": "escrow",
      "network": "eip155:8453",
      "amount": "10000",
      "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "payTo": "0xReceiver...",
      "maxTimeoutSeconds": 259200,
      "extra": {
        "name": "USDC",
        "version": "2",
        "escrowContract": "0xEscrow...",
        "minDeposit": "5000000",
        "maxDeposit": "100000000",
        "authorizationExpirySeconds": 259200
      }
    }
  ]
}

PaymentPayload uses the same ERC-3009 authorization as exact, plus a salt for session uniqueness:

{
  "x402Version": 2,
  "accepted": { "scheme": "escrow", ... },
  "payload": {
    "signature": "0x...",
    "authorization": {
      "from": "0xPayer...",
      "to": "0xEscrow...",
      "value": "50000000",
      "validAfter": "0",
      "validBefore": "1734567890",
      "nonce": "0x..."
    },
    "sessionParams": {
      "salt": "0x..."
    }
  }
}

Clients that don't understand escrow simply use exact-fully backwards compatible.

Safety

The escrow contract enforces all invariants on-chain:

  • Facilitator can't overcharge - capture limited to authorized amount
  • Client can't double-spend - funds locked at authorization
  • Client can't withdraw early - reclaim blocked until expiry
  • Client protected if Facilitator disappears - reclaim available after expiry

Neither party needs to trust the other beyond the contract guarantees.

Status

This is an early proposal seeking community feedback. A reference implementation is in development. We welcome discussion on:

  • The overall approach
  • Naming and specification details
  • Integration with existing Facilitators

Open Questions

  1. Is escrow the right name, or would another be clearer?
  2. Should X-SESSION-ID headers be standardized in the x402 spec?
  3. How should Facilitators advertise escrow support?
  4. Should this subsume upto and deferred, or coexist?

References

Authors

Panche Isajeski / Agentokratia


We'd love feedback from the community on this approach.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions