Building a real-time cross-chain orderbook with Switchboard
A practical walkthrough of a perp-style orderbook that quotes on three chains and settles atomically across all of them, using Switchboard for state coordination.
This post walks through a concrete, simplified design for a perp-style orderbook that quotes liquidity on three chains — Arbitrum, Base, and Solana — and settles matches across all three with consistent state. The full source is up on GitHub under switchboard-examples/multichain-orderbook. We will hit the interesting parts here.
The product premise: liquidity providers post quotes on whichever chain they like best, takers route through the chain with the cheapest gas at quote time, and settlement updates the maker’s collateral on their home chain. All of it has to happen inside a price-discovery window measured in low hundreds of milliseconds, or the maker will refuse to quote.
The high-level shape
Three components:
- A matching engine (off-chain, stateless except for an in-memory book). Runs as a service. Speaks to Switchboard.
- Per-chain settlement contracts (Solidity on Arbitrum and Base, Anchor on Solana). Hold maker collateral, accept signed settlement intents from the matcher.
- Switchboard routes between the matcher and each chain, with a fail-closed finality policy.
┌─────────────────┐
│ Matcher engine │
│ (in-memory) │
└────────┬────────┘
│ Switchboard SDK
▼
┌────────────────────┐
│ Solana coordinator│
└────────┬───────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
Arbitrum Base Solana
settlement settlement settlement
Posting a quote
A maker posts a quote on their home chain by calling postQuote(token, side, qty, price, expiry) on the local settlement contract. The contract:
- locks the collateral
- emits a
QuoteOpenedevent with aquoteIdderived from(maker, nonce)
The matcher subscribes to QuoteOpened on all three chains via a chainstream adapter and adds the quote to its in-memory book. So far, nothing crosses chains.
import { Switchboard, ChainStream } from '@switchboard/sdk';
const stream = new ChainStream({
chains: ['arbitrum', 'base', 'solana'],
events: ['QuoteOpened', 'QuoteCancelled'],
});
stream.on('event', (e) => {
if (e.name === 'QuoteOpened') book.add(decodeQuote(e.data));
if (e.name === 'QuoteCancelled') book.remove(e.data.quoteId);
});
Matching a taker
A taker calls submitOrder(token, side, qty, maxPrice) on whichever chain they happen to be on. The settlement contract reserves taker collateral and emits an OrderSubmitted event. The matcher picks it up via the same stream, runs price-time priority across the book, and produces a match decision: this taker fills against these N maker quotes for these notional amounts.
The decision is signed by the matcher’s key and dispatched through Switchboard as a single coordinated route:
const sb = new Switchboard({ apiKey: process.env.SWITCHBOARD_KEY });
await sb.route({
routeId: `match:${orderId}`,
from: 'matcher', // off-chain origin
to: [
{ chain: 'arbitrum', payload: settleArbitrumPayload },
{ chain: 'base', payload: settleBasePayload },
{ chain: 'solana', payload: settleSolanaPayload },
],
atomicity: 'all-or-none',
finalityPolicy: 'fail-closed',
timeout: '1500ms',
});
This is the part that does not work without a coordinator. Without Switchboard, the matcher would have to either:
- send three independent transactions and accept that they land at different times (bad — the book is now inconsistent)
- or send them sequentially and accept the latency stacking up (worse — the price moved)
With Switchboard, the coordinator program records a single match intent. The destination chains all verify the same coordinator slot. Either all three settle within the timeout, or all three roll back to their pre-match state. The matcher knows within 400ms which it is.
How “all-or-none” actually works
This is the part of Switchboard people ask about most. The naive implementation of cross-chain atomic settlement is the famous two-phase commit, which involves a “prepare” phase and a “commit” phase and which works exactly like you would expect it to work in a database — except now each phase is a separate cross-chain message and your latency budget is gone.
Our trick: the coordinator program records the match as a conditional commit. The destination settlement contracts are written to look up the coordinator state, see that the match is recorded, and apply the settlement only if the coordinator state is still consistent at the time of execution. If any chain’s settlement fails to land within the timeout, the matcher emits a void instruction to the coordinator. Destinations that already applied the settlement then read the void and roll back; destinations that have not yet applied it see the void and skip the settlement entirely.
In effect, the coordinator slot is the commit point. The destination chains are deterministic functions of the coordinator state. We get atomicity by making the coordinator the only authoritative source of “did this match happen.”
There is a small honesty asterisk: rollbacks are not free. If you regularly hit your timeout, you are paying gas to apply and unapply settlements. In our benchmarks the rollback rate is well below 0.1% under healthy mainnet conditions. We expose rolled_back_settlements_total as a Prometheus counter so you can alert on it.
Latency budget breakdown
For the orderbook to feel real-time to the taker, the wall-clock budget looks like:
- Taker tx inclusion on home chain: ~250ms
- Matcher decision: ~10ms (in-memory book)
- Switchboard coordinator commit: ~400ms
- Destination settlements in parallel: ~350ms p99
- Total: ~1.0s p99
That is fast enough for a perp DEX. It is not fast enough for HFT — there is no story for that yet — but it is faster than every existing cross-chain orderbook we are aware of.
What we deliberately do not handle
A few things the example skips:
- Maker griefing. A maker who cancels just before a match lands gets nothing but a slap on the wrist. A production design would charge a small cancellation fee that scales with how close the cancel was to a match.
- Latency-aware routing. The matcher right now picks the chain with the cheapest gas. A smarter version would also factor in the historical Switchboard p99 for each destination corridor.
- Privacy. The matcher sees all quotes in cleartext. A private orderbook is its own design problem; Switchboard does not solve it for you, though it does not get in the way either.
Why this could not exist three years ago
Two reasons. First, the latency math: at 3-second cross-chain bridges, the maker is unwilling to quote because the price will have moved. Second, the operational complexity: running a matcher on top of three independent bridge integrations was its own staffing problem. Switchboard collapses both — the latency by being fast, the ops by being one integration.
If you want to fork the example and ship something, the code is on GitHub and the free tier covers more than enough ops to run a testnet version. We are very interested in feedback from teams who have tried to build this before and gave up. That is the audience we built Switchboard for.