Flash Trade's $98K Loss and How Olympix Would Have Prevented It
Date: July 21, 2026
Chain: Solana
Loss: 98,000 USDC, reimbursed in full by Flash Trade and MagicBlock
Class: an account accepted as the undelegation buffer without checking it was the expected address
Flash Trade ran a Monday livestream for its community, and according to co-founder Anas Khader on Solana's Pirates Parley podcast, the person who attacked the platform in July 2026 had been in the audience and learned Flash's exact withdrawal cap there. The attack began on July 21 at 16:21 UTC, which was 00:21 on July 22 in Singapore, and at 16:24 UTC 98,000 USDC left Flash's ephemeral instance in a withdrawal no user had made.
Flash's monitoring flagged the withdrawal within minutes and the team paused the platform, and Flash and MagicBlock, the company whose rollup software Flash runs on, reimbursed every affected deposit. The withdrawal was possible because code generated by MagicBlock's #[ephemeral] macro checked that an account in the undelegation step was a signer owned by the right program, but never checked that it was the one account it was supposed to be.
A Perpetuals Exchange on a Faster Lane
Flash Trade is a perpetual futures exchange on Solana. On June 29, 2026, it took version 2 of the platform out of beta, routing trades through MagicBlock's ephemeral rollups for sub-50ms execution, as Solana Compass reported at the launch. An ephemeral rollup is a temporary execution environment that runs next to Solana. Accounts are delegated to it, it processes activity at high speed, and when the session ends the accounts are undelegated and their final state is settled back on Solana's base layer.
Undelegation is the step where that hand-back happens, and it involves a buffer, a temporary account owned by MagicBlock's delegation program that carries the rollup's final state for one delegated account. Programs that integrate MagicBlock don't write the undelegation callback themselves. They add the #[ephemeral] attribute from MagicBlock's SDK to their Anchor program, and the macro generates the instruction the delegation program calls when it hands an account back.
For Flash, the accounts that went through that path included user deposits held for trading on the rollup. Khader said on the podcast that Flash kept those deposits in isolated collateral token accounts, separate from the liquidity pools, and that the pools' value was never touched.
A Buffer That Only Had to Look Right
The undelegation callback depends on one fact: the buffer it reads from belongs to the account being undelegated. MagicBlock derives each buffer as a program-derived address, or PDA, an address computed from fixed seeds and the delegated account's own address, so there's exactly one legitimate buffer for each account.
The generated code checked two things about the buffer it received. It had to be a signer, and it had to be owned by the delegation program. According to coverage of MagicBlock's account of the incident in CoinTurk and Coin Alert News, it didn't check that the buffer's address matched the PDA derived from the expected seeds. Any account that passed the first two checks would do.
Those two checks describe a kind of account, while the missing one would have identified a specific account. A coat check that confirms a ticket was printed by the venue, without reading the number on it, will hand over any coat to anyone holding any ticket. The lesson for builders is that when an instruction trusts an account because of who owns it, it also has to confirm which account it is, and for a PDA that means re-deriving the address from its seeds and comparing.
One Fabricated Deposit
According to the CoinTurk report, the attacker submitted a fabricated account that mimicked a genuine user deposit, and within a single transaction used it as the buffer for a sibling undelegation instruction. Because the account was a signer owned by the delegation program, the generated callback accepted it, and Khader described the result on the podcast as a forged buffer passed in place of the canonical PDA. Coin Alert News reported that the fake account produced an invalid receipt, which enabled the withdrawal. The program released 98,000 USDC.
Flash's withdrawal batching and monitoring, which the team had rolled out shortly before, held the damage to that amount. In its first post at 19:27 UTC on July 21, Flash said it had detected an unauthorized withdrawal of $98,000 from its ephemeral instance, had paused trading, deposits and withdrawals, and would cover the full amount. On the podcast, the team said alerts fired and people were responding within a minute, and that it deployed a no-op instruction in about five minutes through Triton's RPC and transaction service.
Khader said the funds were later traced from Monero through ChangeNOW and across the Mayan bridge to Ethereum, and that the receiving address was flagged with centralized exchanges.
Reimbursed and Patched Within Two Days
Trading came back within a few hours, while deposits and withdrawals stayed paused about 24 hours so the team could reconcile balances first. At 14:35 UTC on July 22, Flash announced it was fully back online and that the exploited amount had been replenished, with no user affected.
MagicBlock published its own statement on July 22. It took responsibility for the issue, said its SDK had to be held to the same security standards as its smart contracts and validator, and said it had contributed to a fund that reimbursed 100% of affected deposits alongside Flash. According to the CoinTurk report, MagicBlock also reviewed other integrations that used the macro and notified affected projects. On July 23, MagicBlock released SDK version 0.16.2. Its release commit on GitHub adds a seeds constraint to the macro's generated buffer account, tying it to the delegated account, and adds a canonical-buffer check to the SDK's undelegation helpers that rejects any other address. Backpack CEO Armani Ferrante used the incident to argue for isolated custody contracts with withdrawal timelocks, and Flash posted on July 23 that it had capped its own worst case at $100,000 per hour of outflows.
How Olympix Finds an Account Checked by Kind Instead of by Address
Olympix reads a program the way an attacker does, as states, transitions and entry points, and takes in the context around the code, including the libraries a program builds on, to infer the rules the system depends on but never states. Here the state that matters is which deposit's funds a callback can release, and the undelegation callback is the entry point that releases them. The signal is an account accepted on ownership and signer checks alone, with no comparison against the address its seeds define, so the account being trusted and the account being described can come apart.
The invariant fits in one sentence: an undelegation callback should only read state from the buffer derived for the account being undelegated. In the contracts Olympix analyzes, the engine maps every path into an entry point like this one, keeps the feasible paths where a supplied account passes the owner and signer checks, and narrows to those where that account isn't the one its seeds define, since only those can break the rule. Against a callback built this way, the counterexample would contain the pieces the public reports describe: two undelegation steps in one transaction, a buffer from one accepted by the other, and a release of funds that no real deposit backed.
Olympix has flagged this class before in other codebases, where an account passed into a call was trusted without checking it was the expected one. Flash's program is Rust on Solana and the defect sat in a third-party macro rather than in Flash's own code, so the precedent here is the class of bug, an account validated by kind instead of by identity, and not a claim about running on Flash's program. For teams building on EVM, the same rule applies to any function that trusts a caller-supplied address because of who deployed or owns it, and Olympix runs automatically in development, at every merge and before release, with a generated Forge test for each counterexample it finds.
Check Which Account, Not Just What Kind
Flash's custody design, its outflow cap and its monitoring kept a missing check from becoming a much larger loss, and two teams covered every user. The check itself was one comparison, between the buffer an instruction received and the one address it should have been, and it was missing from generated code that other integrators had inherited too. Every account a program trusts by owner deserves a second question about which one it is. To see how Olympix traces the accounts and addresses your contracts trust and returns a concrete counterexample wherever that trust can be borrowed, book a technical walkthrough.
On-Chain References
Protocol: Flash Trade (V2 ephemeral instance on MagicBlock)
Chain: Solana
Date: July 21, 2026, 16:21 UTC (00:21 SGT on July 22)
Loss: 98,000 USDC, reimbursed in full
Attacker wallet: BjqHB5...nWej
Attacker program: FDPEws...P661
Flash Perpetuals program: FLASH...fgBn
Drained Flash USDC token account: 99QNie...w9HB
Exploit tx, forged undelegation (ProcessUndelegation, 16:21:20 UTC): 42x3sH...kAuq
Exploit tx, 98,000 USDC withdrawal (WithdrawalSettle, 16:24:00 UTC): 54K9we...64Zr
MagicBlock SDK 0.16.2 fix commit: 7c4d878
Sources
MagicBlock: Statement on the Flash incident
Flash Trade: Unauthorized withdrawal notice
Solana Pirates Parley: The $100K Exploit, the Attacker Who Tuned In and Six Hours to Recovery
CoinTurk: Flash Trade Exploited for $98,000 in USDC, Team Confirms Users Fully Reimbursed
Coin Alert News: Solana DEX Flash Trade loses $98K USDC in exploit
Solana Compass: Flash Trade V2 Launches Out of Beta via MagicBlock
More post-mortems
See every post-mortem →See what Olympix finds on your own code
Send us a repo that's already been audited, and we'll run Olympix at the same commit and show you what comes back.




