DEV Community

Cover image for What one week of new Ethereum contracts looks like through a scam-detection lens
Sanjeev Kumar
Sanjeev Kumar

Posted on

What one week of new Ethereum contracts looks like through a scam-detection lens

I pulled every top-level contract deployed on Ethereum mainnet in the week ending 2026-04-10. There were 8,257 of them. I enriched each one with its deployed bytecode, verified Solidity source where available, and its deployer's first-tx history. Then I scored each contract against five rule families (contract name, bytecode shape, source patterns, deployer reputation, known-drainer hashes) and used Claude Opus 4.7 to write structured walkthroughs for the top scores.

192 contracts came back as high-confidence scam patterns. All 192 are one specific scam family: "FlashUSDT" and its liquidity-bot variant, a fake-Tether template used in off-chain social engineering. Manual review of a stratified sample confirmed 100% precision in the very-high score band. Cross-referencing against ScamSniffer's public blacklist found zero overlap, because public scam lists track established drainer infrastructure, not freshly-deployed scam tokens. The lead time is the story.

Method

The pipeline is four stages. Each stage writes JSON to disk so the run is resumable and the dataset is inspectable end-to-end.

Ingest. Block-by-block scan of mainnet blocks 24,795,371 to 24,845,605 (seven days). For every transaction with to == null, record the deployed contract address, deployer, gas used, and init bytecode. ~17 minutes against QuickNode at concurrency 10. The v0.1 ingest catches only top-level deploys; contracts spawned by internal CREATE and CREATE2 from factories are out of scope for this run.

Enrich. For each of the 8,257 contracts, call eth_getCode for the deployed runtime bytecode, then Etherscan's getsourcecode for the verified Solidity if it exists, and txlist against the deployer for their first-tx timestamp. Throttled to 2.5 calls per second to stay safely under Etherscan's free-tier rate cap. Per-contract files on disk make the ~75-minute run resumable.

Score. Five rule families produce findings, each tagged with a severity (blocker / warn / info) and a confidence (0.0 to 1.0). The score is a weighted sum, capped at 1.0:

  • name: contract-name pattern match against a known-scam dictionary
  • bytecode: empty runtime (deploy-and-destruct), tiny runtime, known-drainer sha256
  • source: regex over verified Solidity for blacklist, pause, owner-mint, asymmetric fees
  • deployer: fresh EOA, no prior tx, high-volume deployer
  • drainer-hash: optional bytecode-hash list (empty in v0.1)

Narrate. For the top 20 contracts by score, send the source code + heuristic findings + deployer info to Claude Opus 4.7. The model returns structured output via tool use: a plain-English summary, an attack narrative, a confidence assessment, a suggested user action, and a verdict from {matches_scam_pattern, ambiguous, likely_legitimate}. The model explains; it does not score. ~$3 total cost for the run.

Headline numbers

Metric Value
Top-level deployments in the week 8,257
Unique deployers 2,428
Verified source rate 32.1%
Contracts with empty runtime bytecode 580 (7.0%)
Total findings ~6,600
BLOCKER-severity contracts 192
Multi-blocker (score >= 0.8) 7

Score distribution:

Bucket Contracts
0.0 (no findings) 2,698 (32.7%)
0.0 - 0.2 4,441 (53.8%)
0.2 - 0.4 911 (11.0%)
0.4 - 0.6 47
0.6 - 0.8 153
0.8 - 1.0 7

The shape is what you'd expect: a long tail of low-signal contracts, a thin cluster of obvious bad ones.

The 192 are one family

Top 10 verified contract names in the dataset:

Name Count
FlashUSDT 134
ERC1967Proxy 119
MintBurnTeamToken 113
EVMToken 111
SimpleSwapToken 102
TransparentUpgradeableProxy 62
FlashUSDTLiquidityBot 58
Forwarder 41
Token 25
ProxyAdmin 23

134 FlashUSDT + 58 FlashUSDTLiquidityBot = 192. That is the entire BLOCKER tier. The other top-frequency names (ERC1967Proxy, TransparentUpgradeableProxy, ProxyAdmin) are OpenZeppelin upgrade-pattern boilerplate and largely benign; MintBurnTeamToken / EVMToken / SimpleSwapToken / Token are generic token-generator boilerplate that scores as WARN, not BLOCKER.

