Micro Socket–style gateway that exposes a main socket (raw Bitcoin/crypto prices from Binance) and sub sockets (channels) where each channel applies per-symbol margins (spread) to the main feed. Different clients can subscribe to the main stream or to a channel and receive different prices (raw vs. adjusted).
- Bun >= 1.0
bun install
bun run dev- WebSocket:
ws://localhost:4000/ws— subscribe tomain(raw) orchannel(withchannelId) - Docs:
http://localhost:4000/docs— REST + WS playground - OpenAPI:
http://localhost:4000/openapi - REST:
GET /prices,GET /stats,GET|POST /channels,GET|PATCH|DELETE /channels/:id
┌─────────────────────────────────────────────────────────┐
│ Price Feed Gateway │
│ │
Binance WSS │ ┌──────────────┐ ┌─────────────────────────────┐ │
(miniTicker) ────►│ │ Price Store │────►│ Broadcast: main + channels │ │
│ └──────────────┘ └─────────────────────────────┘ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ stream: main stream: │
│ │ (raw ticks) channel:id │
│ │ │ (price │
│ │ │ + margin)│
│ │ │ │ │
└────────────┼────────────────────────┼────────┼─────────┘
│ │ │
▼ ▼ ▼
GET /prices Client A Client B
(snapshot) (raw) (ETH+10, BTC+50)
- Upstream: Single WebSocket connection to Binance public stream (
btcusdt@miniTicker,ethusdt@miniTicker,bnbusdt@miniTicker). No API key. - Main socket: Logical stream
main. Subscribers receive every price tick as-is (raw). - Sub sockets (channels): Each channel has an id and a margins map (e.g.
{ "ETHUSDT": 10, "BTCUSDT": 50 }). Subscribers of that channel receive the same ticks with price + margin for each symbol. So one client can get raw prices, another can get “ETH +10, BTC +50” for their own spread/markup.
| Method | Path | Description |
|---|---|---|
| GET | /prices |
Last price per symbol (snapshot from main feed). |
| GET | /stats |
{ connections, streams, subscriptions }. |
| GET | /channels |
List all channels (id, margins, createdAt). |
| GET | /channels/:id |
Get one channel. |
| POST | /channels |
Create channel. Body: { "margins": { "BTCUSDT": 100, "ETHUSDT": 10 } }. Returns channel with id. |
| PATCH | /channels/:id |
Update margins. Body: { "margins": { ... } }. |
| DELETE | /channels/:id |
Delete channel; subscribers of that channel stop receiving. |
-
Endpoint:
ws://localhost:4000/ws(orwss://when TLS is used). -
Single message type:
subscribe. After connect, send exactly one of:-
Main (raw):
{ "type": "subscribe", "stream": "main" }You then receive a stream of objects:
{ "stream": "main", "tick": { "symbol", "price", "priceNum", "eventTime", ... }, "at": "ISO8601" }. -
Channel (adjusted):
{ "type": "subscribe", "stream": "channel", "channelId": "<uuid-from-POST-/channels>" }You then receive:
{ "stream": "channel", "channelId": "<id>", "tick": { ... }, "margin": <number>, "at": "ISO8601" }.tick.priceandtick.priceNumare already adjusted (main price + channel margin for that symbol).
-
-
Responses:
{ "type": "subscribed", "stream": "main" }or{ "type": "subscribed", "stream": "channel", "channelId": "<id>" }{ "type": "error", "code": "CHANNEL_NOT_FOUND", "channelId": "..." }if channelId is invalid{ "type": "error", "code": "INVALID_JSON" }/EXPECTED_SUBSCRIBE/INVALID_STREAMfor bad requests
- Start the server:
bun run dev. - Create a channel with margins:
curl -X POST http://localhost:4000/channels -H "Content-Type: application/json" -d '{"margins":{"ETHUSDT":10,"BTCUSDT":50}}'
Response:{ "id": "<uuid>", "margins": { "ETHUSDT": 10, "BTCUSDT": 50 }, "createdAt": "..." }. - Open two WebSocket clients (e.g. browser console or
/docsplayground):- Client A: Connect to
ws://localhost:4000/ws, send{ "type": "subscribe", "stream": "main" }. You receive raw Binance prices (e.g. ETHUSDT 3500.00). - Client B: Connect, send
{ "type": "subscribe", "stream": "channel", "channelId": "<uuid>" }. You receive the same symbols but with +10 for ETH and +50 for BTC (e.g. ETHUSDT 3510.00, BTCUSDT 43250 + 50).
- Client A: Connect to
- So: main socket = single source of truth (raw); sub sockets = per-channel margins for different clients.
src/
index.ts - Elysia app: REST (channels, prices, stats), WS /ws (subscribe main/channel), /docs
config.ts - PORT, BINANCE_WS_URL, symbols
feed-client.ts - Binance WSS connection; normalizes ticks; updates price-store and broadcast
price-store.ts - In-memory last price per symbol
channels.ts - Channel CRUD (id, margins)
stream-store.ts - Sub-socket store (main, channel:<id>); broadcastTick to main + all channels
types.ts - PriceTick, MainStreamPayload, ChannelStreamPayload, Channel
docs-html.ts - /docs page (REST + WS playground)
- PORT — HTTP/WS server port (default
4000). - BINANCE_WS_URL — Upstream Binance stream. Default: combined
btcusdt@miniTicker,ethusdt@miniTicker,bnbusdt@miniTicker. Change to add/remove symbols (see Binance WebSocket Streams).
- Single upstream connection: One Binance WebSocket; one price store; one broadcast path. No duplicate feed connections per channel.
- Margins in quote units: Margins are additive (e.g. +10 USDT). Applied per symbol per channel; missing symbol = 0 margin.
- Streams as sub-sockets:
mainandchannel:<id>are logical streams; each has a set of connection ids. Broadcast is O(subscribers) per stream. - Channel lifecycle: Deleting a channel removes its stream; existing subscribers stop receiving; connections are not closed.
- No auth in this example: For production, add authentication and authorization for REST and WebSocket (e.g. API keys, JWT) and optionally ACL for which channels a client can subscribe to.