jwinterm 074b232267 nonlogs: ignore markets with no 24h volume, like the cexswap leg already does
The cexswap leg drops pairs with no 7-day volume and says why: a stale
last print on a dead pair must never drag the average. The nonlogs leg
did not, and averaged WOW-USDT — a 0.00723 print with null volume — against
a WOW-BTC route with real trades behind it.

Today that pulls the nonlogs leg 15% low, and since the leg is half the
WOW blend, the published price 8.4% low: 0.009830 against 0.010653. The
drag grows as WOW moves away from whenever that print happened.

Both routes are now volume-weighted by 24h quote volume, and a venue with
no volume anywhere is omitted so the other venue's sample stands alone —
the same shape as the cexswap function.

This feed is what Hash Bags, Smirk, wowlet and the desk's own fair value
display, so the number moves for all of them on deploy.
2026-09-03 12:51:21 -04:00

hash-wallet-prices

Cloudflare Worker that aggregates USD prices for Hash Wallet and serves them at one cached endpoint.

Why this exists: see the architectural notes in the wallet repo. Short version — having every user's phone hit Kraken/NonKYC directly leaks wallet-holder activity to the exchanges and spreads our rate-limit budget across thousands of IPs. One Worker with a cron-refreshed KV cache fixes both.

Sources

  • Kraken (api.kraken.com/0/public/Ticker) — BTC, XMR, LTC, DOGE, ETH, BCH, XNO (all → USD).
  • Nonlogs.io (api.nonlogs.io/api/markets) — WOW. Algorithm averages WOW-BTC × Kraken BTC/USD and WOW-USDT when both exist. Lifted from ~/src/smirk-backend/src/infra/prices.rs.
  • cexswap.cc (cexswap.cc/api/public/markets/summary) — WOW backup. Endpoint returns last_usd precomputed per pair, so we average across all WOW spot pairs.
  • Pinned — USDT, USDC, DAI all return 1.00.

WOW USD price is the average of whichever Nonlogs and cexswap routes returned a value — survives one source going down without a code change.

Endpoints

Path Shape
GET /v2/rates?base=BTC&quote=USD { "results": { "BTC_USD": 67342.5 } } — drop-in compatible with the wallet's existing fetch code
GET /v1/prices { "rates": { ... }, "sources": { ... }, "fetched_at": "..." } — preferred, returns everything in one call
GET /v1/sparkline?coin=BTC&hours=24 { coin, hours, points: [[ts_ms, price], ...] } — hourly buckets, max 168 points (7 days)
POST /v1/trocador-webhook Trocador POSTs the full trade body on every status change; we store it under trade:<id> for 14 days
GET /v1/trade/:trade_id Returns the cached trade body (whatever Trocador last sent for that id), or 404
GET /healthz { ok: true, coins: N, fetched_at: ... }

Non-USD quotes convert through ECB daily reference rates (fetched each cron run); an unknown quote currency returns 0 and the wallet falls back to "price unavailable."

Deploy

npm install
npx wrangler login
npx wrangler kv:namespace create PRICES
# Paste the returned id into wrangler.toml's kv_namespaces.id field.
npx wrangler deploy

After first deploy the worker is live at https://hash-wallet-prices.<your-cloudflare-subdomain>.workers.dev. To put it behind prices.suchsoftware.com, point that hostname's DNS at Cloudflare and uncomment the [[routes]] block in wrangler.toml.

Local dev

npm install
npx wrangler dev          # starts a local server with hot reload
curl localhost:8787/healthz
curl 'localhost:8787/v2/rates?base=BTC&quote=USD'

Adding a coin

  1. If Kraken lists it: add TICKER: "PAIRUSD" to KRAKEN_PAIRS in src/index.ts.
  2. If only smaller exchanges list it: add a new fetcher function alongside fetchNonkyc() and merge its results in refreshPrices(). Keep it try/catch wrapped so a single source failure doesn't poison the whole refresh.

License

MIT.

S
Description
Cloudflare Worker that aggregates USD prices for Hash Wallet (Kraken + Nonlogs + cexswap.cc)
Readme
154 KiB
Languages
TypeScript 100%