Open a dispute against a payment that actually settled. Evidence is hashed, never stored — you get a signed case record with every hash and a combined root, a signed status snapshot per poll, and a signed ruling with a refund instruction naming the payee wallet on its rail.
git clone https://github.com/nirholas/x402-disputes && cd x402-disputes 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.networkoverdue after itBecause only hashes are stored, anyone can later prove a document was the one filed: re-hash it at POST /evidence/hash and compare against the case's evidenceRoot. The service cannot leak evidence it never had.
| Route | Price | Returns |
|---|---|---|
POST /cases | $0.01 | Signed case record + evidence hash |
GET /cases/:id | $0.001 (per poll) | Status snapshot |
POST /rule/:id | free | Signed ruling + refund instruction |
POST /evidence/hash | free | Item hashes, combined root, and a `matches` flag against a filed case |
GET /cases | free | Case index |
GET /rulings/:caseId | free | Signed ruling |
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));
// $0.01 — open the case. Evidence is hashed here and never stored.
const record = await (await payFetch("http://localhost:4025/cases", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({
claimant: "WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW",
respondent: { merchantId: "osteria-fiorentina.example" },
disputedPayment: { network: "solana-devnet", transaction: "5Kq7xJ2mNsignature", amount: "$0.75" },
claimType: "not_delivered",
statement: "Arrived on time, no reservation on file.",
evidence: [{ label: "booking-confirmation", content: confirmationEmail }]
})
})).json();
record.evidenceRoot; // fixes the whole bundle
// $0.001 per poll — each read is a fresh signed snapshot
const snap = await (await payFetch(
`http://localhost:4025/cases/${record.caseId}`
)).json();
snap.status; // "awaiting_response" | "ruled" | …
snap.ruling; // signed ruling + refund instruction, once one exists
/.well-known/x402 — machine-readable manifest, both networks per resource (indexable by x402scan.com, the x402 Bazaar, agentic.market)