Overview
Undo sits between an agent and any paid x402 service. The buyer signs one USDC authorization. The money goes into Coinbase Commerce Payments escrow on Base, the merchant is called, and the payment settles when the dispute window closes. Until then, a refund is one request away.
There are two modes. Covered works with every x402 service today: Undo pays the merchant from its float and is repaid at settlement. Native merchants are the escrow receiver themselves and verify Undo calls with the middleware below.
Agents and PayBox
The Undo plugin adds data tools to PayBox. The only approval is PayBox’s own request_wallet_sign, so a protected purchase costs the user one passkey, the same as an unprotected one. Refunds need no approval at all, because money can only move back to the payer.
undo_quote { url, method, body?, payer } → quote, typedData
request_wallet_sign { op: "typedData", typedData } → signature (the one passkey)
undo_purchase { quoteId, signature } → response, orderId, refundToken
undo_refund { orderId, refundToken, reason } → refunded or case openedAgents keep the refundToken from undo_purchase. It is shown once and is the only thing needed to ask for a refund later.
API reference
JSON over HTTPS. Amounts are atomic USDC strings with 6 decimals, addresses are checksummed and times are unix seconds. Errors look like { "error": { "code", "message" } }. The typed client is @undo/sdk.
POST/v1/quotes
Price a purchase and get the typed data to sign.
POST/v1/purchases
Submit the signature. Returns the order, the response and a refund token.
GET/v1/orders/:orderId
One order with its on-chain events.
GET/v1/orders?payer=0x…
Every order of a payer.
POST/v1/orders/:orderId/refund
Ask for a refund with the refund token and a reason.
POST/v1/merchants
Join as a Native merchant, signed by the payout address.
GET/v1/merchants/:address
A merchant's terms and numbers.
POST/v1/merchants/:address/cases/:orderId
Accept or contest a refund request.
GET/v1/stats
Orders protected, volume, refunds and median refund time.
curl -X POST "$UNDO_API_URL/v1/orders/ord_7Hq2/refund" \
-H "content-type: application/json" \
-d '{ "refundToken": "rt_…", "reason": "Forecast for the wrong city." }'Native middleware
For a Native merchant, the gateway calls your endpoint with two headers: Undo-Payment, the payment id, and Undo-Signature, an EIP-191 signature by the Undo gateway over undo:v1:<paymentId>:<requestHash>. The middleware checks the signature, recomputes the request hash, and reads the case on-chain to confirm the money is held in escrow for exactly this request.
import { createOnchainCaseReader, undoHono } from "@undo/sdk/middleware";
const undo = undoHono({
gateway: "0x0ED3EE2B783Bd1B7752E97e2B8FFF59C05c3a8E9",
readCase: createOnchainCaseReader({
rpcUrl: "https://mainnet.base.org",
operator: "0xA47aa1C3cc3684861E4aC27d5Ff07825C5e76B62",
}),
});
// Undo calls pass; anything else falls through to x402.
app.use("/paid/*", undo);import express from "express";
import { createOnchainCaseReader, undoExpress } from "@undo/sdk/middleware";
app.use("/paid", express.raw({ type: "*/*" }), undoExpress({
gateway: "0x0ED3EE2B783Bd1B7752E97e2B8FFF59C05c3a8E9",
readCase: createOnchainCaseReader({ rpcUrl: "https://mainnet.base.org", operator: "0xA47aa1C3cc3684861E4aC27d5Ff07825C5e76B62" }),
}));Refund rules
The gateway refunds automatically when it can see the failure. The reason is recorded on-chain with the refund.
- Merchant error. The endpoint answered with an error status.
- Timeout. No answer before the delivery deadline.
- Empty or malformed. The answer was empty or not what was promised.
- Duplicate. The same request was charged twice.
- Instant. The buyer asked and the amount is under the merchant's cap.
Any other refund opens a case. The merchant accepts or contests. Silence counts as acceptance. A contested case goes to an arbiter, who decides within 24 hours and can split the amount. Covered services have a 60 min window; Native merchants choose between 60 min and 7 days.
Contracts
All on Base mainnet. The escrow and collector are Coinbase’s audited contracts, reused as they are.
- UndoOperator0xA47aa1C3cc3684861E4aC27d5Ff07825C5e76B62 (address 0xA47aa1C3cc3684861E4aC27d5Ff07825C5e76B62 on Basescan, opens in a new tab)Undo's rules: windows, refunds, disputes.
- AuthCaptureEscrow0xf96815976523E00e65Be8f34cA5e64b4f41EB19c (address 0xf96815976523E00e65Be8f34cA5e64b4f41EB19c on Basescan, opens in a new tab)Coinbase Commerce Payments v1.1. Holds the USDC.
- ERC3009PaymentCollector0x8612dfdc421f80336cd14E8EF9cb1E765dB5ab88 (address 0x8612dfdc421f80336cd14E8EF9cb1E765dB5ab88 on Basescan, opens in a new tab)Pulls the signed USDC into escrow.
- USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 (address 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 on Basescan, opens in a new tab)Circle's USD Coin on Base.
Beta limits
Undo is in beta. Order sizes and daily volume are capped while the system proves itself on mainnet. Read the beta terms and the security model before relying on it.