[Proposal] Escrow Scheme for x402 using Base Commerce Payments Protocol
Based on Agentokratia's escrow proposal (#834) and its reference implementation: x402-escrow (MIT).
Summary
We at x402r.org propose an escrow scheme for x402 v2 that leverages the Base Commerce Payments Protocol to enable refundable payments and conditional fund handling. The client signs once to authorize a maximum amount, and the facilitator settles through the commerce-payments operator — routing funds into escrow or directly to the receiver.
Built on audited on-chain escrow contracts deployed on Base and other EVM chains, this scheme adds the auth/capture pattern as a building block for x402.
Problem
The exact scheme works well for immediate-delivery payments, but creates friction for:
- High-value transactions — No recourse if service fails after payment
- Variable pricing — Usage-based billing (LLM tokens, compute time, API calls)
- Long-running tasks — Work that takes hours or days to complete
Solution
The escrow scheme supports two settlement paths through the commerce-payments operator:
| Method |
Function |
Behavior |
authorize |
authorize() |
Funds held in escrow. Can be captured, refunded, or voided. |
charge |
charge() |
Funds sent directly to receiver. Refundable post-settlement. |
Both methods share identical function signatures and use the same operator, fee system, and token collector infrastructure.
Lifecycle
Authorize (default):
SIGN → AUTHORIZE → RESOURCE DELIVERED
- Client signs an ERC-3009
receiveWithAuthorization for the maximum amount
- Facilitator calls
authorize() on the operator — funds locked in escrow
- Server returns the resource (HTTP 200)
Post-settlement, the commerce-payments contracts enable capture, refund, void, or reclaim.
Charge:
SIGN → CHARGE → RESOURCE DELIVERED
- Client signs an ERC-3009 authorization (same as above)
- Facilitator calls
charge() on the operator — funds go directly to receiver
- Server returns the resource (HTTP 200)
Post-settlement, the operator can refund within refundExpiry if needed. Unlike the authorize path, the payer cannot reclaim() — funds are already with the receiver.
Specification
The escrow scheme uses standard x402 v2 structures with additional fields in extra:
PaymentRequirements
{
"x402Version": 2,
"accepts": [{
"scheme": "escrow",
"network": "eip155:8453",
"amount": "1000000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xReceiverAddress",
"maxTimeoutSeconds": 60,
"extra": {
"name": "USDC",
"version": "2",
"escrowAddress": "0xEscrowAddress",
"operatorAddress": "0xOperatorAddress",
"tokenCollector": "0xCollectorAddress",
"settlementMethod": "authorize",
"minFeeBps": 0,
"maxFeeBps": 1000,
"feeReceiver": "0xOperatorAddress"
}
}]
}
PaymentPayload
{
"x402Version": 2,
"resource": {
"url": "https://api.example.com/resource",
"method": "GET"
},
"accepted": {
"scheme": "escrow",
"network": "eip155:8453",
"amount": "1000000",
"asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"payTo": "0xReceiverAddress",
"maxTimeoutSeconds": 60,
"extra": {
"name": "USDC",
"version": "2",
"escrowAddress": "0xEscrowAddress",
"operatorAddress": "0xOperatorAddress",
"tokenCollector": "0xCollectorAddress",
"settlementMethod": "authorize",
"minFeeBps": 0,
"maxFeeBps": 1000,
"feeReceiver": "0xOperatorAddress"
}
},
"payload": {
"authorization": {
"from": "0xPayerAddress",
"to": "0xCollectorAddress",
"value": "1000000",
"validAfter": "0",
"validBefore": "1740672154",
"nonce": "0xf374...3480"
},
"signature": "0x2d6a...571c",
"paymentInfo": {
"operator": "0xOperatorAddress",
"receiver": "0xReceiverAddress",
"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"maxAmount": "1000000",
"preApprovalExpiry": 1740672154,
"authorizationExpiry": 4294967295,
"refundExpiry": 281474976710655,
"minFeeBps": 0,
"maxFeeBps": 1000,
"feeReceiver": "0xOperatorAddress",
"salt": "0x0000...0001"
}
}
}
extra Fields
Required:
| 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 |
operatorAddress |
address |
Operator address |
tokenCollector |
address |
Token collector contract address |
Optional:
| Field |
Type |
Default |
Description |
settlementMethod |
"authorize" | "charge" |
"authorize" |
Settlement path |
minFeeBps |
uint16 |
0 |
Minimum fee in basis points |
maxFeeBps |
uint16 |
0 |
Maximum fee in basis points |
feeReceiver |
address |
address(0) |
Fee recipient |
preApprovalExpirySeconds |
uint48 |
— |
ERC-3009 signature validity |
authorizationExpirySeconds |
uint48 |
— |
Capture deadline |
refundExpirySeconds |
uint48 |
— |
Refund request deadline |
Relationship to exact
| Aspect |
exact |
escrow |
| Settlement |
Immediate transfer |
Via escrow contract (authorize) or direct (charge) |
| Refundable |
No |
Yes (both paths) |
| Fee system |
None |
Commerce-payments managed (min/max bps) |
| Gas payer |
Facilitator |
Facilitator |
| Signature |
ERC-3009 / Permit2 |
ERC-3009 |
| On-chain contracts |
Token only |
Token + Escrow + Operator + Collector |
The charge settlement method gives escrow a direct-settlement path (like exact) while retaining post-settlement refund capability through the commerce-payments infrastructure.
Relationship to Other Escrow Proposals
| Proposal |
Focus |
Relationship |
| #834 (Agentokratia) |
Session-based payments with facilitator tracking |
This proposal generalizes the auth/capture pattern; session tracking is one operator implementation |
| #839 (cart.fun) |
Pre-funded usage-based payments |
Uses their own escrow contracts; this proposal uses the audited Base Commerce Payments contracts instead |
| #864 (x402r - us) |
Refundable payments with dispute resolution |
Built on Base Commerce Payments; custom operator contracts implemented |
| #946 |
Channel scheme with Merkle proof settlement |
Alternative settlement approach; could potentially use escrow scheme for fund locking |
| #1247 (trust-escrow) |
Task delivery escrow for agents |
Task delivery verification and dispute resolution could be built as an operator on top of this scheme |
Safety
- Cannot overcharge —
amount capped by client-signed maxAmount
- Client can reclaim — Funds recoverable after
authorizationExpiry if operator disappears
- Fee bounds enforced on-chain —
minFeeBps/maxFeeBps are client-signed
- Replay prevention — Nonces derived from
keccak256(chainId, escrowAddress, paymentInfoHash)
- Expiry ordering enforced — Contract requires
preApprovalExpiry <= authorizationExpiry <= refundExpiry
Status
Spec PR: #1425
Reference implementation available at x402r-scheme (TypeScript, EVM).
References
[Proposal] Escrow Scheme for x402 using Base Commerce Payments Protocol
Summary
We at x402r.org propose an
escrowscheme for x402 v2 that leverages the Base Commerce Payments Protocol to enable refundable payments and conditional fund handling. The client signs once to authorize a maximum amount, and the facilitator settles through the commerce-payments operator — routing funds into escrow or directly to the receiver.Built on audited on-chain escrow contracts deployed on Base and other EVM chains, this scheme adds the auth/capture pattern as a building block for x402.
Problem
The
exactscheme works well for immediate-delivery payments, but creates friction for:Solution
The
escrowscheme supports two settlement paths through the commerce-payments operator:authorizeauthorize()chargecharge()Both methods share identical function signatures and use the same operator, fee system, and token collector infrastructure.
Lifecycle
Authorize (default):
receiveWithAuthorizationfor the maximum amountauthorize()on the operator — funds locked in escrowPost-settlement, the commerce-payments contracts enable capture, refund, void, or reclaim.
Charge:
charge()on the operator — funds go directly to receiverPost-settlement, the operator can refund within
refundExpiryif needed. Unlike the authorize path, the payer cannotreclaim()— funds are already with the receiver.Specification
The
escrowscheme uses standard x402 v2 structures with additional fields inextra:PaymentRequirements
{ "x402Version": 2, "accepts": [{ "scheme": "escrow", "network": "eip155:8453", "amount": "1000000", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "payTo": "0xReceiverAddress", "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2", "escrowAddress": "0xEscrowAddress", "operatorAddress": "0xOperatorAddress", "tokenCollector": "0xCollectorAddress", "settlementMethod": "authorize", "minFeeBps": 0, "maxFeeBps": 1000, "feeReceiver": "0xOperatorAddress" } }] }PaymentPayload
{ "x402Version": 2, "resource": { "url": "https://api.example.com/resource", "method": "GET" }, "accepted": { "scheme": "escrow", "network": "eip155:8453", "amount": "1000000", "asset": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "payTo": "0xReceiverAddress", "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2", "escrowAddress": "0xEscrowAddress", "operatorAddress": "0xOperatorAddress", "tokenCollector": "0xCollectorAddress", "settlementMethod": "authorize", "minFeeBps": 0, "maxFeeBps": 1000, "feeReceiver": "0xOperatorAddress" } }, "payload": { "authorization": { "from": "0xPayerAddress", "to": "0xCollectorAddress", "value": "1000000", "validAfter": "0", "validBefore": "1740672154", "nonce": "0xf374...3480" }, "signature": "0x2d6a...571c", "paymentInfo": { "operator": "0xOperatorAddress", "receiver": "0xReceiverAddress", "token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "maxAmount": "1000000", "preApprovalExpiry": 1740672154, "authorizationExpiry": 4294967295, "refundExpiry": 281474976710655, "minFeeBps": 0, "maxFeeBps": 1000, "feeReceiver": "0xOperatorAddress", "salt": "0x0000...0001" } } }extraFieldsRequired:
namestring"USDC")versionstring"2")escrowAddressaddressoperatorAddressaddresstokenCollectoraddressOptional:
settlementMethod"authorize" | "charge""authorize"minFeeBpsuint160maxFeeBpsuint160feeReceiveraddressaddress(0)preApprovalExpirySecondsuint48authorizationExpirySecondsuint48refundExpirySecondsuint48Relationship to
exactexactescrowThe
chargesettlement method givesescrowa direct-settlement path (likeexact) while retaining post-settlement refund capability through the commerce-payments infrastructure.Relationship to Other Escrow Proposals
Safety
amountcapped by client-signedmaxAmountauthorizationExpiryif operator disappearsminFeeBps/maxFeeBpsare client-signedkeccak256(chainId, escrowAddress, paymentInfoHash)preApprovalExpiry <= authorizationExpiry <= refundExpiryStatus
Spec PR: #1425
Reference implementation available at x402r-scheme (TypeScript, EVM).
References