On June 10, 2026 at 12:09:21 UTC, Raydium’s legacy Solana liquidity pool program 27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv was exploited through a remove-liquidity validation bug. The attacker 4WnPebowR4HHfumvNPaDjG6Pa5Hi1jxLm6xmmBq33QVk created attacker-controlled SPL mints with zero decimals, minted one token, and passed that fake token account into the legacy remove-liquidity path as if it were a legitimate LP token. Public reporting attributed about $1.3M of total drained value; independent on-chain checks in this analysis confirm at least two representative successful removals and a broader cluster of successful legacy remove-liquidity calls in the attacker’s transaction history. In the two primary sample transactions, burning only one attacker-minted token caused Raydium vault transfers of 5,602.964099112 WSOL, 83,341.713143 RAY, and 10,692.215336 SRM to attacker-controlled token accounts.

Root Cause

Vulnerable Contract

The vulnerable component is Raydium’s legacy liquidity pool program on Solana at 27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv, commonly indexed as Raydium Liquidity Pool V3. This is a deprecated AMM V3 program, not the currently published Raydium AMM V4 program at 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8. A search across Raydium’s public GitHub repositories found official instruction/client definitions for the old Raydium AMM interface, but did not locate a public, verifiable processor source for deployed program 27haf8.... Therefore the vulnerable code below is a trace-reconstructed Rust pseudocode model, grounded in the transaction’s Raydium logs, inner SPL-token instructions, and the public Raydium instruction layout; it should not be read as recovered source code.

Vulnerable Function

The vulnerable execution path is the Raydium legacy RemoveLiquidity instruction on program 27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv; the observed instruction data for the sample calls is 3xSnUGQKaGEB, which decodes to a withdraw instruction with amount 1. The path burned the supplied user LP token and transferred assets from the pool vaults, but it did not sufficiently bind the supplied LP mint and user LP token account to the pool’s canonical LP mint. This let the attacker create a fresh SPL mint with supply 1, pass it as the LP mint, and make the remove-liquidity math use the fake mint’s supply as the withdrawal denominator while releasing real pool assets.

Vulnerable Code

// Trace-reconstructed Solana/Raydium legacy withdraw pseudocode.
// Not recovered source: public searches found interface code, not the 27haf8... processor.
// Observed instruction data: 04 0100000000000000 => withdraw.amount = 1.
pub fn process_withdraw(accounts: &[AccountInfo], withdraw: WithdrawInstruction) -> ProgramResult {
    let amm = load_amm_state(&accounts.amm)?;
    let lp_mint = spl_token::state::Mint::unpack(&accounts.lp_mint.data.borrow())?;
    let user_lp = spl_token::state::Account::unpack(&accounts.user_lp.data.borrow())?;

    // Missing canonical LP binding:
    // require_keys_eq!(accounts.lp_mint.key(), amm.lp_mint);
    // require_keys_eq!(user_lp.mint, amm.lp_mint);
    // The attacker supplies a fresh mint whose supply is exactly 1.

    let withdraw_amount = withdraw.amount; // 1 in the exploit tx
    let coin_amount = checked_mul_div(
        vault_balance(&accounts.coin_vault),
        withdraw_amount,
        lp_mint.supply, // <-- VULNERABILITY: attacker-controlled denominator is 1
    )?;
    let pc_amount = checked_mul_div(
        vault_balance(&accounts.pc_vault),
        withdraw_amount,
        lp_mint.supply, // <-- VULNERABILITY: attacker-controlled denominator is 1
    )?;

    transfer_signed(&accounts.coin_vault, &accounts.user_coin, &accounts.amm_authority, coin_amount)?;
    transfer_signed(&accounts.pc_vault, &accounts.user_pc, &accounts.amm_authority, pc_amount)?;
    spl_token_burn(&accounts.user_lp, &accounts.lp_mint, &accounts.user_authority, withdraw_amount)?;

    Ok(())
}

Why It’s Vulnerable

Expected behavior: A remove-liquidity instruction must derive or verify every pool-critical account. The LP mint passed to the instruction must equal the pool state’s canonical LP mint, the user LP token account must be owned by the caller and have that canonical LP mint, and the vault token accounts must match the pool’s configured token vaults. Burning an unrelated SPL token should never entitle the caller to pool reserves.

