The Great NFT Heist: How We Recovered 37 NFTs From a Compromised Wallet Using EIP-7702
Hey there! I'm Diluk Angelo, a Tech Lead and Web3 developer passionate about bridging the gap between traditional web solutions and the decentralized future. With years of leadership experience under my belt, I've guided teams and mentored developers in their technical journey. What really drives me is the art of transformation – taking proven Web2 solutions and reimagining them for the Web3 ecosystem while ensuring they remain scalable and efficient. Through this blog, I share practical insights from my experience in architecting decentralized solutions, leading technical teams, and navigating the exciting challenges of Web3 development. Whether you're a seasoned developer looking to pivot to Web3 or a curious mind exploring the possibilities of decentralized technology, you'll find actionable knowledge and real-world perspectives here. Expect deep dives into Web3 architecture, scalability solutions, team leadership in blockchain projects, and practical guides on transitioning from Web2 to Web3. I believe in making complex concepts accessible and sharing lessons learned from the trenches. Join me as we explore the future of the web, one block at a time!
A war story about wallet sweepers, smart contract wizardry, and the power of Ethereum's newest upgrade.
The Nightmare Begins
Picture this: you wake up, grab your coffee, check your wallet... and it's empty. Not just a bit empty. Completely drained. Your precious NFT collection? Gone in the night.
This wasn't a horror movie. This was reality for one of our team members. A malicious signature had turned their wallet into an attacker's personal ATM.
But here's where it gets interesting: the attacker didn't just drain what they could. They set up what's called a wallet sweeper. A malicious bot that watches the compromised wallet 24/7 and instantly steals any funds that land in it. Try to send gas money to rescue your remaining NFTs? Swept in milliseconds.
Game over, right?
Wrong.
The Situation
Here's what we were dealing with:
Compromised wallet:
0x2894...98bE437 NFTs stuck inside across multiple collections
$0 in the wallet (obviously)
Active sweeper bot stealing any ETH sent in
A very stressed NFT owner
The classic problem: you need gas to move the NFTs, but any gas you send gets stolen before you can use it.
Traditional solutions like Flashbots bundles work... sometimes. But we had something better up our sleeve: EIP-7702.
Wait, What's EIP-7702?
EIP-7702 landed with the Pectra upgrade in early 2025, and it's genuinely game-changing for situations like this.
In simple terms: it lets a regular wallet (EOA) temporarily become a smart contract within a single transaction. The really cool part? Someone else can pay the gas.
Think about that for a second. The compromised wallet never needs to hold any ETH. We send a transaction to the compromised wallet, temporarily upgrade it to a smart contract, execute the NFT transfers, and pay for everything from a separate "sponsor" wallet.
The sweeper bot can watch all day long. There's nothing to sweep.
The Attack: How They Got In
Before we dive into the recovery, let's understand the attack. This one was particularly sneaky.
The Malicious Signature Attack
The victim wasn't phished for their seed phrase. They didn't download malware. What happened was more subtle: they signed what looked like a harmless message on a malicious website.
Here's how it works:
Fake dapp or airdrop site prompts the user to "verify wallet" or "claim rewards"
User signs a message thinking it's just authentication
That message is actually an EIP-7702 authorization delegating their wallet to the attacker's contract
Attacker now controls the wallet through the delegated contract, can drain everything
The beauty (from the attacker's perspective) is that the victim never gave up their private key. They still have it. But the attacker doesn't need it anymore. They've got delegation rights.
Once delegated, the attacker:
Drained all ETH and liquid ERC20s immediately
Set up a sweeper bot to catch any future deposits
Left the NFTs behind (those require gas to move, and the attacker was done)
A few NFTs were transferred to other addresses before we could act. Those were losses we had to accept.
Why This Attack is Scary
Traditional wallet compromises require the attacker to have your private key. If you spot it early enough, you can sometimes "outrun" them by moving assets faster.
With signature-based delegation attacks, the attacker doesn't have your key. They have something almost as good: permanent permission to act on your wallet's behalf. And you might not even realize it happened until everything's gone.
The victim in this case still had their hardware wallet. Still had their seed phrase written down safely. None of that mattered.
The Recovery Plan
Our strategy was deceptively simple:
Create a fresh Safe Wallet to act as the gas sponsor and recovery destination
Use EIP-7702 to delegate the compromised wallet to a trusted smart contract (Coinbase Smart Wallet implementation)
Execute NFT transfers while the sponsor pays all gas fees
Do it atomically so the attacker can't interfere
Sounds straightforward. The execution was... less so.
The Technical Deep-Dive (For the Nerds)
Step 1: Understanding the Delegation Target
EIP-7702 requires you to "delegate" your EOA to a smart contract implementation. We chose Coinbase's Smart Wallet implementation (0x000100abaad02f1cfC8Bbe32bD5a564817339E72) because:
It has a clean
execute(address, uint256, bytes)functionIt's audited and battle-tested
It doesn't require initialization for basic operations
Step 2: The Authorization Signature
The compromised wallet owner still needs to sign an authorization. A permission slip saying "yes, temporarily turn me into this smart contract."
const authorization = {
chainId: 1n, // Ethereum Mainnet
address: COINBASE_SW, // What we're delegating to
nonce: walletNonce, // Current nonce
};
// Sign the authorization with the wallet's key
const payload = ethers.encodeRlp([
ethers.toBeHex(authorization.chainId),
authorization.address,
ethers.toBeHex(authorization.nonce)
]);
const digest = ethers.keccak256(ethers.concat(["0x05", payload]));
const signature = compromisedWallet.signingKey.sign(digest);
The good news: the victim still had their private key (the attacker only had delegation rights). Without the key, this recovery wouldn't be possible.
Step 3: The Atomic Recovery Transaction
Here's where the magic happens. We send a Type 4 transaction (new with EIP-7702) that includes:
The authorization (signed by compromised wallet)
The actual call we want to make (NFT transfer)
Gas paid entirely by the sponsor wallet
const tx = await sponsorWallet.sendTransaction({
to: COMPROMISED_ADDR, // Target is the compromised wallet
data: swCalldata, // The execute() call
type: 4, // EIP-7702 transaction
authorizationList: [signedAuth],
maxFeePerGas: ...,
maxPriorityFeePerGas: ...,
gasLimit: 160000,
});
The swCalldata is an execute() call that triggers transferFrom() on the NFT contract, moving the NFT from the compromised address to our Safe.
Step 4: The 50% Success Rate Problem
Our first attempts showed a frustrating pattern: about half the transactions succeeded, half reverted. What gives?
Turns out, the attacker had also been deploying EIP-7702 delegations on the compromised wallet. Pointing to their own contract. We were in a race condition.
Every transaction, there was a coin flip: did the delegation point to Coinbase's implementation (ours) or the attacker's contract?
The solution: retry failed transfers. Each run, we'd check ownership before attempting a transfer. Already recovered? Skip it. Still compromised? Try again.
for (const nftId of ALL_NFT_IDS) {
const owner = await nftContract.ownerOf(nftId);
if (owner.toLowerCase() === SAFE_ADDR.toLowerCase()) {
console.log(`NFT #${nftId}: Already recovered ✓`);
continue;
}
// ... attempt recovery
}
It wasn't elegant, but it worked.
The Results
After about 45 minutes of running recovery scripts:
| Metric | Value |
| Total NFTs targeted | 40 |
| Successfully recovered | 37 |
| Lost before recovery | 3 |
| Success rate | 92.5% |
Not bad for a wallet that was supposedly "lost forever."
Cost Analysis
The entire recovery cost about 0.03 ETH in gas (around $75 at the time). The NFT collection was worth considerably more than that.
EIP-7702 transactions are more expensive than regular transfers because they include delegation data, but when the alternative is losing everything, it's a bargain.
Lessons Learned
For Users
Never sign messages you don't understand. If a site asks you to sign something to "verify" or "claim," be extremely suspicious.
Use transaction simulation tools. Extensions like Pocket Universe or Fire can show you what a signature will actually do before you sign.
Check what you're signing. If MetaMask shows a weird hex blob instead of readable text, that's a red flag.
Use a hardware wallet. But remember: it won't save you if you sign a malicious delegation. You still need to be careful.
Consider separate wallets. Keep high-value assets in a "cold" wallet that never interacts with random dapps.
For Developers
EIP-7702 is incredibly powerful for recovery scenarios, sponsored transactions, and gas abstraction.
ethers.js v6 has wonky Type 4 support. Expect parsing errors on transaction receipts. Work around with
getTransactionReceipt().Atomic operations are key. If delegation and execution are separate transactions, attackers can interfere between them.
Build retry loops. Network conditions and nonce races mean some transactions will fail. Handle it gracefully.
How to Check if Your Wallet is Compromised
Worried this happened to you? Here's how to check:
Look at your wallet's code on Etherscan. If there's delegation bytecode starting with
0xef0100, your wallet has been delegated.Check recent "Signed Messages" in your wallet history. Any you don't recognize?
Use a fresh wallet for valuable assets. If you're unsure, just move everything to a new address you've never connected anywhere.
The Sweeper Didn't Win
The wallet sweeper was sophisticated. It had probably drained hundreds of wallets before ours. But it had one fundamental weakness: it assumed the only way to move assets was with gas in the compromised wallet.
EIP-7702 shattered that assumption.
The attacker's bot sat there, watching and waiting for ETH that never came, while we extracted 37 NFTs right under its nose.
In the eternal cat-and-mouse game of blockchain security, this round goes to the good guys.
Recovery Script Quick Reference
For anyone facing a similar situation, here's the minimal working recovery code:
import { ethers } from "ethers";
async function recoverNFT(
provider: ethers.JsonRpcProvider,
compromisedWallet: ethers.Wallet,
sponsorWallet: ethers.Wallet,
nftContract: string,
tokenId: number,
destination: string
) {
const COINBASE_SW = "0x000100abaad02f1cfC8Bbe32bD5a564817339E72";
// Create authorization
const nonce = await provider.getTransactionCount(compromisedWallet.address);
const chainId = (await provider.getNetwork()).chainId;
const authorization = { chainId, address: COINBASE_SW, nonce };
const payload = ethers.encodeRlp([
ethers.toBeHex(chainId),
COINBASE_SW,
ethers.toBeHex(nonce)
]);
const digest = ethers.keccak256(ethers.concat(["0x05", payload]));
const sig = compromisedWallet.signingKey.sign(digest);
// Encode transfer call
const nftIface = new ethers.Interface(["function transferFrom(address,address,uint256)"]);
const swIface = new ethers.Interface(["function execute(address,uint256,bytes)"]);
const transferData = nftIface.encodeFunctionData("transferFrom", [
compromisedWallet.address, destination, tokenId
]);
const executeData = swIface.encodeFunctionData("execute", [nftContract, 0n, transferData]);
// Send EIP-7702 transaction
const tx = await sponsorWallet.sendTransaction({
to: compromisedWallet.address,
data: executeData,
type: 4,
authorizationList: [{
...authorization,
signature: { v: sig.v, r: sig.r, s: sig.s, yParity: sig.yParity }
}],
gasLimit: 160000,
});
return tx;
}
Use at your own risk. Always test on testnets first.
Final Thoughts
Signature-based attacks are the new frontier of wallet compromises. They're subtle, they're hard to detect, and they leave victims confused about what even happened.
But Ethereum keeps evolving. EIP-7702 gave us a new tool to fight back against sweepers and drain attacks. It won't save everyone, but for those with NFTs or tokens stuck in compromised wallets, there's now hope.
Stay safe out there. Think before you sign. And if the worst happens, remember: the game might not be over yet.
Questions about your own recovery situation? Drop us a line. We've been through the trenches and are happy to help.



