A detection engine that strips scam transactions out of wallet data and flags Ethereum wallets that have been drained by bots, including attackers that aren't on any blacklist yet.
Type
Personal project
Role
Design, build & testing
When
2025 – 2026
Stack
Python · Polars · PostgreSQL · MongoDB
01 · The problem
Bad data looks like real activity
ScamFilter is a personal project built on a wallet analytics pipeline. The pipeline
pulls a wallet's full transaction history from Etherscan and turns it into metrics
such as active days, total gas spent and wallet age.
It's my own rebuild of ideas from the fraud-detection work I did as an associate
data engineer at Crimson Umbrella Technologies, written outside the job.
Raw on-chain history is noisy. Scammers send fake airdrops and "address poisoning"
transactions to millions of wallets, and those transactions show up in the victim's
history as if the victim took part. Worse, when a wallet's private key leaks,
a sweeper bot watches it and drains every deposit within seconds.
Left alone, that wallet looks busy and healthy in the metrics.
So the pipeline needed two things before calculating anything: remove transactions
that come from known scammers, and label wallets that are being drained, even when
the attacker has never been reported.
02 · Where it fits
Filter first, then measure
Transactions are loaded into PostgreSQL. For each wallet, ScamFilter reads the
history in time order and splits it into clean and flagged transactions. Flagged
transactions are recorded in their own table with a source and severity. Only the
clean set goes on to the metrics engine, which is written in Polars and merges
its results into each wallet's MongoDB document without double-counting earlier runs.
Fig. 1 — Scam transactions are removed before any metric is calculated.
03 · Known threats
Start with the easy wins
The first check is a blacklist. ScamFilter downloads and caches ScamSniffer's
public scam database, then checks the sender and the destination of every
transaction against it. Any match is removed from the clean set.
I deliberately left out a third field, contractAddress. In Etherscan's
data it is only filled in when a transaction creates a contract, not when it
calls one, and calls to a scam contract are already caught by the destination check.
Knowing exactly what each field in the source data means kept the rules simple.
A blacklist only catches attackers someone has already reported, though. A sweeper
bot can use a brand-new wallet every time.
04 · Unknown threats
Scoring the behaviour of a sweeper bot
Sweeper bots behave in a recognisable way: they empty the wallet, they do it
seconds after money arrives, and they overpay for gas to get in first. Instead of
one hard rule, I gave each of these behaviours a weight. Every outgoing transaction
gets a score, and a transaction counts as a sweep only if both of these hold:
its total score is 65 points or more, and
at least one economic signal fired, meaning the transaction
makes no financial sense for a normal user.
The second rule matters most. Plenty of honest users forward money quickly,
such as payment relays and multi-sig operators, and they rack up points for speed
alone. What they don't do is empty the whole wallet, massively overbid on gas or
pay more in fees than they send.
ID
Signal
What it looks for
Points
S1
Balance drain econ
Sends 90% or more of the wallet's running ETH balance
25
S2
High gas price econ
Gas price above the threshold for that period (see below)
20
S3
Upside-down economics econ
Pays more than 10× the transferred value in fees
20
S4
Small extraction
Sends more than 0 but less than 0.1 ETH
20
S5
Plain ETH transfer
No contract call, exactly 21,000 gas
15
S6
Timing
Sent within 30 seconds of the last deposit
30
S7
Permit2 drain econ
A signed token approval is used to take 95%+ of a token balance, with nothing given back
30
S8
Sequencer latency econ
L2 only: the drain lands within 2 positions of the deposit in the same block
25
S9
Paymaster drain econ
A sponsored ERC-4337 operation fails after burning 90%+ of its gas budget
25
S10
Mixer-funded recipient
The receiving wallet was first funded by Tornado Cash within 72 hours
15
S11
Whitehat rescue
Sent privately via Flashbots to a verified recovery contract
−40
Two transactions, side by side
These are illustrative examples of how the score and the economic gate combine.
A sweep
0.04 ETH arrives. 12 seconds later, 98% of the balance leaves as a plain transfer at 300 gwei, in 2024.
S1 · balance drain econ+25
S2 · high gas econ+20
S4 · small extraction+20
S5 · plain transfer+15
S6 · within 30 s+30
Score110
Flagged — over 100, so the wallet is marked compromised right away
A quick payment
A user receives funds and 20 seconds later sends 0.05 ETH to a friend at normal gas, keeping most of the balance.
S4 · small extraction+20
S5 · plain transfer+15
S6 · within 30 s+30
Score65
Not flagged — meets the score, but no economic signal
05 · Different chains, different rules
The same signal can mean different things
The filter covers Ethereum and three layer-2 networks: Arbitrum, Optimism and
Base. A rule that is a strong signal on one chain can be meaningless on another,
so the filter is told which chain it is scoring and adjusts.
Arbitrum processes transactions first-come, first-served.
Paying more gas doesn't get you in earlier, so the high-gas signal (S2) is switched off there.
Serious bots compete on network speed instead, which is what S8 detects.
Layer-2 fees include the cost of posting data to Ethereum.
Using the full fee made ordinary transfers look like they were overpaying, so on L2s the
upside-down-economics signal (S3) uses only the computation part of the fee.
"High gas" depends on when. During the DeFi and NFT boom
(June 2020 to October 2022), gas regularly went above 100 gwei for everyone.
The threshold is 500 gwei for that period and 100 gwei otherwise.
06 · From transactions to a verdict
Being careful about labelling a wallet
Calling a wallet compromised is a strong claim, so a single suspicious
transaction isn't enough. A wallet is marked compromised after it sends
three transactions that pass both checks. For very busy wallets, those with more than
20,000 transactions, the bar rises to one strike per 5,000 transactions. A single transaction
scoring 100 or more (90 for token transfers) skips the wait, because a score that
high leaves little doubt.
The label also says how the wallet was attacked: an ETH sweeper, a token
sweeper, a Permit2 signature drain or paymaster griefing. A wallet is never downgraded
to a milder label, and it isn't blacklisted forever. After 90 days with
no further sweeps, it moves to Healthy (Recovered).
Rescues needed special care. When a key leaks, the owner or a whitehat often
races the bot by sending funds privately through Flashbots to a recovery wallet,
and that looks a lot like a sweep. S11 takes 40 points off, but only when
both conditions hold: the transaction came through a private builder,
and it went to a verified recovery contract such as a Gnosis Safe.
An attacker who only uses a private mempool gets no discount.
07 · Designing for missing data
Signals that switch on when the data does
Etherscan's transaction lists cover S1–S6. The newer attacks behind S7–S11,
such as Permit2 signature drains, ERC-4337 paymasters, mixer funding and Flashbots
bundles, need extra fields that another part of the pipeline has to work out first.
I defined those 15 fields as a contract between the ingestion pipeline and the
filter. Every field is optional and defaults to a harmless value, so when a
field is missing its signal simply doesn't fire. The filter runs the same on plain
Etherscan data today, and picks up each advanced signal as soon as the upstream
pipeline starts providing its field. No change to the filter is needed.
08 · Automation is not malice
Keeping two questions apart
The second layer looks at a wallet's whole history in Polars and asks a different
question: is this wallet run by a script?
No sleep: activity spread almost evenly across all 24 hours of the day.
Block-rate bursts: more than half of transactions within 12 seconds (one Ethereum block) of the previous one.
Disposable wallets: over 500 transactions in its first 7 days.
Mempool sniping: more than half of 20+ transactions failed.
Airdrop spam: over 100 outgoing transactions, nearly every one to a different address.
Exchanges, bridges and market makers are automated by design and trip these flags
all the time. So this layer produces a separate automation score, and it is
never used to decide whether a wallet is compromised. Mixing the two would have
labelled every exchange hot wallet as hacked.
09 · Testing
Testing what shouldn't be flagged
The filter has 34 unit tests. Many of them check that honest behaviour
isn't flagged: a legitimate gasless swap through Permit2, a whitehat rescue,
a successful sponsored transaction, a partial Permit2 transfer, two paymaster
failures where three are required, and Ethereum behaving exactly as before when
the L2 rules were added. In a system like this, a false accusation costs more
than a missed catch, so those cases got the most tests.
Recap
Key takeaways
Hello there! Welcome to the world of ON-CHAIN DATA! My name is OAK. Let me tell you what SAGUN learned building SCAMFILTER.
Clean the data before you measure it. Scam transactions look just like real activity, and they quietly skew every metric.
A blacklist only catches attackers someone has already reported. To catch new ones, score behaviour: draining the wallet, moving seconds after a deposit, overpaying for gas.
Speed alone proves nothing. A transaction only counts as a sweep if it also makes no economic sense, and that one rule protects honest users.
The same signal can mean different things on different chains. On Arbitrum, paying more gas doesn't get you in first, so high gas stops being a clue.
Keep "is this a bot?" apart from "was this wallet drained?". Exchanges are automated, not hacked.
Design for data you don't have yet. Optional fields let new signals switch on later without touching the filter.
Test what should NOT be flagged. In fraud detection, a false accusation costs more than a missed catch. That's everything from me. Thanks for reading!
Want to talk about data pipelines or this project?