Hartii developer docs

Games

Public API

Base URL for the hub: https://hartiigames.com. Base URL for Biome: https://hartiibiome.com. Examples below are trimmed real responses captured live against production.

Global state

GET /api/config

Public client config — the kill switch and split constants the client trusts.

json
{
  "paidPlay": false,
  "paidGames": ["hash-wars"],
  "entryAsset": "QUAI",
  "playerAllocationBps": 7500,
  "reserveAllocationBps": 2500
}

paidPlay: false (live at doc time) means ranked play is globally disabled — the client hides all stake UI when this is false. Cache-Control: public, max-age=30.

GET /api/pools

json
{ "paidPlay": false, "pools": {}, "note": "paid play is not yet enabled" }

When paidPlay is true, pools becomes { [gameId]: poolAddress } for each game with ranked enabled (currently only hash-wars would appear, per PAID_GAME_IDS in source).

GET /api/health

json
{ "ok": true, "rpc": true, "price": true, "d1": { "ok": true, "missing": [] }, "at": 1790174499542 }

GET /api/transparency

Public accounting summary — proceeds split, entry-fee split, live pool balances, cosmetics creator-economy rules.

json
{
  "chain": { "id": 9, "name": "Quai mainnet, Cyprus-1" },
  "proceedsSplit": { "playersPct": 50, "buybacksPct": 50 },
  "entry": { "paidPlay": false, "playerAllocationBps": 7500, "reserveAllocationBps": 2500 },
  "pools": [{ "game": "hash-wars", "address": "0x0056C742a880eC0A55b87Db3a893F2a92E94dD89", "balanceQuai": 0 }],
  "creators": { "rule": { "tradeFeeBps": 100, "creatorShareBps": 5000, "maxTradeFeeBps": 300 } }
}

GET /api/gas

One cached gas quote for the client's txs — measured, not guessed (source comment: a real enterRanked() used 24,771 gas, 2026-07-05).

json
{
  "gasPriceWei": "29482625609348",
  "usdPer21k": 0.00636966,
  "usd": { "entry": 0.00758, "approve": 0.01395, "buy1155": 0.03640, "claim": 0.01820 },
  "elevated": false
}

GET /api/quai-price

{ usd, source, at } — MEXC QUAIUSDT primary, CoinGecko fallback, ~2 min edge cache.

Ranked play

POST /api/ranked/enter

Verify an on-chain enterRanked() (or bare-transfer) payment and issue a run seed.

Body: { gameId, tier, wallet, txHash, ack: true, signature?, ref? }. ack (18+/eligibility acknowledgement) is required and checked server-side. Checks performed, in order: paid-play kill switch, jurisdiction gate (cf-ipcountry, edge-verified — not client-suppliable), rate limit (360/hour), gameId must currently be hash-wars (403 for anything else), tx not already used (single-use, exactly-once via a D1 unique constraint — not just a racy KV check), one active run per wallet per game, the tx's to matches that game's pool and from matches the wallet, the paid value is within −12% / +50% of the tier's USD quote at the current QUAI price (tight lower bound against underpaying, wide upper bound to absorb price movement between quote and confirmation — any overpayment simply donates to the pool the player is competing for).

json
{ "runId": "…", "seed": "ranked-hash-wars-…", "gameId": "hash-wars", "tier": "micro" }

Errors are 400/404/409/451 (blocked jurisdiction)/503 (kill switch off, storage unavailable) with { error }.

POST /api/ranked/submit

Body: { runId, score, inputLog }. The server replays the run itself using the seed it issued and rejects on any mismatch — a client's claimed score is never trusted. On success it places the score on the tier's weekly and monthly leaderboards and returns promotion info.

json
{ "accepted": false, "reason": "replay mismatch", "replayScore": 0 }

GET /api/ranked/status, POST /api/ranked/prepare, POST /api/ranked/attach-payment

Supporting steps in the same flow (status polling, a provisional pre-payment run context, and attaching a late-arriving payment to a provisional run) — see functions/api/ranked/*.js.

GET /api/claimable/:wallet

Reads claimableBalances(wallet) across all ten MonthlyGamePayouts pools in parallel (a pure read — the actual claim is the wallet's own claimPayout() transaction, this endpoint never moves funds).

json
{ "wallet": "0x0031b09e63592b713768761cddacc603974c10bd", "pools": [], "totalQuai": 0, "rewards": null, "hartiiClaimable": 0 }

Leaderboards, activity, tournaments

GET /api/leaderboard/:game?board=free|daily|ranked&tier=…&period=weekly|monthly

json
{ "game": "hash-wars", "board": "free", "key": "lb:free:hash-wars",
  "entries": [{ "wallet": "0x00700bd1…", "score": 408832, "at": 1789420663494, "name": "Swimgood" }] }

GET /api/dailies[?wallet=0x…]

Today's shared-seed daily challenge summary per game, plus (with a wallet) that wallet's server-computed streak.

json
{ "date": "20260923", "games": [{ "id": "hash-wars", "top": 0, "players": 0 }], "streak": null }

GET /api/activity

Recent verified runs / claims / ranked entries, newest-first, edge-cached (s-maxage=10, stale-while-revalidate=20) so a burst of viewers costs one origin hit.

json
{ "items": [] }

GET /api/tournaments?status=active|past|all

json
{ "items": [], "status": "active", "at": "2026-09-23T14:50:45.939Z" }

Always returns an honest empty list rather than an error if the sync hasn't run — never a bare {}.

GET /api/profile/:wallet

XP, level (1 + floor(sqrt(xp/50)), capped at 100), Bronze→Diamond rank tier, per-game bests, daily streak.

Cosmetics, marketplace, token

  • GET /api/cosmetics/catalog — shop catalog (type, name, rarity, price, image) plus the live HartiiCosmetics contract address.
  • POST /api/cosmetics/equip, GET /api/cosmetics/equipped/:wallet, GET /api/cosmetics/owned/:wallet
  • GET /api/marketplace/listings — active cosmetics resale listings, self-heals from chain if its KV index is empty.
  • POST /api/marketplace/record — indexes a listing/sale after the client's own on-chain tx.
  • GET /api/token/config
json
{ "token": "0x00356B9bc20Ea80C654D77655Da53ca07547c216", "sale": "0x00483bE6EAaA8015130b216e06d8f70723dF822D",
  "cosmetics": "0x007fDe17CbA1Fe1750906F08f64fEf44195702eA", "market": "0x0034D51cc6162E27bCfDce5E276d92Eb563bb73C",
  "rate": 1000, "decimals": 18, "symbol": "HARTII" }

Identity

  • GET /api/username/:wallet{ wallet, name }, CORS-open, name: null if unclaimed.
  • POST /api/username/claim, GET /api/username/check, GET /api/username/resolve

Studio (creator NFT minting) and vault (IPFS document pinning)

functions/api/studio/** (create/mint a creator-submitted art type, eligibility check) and functions/api/vault/** (nonce, pin, public, list/:wallet — a public, paginated IPFS document directory) exist and are live but are write-gated by wallet signature per-route; not detailed further here beyond GET /api/vault/public?limit=&cursor=, a plain paginated read:

json
{ "items": [{ "cid": "QmWYLKGZSCFHTpv8sgRR4oNCACf5ZQ7DTafcBrcaR1Qmo8", "name": "poetry.txt",
  "textBytes": 3555, "at": 1789494765339, "url": "https://ipfs.qu.ai/ipfs/QmWYLKGZ…" }] }

Out of scope here

functions/api/qaxe/** integrates with the separate QuaiAxe product (documented elsewhere). functions/api/sink/**, admin/settlements.js, auth/nonce.js and the daily/* variants are internal plumbing / operator-only — no public contract documented here beyond "exists and is gated."

Biome endpoints

Base: https://hartiibiome.com. All three are GET, read-only, no wallet/money logic.

GET /api/chain

json
{ "blockNumber": 10250128, "gasPriceWei": "29484720446206", "gasPriceGwei": 29484.72 }

quai_blockNumber / quai_gasPrice against the public RPC, max-age=45. Fields are null (never a fabricated value) when the RPC is unreachable.

GET /api/prices

json
{ "quaiUsd": 0.010354, "changePct": 6.44, "volumeUsd": 93962.9, "qiQuai": 105.025, "qiUsd": 1.087, "spark": [0.009965, "…24 hourly closes"] }

MEXC 24h ticker + quai_qiToQuai RPC read for the Qi↔QUAI protocol rate. ~2 min edge cache.

GET /api/ecosystem

Fans out server-side to Games (/api/tournament), Gallery (/api/collections) and Labs, cached 5 minutes per colo, only caches a response if all three sibling fetches succeeded (x-ecosystem-complete: 1) — a partial failure is never pinned into the cache.

json
{ "games": { "month": "202609", "gamesWithPlay": 10,
  "standings": [{ "wallet": "0x00700bd1…", "points": 5772, "games": 6, "name": "Swimgood" }] } }
Quai Network mainnet · chain 9 · Cyprus-1. Figures marked "read on" a date were read from the chain that day; re-read before relying on them.