<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bryan MARTIN</title>
    <description>The latest articles on DEV Community by Bryan MARTIN (@mik3fly__).</description>
    <link>https://dev.to/mik3fly__</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3974308%2Fe5789af9-845a-421d-b21e-cfe936af473f.jpg</url>
      <title>DEV Community: Bryan MARTIN</title>
      <link>https://dev.to/mik3fly__</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mik3fly__"/>
    <language>en</language>
    <item>
      <title>We got scraped, so we built a free Ethereum scam API</title>
      <dc:creator>Bryan MARTIN</dc:creator>
      <pubDate>Mon, 29 Jun 2026 15:06:30 +0000</pubDate>
      <link>https://dev.to/mik3fly__/we-got-scraped-so-we-built-a-free-ethereum-scam-api-1nl1</link>
      <guid>https://dev.to/mik3fly__/we-got-scraped-so-we-built-a-free-ethereum-scam-api-1nl1</guid>
      <description>&lt;p&gt;&lt;em&gt;By &lt;a href="https://x.com/mik3fly__" rel="noopener noreferrer"&gt;Bryan Martin&lt;/a&gt; - founder of RektRadar. Ethereum scam-detection infrastructure since 2024. &lt;a href="https://github.com/mik3fly" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; - &lt;a href="https://www.linkedin.com/in/bryan-martin-0560b069/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A few weeks ago a single machine on Alibaba Cloud (autonomous system AS45102) found our public &lt;code&gt;/v1&lt;/code&gt; endpoints and decided to take everything. Over three sessions it fired &lt;strong&gt;1,911 requests&lt;/strong&gt;, most of them automated fuzzing: random hex strings where a contract address should be, malformed query params, the usual "let's see what this thing returns" sweep. Our per-IP rate limiter answered &lt;strong&gt;96% of those calls with HTTP 429&lt;/strong&gt;. Fewer than 1 in 20 got data back.&lt;/p&gt;

&lt;p&gt;The easy move is to block the IP and move on. We did block it. But the incident made an obvious point: people clearly want programmatic access to our scam data, and until now the only "API" was whatever public endpoints they reverse-engineered from the app. So we did the other thing too. We documented the surface, put real rate limits and tiers behind it, and shipped it as a proper free API.&lt;/p&gt;

&lt;p&gt;This post is the data behind that decision: what the scraper actually did, what the dataset it was reaching for contains, and how the free tier is designed so that abuse like this stays cheap to absorb while honest developers get a useful amount of access for nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dataset snapshot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Snapshot:&lt;/strong&gt; 2026-06-29, 13:44 UTC. Counts are straight &lt;code&gt;SELECT&lt;/code&gt;s against our &lt;code&gt;token_analysis&lt;/code&gt; table on the production database. No estimates.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;N_total analyzed Ethereum tokens: &lt;strong&gt;103,954&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;N flagged scam (&lt;code&gt;risk_score &amp;gt;= 70&lt;/code&gt;): &lt;strong&gt;60,328&lt;/strong&gt; (58.0%)&lt;/li&gt;
&lt;li&gt;N high-confidence (&lt;code&gt;risk_score &amp;gt;= 90&lt;/code&gt;): &lt;strong&gt;3,611&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Analyzed in the last 7 days: &lt;strong&gt;3,529&lt;/strong&gt;, of which &lt;strong&gt;2,870 (81.3%)&lt;/strong&gt; scored as scams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two numbers stand out. First, &lt;strong&gt;58% of every token we have ever fully scored is a scam&lt;/strong&gt; by our 70-or-above threshold. Second, on fresh launches the rate is far worse: &lt;strong&gt;81% of the tokens analyzed in the past week&lt;/strong&gt; crossed the scam line. New ERC-20s on Ethereum skew overwhelmingly toward fraud, and that skew is exactly what the free API exposes to anyone who wants to check an address before they touch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The scraper that started this
&lt;/h2&gt;

&lt;p&gt;Here is what 1,911 calls from one IP looks like in aggregate:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Total requests&lt;/td&gt;
&lt;td&gt;1,911&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distinct sessions&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP 429 (rate-limited)&lt;/td&gt;
&lt;td&gt;~96%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Successful data responses&lt;/td&gt;
&lt;td&gt;~4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source&lt;/td&gt;
&lt;td&gt;one IP, Alibaba Cloud AS45102&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern was not a careful integration. It was a fuzzer: garbage in the address slot, repeated hammering of the same feed endpoints, no backoff when the 429s started. That is the signature of someone trying to scrape a dataset wholesale rather than look up a token they actually care about.&lt;/p&gt;

&lt;p&gt;The rate limiter held. But "the limiter held" is not a product. A developer who genuinely wants our risk score for one contract should not have to guess at undocumented endpoints and get throttled alongside a fuzzer. So the limits got formalized into tiers, and the endpoints got a docs page at &lt;a href="https://rektradar.io/developers/" rel="noopener noreferrer"&gt;rektradar.io/developers&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tiers: anonymous, free key, paid
&lt;/h2&gt;

&lt;p&gt;Three levels of access, and you can start at the bottom with nothing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Access&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;th&gt;Monthly quota&lt;/th&gt;
&lt;th&gt;Data freshness&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Anonymous (no key)&lt;/td&gt;
&lt;td&gt;10 req/min&lt;/td&gt;
&lt;td&gt;best-effort&lt;/td&gt;
&lt;td&gt;feeds delayed ~10 min, token lookups real-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free key (one email)&lt;/td&gt;
&lt;td&gt;40 req/min&lt;/td&gt;
&lt;td&gt;10,000 calls&lt;/td&gt;
&lt;td&gt;feeds delayed ~10 min, token lookups real-time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid (from 19.99/mo)&lt;/td&gt;
&lt;td&gt;up to 300 req/min&lt;/td&gt;
&lt;td&gt;50k to 1M&lt;/td&gt;
&lt;td&gt;real-time everywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Anonymous is deliberately strict at &lt;strong&gt;10 requests per minute&lt;/strong&gt;: enough to look up the token you are about to ape into, nowhere near enough to mirror our database. That is the level the scraper was hitting, and 10 req/min is why 96% of its calls bounced.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;free email-verified key lifts you to 40 req/min and 10,000 calls per month&lt;/strong&gt;. That is a real allowance, not a teaser. It comfortably covers a Telegram bot for a small group, a personal dashboard, or a hobby project that checks every new pair in a feed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The delay is the paywall
&lt;/h2&gt;

&lt;p&gt;We thought hard about how to keep a free tier genuinely useful without giving away the part that costs us the most to produce: real-time intelligence. The answer is freshness, not feature-gating.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Targeted token lookups are real-time for everyone.&lt;/strong&gt; &lt;code&gt;GET /v1/token/:address&lt;/code&gt; and &lt;code&gt;/v1/token/:address/full&lt;/code&gt; return the current score and flags with no delay, on anonymous and free keys alike. If you have a specific contract in hand, you get the live verdict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The activity feeds are delayed on free.&lt;/strong&gt; &lt;code&gt;GET /v1/rugs&lt;/code&gt;, &lt;code&gt;/v1/recent&lt;/code&gt;, and &lt;code&gt;/v1/trends&lt;/code&gt; run roughly 10 minutes behind on anonymous and free access. You can build a "recent scams" or "fresh rugs" view for free; you just see it 10 minutes after a paid key does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Paid removes the delay everywhere&lt;/strong&gt;, adds WebSocket streams and signed webhooks, and pushes the monthly quota from 50,000 up to 1,000,000 calls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The logic is simple. The value of a rug-pull alert decays by the minute. Charging for the 10 minutes that matter is honest pricing, and it means the free tier is never crippled, only slightly behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Passwordless: a key in one email
&lt;/h2&gt;

&lt;p&gt;Getting a key takes one field. You enter an email at &lt;a href="https://app.rektradar.io/api-key" rel="noopener noreferrer"&gt;app.rektradar.io/api-key&lt;/a&gt;, we send a magic link, you click it, the key appears. &lt;strong&gt;No password to create, no card to enter, no sales call.&lt;/strong&gt; The whole point is that the friction of getting a key should be lower than the friction of writing a scraper, so people use the front door.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anti-abuse that does not punish developers
&lt;/h2&gt;

&lt;p&gt;The scraper incident shaped the abuse controls, and the design rule was: stop wholesale mirroring without adding friction for a normal integrator.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-IP key-creation cap of 3 keys per day.&lt;/strong&gt; You cannot spin up a hundred free keys from one box to multiply your quota. One developer, a handful of keys, is fine; a key farm is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Disposable-email block.&lt;/strong&gt; Throwaway inbox domains are rejected at signup, so the "one email = 10,000 calls" math cannot be gamed with an infinite supply of burner addresses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional per-key IP allowlist, tier-scaled.&lt;/strong&gt; You can pin a key to the IPs that are allowed to use it. If a key leaks, it is useless from anywhere you did not authorize. Higher tiers get more allowlist entries.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these touch the happy path. A developer who signs up with a real email and calls from their server never sees any of it. They exist to make the scraper's economics worse, not the integrator's.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you can actually query
&lt;/h2&gt;

