Pectra went live on 7 May. By the end of that month, most of the 7702 delegations on mainnet were not people turning their EOA into a smarter wallet. They were sweeper contracts sitting on leaked keys. By late August, phishing kits had figured out the more interesting version: you do not need the key. You need one authorization signature.
I have been sitting with this for a while because it is not the usual user signed a bad approve story. The approve is scoped. This is not. Once the delegation is written, the account’s code pointer says run that contract in my context, and it stays that way until someone signs a revoke. Incoming ETH, a token callback, a bundler, a ping from the attacker — any of those can fire the code. The victim does not have to click again.
That is the part that matters.
What 7702 changed
The Ethereum Foundation’s Pectra announcement is still the cleanest description of the intent. EIP-7702 lets an existing EOA pick up contract behaviour without moving to a new address. Batch approve-and-swap in one transaction. Let someone else pay gas. Passkeys. Spending limits. Recovery. All the things people have been promising under “account abstraction” while most users still live on a raw key.
The mechanism is a new typed transaction, type 0x04. The extra field is an authorization list. Each item is:
[chain_id, address, nonce, y_parity, r, s]
address is the already-deployed contract whose code the EOA will run. The signature is not over the outer transaction. It is over:
keccak256(0x05 || rlp([chain_id, address, nonce]))
0x05 is just a domain separator so this blob cannot be confused with some other signed message. The recovered signer is the authority — the EOA that is about to get code. That signer does not have to be tx.origin. Someone else can wrap your authorization in their own type-4 transaction and pay the gas. That is how sponsorship is supposed to work. It is also how a phishing site can collect a signature and have a bot submit it two minutes later.
When the tuple is valid, the client writes 23 bytes into the authority’s code slot:
0xef0100 || delegate
0xef is the prefix EIP-3541 already banned as normal bytecode, so nobody “accidentally” executes the designator as EVM. From then on, CALL into that EOA, or a transaction whose to is that EOA, loads the delegate and runs it in the EOA’s storage and balance. EXTCODESIZE still returns 2. EXTCODEHASH hashes the designator, not the implementation. Execution follows the pointer. Introspection does not. The spec did that on purpose so you cannot briefly pretend to have some other contract’s codehash.
Delegation is persistent. It survives the transaction that installed it. To drop it, you sign a new tuple pointing at the zero address. To change it, you sign a different target.
The EF post lists the safety checks they baked in: chain-specific by default, nonce-bound, always revocable by the key holder. All of that is real. None of it helps if the user signed the wrong address.
ethereum.org is blunt about the leftover key: delegating to a Safe does not make the account a multisig, because the original private key can still bypass whatever policy the contract thinks it has. The key is still god. The contract is extra surface, not a replacement.
Two different 7702 messes
Wintermute’s research team posted at the end of May that over 97% of EIP-7702 delegations they looked at pointed at multiple contracts running the same bytecode. Sweepers. Copy-paste. They decompiled the common variant, verified it on Etherscan as “CrimeEnjoyor,” and left a warning in the source: this thing watches for incoming ETH on a compromised address and forwards it. The operators had spent about 2.88 ETH to authorize on the order of 79,000 addresses, and a lot of those sweepers were sitting empty. Cheap to spray, not automatically a gold mine.
Wintermute’s own framing is the one that stuck. Those sweepers are what you do after you already have the private key. 7702 did not steal the key. It made draining the address cheaper than running a deposit-watching bot. That is a real problem, but it is not phishing.
The phishing version showed up in the same window.
On 23–24 May, Scam Sniffer flagged a MetaMask user who lost about $146,550 after signing what looked like a swap. The tx is public: the signature installed a delegation and, in the same batch, ran approvals and transfers. One prompt. Atomic. Inferno Drainer’s kit was in the mix. The user never sat through a normal approve flow. They signed once.
Scam Sniffer flagged the same pattern again in late August. One victim, about $1.54 million across wstETH, cbBTC, and NFTs. Kits had had three months to productize it: type-4 templates, generated calldata, simulation that shows the swap and hides the rest.
Those two incidents are the ones I care about for this piece. Not because they are the only losses — they are not — but because the user still had their key. They signed a compact tuple and handed the account’s execution to someone else.
Why a type-4 tx is easy to lie about
A normal Ethereum transaction has a to and some data. Wallets have spent years getting slightly better at decoding that. 7702 puts the important part in a side list most UIs still treat as metadata.
The authorization tuple does not say “transfer 50 ETH.” It says “from now on, my account is this other contract.” The contract’s behaviour is whatever is deployed at that address. There is no expiry, no spend cap, no allowlist of selectors in the signature. Scope lives entirely in the delegate, if the delegate bothers to implement it.
Qi, Wang, Li, Zhu, and Chen put a preprint on arXiv on 13 December (2512.12174) that is the first measurement pass I have seen that is not a Twitter thread. They pulled more than 150,000 authorization and execution events across 26,000-plus addresses and a few hundred delegator contracts. The picture matches what Wintermute saw in May, just later and wider: a small set of contract families, lots of reuse, a chunk of that reuse sitting next to known drainers. They also ran the attack in a patched Hardhat node and showed three ways the installed code actually fires.
First, the victim does something ordinary — even sending ETH to themselves to unstick a nonce. The call hits their own address, the designator redirects, fallback runs, tokens leave.
Second, the attacker calls the victim. No second signature. The account is a contract now.
Third, something in the background calls them. A router. A reward distributor. An ERC-4337 handleOps. Their paper points at a real handleOps on Plasma where the sender is not a deployed wallet. It is an EOA whose code starts 0xef0100. The EntryPoint follows the pointer. The bundler is doing its job. The victim signed weeks earlier.
That third path is the one protocol people keep missing. 7702 was explicitly designed to sit in 4337 bundles. The EF and the spec both say so. Composition is the feature. It is also a remote trigger.
One more detail from the spec that attackers already use: the client processes the authorization list before execution, and a later revert does not undo the write. You can wrap a malicious tuple in a dummy call that fails and still leave the pointer sitting in the account.
chain_id = 0 is not a small footnote
The tuple has a chain_id. Set it to the current chain and the authorization is valid only there. Set it to 0 and it is valid on every chain that implements 7702.
Wallet vendors asked for that. One signature, same delegate on every EVM, user is not clicking 12 times. The rationale section of the EIP treats it as an acceptable trade.
ethereum.org’s 7702 page is less relaxed about the consequence:
When using
chain_id=0the delegation is applied to all chain ids. Only delegate to an immutable contract (never delegate to a proxy), and only to contracts that were deployed using CREATE2 (with standard initcode — no metamorphic contracts) so the deployer can't deploy something different to the same address elsewhere. Otherwise your delegation puts your account at risk on all other EVM chains.
The arXiv group ran the obvious test. Three local chains. Same malicious delegate at the same address on each. One chain_id = 0 signature. Submit that same tuple on all three. All three accept it. Each chain has its own nonce, so “nonce binding” does not contain anything. You have not replayed a transaction. You have reused a capability.
For anyone who has ever bridged to Base or Arbitrum and used the same address — which is most people I know — that is the default layout of their life. One phishing page, every chain they exist on.
I do not think wallets should offer chain_id = 0 as a default. If a power user wants it, make them type the zero. Show the sentence: this signature works on every EVM this account exists on.
Init plus delegation in the same breath
This one is easy to get backwards, so I am going to go slowly.
A 7702 delegation does not run a constructor on the EOA. There is no initcode in the type-4 transaction. If you point at a normal smart-account implementation that expects initialize(...), that function has to be called somehow, in the EOA’s context, after the pointer exists.
If initialize is unprotected, whoever calls it first owns the account. A frontrunner watching the mempool sees your delegation, submits their own type-4 with your authorization tuple (they can, because they are not required to be the authority), and calls initialize with their own owner argument. You thought you were upgrading your wallet. You handed them the storage.
ethereum.org calls this out directly. Their suggested fixes are not “hope the bundler is honest”:
- Replace
initwithinitWithSig, so the init parameters themselves are signed by the EOA. - Or require that init only runs through the ERC-4337 EntryPoint, which at least puts it behind 4337 validation.
Alchemy’s warning on proxies is the same class of bug: an unprotected initializer on a 7702 proxy is account takeover.
Batching delegation and init in one transaction is necessary and not sufficient. Atomic with the wrong auth is still a frontrun. The signature has to commit to the init parameters.
And if you later redelegate, storage is not wiped. Slot 0 that used to be a bool is now a uint with leftover bits in it. Treat the EOA’s slots as outliving any one delegate — namespaced storage, same discipline as an upgradeable proxy.
What a wallet has to show
I have built enough wallet-adjacent UI this year to be tired of truncated addresses. 7702 makes that habit worse, because the thing you are approving is only an address.
ethereum.org says the target contract should be clearly and prominently displayed. Hardware wallets should not expose arbitrary delegation at all. The current consensus on that side is a whitelist. The known-implementations table on that page is the practical list right now — Uniswap/calibur, Alchemy modular account, Ambire, MetaMask’s delegation framework (0x63c0…32b), the EF AA team contract, Luganodes’ batch contract. All audited. If a dapp is asking you to delegate to something else, that is already the story.
A few things I think have to be on the confirm screen, not in an advanced drawer:
- The full delegate address, no truncation, with a name if and only if it is on the wallet’s own list.
- The
chain_id. If it is0, a full-screen warning, not a badge. - The nonce being bound, and whether this authorization can still be submitted if the account nonce moves.
- That this is persistent. Not “this transaction.” “This account will keep running this code until you revoke.”
- Every inner call if this is a batch. Inferno’s May victim thought they were swapping. The other legs were the attack.
- Simulation of the delegate, not just the outer
to/data. Outer fields are how you hide a type-4.
dApps should not be collecting 7702 signatures themselves. There is no standard for that, on purpose. ethereum.org tells apps to go through wallet_sendCalls (ERC-5792) and let the wallet pick a whitelisted delegate. If a website is asking you to “enable smart account” and showing a raw authorization, treat it the way you treat a random eth_sign in 2018.
MetaMask locking the default delegator to their own framework is the right instinct even if you dislike MetaMask. Arbitrary delegator from a webpage is the phishing.
What I do with this
I keep a small habit of checking whether an address I am about to send to is visually close to one I already trust. 7702 is adjacent to that, not the same bug. Poisoning tricks your eyes on the recipient. 7702 tricks your eyes on the code you attach to yourself. Same class of “the UI showed me four hex characters and I nodded.”
If you already delegated this year, look at your account’s code. If it starts 0xef0100, follow the next twenty bytes. If that contract is not on the ethereum.org list, revoke — new type-4, target address(0) — from a wallet you trust, on each chain you use. Then move funds if you have any reason to think the delegate was doing more than batching.
If you are writing a delegate: every path that can move value needs a signature from the EOA over nonce, value, gas, target, and calldata. Miss one and the spec’s own line applies — a poorly implemented delegate can give someone near-complete control of the signer’s EOA. Ambire’s ~200-line contract exists because short code is auditable code.
If you are writing a protocol: msg.sender == tx.origin is no longer “this is an EOA at the top of the stack.” A delegated account can call you and both of those addresses will match. Use a real reentrancy guard. If you need to refuse 7702 callers, check the first three bytes of msg.sender.code for 0xef0100.
Pectra did what it said it would do. The accounts got more powerful. The signature that used to mean “send this transaction” can now mean “rewrite how I execute.” Wallets that still render that as a generic confirm are the reason Inferno did not need a zero-day.
References
- Ethereum Foundation, “Pectra Mainnet Announcement,” 23 April 2025. Activation: epoch 364032, 7 May 2025, 10:05:11 UTC. https://blog.ethereum.org/2025/04/23/pectra-mainnet
- ethereum.org, “Pectra EIP-7702 guidelines.” Authorization tuple,
chain_id = 0, init frontrunning, phishing, hardware-wallet whitelist. https://ethereum.org/roadmap/pectra/7702/ - EIP-7702, “Set EOA account code.” Type
0x04, designator0xef0100, authorization processing. https://eips.ethereum.org/EIPS/eip-7702 - Wintermute, May 2025 research / CrimeEnjoyor verification; coverage in CoinDesk, 2 June 2025. https://www.coindesk.com/tech/2025/06/02/post-pectra-upgrade-malicious-ethereum-contracts-are-trying-to-drain-wallets-but-to-no-avail-wintermute
- Scam Sniffer, 23–24 May 2025, ~$146,550 Inferno Drainer 7702 batch case; SlowMist analysis of the same transaction.
- Scam Sniffer, 24 August 2025, ~$1.54M 7702 batch-phishing case (wstETH, cbBTC, NFTs).
- Minfeng Qi, Qin Wang, Runnan Li, Tianqing Zhu, Shiping Chen, “EIP-7702 Phishing Attack,” arXiv:2512.12174, 13 December 2025. https://arxiv.org/abs/2512.12174