#Solana#DEX#Developer#Raydium#Orca#Meteora

Every Solana DEX Program ID and Pool Layout, Decoded

Verified program IDs and byte offsets for Raydium, Orca Whirlpool, Meteora, PumpSwap, Moonit and FluxBeam, from twelve decoders we run in production.

16 min read
By ScreenerBot Team

If you have a pool address and need to know which DEX it belongs to, what its account data contains, and how to turn that data into a price, this page is the whole answer. ScreenerBot decodes twelve Solana pool programs locally, on every price tick, so every constant below is one we run in production rather than one copied from a blog post.

Start with the identification table. Everything after it is detail.


The program IDs

A pool account's owner is the DEX program. One getAccountInfo call tells you which decoder to use — you never have to guess from the token pair or the pool name.

DEX / programProgram IDPricing model
Raydium AMM v4 (legacy)675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8vault reserves
Raydium CPMMCPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1Cvault reserves
Raydium CLMMCAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqKsqrt_price Q64.64
Orca WhirlpoolwhirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCcsqrt_price Q64.64
Orca v1 (legacy)9W959DqEETiGZocYWCQPaJ6sBmUzgfxXfqGeTEdp3aQPvault reserves
Meteora DLMMLBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxobin reserves
Meteora DAMMcpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGGsqrt_price Q64.64
Meteora DBC (bonding curve)dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqNcurve reserves
PumpSwap (pump.fun AMM)pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEAvault reserves
pump.fun (bonding curve)6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6Pvirtual reserves
Moonit (Moonshot)MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrGcurve reserves
FluxBeamFLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1Xvault reserves

Routers and aggregators are not pool programs. If a transaction's top-level program is one of these, the pool it touched is still one of the above:

RouterProgram ID
Jupiter v6JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4
Jupiter v4JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB
Jupiter v3JUP3c2Uh3WA4Ng34tw6kPd2G4C5BB21Xo36Je1s32Ph
GMGNGMGNjvGr7ddxt2u1XSf8Zo6LLnDjDm9mJahGfhq7j6gk

There are only two pricing models

Twelve programs, but only two ways to get a price. Knowing which one a pool uses tells you what to fetch.

Reserve-based (constant product). The pool owns two SPL token accounts. The price is the ratio of their balances, adjusted for decimals. You must fetch the two vault accounts in addition to the pool account.

Square-root price (concentrated liquidity). The pool account itself stores sqrt_price as a Q64.64 fixed-point number. No vault fetch is needed for a price — one account read is enough, which makes CLMM-style pools materially cheaper to poll.

Reading reserves from a vault

Every vault is a standard SPL token account, so one rule covers all of them: the amount is a little-endian u64 at byte offset 64, and the mint is the pubkey at offset 0. That second field is worth reading even when you think you know the answer — it is how you catch a mis-parsed vault before it becomes a wrong price.

fn token_account_amount(data: &[u8]) -> Option<u64> {
    if data.len() < 72 {
        return None;
    }
    Some(u64::from_le_bytes(data[64..72].try_into().ok()?))
}

Price from reserves, with decimals applied:

price_in_sol = (sol_reserve / 10^sol_decimals) / (token_reserve / 10^token_decimals)

Converting a Q64.64 square-root price

sqrt_price encodes the square root of the price of token B per token A, in Q64.64 fixed point. Square it, divide by 2^128, then correct for the decimal difference:

price_b_per_a = (sqrt_price^2 / 2^128) * 10^(decimals_a - decimals_b)

Doing it as (sqrt_price / 2^64)^2 first, rather than sqrt_price^2 / 2^128, keeps the intermediate value inside f64 range. Squaring a raw u128 before dividing overflows for high-priced pools, and the failure is silent.


Raydium AMM v4 (legacy)

The long-lived Raydium AMM. The account is LIQUIDITY_STATE_LAYOUT_V4: 32 consecutive u64 fields (bytes 0–256), then a block of swap counters, then the pubkeys. The offsets that matter:

FieldOffsetSize
quoteTotalPnl2088
baseTotalPnl2168
baseVault33632
quoteVault36832
baseMint40032
quoteMint43232
lpMint46432

Read the two mints, decide which side is SOL, then fetch the matching vault and read its balance at offset 64.

Verify each vault's mint against the mint you read from the pool account before you trust its balance. A hardcoded vault offset that is even a few bytes off still yields a syntactically valid pubkey — it just points at a different account, and you get a confident, wrong price rather than an error.


Raydium CPMM

The modern constant-product program, and the one with Token-2022 support. The account is Anchor-style: an 8-byte discriminator, then fields in a fixed order with no padding until the tail.

FieldOffset
discriminator0
amm_config8
pool_creator40
token_0_vault72
token_1_vault104
lp_mint136
token_0_mint168
token_1_mint200
token_0_program232
token_1_program264
observation_key296
auth_bump, status, lp_mint_decimals, pool_mint_0_decimals, pool_mint_1_decimals328–333
lp_supply333

token_0_program and token_1_program are the reason this layout matters: a CPMM pool can hold a Token-2022 mint on either side, and a transfer-fee extension on that mint means the amount that reaches the pool is smaller than the amount you sent. Read those two fields rather than assuming the classic SPL Token program.


Raydium CLMM and Orca Whirlpool

