
If you spend enough time in Web3, you eventually see a friend get their wallet drained. The most frustrating part isn't just the loss of funds; it's the UI.
When a user encounters a malicious smart contract, standard security tools often throw hex codes at them:
โ ๏ธ Error: 0x095ea7b3...
Normal users panic, don't understand the alert, and click "Ignore" anyway. Or worse, they get "alert fatigue" and turn the tool off completely.
I wanted to solve this "False Sense of Security" problem. So, I built SafeSign Visualizerโan open-source Chrome extension that intercepts malicious transactions before MetaMask opens, and translates them into a "Visual Time-Travel UI" that anyone can understand.
In this post, I'll walk through the core architecture of how to intercept and decode Web3 scams using Plasmo and React.
๐ง The Architecture: The "Man-in-the-Middle" Shield
Chrome Extensions live in an "Isolated World." They cannot see the window.ethereum object that MetaMask injects into a webpage.
To intercept a transaction, we must inject a script into the MAIN world of the page before MetaMask loads, and overwrite the window.ethereum.request function with a Proxy.
Using Plasmo, this is incredibly clean. We create a content script that runs at document_start:
import type { PlasmoCSConfig } from "plasmo"export const config: PlasmoCSConfig = { matches: [""], run_at: "document_start", world: "MAIN" // This is the magic key}const intercept = () => { if (window.ethereum) { const originalRequest = window.ethereum.request // Overwrite the request function window.ethereum.request = async (args) => { // 1. Analyze the transaction // 2. If bad -> throw error (stops MetaMask) // 3. If good -> return originalRequest(...) } } else { setTimeout(intercept, 50) // Wait for wallet to load }}intercept()
๐ก๏ธ Decoding the Scams (Heuristics)
Once we intercept the request, we need to analyze the args.params[0].data to see what the smart contract is actually trying to do.
- The Unlimited Token Approval (ERC-20) The most common drain. A site asks you to swap, but the hidden code calls the approve function (0x095ea7b3) with the maximum possible integer (ffffffff...).
if (args.method === "eth_sendTransaction") {
const data = args.params?.[0]?.data
if (data.startsWith("0x095ea7b3")) {
// Extract the amount (last 32 bytes)
const amountHex = data.slice(74)
const maxUint = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
if (amountHex === maxUint) {
// BLOCK! Scammer is asking for infinite access.
}
}
}
- The "Invisible" Gasless Drain (EIP-2612) This is the most dangerous scam right now. Scammers use a fake "Sign-In With Ethereum" button. It costs no gas, so users think it's harmless. But hidden in the eth_signTypedData_v4 payload is a Permit signature.
By signing it, the user gives the scammer a legal off-chain permission slip to drain their USDT later.
To stop this, we parse the JSON payload of the signature request:
if (args.method === "eth_signTypedData_v4") {
const typedData = JSON.parse(args.params[1])
const isPermit = typedData?.primaryType === "Permit"
const hasPermitFields = typedData?.message?.spender && typedData?.message?.value
if (isPermit || hasPermitFields) {
// BLOCK! This is a gasless permit drain.
}
}
๐จ Visual Storytelling (The UI)
When a scam is detected, we throw an error to stop MetaMask from opening, and we send a window.postMessage to our React Content Script UI to mount a full-screen overlay.
Instead of technical jargon, we show a timeline of the consequence:
Now: You click sign.
+2 Seconds: Site gets Unlimited Access.
+10 Seconds: Wallet Drained.
This "Visual Storytelling" approach ensures that non-technical users actually understand why they shouldn't click sign, rather than just ignoring a red warning box.
๐ Open Source
I built SafeSign to be 100% free, open-source, and privacy-first. There is no backend, no tracking, and no data collection. All analysis happens locally in the browser.
If you want to look at the exact code, borrow the interception logic for your own dApp, or install it to protect your own wallet, you can find it here:
๐ป GitHub Repository
๐ Chrome Web Store
I'd love to hear your feedback on the architecture or any ideas you have for improving the heuristics!
Top comments (0)