Root Cause
Limit Break's Payment Processor V2 (formerly used by Magic Eden for EVM NFT settlement) accepted a buyer address passed by an approved trusted-forwarder contract and used that address to satisfy standing setApprovalForAll checks — without verifying that the claimed buyer had authorized the current transaction.
This is a textbook EIP-2771 meta-transaction misuse: when a contract delegates caller identity to an external forwarder without requiring a per-transaction signature, any caller who can invoke the forwarder can impersonate any approved address.
// Simplified illustration of the vulnerable Payment Processor V2 pattern
// (conceptual reconstruction from public incident reporting)
contract PaymentProcessorV2 {
// Maps operator => approved-for-all
mapping(address => mapping(address => bool)) public approvals;
address public trustedForwarder; // can pass arbitrary 'buyer'
function buyItem(
address buyer, // ← supplied by caller via forwarder; NOT verified
address seller,
address nftContract,
uint256 tokenId,
uint256 price
) external {
require(msg.sender == trustedForwarder, "Not forwarder");
// Uses STANDING approval — no per-tx buyer signature check
require(approvals[buyer][address(this)], "No approval");
IERC721(nftContract).transferFrom(buyer, address(this), tokenId);
_payoutSeller(seller, price); // price could be zero
}
}
Because Payment Processor V2 is immutable (no upgrade or pause mechanism), the vulnerability cannot be fixed. All wallets that ever granted it setApprovalForAll remain permanently at risk.
Attack Steps
| Step | Description |
|---|---|
| 1 | Identified wallets with live setApprovalForAll approval to Payment Processor V2 |
| 2 | Constructed call through the trusted forwarder, supplying a victim's address as buyer |
| 3 | Payment Processor V2 found a valid standing approval and executed the NFT transfer at zero price |
| 4 | Repeated across multiple victim wallets; ~$2.8M in NFTs drained |
| 5 | Whitehats (led by Yuga Labs' 0xQuit) used the same exploit path, rescuing 23,155 NFTs (~$5.7M) into a safe holding wallet |
| 6 | Limit Break paused Payment Processor V3 on all chains except ApeChain |
Impact
- ~$2.8M in NFTs stolen from user wallets on Ethereum and other EVM chains
- 23,155 NFTs (~$5.7M) rescued by whitehats before attackers could reach them
- ~660 WETH confirmed lost according to Yuga Labs' 0xQuit
- Payment Processor V2 cannot be paused or patched (immutable contract)
- Payment Processor V3 paused on all chains except ApeChain (active through Nov 30, 2026)
- All historical Payment Processor V2 approvals remain live and exploitable
Lessons for Auditors
- Trusted-forwarder contracts must authenticate the original sender per-transaction. EIP-2771 requires the forwarder to relay a signature proving the claimed sender authorized the specific call; a contract that merely trusts whatever address the forwarder passes is fundamentally broken.
setApprovalForAllgrants are forever. Protocols using operator approvals as their authorization model must plan for what happens when the approved contract becomes vulnerable. Consider time-limited approvals or on-chain revocation mechanisms.- Immutable contracts with broad approvals are systemic risks. Any NFT settlement contract that is non-upgradeable and non-pauseable should be treated as a permanent attack surface for all wallets that have ever approved it. Revocation campaigns and user tooling (e.g., revoke.cash) are the only mitigations post-deployment.
- Establish formal whitehat/rescue channels before you need them. The $5.7M rescue was improvised. A designated rescue vault and a clear legal safe harbor would reduce friction and increase the amount recoverable in future incidents.
- Audit meta-transaction forwarder integrations separately from the core contract — the attack surface includes every contract that can call the target with a forwarded identity.