A booking token is a signed, transferable claim on a held reservation. List one for a $0.005 fee, and a buyer pays the ask to receive the same token reassigned to their wallet — signed, in the 200 body. Holders live on either rail: an EVM address or a Solana pubkey.
git clone https://github.com/nirholas/x402-transfer-market && cd x402-transfer-market npm install && npm run dev
Every paid route answers with a dual-rail 402. The accepts array carries one EVM entry and one Solana entry; your client (or the checkout modal) picks whichever wallet it has. Each rail settles through its own facilitator — no single one handles both.
base-sepolia / base0x40252CFDF8B20Ed757D61ff157719F33Ec332402x402.org/facilitatorsolana-devnet / solanaWwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwWfacilitator.payai.network/buy/:id; server quotes the listing's ask on both railsThe token keeps its tokenId across owners; what changes is holder, the bumped transferCount, the signature, and the holderKey. Only the current holder's key can list it again.
| Route | Price | Returns |
|---|---|---|
POST /list | $0.005 | Signed listing of a transferable booking token |
POST /buy/:listingId | $0.750 (the listing's own ask) | Booking token reassigned to the buyer wallet, signed |
POST /bookings | free | Signed booking token + its secret holderKey |
GET /listings | free | Open signed listings |
GET /listings/:id | free | Signed listing |
GET /holdings/:wallet | free | Signed booking tokens (holderKey redacted) |
GET /transfers/:id | free | Signed transfer receipt |
GET /stats | free | Aggregate market counters |
POST /verify | free | `{ valid: true | false }` |
GET /health | free | `{ ok: true }` |
import { wrapFetchWithPayment } from "x402-fetch";
import { privateKeyToAccount } from "viem/accounts";
const payFetch = wrapFetchWithPayment(fetch, privateKeyToAccount(process.env.PRIVATE_KEY));
// free: mint a booking token held by an EVM wallet
const token = await (await fetch("http://localhost:4023/bookings", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ holder: "0xSellerAddress",
booking: { kind: "table", venue: "Osteria Fiorentina", party: 2 } })
})).json();
// $0.005: list it, payout to a Solana wallet
const listing = await (await payFetch("http://localhost:4023/list", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ tokenId: token.tokenId, holderKey: token.holderKey,
ask: "$0.75", payoutTo: "WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW" })
})).json();
// ask price: buy it — the reassigned token comes back in the response
const bought = await (await payFetch(`http://localhost:4023/buy/${listing.listingId}`, {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ buyer: "WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW" })
})).json();
bought.token.holder; // { rail: "solana", address: "Wwwu…T3WwW" }
bought.token.holderKey; // fresh secret — only the buyer sees it
/.well-known/x402 — machine-readable manifest, both networks per resource (indexable by x402scan.com, the x402 Bazaar, agentic.market)