Skip to main content

Component Architecture

Exchange Web uses a modular component architecture with clear separation of concerns. Components are organized by feature and responsibility.

Main Components

The application consists of five main top-level components:

SwapInterface

Order entry interface for buying/selling

TradeInterface

Trading view with chart and order book

Depth

Order book and recent trades display

MarketBar

Market information and price ticker

NetBar

Network status and user information

SwapInterface Component

The SwapInterface provides order entry functionality with support for limit and market orders.

Key Features

  • Buy/Sell mode toggle
  • Order type selection (Limit/Market)
  • Real-time fee calculation
  • Position sizing
  • Price integration from context

Component Interface

interface SwapInterfaceProps {
  market: string;
}

export const SwapInterface = ({ market }: SwapInterfaceProps) => {
  // Implementation
}

State Management

From apps/web/src/components/SwapInterface.tsx:7-17:
const { price } = useContext(TradesContext);
const currentPrice = parseFloat(price ?? "0");

const [isBuyMode, setIsBuyMode] = useState(true);
const [orderType, setOrderType] = useState("Limit");
const [limitPrice, setLimitPrice] = useState(currentPrice);
const [size, setSize] = useState("");
const [maxUSD, setMaxUSD] = useState(0.0);
const [fees, setFees] = useState(0.0);
const [position, setPosition] = useState(0.0);

Order Creation

From apps/web/src/components/SwapInterface.tsx:29-62:
const handleCreateOrder = async () => {
  const quantity = Number(size);

  if (!quantity || quantity <= 0) {
    toast.error("Please enter a valid size greater than zero.");
    return;
  }

  if (orderType === "Limit" && (limitPrice <= 0 || isNaN(limitPrice))) {
    toast.error("Please enter a valid limit price.");
    return;
  }

  const side = isBuyMode ? "BUY" : "SELL";
  const orderPrice = orderType === "Market" ? currentPrice : limitPrice;

  const order: CreateOrder = {
    market,
    side,
    quantity,
    price: orderPrice,
    userId: localStorage.getItem("user_id") ?? "test_user",
  };

  try {
    const response = await createOrder(order);
    console.log("Order created:", response);
    toast.success("Order created successfully!");
  } catch (error) {
    console.error("Error creating order:", error);
    toast.error("Error creating order!");
  }
};

TradeInterface Component

A composition component that combines the trading chart and order book display.

Component Code

From apps/web/src/components/TradeInterface.tsx:1-12:
import { Depth } from "./Depth";
import { TradeView } from "./trade_interface/TradeView";

export const TradeInterface = ({ market }: { market: string }) => {
  return (
    <div className="grid grid-rows-[600px_1fr] grid-cols-1 md:grid-cols-[3fr_1fr] gap-2">
      <TradeView market={market as string} />
      <Depth market={market as string} />
    </div>
  );
};

Layout

  • Desktop: Side-by-side layout (3:1 ratio)
  • Mobile: Stacked layout
  • Chart Area: Fixed 600px height
  • Depth Area: Responsive height

Depth Component

Manages order book and recent trades with real-time WebSocket updates.

Props Interface

interface DepthProps {
  market: string;
}

State from Context

From apps/web/src/components/Depth.tsx:14-22:
const {
  setTrades,
  setBids,
  setAsks,
  setPrice,
  setTotalBidSize,
  setTotalAskSize,
  orderBookRef,
} = useContext(TradesContext);

WebSocket Integration

The component registers callbacks for depth and trade updates: From apps/web/src/components/Depth.tsx:24-96:
useEffect(() => {
  WsManager.getInstance().registerCallback(
    "depth",
    (data: any) => {
      console.log("depth has been updated");
      console.log(data);

      setBids((originalBids) => {
        let bidsAfterUpdate = [...(originalBids || [])];

        // Update existing bids
        for (let i = 0; i < bidsAfterUpdate.length; i++) {
          for (let j = 0; j < data.bids.length; j++) {
            if (bidsAfterUpdate[i][0] === data.bids[j][0]) {
              bidsAfterUpdate[i][1] = data.bids[j][1];
              if (Number(bidsAfterUpdate[i][1]) === 0) {
                bidsAfterUpdate.splice(i, 1);
              }
              break;
            }
          }
        }

        // Add new bids
        for (let j = 0; j < data.bids.length; j++) {
          if (
            Number(data.bids[j][1]) !== 0 &&
            !bidsAfterUpdate.map((x) => x[0]).includes(data.bids[j][0])
          ) {
            bidsAfterUpdate.push(data.bids[j]);
            break;
          }
        }
        
        bidsAfterUpdate.sort((x, y) =>
          Number(y[0]) < Number(x[0]) ? -1 : 1
        );

        bidsAfterUpdate = bidsAfterUpdate.slice(-30);
        return bidsAfterUpdate;
      });

      // Similar logic for asks...
    },
    `DEPTH-${market}`
  );

  // Register trade callback...
}, [market, ...dependencies]);

Tab Interface

From apps/web/src/components/Depth.tsx:11-12:
const [activeTab, setActiveTab] = useState("orderbook");
Users can toggle between:
  • Order Book: Live bid/ask ladder
  • Recent Trades: Trade history stream

MarketBar Component

Displays current market information and statistics.

Context Integration

From apps/web/src/components/MarketBar.tsx:6-8:
const { ticker, setTicker, stats, setStats, price } =
  useContext(TradesContext);

Data Fetching

From apps/web/src/components/MarketBar.tsx:10-18:
useEffect(() => {
  getTicker(market).then(setTicker);

  setStats([
    { label: "24h Volume", value: `$${ticker?.volume.toLocaleString()}` },
    { label: "24h High", value: `$${ticker?.high}` },
    { label: "24h Low", value: `$${ticker?.low}` },
  ]);
}, [market, setStats, setTicker, ticker?.high, ticker?.low, ticker?.volume]);

Displayed Information

  • Current price
  • 24h price change percentage
  • 24h trading volume
  • 24h high/low prices
  • Market pair (SOL/USDC)

Component Composition

Components follow React best practices:

Props Pattern

// Market identifier passed to all trading components
interface ComponentProps {
  market: string;
}

Context Consumption

All major components consume TradesContext:
import { useContext } from "react";
import { TradesContext } from "../state/TradesProvider";

const { price, bids, asks, trades } = useContext(TradesContext);

TypeScript Interfaces

From apps/web/src/utils/types.ts:
export interface Trade {
  id: number;
  isBuyerMaker: boolean;
  price: string;
  quantity: string;
  quoteQuantity: string;
  timestamp: number;
}
export interface Depth {
  bids: [string, string][];
  asks: [string, string][];
  lastUpdateId: string;
}
export interface CreateOrder {
  market: string;
  side: string;
  quantity: number;
  price: number;
  userId: string;
}
export interface Ticker {
  firstPrice: string;
  high: string;
  lastPrice: string;
  low: string;
  priceChange: string;
  priceChangePercent: string;
  quoteVolume: string;
  symbol: string;
  trades: string;
  volume: string;
}

Component Lifecycle

Initialization Flow

1

Mount

Component mounts and retrieves context
2

Register Callbacks

Subscribe to WebSocket events via WsManager
3

Fetch Initial Data

Load initial order book and trade history via REST API
4

Receive Updates

Process real-time WebSocket messages
5

Cleanup

Unsubscribe from events on unmount

Cleanup Example

From apps/web/src/components/Depth.tsx:180-192:
return () => {
  WsManager.getInstance().deRegisterCallback("depth", `DEPTH-${market}`);
  WsManager.getInstance().sendMessage({
    method: "UNSUBSCRIBE",
    params: [`depth.${market}`],
  });

  WsManager.getInstance().deRegisterCallback("trade", `TRADE-${market}`);
  WsManager.getInstance().sendMessage({
    method: "UNSUBSCRIBE",
    params: [`trade.${market}`],
  });
};

Next Steps

State Management

Learn how TradesProvider manages global state

WebSocket

Understand real-time data communication

Build docs developers (and LLMs) love