The FlashUSDT family was distributed across 59 unique deployer EOAs in the seven-day window. The top deployer accounts for 12 FlashUSDT deployments and zero non-scam deployments. The next four account for 11, 8, 7, and 5 respectively. This looks like a campaign with at least dozens of operators using a shared template, not one author.

Worked example: FlashUSDT

Contract: 0x07d0aacdb2603ed053f2a362c0d4ac618a46f1dc
Score: 0.90 (blocker)
Findings: name:high-confidence:FlashUSDT, deployer:fresh-eoa (deployer's first tx 0.2 days before deployment), source:owner-mint

Claude's walkthrough, verbatim:

Summary. The contract is a minimal ERC20-like token that hardcodes the name "Tether USD" and symbol "USDT" with 6 decimals, mints an initial 1,000,000 units to the deployer, and exposes an owner-only mint function that can create unlimited additional supply at any time. It is not affiliated with Tether; it is an independent token deployed by a fresh externally-owned account that simply impersonates USDT's metadata.

Attack narrative. This contract matches the well-known "Flash USDT" scam template, which is typically used in social-engineering schemes rather than as a direct on-chain drainer. The mechanism by which a user loses funds generally proceeds as follows: (1) The operator sends a victim a large balance of this token, which appears in wallets and block explorers as "Tether USD (USDT)" because the name and symbol strings are spoofed. (2) The victim is convinced the transfer is real USDT. Common pretexts include OTC trades, "test" payments, escrow proofs, job-offer signing bonuses, or "flash loan" demonstrations. (3) The victim then sends real value (real USDT on the legitimate Tether contract, ETH, goods, services, or grants approvals) to the operator in exchange. (4) Because this token lives at a different contract address than canonical USDT, it has zero market value and cannot be sold or redeemed; the victim is left holding worthless balances while the operator keeps the real assets.

Confidence assessment. Confidence that this matches the fake-USDT scam pattern is very high. The structural signals are unambiguous: the contract name is literally "FlashUSDT", the public metadata is verbatim "Tether USD"/"USDT"/6 decimals, an owner-mint function with no cap exists, the deployer EOA is hours old, and the source comment itself describes mint as "allows owner to create tokens out of thin air."

Suggested user action. Treat any balance of this token as worthless and do not send real assets, USDT, or ETH to anyone who points to it as "proof of funds" or payment. There is no need to revoke approvals; the contract cannot pull tokens from your wallet. Remove or hide the token in your wallet UI so it does not get confused with real USDT (canonical Tether on Ethereum is at 0xdAC17F958D2ee523a2206206994597C13D831ec7).

Verdict. matches_scam_pattern

Worked example: FlashUSDTLiquidityBot

Contract: 0x42ca40f4d556c50d943de4b6bf427925bba8adc4
Score: 0.76 (blocker)
Findings: name:high-confidence:FlashUSDTLiquidityBot, deployer:fresh-eoa

The companion contract to the FlashUSDT class. The narrative (in the full dataset) describes the operator simulating a "liquidity bot" that the victim is sold access to or convinced is generating real yield; it pairs with a FlashUSDT deployment to make the demo look credible. Same family, same operator profile, same deploy-time signal.

Validation

Manual stratified sample. I picked 30 random contracts (6 from each of five score bands) and opened each one on Etherscan. Verdict: 6/6 confirmed scam in the very-high band (score >= 0.8), 1/1 confirmed in the low band (the rest were unclear without further tooling), and 0 confirmed-non-scam anywhere. The "unclear" rate in the middle bands is itself a finding: a human reviewer cannot easily evaluate unverified-bytecode-only contracts without decompilation or interaction simulation. This is the gap the analyzer fills.

Band yes no unclear Precision (where decided)
very-high (>= 0.8) 6 0 0 100%
high (0.6 - 0.8) 0 0 6 n/a
medium-high (0.4 - 0.6) 0 0 6 n/a
medium (0.2 - 0.4) 0 0 6 n/a
low (0.0 - 0.2) 1 0 5 100%

ScamSniffer cross-reference. I downloaded ScamSniffer's public blacklist (2,530 addresses) and checked overlap with the 192 flagged contracts and their 59 unique deployers. Zero overlap. This is a real finding, not a bug. Public scam lists are weeks-to-months lagging indicators: they require a victim to report, a maintainer to confirm, and an entry to land. Heuristics that run at deploy time produce a lead-time signal that public lists structurally cannot match.

Limitations

The dataset and the analyzer both have honest gaps. Calling them out:

  • Factory deployments are missing. Only top-level to == null deploys are ingested. Factory routers using internal CREATE / CREATE2 are skipped. Many real scam token launches go through factories. The 8,257 number is a lower bound on actual fresh-contract activity for the week.
  • Source-only heuristics on a 32% verified rate. Two-thirds of contracts have no Solidity source on Etherscan. The source-text checks (blacklist, pause, owner-mint, asymmetric fees) are blind to those. Bytecode-only heuristics catch some of them (empty-runtime, tiny-runtime), but the long tail of unverified bytecode is the natural next analysis.
  • One scam family explains the entire BLOCKER tier. The FlashUSDT class is so common in this window that it crowds out other classes from the top scores. A different week with a different active campaign would look different. Generalizing the precision number requires multiple windows.
  • Hand-curated name patterns. The high-confidence name dictionary (FlashUSDT, FlashUSDTLiquidityBot) was seeded from a quick look at the data. It will not catch novel scam classes whose names I haven't seen yet. A pre-registered name list from public scam taxonomies would harden this.
  • No on-chain interaction analysis. The first 24 hours of interactions with a deployed contract is a strong signal (real holders vs. only the deployer; transfers in patterns consistent with bots). Pulling that requires either an indexer or expensive log scans. Out of scope for v0.1.
  • No bytecode decompilation. Runtime bytecode is hashed but not decompiled. A bytecode-level pattern matcher against well-known drainer kits (Pink Drainer, Inferno Drainer, etc.) is the right next addition. The seed hash list ships empty.

Reproducibility

The whole pipeline is one Python project. Everything ships open source under MIT.

git clone https://github.com/sanjeevkkansal/evm-deploy-watch
cd evm-deploy-watch
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,narrate]"

