Project: Incognitus — On-Chain Central Limit Order Book (CLOB) Chain: Solana Scope: Frontend client — WASM + WebGPU + React Hackathon: Colosseum Frontier (April 6 – May 11, 2026)
incognitus-frontend-spec.md 27 KB Nomadic° — 8:08 PM lol yeah same. alright I am going to review your PR right now.
Project: Incognitus — On-Chain Central Limit Order Book (CLOB) Chain: Solana Scope: Frontend client — WASM + WebGPU + React Hackathon: Colosseum Frontier (April 6 – May 11, 2026) Status: WIP — instruction accounts and details will be finalized as the on-chain program is built. This spec is the starting point for implementation. Expect iteration.
- What Incognitus Is
- Frontend's Role
- System Architecture
- User Transaction Pipelines
- Instructions
- Accounts the Frontend Reads
- Client Architecture
- UI State
- Data Flow
- Glossary
Incognitus is a fully on-chain Central Limit Order Book (CLOB) built on Solana. It enables spot trading between token pairs.
Two types of participants interact with the market:
- Maker — places a limit order (a resting order at a specific price that sits in the book until matched)
- Taker — places a market order (fills immediately against existing limit orders at the best available price)
Orders are stored on-chain. Matching happens on-chain. Settlement (the actual token transfer) happens on-chain.
The on-chain program never moves by itself. It only responds to transactions. The client is responsible for submitting each step of the pipeline in sequence.
The frontend is responsible for three things:
- Order placement — collect user input, compute required values, build and sign all pipeline transactions upfront, then dispatch them one by one as each prior step confirms
- Display — read on-chain account data and render the order book and balances
- History and candles — sourced from the indexer (not on-chain account reads)
graph LR
User["Trader"] -->|"order input"| Frontend["Frontend Client"]
Frontend -->|"signed transactions"| Solana["Solana Program"]
Solana -->|"account state"| Frontend
Frontend -->|"render order book + balances"| Display["UI"]
Indexer -->|"trade history + candles"| Frontend
How to read this diagram: Each box is a component. Each arrow is data moving between them, labeled with what's being sent. The diagram shows: the trader gives input to the frontend, the frontend sends signed transactions to Solana and reads back account state, and the indexer provides history and candles separately.
graph TB
subgraph BROWSER["Browser"]
subgraph REACT["React / Vite UI"]
OBDisplay["Order Book Ladder"]
OrderForm["Order Form"]
TradeHistory["Trade History"]
Candles["Candles (TradingView)"]
BalanceView["Balances / Account State"]
PipelineStatus["Pipeline Status\n(in-flight tx progress)"]
end
subgraph WASM["WASM Client (Rust → WebAssembly)"]
IB["Instruction Builders\nbuild all pipeline transactions"]
AD["Account Decoders\nparse slab byte buffers"]
PM["Pipeline Dispatcher\ndispatch pre-signed txs on events"]
PC["Pre-computation\nquote = price × quantity"]
VAL["Validation\ndivisibility checks"]
end
subgraph WALLET["Wallet (Phantom / Backpack)"]
SIGN["Signs transactions\n(browser extension)"]
end
subgraph GPU["WebGPU"]
CS["Compute Shaders\n(sort, aggregate)"]
RS["Render Pipeline\n(depth chart, heatmap, ladder)"]
end
REACT -->|"user actions"| WASM
WASM -->|"built transactions"| WALLET
WALLET -->|"signed transactions"| WASM
WASM -->|"decoded state"| REACT
WASM -->|"raw price bytes"| GPU
GPU -->|"sorted + aggregated data"| WASM
GPU -->|"rendered frames → canvas"| REACT
end
subgraph SOLANA["Solana (On-Chain)"]
Program["Solana Program"]
OrderBook["Order Book\n(Slab Accounts)"]
Ledger["Ledger\n(Slab Accounts)"]
Transient["Transient Accounts\n(Queues, Aggregates, Events)"]
end
subgraph INDEXER["Indexer (post-MVP)"]
CandleAPI["Candle History"]
EventStream["Event Stream (WebSocket)"]
end
WASM -->|"RPC: submit txs"| Program
WASM -->|"RPC: getMultipleAccounts"| OrderBook
EventStream -->|"trade history + candles"| WASM
On-chain components the frontend interacts with:
| Component | What It Is | How the Frontend Touches It |
|---|---|---|
| Solana Program | Entry point — validates and routes all instructions | Submit transactions via RPC |
| Order Book | Slab accounts storing all resting limit orders | Read for order book display |
| Ledger | Slab accounts tracking trader identities and order status | Read for user open orders |
| Transient Accounts | Per-pipeline queues, aggregates, and event records | Read and written during the pipeline |
Note on account growth: When a slab account needs to grow in size, the client must include a resize transaction before the instruction that requires it. This is part of pipeline management — when account growth is needed, the client detects it and inserts the resize step automatically.
All transactions in a pipeline are built and signed upfront before any are dispatched. Once signed, they are dispatched one by one — each dispatched only after the previous one confirms.
sequenceDiagram
participant WASM as WASM Client
participant Wallet as Wallet
participant RPC as Solana RPC
WASM->>WASM: Build all pipeline transactions
WASM->>Wallet: Request signatures for all transactions
Wallet-->>WASM: All transactions signed
loop for each transaction in pipeline
WASM->>RPC: Dispatch Tx[N]
RPC-->>WASM: Confirmation event
end
Why pre-build and pre-sign: The wallet signs everything at once. The user approves once. Then the pipeline runs automatically, dispatching each signed transaction as the prior one confirms.
On dispatch failures: If a transaction fails or times out, surface the error to the user. Whether to retry or abort is context-dependent and will be handled as issues arise during implementation.
sequenceDiagram
actor Maker
participant WASM as WASM Client
participant Wallet as Wallet
participant RPC as Solana RPC
participant Program as Solana Program
Maker->>WASM: Place limit order (side, action, price, quantity, quote)
Note over WASM: Compute quote = price × quantity
Note over WASM: Validate divisibility
WASM->>WASM: Build all 5 transactions
WASM->>Wallet: Request signatures
Wallet-->>WASM: All signed
WASM->>RPC: Dispatch PlaceLimitOrder
RPC->>Program: PlaceLimitOrder
Program-->>RPC: Confirmed
RPC-->>WASM: Event
Note over WASM: Dispatch all 3 aggregate txs simultaneously
WASM->>RPC: Dispatch OrderAggregate + PriceAggregate + TrieAggregate (parallel)
RPC->>Program: All 3 aggregate txs
Program-->>RPC: All confirmed
RPC-->>WASM: Event
Note over WASM: Check write-lock before dispatching
WASM->>RPC: Dispatch UpdateOrderBook
RPC->>Program: UpdateOrderBook
Program-->>RPC: Confirmed
RPC-->>WASM: Event
WASM-->>Maker: Limit order is live in the order book
| Step | Instruction(s) | Dispatched | What Happens |
|---|---|---|---|
| 1 | PlaceLimitOrder |
Alone | Order validated and written into limit order queue |
| 2 | OrderAggregate + PriceAggregate + TrieAggregate |
All at once (parallel) | Queue data grouped and prepared — one tx per aggregate type per side |
| 3 | UpdateOrderBook |
Alone (after write-lock check) | Order written into the live order book |
Note:
PlaceMarketOrderandCacheMarketOrderare out of scope for hackathon v1. The taker pipeline starts at matching.
sequenceDiagram
actor Taker
participant WASM as WASM Client
participant Wallet as Wallet
participant RPC as Solana RPC
participant Program as Solana Program
Taker->>WASM: Initiate market order
WASM->>WASM: Build all 3 transactions
WASM->>Wallet: Request signatures
Wallet-->>WASM: All signed
Note over WASM: Check write-lock before dispatching
WASM->>RPC: Dispatch MatchOrders
RPC->>Program: MatchOrders
Program-->>RPC: Confirmed
RPC-->>WASM: Event
WASM->>RPC: Dispatch Settlement
RPC->>Program: Settlement
Program-->>RPC: Confirmed
RPC-->>WASM: Event
Note over WASM: Dispatch cleanup per side (parallel)
WASM->>RPC: Dispatch Cleanup (bid) + Cleanup (ask)
RPC->>Program: Both cleanup txs
Program-->>RPC: Confirmed
RPC-->>WASM: Event
WASM-->>Taker: Order filled and settled
| Step | Instruction(s) | Dispatched | What Happens |
|---|---|---|---|
| 1 | MatchOrders |
Alone (after write-lock check) | Matching engine runs — order fills against the book |
| 2 | Settlement |
Alone | Ledger updated, balances credited and debited |
| 3 | Cleanup (bid + ask) |
Both at once (parallel) | Matched entries removed from the order book |
Before dispatching UpdateOrderBook (maker step 3) and MatchOrders (taker step 1), the client must check the write-lock state on OrderBookConfig.
flowchart LR
A["Ready to dispatch\nwrite-lock instruction"] --> B{"Write-lock\nactive?"}
B -->|"No"| C["Dispatch immediately"]
B -->|"Yes"| D{"More than 2 slots\nelapsed since lock?"}
D -->|"Yes"| C
D -->|"No"| E["Wait then re-check"]
E --> B
Note: Instruction names are working names. Exact names, discriminators, and remaining account details will be finalized with the program. Fields marked TBD will be updated. This spec is a starting point — expect changes as the program is built.
Pipeline: Maker step 1 of 3
Purpose: Validate a new limit order or cancel request and write it into the limit order queue.
Client pre-computation (before building the transaction):
quote = price × quantity
Checks:
quote % price == 0
quote % quantity == 0
If either check fails — reject locally, do not submit.
Inputs:
| Field | Type | Description |
|---|---|---|
side |
u8 |
0 = bid, 1 = ask |
action |
u8 |
0 = open, 1 = cancel |
price |
u64 |
Limit price — only if action = open |
quantity |
u64 |
Base token amount — only if action = open |
quote |
u64 |
Pre-computed: price × quantity — only if action = open |
order_id |
TBD | Order to cancel — only if action = cancel |
Accounts:
| Account | Access | Description |
|---|---|---|
MarketConfig |
Read | Market reference |
AccountStatus |
Read | User balance validation |
LimitOrderQueue |
Write | Destination queue |
| User wallet | Signer | Transaction authority |
On-chain validation:
- If ask + open:
quantity <= AccountStatus.quantity_amount - If bid + open:
quote <= AccountStatus.quote_amount quote % price == 0quote % quantity == 0
Pipeline: Maker step 2 of 3
Purpose: Process the limit order queue data into structured aggregates ready for order book insertion. Three separate aggregate transactions, each covering one type of data. Both sides (bid + ask) run in parallel. All can be dispatched simultaneously — they do not conflict.
| Account | Access | Description |
|---|---|---|
MarketConfig |
Read | Market reference |
LimitOrderQueue |
Read | Source queue (per side) |
OrderAggregate |
Write | Aggregated order entry output |
| User wallet | Signer | — |
| Account | Access | Description |
|---|---|---|
MarketConfig |
Read | Market reference |
LimitOrderQueue |
Read | Source queue (per side) |
PriceAggregate |
Write | Aggregated price level output |
| User wallet | Signer | — |
| Account | Access | Description |
|---|---|---|
MarketConfig |
Read | Market reference |
LimitOrderQueue |
Read | Source queue (per side) |
TrieAggregate |
Write | Aggregated trie path output |
| User wallet | Signer | — |
Pipeline: Maker step 3 of 3
Purpose: Apply aggregated data to the live order book. Insert new TrieNodes, PriceNodes, and OrderBlocks into the slab accounts.
Write-lock: OrderBookConfig carries a write-lock. Before dispatching, check the lock. If locked, wait until released or until more than 2 slots have elapsed.
Inputs: None.
Accounts:
| Account | Access | Description |
|---|---|---|
MarketConfig |
Read | Market reference |
OrderBookConfig |
Write | Write-lock state |
| Bid side | ||
EntryAggregate |
Read | Aggregated order entries for bid |
PriceAggregate |
Read | Aggregated price levels for bid |
TrieAggregate |
Read | Aggregated trie paths for bid |
SlabTrieNode (bid) |
Write | Trie node slab |
SlabTrieLinkedNode (bid) |
Write | Trie link node slab |
SlabPriceNode (bid) |
Write | Price node slab |
SlabOrderBlockNode (bid) |
Write | Order block slab |
SlabOrderHeader (bid) |
Write | Order header slab |
| Ask side | ||
OrderAggregate |
Read | Aggregated order entries for ask |
PriceAggregate |
Read | Aggregated price levels for ask |
TrieAggregate |
Read | Aggregated trie paths for ask |
SlabTrieNode (ask) |
Write | Trie node slab |
SlabTrieLinkedNode (ask) |
Write | Trie link node slab |
SlabPriceNode (ask) |
Write | Price node slab |
SlabOrderBlockNode (ask) |
Write | Order block slab |
SlabOrderHeader (ask) |
Write | Order header slab |
| User wallet | Signer | — |
Out of scope for hackathon v1. Skip.
Out of scope for hackathon v1. Skip.
Pipeline: Taker step 1 of 3
Purpose: The matching engine runs on-chain. The market order fills against resting limit orders in price-time priority. Fill records are written to the event queue.
Write-lock: Same check as UpdateOrderBook — check OrderBookConfig lock before dispatching.
Inputs: None.
Accounts:
| Account | Access | Description |
|---|---|---|
MarketConfig |
Read | Market reference |
OrderBookConfig |
Write | Write-lock state |
EventQueue |
Write | Fill records output |
| Bid side | ||
SlabTrieNode (bid) |
Write | Trie traversal and delta updates |
SlabTrieLinkedNode (bid) |
Write | Child pointer chains |
SlabPriceNode (bid) |
Write | Price level cursor updates |
SlabOrderBlockNode (bid) |
Write | Entry consumption |
SlabOrderHeader (bid) |
Write | — |
| Ask side | ||
SlabTrieNode (ask) |
Write | Trie traversal and delta updates |
SlabTrieLinkedNode (ask) |
Write | Child pointer chains |
SlabPriceNode (ask) |
Write | Price level cursor updates |
SlabOrderBlockNode (ask) |
Write | Entry consumption |
SlabOrderHeader (ask) |
Write | — |
| User wallet | Signer | — |
Pipeline: Taker step 2 of 3
Purpose: Apply fill results to the ledger. Credit and debit trader balances.
Write-lock: LedgerConfig carries a write-lock. Check before dispatching — same 2-slot rule applies. SlabOrderBlockNode is read-only here so it does not conflict with other writes.
Accounts:
| Account | Access | Description |
|---|---|---|
MarketConfig |
Read | Market reference |
LedgerConfig |
Write | Write-lock state |
EventQueue |
Read | Fill records from MatchOrders |
SlabOrderBlockNode |
Read | Verify fill data |
SlabAccountLedger |
Write | Trader identity records |
SlabOrderStatus |
Write | Order status updates |
SlabOrderEntry |
Write | Order entry records |
SlabOrderStatusLinkedNode |
Write | Order status link nodes |
SlabOrderEntryLinkedNode |
Write | Order entry link nodes |
| User wallet | Signer | — |
Pipeline: Taker step 3 of 3
Purpose: Remove matched entries from the order book. Free slab memory.
Write-lock: Lock still applies — same check as UpdateOrderBook before dispatching.
Note: One transaction per side (bid + ask). Both can be dispatched at the same time.
Accounts (per side):
| Account | Access | Description |
|---|---|---|
MarketConfig |
Read | Market reference |
EventQueue |
Read | Fill records to process |
OrderBookConfig |
Write | Write-lock |
SlabTrieNode |
Write | Remove empty trie nodes |
SlabTrieLinkedNode |
Write | Remove empty link nodes |
SlabPriceNode |
Write | Remove empty price levels |
SlabOrderBlockNode |
Write | Free matched entries |
SlabOrderHeader |
Write | — |
| User wallet | Signer | — |
The client reads PriceNode slab accounts to build the order book display. Each account contains a sequence of PriceNode structs. The client reads through each node in the account, extracts the price and quantity, and builds the ladder.
Per PriceNode, extract:
| Field | Type | Description |
|---|---|---|
price |
u64 |
The price level |
delta_quantity_total |
u64 |
Total resting quantity at this price |
side |
derived | Which slab you're reading — bid slab = bid, ask slab = ask |
Account naming pattern:
SlabPriceNode [bid] [market] [0] ← bid side
SlabPriceNode [ask] [market] [0] ← ask side
PDA seeds TBD — finalized with program.
Display output: Sort by price, group into bid and ask ladder, compute cumulative depth for depth chart.
Status: WIP — structure finalized with program.
| Account | Contents | Use |
|---|---|---|
AccountStatus |
User token balances | Show available balance, validate order inputs |
SlabOrderStatus |
Open orders and fill history | Show active orders |
| Account | Contents | Use |
|---|---|---|
MarketConfig |
Market parameters, token pair | Validate orders reference correct market |
OrderBookConfig |
Write-lock state | Check before dispatching write-lock instructions |
graph TB
subgraph REACT["React Layer — UI"]
R1["Order Form"]
R2["Order Book Ladder"]
R3["Trade History"]
R4["Candles — TradingView widget"]
R5["Balances + Open Orders"]
R6["Pipeline Status — tx progress"]
end
subgraph WASM["WASM Layer — Rust → WebAssembly"]
W1["Instruction Builders — build all pipeline txs"]
W2["Account Decoders — parse slab byte buffers"]
W3["Pipeline Dispatcher — dispatch pre-signed txs on events"]
W4["Pre-computation — quote = price × quantity"]
W5["Validation — divisibility checks"]
W6["Write-lock checker — slot-aware dispatch logic"]
end
subgraph WALLET["Wallet — Phantom / Backpack"]
WS["Signs transactions\nUser approves once per pipeline"]
end
subgraph GPU["WebGPU Layer"]
G1["Radix Sort — sort price levels"]
G2["Aggregation — parallel reduction by price"]
G3["Depth Chart Renderer"]
G4["Heatmap Renderer"]
G5["Order Book Ladder Renderer"]
end
REACT -->|"user input"| WASM
WASM -->|"decoded state"| REACT
WASM -->|"built transactions"| WALLET
WALLET -->|"signed transactions"| WASM
WASM -->|"raw price bytes"| GPU
GPU -->|"rendered frames → canvas"| REACT
WASM (Rust compiled to WebAssembly):
- Build and serialize transactions
- Decode on-chain account data (slab byte buffers)
- Pre-compute
quote = price × quantity - Run divisibility validation before submitting
- Dispatch pre-signed transactions and listen for events
- Write-lock check and slot-elapsed logic
Wallet (browser extension — Phantom / Backpack):
- Sign transactions — this cannot happen in WASM in the browser, the wallet extension handles it
- The WASM layer passes built transactions to the wallet adapter, the wallet signs them and returns them
WebGPU:
- Sort price levels (radix sort — 10–30× faster than WASM for large sets)
- Aggregate quantities by price (parallel reduction)
- Render depth chart, heatmap, and order book ladder
- For hackathon v1: start without WebGPU, add rendering and sort once core pipeline works
React:
- All UI rendering and user interaction
- Receives decoded state from WASM and rendered frames from WebGPU
- Shows pipeline status (which tx in flight, confirmations)
| Workload | Layer | Reason |
|---|---|---|
| Build transactions | WASM | Sequential serialization |
| Sign transactions | Wallet | Browser extension only |
| Decode account data | WASM | Sequential struct parsing |
| Single order validation | WASM | Trivial, no GPU overhead worth it |
| Pipeline dispatch logic | WASM | State machine, event-driven |
| Write-lock check | WASM | Slot-aware branching logic |
| Sort price levels | WebGPU | GPU radix sort is 10–30× faster |
| Aggregate quantities by price | WebGPU | Parallel reduction |
| Render depth chart | WebGPU | Uniform pixel ops — stays on GPU |
| Render heatmap | WebGPU | Per-cell color mapping, ideal GPU work |
| Render order book ladder | WebGPU | Per-frame updates at 60fps |
| State | Source | Updated When |
|---|---|---|
| Order book bid levels | SlabPriceNode (bid) |
Account change / per slot |
| Order book ask levels | SlabPriceNode (ask) |
Account change / per slot |
| Trade history | Indexer event stream | Match event received |
| Candle data (OHLCV) | Indexer on load, then live trade events | Trade event → client updates current candle |
| Best bid / best ask | Derived from order book | Order book update |
| State | Source | Updated When |
|---|---|---|
| Available balance | AccountStatus |
After Settlement confirms |
| Active / open orders | SlabOrderStatus |
After UpdateOrderBook confirms |
| Order history | Indexer event stream | Match event received |
Track each in-flight pipeline so the UI can show progress.
stateDiagram-v2
direction LR
[*] --> Idle
Idle --> BuildSign : user submits limit order
BuildSign --> Dispatching1 : wallet signs all txs
Dispatching1 --> Dispatching2 : PlaceLimitOrder confirmed
Dispatching2 --> Dispatching3 : all Aggregate txs confirmed
Dispatching3 --> LiveInBook : UpdateOrderBook confirmed
LiveInBook --> [*]
Idle --> BuildSign2 : taker initiates match
BuildSign2 --> TakerDispatching1 : wallet signs all txs
TakerDispatching1 --> TakerDispatching2 : MatchOrders confirmed
TakerDispatching2 --> TakerDispatching3 : Settlement confirmed
TakerDispatching3 --> Filled : Cleanup confirmed
Filled --> [*]
flowchart TD
Input["User Input\nside, action, price, quantity"] --> Compute["WASM: Compute\nquote = price × quantity"]
Compute --> Check{"Divisibility\nvalid?"}
Check -->|"No"| Reject["Show error\ndo not proceed"]
Check -->|"Yes"| Build["WASM: Build all pipeline transactions"]
Build --> Sign["Wallet: Sign all transactions"]
Sign --> Dispatch["WASM: Dispatch Tx[0]"]
Dispatch --> Listen["Listen for confirmation event"]
Listen --> Next["Dispatch Tx[N+1]\n(after write-lock check if applicable)"]
Next --> Listen
flowchart TD
Trigger["Account change event\n(per slot)"] --> Fetch["RPC: getMultipleAccounts\nSlabPriceNode — bid + ask"]
Fetch --> Decode["WASM: iterate each node\nextract price + quantity"]
Decode --> Upload["Upload to GPU storage buffer"]
Upload --> Compute["WebGPU Compute\n1. Radix sort by price\n2. Prefix sum for depth\n3. Color mapping"]
Compute --> Render["WebGPU Render\nLadder + depth chart + heatmap"]
Render --> Canvas["Canvas in React UI"]
flowchart TD
Load["Page load"] --> Fetch["Indexer: fetch OHLCV history"]
Fetch --> Feed["Feed into TradingView widget"]
Stream["Live trade events\n(Indexer WebSocket)"] --> Update["WASM: update current candle\nopen, high, low, close, volume"]
Update --> Feed
| Term | Definition |
|---|---|
| Maker | Trader who places a limit order — adds liquidity |
| Taker | Trader who places a market order — removes liquidity |
| CLOB | Central Limit Order Book — matches orders by price-time priority |
| Slab Allocator | On-chain memory manager — fixed-size byte buffer managing node storage |
| TrieNode | Node in the price trie — organizes limit orders by price digit |
| PriceNode | Record for one specific price level — links to all order entries at that price |
| OrderBlock | Batch of up to 16 order entries at a price level |
| OrderEntry | Single resting limit order — the atomic unit |
| Pipeline | The sequence of transactions required to complete one order lifecycle |
| Settlement | On-chain step that credits and debits trader balances after a match |
| Write-lock | Flag on OrderBookConfig or LedgerConfig signaling the resource is being written to |
| Event Queue | On-chain account holding fill records produced by MatchOrders, consumed by Settlement and Cleanup |
| PDA | Program Derived Address — deterministic on-chain account address |
| WASM | WebAssembly — Rust compiled to run in the browser |
| WebGPU | Browser GPU API — parallel computation and rendering |
| Slot | ~400ms unit of time on Solana — one block |
| incognitus-frontend-spec.md | |
| 27 KB |