Skip to content

oguzhan18/price-feed-gateway

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Micro Socket Price Feed Gateway

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).


Requirements


Quick Start

bun install
bun run dev
  • WebSocket: ws://localhost:4000/ws — subscribe to main (raw) or channel (with channelId)
  • 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

Architecture (Micro Socket)

                    ┌─────────────────────────────────────────────────────────┐
                    │                  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.

REST API (Summary)

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.

WebSocket Protocol

  • Endpoint: ws://localhost:4000/ws (or wss:// 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.price and tick.priceNum are 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_STREAM for bad requests

Example: Main vs channel (margins)

  1. Start the server: bun run dev.
  2. 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": "..." }.
  3. Open two WebSocket clients (e.g. browser console or /docs playground):
    • 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).
  4. So: main socket = single source of truth (raw); sub sockets = per-channel margins for different clients.

Project layout

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)

Configuration

  • 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).

Design notes (best practices)

  • 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: main and channel:<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.

About

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.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors