CarbiumxMM

Integrate

no SDK1 live poolRPC-only

Wire xMM into any route: the live Pulse pool fills at its posted price (disc 18) inside splits and multi-hops; Apex when it has a pool. All ix bodies are little-endian; only PDA index seeds are big-endian. Decode pool, quote, build ix.

skill.md for AI agentscopy + paste

One copyable, self-contained spec. Paste it into Claude Code, Cursor, or any coding agent to wire xMM with no SDK. Sourced 1:1 from the live TS client + IDL.

IDL JSONprogram xmm9Lk..in1Z1 live pool1-byte disc
# xMM integration skill (paste into any AI coding agent)

A self-contained spec to integrate xMM, a Solana AMM with one live pool today, Pulse
SOL/USDC (oracle-anchored maker); the program also runs Apex (CLMM), with no live pool yet. No SDK needed: decode the pool, quote, build the
1-byte-discriminator swap instruction, sign, send. Sourced from the live TS client and IDL.

Conventions, fixed:
- Each xMM account carries a 1-BYTE discriminator at byte 0 (not Anchor's 8-byte hash).
- Instruction data: byte 0 is the 1-byte ix discriminator; all body scalars LITTLE-endian.
- PDA seeds: every index-bearing seed is BIG-endian (i32/u16 to_be_bytes); the pool and
  position-asset nonce tails are u32 LITTLE-endian. Account-DATA scalars are LITTLE-endian.
- u128 in account data = two LE u64 halves, low 8 bytes then high 8 bytes.

## Address book (mainnet)
program     xmm9LkUX7yWLoMNEpeGwxtVF8BZjJsNUih5bqx1in1Z
amm_config  CCbTujD6T5t7h1TWLbHQiaw7BUxQBc6DsGJfbPzCoix4
Pulse pool  xmmCRmkNHKJBrB23sWAE7di528hFEZsiY8QdTFnwu4o   (anchored, SOL/USDC, account disc 7)
mpl-core    CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d   (position NFT)
SPL Token   TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
Token-2022  TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
IDL (Anchor-shaped, 1-byte discriminators): https://xmm.carbium.sh/xmm.json

Mints: SOL So11111111111111111111111111111111111111112 (9), USDC
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v (6).

## Apex (CLMM) swap, ix disc 6
data (34 bytes):
[0]      u8    disc = 6
[1..9]   u64   amount                 (LE)
[9..17]  u64   other_amount_threshold (LE)   // exact-in -> min out
[17..33] u128  sqrt_price_limit_x64   (LE)    // 0 = no limit
[33]     u8    is_base_input          (1/0)

accounts (10 head, then ordered tail):
0   payer                 signer, writable
1   amm_config            readonly
2   pool                  writable
3   user_in  (ATA)        writable
4   user_out (ATA)        writable
5   input_vault           writable
6   output_vault          writable
7   observation           writable
8   input_token_program   readonly   // must own the INPUT vault (SPL vs Token-2022)
9   output_token_program  readonly   // must own the OUTPUT vault
-- tail --
10  bitmap_ext            writable    // FIRST in tail
11+ tick_array[]          writable    // direction-ordered window

Direction is inferred on-chain from the input vault. zero_for_one = input is mint_0
(price falls); one_for_zero = input is mint_1 (price rises). Vaults: zero_for_one ->
input=vault0, output=vault1; else swapped. Tick-array window: start at the array holding
tick_current, walk the swap direction (DOWN for zero_for_one, UP otherwise), step =
tick_spacing * 60; include only on-chain-initialized neighbors (account disc 3), pad to
>=2 with the current array (an uninitialized neighbor reverts 6002). Cap 12 arrays.

## Pulse (anchored) swap, ix disc 35 (swap_anchor_signed)
data (41 bytes):
[0]       u8    disc = 35
[1..9]    u64   amount                 (LE)
[9..17]   u64   other_amount_threshold (LE)   // min out
[17..33]  u128  price_x64              (LE)   // the fill price, spread included
[33..41]  u64   expiry_slot            (LE)

accounts (11):
0  payer                 signer, writable
1  pool                  writable
2  user_in  (ATA)        writable
3  user_out (ATA)        writable
4  input_vault           writable
5  output_vault          writable
6  input_token_program   readonly
7  output_token_program  readonly
8  quote_key             signer, readonly   // == pool.quote_key @624; signs last
9  ref_base              readonly           // == pool.ref_base @512
10 ref_quote             readonly           // == pool.ref_quote @544
No tick arrays, no bitmap, no observation: the price comes signed inside the trade, and the chain
holds it to the pool's two feeds.

## Pulse (anchored) swap, ix disc 18 (swap_anchor), at the posted price
data (17 bytes):
[0]       u8    disc = 18
[1..9]    u64   amount                 (LE)
[9..17]   u64   other_amount_threshold (LE)   // min out

accounts (8): 0 to 7 as in disc 35, the payer the only signer; no quote_key, no feeds.
No service call: it fills at the price the pool posts, so a program can call it (CPI) with the amount
it holds at run time, inside splits and multi-hops. It reverts 6037 once the post is more than
max_slot_lag @360 slots or max_age_ms @368 old, or its confidence @272 passes a set conf_max @384.

## PDA seeds (program id xmm9LkUX7yWLoMNEpeGwxtVF8BZjJsNUih5bqx1in1Z)
amm_config   "amm_config", index                                  index = u16 BE
pool (Apex)  "pool", amm_config, mint0, mint1, nonce              nonce = u32 LE
pool (Pulse) "pool_anchor", amm_config, mint0, mint1, nonce       nonce = u32 LE
vault        "pool_vault", pool, mint                             none
observation  "observation", pool                                  none
bitmap_ext   "pool_tick_array_bitmap_extension", pool             none
tick_array   "tick_array", pool, start_tick_index                 start = i32 BE
position     "position", core_asset                               none (NFT mint)
mint_0 addr < mint_1 addr. tick_array start = floor-toward-negative-infinity of
tick_current over (tick_spacing * 60) ticks/array. ATA = [owner, tokenProgram, mint].

## Pool decode offsets
PoolClmm (disc 2, 1545 B):
2 amm_config pubkey | 66 token_mint0 | 98 token_mint1 | 130 token_vault0 |
162 token_vault1 | 194 observation | 226 mint_decimals0 u8 | 227 mint_decimals1 u8 |
228 tick_spacing u16 | 230 liquidity u128 | 246 sqrt_price_x64 u128 | 262 tick_current i32 |
382 status u8 (bit4=swaps off, bit0=no new liquidity) | 1089 fee_rate u32 (bp = fee_rate/100000; 10000 -> 0.1 bp) |
1093 pool_nonce u32 | 1137 dynamic_fee [34] (the automatic fee; control u32 @1143, 0 = flat)
PoolAnchor (disc 7):
72 token_mint0 | 104 token_mint1 | 136 token_vault0 | 168 token_vault1 |
200 pricer_hot_key | 232 mint_decimals0 u8 | 233 mint_decimals1 u8 | 234 status u8 |
235 haircut_bps_p1 u8 | 236 round_mode u8 | 240 price_x64 u128 | 256 bid_factor u64 |
264 ask_factor u64 | 272 confidence u64 | 280 price_updated_ms u64 | 288 data_slot u64 |
304 capacity_cap u64 | 360 max_slot_lag u64 | 368 max_age_ms u64 | 376 cap_bps u64 | 384 conf_max u64 |
456 slot_budget_slot u64 | 464 slot_quote_filled u128 |
512 ref_base | 544 ref_quote | 576 ref_owner | 608 ref_tol_bps u64 | 616 ref_max_age_s u64 |
624 quote_key | 656 signed_max_slots u64 | 664 size_band u64 | 792 lean_strength u64 | 800 size_free u64

## Quote
Apex (CLMM) spot:
  price     = (sqrt_price_x64 / 2^64)^2 * 10^(dec0 - dec1)   // quote per base, human
  feeFactor = 1 - fee_rate / 1e9   // flat; with the automatic fee each tick group pays fee_rate + ceil(control*(va*tick_spacing)^2/1e10), at most 1e8
  zeroForOne = inputMint == token_mint0
  out       = zeroForOne ? amountIn * price * feeFactor : (amountIn / price) * feeFactor
  minOut    = floor(out * (1 - slippage))
  // constant-price at current sqrt_price; walk tick arrays for size. tick->price = 1.0001^tick * 10^(dec0-dec1)
Pulse (anchored), posted (disc 18), from the pool account (and both vaults' amounts, u64 at byte 64, when lean_strength is above 0):
  f   = base in ? bid_factor@256 : ask_factor@264     // 1e12 = 1x; 0 reads as 1e12
  F   = slot_budget_slot@456 == this slot ? slot_quote_filled@464 : 0
  q   = the fill's quote leg (quote in: amountIn; base in: amountIn at the mid), at most capacity_cap - F (capacity_cap 0: no slot cap, allowed only with size_band above 0)
  w   = ceil(size_band@664 * (2F + q) / 2e6)         // 0 when size_band is 0
  f'  = base in ? max(f - w, 1) : f + w
  L   = lean_strength@792 * (G - T + Q) / (G + T)  // 0 when lean_strength is 0 or both vaults are empty; G, T = the
        // vault the trader pays into and the one it takes from, in raw quote at the mid; Q = q before any slot cap;
        // at most lean_strength either way, a pay (L above 0) rounded up, a premium down
  f'' = base in ? max(f' - L, 1) : max(f' + L, 1)  // a pay lowers the bid and lifts the ask, a premium the reverse
  eff = price_x64@240 * f'' / 1e12
  out = amountIn*eff*10^(d1-d0) / 2^64     // base in
  out = amountIn*2^64*10^(d0-d1) / eff     // quote in; one floor, a negative power divides
  out = out * (10000 - haircut) / 10000     // haircut from @235: 1 = 0 bp, as on the live pool
  // reverts: swaps off or halted (6038); a stale post or a confidence past conf_max (6037); the slot past
  // capacity_cap, a base-in fill whose size price reaches the bid (size_band * (F + q) >= f * 1e6), or out
  // past cap_bps of its vault (6040);
  // out under min out (6004)
Pulse (anchored), co-signed (disc 35):  // a price signed inside the trade
  POST /api/swap {"op":"quote","side":"base_in"|"quote_in","amount":"<raw>","taker":"<wallet>"}
  -> {"ok":true,"price_x64","out","min_out","expiry_slot","slot","mid","quote_key"}
  out     = the posted formula above at price_x64 with f = 1e12 and F = our fills still in flight, its w past
            size_free@800 only: w = ceil(size_band * ((F+q-size_free)+^2 - (F-size_free)+^2) / (2e6 * q)), 0 while
            F + q stays at or under it, and the depth test on size_band * (F + q - size_free)+; every other door pays from 0
  min_out = the same with F + 10 USDC: send it as other_amount_threshold, never out (6004 if a fill lands first)
  // no quote if: status & 0b1001 (swaps off or guardian halt); no fresh six-exchange price; our
  // price is off the Pyth guardrail; or the quote leg passes capacity_cap (0: no limit) or the
  // room our fills still in flight leave in it. The chain also refuses out > dst_vault * cap_bps /
  // 10000 or a base-in fill whose size price reaches the bid (6040), a price past expiry_slot, further than ref_tol_bps from the Pyth feeds, or with a
  // feed stale or unsure.

## Enumerate / stream
memcmp the 1-byte disc at offset 0. Single-byte disc base58 = one char: disc 2 -> "3",
disc 7 -> "8", position disc 4 -> "5".
  getProgramAccounts(programId, { filters: [{ memcmp: { offset: 0, bytes: "3" } }] })  // every Apex pool
  getAccountInfo(pool, "base64"); require owner == programId && data[0] == 2 (or 7)
Yellowstone gRPC: subscribe accounts owned by the program; add { memcmp: { offset: "0",
base58: "3" } } to scope one engine. Decode bytes the same way as getAccountInfo.

## Build + send
1. getAccountInfo(pool); require owner == program and data[0] == 2 (Apex) or 7 (Pulse).
2. Decode at the offsets above; quote with the engine formula.
3. Resolve user_in/user_out ATAs; resolve each token program from the mint owner (both
   legs SPL for the live pools). SOL leg: wrap via System transfer + SPL sync_native
   (byte 17); unwrap after via SPL close_account (byte 9).
4. Build swap ix: data = [disc] + LE body; accounts in the order above. Apex tail =
   bitmap_ext FIRST, then the direction-ordered tick-array window.
5. Apex, and Pulse at the posted price (disc 18): sign + sendTransaction, the trader the only signer.
   Pulse co-signed (disc 35): sign with the taker's key only, as a v0 or v1 message whose fill is
   one top-level disc-35 instruction on 11 static keys (v0 lookup tables are fine for anything else), then
   POST /api/swap {"op":"cosign","tx":"<base64>"} -> {"ok":true,"signature"} or
   {"ok":false,"why":"expired|moved|size|no_quote|rate|seen|shape|unknown"}. The service signs last and
   sends: watch the signature. A co-signed Pulse fill uses about 5,850 CU in the program; the trader
   pays two signatures (10,000 lamports) plus priority, where a posted fill pays one (5,000) plus priority.

Address book (mainnet)

Pinocchio program, 1-byte discriminator dispatch. The price is our own median of six exchanges, no paid feed; Pyth's free SOL and USDC accounts are a guardrail on chain, never the price.

WhatAddressNote
programxmm9Lk..in1Zowner of all xMM accounts
amm_configCCbTuj..oix4fee tier, shared by both engines
Pulse SOL/USDCxmmCRm..wu4oanchored, disc 7
mpl-coreCoREEN..hX7dposition NFT (range + fees)
fee 0.01 bpspermissionlessStrata bins = ROADMAP

Engine matrix

One live pool (Pulse); the program also runs Apex, with no live pool yet. Pick by the pool account discriminator at byte 0.

FieldApex (CLMM)Pulse (anchored)
modelconcentrated liquidityoracle-anchored maker, no curve
pool disc27
swap ix disc6 · swap18 · swap_anchor
35 · swap_anchor_signed
ix bodyu64 amount, u64 thr, u128 sqrtLimit, u8 baseIn (34B)18: u64 amount, u64 thr (17B)
35: u64 amount, u64 thr, u128 price, u64 expiry (41B)
accounts10 head + bitmap + tick arrays18: 8 · 35: 11
quote(sqrtP/2^64)^2 * 10^(d0-d1)18: the posted priceX64/2^64, banded, wider with size
35: the signed priceX64/2^64, flat
fee0.1 bps0.01 bps
swap CUno live pool18: ~5,790 · 35: ~5,850

Reference: the six SOL/USDC pools we measured on 2026-11-05 ask 2x to 10x the compute of a Pulse swap (15,038 to 62,639 CU).

Swap instruction layout

data[0] = 1-byte disc. Body scalars LITTLE-endian. inputTokenProgram / outputTokenProgram must match the vault owners (SPL vs Token-2022).

Apex swapdisc 6
data (34 bytes)
[0]      u8   disc = 6
[1..9]   u64  amount            (LE)
[9..17]  u64  otherAmtThreshold (LE)  // min out / max in
[17..33] u128 sqrtPriceLimitX64 (LE)  // 0 = no limit
[33]     u8   isBaseInput       (1/0)

accounts (10 head, then tail)
0  payer            signer, w
1  amm_config       ro
2  pool             w
3  user_in (ATA)    w
4  user_out (ATA)   w
5  input_vault      w
6  output_vault     w
7  observation      w
8  input_token_prog ro   // routes input vault
9  output_token_prog ro  // routes output vault
-- tail --
10 bitmap_ext       w    // FIRST in tail
11.. tick_array[]    w    // window, ordered
Pulse swapdisc 35 · swap_anchor_signed
data (41 bytes)
[0]       u8    disc = 35
[1..9]    u64   amount            (LE)
[9..17]   u64   otherAmtThreshold (LE)  // min out
[17..33]  u128  priceX64          (LE)  // fill price, spread in
[33..41]  u64   expirySlot        (LE)

accounts (11)
0 payer             signer, w
1 pool              w
2 user_in (ATA)     w
3 user_out (ATA)    w
4 input_vault       w
5 output_vault      w
6 input_token_prog  ro   // routes input vault
7 output_token_prog ro   // routes output vault
8 quote_key         signer, ro  // signs last
9 ref_base          ro   // pool's base feed
10 ref_quote        ro   // pool's quote feed

// no tick arrays, no bitmap, no observation:
// the price comes signed inside the trade, held to the feeds.
Pulse swapdisc 18 · swap_anchor
data (17 bytes)
[0]       u8    disc = 18
[1..9]    u64   amount            (LE)
[9..17]   u64   otherAmtThreshold (LE)  // min out

accounts (8)
0 payer             signer, w   // the only signer
1 pool              w
2 user_in (ATA)     w
3 user_out (ATA)    w
4 input_vault       w
5 output_vault      w
6 input_token_prog  ro   // routes input vault
7 output_token_prog ro   // routes output vault

// the price the pool posts: any caller, up to capacity_cap@304 a slot and cap_bps@376 of
// the output vault, inside splits and multi-hops.

Direction is inferred on-chain from the input vault. zero_for_one = input is mint_0 (price falls); one_for_zero = input is mint_1 (price rises). The Apex tick-array window starts at tick_current's array and walks the swap direction (down for zero_for_one), max 12 arrays.

PDA derivation

Every index-bearing seed is BIG-endian (to_be_bytes); the pool and position-asset nonce tails are u32 LITTLE-endian. Account-data scalars are LE.

AccountSeedsEncoding
amm_config"amm_config", indexindex = u16 BE
pool (Apex)"pool", amm_config, mint0, mint1, noncenonce = u32 LE
pool (Pulse)"pool_anchor", amm_config, mint0, mint1, noncenonce = u32 LE
vault"pool_vault", pool, mintnone
observation"observation", poolnone
bitmap_ext"pool_tick_array_bitmap_extension", poolnone
tick_array"tick_array", pool, start_tick_indexstart = i32 BE
position"position", core_assetnone (NFT mint)

mint_0 addr < mint_1 addr. tick_array start = floor-toward-negative-infinity over tick_spacing × 60 ticks per array.

Pool decode offsets

Fixed-offset decoders. u128 is two LE u64 halves (lo then hi). disc at byte 0.

PoolClmm (disc 2)
2    amm_config     pubkey
66   token_mint0    pubkey
98   token_mint1    pubkey
130  token_vault0   pubkey
162  token_vault1   pubkey
194  observation    pubkey
226  mint_decimals0 u8
227  mint_decimals1 u8
228  tick_spacing   u16
230  liquidity      u128
246  sqrt_price_x64 u128
262  tick_current   i32
382  status         u8   (bit4 = swaps off, bit0 = no new liquidity)
1089 fee_rate       u32  (/1e5 = bp)
1093 pool_nonce     u32
1137 dynamic_fee    [34] (the automatic fee; control u32 @1143, 0 = flat)
PoolAnchor (disc 7)
72   token_mint0    pubkey
104  token_mint1    pubkey
136  token_vault0   pubkey
168  token_vault1   pubkey
200  pricer_hot_key pubkey
232  mint_decimals0 u8
233  mint_decimals1 u8
234  status         u8
235  haircut_bps_p1 u8   (SENTINEL: 0=>5bp; v>=1=>min(v-1,5) bp)
236  round_mode     u8   (SENTINEL: 1=precise rounding, else legacy)
240  price_x64      u128
256  bid_factor     u64
264  ask_factor     u64  (band = (f/1e12-1)*1e4 bp)
272  confidence     u64
280  price_updated_ms u64
288  data_slot      u64
304  capacity_cap   u64  (quote per slot, 0 = none, only with size_band above 0)
360  max_slot_lag   u64
368  max_age_ms     u64
376  cap_bps        u64  (share of the paying vault per fill)
384  conf_max       u64
456  slot_budget_slot u64  (the slot the next field counts)
464  slot_quote_filled u128 (quote filled in that slot)
664  size_band      u64  (band widening per USDC filled in the slot, 1e8 = 1 bp)
792  lean_strength  u64  (band units a fill leans with one vault empty, 0 = off)
800  size_free      u64  (the slot's quote a co-signed fill pays no size price on, 0 = off)

Integrate a swap in 3 steps · Apex

Apex (CLMM): walk ticks at the current sqrt_price.

1. DECODE POOL
   getAccountInfo(pool); require owner == program && data[0] == 2
   read sqrt_price_x64@246, tick_current@262, tick_spacing@228,
        decimals@226/227, fee_rate@1089, dynamic_fee@1137, vaults@130/162

2. QUOTE  (spot, refine by walking ticks for size)
   price = (sqrt_price_x64 / 2^64)^2 * 10^(dec0 - dec1)   // quote per base
   feeFactor = 1 - fee_rate / 1e9   // flat; with the automatic fee each tick group pays
                                    // fee_rate + ceil(control*(va*tick_spacing)^2/1e10), at most 1e8
   zeroForOne = inputMint == token_mint0
   out = zeroForOne ? amountIn * price * feeFactor
                    : (amountIn / price) * feeFactor
   minOut = floor(out * (1 - slippage))

3. BUILD + SEND  (ix disc 6)
   input/output vault by direction (zeroForOne -> vault0/vault1)
   tickArrays = window from arrayStart(tick_current) walking direction
   data = [6] u64(amountIn) u64(minOut) u128(0) u8(1)   // baseIn, no limit
   accounts: 10 head + bitmap_ext + tickArrays  (table above)
   sign + sendTransaction

Integrate a swap in 3 steps · Pulse

Pulse (anchored): no curve, no tick arrays. At the posted price (disc 18) from the pool account (and both vaults when it leans), or co-signed (disc 35) for a direct trade.

POSTED (disc 18): any caller, up to capacity_cap a slot and cap_bps of the output vault
1. DECODE POOL
   getAccountInfo(pool); require owner == program && data[0] == 7
   read price_x64@240, bid/ask_factor@256/264, price_updated_ms@280, data_slot@288, haircut@235,
        capacity_cap@304, max_slot_lag@360, max_age_ms@368, size_band@664, lean_strength@792,
        slot_budget_slot@456, slot_quote_filled@464, vaults@136/168
   lean_strength above 0: getMultipleAccountsInfo(vaults), each amount the u64 at byte 64

2. QUOTE  (the posted formula in the spec above)
   out = amountIn at price_x64, banded by side and widened by size_band for the slot's fills
   minOut = floor(out * (1 - slippage))

3. BUILD + SEND  (ix disc 18)
   data = [18] u64(amountIn) u64(minOut)
   accounts: 8 (payer, pool, user_in, user_out, input_vault, output_vault, in_tprog, out_tprog)
   sign + sendTransaction; the trader is the only signer

CO-SIGNED (disc 35): a direct trade at a price our quote service signs
1. QUOTE
   POST /api/swap {"op":"quote","side":"base_in"|"quote_in","amount":"<raw>","taker":"<wallet>"}
   -> {"ok":true,"price_x64","out","expiry_slot","slot","mid","quote_key"}
   // out = amountIn*price_x64*10^(d1-d0) / 2^64     base in
   // out = amountIn*2^64*10^(d0-d1) / price_x64     quote in; one floor, a negative power divides

2. BUILD + SIGN  (ix disc 35)
   read vaults@136/168, ref_base@512, ref_quote@544; input/output vault by direction
   data = [35] u64(amountIn) u64(out) u128(price_x64) u64(expiry_slot)
   accounts: 11 (payer, pool, user_in, user_out, input_vault, output_vault,
                 in_tprog, out_tprog, quote_key, ref_base, ref_quote)
   sign with the taker's key only; the fill's 11 accounts are static keys (lookup tables serve the rest)
   v0 up to 1,232 bytes, or v1 (SIMD-0385) up to 4,096: lay its CU and loaded-data limits, one left out is 0

3. CO-SIGN + SEND
   POST /api/swap {"op":"cosign","tx":"<base64>"}
   -> {"ok":true,"signature"}, or {"ok":false,"why":"expired|moved|size|no_quote|rate|seen|shape|unknown"}
   // the service signs last and sends; watch the taker's signature, which is the transaction id.
   // "seen", "unknown" or a lost connection: it may have gone out, so keep watching until the expiry passes

Read pools (RPC + gRPC)

Enumerate, snapshot, and stream every xMM pool/position off-chain. memcmp the 1-byte disc at offset 0; single-byte disc base58 is one char (bs58Disc, e.g. disc 2 -> "3").

Accountdiscmemcmp byte (base58)
Apex pool2"3"
Pulse pool7"8"
position4"5"
JSON-RPCenumerate one engine
// every Apex (CLMM) pool: getProgramAccounts + memcmp disc 2 -> "3"
// Pulse pools -> disc 7 -> "8"; positions -> disc 4 -> "5"
curl $RPC -s -d '{
  "jsonrpc":"2.0","id":1,"method":"getProgramAccounts",
  "params":["xmm9LkUX7yWLoMNEpeGwxtVF8BZjJsNUih5bqx1in1Z",{
    "encoding":"base64",
    "filters":[{"memcmp":{"offset":0,"bytes":"3"}}]
  }]
}'
// -> [{ pubkey, account:{ owner, data:[<base64>,"base64"] }}, ...]
JSON-RPCsnapshot one pool
// one known pool -> base64 -> decode at the offsets above
curl $RPC -s -d '{
  "jsonrpc":"2.0","id":1,"method":"getAccountInfo",
  "params":["<a CLMM pool>",
            {"encoding":"base64"}]
}'
// d = base64-decode(data[0]); require d[0] == 2 (Apex)
//   sqrt_price_x64 = u128 LE @246  tick_current = i32 LE @262
//   liquidity      = u128 LE @230
// Pulse pool xmmCRmkNHKJBrB23sWAE7di528hFEZsiY8QdTFnwu4o, d[0] == 7:
//   price_x64 = u128 LE @240  bid_factor / ask_factor = u64 LE @256 / @264
//   data_slot = u64 LE @288   size_band = u64 LE @664
// a co-signed fill prices off POST /api/swap {"op":"price"} -> {"ok":true,"mid","bid_x64","ask_x64","slot","sources","age_ms"}
Yellowstone gRPCstream every change
// Geyser push updates: filter accounts owned by the program. Add the
// disc memcmp to scope one engine. Endpoint is integrator-configured.
import Client from "@triton-one/yellowstone-grpc";

const client = new Client(GRPC_ENDPOINT, GRPC_TOKEN, undefined);
const stream = await client.subscribe();
stream.write({
  accounts: {
    xMM: {
      owner: ["xmm9LkUX7yWLoMNEpeGwxtVF8BZjJsNUih5bqx1in1Z"],
      account: [],
      // optional: one engine only (Apex). drop to stream all accounts.
      filters: [{ memcmp: { offset: "0", base58: "3" } }],
    },
  },
  slots: {}, transactions: {}, blocks: {}, blocksMeta: {}, entry: {},
  accountsDataSlice: [], commitment: 1, // CONFIRMED
});
// each msg.account.account = { pubkey, owner, data(bytes), ... }
// decode data the same way as getAccountInfo above (disc @0).

The xMM TS client reads its pools by address instead (apps/web/src/lib/xmm/pools.ts) and decodes them the same way (decodePoolClmm / decodePoolAnchor in state.ts).

Public REST APIopenrate limited

Open, no key, read-only (GET). Base /api/v1. CORS Access-Control-Allow-Origin: *. Per-IP token bucket, 60 req/min; every response carries X-RateLimit-Limit/Remaining/Reset, a 429 adds Retry-After. The reads are the xMM TS client (listLivePools / loadPoolRow / quoteClmm) over RPC.

EndpointReturns
GET /api/v1/poolsthe listed pools (engine, pair, price, fee, tvl, volume, reserves, capacity per slot, addresses)
GET /api/v1/pools/{id}one pool (either engine); 404 if not an xMM pool
GET /api/v1/pools/{id}/fillsthe pool's latest fills, newest first
GET /api/v1/quote?pool=&in=&amount=Apex (CLMM) spot quote: out, price, feeBps, minReceived hint
GET /api/v1/oraclefirst-party multi-source price oracle (baselines, pairs, freshness)
GET /api/v1/infoprogram id, pools, amm_config, IDL url, engines, version
CORS *60 req/min/IPX-RateLimit-Limit/Remaining/Reset429 + Retry-After
# Carbium xMM public REST API   base: https://xmm.carbium.sh/api/v1
# Open, no key. CORS: Access-Control-Allow-Origin: *. Read-only (GET).
# Rate limit: 60 req/min per IP (token bucket). Every response sets:
#   X-RateLimit-Limit: 60
#   X-RateLimit-Remaining: <n>
#   X-RateLimit-Reset: <unix-seconds when the bucket is full>
# Over the limit -> HTTP 429 { "error": "rate limit exceeded", "retryAfter": <s> }
#   + Retry-After: <seconds>

## GET /api/v1/pools            the listed pools
curl -s https://xmm.carbium.sh/api/v1/pools
# -> { count, pools: [ { name, id, engine, owner, pair, base, quote, price, feeBps, tvl,
#      volumeQuote, status, reserves:{base,quote}, addresses:{pool,ammConfig,baseMint,
#      quoteMint,vault0,vault1,observation}, decimals:{base,quote}, tickCurrent, tickSpacing,
#      liquidity, bandBps, spreadBps, capacityPerSlot, confidence, priceUpdatedMs } ] }
#   feeBps is the fee tier; an Apex pool on the automatic fee or inside its launch fee also
#   carries feeNowBps (what a small swap pays now), and an automatic-fee pool feeMaxBps (the
#   most a step can pay)
# volumeQuote: quote traded since the pool opened, null if unknown.
# Pulse only: capacityPerSlot = the most quote one slot may fill, absent with no limit; spreadBps = a co-signed
# price's half-spread, absent on a row that carries the pool's own post.
# On a Pulse row that carries spreadBps, price and priceUpdatedMs are the quote service's price at that moment, up to
# about 11 s old, and the pool's own post on chain may be older while the pool rests; without spreadBps they are the post.

## GET /api/v1/pools/{id}       one pool (either engine); 404 if not an xMM pool
curl -s https://xmm.carbium.sh/api/v1/pools/xmmCRmkNHKJBrB23sWAE7di528hFEZsiY8QdTFnwu4o
# -> the single pool object (same shape as an element of /pools)
# 404 -> { "error": "not an xMM pool", "id": "<addr>" }

## GET /api/v1/pools/{id}/fills the pool's latest fills, newest first
curl -s https://xmm.carbium.sh/api/v1/pools/xmmCRmkNHKJBrB23sWAE7di528hFEZsiY8QdTFnwu4o/fills
# -> { fills: [ { sig, slot, at, pool, in, out, quotedRaw, landedRaw, bps } ] }
# in, out = "<amount> <symbol>". Read at most every 30 s; fills is null before the first read.
# Any other pool -> { fills: [] }

## GET /api/v1/quote            Apex (CLMM) spot quote
#   pool         xMM CLMM pool address (clmm only; 400 for an anchored pool)
#   in           input token: symbol (SOL/USDC) OR mint address
#   amount       input amount, human units of `in`
#   slippageBps  optional, default 50 (0.5%) -> drives minReceived
curl -s 'https://xmm.carbium.sh/api/v1/quote?pool=<apex-pool>&in=SOL&amount=1.5'
# -> { pool, engine, in:{token,amount}, out:{token,amount}, price, feeBps,
#      [feeNowBps, feeMaxBps,] inputIsBase, slippageBps, minReceived, note }
# minReceived = out * (1 - slippageBps/1e4); set as other_amount_threshold (raw)
# on the disc-6 swap ix (see Write operations below).

## GET /api/v1/oracle           first-party multi-source price oracle (display mirror)
curl -s https://xmm.carbium.sh/api/v1/oracle
# -> { asset:"USD", baselines:{ SOL,USDC,USDT,CHF,EUR,SPDR,EUROC: { price, conf,
#      dispersion, median, liveCount, keptCount, unpriced?,
#      sources:[{name,price,ageSec,kept,stale}], fresh } },
#      pairs:[ { pair, base, quote, mid, fresh } ], staleness, sources, firstParty,
#      offChain, maintainedBy, ts, cachedForMs }
# A display mirror of the rule our quote service prices by; about 1.5 s server cache.

## GET /api/v1/info             program id, pools, amm_config, IDL url, engines, version
curl -s https://xmm.carbium.sh/api/v1/info
# -> { name, version, build, programId, ammConfig, idlUrl, engines[], pools[], api{} }

# Errors are JSON: { "error": "<msg>", ... }. Statuses: 200 ok, 400 bad query,
# 404 not an xMM pool, 429 rate limited, 502 upstream RPC error.

Write operations (pool lifecycle)build + sign1-byte disc

The REST API is read-only. To write, build + sign these instructions yourself. disc = 1-byte ix at data[0]; body scalars LITTLE-endian; PDA index seeds BIG-endian (nonce tails u32 LE). Mirrored from packages/idl/xmm.json and apps/web/src/lib/xmm/ix.ts.

create_pool 2open_position 3increase 4create_tick_array 24decrease 5collect_fees 7close_position 8
# xMM WRITE operations (on-chain instruction recipes)
# The REST API is read-only. To WRITE, build + sign these instructions yourself.
# program xmm9LkUX7yWLoMNEpeGwxtVF8BZjJsNUih5bqx1in1Z
# data[0] = 1-byte ix disc; body scalars LITTLE-endian; string = u8 len + bytes.
# PDA index seeds BIG-endian; pool nonce tail u32 LE; ATA = [owner, tokenProgram, mint].
# token_program_{0,1} MUST equal the owner of mint_{0,1} (SPL vs Token-2022).
# mpl_core = CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d
# system   = 11111111111111111111111111111111

## create_pool  (disc 2)  -> PoolStateClmm + 2 vaults + observation + bitmap_ext + Core collection
body: pool_nonce u32 | sqrt_price_x64 u128 | name (u8 len + bytes) | uri (u8 len + bytes)
accounts:
 0  creator               signer, w   funds rent
 1  amm_config            ro          decoded for tick_spacing/fee
 2  pool                  w           PDA ["pool", amm_config, mint0, mint1, nonce_le]
 3  token_mint_0          ro          addr(mint0) < addr(mint1)
 4  token_mint_1          ro
 5  token_vault_0         w           PDA ["pool_vault", pool, mint0]
 6  token_vault_1         w           PDA ["pool_vault", pool, mint1]
 7  observation           w           PDA ["observation", pool]
 8  bitmap_extension      w           PDA ["pool_tick_array_bitmap_extension", pool]
 9  collection            w           PDA ["collection", pool] (Core collection, program signs)
 10 token_program_0       ro          == owner(mint0)
 11 token_program_1       ro          == owner(mint1)
 12 mpl_core              ro
 13 system_program        ro
# sqrt_price_x64 = floor(sqrt(price_raw) * 2^64), price_raw = token1_raw/token0_raw.

## open_position  (disc 3)  -> PositionState PDA + Core NFT, liquidity starts 0
body: tick_lower_index i32 | tick_upper_index i32 | nonce u32     (lower < upper, on tick_spacing)
accounts:
 0  payer                 signer, w   funds rent; NFT owner; rent_receiver
 1  pool                  w           decoded for tick_spacing; counts open positions
 2  position              w           PDA ["position", asset]
 3  asset                 w           PDA ["asset", pool, payer, nonce_le] (Core NFT, program signs)
 4  collection            w           PDA ["collection", pool]
 5  tick_array_lower      w           PDA ["tick_array", pool, lower_start_be]
 6  tick_array_upper      w           PDA ["tick_array", pool, upper_start_be]
 7  mpl_core              ro
 8  system_program        ro
# *_start = floor-to-neg-inf of the boundary tick over (tick_spacing * 60) ticks.
# Pick any nonce you have not used on this pool; a used one refuses. Return data: asset | position.

## increase_liquidity  (disc 4)  -> deposit (amounts round UP)
body: amount_0_max u64 | amount_1_max u64 | amount_0_min u64 | amount_1_min u64
accounts:
 0  owner                 signer      gated by asset ownership
 1  pool                  w
 2  position              w           PDA ["position", asset]
 3  asset                 ro          Core NFT (ownership gate)
 4  tick_array_lower      w
 5  tick_array_upper      w
 6  token_owner_0         w           user src ATA (mint0)
 7  token_owner_1         w           user src ATA (mint1)
 8  token_vault_0         w           == pool.token_vault_0
 9  token_vault_1         w           == pool.token_vault_1
 10 token_program_0       ro
 11 token_program_1       ro
 12 bitmap_extension      w  OPTIONAL  ["pool_tick_array_bitmap_extension", pool]
# bitmap_extension only when a boundary array is outside the pool-internal window.

## decrease_liquidity  (disc 5)  -> withdraw (amounts round DOWN; pool PDA signs vault-out)
body: liquidity u128 | amount_0_min u64 | amount_1_min u64
accounts: same 13 as increase_liquidity (idx 12 bitmap_extension OPTIONAL),
          token_owner_{0,1} are the user DST ATAs here.

## collect_fees  (disc 7)  -> pay owed fees to the owner, re-snapshot fee_growth_inside
body: (none)
accounts:
 0  owner                 signer      ownership gate
 1  pool                  ro          fee_growth source; PDA signs vault-out
 2  position              w           PDA ["position", asset]
 3  asset                 ro          Core NFT
 4  token_owner_0         w           user dst ATA (mint0)
 5  token_owner_1         w           user dst ATA (mint1)
 6  token_vault_0         w
 7  token_vault_1         w
 8  token_program_0       ro
 9  token_program_1       ro
 10 tick_array_lower      ro          fee_growth_outside source
 11 tick_array_upper      ro

## close_position  (disc 8)  -> burn the Core NFT, reclaim rent (liquidity==0 AND owed==0)
body: (none)
accounts:
 0  owner                 signer, w   ownership gate; BurnV1 authority
 1  position              w           PDA ["position", asset]
 2  asset                 w           Core NFT to burn
 3  collection            w           the pool's Core collection
 4  rent_receiver         w           == position.rent_receiver
 5  mpl_core              ro
 6  system_program        ro
 7  pool                  w           == position.pool_id; counts open positions

## create_tick_array  (disc 24)  -> the array a swap walks through; open_position makes only its boundaries
body: start_index i32 LE
accounts:
 0  payer                 signer, w   funds rent; gets it back when the array is closed
 1  pool                  ro
 2  tick_array            w           PDA ["tick_array", pool, start_be]
 3  system_program        ro

# Lifecycle order:
#   create_pool -> open_position -> increase_liquidity -> create_tick_array (the current price's array)
#   -> (swap disc 6 / collect_fees) -> decrease_liquidity -> collect_fees -> close_position

Pulse depositsbuild + sign

A share of both Pulse vaults, in kind, held as a position NFT. Depositors trust Carbium's pricing and the pool owner's settings.

open_pulse_position 37pulse_withdraw 38close_pulse_position 39
# xMM Pulse deposits (on-chain instruction recipes)
# Depositors trust Carbium's pricing and the pool owner's settings: the pool fills at Carbium's own price,
# and its owner sets the pool's price bounds and keys.
# A share is in kind: a deposit pays both tokens at the vaults' own ratio, a withdraw takes the same share of
# both vaults back. The spread the pool's fills earn stays in the vaults, so a share grows with it.
# The pool owner's first position claims the inventory already in the vaults (isqrt(R0*R1) shares, no tokens
# move) and sets the hold; nobody else can deposit before it. While shares exist the owner's own
# withdraw_inventory and deposit_inventory refuse (6063); close_pool refuses while a position is open (6050).
# pool bytes: lp_total_shares u64 @320, lp_positions u32 @328, lp_hold_secs u32 @332,
#   "PULSELP1" @344 (without it the three read as 0)
# position: disc 8, 184 bytes, PDA ["position", asset]; asset PDA ["asset", pool, payer, nonce u32 LE];
#   pool @8, asset @40, shares u64 @72, deposited_at i64 @80 (unix s), rent_receiver @88

## open_pulse_position  (disc 37)  -> a deposit and its position NFT; returns asset | position
body: nonce u32 | max_0 u64 | max_1 u64 | min_shares u64 | hold_secs u32 (the owner's first position only)
shares = min(floor(max_i * S / R_i)); pays a_i = ceil(shares * R_i / S); refused while swaps, deposits or the guardian are off
accounts:
 0  payer                 signer, w   pays both tokens and the rent; owns the NFT
 1  pool                  w
 2  position              w           PDA ["position", asset]
 3  asset                 w           PDA ["asset", pool, payer, nonce_le]
 4  collection            w           PDA ["collection", pool]
 5  token_owner_0         w
 6  token_owner_1         w
 7  vault_0               w
 8  vault_1               w
 9  token_program_0       ro
10  token_program_1       ro
11  mpl_core              ro
12  system_program        ro

## pulse_withdraw  (disc 38)  -> out_i = floor(shares * R_i / S); returns amount_0 | amount_1
body: shares u64 | min_0 u64 | min_1 u64; waits lp_hold_secs from deposited_at (6062); no pool status stops it
accounts:
 0  holder                signer      owns the NFT
 1  pool                  w
 2  position              w
 3  asset                 ro
 4  token_owner_0         w
 5  token_owner_1         w
 6  vault_0               w
 7  vault_1               w
 8  token_program_0       ro
 9  token_program_1       ro

## close_pulse_position  (disc 39)  -> burns an emptied position's NFT; its rent returns to the depositor
body: (none). An NFT burned outside the program leaves a 1-byte husk: anyone may close that position, and its shares go to the rest.
accounts:
 0  signer                signer, w   the holder (pays the burn)
 1  position              w
 2  asset                 w
 3  collection            w
 4  rent_receiver         w           == position.rent_receiver
 5  mpl_core              ro
 6  system_program        ro
 7  pool                  w
seeds = BE index, LE nonceix body = LEu128 = lo|hi LE u64token prog must match vault ownermemcmp disc @0