# The Great NFT Heist: How We Recovered 37 NFTs From a Compromised Wallet Using EIP-7702

*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...98bE4`
    
* **37 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:

1. **Fake dapp or airdrop site** prompts the user to "verify wallet" or "claim rewards"
    
2. **User signs a message** thinking it's just authentication
    
3. **That message is actually an EIP-7702 authorization** delegating their wallet to the attacker's contract
    
4. **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:

1. **Create a fresh Safe Wallet** to act as the gas sponsor and recovery destination
    
2. **Use EIP-7702 to delegate** the compromised wallet to a trusted smart contract (Coinbase Smart Wallet implementation)
    
3. **Execute NFT transfers** while the sponsor pays all gas fees
    
4. **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)` function
    
* It'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."

```typescript
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
    

```typescript
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.

```typescript
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**

1. **Never sign messages you don't understand.** If a site asks you to sign something to "verify" or "claim," be extremely suspicious.
    
2. **Use transaction simulation tools.** Extensions like Pocket Universe or Fire can show you what a signature will actually do before you sign.
    
3. **Check what you're signing.** If MetaMask shows a weird hex blob instead of readable text, that's a red flag.
    
4. **Use a hardware wallet.** But remember: it won't save you if you sign a malicious delegation. You still need to be careful.
    
5. **Consider separate wallets.** Keep high-value assets in a "cold" wallet that never interacts with random dapps.
    

### **For Developers**

1. **EIP-7702 is incredibly powerful** for recovery scenarios, sponsored transactions, and gas abstraction.
    
2. **ethers.js v6 has wonky Type 4 support.** Expect parsing errors on transaction receipts. Work around with `getTransactionReceipt()`.
    
3. **Atomic operations are key.** If delegation and execution are separate transactions, attackers can interfere between them.
    
4. **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:

1. **Look at your wallet's code on Etherscan.** If there's delegation bytecode starting with `0xef0100`, your wallet has been delegated.
    
2. **Check recent "Signed Messages"** in your wallet history. Any you don't recognize?
    
3. **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:

```typescript
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.*