&lt;p&gt;The free API exposes the same dataset the scraper was reaching for, the 103,954 analyzed tokens in the snapshot above, through a small set of documented endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/token/:address&lt;/code&gt; returns a 0-100 risk score and the on-chain red-flag list (honeypot simulation result, ownership state, liquidity, deployer reputation, and more).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/token/:address/full&lt;/code&gt; adds liquidity and holder distribution.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/rugs?since=14d&lt;/code&gt; lists recent rug pulls.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/recent&lt;/code&gt; is the live analysis feed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/deployers/top&lt;/code&gt; is the leaderboard of the wallets that ship the most scams.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /v1/stats&lt;/code&gt; returns the platform-wide counters (tokens scanned, scams detected, deployers mapped).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also an official TypeScript SDK if you would rather not write the HTTP plumbing yourself. With a free key, every one of those is callable today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of our data
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scorer-conditional, not isolated truth.&lt;/strong&gt; "58% are scams" means 58% of tokens scored 70-or-above on our multi-signal scale. That is our classifier's judgment, not a court verdict. The threshold is a product choice; move it and the percentage moves.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selection bias in what gets analyzed.&lt;/strong&gt; A contract enters &lt;code&gt;token_analysis&lt;/code&gt; only when our mempool-watcher or factory-watcher sees it deploy with enough liquidity to matter. Tokens launched through obscure paths, or with no real pool, are under-represented. The dataset describes the tradeable long tail of Ethereum, not literally every contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A moving target.&lt;/strong&gt; The 81% scam rate on last-week launches is a 7-day window ending 2026-06-29. Scam techniques drift, campaigns spike and fade, and these numbers will read differently next month. Treat them as a snapshot, not a constant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate-limit counts are approximate.&lt;/strong&gt; The 1,911 calls and 96% 429 figures come from our nginx access logs for one source IP across three observed sessions. Session boundaries are inferred from gaps in activity, so "3 sessions" is a reasonable grouping, not a hard count.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A single Alibaba Cloud IP fuzzed our public API &lt;strong&gt;1,911 times across 3 sessions; 96% were rate-limited (HTTP 429)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Instead of only blocking it, we shipped a documented free Ethereum scam-detection API over the same dataset: &lt;strong&gt;103,954 analyzed tokens, 60,328 (58%) flagged scam&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Three tiers: &lt;strong&gt;anonymous 10 req/min&lt;/strong&gt; (no signup), &lt;strong&gt;free key 40 req/min + 10,000 calls/month&lt;/strong&gt; (one email, magic link, no card), &lt;strong&gt;paid real-time&lt;/strong&gt; from 19.99/mo.&lt;/li&gt;
&lt;li&gt;The delay is the paywall: &lt;strong&gt;targeted token lookups are real-time for everyone&lt;/strong&gt;; activity feeds run ~10 min behind on free and live on paid.&lt;/li&gt;
&lt;li&gt;Anti-abuse (3 keys/IP/day, disposable-email block, optional IP allowlist) targets scrapers, not developers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Get a free key at &lt;a href="https://rektradar.io/developers/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=mik3fly_we-built-a-free-ethereum-scam-api" rel="noopener noreferrer"&gt;rektradar.io/developers&lt;/a&gt; and you can query the score, flags, and deployer history for any Ethereum contract in one request, no signup required to start.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>api</category>
      <category>security</category>
      <category>web3</category>
    </item>
    <item>
      <title>JaredFromSubway's bot lost $7.5M to fake tokens. We had already flagged them.</title>
      <dc:creator>Bryan MARTIN</dc:creator>
      <pubDate>Mon, 22 Jun 2026 14:29:33 +0000</pubDate>
      <link>https://dev.to/mik3fly__/jaredfromsubways-bot-lost-75m-to-fake-tokens-we-had-already-flagged-them-4dm</link>
      <guid>https://dev.to/mik3fly__/jaredfromsubways-bot-lost-75m-to-fake-tokens-we-had-already-flagged-them-4dm</guid>
      <description>&lt;p&gt;One of the most profitable MEV bots on Ethereum - the one trading as &lt;code&gt;jaredfromsubway.eth&lt;/code&gt; - just lost around &lt;strong&gt;$7.5M&lt;/strong&gt;. And the interesting part is how.&lt;/p&gt;

&lt;p&gt;It wasn't a contract exploit. No reentrancy, no bug in a DeFi protocol. It wasn't phishing either - no human signed anything. The bot &lt;strong&gt;approved its own robbery&lt;/strong&gt;, because its automation was tricked into thinking it was about to feast on a juicy MEV opportunity.&lt;/p&gt;

&lt;p&gt;We traced the whole thing on-chain. And then we noticed something: our own scanner had already flagged the bait tokens as fakes - up to a full day before the money moved.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the trap works
&lt;/h2&gt;

&lt;p&gt;ERC-20 has a two-step spend model. To let a contract move your tokens you first call &lt;code&gt;approve(spender, amount)&lt;/code&gt;, which creates an &lt;strong&gt;allowance&lt;/strong&gt;. The spender can then call &lt;code&gt;transferFrom(you, dest, amount)&lt;/code&gt; up to that allowance - and the allowance &lt;strong&gt;persists&lt;/strong&gt; until it is used up or revoked. To save gas, bots and routers often approve &lt;code&gt;type(uint256).max&lt;/code&gt;: an infinite, standing blank cheque.&lt;/p&gt;

&lt;p&gt;That is the whole attack surface. The sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The attacker deploys &lt;strong&gt;fake wrapper tokens&lt;/strong&gt; (fWETH, fUSDC, fUSDT) and fake pools engineered to look like a juicy arbitrage.&lt;/li&gt;
&lt;li&gt;To trade that "opportunity", the bot's own logic approves &lt;strong&gt;attacker-controlled helper contracts&lt;/strong&gt; as spenders of its &lt;strong&gt;real&lt;/strong&gt; WETH, USDC and USDT.&lt;/li&gt;
&lt;li&gt;On the early transactions the attacker consumes those approvals immediately - everything looks profitable, nothing trips an alarm.&lt;/li&gt;
&lt;li&gt;On later transactions the bot grants approvals that are &lt;strong&gt;never consumed or revoked&lt;/strong&gt; - they sit there, dormant, as standing spend power.&lt;/li&gt;
&lt;li&gt;Once enough allowances are stacked, the attacker calls &lt;code&gt;transferFrom&lt;/code&gt; and pulls the real assets straight out of the bot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The bot did exactly what it was built to do. Its strength - aggressive, automated profit-chasing that approves whatever a trade needs - became the hole.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the chain shows
&lt;/h2&gt;

