Integrate
no SDK1 live poolRPC-onlyWire 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.
# 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.
| What | Address | Note |
|---|---|---|
| program | xmm9Lk..in1Z | owner of all xMM accounts |
| amm_config | CCbTuj..oix4 | fee tier, shared by both engines |
| Pulse SOL/USDC | xmmCRm..wu4o | anchored, disc 7 |
| mpl-core | CoREEN..hX7d | position NFT (range + fees) |
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.
| Field | Apex (CLMM) | Pulse (anchored) |
|---|---|---|
| model | concentrated liquidity | oracle-anchored maker, no curve |
| pool disc | 2 | 7 |
| swap ix disc | 6 · swap | 18 · swap_anchor 35 · swap_anchor_signed |
| ix body | u64 amount, u64 thr, u128 sqrtLimit, u8 baseIn (34B) | 18: u64 amount, u64 thr (17B) 35: u64 amount, u64 thr, u128 price, u64 expiry (41B) |
| accounts | 10 head + bitmap + tick arrays | 18: 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 |
| fee | 0.1 bps | 0.01 bps |
| swap CU | no live pool | 18: ~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).
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
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.
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.
| Account | Seeds | Encoding |
|---|---|---|
| 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 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 + sendTransactionIntegrate 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 passesRead 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").
| Account | disc | memcmp byte (base58) |
|---|---|---|
| Apex pool | 2 | "3" |
| Pulse pool | 7 | "8" |
| position | 4 | "5" |
// 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"] }}, ...]// 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"}// 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.
| Endpoint | Returns |
|---|---|
| GET /api/v1/pools | the 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}/fills | the 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/oracle | first-party multi-source price oracle (baselines, pairs, freshness) |
| GET /api/v1/info | program id, pools, amm_config, IDL url, engines, version |
# 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.
# 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_positionPulse 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.
# 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