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
- The attacker used Solana account
4WnPebowR4HHfumvNPaDjG6Pa5Hi1jxLm6xmmBq33QVkas the signer and authority for the exploit transactions. - For each targeted pool, the attacker created a fresh SPL token mint with zero decimals and attacker-controlled mint authority.
- The attacker created a token account for the fake mint and minted exactly
1token to it. - The attacker invoked Raydium legacy
RemoveLiquiditywith instruction amount1and with the fake mint/account supplied in the LP-token positions. - 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:
2gwZ1P37p3S3963YwovvzE1FF7zXY3g1Dj8BUVwzsUAQaJD2W7sxXx9geFcxaUny4tAEguzv187ha1YmE9iBZTMNon Solana, slot425542754, status success. - Transaction:
9jBVvs6stJfpVan97pzkW2huUdynQ41yzWJh14w6j1jXee2MKZwPjCGQ5zHyBdSMBdHX9mbhTYzNMfD1tupbxbRon Solana, slot425542757, 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 mintGPJL2nunF9jhNQQVTvq4oL7AsvX9Jxms44rMmFWvMmXFwas initialized and exactly1token was minted before the Raydium remove-liquidity call. - Key on-chain fact: The Raydium inner instructions in
2gwZ1P37...transferred74,720.095333 RAYand5,602.964099112 WSOLto attacker-owned token accounts while burning only1token of the attacker-created mint. - Key on-chain fact: In
9jBVvs6..., the same pattern transferred8,621.61781 RAYand10,692.215336 SRMto attacker-owned token accounts while burning only1token of another attacker-created mint. - Key on-chain fact: GoPlus identified
0x0EaBAAb9a56011c6158D4aA7f2E49A82fB34E609as the Ethereum-side receiving address, and PeckShield reported downstream deposits of810 ETHto Tornado Cash and7 ETHto 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.