Skip to content

PublicTradesFeed

Global Durable Object that maintains the latest 1000 closed trades for public visibility.

Purpose

Provides a public feed of recent closed trades across all users. Receives trade events from UserPaperTradePortfolio on position close, stores them in SQLite, and serves cursor-based paginated queries. Wallet addresses are shortened for privacy.

  • Single global instance (id: "global")
  • Capped at 1000 trades via hourly alarm cleanup
  • Cursor-based pagination (by closed_at timestamp)
  • Idempotent writes via trade_id unique constraint

High-Level Design

Data Flow

UserPaperTradePortfolio (position fully closed)

OrderExecutionManager.executeOrder()
    ↓ only when position is fully closed (not partial closes)
    ↓ maps trigger source to public status:
    ↓   LIQUIDATION → LIQUIDATED
    ↓   STOP_LOSS → SL_TRIGGERED
    ↓   TAKE_PROFIT → TP_TRIGGERED
    ↓   (manual/other) → CLOSED

Schedules PUBLIC_FEED_SYNC task via TaskScheduler
    ↓ (async, does not block trade execution)

PublicFeedSyncHandler validates payload, calls recordTrade()

PublicTradesFeed DO: INSERT OR REPLACE into public_trades

Trade sync is deferred via TaskScheduler rather than direct RPC. This decouples the public feed from the critical trade execution path — the user's position close completes immediately, and the feed update happens asynchronously with automatic retry on failure.

Key Concepts

Idempotent Recording: trade_id has a UNIQUE constraint, and writes use INSERT OR REPLACE. TaskScheduler retries and duplicate deliveries are safe — same trade data simply overwrites itself.

Privacy at Response Time: Full wallet addresses are stored in SQLite (enabling future admin queries by address), but shortened to 0x1234...cdef only when returned in API responses.

Cursor-Based Pagination: Clients paginate using closed_at timestamps. The query fetches limit + 1 rows to detect hasMore, and returns the last trade's closedAt as nextCursor. This remains correct even as new trades arrive during pagination.

Bounded Storage: An hourly alarm deletes all rows outside the top 1000 by closed_at DESC. The alarm self-reschedules after each run to guarantee recurrence. All monetary values (realizedPnL, sizeUsd, entryPrice, closePrice) are stored as TEXT strings to preserve BigNumber precision.

PublicTradesStorageManager

The single manager for this DO. Handles all SQLite operations.

Constants

  • MAX_TRADES = 1000 -- maximum trades retained after cleanup.

Methods

  • recordTrade(params) -- INSERT OR REPLACE into public_trades. Logs each recorded trade.
  • getRecentTrades(params) -- Fetches limit + 1 rows where closed_at < cursor, ordered by closed_at DESC. Detects hasMore by checking if extra row exists. Maps rows to PublicTrade with shortened addresses.
  • cleanup() -- Deletes rows not in the top 1000 by closed_at DESC. Returns count of deleted rows.
  • getTradeCount() -- SELECT COUNT(*) from public_trades.

Helper: shortenAddress

Converts "0x1234567890abcdef" to "0x1234...cdef". Returns input unchanged if shorter than 10 characters.

Helper: mapRowToPublicTrade

Maps a database row to the PublicTrade response type, applying shortenAddress to user_id and casting column types.