ClashTX ClashTX Docs
Reference

API reference

The backend is a thin, public read layer over the chain plus a relay for already-signed transactions. Everything below is unauthenticated and rate-limited (one exception: posting news, which is wallet-signature gated, see POST /api/news). Build a bot, a dashboard, or your own frontend against it if you want.

💡

Base URL is wherever the frontend is served from. These examples assume same-origin requests (/api/...).

GET /api/health

Indexer health check. Returns 503 if the indexer hasn't polled recently or found the curve yet.

{
  "ok": true,
  "cluster": "devnet",
  "curveFound": true,
  "lastPolledAt": 1783956542549,
  "staleMs": 812,
  "lastError": null,
  "tradesIndexed": 21
}

GET /api/curve

The current curve snapshot, plus who's winning the jackpot right now (if anyone has bought yet) and who won it (once complete).

{
  "programId": "...",
  "cluster": "devnet",
  "decimals": 6,
  "curve": {
    "exists": true,
    "address": "...",
    "authority": "...",
    "mint": "...",
    "vault": "...",
    "virtualSolReserves": "30162179250",
    "virtualTokenReserves": "1067230578122766",
    "realSolReserves": "162179250",
    "realTokenReserves": "994230578122766",
    "targetSolReserves": "85000000000",
    "isComplete": false,
    "bump": 253
  },
  "currentLeader": { "trader": "...", "signature": "...", "blockTime": 1783951139 },
  "jackpotWinner": null,
  "asOf": 1783956824361
}

Before anyone has ever bought, curve.exists is false and the reserve fields reflect the default shape the first buy will auto-initialize, enough to quote a price without an on-chain account existing yet. All numeric reserve fields are strings (they're u64s on-chain, too large for a plain JS number).

GET /api/trades

Recent trade history, newest first.

?limit=50&offset=0

limit: 1-200, default 50. offset: default 0.

{
  "trades": [
    {
      "signature": "...",
      "blockTime": 1783951139,
      "mint": "...",
      "trader": "...",
      "isBuy": true,
      "solAmount": "500000000",
      "tokenAmount": "17845234123"
    }
  ],
  "total": 21
}

GET /api/news

The news feed: an auto-generated recap posted the instant each game's jackpot pays out, plus admin announcements. Newest first.

?gameId=1&limit=20&offset=0

gameId: optional, scopes to one game's posts. limit: 1-100, default 20.

{
  "posts": [
    {
      "id": "...",
      "type": "auto",
      "gameId": "1",
      "title": "Game #1 complete: 3eGj...Fzsu takes the jackpot",
      "body": "Game #1 wrapped up after 12 trades from 8 unique wallets...",
      "authorWallet": null,
      "meta": {
        "winner": "...", "winnerAmount": "1751377600", "houseAmount": "500393600",
        "rebatePool": "250196800", "realSolReserves": "2000000000",
        "targetSolReserves": "2000000000", "totalBuyVolume": "2501968000",
        "totalTrades": 12, "uniqueTraders": 8, "signature": "..."
      },
      "createdAt": 1783956824361
    }
  ],
  "total": 1
}

type is "auto" for the end-of-game recap (with meta) or "admin" for a manually posted announcement (meta is null, authorWallet is set instead).

POST/api/news

The one authenticated endpoint here: it posts an admin announcement. Unlike every other write in this app, this isn't an on-chain transaction: the caller signs `ClashTX News Post\n${timestamp}\n${title}\n${body}` with their wallet (no gas, no confirmation wait), and the backend checks that signature against whichever wallet is currently GameRegistry.admin. Body: { title, body, wallet, timestamp, signature } (signature base64, timestamp epoch ms, valid for 5 minutes).

GET /api/candles

OHLC price candles, bucketed from trade history. Computed on request, not cached.

?interval=60&limit=1000&decimals=6

interval: bucket size in seconds, minimum 5, default 60.

[
  { "time": 1783951020, "open": 2.88e-8, "high": 2.88e-8, "low": 2.88e-8, "close": 2.88e-8 },
  { "time": 1783951080, "open": 3.93e-8, "high": 5.80e-8, "low": 3.93e-8, "close": 5.80e-8 }
]

GET /api/wallet/:pubkey

SOL and token balance for any address, for the current curve's mint, plus their rebate pool status, if they've ever bought.

{
  "address": "...",
  "lamports": 1695961213,
  "tokenAccount": "...",
  "tokenAmountRaw": "17845234123",
  "tokenUiAmount": 17845.234123,
  "rebate": {
    "totalSolIn": "500000000",
    "claimed": false,
    "claimableRebate": "35714285"
  }
}

rebate.claimableRebate is only non-zero once the curve has completed. Before that, or if this address never bought, totalSolIn is "0". See The Jackpot for how the pro-rata math works.

GET /api/blockhash

A fresh blockhash for building a transaction client-side.

{ "blockhash": "...", "lastValidBlockHeight": 349812345 }
POST/api/transaction

Relays an already wallet-signed transaction. The backend never signs anything. It only checks every instruction targets an allowed program (this program, System, Token, Associated Token, Compute Budget) before broadcasting, so it can't be used as an open relay. buy, sell, and claim_rebate are all just program instructions. There's no separate REST endpoint for claiming a rebate; it goes through this same relay like any other transaction.

// request
{ "signedTransaction": "<base64>" }

// response
{ "signature": "..." }

GET /api/stream

Server-Sent Events. Emits {"type":"trade"} the moment the indexer sees a new trade (pushed from a websocket log subscription, not polling). Subscribe instead of polling /api/curve or /api/trades on a timer if you want near-instant updates.

const es = new EventSource("/api/stream");
es.onmessage = () => {
  // re-fetch /api/curve, /api/trades, /api/candles
};

GET /api/sol-price

Current SOL/USD price, for display purposes only.

{ "solUsd": 178.42 }