&lt;p&gt;We pulled the transfers with our own RPC nodes and Etherscan. The receiving wallet is &lt;a href="https://etherscan.io/address/0x3e37f4A10d771Ba9dE44b6d301410b1BEdeA65d0" rel="noopener noreferrer"&gt;&lt;code&gt;0x3e37f4A10d771Ba9dE44b6d301410b1BEdeA65d0&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A funded, purpose-built account.&lt;/strong&gt; It was first funded on &lt;strong&gt;Jun 7&lt;/strong&gt; with ~19.9 ETH from a throwaway wallet (&lt;code&gt;0xe8b7e6...478703&lt;/code&gt;, two transactions of lifetime). It is also an &lt;strong&gt;EIP-7702 delegated account&lt;/strong&gt;: its code points to a smart-account implementation at &lt;code&gt;0x63c0...ae32b&lt;/code&gt;, so a single signed action can run a batched, contract-like routine - convenient when you need to harvest many approvals and fire the drain cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The drain.&lt;/strong&gt; Moving directly from the bot contract (&lt;a href="https://etherscan.io/address/0x1f2f10d1c40777ae1da742455c65828ff36df387" rel="noopener noreferrer"&gt;&lt;code&gt;0x1f2f10...df387&lt;/code&gt;&lt;/a&gt;) to the attacker, on Jun 20 between 18:49 and 18:56 UTC, we count:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Asset&lt;/th&gt;
&lt;th&gt;Pulled directly from the bot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;USDT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,035,760&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;USDC&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2,870,573&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WETH&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,474.58&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Routed through real infrastructure - the Uniswap V4 PoolManager and CoW Protocol's settlement contract - to swap the fakes for the real reserves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bait.&lt;/strong&gt; The attacker's fake tokens are exactly what the reports described, plus a few extras:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fWETH  0x6bea01ec24cd029b1ed0898ab30c8f620c8e8c4e   "Wrapped Ether"
fUSDC  0xd370f4e528d83f9941ff2803685552660af7c238   "USD Coin"
fUSDT  0x86070300d54c335717f87dcb11c6515c9a27688e   "Tether USD"
fCAP   0xf2ae7acf310812fb33451610a97020ef961f0521
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(homoglyph spoofs of ETH described in the original post.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The part we did not expect
&lt;/h2&gt;

&lt;p&gt;Impersonation tokens are our bread and butter. We score every new ERC-20 that hits mainnet on 100+ on-chain signals. So we checked the bait against our own API - and the bot was interacting with tokens &lt;strong&gt;we had already scored as high-risk fakes&lt;/strong&gt;, before the drain:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bait token&lt;/th&gt;
&lt;th&gt;RektRadar verdict&lt;/th&gt;
&lt;th&gt;Scored at&lt;/th&gt;
&lt;th&gt;vs. the drain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;fWETH&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;70/100&lt;/strong&gt; - &lt;code&gt;impersonates_major_token&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Jun 19, 17:38&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~25h earlier&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;fUSDC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;70/100&lt;/strong&gt; - &lt;code&gt;impersonates_major_token&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Jun 20, 00:38&lt;/td&gt;
&lt;td&gt;~18h earlier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;fUSDT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;70/100&lt;/strong&gt; - &lt;code&gt;impersonates_major_token&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Jun 20, 00:48&lt;/td&gt;
&lt;td&gt;~18h earlier&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The flags that fired are exactly the ones you'd want: &lt;code&gt;name_mimics_known_token&lt;/code&gt;, &lt;code&gt;impersonates_major_token&lt;/code&gt;, &lt;code&gt;deploys_scam_bytecode&lt;/code&gt;, &lt;code&gt;mass_deployer&lt;/code&gt;, &lt;code&gt;multi_flag_rug_setup&lt;/code&gt;. A token literally named "Wrapped Ether" with symbol &lt;code&gt;fWETH&lt;/code&gt;, unverified, from a mass deployer pushing known scam bytecode, is not a subtle case.&lt;/p&gt;

&lt;p&gt;To be clear: a risk score doesn't reach into a bot and stop it from signing. The bot's flaw was its own approval logic. But the &lt;strong&gt;bait was not invisible&lt;/strong&gt; - it was an off-the-shelf impersonation pattern that an independent scanner had already caught and labelled. The information needed to refuse the interaction existed, on-chain, in advance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Revoke approvals, and never approve infinity.&lt;/strong&gt; A standing &lt;code&gt;type(uint256).max&lt;/code&gt; allowance to an unknown spender is a blank cheque that survives until someone cashes it. Approve exact amounts, or use scoped/expiring approvals (Permit2-style).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Is this the real WETH?" is a question, not an assumption.&lt;/strong&gt; Most of the loss rode on the bot treating &lt;code&gt;fWETH&lt;/code&gt;/&lt;code&gt;fUSDC&lt;/code&gt;/&lt;code&gt;fUSDT&lt;/code&gt; as the assets they impersonate. Impersonation is one of the oldest scam-token patterns on Ethereum, and it is machine-checkable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation needs the same guardrails as humans.&lt;/strong&gt; A bot making millions still has to ask whether the token in front of it is a fake. That check is a single API call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We run that check on every new token on Ethereum. You can run it on any contract at &lt;a href="https://rektradar.io" rel="noopener noreferrer"&gt;rektradar.io&lt;/a&gt;, or wire it into your own bot via the &lt;a href="https://rektradar.io/developers/" rel="noopener noreferrer"&gt;API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;All addresses and amounts above are verifiable on-chain. The ~$7.5M net figure is from public reporting; the per-asset amounts are the transfers we traced directly from the bot contract to the attacker.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>security</category>
      <category>web3</category>
      <category>crypto</category>
    </item>
    <item>
      <title>Add rug-pull protection to your Ethereum bot in 5 lines</title>
      <dc:creator>Bryan MARTIN</dc:creator>
      <pubDate>Wed, 17 Jun 2026 08:57:31 +0000</pubDate>
      <link>https://dev.to/mik3fly__/add-rug-pull-protection-to-your-ethereum-bot-in-5-lines-2kbf</link>
      <guid>https://dev.to/mik3fly__/add-rug-pull-protection-to-your-ethereum-bot-in-5-lines-2kbf</guid>
      <description>&lt;p&gt;If your bot, wallet or launchpad touches freshly deployed ERC-20s, every one of them is a coin flip: honeypot, hidden mint, unlocked LP, or a deployer who has already rugged ten tokens this week. You can reimplement honeypot simulation and bytecode analysis yourself, or you can ask an API.&lt;/p&gt;

&lt;p&gt;Here is the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 lines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @mik3fly-lab/rektradar-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RektRadar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mik3fly-lab/rektradar-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RektRadar&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REKTRADAR_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xTOKEN&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// high risk: skip the trade&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;verdict.score&lt;/code&gt; is 0-100 and &lt;code&gt;verdict.flags&lt;/code&gt; is a list of machine-readable red flags (&lt;code&gt;hidden_mint&lt;/code&gt;, &lt;code&gt;lp_not_locked&lt;/code&gt;, &lt;code&gt;ownership_not_renounced&lt;/code&gt;, ...). Targeted lookups like this are real-time for everyone. No key? It still runs, anonymously, on the free tier.&lt;/p&gt;

&lt;h2&gt;
  
  
  No signup to try it
&lt;/h2&gt;

&lt;p&gt;The base URL is &lt;code&gt;https://api.rektradar.io&lt;/code&gt;. Anonymous calls work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.rektradar.io/v1/token/0xTOKEN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A key (free or paid) lifts the rate limit and removes the delay on the live feed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting part: the real-time flow
&lt;/h2&gt;

&lt;p&gt;A verdict that arrives 10 minutes late is worthless, so the live activity &lt;em&gt;flow&lt;/em&gt; is the real product: new high-risk deploys (so you avoid them before you buy) and rug events the moment liquidity is pulled. On a free key the flow is delayed about 10 minutes; on a paid key it is real-time. Every response carries &lt;code&gt;dataDelaySeconds&lt;/code&gt; (0 = real-time, 600 = delayed).&lt;/p&gt;

&lt;p&gt;Poll the REST feed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rugs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dataDelaySeconds&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rugs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;since&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;24h&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or subscribe to the push stream and act the moment liquidity is pulled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connectStream&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mik3fly-lab/rektradar-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;connectStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REKTRADAR_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imminent_rug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;imminent_rug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;getOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// pending rug, before it mines&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;notifyHolders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prefer server-side push? Register an HTTPS endpoint and RektRadar POSTs signed events to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verifyWebhook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mik3fly-lab/rektradar-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;verifyWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-RektRadar-Signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it is (and is not)
&lt;/h2&gt;

&lt;p&gt;Basic honeypot checks are commodity - several providers give them away free. The edge here is the proprietary intel on top: the deployer graph (who deployed, funded by whom), reused drainer-kit bytecode clusters, rug forensics, and the real-time new-deploy and rug feed.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;REST + WebSocket + signed webhooks&lt;/li&gt;
&lt;li&gt;Official TypeScript SDK (zero runtime deps, isomorphic)&lt;/li&gt;
&lt;li&gt;OpenAPI 3.1 reference: &lt;a href="https://api.rektradar.io/v1/docs" rel="noopener noreferrer"&gt;https://api.rektradar.io/v1/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Docs: &lt;a href="https://rektradar.io/developers/" rel="noopener noreferrer"&gt;https://rektradar.io/developers/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Free to start. Wire the five lines into your buy path and stop trading into honeypots.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>web3</category>
      <category>javascript</category>
      <category>api</category>
    </item>
    <item>
      <title>Build an Ethereum rug-pull alert bot in 20 lines</title>
      <dc:creator>Bryan MARTIN</dc:creator>
      <pubDate>Tue, 16 Jun 2026 11:55:32 +0000</pubDate>
      <link>https://dev.to/mik3fly__/build-an-ethereum-rug-pull-alert-bot-in-20-lines-3dl8</link>
      <guid>https://dev.to/mik3fly__/build-an-ethereum-rug-pull-alert-bot-in-20-lines-3dl8</guid>
      <description>&lt;p&gt;A rug is a race, and dashboards lose it. By the time you alt-tab to a scanner, paste the address, and read the verdict, the liquidity is already in the deployer's wallet. The only thing that keeps up with a rug is another process - something that watches the chain for you and pings the instant a pool dies or a fresh high-risk contract deploys.&lt;/p&gt;

&lt;p&gt;That is what the &lt;a href="https://rektradar.io" rel="noopener noreferrer"&gt;RektRadar&lt;/a&gt; API is for. It is live now: the same pipeline behind the web app - honeypot simulation, bytecode signatures, deployer graphs, liquidity and mempool analysis - exposed as a REST endpoint, a WebSocket stream, and signed webhooks, with an official TypeScript SDK on npm.&lt;/p&gt;

&lt;p&gt;This post builds a working rug-pull alert bot in about 20 lines. First the one-call check, then the live stream, then how to wire it to Discord, Telegram, or your own trading bot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the API
&lt;/h2&gt;

&lt;p&gt;Three surfaces, one mental model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;REST&lt;/strong&gt; - ask about one token, get a verdict. &lt;code&gt;GET /v1/token/&amp;lt;address&amp;gt;&lt;/code&gt; returns a 0-100 risk score and the on-chain flags behind it. Targeted lookups are real-time for everyone.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stream&lt;/strong&gt; - subscribe to the firehose. A WebSocket pushes every new deploy, every completed analysis, every rug, as it happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webhooks&lt;/strong&gt; - same events, delivered to an HTTPS endpoint you own, HMAC-signed so you can trust them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is one rule that shapes the whole thing: &lt;strong&gt;real-time is the paywall.&lt;/strong&gt; Everything is free, but the live &lt;em&gt;flow&lt;/em&gt; (the stream and the rug feed) arrives on a ~10 minute delay on a free key and in real time on a paid one. A rug alert that is 10 minutes late is worthless to a trader, so that is exactly where the value - and the price - sits. Targeted &lt;code&gt;/v1/token&lt;/code&gt; lookups are real-time for everybody. Every flow response carries an &lt;code&gt;X-Data-Delay-Seconds&lt;/code&gt; header so you always know what you are looking at.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get a key
&lt;/h2&gt;

&lt;p&gt;Grab one from your account - no credit card for the free tier: &lt;a href="https://app.rektradar.io/account#api-keys" rel="noopener noreferrer"&gt;app.rektradar.io/account#api-keys&lt;/a&gt;. Pass it as a Bearer token (or the &lt;code&gt;X-API-Key&lt;/code&gt; header). No key at all still works - you just get anonymous, delayed access.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-call check
&lt;/h2&gt;

&lt;p&gt;Before the bot, the simplest thing the API does: score a single contract. Here is a real honeypot, live on mainnet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer rr_live_xxx"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://app.rektradar.io/v1/token/0x682d9d317a077305d36c1fcc9821734046d4de2b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x682d9d317a077305d36c1fcc9821734046d4de2b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"flags"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"honeypot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hidden_owner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"low_liquidity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"buy_failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sell_failed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scam_factory_name"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BABY ASTEROID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BASTEROID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"analyzed_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-27T16:31:46.062Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same call through the SDK:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @mik3fly-lab/rektradar-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RektRadar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mik3fly-lab/rektradar-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RektRadar&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REKTRADAR_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;token&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x682d9d317a077305d36c1fcc9821734046d4de2b&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high risk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the on-demand half: you have an address, you want a verdict. The interesting half is not asking - it is being told.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bot
&lt;/h2&gt;

&lt;p&gt;Here is the whole thing. It opens the live stream, listens for two kinds of event - a token that just scored high-risk, and a pool that just got rugged - and fires an alert. About 20 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;connectStream&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mik3fly-lab/rektradar-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;WebSocket&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;connectStream&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;REKTRADAR_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// paid key = live; omit it and events arrive ~10 min late&lt;/span&gt;
  &lt;span class="na"&gt;events&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token_scored&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="nx"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                            &lt;span class="c1"&gt;// Node needs the 'ws' package; browsers use the built-in&lt;/span&gt;
  &lt;span class="na"&gt;onOpen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;watching Ethereum for rugs and high-risk deploys...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;onMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`RUG: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - liquidity pulled`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token_scored&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`HIGH RISK (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/100): &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// swap this for a Discord/Telegram webhook, a phone push, or a halt on your trading bot&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with a paid key and the alerts land in real time - roughly 10-12 events a minute across the whole chain, the moment they happen. Run it with no key, or a free one, and the exact same alerts arrive on a ~10 minute delay, which is fine for a research log and useless for front-running a rug. That difference is the entire pricing model in one &lt;code&gt;apiKey&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;alert()&lt;/code&gt; function is the part you make yours. A &lt;code&gt;fetch&lt;/code&gt; to a Discord webhook. A Telegram &lt;code&gt;sendMessage&lt;/code&gt;. A push notification. Or, if you run an auto-trading bot, a hard stop that refuses to buy anything the stream just flagged.&lt;/p&gt;

&lt;h2&gt;
  
  
  No always-on process? Use webhooks
&lt;/h2&gt;

&lt;p&gt;If you would rather not babysit a long-lived WebSocket, register an HTTPS endpoint and RektRadar will POST the same events to it. Every delivery is HMAC-SHA256 signed, and the SDK verifies it for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;verifyWebhook&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mik3fly-lab/rektradar-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/rektradar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-RektRadar-Signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;verifyWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// reject anything we did not sign&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rug.detected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token.high_risk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same events, no open socket - perfect for a serverless function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plans and limits
&lt;/h2&gt;

&lt;p&gt;API access is included in every plan. The free tier is genuinely useful for research, dashboards, and backtesting; the paid tiers buy you real time and headroom.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price /mo&lt;/th&gt;
&lt;th&gt;Data&lt;/th&gt;
&lt;th&gt;Quota&lt;/th&gt;
&lt;th&gt;Rate&lt;/th&gt;
&lt;th&gt;WS conns&lt;/th&gt;
&lt;th&gt;Webhooks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;~10 min delayed&lt;/td&gt;
&lt;td&gt;10k&lt;/td&gt;
&lt;td&gt;10/min&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;19.99&lt;/td&gt;
&lt;td&gt;real-time&lt;/td&gt;
&lt;td&gt;50k&lt;/td&gt;
&lt;td&gt;30/min&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Premium&lt;/td&gt;
&lt;td&gt;49.99&lt;/td&gt;
&lt;td&gt;real-time + forensics&lt;/td&gt;
&lt;td&gt;250k&lt;/td&gt;
&lt;td&gt;120/min&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pro&lt;/td&gt;
&lt;td&gt;99&lt;/td&gt;
&lt;td&gt;real-time + SLA&lt;/td&gt;
&lt;td&gt;1M&lt;/td&gt;
&lt;td&gt;300/min&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Full endpoint reference, the event catalogue, and the SDK source are on the docs: &lt;a href="https://rektradar.io/developers/" rel="noopener noreferrer"&gt;rektradar.io/developers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The data was always there - 100+ flags across seven analyzers, scoring thousands of Ethereum tokens a day. Now you can build on it. Ship the bot, point &lt;code&gt;alert()&lt;/code&gt; at your channel, and let the chain wake you up instead of the other way around.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>The contract is clean - for now: catching crypto scams that survive launch-time checks</title>
      <dc:creator>Bryan MARTIN</dc:creator>
      <pubDate>Mon, 15 Jun 2026 15:36:11 +0000</pubDate>
      <link>https://dev.to/mik3fly__/the-contract-is-clean-for-now-catching-crypto-scams-that-survive-launch-time-checks-1bj7</link>
      <guid>https://dev.to/mik3fly__/the-contract-is-clean-for-now-catching-crypto-scams-that-survive-launch-time-checks-1bj7</guid>
      <description>&lt;p&gt;Most token scam detectors, including the one I work on, share one implicit assumption: &lt;strong&gt;the contract you analyze at launch is the contract people will trade.&lt;/strong&gt; Read the source, simulate a buy and a sell, cluster the deployer, score it, done.&lt;/p&gt;

&lt;p&gt;That is a snapshot. And a snapshot is exactly what a patient scammer plays against. Two token designs pass every launch-time check and then turn hostile later. This is how they work, and the two on-chain techniques we shipped this week to catch them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design 1: the delayed honeypot
&lt;/h2&gt;

&lt;p&gt;A honeypot is a token you can buy but cannot sell. The classic version is non-sellable from block one, so a buy-then-sell simulation catches it instantly.&lt;/p&gt;

&lt;p&gt;The patient version is sellable at launch. Early buyers sell fine, the chart looks healthy, the token earns a clean verdict from every checker that judged it at T0. Then, days later, the operator flips a switch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;strong&gt;timed blacklist&lt;/strong&gt; that rejects transfers after a block height or timestamp,&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;setTrading(false)&lt;/code&gt; / &lt;code&gt;pause()&lt;/code&gt; kill switch pulled once liquidity has accumulated,&lt;/li&gt;
&lt;li&gt;a fee setter cranked to 100% on sells.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From that moment it is a honeypot. But the only verdict on record is the clean one from launch day. The detection ran once, at the worst possible time to run it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix: re-simulate at J7
&lt;/h3&gt;

&lt;p&gt;We keep post-launch snapshots of every token at J0, J7 and J30 (originally to catch slow rugs: volume collapse, late LP burns). The new piece re-runs the &lt;strong&gt;full buy/sell honeypot simulation&lt;/strong&gt; at J7, but only for tokens that were genuinely sellable at J0. A clean-to-honeypot flip is the signal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Only for tokens sellable + tradable at J0 - a clean-&amp;gt;honeypot flip is the point.&lt;/span&gt;
&lt;span class="c1"&gt;// Bounded per run because it is RPC-heavy.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;eligible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;j0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;risk_flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;J0_SKIP_RESIM_FLAGS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;eligible&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;resims&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;resimLimit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isNowHoneypot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;detectLateHoneypot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tokenAddress&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isNowHoneypot&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;late_honeypot&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// +40 risk at J7&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One rule we hold to: an RPC hiccup never &lt;strong&gt;fabricates&lt;/strong&gt; a &lt;code&gt;late_honeypot&lt;/code&gt;. A failed re-simulation returns "not a honeypot" rather than inventing one. Better to miss a flip than cry wolf on a healthy token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design 2: the proxy whose implementation is the payload
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;proxy&lt;/strong&gt; is a thin contract that holds storage and forwards every call via &lt;code&gt;delegatecall&lt;/code&gt; to a separate &lt;strong&gt;implementation&lt;/strong&gt; contract that holds the logic. Completely legitimate, extremely common (every upgradeable token uses it). That is exactly why it is good cover.&lt;/p&gt;

&lt;p&gt;The trick: you analyze the proxy address. Its bytecode is tiny - "delegatecall to whatever address is in this storage slot." Nothing to flag. The source, if verified, is boilerplate. Clean. Meanwhile the blacklist, the mint, the drain live in the implementation the proxy points to, which nobody looked at - and which an admin can &lt;strong&gt;swap&lt;/strong&gt; in one transaction. The proxy address never changes, so explorers keep showing the same "contract."&lt;/p&gt;

&lt;p&gt;So we taught the analyzer to follow the &lt;code&gt;delegatecall&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resolving the implementation on-chain
&lt;/h3&gt;

&lt;p&gt;You do not need verified source to find the implementation. The proxy standards store the address in well-known storage slots. We read them in order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// EIP-1967: slot = keccak256("eip1967.proxy.implementation") - 1&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readSlotAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EIP1967_IMPL_SLOT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Beacon proxy: the beacon contract holds the address.&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beacon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;readSlotAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EIP1967_BEACON_SLOT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;beacon&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;callImplementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beacon&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// then the OpenZeppelin legacy slot, then EIP-1822 / UUPS PROXIABLE.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we run &lt;strong&gt;the same bytecode analysis we run on any deployed contract&lt;/strong&gt; against the implementation: function selectors, dangerous opcodes, and a known-scam-factory hash match. If the implementation is byte-identical to a known mass-scam template, the token gets &lt;code&gt;proxy_implementation_known_scam&lt;/code&gt; - a clean-looking proxy delegating to a confirmed scam.&lt;/p&gt;

&lt;p&gt;Two more signals fall out for free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;proxy_implementation_missing&lt;/code&gt;&lt;/strong&gt; - the proxy points at an address with no code. A loaded trap: deploy malicious logic there (or repoint the proxy) once liquidity has piled up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;mutable_proxy_admin&lt;/code&gt;&lt;/strong&gt; - the EIP-1967 admin slot holds a live address that can swap the implementation at will. A rug switch in plain sight.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One nuance worth stating, because it is a common false-positive trap: an &lt;strong&gt;empty&lt;/strong&gt; admin slot does not mean immutable. UUPS proxies keep the upgrade authority inside the implementation, not the admin slot. So we only raise &lt;code&gt;mutable_proxy_admin&lt;/code&gt; on a populated slot, and never claim immutability from an empty one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The common thread: stop trusting the snapshot
&lt;/h2&gt;

&lt;p&gt;Both detections come from the same shift. A launch-time verdict answers "is this a scam right now?" The questions that actually protect a buyer are "&lt;strong&gt;will&lt;/strong&gt; it still be safe next week?" and "is the code I'm reading the code that will run?"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The delayed honeypot attacks the &lt;strong&gt;time&lt;/strong&gt; axis: clean now, hostile later. Answer: re-judge at J7.&lt;/li&gt;
&lt;li&gt;The proxy attacks the &lt;strong&gt;indirection&lt;/strong&gt; axis: clean here, hostile one delegatecall away. Answer: follow the pointer.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cost stays bounded: the honeypot re-sim only runs on J0-sellable tokens and is capped per cycle; the proxy resolution only fires when the bytecode actually contains a &lt;code&gt;DELEGATECALL&lt;/code&gt; opcode, so the millions of non-proxy tokens pay nothing.&lt;/p&gt;

&lt;p&gt;If you build anything that judges smart contracts: a clean verdict at launch is a statement about launch, not a promise about next week. Treat it that way.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Full write-up with more detail on the &lt;a href="https://rektradar.io/blog/posts/scams-that-survive-launch-checks/" rel="noopener noreferrer"&gt;RektRadar blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>security</category>
      <category>web3</category>
      <category>typescript</category>
    </item>
    <item>
      <title>$35M in 4 months: how much ETH Uniswap V2 scam pairs drained</title>
      <dc:creator>Bryan MARTIN</dc:creator>
      <pubDate>Thu, 11 Jun 2026 13:09:34 +0000</pubDate>
      <link>https://dev.to/mik3fly__/35m-in-4-months-how-much-eth-uniswap-v2-scam-pairs-drained-pak</link>
      <guid>https://dev.to/mik3fly__/35m-in-4-months-how-much-eth-uniswap-v2-scam-pairs-drained-pak</guid>
      <description>&lt;p&gt;&lt;em&gt;By &lt;a href="https://x.com/mik3fly__" rel="noopener noreferrer"&gt;Bryan Martin&lt;/a&gt; - founder of RektRadar. Ethereum scam-detection infrastructure since 2024. &lt;a href="https://github.com/mik3fly" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; - &lt;a href="https://www.linkedin.com/in/bryan-martin-0560b069/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsl5bdd1d7plr8wo1dh04.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsl5bdd1d7plr8wo1dh04.png" alt="WETH drained by Uniswap V2 scam pairs: 16,830 ETH net buyer losses, 8,257 ETH already pulled by ruggers, 71% of V2 launches flagged" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"How much money do these scams actually steal?" is the question we get most, and most answers out there are extrapolations. We decided to answer it the hard way: by summing every swap, every liquidity mint and every liquidity burn on every Uniswap V2 pair our infrastructure has watched since it went live.&lt;/p&gt;

&lt;p&gt;The result: in 4.5 months, buyers collectively lost a net &lt;strong&gt;16,830 ETH (~$35M at the period's average price)&lt;/strong&gt; inside V2 pairs whose token our scoring pipeline flags as a scam. Ruggers have already extracted &lt;strong&gt;8,257 ETH (~$17M)&lt;/strong&gt; of it through liquidity pulls. And the single most uncomfortable number: &lt;strong&gt;71% of all Uniswap V2 pair launches we observed were scam pairs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is not a model and not a projection. It is SQL over our own event log.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dataset snapshot
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Snapshot:&lt;/strong&gt; 2026-06-11. Source: RektRadar's &lt;code&gt;events&lt;/code&gt; table (29.1M decoded on-chain events captured live since 2026-02-01 by our factory and mempool watchers) joined with &lt;code&gt;token_analysis&lt;/code&gt; (93,691 tokens fully scored by the 9-dimension pipeline).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;N_total: 20,452 distinct Uniswap V2 pairs created between 2026-02-01 and 2026-06-11&lt;/li&gt;
&lt;li&gt;N_filtered: 14,601 pairs (71.4%) pairing WETH with a token scoring &amp;gt;= 70 on our risk scale&lt;/li&gt;
&lt;li&gt;Events aggregated on those pairs: 2,770,692 swaps + 46,625 mints + 44,354 burns (deduplicated)&lt;/li&gt;
&lt;li&gt;Key metric: 16,830 ETH net buyer losses, 8,257 ETH net extracted by liquidity pulls&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The headline numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;ETH (WETH)&lt;/th&gt;
&lt;th&gt;USD at period avg (~$2,105)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;WETH spent buying scam tokens&lt;/td&gt;
&lt;td&gt;221,062&lt;/td&gt;
&lt;td&gt;$465M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WETH recovered selling them back&lt;/td&gt;
&lt;td&gt;204,232&lt;/td&gt;
&lt;td&gt;$430M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Net buyer losses&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;16,830&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$35.4M&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gross buyer losses (losing pools only)&lt;/td&gt;
&lt;td&gt;23,207&lt;/td&gt;
&lt;td&gt;~$48.9M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WETH seeded by deployers (mints)&lt;/td&gt;
&lt;td&gt;16,123&lt;/td&gt;
&lt;td&gt;$33.9M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WETH pulled out via burns&lt;/td&gt;
&lt;td&gt;24,380&lt;/td&gt;
&lt;td&gt;$51.3M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Net extracted by ruggers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;8,257&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$17.4M&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ETH traded between $1,569 and $2,421 over the window, averaging ~$2,105. At today's price (~$1,661) the net buyer loss is "only" $27.9M, which says more about ETH than about the scammers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the money sits: a pool is a closed box
&lt;/h2&gt;

&lt;p&gt;A Uniswap V2 pair only has four doors for WETH: swaps in, swaps out, liquidity mints, liquidity burns. That makes the accounting honest. Per pool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Net buyer losses&lt;/strong&gt; = WETH in via buys - WETH out via sells. This is what traders collectively left inside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Net extracted&lt;/strong&gt; = WETH burned - WETH minted. This is what liquidity providers (on a scam pair, the scammer) walked away with beyond their initial seed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two numbers bracket the theft. Buyers left 16,830 ETH in the boxes; ruggers have already carried 8,257 ETH out the &lt;a href="https://rektradar.io/signals/liquidity_removed/" rel="noopener noreferrer"&gt;liquidity-removal door&lt;/a&gt;; roughly 8,573 ETH is still sitting inside flagged pairs or left through paths we do not attribute (scammer self-dumps count as ordinary sells in our sums, so the real victim losses are higher, not lower).&lt;/p&gt;

&lt;h2&gt;
  
  
  The long tail: the median rug steals 0.21 ETH
&lt;/h2&gt;

&lt;p&gt;13,247 of the flagged pairs had at least one swap. 9,295 of them (70%) ended net-negative for buyers. But the median net loss per pool is just &lt;strong&gt;0.21 ETH (~$440)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Ethereum scam economy is not a few heists. It is an industrial long tail of micro-rugs: deploy a token for pennies, seed 0.5 ETH of liquidity, catch a handful of snipers and tourists, pull, repeat. Our &lt;a href="https://rektradar.io/blog/posts/anatomy-of-a-serial-scammer-wallet/" rel="noopener noreferrer"&gt;serial scammer wallet anatomy&lt;/a&gt; post shows the same pattern from the deployer side; this is what it adds up to on the victim side.&lt;/p&gt;

&lt;p&gt;The top 10 pools account for roughly half of all net inflows. Concentration at the top, industrial volume at the bottom.&lt;/p&gt;

&lt;h2&gt;
  
  
  The biggest drains
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Token&lt;/th&gt;
&lt;th&gt;Risk score&lt;/th&gt;
&lt;th&gt;Net buyer inflow (ETH)&lt;/th&gt;
&lt;th&gt;Liquidity pulled?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://app.rektradar.io/scam/ZAYU" rel="noopener noreferrer"&gt;$ZAYU&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;2,900&lt;/td&gt;
&lt;td&gt;not yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://app.rektradar.io/scam/KNX" rel="noopener noreferrer"&gt;$KNX&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;1,745&lt;/td&gt;
&lt;td&gt;not yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ETHX&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;999&lt;/td&gt;
&lt;td&gt;not yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RISE&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;648&lt;/td&gt;
&lt;td&gt;not yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DU&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;581&lt;/td&gt;
&lt;td&gt;not yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OPNTR&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;419&lt;/td&gt;
&lt;td&gt;yes - 412 ETH out&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CCLS&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;333&lt;/td&gt;
&lt;td&gt;yes - 330 ETH out&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The "not yet" rows are the chilling part. The largest flagged pairs still hold their liquidity: thousands of ETH of buyer money sitting in tokens our pipeline scores 80+, one transaction away from extraction. OPNTR and CCLS show what that transaction looks like: when the pull comes, it takes essentially everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we excluded Uniswap V3 (and you should distrust V3 "stolen" stats)
&lt;/h2&gt;

&lt;p&gt;We ran the same aggregation on 3,869 flagged V3 pools and got numbers that violate conservation: 487,266 WETH burned out of pools that only ever received 315,582 via mints and 2,299 net via swaps. Impossible - unless WETH enters pools through untracked direct transfers, which is exactly what liquidity-cycling and wash-trading setups do.&lt;/p&gt;

&lt;p&gt;Two tokens (uPEG and ASTEROID) account for 72% of that fake volume, with hundreds of thousands of ETH churned in and out as LP positions. None of it is victim money. Any "X million stolen" figure built naively on V3 burn events will be inflated by an order of magnitude. Scams launch on V2; V3 is where volume-faking lives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Methodology
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scam set&lt;/strong&gt;: tokens with &lt;code&gt;risk_score &amp;gt;= 70&lt;/code&gt; in &lt;code&gt;token_analysis&lt;/code&gt; (42,796 of 93,691 analyzed - the threshold and pipeline are detailed in &lt;a href="https://rektradar.io/blog/posts/we-analyzed-78k-ethereum-tokens/" rel="noopener noreferrer"&gt;our 78k-token breakdown&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pool set&lt;/strong&gt;: V2 &lt;code&gt;pair_created&lt;/code&gt; events where one side is canonical WETH and the other side is in the scam set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aggregation&lt;/strong&gt;: per pool, sum the WETH legs of &lt;code&gt;swap&lt;/code&gt; (amountIn/amountOut), &lt;code&gt;mint&lt;/code&gt; and &lt;code&gt;burn&lt;/code&gt; events, deduplicated on &lt;code&gt;(tx_hash, log_index)&lt;/code&gt; because our pipeline writes both pending and confirmed states.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Totals&lt;/strong&gt;: net figures sum all pools; gross figures sum only pools where the per-pool net is positive (so winners do not mask losers).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The full analysis (SQL included) lives in our infra repo, and the aggregate is now exposed live: the same numbers power the "all time" counter on &lt;a href="https://rektradar.io/?utm_source=blog&amp;amp;utm_medium=internal&amp;amp;utm_campaign=mik3fly_eth-drained-uniswap-v2-scam-pairs-counter" rel="noopener noreferrer"&gt;rektradar.io&lt;/a&gt; and refresh every 6 hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits of our data
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Scorer-conditional totals.&lt;/strong&gt; "Scam pair" means "paired with a token our multi-flag scorer puts at 70+". False positives inflate the totals; false negatives (scams we scored under 70) deflate them. Our precision/recall audit suggests the two do not cancel neatly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Net != victim losses.&lt;/strong&gt; Scammer self-dumps are indistinguishable from ordinary sells in pool-level sums, so net buyer losses understate what real victims lost. Winning snipers also capture part of the flow: not all of it ends in scammer wallets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sampling window and scope.&lt;/strong&gt; 2026-02-01 to 2026-06-11, Ethereum mainnet, Uniswap V2 WETH pairs only. No V3/V4 (see above), no stablecoin-quoted pairs, no other DEXes, no L2s. The true ecosystem-wide figure is strictly larger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Still-live pools can move.&lt;/strong&gt; ZAYU-style pairs holding thousands of ETH may still rug (raising extraction) or, rarely, turn out legitimate (lowering losses). The 6-hourly recompute will track it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;71% of the 20,452 Uniswap V2 pairs launched since February pair WETH with a token we flag as a scam (risk &amp;gt;= 70)&lt;/li&gt;
&lt;li&gt;Buyers net-lost 16,830 ETH (~$35M at period prices) inside those pairs; gross losses on losing pools: 23,207 ETH&lt;/li&gt;
&lt;li&gt;Ruggers already extracted 8,257 ETH (~$17M) via liquidity pulls; ~8,500 ETH of buyer money still sits in flagged pairs&lt;/li&gt;
&lt;li&gt;The median rug nets just 0.21 ETH - it is an industrial long tail, not a few heists&lt;/li&gt;
&lt;li&gt;V3 burn-based "stolen" stats are inflated by LP churn and wash trading; we measured and excluded it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try the &lt;a href="https://rektradar.io/?utm_source=blog&amp;amp;utm_medium=internal&amp;amp;utm_campaign=mik3fly_eth-drained-uniswap-v2-scam-pairs-cta" rel="noopener noreferrer"&gt;free scan, no signup, no card&lt;/a&gt; - the 14,601 pairs in this post were all flagged automatically before most of them had their first victim.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>crypto</category>
      <category>security</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>What I shipped in one Claude Code session: a real-time mempool rug-detector pipeline</title>
      <dc:creator>Bryan MARTIN</dc:creator>
      <pubDate>Wed, 10 Jun 2026 19:29:12 +0000</pubDate>
      <link>https://dev.to/mik3fly__/what-i-shipped-in-one-claude-code-session-a-real-time-mempool-rug-detector-pipeline-2014</link>
      <guid>https://dev.to/mik3fly__/what-i-shipped-in-one-claude-code-session-a-real-time-mempool-rug-detector-pipeline-2014</guid>
      <description>&lt;p&gt;I run &lt;a href="https://rektradar.io" rel="noopener noreferrer"&gt;RektRadar&lt;/a&gt;, a real-time scam-token detector for Ethereum. This is an honest build-log of one session with Claude Code (the Fable model) where I went from "our scam detection misses a few things" to four new detection signals shipped and a three-repo real-time alert pipeline live in production. Every PR linked below is public.&lt;/p&gt;

&lt;p&gt;Not "the AI did everything magically". The interesting part is the division of labor: where an agent is genuinely strong, where I had to steer, and the one moment it was about to do something dumb and I stopped it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;RektRadar is ~7 repos: a mempool watcher, a contract analyzer (7 sub-analyzers), a graph crawler, a Telegram bot, an Astro marketing site, a Vue app, and the Ansible infra. Node/TypeScript throughout, real test suites (the contract analyzer alone has ~7,000 tests).&lt;/p&gt;

&lt;p&gt;I started the session with a list of detection gaps a security discussion had surfaced, and worked down it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The headline: catching a rug in the mempool, before it mines
&lt;/h2&gt;

&lt;p&gt;A rug is usually a single transaction: &lt;code&gt;removeLiquidity&lt;/code&gt;, &lt;code&gt;setFee&lt;/code&gt; to a confiscatory tax, &lt;code&gt;setBlacklist&lt;/code&gt;, &lt;code&gt;pause&lt;/code&gt;, &lt;code&gt;mint&lt;/code&gt;. Between broadcast and inclusion it sits in the public mempool for seconds. We already stream pending txs for sandwich detection, so the move was to watch those same txs for a rug action against a token we already flagged as high-risk.&lt;/p&gt;

&lt;p&gt;The design Claude landed on, mirroring the existing &lt;code&gt;DeployWatcher&lt;/code&gt; pattern in the repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;classifyPrivilegedCall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PrivilegedTxLike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;byToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;WatchEntry&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PrivilegedAlert&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// liquidity removal goes through the router, but the token is in the calldata&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;LIQUIDITY_REMOVAL_SELECTORS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;decodeRemovedLiquidityToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;byToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;liquidity_removal_pending&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// owner functions (setFee/blacklist/pause/mint...) are called on the token itself&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;byToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;pure classifier&lt;/strong&gt; + an injected watcher that refreshes the high-risk token set every few minutes. That split mattered: the classifier got 19 unit tests with zero mocks, while the I/O stayed in the watcher. This is the thing agents are quietly good at - given a clean existing pattern, they reproduce its shape rather than inventing a new one.&lt;/p&gt;

&lt;p&gt;This is where it gets cross-service. The detection lives in the mempool watcher; the alert needs to reach a Telegram channel. So the session built a three-repo pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a Postgres &lt;code&gt;privileged_alerts&lt;/code&gt; hand-off table (infra migration),&lt;/li&gt;
&lt;li&gt;the mempool watcher writing detected alerts to it (idempotent on tx hash),&lt;/li&gt;
&lt;li&gt;the Telegram bot polling unposted rows and posting an "imminent rug" message, stamping &lt;code&gt;posted_at&lt;/code&gt; so each fires once.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Decoupled producer/consumer, each with its own tests. All three merged and deployed in the same session.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Where I had to keep it honest
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The ABI verification.&lt;/strong&gt; The lock-quality signal needed to read a liquidity lock's &lt;em&gt;unlock date&lt;/em&gt; to flag "lock expires in 3 days". That means reading the UNCX locker contract on-chain. Claude wanted to write the read against the documented &lt;code&gt;tokenLocks(lpToken, index)&lt;/code&gt; ABI. I made it verify the struct against the live contract first - we pulled a real locked token off the locker's global index and decoded an actual &lt;code&gt;unlockDate&lt;/code&gt; before trusting the field order. On a security tool, a misread field that emits a false "this is safe" is worse than no signal. The agent will happily write plausible code; the human has to insist on grounding it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The sybil false-positive.&lt;/strong&gt; The sybil-holder signal clusters top holders by their funding source - dozens of fresh wallets funded by one address is fake demand. The trap: a cohort that all withdrew from the same exchange is &lt;em&gt;normal&lt;/em&gt;. Without a CEX exclusion list this would false-positive on half of crypto. We pulled the 64-address exchange set from the graph-crawler service and excluded it. This is the kind of domain constraint that does not show up in the code, only in the consequences - exactly what you need a human in the loop for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "don't push" moment.&lt;/strong&gt; At one point I asked Claude to open a PR, then immediately realized I wanted to review the branch more first. It had already opened it. It converted it to draft within seconds of my "wait", then closed it cleanly when I confirmed. Cheap recovery, but a reminder that "open a PR" is an outward action you want a beat of confirmation on.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What actually shipped
&lt;/h2&gt;

&lt;p&gt;Four new signals, all live:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mempool imminent-rug warning&lt;/strong&gt; (the pipeline above)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sybil-funded holders&lt;/strong&gt; - funding-graph clustering with CEX exclusion&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock quality&lt;/strong&gt; - partial lock + verified-on-chain expiring lock&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phishing-bait&lt;/strong&gt; - URL / claim-channel in the token's on-chain name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Plus the supporting work: the signal documentation catalogue went from 55 to 105 pages (every flag the analyzers raise now has a page explaining it), and a UI palette/spacing refresh on the app.&lt;/p&gt;

&lt;p&gt;~18 PRs across 7 repos, all merged through the normal CI + review pipeline, all with tests. The mempool watcher suite is at 2,496 tests, the contract analyzer at ~7,089, the Telegram bot at 114.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest scorecard
&lt;/h2&gt;

&lt;p&gt;What the agent was strong at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pattern matching the codebase.&lt;/strong&gt; New code looked like the surrounding code - same logger shape, same port/adapter split, same test style.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test discipline.&lt;/strong&gt; Every behavior change came with tests, and it iterated on real failures (a couple of mocked-config tests broke when I added a new export; it found and fixed them).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-service plumbing.&lt;/strong&gt; Holding the shape of a producer/consumer pipeline across three repos without losing the thread.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What needed me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grounding claims in reality&lt;/strong&gt; (verify the ABI live, don't trust the docs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Domain constraints that only show as false positives&lt;/strong&gt; (the CEX exclusion).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outward actions&lt;/strong&gt; (the PR I didn't want pushed yet).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The summary I'd give: it is a very fast, very literal pair-programmer that is excellent at the "how" once you are clear on the "what" - and a security tool is exactly the place where you still own the "what".&lt;/p&gt;

&lt;p&gt;You can paste any Ethereum token address into the free detector at &lt;a href="https://rektradar.io?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=mik3fly_buildlog" rel="noopener noreferrer"&gt;rektradar.io&lt;/a&gt; and watch these signals run live. If you want the product-side writeup of the signals themselves, that's &lt;a href="https://rektradar.io/blog/posts/four-new-scam-signals-mempool-rug-warning/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=mik3fly_buildlog" rel="noopener noreferrer"&gt;on the blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>programming</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>We thought we caught every scam token. A dev.to post showed us a blind spot.</title>
      <dc:creator>Bryan MARTIN</dc:creator>
      <pubDate>Tue, 09 Jun 2026 16:30:53 +0000</pubDate>
      <link>https://dev.to/mik3fly__/we-thought-we-caught-every-scam-token-a-devto-post-showed-us-a-blind-spot-2c9g</link>
      <guid>https://dev.to/mik3fly__/we-thought-we-caught-every-scam-token-a-devto-post-showed-us-a-blind-spot-2c9g</guid>
      <description>&lt;p&gt;We run a real-time scam-token detector for Ethereum. It analyzes new ERC-20s, simulates buys and sells to catch honeypots, clusters deployers and funders, and scores everything live. After four months and ~93,000 analyzed contracts, we were fairly sure our funnel saw everything that mattered.&lt;/p&gt;

&lt;p&gt;Then &lt;a href="https://dev.to/"&gt;@sanjeevkkansal&lt;/a&gt; published &lt;a href="https://github.com/sanjeevkkansal/evm-deploy-watch" rel="noopener noreferrer"&gt;evm-deploy-watch&lt;/a&gt;, an MIT-licensed week-long study of every new contract on Ethereum. It is a genuinely good piece of work, and it did two things for us. First, it independently validated our core thesis. Second, it pointed a flashlight straight at a blind spot we did not know we had.&lt;/p&gt;

&lt;p&gt;This is the gap, measured, and how we closed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thesis we already shared
&lt;/h2&gt;

&lt;p&gt;The deploy-watch author's central finding is that &lt;strong&gt;deploy-time heuristics beat lagging public blacklists&lt;/strong&gt;. Reading source code, deployer history, and bytecode the moment a contract is created catches scams days before they show up on a blocklist. The study reported essentially &lt;strong&gt;zero overlap&lt;/strong&gt; with ScamSniffer's feed - the contracts it flagged were not (yet) on anyone's list.&lt;/p&gt;

&lt;p&gt;That is exactly the bet our pipeline is built on, so it was great to see it confirmed by someone with a completely independent dataset and methodology. The article's "future next steps" section reads almost like our changelog: buy/sell simulation, CREATE2 / factory clustering, deployer-funder graphs, multi-signal scoring. We already run those in production. More on that at the end, because the honest part of this post is the thing we were &lt;em&gt;not&lt;/em&gt; doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blind spot: our funnel is DEX-triggered
&lt;/h2&gt;

&lt;p&gt;Here is the architectural assumption that bit us.&lt;/p&gt;

&lt;p&gt;Our ingestion is driven by &lt;strong&gt;liquidity&lt;/strong&gt;. A &lt;code&gt;factory-watcher&lt;/code&gt; service reacts to Uniswap V2/V3/V4 pool creation events. When a token opens a pool, it enters the funnel and gets the full analysis: source regex, bytecode clustering, a real buy-then-sell simulation, deployer graph, scoring. This is the right trigger for the scams we care most about, because a rug or a honeypot &lt;strong&gt;needs&lt;/strong&gt; a pool. You cannot trap a buyer who cannot buy.&lt;/p&gt;

&lt;p&gt;But there is an entire family of fraud that &lt;strong&gt;never creates a pool&lt;/strong&gt;, because it never intends for anyone to trade it.&lt;/p&gt;

&lt;h2&gt;
  
  
  FlashUSDT / fake-Tether: a scam with no on-chain victim
&lt;/h2&gt;

&lt;p&gt;The "FlashUSDT" or "proof-of-funds" family works entirely off-chain. The mechanics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An operator deploys an ERC-20 whose &lt;code&gt;name()&lt;/code&gt; returns &lt;code&gt;Tether USD&lt;/code&gt;, &lt;code&gt;symbol()&lt;/code&gt; returns &lt;code&gt;USDT&lt;/code&gt;, and &lt;code&gt;decimals()&lt;/code&gt; returns &lt;code&gt;6&lt;/code&gt;. Anyone can do this. Nothing on-chain stops you from naming your token whatever you want.&lt;/li&gt;
&lt;li&gt;They send a large balance of this token to a wallet, or show you a wallet that holds it. Your wallet UI and most explorers read the metadata and display it as &lt;strong&gt;250,000 USDT&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;They give you a reason the "funds" need to move: an OTC deal, an escrow proof, a job signing bonus, a "liquidity bot" demo.&lt;/li&gt;
&lt;li&gt;You send something genuinely valuable in return - real USDT, ETH, goods, a token approval.&lt;/li&gt;
&lt;li&gt;The fake balance was always worth zero. There is no pool, no price, no trade. The whole con is the spoofed metadata plus social engineering.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Because these tokens never touch a DEX, &lt;strong&gt;simulation-based detection never sees them&lt;/strong&gt;. There is nothing to simulate. They are invisible to anything that waits for a Uniswap pair - including, it turned out, us.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap, quantified
&lt;/h2&gt;

&lt;p&gt;Numbers, because "we have a blind spot" is a feeling and "we capture 0.5% of this family" is a bug report.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our database held &lt;strong&gt;15&lt;/strong&gt; FlashUSDT-named tokens across &lt;strong&gt;four months&lt;/strong&gt; (2026-02-12 to 06-08), out of 93,000 analyzed.&lt;/li&gt;
&lt;li&gt;The deploy-watch study observed roughly &lt;strong&gt;~192 per week&lt;/strong&gt; of this family in its window (134 &lt;code&gt;FlashUSDT&lt;/code&gt; + 58 &lt;code&gt;FlashUSDTLiquidityBot&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;So we were capturing on the order of &lt;strong&gt;~0.5%&lt;/strong&gt; of it.&lt;/li&gt;
&lt;li&gt;The two contracts the article dissects in detail were simply &lt;strong&gt;not in our database at all&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a scoring miss. It is a coverage miss: the contracts never entered the funnel, so there was nothing to score. You cannot rank what you never ingest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing it: ingest every deploy, not every pool
&lt;/h2&gt;

&lt;p&gt;The fix is conceptually simple - watch contract &lt;strong&gt;creations&lt;/strong&gt;, not pool creations - and the engineering is mostly about keeping the volume bounded.&lt;/p&gt;

&lt;p&gt;We already run a &lt;code&gt;mempool-watcher&lt;/code&gt; that streams pending transactions. A contract creation is just a transaction with &lt;code&gt;to === null&lt;/code&gt;. So the cheapest place to hook this in is right there in the mempool loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// main-loop.ts - a contract creation is a tx with no recipient&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;adapters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deployWatcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;adapters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deployWatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onDeploy&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The deploy watcher predicts the contract address from the sender and nonce, waits for the deploy to mine, reads two metadata fields, and only then decides whether the contract is worth a full analysis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JsonRpcProvider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;getCreateAddress&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ethers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ERC20_MIN_ABI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function name() view returns (string)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function symbol() view returns (string)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nf"&gt;onDeploy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DeployTx&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCreateAddress&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nonce&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// CREATE2 / malformed - out of scope for v1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// dedupe; bounded Set, cleared at 50k&lt;/span&gt;
  &lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// reverted or not mined yet&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Contract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ERC20_MIN_ABI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isImpersonation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;opts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// -&amp;gt; analysis_queue -&amp;gt; contract-analyzer&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;delayMs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ~45s: deploys are seen pending, read metadata after they mine&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few deliberate choices in there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Metadata-only triage.&lt;/strong&gt; At this stage we make exactly two &lt;code&gt;eth_call&lt;/code&gt;s: &lt;code&gt;name()&lt;/code&gt; and &lt;code&gt;symbol()&lt;/code&gt;. No simulation, no source fetch, no graph. The study counted ~1,200 top-level deploys per day. Two reads each, against our own Nethermind node, is nothing. The expensive analysis only runs for the handful that actually spoof a major token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predict-then-confirm.&lt;/strong&gt; &lt;code&gt;getCreateAddress(from, nonce)&lt;/code&gt; gives us the deterministic address for a plain &lt;code&gt;CREATE&lt;/code&gt; deploy, so we can start watching it the moment the tx is pending and read its code once it mines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A &lt;code&gt;seen&lt;/code&gt; Set, bounded.&lt;/strong&gt; Deploys can appear multiple times across mempool snapshots; the set dedupes and self-clears at 50k entries so it never leaks memory. Re-checking a stale address is harmless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CREATE2 is out of scope for v1.&lt;/strong&gt; Factory-deployed contracts whose address depends on a salt are not covered by &lt;code&gt;getCreateAddress&lt;/code&gt;. We flagged this as a known limitation rather than pretending otherwise - it is a real future item.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The check that defeats the whole family
&lt;/h2&gt;

&lt;p&gt;The actual detection is almost anticlimactic, and that is the point. A token is its &lt;strong&gt;address&lt;/strong&gt;, not its name. The real USDT lives at exactly one contract. Anything claiming to be USDT at any other address is, by definition, not USDT.&lt;/p&gt;

&lt;p&gt;So the signal is: name or symbol matches a major token, &lt;strong&gt;and&lt;/strong&gt; the address is not the canonical one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CANONICAL_TOKENS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;USDT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xdac17f958d2ee523a2206206994597c13d831ec7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tether usd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;USDC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;usd coin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;WETH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wrapped ether&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;DAI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x6b175474e89094c44da98b954eedeac495271d0f&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dai stablecoin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;WBTC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0x2260fac5e5542a773aa44fbcfedf7c193bc2c599&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wrapped btc&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... FRAX, LUSD, BUSD, stETH, wstETH&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isImpersonation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sym&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;canonSym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CANONICAL_TOKENS&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sym&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;canonSym&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;nm&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pitfalls hide in that tiny function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The allowlist is the safety rail, and it has to be exact.&lt;/strong&gt; If you get one canonical address wrong, you either miss a whole family or, worse, you flag the &lt;strong&gt;real&lt;/strong&gt; USDT as a scam. Every address is stored lowercased and compared lowercased so EIP-55 checksums never cause a false negative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exact match, not fuzzy.&lt;/strong&gt; We compare &lt;code&gt;symbol === "USDT"&lt;/code&gt;, not &lt;code&gt;symbol.includes("USDT")&lt;/code&gt;. Fuzzy matching would light up every legitimate bridged or wrapped variant (&lt;code&gt;USDT.e&lt;/code&gt;, &lt;code&gt;axlUSDC&lt;/code&gt;, and friends) and bury the real signal in false positives. The trade-off is that a typo-squat like &lt;code&gt;USDTT&lt;/code&gt; slips this specific check - but those usually do open a pool and get caught by the rest of the pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicated, on purpose.&lt;/strong&gt; The same map and check live in both &lt;code&gt;mempool-watcher&lt;/code&gt; (the cheap pre-filter) and &lt;code&gt;contract-analyzer&lt;/code&gt; (the authoritative signal). The list is ten lines and changes maybe twice a year; a shared package would be more ceremony than it is worth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When the pre-filter enqueues an address, &lt;code&gt;contract-analyzer&lt;/code&gt; runs the same check as a first-class signal and the scorer applies a hard floor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// risk-assessment.ts - impersonation is high-severity regardless of other signals&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allFlags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;impersonates_major_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fake USDT with no pool and no trading history would otherwise score near zero on a pipeline built around liquidity and honeypot mechanics. The floor says: a contract pretending to be a major asset at a fake address is dangerous on its own, full stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  The result
&lt;/h2&gt;

&lt;p&gt;The deploy-watch path and the impersonation signal went fully live on 2026-06-08. The first two days:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Coverage of the impersonation family&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Before&lt;/strong&gt; (DEX-triggered)&lt;/td&gt;
&lt;td&gt;15 in 4 months - about 0.5% of the wild rate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;After&lt;/strong&gt; (deploy-time)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;77 in 2 days&lt;/strong&gt;: 30 fake USDT, 26 fake USDC, 21 fake DAI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;77 in two days is roughly &lt;strong&gt;270 per week&lt;/strong&gt;, which puts us &lt;strong&gt;above&lt;/strong&gt; the ~192/week the study observed - and every one of them was flagged &lt;strong&gt;at deploy time, with no pool&lt;/strong&gt;, off two &lt;code&gt;eth_call&lt;/code&gt;s. The blind spot is now one of our better-covered families.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we were already doing on top
&lt;/h2&gt;

&lt;p&gt;Credit where it is due: the deploy-watch study is the reason we found this, and its author is clearly building toward the same place we are. The difference is mostly that we have had a head start on the items he lists as "next steps", so if you are building something similar, here is what is worth adding after deploy-time metadata:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real buy/sell simulation.&lt;/strong&gt; We execute a buy then a sell against the live pair across multiple amounts. A contract that accepts buys and silently reverts sells is a honeypot, no matter how clean its source reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bytecode clustering.&lt;/strong&gt; We hash deployed runtime bytecode and group identical contracts. One scam template gets reused thousands of times; clustering turns "a new scam" into "the 4,050th token off a known factory".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployer and funder graph.&lt;/strong&gt; We trace who deployed a contract and who funded that wallet, through mixers where possible. A throwaway EOA funded one hop from a known scam operator is a strong prior before you read a single opcode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-signal weighted scoring with floors.&lt;/strong&gt; No single heuristic decides. Signals combine into a score, and high-severity ones (like impersonation) set floors so a dangerous contract cannot be averaged down to "safe".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live, not batch.&lt;/strong&gt; All of this runs as contracts appear, not in a nightly sweep.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that helped against FlashUSDT, because FlashUSDT never enters the part of the pipeline where any of it runs. That is the lesson worth keeping: a detector is only as good as its &lt;strong&gt;ingestion&lt;/strong&gt;. The cleverest scoring in the world is useless on a contract you never ingested. We had spent months tuning the scoring and almost none questioning the trigger.&lt;/p&gt;

&lt;p&gt;It took an outside writeup, with a different dataset and a different goal, to make the assumption visible. Thanks for that.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;You can paste any token address into the free detector at &lt;a href="https://rektradar.io" rel="noopener noreferrer"&gt;rektradar.io&lt;/a&gt; to check whether it is the real asset or an impersonator. If you want the user-facing version of how this scam plays out, we wrote that up &lt;a href="https://rektradar.io/blog/posts/fake-usdt-proof-of-funds-scam/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>security</category>
      <category>web3</category>
      <category>typescript</category>
    </item>
  </channel>
</rss>
