CROSSBOOK
Launch App
Platform

Architecture

The adapter boundary between the interface and execution, and how contracts replace the simulation.

The interface is written against one interface, ExchangeAdapter. Market data, pre-trade estimates, order placement, cancellation and balances all pass through it, so the execution venue is a swappable implementation detail rather than something threaded through the components.

Execution pathOne adapter, two implementations
Interface
Terminal, order ticket, portfolio, docs
ExchangeAdapter
Market data, validation, place, cancel, balances
MockAdapterActive
Active in this build
ContractAdapterNot connected
Robinhood Chain contracts
Simulation
Deterministic market data · local matching engine · localStorage
Chain state
Onchain book · matching · token settlement
The interface never talks to an execution venue directly. It calls the adapter interface, which today resolves to the simulation and is intended to resolve to deployed contracts.

The adapter boundary

No component imports the simulation or the matching engine directly for anything that would eventually be onchain. That single rule is what makes the diagram above true rather than aspirational.

src/lib/exchange/adapter.ts
export interface ExchangeAdapter {
  readonly id: "simulation" | "onchain";
  readonly isLive: boolean;

  // market data
  getTicker(symbol: string, at: number): Ticker;
  getCandles(symbol: string, timeframe: Timeframe, count: number, at: number): readonly Candle[];
  getOrderBook(query: OrderBookQuery): OrderBookSnapshot;
  getRecentTrades(symbol: string, at: number, count: number): readonly PublicTrade[];

  // account reads
  getBalances(): readonly Balance[];
  getOpenOrders(symbol?: string): readonly Order[];

  // pre-trade
  estimate(symbol, side, amount, at, limitPrice?): ExecutionEstimate | null;
  validate(draft: OrderDraft, at: number): ValidationResult;

  // writes
  placeOrder(draft: OrderDraft, at: number): Promise<PlaceOrderResult>;
  cancelOrder(orderId: string, at: number): Promise<boolean>;
}

Layers

LayerLocationResponsibility
Interfacesrc/app, src/componentsRoutes, terminal, order ticket, portfolio, docs
Adaptersrc/lib/exchange/adapter.tsThe only contract the interface depends on
Simulationsrc/lib/exchange/mockAdapter.tsActive implementation: market data plus local matching
Onchainsrc/lib/exchange/contractAdapter.tsStub for deployed contracts; every method throws today
Market datasrc/lib/exchange/marketData.tsDeterministic prices, candles, depth and prints
Enginesrc/lib/exchange/orderEngine.tsValidation, book walking, fills, resting orders, fees
Statesrc/stateBalances, orders, trades, activity, UI preferences
Configsrc/configBrand, chain and market registries

Replacing the simulation

contractAdapter exists and is deliberately unimplemented: every method throws instead of returning plausible-looking data, which keeps the codebase honest about what is connected. Wiring a real deployment means filling in that one file and setting the addresses in chain configuration.

  • Map a Market to the sorted token pair and the active book epoch.
  • Convert display prices to the engine's tick representation and sizes to raw units.
  • Submit orders with the flags implied by the selected time in force, and read resting quantity back from the book.
  • Derive order status from book presence and remaining quantity, as an indexer would.
  • Read balances from chain state instead of local storage.
Selecting the implementation
// src/lib/exchange/index.ts
export const exchange: ExchangeAdapter = hasDeployment ? contractAdapter : mockAdapter;

Rendering and data flow

  • Market data is read through hooks with per-surface refresh rates: the book updates several times a second, a market list once a second.
  • Time comes from one shared clock, so every panel renders the same instant and the market never appears to disagree with itself.
  • Persisted stores hydrate explicitly, so the first client render matches the server render.
  • The terminal mounts one layout at a time — desktop grid or mobile tabs — rather than rendering both and hiding one.