x402-price-watch

Pay-per-poll price watching. Every check returns a fresh snapshot plus the delta against the cursor you sent — so a single cent buys you a complete, self-contained answer to “what changed since last time?”

USDC on BaseUSDC on SolanaHTTP 402Apache-2.0CoinGecko (live, keyless)Amadeusstateless cursors

Pay on either rail — your client picks. Every 402 challenge lists USDC on Base and USDC on Solana at the same price.

git clone https://github.com/nirholas/x402-price-watch && cd x402-price-watch
npm install && cp .env.example .env   # pre-filled — runs with no edits
npm run dev                            # http://localhost:4044

Pricing

RoutePriceReturns
GET /check$0.002Signed snapshot + delta vs cursor + trend + next cursor
GET /watch-keyfreeThe normalized watch key your parameters map to
GET /sourcesfreeWhich price sources are live here
POST /verifyfreeSignature check for any check artifact
GET /healthzfreeLiveness + accepted rails

Payment rails

USDC on Base

EIP-3009 transferWithAuthorization, signed in the wallet. Nothing on-chain until the facilitator settles.

network
base-sepolia (default) · base
payTo
0x40252CFDF8B20Ed757D61ff157719F33Ec332402
facilitator
https://x402.org/facilitator

USDC on Solana

Fee-sponsored SPL transferChecked. The facilitator's feePayer covers the SOL fee — you need only USDC.

network
solana (default) · solana-devnet
payTo
WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW
facilitator
https://facilitator.payai.network

A rail with no configured address is dropped from the challenge instead of failing the request, so a single-rail deployment is a one-line change.

How a paid call works

1 · RequestGET /check?market=crypto&ids=bitcoin&cursor=…
2 · 402accepts[]: Base and Solana, same price
3 · Paypick a rail, sign, send X-PAYMENT
4 · 200snapshot + delta + new cursor, in-body

The cursor is the whole idea

A subscription price-watcher needs an account, a database of your watches, and a retention policy. This one needs none of that, because the client carries the state. Each response hands back an opaque, HMAC-signed cursor encoding the prices you just saw:

$ curl "…/check?market=crypto&ids=bitcoin,ethereum"          # first poll
{ "snapshot": { "items": [ { "id": "bitcoin", "priceUsd": 64266 } ] },
  "delta": { "hasCursor": false, "cursorStatus": "none" },
  "trend": { "direction": "down", "basis": "provider_24h", "pct": -0.11 },
  "cursor": "eyJrIjoiY3J5cHRvOmJpdGNvaW4…" }

$ curl "…/check?market=crypto&ids=bitcoin,ethereum&cursor=eyJrIjoi…"   # next poll
{ "delta": { "hasCursor": true, "cursorStatus": "applied", "elapsedSeconds": 3600,
             "changes": [ { "id": "bitcoin", "previousUsd": 64266, "currentUsd": 64980,
                            "absUsd": 714, "pct": 1.111, "direction": "up" } ],
             "summary": { "up": 1, "down": 1, "biggestMoverId": "bitcoin" } },
  "trend": { "direction": "up", "basis": "cursor", "pct": 0.62 } }

Cursors are signed, so a client cannot forge a “previous price”, and scoped to their watch key, so a bitcoin cursor can never be compared against a JFK→LAX snapshot. A mismatched or tampered cursor is reported in delta.cursorStatus rather than failing the call — you still get the snapshot you paid for.

Why pay-per-poll

Watching is bursty. You check a fare twice a day for a fortnight and then never again; you check a token every minute for an hour during a move. Subscriptions get that wrong in both directions — you pay through the quiet weeks and get rate-limited in the busy hour. At $0.002 a poll an agent can watch as often as the question deserves and stop the moment it doesn’t.

Sources

MarketProviderLiveNotes
cryptoCoinGecko /simple/pricealways — keylessAlso returns each coin’s own 24h move, used for the trend on a first poll
flightAmadeus /v2/shopping/flight-offerswith free credentialsTracks cheapest fare and cheapest nonstop separately — they move independently

Without Amadeus credentials, flight polls return deterministic fixtures that drift in ten-minute buckets, labeled source: "fixture" and live: false — enough to build a client against, never mistakable for a real quote.