How Sweetspot works
You only need to read this if you're quoting. Read-only integrators can skip to Market data.
The bits below are what the on-chain program forces on every quoting client — sequence numbers, the unit system, the two-balance trap, the cross-spread gotcha. Get them wrong and your orders won't match.
Markets
A market is identified by its pair name — "SOL/USDC". Each pair is two on-chain SpotMarket accounts (one per token) joined virtually at match time. Two swap modes exist:
- Global swap — base token vs. the quote currency (e.g. SOL ↔ USDC). The matcher loads one spot account; quote balances live in the singleton
GlobalMarket. - Cross swap — base vs. base (e.g. SOL ↔ ETH). The matcher loads both spot accounts and bridges them through each maker's per-spot micro-book.
The SDK's ListPairs RPC returns the catalog of discovered pairs along with their per-token metadata (decimals, atoms-per-lot, mints).
Quoting strategies
Pick one per market. Three are supported on-chain; the SDK exposes each as a mode-exclusive quoting client.
| Strategy | When to use | SDK |
|---|---|---|
| OracleOffset | You have an off-book fair price (oracle, internal mid). Quote a fair anchor + per-side delta vectors. | OracleOffsetQuotingClient |
| OrderList | You want explicit order-by-order control (place / cancel by price + size). | OrderListQuotingClient |
| LinearDistribution | Server interpolates linearly between buy/sell price ranges. Cheapest message size. | LinearDistributionQuotingClient |
Strategies are documented under Quoting.
Unit system
You as a quoter work in human units (price 155.14, size 10.0 SOL). The SDK converts to the on-chain integer forms at the boundary. You never write atoms or lots in normal flow.
The three on-chain units exist:
| Unit | Meaning |
|---|---|
| Atoms | Smallest token integer. 1 SOL = 10⁹ atoms. |
| Lots | atoms / atoms_per_contract_lots. The matcher works in lot-space. |
| Oracle units | Common reference unit for price comparisons across markets. |
The SDK's pricing helpers (human_to_oracle, human_size_to_lots, oracle_to_human) do every conversion. See Prices, sizes, units.
Sequence numbers
Two monotonic counters per spot market are checked by the matcher. Both must strictly increase across all transactions you ever submit, including across process restarts.
| Counter | Purpose |
|---|---|
oracle_sequence_number | Anti-replay on OracleFair updates. Bump on every fair-price flush. |
order_sequence | Anti-replay on UpdateQuotingParams. Bump on every params/order flush. |
client_order_id | Per-maker monotonic id for OrderList placements. |
The SDK seeds all three counters from SystemTime::now().as_micros() on startup, so a process restart never collides with prior on-chain state. Don't override the seed unless you know what you're doing.
The two-balance trap
A maker has two separate on-chain balance entries:
SpotMakerMicroBook.balance(per-spot, per-micro-book) — base token holdings. Set viaUpdateQuotingParams.max_balance. Affectsavailable_to_sellfor the base side.GlobalMarket.balances[maker_id](singleton) — quote (USDC) holdings. Has its ownmax_balance. Set viaDepositWithdrawQuote.
The matcher gates SELL fills on the global quote-side available_to_buy(). With max_balance=0 on the global quote side, sells silently never match — the order shows in the book but the matcher drops it. Set max_balance > 0 on both before quoting.
Cross-market spread
For any cross-swap market (e.g. SOL ↔ ETH), you must explicitly allow crossing into the counterparty market's spot id by including a cross_spread entry. The matcher looks up book.get_cross_params(SpotId::OF_COUNTERPARTY) and silently filters your maker if absent.
For a global swap (e.g. SOL ↔ USDC), the counterparty is spot_id=0 (the global quote). You still need a cross_spread entry for 0 even though you don't care about hedging. Some(0) is enough to pass the gate without widening.
oc.queue_params_update(
"SOL/USDC",
ParamsUpdate {
enable: true,
max_balance: 100_000_000_000,
cross_spread: vec![(0, Some(0))], // required for SOL/USDC
},
).await?;OracleFair staleness
Each oracle-offset order carries a max_slot_staleness byte. The matcher computes slot_delay = current_slot - last_oracle_slot and rejects any order where slot_delay > max_slot_staleness.
The SDK stamps last_oracle_slot with the freshest slot it's seen (via ServerState.current_slot). For 1 Hz flush cadences, staleness = 30 is a reasonable default. Lower than that and orders silently expire between flushes.
L3 doesn't show oracle-offset orders
L3 streams (MarketDataService.Subscribe with level: L3) show OrderList entries only — oracle-offset orders are virtual, reconstructed at match time from OracleFair + offsets. If you oracle-offset quote and your L3 stream shows zero orders for your maker_id, that's normal. Use BalanceService + your own commit log to verify what you submitted.