It’s Live: EIP-7702 Revoker Is Now a Public Web Service
Part 5 of the EIP-7702 Revoker series
It’s Live: EIP-7702 Revoker Is Now a Public Web Service
Part 5 of the EIP-7702 Revoker series
When I wrote Part 2 of this series, I ended with a sentence that felt more like a promise than a plan: “A web version is in development.” That was ten days ago. Today, that promise is a URL.
**eip-7702-revoker-web.vercel.app**
No install. No CLI. No private key sent anywhere. Open the link, connect your compromised wallet, and revoke — gas paid by the sponsor.
This article is the story of how that got built, what technical problems came up, and why some decisions that seemed obvious turned out to be wrong.
The original sin: assuming wallets could sign EIP-7702
When I started building the web version, my first instinct was to use wagmi’s signAuthorization. It's the natural choice — the user already has MetaMask connected, why not use it?
The answer: almost no wallet extension supports wallet_signAuthorization yet. MetaMask has it behind a flag. Most others silently fail or throw a cryptic error. The user is left staring at a rejected transaction with no explanation.
The solution felt uncomfortable at first: ask the user to paste their private key into a browser field.
I know how that sounds. So I spent a lot of time getting the security story right before shipping it.
Here’s what actually happens when you type your key:
Browser only
─────────────────────────────────────────
1. privateKeyToAccount(key) → derives address for verification
2. walletClient.signAuthorization() → produces {r, s, yParity}
3. setPrivateKey('') → key wiped from React state
4. fetch('/api/revoke', { body: { r, s, yParity, chainId, nonce } })
─────────────────────────────────────────
The key never touches the network.
The server receives a cryptographic tuple. It cannot reconstruct the private key from it. It cannot sign anything else with it. The authorization is bound to a specific address, chain, and nonce — replay on another chain or with a different nonce is impossible.
The gas problem, solved properly
The whole point of this project is that a compromised wallet has no ETH. Gas has to come from somewhere else.
The architecture is simple: a server-side sponsor wallet pays for every transaction. The user signs the EIP-7702 authorization locally; the server wraps it in a Type-4 transaction, attaches the sponsor’s funds, and broadcasts.
But “simple” hides a few edge cases.
Problem 1: gas estimation. Early versions used raw eth_estimateGas fetch calls. They were fragile — different RPCs serialize the authorizationList differently, some ignored it entirely, others returned wildly wrong numbers. Switching to viem's publicClient.estimateGas() with the full authorization object fixed this. Chain-specific fallbacks handle the cases where estimation still fails.
Problem 2: nonce collisions. When revoking across multiple chains in sequence (“Revoke All”), the sponsor wallet can end up with pending transactions that shift its nonce. The fix is a retry loop — up to 3 attempts with a short backoff, re-fetching the nonce each time:
for (let attempt = 1; attempt <= 3; attempt++) {
try {
txHash = await walletClient.sendTransaction({ ... });
break;
} catch (e) {
if (isNonceError(e) && attempt < 3) {
await sleep(500 * attempt);
continue;
}
throw e;
}
}
Problem 3: sponsor balance. If the sponsor wallet runs dry on a given chain, the transaction fails with a confusing RPC error. The server now checks balance before sending and returns a human-readable message: “Sponsor wallet is low on funds on Base. Has: 0.000012 ETH, need: ~0.000089 ETH.” And the UI surfaces the sponsor’s address directly in the error so anyone can top it up.
The security layer I almost skipped
Rate limiting and signature verification felt like overkill for a personal tool. They’re not overkill for a public service.
Without rate limiting, anyone can hit /api/revoke in a loop and drain the sponsor wallet in minutes. The implementation is a simple in-memory Map keyed by IP — 10 requests per minute per address. Not bulletproof (Vercel serverless instances don't share memory), but enough to make abuse meaningfully harder.
Signature verification is more important. Before sending any transaction, the server recovers the signer from {r, s, yParity} and checks it matches the claimed address:
const recovered = await recoverAuthorizationAddress({ authorization });
if (recovered.toLowerCase() !== body.address.toLowerCase()) {
return NextResponse.json({ error: 'Signature verification failed' }, { status: 400 });
}
This means a bad actor can’t craft a fake authorization for an address they don’t control and use the sponsor’s gas to mess with arbitrary accounts.
18 networks, one scan
The delegation scan hits all 18 supported networks in parallel. Each request goes to /api/delegation?address=...&chainId=..., which reads the account's bytecode and checks for the 0xEF0100 prefix — the EIP-7702 delegation marker. The remaining 20 bytes are the delegated contract address.
Most networks respond in under a second. Berachain occasionally times out (its RPC is slow). zkSync Era has a non-standard VM that may not fully support EIP-7702 — delegations there are flagged with a warning. Any network that errors gets a retry button instead of a silent grey dot.
What it looks like to use it
- Open the app. Connect the compromised wallet — read-only, just for the address.
- Watch the scan complete. Yellow dots are active delegations; green is clean.
- Paste the private key. The derived address appears for verification. The key field is
type="password". - Click “Revoke” next to a network, or “Revoke All” to clear everything sequentially. A progress bar shows which chain is being processed.
- Get an explorer link. Done.
The whole flow takes under two minutes if you know what you’re doing. The sponsor pays the gas. You pay nothing.
The sponsor model
Running a public service means someone has to fund the gas. Right now, that’s me.
The sponsor wallet is 0x3F7Bd7b07A47071D824795F9CB2AcB28395056dA. It needs small amounts of native token on each chain — a few cents on L2s, a dollar or two on Ethereum mainnet per transaction.
If this tool helped you, consider sending a small amount to the sponsor address on any network. If you see a transaction failing with an “insufficient funds” error, that’s the signal that a particular chain needs topping up.
This isn’t a business. It’s infrastructure for a problem that shouldn’t be someone’s problem.
What’s next
The current version handles the core case well. A few things are still on the list:
Hardware wallet support. Ledger and Trezor can’t sign EIP-7702 authorizations through their current firmware. When they can, the private key input goes away entirely.
Better error messages. Some RPC errors are still surfaced as raw strings. They should be translated into something actionable.
More networks. EIP-7702 adoption is spreading. As new chains enable it, they’ll be added.
The full series
- Part 1 — Your Wallet Was Hacked. Now You Can’t Even Afford to Fix It.
- Part 2 — From CLI to Web: Building a Sponsored EIP-7702 Revocation Service
- Part 3 — How EIP-7702 Revoker Compares to Existing Tools
- Part 4 — The Browser Extension That Fixes What dApps Can’t
- Part 5 — It’s Live ← you are here
GitHub: github.com/Serge693/EIP7702-revoker-WEB Live: eip-7702-revoker-web.vercel.app Telegram: @Sergio6967
메타데이터
- post_id
- 579472f8f034
- slug
- its-live-eip-7702-revoker-is-now-a-public-web-service-579472f8f034
- url
- https://medium.com/@skartanenkov/its-live-eip-7702-revoker-is-now-a-public-web-service-579472f8f034
- canonical_url
- https://medium.com/@skartanenkov/its-live-eip-7702-revoker-is-now-a-public-web-service-579472f8f034
- author_url
- https://medium.com/@skartanenkov
- status
- ok
- fetched_at
- 2026-07-21 08:44:55