4. Paying with x402 on Cardano
x402 turns HTTP’s 402 Payment Required into a payment protocol: the
server answers 402 with machine-readable requirements, and the client retries with a payment. On
Cardano, ZyroPay uses x402 version 2 with the exact scheme: a transfer of exactly the quoted
amount to the quoted payTo address, valid for at most the quoted time. The agent asks; ZyroPay
builds and checks the transaction; you sign; the seller’s facilitator submits it.
- The agent calls
pay_x402with the resource URL, the network (cardano-preprod), the asset, the most it accepts (maxAmount, in lovelace) and an idempotency key. - The MCP worker requests the resource.
- The seller answers
402with aPAYMENT-REQUIREDheader:x402Version: 2, schemeexact, networkcardano:preprod, amount, assetlovelace,payTo,maxTimeoutSeconds, and the resource’s exact URL. - The worker passes the requirements to the API, which checks them and the
policy, then builds a plain ADA transfer to
payTo(see below). - The request comes back
PENDING_APPROVAL. - You approve with your passkey, which signs the transaction body hash (step 3). The API attaches the witness and broadcasts nothing.
- The agent’s next
get_approval_statuscall sees the payment is ready. - The MCP worker fetches the payment header from the API.
- It retries the exact bound URL with
PAYMENT-SIGNATURE(public https only, no redirects, 64 KiB, 90 s). The seller’s facilitator verifies the transaction, then the handler runs, and settlement happens only if the handler answers below 400. - The facilitator submits the transaction to Cardano.
- The seller answers
200with the report,PAYMENT-RESPONSE(the transaction id) and anX-Delivery-Receipt(step 7). - The MCP worker reports the delivery to the API: the receipt, verbatim, and its own SHA-256 of the body bytes it read.
- The request is at
BROADCASTunder the transaction id ZyroPay already knew; the API follows it toCONFIRMEDand settles the reservation into the ledger. - The agent gets the paid response and the receipt verdict, exactly once.
What ZyroPay checks before you are asked
Section titled “What ZyroPay checks before you are asked”From the 402, ZyroPay takes only a requirement it can honour, and refuses the payment otherwise:
- x402 version 2, scheme
exact, the network the agent named, and the transfer methoddefault(ormasumi, for escrow). Thescriptmethod, which would lock funds at an arbitrary script with a server-chosen datum, is always refused. So are sponsored fees. - The amount is above zero, at most the agent’s
maxAmount, and at most the deployment’s cap (X402_CAPS, 5 tADA on staging). payTois a valid key-hash address on that network and not your own address.maxTimeoutSecondsis within bounds, and the transaction’s validity ends no later than that.- The 402’s resource URL is the URL the agent asked for (host case and default ports normalized; query order is not). A 402 for another URL is refused.
The terms (payTo, amount, asset, URL, method, the raw requirement) go into the payload’s plugin
data, so the payloadHash you approve commits to them. The transaction has one output of exactly the
amount to payTo, plus change to you, and no metadata or scripts.
Our own CBOR, the official facilitator
Section titled “Our own CBOR, the official facilitator”ZyroPay’s API builds Cardano transactions with its own CBOR codec, not the Evolution SDK the
@x402/cardano package uses, so compatibility had to be shown rather than assumed. An offline test
builds and signs a payment exactly as the API does and runs it through the official
@x402/cardano 2.29.0 facilitator: it verifies (isValid: true), the facilitator’s transaction id
equals ZyroPay’s body hash, and the transaction is byte-identical after Evolution decodes and
re-encodes it. Nine negative controls fail as they should. The packages are pinned to 2.29.0 exactly,
because 2.26 re-encoded the transaction in the facilitator and broke the body hash.
The demo seller runs that facilitator inside its own worker (FACILITATOR_MODE=embedded), which
answers as soon as the transaction is submitted. The hosted preprod facilitator waits for a block
behind a gateway timeout of about 60 s, so a payment could land while the buyer saw a timeout.
Proven live
Section titled “Proven live”ChatGPT, connected through the ZyroPay MCP connector, bought an address report from the demo seller
for 2 ADA. The user approved with a passkey; the facilitator submitted; the report arrived with HTTP 200
and its receipt verified. About 160 s from the request to CONFIRMED, most of it the human approval.
Open 83ecc5742f…9d787b on Cardanoscan (preprod) and check:
| Where | What you should see |
|---|---|
| Input | One UTxO from the same user address addr_test1vqj6…3wa |
| Output #0 | Exactly 2 ADA (the 402's amount) to payTo addr_test1qr954…kaen |
| Output #1 | Change to the sender |
| Validity | TTL 489 slots after inclusion: inside the 402's maxTimeoutSeconds (600) |
| Fee | 0.167349 ADA |
| Scripts, metadata | None. A plain transfer: the x402 exact scheme |
Tested offline The API lifecycle, receipt failures and expiry
(apps/api/test/integration/x402-cardano.test.ts), the paid retry through the real MCP SDK client
(apps/mcp/tests/e2e/x402.test.ts) and facilitator compatibility
(apps/seller/test/facilitator-compat.test.ts).
Not on other chains yet
Section titled “Not on other chains yet”x402 payments on Ethereum, Base and Solana are quoted, bound and signed (EVM as an EIP-3009
authorization signed with the passkey), but not handed to the resource: their x402.status is
unsupported. Cardano is the only chain where the agent receives the paid response today.
Code: packages/integrations/src/x402.ts, apps/api/src/services/x402.ts,
apps/mcp/src/mcp/x402.ts, apps/mcp/src/mcp/x402-delivery.ts, apps/seller.