Skip to content

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.

StrategyWhen to useSDK
OracleOffsetYou have an off-book fair price (oracle, internal mid). Quote a fair anchor + per-side delta vectors.OracleOffsetQuotingClient
OrderListYou want explicit order-by-order control (place / cancel by price + size).OrderListQuotingClient
LinearDistributionServer 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:

UnitMeaning
AtomsSmallest token integer. 1 SOL = 10⁹ atoms.
Lotsatoms / atoms_per_contract_lots. The matcher works in lot-space.
Oracle unitsCommon 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.

CounterPurpose
oracle_sequence_numberAnti-replay on OracleFair updates. Bump on every fair-price flush.
order_sequenceAnti-replay on UpdateQuotingParams. Bump on every params/order flush.
client_order_idPer-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:

  1. SpotMakerMicroBook.balance (per-spot, per-micro-book) — base token holdings. Set via UpdateQuotingParams.max_balance. Affects available_to_sell for the base side.
  2. GlobalMarket.balances[maker_id] (singleton) — quote (USDC) holdings. Has its own max_balance. Set via DepositWithdrawQuote.

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.

rust
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.

Apache 2.0