Trade architecture / runtime map

Skąd dane trafiają, w jakiej postaci i które endpointy naprawdę wystawiamy

Ta strona opisuje faktyczny przepływ runtime dla waszej appki: browser -> frontend -> API/Hasura -> Postgres/Redis oraz zależności od Agave RPC i Yellowstone gRPC. To nie jest ogólny diagram architektury, tylko mapa wejść, wyjść, formatów danych i tras HTTP/WS używanych przez produkt.

Public web surface

To widzi użytkownik i to jest jedyny realny entrypoint produktu na zewnątrz.
NodePort / browser entry

trade-frontend

Serwuje SPA, obsługuje logowanie, robi proxy do API i Hasury.

NodePort 30081 service 8081 /api/* /graphql
Browser calls

Frontend SPA

Wykres bierze REST JSON. DLOB live bierze GraphQL WebSocket. Auth idzie przez sesję cookie.

GET /whoami POST /auth/login GET /api/v1/chart WS /graphql

App services in k3s

Wewnętrzne usługi aplikacyjne. Tylko frontend jest dziś wystawiony na zewnątrz jako NodePort.
ClusterIP / app logic

trade-api

REST backend. Zwraca candles, przyjmuje tick ingest, zarządza tokenami.

ClusterIP 8787 /v1/chart /v1/ticks /v1/ingest/tick
ClusterIP / query plane

Hasura + Postgres

Frontend subskrybuje Hasurę po GraphQL WS. API czyta i zapisuje przez GraphQL do Hasury.

Hasura 8080 Postgres 5432 GraphQL subscriptions
Redis-backed read side

DLOB stack

publisher 0/1 server 0/1 bo bootstrapping zależy od zdrowego Agave RPC.

Redis 6379 dlob-server 6969 workers

Chain and execution plane

To nie jest wystawiane użytkownikowi końcowemu. To feed i write side dla botów.
mevnode_sol / source of truth

Agave RPC

Punktowe odczyty: getAccountInfo, getMultipleAccounts, getSlot, getHealth.

127.0.0.1:8899 RPC only
mevnode_sol / live feed

Yellowstone gRPC

Pushowy stream kont, slotów i transakcji po wg0.

10.91.0.1:10000 token auth
future execution split

tx-router / TPU / Jito

To jest write path. Nie mylić z Yellowstone, który jest tylko read streamem.

End-to-end runtime flow
1. BrowserUżytkownik otwiera SPA i trzyma sesję cookie.
2. trade-frontendSerwuje UI i robi reverse proxy do /api i /graphql.
3. trade-api / HasuraREST dla chartów, GraphQL WS dla live DLOB.
4. Postgres / RedisTrwały stan i szybki stan pośredni.
5. DLOB workersPrzeliczają depth, slippage, orderbook views.
6. Agave / YellowstoneRPC do odczytów punktowych, gRPC do streamu live.

Najważniejszy podział: chart path = REST JSON przez trade-api, a DLOB live = GraphQL WS przez Hasurę. To oznacza, że nie cały frontend jedzie jednym typem transportu. Produkt ma dwa równoległe read pathy.

Surface Endpoint Transport Input Output Where it goes
Frontend GET /whoami HTTP JSON cookie session { ok, user, mode } handled directly by trade-frontend
Frontend POST /auth/login HTTP JSON/form username, password { ok, user } + session cookie handled directly by trade-frontend
Frontend POST /auth/logout HTTP JSON session cookie { ok: true } handled directly by trade-frontend
Frontend proxy /api/* HTTP browser request, frontend injects read token proxied API response trade-api:8787
Frontend proxy /graphql HTTP GraphQL GraphQL query/mutation GraphQL response hasura:8080/v1/graphql
Frontend proxy WS /graphql GraphQL WebSocket subscription payload live subscription frames hasura:8080/v1/graphql
API GET /v1/chart HTTP JSON symbol, tf, limit, optional source candles + indicators + flow rows Hasura function get_drift_candles + table drift_ticks
API POST /v1/ingest/tick HTTP JSON tick payload { ok, id } writes tick through Hasura into DB
API GET /v1/ticks HTTP JSON symbol, optional source, limit, from, to tick list reads drift_ticks through Hasura
API admin POST /v1/admin/tokens HTTP JSON name, scopes new API token stored in DB via Hasura
API admin POST /v1/admin/tokens/revoke HTTP JSON token id revocation status updates token row in DB
Chain read Agave RPC JSON-RPC account/state requests point reads mevnode_sol
Chain live Yellowstone gRPC gRPC streaming subscription config + token account/slot/tx stream mevnode_sol
Chart response
{
  "ok": true,
  "symbol": "SOL-PERP",
  "tf": "1m",
  "bucketSeconds": 60,
  "candles": [
    {
      "time": 1710000000,
      "open": 132.1,
      "high": 133.2,
      "low": 131.8,
      "close": 132.9,
      "volume": 412,
      "oracle": 132.7,
      "flow": { "up": 0.46, "down": 0.41, "flat": 0.13 },
      "flowRows": [1, 1, 0, -1],
      "flowMoves": [0.2, 0.1, 0, 0.3]
    }
  ],
  "indicators": {
    "sma20": [{ "time": 1710000000, "value": 131.4 }],
    "ema20": [{ "time": 1710000000, "value": 131.8 }],
    "bb20": { "upper": [], "lower": [], "mid": [] },
    "rsi14": [],
    "macd": { "macd": [], "signal": [] }
  }
}
Tick ingest body
{
  "ts": "2026-03-13T11:20:00.000Z",
  "market_index": 0,
  "symbol": "SOL-PERP",
  "oracle_price": "132.70",
  "mark_price": "132.91",
  "oracle_slot": 406125100,
  "source": "drift",
  "raw": {
    "provider": "internal"
  }
}
GraphQL DLOB subscriptions
subscription DlobStats($market: String!) {
  dlob_stats_latest(where: {market_name: {_eq: $market}}, limit: 1) {
    market_name
    mark_price
    oracle_price
    best_bid_price
    best_ask_price
    mid_price
    spread_abs
    spread_bps
    depth_bid_usd
    depth_ask_usd
    imbalance
    updated_at
  }
}
L2 payload shape
{
  "market_name": "SOL-PERP",
  "bids": [
    { "price": 132910000, "size": 2500000000 }
  ],
  "asks": [
    { "price": 132930000, "size": 1700000000 }
  ],
  "updated_at": "2026-03-13T11:20:01.000Z"
}

Frontend przelicza to przez:
- pricePrecision = 1_000_000
- basePrecision = 1_000_000_000

Krytyczne rozróżnienie: DLOB live nie idzie z REST API. DLOB do UI idzie przez Hasura GraphQL subscriptions, a chart przez REST JSON z trade-api. To są dwa osobne transporty, dwa osobne modele danych i dwa osobne źródła opóźnień.