-
Notifications
You must be signed in to change notification settings - Fork 970
Description
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:
- Authorize - Client signs once, funds locked in escrow contract
- Use - Client makes requests using a session ID (off-chain, no gas)
- Capture - Facilitator settles earned amount (batched)
- 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)
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 addresspayer- Client's walletreceiver- Final recipient (payTo)token- USDC addressamount- Authorized amountauthorizationExpiry- When client can reclaimsalt- 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 identifierX-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 -
capturelimited to authorized amount - Client can't double-spend - funds locked at authorization
- Client can't withdraw early -
reclaimblocked until expiry - Client protected if Facilitator disappears -
reclaimavailable 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
- Is
escrowthe right name, or would another be clearer? - Should
X-SESSION-IDheaders be standardized in the x402 spec? - How should Facilitators advertise escrow support?
- Should this subsume
uptoanddeferred, or coexist?
References
- Base Commerce Payments Protocol
- AuthCaptureEscrow Contract
- EIP-3009: Transfer With Authorization
- Circle x402 Proposal #447
Authors
Panche Isajeski / Agentokratia
We'd love feedback from the community on this approach.