Actual behavior: The observed transactions show the attacker creating a new SPL mint in the same transaction, initializing it with zero decimals and attacker mint authority, creating a token account for it, minting exactly 1 token, and then invoking Raydium’s legacy remove-liquidity instruction. The Raydium program logs show the calculation using lp_mint_supply=1 and withdraw_amount=1, producing output amounts equal to the real vault balances in the sample transaction. The inner instructions under the Raydium call transfer real assets from Raydium vault-owned accounts into attacker-owned accounts and then burn exactly 1 token from the attacker-created mint. Because the burned mint was not the legitimate pool LP mint, the burn did not represent any real share of the pool, but the program still released real vault assets.

This breaks the core LP accounting invariant: only canonical LP supply should be redeemable for vault assets. Once the LP mint/account binding is bypassed, the attacker can manufacture redeemable input at negligible cost and repeat the pattern against affected legacy pools until profitable vault balances are depleted or the path stops accepting the provided account set.

Attack Execution

High-Level Flow

  1. The attacker used Solana account 4WnPebowR4HHfumvNPaDjG6Pa5Hi1jxLm6xmmBq33QVk as the signer and authority for the exploit transactions.
  2. For each targeted pool, the attacker created a fresh SPL token mint with zero decimals and attacker-controlled mint authority.
  3. The attacker created a token account for the fake mint and minted exactly 1 token to it.
  4. The attacker invoked Raydium legacy RemoveLiquidity with instruction amount 1 and with the fake mint/account supplied in the LP-token positions.
  5. Raydium calculated the withdrawal with lp_mint_supply=1, transferred real vault assets, including WSOL, RAY, and SRM, to attacker-controlled token accounts, and then burned the fake token.

Detailed Call Trace

In transaction 2gwZ1P37p3S3963YwovvzE1FF7zXY3g1Dj8BUVwzsUAQaJD2W7sxXx9geFcxaUny4tAEguzv187ha1YmE9iBZTMN, the attacker first created mint GPJL2nunF9jhNQQVTvq4oL7AsvX9Jxms44rMmFWvMmXF and initialized it through the SPL Token program with zero decimals and mint authority 4WnPebowR4HHfumvNPaDjG6Pa5Hi1jxLm6xmmBq33QVk. The attacker then created token account BDWNn6c6GdaTi2AUuiebcwmfufFXCTPnMhFuJ7k17uDv for that mint and minted 1 token to it.

The transaction then called Raydium program 27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv with instruction data 3xSnUGQKaGEB. This base58 instruction data decodes to bytes 04 0100000000000000, i.e. the Raydium withdraw discriminator followed by little-endian amount 1. The Raydium logs record lp_mint_supply=1, withdraw_amount=1, coin_amount=74720095333, and pc_amount=5602964099112. Inside that Raydium instruction, the SPL Token program transferred 74,720.095333 RAY from vault account Fy6SnHwAkxoGMhUH2cLu2biqAnHmaAwFDDww9k6gq5ws to attacker token account 5cueDH5D98tLybGY1yvzofuRSb8mDcGARACK8b5tshE2, and transferred 5,602.964099112 WSOL from vault account GoRindEPofTJ3axsonTnbyf7cFwdFdG1A3MG9ENyBZsn to attacker token account 5fBQmfAYdnFL4JhykUwfVwLoQA2Yap81pt2s1mL1vCYj. The same inner instruction group then burned 1 token from the attacker-created mint GPJL2nunF9jhNQQVTvq4oL7AsvX9Jxms44rMmFWvMmXF.

