- Backend:
apps/backend(NestJS) - Web:
apps/web(Next.js)
- Subscribes to Solana logs and parses swaps from raw pre/post token balances
- Stores swaps in Postgres via TypeORM (Postgres driver)
- WebSocket updates via Socket.IO
- REST:
GET /swaps/latest - Web UI: auto-updates last 10 swaps
- Dockerfiles + docker-compose
Backend (apps/backend/.env):
# Required: Postgres connection
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/swaps
# Optional: API port (default 4000)
PORT=4000
# Solana RPC (HTTP). Use a provider with sufficient limits
SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
# Optional: Solana WS endpoint (for logs). If provider lacks logsSubscribe, leave empty
# SOLANA_WS_URL=wss://your-ws-provider
# Tuning for rate limits / retries
# Concurrent signature processing (default 1)
SOL_TX_CONCURRENCY=1
# Min delay between RPC calls in ms (default 250)
SOL_TX_MIN_DELAY_MS=250
Frontend (apps/web):
# Backend base URL used by the web app
NEXT_PUBLIC_API_URL=http://localhost:4000
# Explorer base for tx/user links (optional; default https://solscan.io)
# NEXT_PUBLIC_SOL_EXPLORER_BASE=https://solscan.io
- Postgres running locally, create DB
swapsand setapps/backend/.env:DATABASE_URL=postgresql://postgres:postgres@localhost:5432/swapsPORT=4000SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
- Database schema:
- Dev:
synchronize: truewill auto-create tables. - Prod: disable
synchronizeand run TypeORM migrations.
- Dev:
- Start backend and web:
Or concurrently from root if you install concurrently:
npm run dev:backend npm run dev:web
npm run dev.
docker compose up --build- Backend:
http://localhost:4000 - Web:
http://localhost:3000
Compose uses:
- Postgres 16 (user
postgres, passwordpostgres, dbswaps) - Backend container with
DATABASE_URL=postgresql://postgres:postgres@db:5432/swaps - Web container pointing
NEXT_PUBLIC_API_URLat backend
You can override compose envs via .env.docker in the repo root (compose reads it automatically).
- No high-level SDKs used for parsing swaps; we read
getTransactiondata and infer swaps using pre/post token account balances for the signer. This catches swaps executed via aggregators (e.g., Jupiter) on top of underlying DEX programs (Orca/Raydium), without Anchor parsers. - For production-grade accuracy, extend with per-DEX instruction decoding to map exact pools and fees.
- Scaling to thousands TPS:
- Use
onLogs(programId)for targeted DEX IDs and multiple workers - Batch
getTransactionviagetBlock/getSignaturesForAddressand decode offline - Connection pooling for Postgres; write-ahead queue (e.g., Kafka/NATS)
- Use
- Reorg handling:
- Subscribe at
finalizedand reconcile differences; maintain seen-signatures by slot and re-validate on slot rollback
- Subscribe at
- Retry and backfill:
- Retry
getTransactionfailures with exponential backoff - Backfill recent slots on startup to cover downtime
- Retry
- Metrics and ops:
- Prometheus metrics (queue size, swaps/sec, RPC latencies)
- Structured logging and alerting
- Health endpoints (liveness/readiness)
- Data quality:
- Decode instructions per protocol (Orca, Raydium) to get pool addresses and fee info
- Map token mints to symbols via a curated token list
- Architecture:
- Add an EVM listener service subscribing to logs for swap events (UniswapV2/V3, etc.)
- Normalize into shared
Swapabstraction (chain, txHash, fromToken, toToken, amounts, blockNumber/time, user)
- Tools:
ethersorviemfor JSON-RPC- ABIs for routers/pools; multicall for efficiency
- Shared layer design:
- Interface-driven parser modules per chain/dex
- Common storage schema with
chainandlogIndex
- REST endpoint: Implemented (
GET /swaps/latest). - Min USD filter: Implemented (USDC-only baseline)
- Client: number input on the page
- Server:
GET /swaps/latest?min_usd=1000(filters USDC amounts) - Future: integrate price API (e.g., Jupiter) to compute USD for all tokens server-side
- Show current slot/count: Implemented via
/swaps/status+ UI badge on the page - Telegram bot: Not included to keep scope focused. Could be added as a small Nest provider posting on new swaps.
- Open the web UI and trigger a swap: the newest entry appears at the top, older entries drop off after 10.
- Adjust the Min USD field: the list updates according to the threshold (USDC baseline).
- Current slot badge updates roughly every 5s.
- API endpoints:
GET /swaps/latest(optionally?limit=10&min_usd=1000)GET /swaps/status
- RPC provider:
- If
logsSubscribeis not supported, the service still works via polling fallback (tunable). - Prefer a provider with WS logs (paid tiers often required).
- If
- Rate limits (envs):
SOL_TX_CONCURRENCY(default 1): signature processing concurrencySOL_TX_MIN_DELAY_MS(default 250): min gap between RPC callsSOL_POLL_INTERVAL_MS(if polling enabled): signature polling cadence
- WebSocket:
- Backend Socket.IO path:
/socket.io - Frontend will fall back to polling if WS upgrade fails.
- Backend Socket.IO path:
- Socket 404 on
/socket.io: ensure backend is running on port 4000 and Socket.IO adapter is mounted; no reverse proxy on that port. - 429 Too Many Requests: lower
SOL_TX_CONCURRENCY, increaseSOL_TX_MIN_DELAY_MS, or use a higher-capacity RPC. - No logs with certain providers: set
SOLANA_WS_URLto a WS endpoint that supports logs, or rely on polling.