Both are concentrated liquidity, both price from sqrt_price, and both are single-account reads. Orca's Whirlpool account is the better documented of the two, and its layout is stable:

FieldOffsetSize
discriminator08
whirlpools_config832
whirlpool_bump401
tick_spacing412
fee_tier_index_seed432
fee_rate452
protocol_fee_rate472
liquidity4916
sqrt_price6516
tick_current_index814
protocol_fee_owed_a858
protocol_fee_owed_b938
token_mint_a10132
token_vault_a13332
fee_growth_global_a16516
token_mint_b18132
token_vault_b21332

A valid Whirlpool account is at least 653 bytes; treat anything shorter as a decode failure rather than parsing it. Note that liquidity and sqrt_price are u128, not u64 — reading either as eight bytes gives a plausible-looking number that is wrong by an enormous factor.

fee_tier_index_seed at offset 43 is a comparatively recent addition. If you are working from an older struct definition that omits it, every field after offset 43 shifts by two bytes and the whole tail decodes to garbage. This is the most common cause of "my Whirlpool decoder used to work".


Meteora: DLMM, DAMM and DBC

Meteora ships three distinct programs and they do not share a layout.

DLMM is a discretized liquidity market maker: liquidity sits in fixed-width bins, and the pool stores an active_id and a bin_step rather than a single price. The theoretical price of the active bin is:

price = (1 + bin_step / 10000) ^ active_id

That value is the bin's nominal price and it ignores decimals and the real balance of the active bin. For anything that touches money, read the actual reserve_x and reserve_y token accounts referenced by the pool and price from those balances. Use the bin math as a sanity check on the reserve-derived price, not as a replacement for it.

DAMM is a constant-product pool that also carries a sqrt_price. In practice the two disagree on very low-liquidity pools, where accumulated protocol fees are large relative to the reserves. Our decoder cross-checks the square-root price against the vault ratio and rejects the square-root value when it diverges by more than a few hundred percent. If you only implement one path, implement the vault ratio.

DBC is Meteora's bonding-curve launch program. A DBC pool that has not graduated has no conventional pair of vaults — check both vault fields against the system program address 11111111111111111111111111111111 and treat that as "not yet a real pool" rather than as a zero balance.


pump.fun and PumpSwap

These are two different programs at two different stages of a token's life, and conflating them is a common source of missing pools.

pump.fun (6EF8rrec...) is the bonding curve. A new token trades against a virtual-reserve curve on this program, with no LP and no external liquidity.

PumpSwap (pAMMBay6...) is the AMM a token graduates into. Its pool account is Anchor-style, and the field order is: discriminator (8), pool_bump (1), index (2), then the pubkeys — creator, base_mint, quote_mint, lp_mint, pool_base_token_account, pool_quote_token_account — followed by lp_supply.

Because a coin_creator field was added to this struct after launch, the offset of lp_supply differs between older and newer pools. Derive it by walking the pubkeys forward from offset 11 rather than hardcoding it, and validate that the account owner really is pAMMBay6... before parsing — the discriminator alone is not a strong enough check.


Fetching efficiently

Decoding is cheap; fetching is not. Three rules keep an RPC budget under control:

  • Batch pool and vault reads with getMultipleAccounts instead of issuing one getAccountInfo per account. Keep each batch to 50 accounts or fewer — that stays inside the limits of every provider we have used, including those that advertise 100.
  • Prefer concentrated-liquidity pools when you only need a price. One account read beats three.
  • Cache the pool-account decode. Mints, vaults and decimals never change for a given pool; only the balances and sqrt_price do. Re-reading the layout on every tick wastes the majority of your request budget.

Avoid getProgramAccounts against these programs in a hot path. It is heavily rate-limited or disabled on most providers, and on a program the size of Raydium AMM v4 the response is enormous.


The traps, in order of how much they cost

  1. Assuming base is the token. Always compare both mints against wrapped SOL, So11111111111111111111111111111111111111112, and orient from that. Getting this wrong inverts the price.
  2. Reading a u128 as a u64. liquidity and sqrt_price are 16 bytes in every concentrated-liquidity layout here.
  3. Squaring sqrt_price before dividing. Divide by 2^64 first, then square.
  4. Trusting a vault offset without checking its mint. A wrong offset yields a valid-looking pubkey and a confidently wrong price, never an error.
  5. Ignoring token_0_program and token_1_program on CPMM. A Token-2022 transfer fee changes the amount that actually arrives.
  6. Forgetting decimals. Every formula here needs 10^(decimals_a - decimals_b); skipping it produces prices off by orders of magnitude.

Why decode at all

A price API returns a number somebody else computed, at their cadence, with their idea of which pool matters. Decoding the pool yourself gives you the price at your own cadence, from the specific pool you are going to trade against, with no third party between your decision and the chain. ScreenerBot refreshes pool prices roughly twice a second from local decoders for exactly that reason — and the same twelve program IDs above are what make it possible.

If you would rather read this data than build it, the ScreenerBot Terminal shows live pool-derived prices for Solana markets, and Token DNA traces the on-chain history behind them. If you want the pipeline running on your own machine, the desktop app does all of the above locally.

For the API layer that sits above these programs, see our Jupiter API guide, and for the maths behind constant-product pricing, how DEX prices work.

Keep reading

Ready to Start Trading?

Download ScreenerBot and start automated DeFi trading on Solana.

Download Now