TL;DR
At 13:07:59 UTC on September 24, 2026, an attacker deployed a contract that took 50 Otherdeeds, 10 Meebits and 10 World of Women NFTs out of one wallet. The whitehat 0xQuit's technical breakdown shows they were bought for 0 WETH through Limit Break's Payment Processor V2, a settlement contract that trusted an ERC-2771 "original sender" passed in by a forwarder, and traces the forgery to a calldata offset of zero that shifted a victim's address into the slot where the real caller belonged. Anyone who had approved V2 was exposed, and the best-known source of those approvals was Magic Eden's EVM marketplace, which settled trades through V2 from about February to October 2024 and had been closed for months. V2 could not be paused, so 0xQuit and a few helpers moved 23,155 NFTs worth more than $5.7 million into custody before attackers reached them, and lost the race for the WETH. Revoke.cash puts total losses at $2.8 million across chains. Limit Break has not published a technical postmortem.
What Happened to Payment Processor V2?
Payment Processor is Limit Break's NFT settlement protocol. Marketplaces route trades through it, and users grant it setApprovalForAll on their collections and an ERC-20 allowance on WETH so it can move assets when a trade settles. Magic Eden used V2 for its EVM trades in 2024, stopped in October 2024, and ended its EVM marketplace in Q1 2026, according to its interim update. CryptoSlate dates that closure to March 9, 2026.
Closing a marketplace does not touch the chain. Every approval given to V2 stayed live, and so did every bug in it.
The first exploit transaction is on-chain and unambiguous. The deployer 0x0e9E…d1c7 created an attack contract, and inside that single creation transaction 70 NFTs moved from one victim wallet to the deployer, at a gas cost of about 0.0046 ETH. 0xQuit's first public account lists the same haul plus 235 Desperate Apewives taken in follow-up transactions.
Nobody noticed for half a day. "It wasn't until over 12 hours later that somebody reported it to me," 0xQuit wrote. The exploit sat in plain view on a contract its best-known integrator had retired almost two years earlier.
How Did a Forwarder Let Attackers Trade as Someone Else?
ERC-2771 is the meta-transaction standard. A trusted forwarder calls the target contract and appends the original sender's address as the last 20 bytes of calldata. The target checks that msg.sender is a forwarder it trusts, then reads those 20 bytes as the real caller. Limit Break's version does exactly that in TrustedForwarderERC2771Context. The whole scheme rests on one assumption: the last 20 bytes the contract reads are the 20 bytes the forwarder wrote.
Payment Processor broke that assumption inside its own dispatcher. 0xQuit's breakdown puts it in two sentences: "The attacker supplied an unusual offset of 0 for the transaction's dynamic data. PaymentProcessor copied the data using the size of the entire call, shifting the victim's address into the final 20 bytes and discarding the real caller's address."
The pattern is visible in Limit Break's public Payment Processor repository, which carries the V3 code. That V3 shared the bug is 0xQuit's claim, and Limit Break quickly pausing V3 is consistent with it. The delegateCall modifier forwards each trade to a module:
// The magic number of 68 is:
// 4 bytes for the selector
// 32 bytes calldata offset to the data parameter
// 32 bytes for the length of the data parameter
let lengthWithAppendedCalldata := sub(calldatasize(), 68)
let ptr := mload(0x40)
mstore(ptr, selector)
calldatacopy(add(ptr,0x04), data.offset, lengthWithAppendedCalldata)The copy length is computed as if data always starts at byte 68, which is true only for the canonical ABI encoding where the offset word is 0x20. The start of the copy, though, is data.offset, which the decoder takes from the caller. Encode that offset as zero and data begins 32 bytes earlier. The copy runs for the same length, so it now stops 32 bytes short of the end of the call. The forwarder's appended address, the real caller, falls off the end. Whatever the attacker placed just before it becomes the last 20 bytes the module sees. That is our reading of the public code, and it matches 0xQuit's account step for step.
The forwarder did its job. Limit Break's TrustedForwarderFactory lets any address clone a forwarder, and every clone is marked trusted. That is safe by design, because every clone writes msg.sender honestly. The dispatcher was the component that threw the real caller away. Our trace of the first exploit transaction shows it did not even bother to clone a forwarder of its own. It routed through a forwarder clone created on February 6, 2024 (0xb233…84C9), more than two years earlier, by an address other than the attacker's deployer.
This is CWE-290, authentication bypass by spoofing, and it has a precedent. In December 2023 OpenZeppelin disclosed an arbitrary address spoofing attack in which Multicall let attackers forge the ERC-2771 suffix and drain thirdweb-based contracts. In both cases, some code between the forwarder and _msgSender() rewrote where the last 20 bytes came from.
Why Did Cancelling Listings Not Help?
Because the victim's listing was never used. With _msgSender() forged, 0xQuit explains, the attacker "created an offer for a victim's NFT at no cost, they then made the contract believe the victim was accepting that offer." The attacker wrote the entire order. The only thing taken from the victim was the standing approval to Payment Processor.
Blockaid made the same point in its early warning, relayed by TechFlow: "Simply canceling listings or using Master Nonce cannot revoke this authorization." A hardware wallet would not have helped either. The approval was signed in 2024, and the exploit never asked the victim for a new signature.
The WETH path ran the same trick in reverse. 0xQuit's rescue post says the team "later discovered that a similar exploit could be used in reverse to steal WETH." Read literally, that means the forged victim becomes the buyer instead of the seller, paying for the attacker's NFT out of a standing WETH allowance. Neither Limit Break nor 0xQuit has published that path in detail.
What Did the First Attacker Do With the NFTs?
Sold them at bid and left. Our trace of the deployer's address shows the whole operation took 43 minutes. At 12:53:23 UTC it received 0.0565 ETH from an address that Blockscout labels as both a Relay solver and a FixedFloat deposit address. Fourteen minutes later it deployed the attack contract. In all it took 305 NFTs: the 70 from the first transaction plus 235 Desperate ApeWives. Between 13:16 and 13:31 UTC it filled standing Seaport bids with 304 of them, collected about 9.3 WETH, and by 13:36 had pushed roughly 9 WETH out through Relay's bridge contracts.
Nine WETH was worth about $24,000 at the roughly $2,700 ETH price quoted in that week's coverage. The same bug reached NFTs the whitehats later valued at more than $5.7 million, plus every exposed WETH balance. Either the first attacker did not see how many wallets were exposed, or chose a small fast exit over a large slow one. The public record does not say which.
The Attack, Step by Step
| Step | When (UTC) | What happened | Source |
|---|---|---|---|
| 1 | Feb to Oct 2024 | Magic Eden settles EVM trades through V2; users approve it for NFTs and WETH | Magic Eden |
| 2 | Oct 2024 to Mar 2026 | Magic Eden stops routing to V2, then closes its EVM marketplace. Approvals stay live | Magic Eden, CryptoSlate |
| 3 | Sep 24, 13:07:59 | Attack contract deployed. Through a trusted forwarder, it accepts zero-price offers "from" one victim. 70 NFTs move | On-chain |
| 4 | Sep 25, ~01:00 | About 12 hours later, @Boomskite flags the transaction to 0xQuit | 0xQuit, CryptoTicker |
| 5 | Overnight | Limit Break confirms. V3 is paused. V2 has no pause, and attempts to brick it fail | 0xQuit |
| 6 | Sep 25, 05:46 | Rescue batches begin, highest-value assets first. Other wallets are now exploiting too | 0xQuit |
| 7 | Sep 25, 07:58 | A helper spots the ERC-20 path: 612 WETH and 8k USDC exposed | 0xQuit |
| 8 | Sep 25, 08:25 | WETH drain begins, 281.66 WETH from 25 wallets in the first wave | CryptoTicker |
| 9 | Sep 25, 08:53 | 0xQuit's monitoring shows 5 WETH left. An unfinished rescue contract grabs about 3 WETH and cannot send it out | 0xQuit |
| 10 | Sep 25, 09:06 | 0xQuit reports 23,155 NFTs rescued and 660 WETH at risk and not recovered | 0xQuit |
| 11 | Sep 25 to 26 | 0xQuit builds nftsaresafu.xyz, a claim tool on EIP-7702 and merkle proofs | 0xQuit |
0xQuit's thread gives US Eastern times, which run at UTC-4 in late September; they are converted here. The response had more moving parts than the table shows: two exploit paths, a pausable V3 next to an unpausable V2 (plus V3 on ApeChain, which 0xQuit says was temporarily unpausable too), and a rescue racing a growing crowd of copycats.
How Much Was Actually Lost?
The figures disagree because they count different things.
| Figure | Who | What it measures |
|---|---|---|
| ~$1.7M | Blockaid, early on Sep 25 | NFTs stolen in the first three transactions |
| 660 WETH | 0xQuit, Sep 25 | WETH at risk that the rescue did not recover |
| 530.7 WETH from 911 wallets | CryptoTicker, first count | WETH drained on Ethereum |
| 541.9 WETH from 970 wallets | CryptoTicker, updated Sep 29 | WETH drained on Ethereum, later count |
| $2.8M | Revoke.cash | Total losses across chains, NFTs plus tokens |
| $5.7M+ | 0xQuit, Crypto Briefing | NFTs saved, not lost |
We trust the WETH tally most. It counts WETH that actually left victims' wallets, and at 541.9 WETH on Ethereum by September 29 it sits under 0xQuit's 660 WETH, which fits 660 WETH being what was at risk in the moment rather than what was finally drained. Revoke.cash does not publish how its $2.8 million splits. CryptoTicker valued 530.7 WETH at about $1.43 million, so roughly half the total has to be NFTs and other tokens, such as the 8k USDC 0xQuit flagged. Treat it as the best available all-in estimate, not an audited loss.
Ignore one number as a loss figure: CryptoTicker's 121,595 zero-price NFT transfers from 14,835 wallets. The whitehat rescue also used zero-price transfers (3,832 of them in the first batch Revoke.cash flagged), so that count mixes theft and rescue. By September 29, CryptoTicker reports, owners had reclaimed 12,781 rescued NFTs.
Nobody has publicly attributed the attack or said how the bug was found. More than one party took assets: by the time the rescue started, 0xQuit writes, "other wallets were exploiting", and 0xQuit is now in contact with several parties involved in the exploit about returning funds.
What Did the Whitehats Try First?
They tried to turn the contract off. "It could not be paused, so we looked at setting up reverting fee recipients, malformed forwarders, and anything else that could halt trading. We found no way to shut it off," 0xQuit wrote. Limit Break's position, as CryptoTicker reports it, is that "Payment Processor V2 is an immutable contract that nobody can fix," and users should revoke rather than wait.
So the only defense left was to use the bug first: exploit every exposed approval before someone hostile did, then hold the assets until owners revoked. The NFT scan meant combing "hundreds of thousands of events," and the ERC-20 rescue contract was still unfinished when the WETH drain started.
The attacker's first transaction cost about 0.0046 ETH in gas. The defense took a night of manual work by a handful of volunteers, and still lost nearly all the WETH it was racing for.
Why This Matters Beyond NFTs
An approval is a grant to a contract's bytecode. Magic Eden routed trades through V2 for about eight months, and the approvals from those months sat live for almost two years after it stopped. Closing the marketplace did nothing to narrow what V2 could do with a 2024 setApprovalForAll, and with no pause, nothing could.
The same structure sat under the dangling approval that sank jaredfromsubway's MEV bot and the delegated module permissions behind the SquidRouterModule drain: a standing permission whose blast radius is set by the worst bug anyone ever finds in the holder.
Two years of clean settlement never exercised the offset-zero path, because no honest encoder sends one.
What Should Operators Do?
-
Treat deprecation as an approvals event. When you stop routing to a settlement contract, publish the address, tell users to revoke, and keep saying it. Magic Eden's interim update gave those instructions after the drain began, almost two years after it stopped routing to V2.
-
Ship a pause, or ship an approval boundary you can close. An immutable contract that holds operator approvals over user assets needs a way to stop settlement. V3 had a pause and Limit Break used it as soon as the bug was confirmed. V2 had none, and a handful of volunteers spent a night exploiting it defensively instead.
-
Never derive the ERC-2771 suffix position from an assumed ABI layout. Read the sender from the actual end of
msg.data, or reject non-canonical offsets before you copy. Fuzz the dispatcher with zero, overlapping and out-of-range offsets, and assume any caller can reach it through a permissionless forwarder factory. -
Watch your retired contracts. A contract you no longer route to should be quiet. Settlement activity on it, and especially zero-price fills, is a signal by definition.
-
Pre-build rescue tooling. The WETH race was lost while a rescue contract was still being written. Keep a tested sweep contract for each approval type your protocol holds.
-
Users: revoke, then reclaim. Revoke V2 on Ethereum, Polygon and Base and V3 on ApeChain via Revoke.cash, then use the claim site. Revoking does not return assets that already moved.
Would Monitoring or an Audit Have Caught This?
The dispatcher bug was a code defect in hand-written assembly. A review that fuzzes calldata encoding at the ERC-2771 boundary is the control that finds it before deploy. That is the work our smart contract audits do on any contract that parses its own calldata, and it is the only control that would have stopped the first transaction.
The first transaction was atomic and V2 could not be paused, so no alert could have saved that wallet. The time after it is where alerting pays. At 13:07:59 UTC on September 24, one transaction moved 70 NFTs (50 Otherdeeds, 10 Meebits, 10 World of Women) out of a single wallet at a price of 0 WETH, on a contract Magic Eden had stopped routing to in October 2024. That one transaction is enough to fire a rule. Tripwire watches contracts that hold user approvals for exactly these events: settlement on a contract its integrator has retired, and blue-chip NFTs filled at a price of zero. The rescue started at 05:46 UTC the next morning, about 16 and a half hours later. The WETH path was spotted at 07:58 UTC and the drain began at 08:25. An alert on the 13:07 transaction puts the problem in front of a responder about 12 hours before the first human flag did, with the WETH drain still 19 hours away.
Frequently Asked Questions
Was Magic Eden hacked?
No. The exploited contract was Limit Break's Payment Processor V2, which Magic Eden used to settle EVM trades in 2024, and Magic Eden says no live listings were affected. The exposure came from approvals users granted to V2 while trading on Magic Eden at the time.
Does cancelling my old listings protect me?
No. The attacker wrote the whole order and only needed your standing approval to Payment Processor. The fix is to revoke approvals to V2 (0x9A1D00bEd7CD04BCDA516d721A596eb22Aac6834) on Ethereum, Polygon and Base, and to V3 (0x9a1D00000000fC540e2000560054812452eB5366) on ApeChain.
Can I get my NFTs back?
Yes, if the whitehat rescue moved them: owners reclaim them on nftsaresafu.xyz after revoking the exploitable approval. Assets that reached attacker wallets are not covered, though 0xQuit says they are in contact with several parties about returns.
Is this the same bug as the 2023 ERC-2771 Multicall issue?
No. The class is the same, a forged ERC-2771 sender, but the component differs. In 2023 Multicall rewrote the suffix. Here, by 0xQuit's account and the public code, Payment Processor's own dispatcher computed a copy length from a fixed offset and let a non-canonical encoding push the real caller off the end.
Sources / References
- Etherscan: first exploit transaction, Sep 24, 2026
- Etherscan: first attacker's deployer address 0x0e9E…d1c7
- Etherscan: Limit Break Payment Processor V2
- Etherscan: cloneTrustedForwarder transaction creating forwarder 0xb233…84C9, Feb 6, 2024
- 0xQuit on X: technical breakdown of the PaymentProcessorV2 exploit and rescue
- 0xQuit on X: rescue summary, Sep 25
- Magic Eden on X: interim update on Payment Processor V2
- Revoke.cash: 2026 Magic Eden / Limit Break Hack: Check If You're Affected
- CryptoTicker: Magic Eden and Limit Break exploit: WETH and NFTs drained
- The Crypto Times: Limit Break NFT Exploit: Yuga Labs' Quit Rescues 23,155 NFTs, Says 660 WETH Lost
- Crypto Briefing: Whitehats rescue $5.7 million in NFTs after Limit Break Payment Processor exploit
- CryptoSlate: Old Magic Eden NFT approvals put users at risk after whitehat moves 3,832 NFTs
- TechFlow: Blockaid: Limit Break Faces Sustained Attacks, Approximately $1.7 Million in NFTs Stolen
- GitHub: limitbreakinc/payment-processor, PaymentProcessor.sol (delegateCall modifier)
- GitHub: limitbreakinc/TrustedForwarder, TrustedForwarderERC2771Context.sol
- GitHub: limitbreakinc/TrustedForwarder, TrustedForwarderFactory.sol
- EIP-2771: Secure Protocol for Native Meta Transactions
- OpenZeppelin: Arbitrary Address Spoofing Attack: ERC2771Context Multicall Public Disclosure
- MITRE CWE-290: Authentication Bypass by Spoofing



