Skip to content

Stock Trading Architecture

Backpack treats stocks as tokenized real-world assets: actual shares are held in custody by a TradFi broker (Atomic Vaults), and each share is represented 1:1 as a Solana SPL token on-chain. This lets stock positions participate in the same cross-asset margin engine as crypto without requiring a separate settlement rail.


Asset Representation

Stocks exist in two database layers:

  • tokens — the on-chain token (same table as crypto assets)
  • token_external_symbol — maps each stock to its external identifiers:
    • cusip — for regulatory reporting
    • pyth_symbol / dxfeed_symbol — real-time price feed sources
    • atomic_vaults_symbol — identifier used with the custody/settlement broker

A CUSIP is stored per-token, which is necessary for broker-dealer compliance. Crypto tokens have no equivalent field.


Order Routing

Stocks currently trade exclusively via RFQ (request-for-quote). There is no stock order book yet — MarketScope::Stock exists in the codebase and is separated from MarketScope::Spot at the jurisdiction policy layer, but order-book routing for equities is a planned addition.

Access is gated by a per-user is_stocks_enabled feature flag. The @stocks symbol group automatically expands to all entries in token_external_symbol, enabling bulk jurisdiction policy rules without enumerating every ticker.


RFQ Flow

The stock broker is an independent service that manages the lifecycle of each RFQ and bridges Backpack's engine to Atomic Vaults.

State machine

Received → QuoteSubmitted → AcceptedBinding → ReadyToSettle → Filled
                                                            ↘ WithdrawingToken → Filled  (sell side)

Step-by-step

  1. User submits RFQ — e.g., "buy 5 AAPL.US". Engine validates, locks USDC, opens a 60-second submission window.

  2. Stock broker receives RfqEvent::New — validates market session is open (US equities: 9:30–16:00 ET regular hours, plus pre/post/overnight sessions), checks quantity limits, fetches real-time mid price from Pyth Lazer or dxFeed.

  3. Quote calculation:

    bid = broker_bid × (1 − margin_bps) × (1 − aggression_bps)
    ask = broker_ask × (1 + margin_bps) × (1 + aggression_bps)

    Default margin_bps is 20 (0.20%). aggression_bps biases the price toward faster fills. Both are runtime-configurable per-session.

  4. Stock broker submits two-sided quote to the engine via ExchangeCommand::SubmitQuote.

  5. User accepts — engine locks payment, notifies stock broker via RfqEvent::AcceptedBinding.

  6. Stock broker places order at Atomic Vaults — a limit order with external_id = "rfq-{rfq_id}-001" for idempotent retry. Price is set at quoted_price / (1 ± margin), clamped to quoted price so the user never gets a worse fill than quoted.

  7. Atomic Vaults fills the order against a real stock exchange. Fill events stream back over WebSocket.

  8. Post-fill (buy side): stock broker fires CustodyCommand::MintStockTokens. The custody service mints the corresponding SPL token on Solana — this happens concurrently with settlement, not blocking it.

    Post-fill (sell side): stock broker fires WithdrawToken to pull shares back from the bridge account. RFQ enters WithdrawingToken state until withdrawal confirms.

  9. Stock broker verifies its own balances, then fires ExchangeCommand::SettleQuote with optional settle_price / settle_quantity for price improvement if the broker got a better fill than quoted.

  10. Engine settles — user receives AAPL tokens (buy) or USDC (sell), locked funds are released. RFQ reaches terminal Filled state.

Broker order management

  • Orders are polled every 5 seconds; stale orders are queried or cancelled.
  • Hard timeout: 30 seconds before cancel.
  • Stock broker can reload all state from a Redis event stream on restart and reconciles any non-terminal orders with Atomic Vaults.

Settlement

Settlement is always deferred — unlike crypto spot trades which settle atomically, stocks require the broker fill to complete before funds move.

Key mechanics:

  • Payment is locked (moved to locked balance) on quote acceptance, not transferred.
  • SettleQuote carries optional settle_price and settle_quantity, allowing the broker to pass through price improvement.
  • If the broker gets a partial fill, settle_quantity reflects only the filled portion.

Post-trade equity reconciliation

After settlement the engine may have residual unsettled_equity — the gap between realized P&L and available balance. The handle_settle_equity command resolves this:

  • User owes exchange: drain available USDC → redeem USDC lends → create a USDC borrow (bounded by MMF, not IMF)
  • Exchange owes user: draw from the liquidity fund up to min(unsettled_equity, liquidity_fund.available)
  • An optional collateral_conversion parameter can force-sell non-USDC collateral first before creating a borrow.

Stocks as Collateral

The collateral system is asset-agnostic. AAPL tokens contribute to margin the same way BTC does:

collateral_value = balance × mark_price × haircut_function(notional)

Haircut functions use inverse-square-root curves — larger positions receive a steeper haircut to penalize concentration. IMF (initial margin fraction) controls entry; MMF (maintenance margin fraction) triggers liquidation.

Cross-asset margin means stock holdings can offset futures margin requirements. The liquidator's collateral reconciler handles undercollateralized accounts by converting stock tokens (and other non-USDC collateral) to USDC via spot orders or RFQ, in order of MMF priority.


Market Data

Stocks draw from multiple price sources:

  • Pyth Lazer — low-latency on-chain oracle (primary for quoting)
  • dxFeed — traditional equities market data provider (fallback)
  • Atomic Vaults — custody system, also provides fill prices

There is no explicit NBBO aggregation wired up. Instead, the stock broker applies a local spread markup and places limit orders at Atomic Vaults, letting the broker route to the best available price. Explicit NBBO monitoring is a future enhancement.


Market Hours

Stock trading is session-gated. Pre-configured sessions:

SessionHours (ET)Days
Pre-market04:00–09:30Mon–Fri
Regular09:30–16:00Mon–Fri
Post-market16:00–20:00Mon–Fri
Overnight20:00–04:00Sun–Thu

An external_market_holidays table enforces full and partial-day closures (e.g., early closes). Per-token session overrides and per-session quantity limits (min/max/step_size) are supported.


Compliance & Jurisdiction

  • PolicyScope::Stock is enforced separately from PolicyScope::Spot — equities get their own jurisdiction policy rules.
  • CUSIP codes are stored per-token for regulatory reporting.
  • Entity records track LEI numbers, FATCA reportability, and client categorization (Retail / Professional / Eligible Counterparty) — required for broker-dealer operations.

Key Components

ComponentRole
stock-broker/src/core/processor.rsRFQ state machine; manages Atomic Vaults orders
stock-broker/src/core/state.rsRfqStatus enum
engine/src/engine/rfq/mod.rsEngine-side RFQ handling, quote acceptance, locking
engine/src/engine/settle_equity/commands.rsPost-trade equity reconciliation
engine/src/clearing_house/settlement/rfq.rsRFQ settlement logic
store/src/token_external_symbol.rsStock-to-external-market mapping
store/src/external_market_sessions.rsMarket hours configuration
liquidator/src/tasks/collateral_reconciler.rsCollateral conversion and liquidation