Appearance
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_attimestamp) - Idempotent writes via
trade_idunique 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_tradesTrade 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 REPLACEintopublic_trades. Logs each recorded trade.getRecentTrades(params)-- Fetcheslimit + 1rows whereclosed_at < cursor, ordered byclosed_at DESC. DetectshasMoreby checking if extra row exists. Maps rows toPublicTradewith shortened addresses.cleanup()-- Deletes rows not in the top 1000 byclosed_at DESC. Returns count of deleted rows.getTradeCount()--SELECT COUNT(*)frompublic_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.