# Set keys in .env:
#   QUICKNODE=https://your-quicknode-mainnet-url
#   ETHERSCAN_API_KEY=...
#   ANTHROPIC_API_KEY=...   (only for the narrate phase)

# Reproduce this week's run:
evm-deploy-watch find-blocks --start 2026-04-03T00:00:00Z --end 2026-04-10T00:00:00Z
evm-deploy-watch ingest --start-block 24795371 --end-block 24845605 \
  --output data/window_2026_04_w1.json --concurrency 10
evm-deploy-watch enrich --input data/window_2026_04_w1.json \
  --contracts-dir data/contracts --deployers-dir data/deployers
evm-deploy-watch score --window data/window_2026_04_w1.json \
  --contracts-dir data/contracts --deployers-dir data/deployers \
  --output data/scored_2026_04_w1.json
evm-deploy-watch narrate --scored data/scored_2026_04_w1.json \
  --window data/window_2026_04_w1.json \
  --contracts-dir data/contracts --deployers-dir data/deployers \
  --output-dir data/narratives --n 20
Enter fullscreen mode Exit fullscreen mode

Total runtime: ~2 hours for ingest + enrich. Total cost: ~$3 for narration on Claude Opus 4.7 if you run it.

The raw dataset (window + enriched contracts + deployers + scored output + narratives) is roughly 320 MB. Source code for verified contracts is included verbatim, so antivirus tools may flag the directory once it lands. I exclude it from AV scanning locally and recommend the same.

What I'd add next

In rough order of payoff:

  1. Factory-deployment ingest. Use trace_block or a derivable equivalent to catch contracts spawned via internal CREATE / CREATE2. Probably 4-10x the deployment count, mostly weighted toward scam-factory output.
  2. Bytecode-hash matching against a real drainer-kit seed. Pull known runtime hashes from drainer-archive and ScamSniffer reports.
  3. First-24h interaction analysis. "How many distinct callers in the first day" is a much stronger legitimacy signal than name or source matching.
  4. Run on multiple weeks. Lets the precision number generalize beyond the single FlashUSDT-heavy window.
  5. Compare against Base mainnet. Same pipeline, different chain, different scam economy.

Notes

Tools used: Python 3.13, httpx, pydantic, the Anthropic Python SDK, QuickNode for RPC, Etherscan v2 for source and account lookups, Claude Opus 4.7 for narration. No paid security tools, no SaaS pipelines.

Everything is in the repo. Issues, PRs, and disagreement welcome.

Top comments (0)