In transaction 9jBVvs6stJfpVan97pzkW2huUdynQ41yzWJh14w6j1jXee2MKZwPjCGQ5zHyBdSMBdHX9mbhTYzNMfD1tupbxbR, the same signer created another fake mint dQ6xPbe5FTrKiy9HhwXzudvibvYxTJDc3UR8ouBoc1J, minted 1 token to account 5hXMUhaWuacZyQPDaQTWngfCwU3g4y6Rifgq5x71dv9g, and invoked the same Raydium remove-liquidity instruction. The Raydium call transferred 8,621.61781 RAY from vault account Eg6sR9H28cFaek5DVdgxxDcRKKbS85XvCFEzzkdmYNhq to the attacker’s existing RAY account and 10,692.215336 SRM from vault account 8g2nHtayS2JnRxaAY5ugsYC8CwiZutQrNWA9j2oH8UVM to attacker token account Bome5iYrA2HuZ5KnkBoayRfYBJ7wt3e6H16g6MKH5VSb, while burning only 1 token of the fake mint.

A scan of fetched successful transactions signed by 4WnPebowR4HHfumvNPaDjG6Pa5Hi1jxLm6xmmBq33QVk found 17 successful transactions that invoked the same Raydium legacy program path between 2026-06-10 12:09:21 UTC and 2026-06-10 12:22:04 UTC. Some later calls in the attacker’s history failed with custom program errors, consistent with an active drain attempt reaching exhausted or invalid pool/account states.

Financial Impact

Public reporting by PeckShield and GoPlus stated that the Raydium legacy pool exploit caused approximately $1.3M in losses. This analysis independently confirms representative drained assets in the fetched sample set: 5,602.964099112 WSOL, 83,341.713143 RAY, and 10,692.215336 SRM flowed into attacker-owned token accounts across the two primary example transactions. The total externally reported USD impact is larger than the independently priced sample here and likely includes subsequent swaps, bridging, and additional pools not fully repriced in this run. PeckShield reported that proceeds were bridged from Solana to Ethereum and that 810 ETH was deposited to Tornado Cash plus 7 ETH to FixedFloat; those downstream Ethereum movements were treated as external context rather than independently re-priced Solana pool losses.

Evidence

  • Transaction: 2gwZ1P37p3S3963YwovvzE1FF7zXY3g1Dj8BUVwzsUAQaJD2W7sxXx9geFcxaUny4tAEguzv187ha1YmE9iBZTMN on Solana, slot 425542754, status success.
  • Transaction: 9jBVvs6stJfpVan97pzkW2huUdynQ41yzWJh14w6j1jXee2MKZwPjCGQ5zHyBdSMBdHX9mbhTYzNMfD1tupbxbR on Solana, slot 425542757, status success.
  • Attacker: 4WnPebowR4HHfumvNPaDjG6Pa5Hi1jxLm6xmmBq33QVk.
  • Vulnerable contract: 27haf8L6oxUeXrHrgEgsexjSY5hbVUWEmvv9Nyxg8vQv.
  • Impacted pool/token/protocol component: Raydium legacy liquidity pools and their SPL-token vaults.
  • Key on-chain fact: In 2gwZ1P37..., the attacker-created mint GPJL2nunF9jhNQQVTvq4oL7AsvX9Jxms44rMmFWvMmXF was initialized and exactly 1 token was minted before the Raydium remove-liquidity call.
  • Key on-chain fact: The Raydium inner instructions in 2gwZ1P37... transferred 74,720.095333 RAY and 5,602.964099112 WSOL to attacker-owned token accounts while burning only 1 token of the attacker-created mint.
  • Key on-chain fact: In 9jBVvs6..., the same pattern transferred 8,621.61781 RAY and 10,692.215336 SRM to attacker-owned token accounts while burning only 1 token of another attacker-created mint.
  • Key on-chain fact: GoPlus identified 0x0EaBAAb9a56011c6158D4aA7f2E49A82fB34E609 as the Ethereum-side receiving address, and PeckShield reported downstream deposits of 810 ETH to Tornado Cash and 7 ETH to FixedFloat.

Remediation

Raydium should keep the vulnerable legacy pool path disabled or permanently retire it for affected pools. Any remove-liquidity implementation must derive the canonical LP mint and vault accounts from pool state, reject caller-supplied substitutes, and assert that the user LP token account’s mint equals the pool LP mint before any vault transfer occurs. The program should also validate pool vault ownership, vault mint identities, authority derivations, LP supply, and token-program identity before computing withdraw amounts. Regression tests should include an attacker-created mint with one token and assert that remove-liquidity fails before any vault transfer, plus account-substitution tests for every pool-critical account.