<?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: Sanjeev Kumar</title>
    <description>The latest articles on DEV Community by Sanjeev Kumar (@sanjeevkkansal).</description>
    <link>https://dev.to/sanjeevkkansal</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3944644%2Fdc739745-788c-42e6-a963-1f0aa7fe5e0f.png</url>
      <title>DEV Community: Sanjeev Kumar</title>
      <link>https://dev.to/sanjeevkkansal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sanjeevkkansal"/>
    <language>en</language>
    <item>
      <title>What one week of new Ethereum contracts looks like through a scam-detection lens</title>
      <dc:creator>Sanjeev Kumar</dc:creator>
      <pubDate>Sat, 06 Jun 2026 06:46:02 +0000</pubDate>
      <link>https://dev.to/sanjeevkkansal/what-one-week-of-new-ethereum-contracts-looks-like-through-a-scam-detection-lens-599n</link>
      <guid>https://dev.to/sanjeevkkansal/what-one-week-of-new-ethereum-contracts-looks-like-through-a-scam-detection-lens-599n</guid>
      <description>&lt;p&gt;I pulled every top-level contract deployed on Ethereum mainnet in the week ending 2026-04-10. There were 8,257 of them. I enriched each one with its deployed bytecode, verified Solidity source where available, and its deployer's first-tx history. Then I scored each contract against five rule families (contract name, bytecode shape, source patterns, deployer reputation, known-drainer hashes) and used Claude Opus 4.7 to write structured walkthroughs for the top scores.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Method
&lt;/h2&gt;

&lt;p&gt;The pipeline is four stages. Each stage writes JSON to disk so the run is resumable and the dataset is inspectable end-to-end.&lt;/p&gt;

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

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

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

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

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

&lt;h2&gt;
  
  
  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;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Top-level deployments in the week&lt;/td&gt;
&lt;td&gt;8,257&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unique deployers&lt;/td&gt;
&lt;td&gt;2,428&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verified source rate&lt;/td&gt;
&lt;td&gt;32.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Contracts with empty runtime bytecode&lt;/td&gt;
&lt;td&gt;580 (7.0%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total findings&lt;/td&gt;
&lt;td&gt;~6,600&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BLOCKER-severity contracts&lt;/td&gt;
&lt;td&gt;192&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-blocker (score &amp;gt;= 0.8)&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Score distribution:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bucket&lt;/th&gt;
&lt;th&gt;Contracts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.0 (no findings)&lt;/td&gt;
&lt;td&gt;2,698 (32.7%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.0 - 0.2&lt;/td&gt;
&lt;td&gt;4,441 (53.8%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.2 - 0.4&lt;/td&gt;
&lt;td&gt;911 (11.0%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.4 - 0.6&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.6 - 0.8&lt;/td&gt;
&lt;td&gt;153&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.8 - 1.0&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;h2&gt;
  
  
  The 192 are one family
&lt;/h2&gt;

&lt;p&gt;Top 10 verified contract names in the dataset:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FlashUSDT&lt;/td&gt;
&lt;td&gt;134&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ERC1967Proxy&lt;/td&gt;
&lt;td&gt;119&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MintBurnTeamToken&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EVMToken&lt;/td&gt;
&lt;td&gt;111&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SimpleSwapToken&lt;/td&gt;
&lt;td&gt;102&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TransparentUpgradeableProxy&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FlashUSDTLiquidityBot&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forwarder&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ProxyAdmin&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

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

&lt;h2&gt;
  
  
  Worked example: FlashUSDT
&lt;/h2&gt;

&lt;p&gt;Contract: &lt;a href="https://etherscan.io/address/0x07d0aacdb2603ed053f2a362c0d4ac618a46f1dc" rel="noopener noreferrer"&gt;0x07d0aacdb2603ed053f2a362c0d4ac618a46f1dc&lt;/a&gt;&lt;br&gt;
Score: 0.90 (&lt;code&gt;blocker&lt;/code&gt;)&lt;br&gt;
Findings: &lt;code&gt;name:high-confidence:FlashUSDT&lt;/code&gt;, &lt;code&gt;deployer:fresh-eoa&lt;/code&gt; (deployer's first tx 0.2 days before deployment), &lt;code&gt;source:owner-mint&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Claude's walkthrough, verbatim:&lt;/p&gt;

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

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

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

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

&lt;p&gt;&lt;strong&gt;Verdict.&lt;/strong&gt; &lt;code&gt;matches_scam_pattern&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Worked example: FlashUSDTLiquidityBot
&lt;/h2&gt;

&lt;p&gt;Contract: &lt;a href="https://etherscan.io/address/0x42ca40f4d556c50d943de4b6bf427925bba8adc4" rel="noopener noreferrer"&gt;0x42ca40f4d556c50d943de4b6bf427925bba8adc4&lt;/a&gt;&lt;br&gt;
Score: 0.76 (&lt;code&gt;blocker&lt;/code&gt;)&lt;br&gt;
Findings: &lt;code&gt;name:high-confidence:FlashUSDTLiquidityBot&lt;/code&gt;, &lt;code&gt;deployer:fresh-eoa&lt;/code&gt;&lt;/p&gt;

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

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

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Band&lt;/th&gt;
&lt;th&gt;yes&lt;/th&gt;
&lt;th&gt;no&lt;/th&gt;
&lt;th&gt;unclear&lt;/th&gt;
&lt;th&gt;Precision (where decided)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;very-high (&amp;gt;= 0.8)&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;high (0.6 - 0.8)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;medium-high (0.4 - 0.6)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;medium (0.2 - 0.4)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;low (0.0 - 0.2)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&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;&lt;strong&gt;ScamSniffer cross-reference.&lt;/strong&gt; I downloaded ScamSniffer's public blacklist (2,530 addresses) and checked overlap with the 192 flagged contracts and their 59 unique deployers. Zero overlap. This is a real finding, not a bug. Public scam lists are weeks-to-months lagging indicators: they require a victim to report, a maintainer to confirm, and an entry to land. Heuristics that run at deploy time produce a lead-time signal that public lists structurally cannot match.&lt;/p&gt;
&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;The dataset and the analyzer both have honest gaps. Calling them out:&lt;/p&gt;

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

&lt;p&gt;The whole pipeline is one Python project. Everything ships open source under MIT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/sanjeevkkansal/evm-deploy-watch
&lt;span class="nb"&gt;cd &lt;/span&gt;evm-deploy-watch
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev,narrate]"&lt;/span&gt;

&lt;span class="c"&gt;# Set keys in .env:&lt;/span&gt;
&lt;span class="c"&gt;#   QUICKNODE=https://your-quicknode-mainnet-url&lt;/span&gt;
&lt;span class="c"&gt;#   ETHERSCAN_API_KEY=...&lt;/span&gt;
&lt;span class="c"&gt;#   ANTHROPIC_API_KEY=...   (only for the narrate phase)&lt;/span&gt;

&lt;span class="c"&gt;# Reproduce this week's run:&lt;/span&gt;
evm-deploy-watch find-blocks &lt;span class="nt"&gt;--start&lt;/span&gt; 2026-04-03T00:00:00Z &lt;span class="nt"&gt;--end&lt;/span&gt; 2026-04-10T00:00:00Z
evm-deploy-watch ingest &lt;span class="nt"&gt;--start-block&lt;/span&gt; 24795371 &lt;span class="nt"&gt;--end-block&lt;/span&gt; 24845605 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; data/window_2026_04_w1.json &lt;span class="nt"&gt;--concurrency&lt;/span&gt; 10
evm-deploy-watch enrich &lt;span class="nt"&gt;--input&lt;/span&gt; data/window_2026_04_w1.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--contracts-dir&lt;/span&gt; data/contracts &lt;span class="nt"&gt;--deployers-dir&lt;/span&gt; data/deployers
evm-deploy-watch score &lt;span class="nt"&gt;--window&lt;/span&gt; data/window_2026_04_w1.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--contracts-dir&lt;/span&gt; data/contracts &lt;span class="nt"&gt;--deployers-dir&lt;/span&gt; data/deployers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; data/scored_2026_04_w1.json
evm-deploy-watch narrate &lt;span class="nt"&gt;--scored&lt;/span&gt; data/scored_2026_04_w1.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--window&lt;/span&gt; data/window_2026_04_w1.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--contracts-dir&lt;/span&gt; data/contracts &lt;span class="nt"&gt;--deployers-dir&lt;/span&gt; data/deployers &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output-dir&lt;/span&gt; data/narratives &lt;span class="nt"&gt;--n&lt;/span&gt; 20
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Total runtime: ~2 hours for ingest + enrich. Total cost: ~$3 for narration on Claude Opus 4.7 if you run it.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  What I'd add next
&lt;/h2&gt;

&lt;p&gt;In rough order of payoff:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

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

&lt;p&gt;Everything is in &lt;a href="https://github.com/sanjeevkkansal/evm-deploy-watch" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;. Issues, PRs, and disagreement welcome.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>blockchain</category>
      <category>ethereum</category>
      <category>security</category>
    </item>
    <item>
      <title>HashiCorp built an MCP server for writing Terraform. I built one for reviewing it</title>
      <dc:creator>Sanjeev Kumar</dc:creator>
      <pubDate>Thu, 21 May 2026 18:11:24 +0000</pubDate>
      <link>https://dev.to/sanjeevkkansal/hashicorp-built-an-mcp-server-for-writing-terraform-i-built-one-for-reviewing-it-9mn</link>
      <guid>https://dev.to/sanjeevkkansal/hashicorp-built-an-mcp-server-for-writing-terraform-i-built-one-for-reviewing-it-9mn</guid>
      <description>&lt;p&gt;A few weeks ago HashiCorp shipped &lt;a href="https://github.com/hashicorp/terraform-mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;terraform-mcp-server&lt;/code&gt;&lt;/a&gt;. It's an official MCP server that lets a model lean on the Terraform Registry: search providers, pull module docs, manage HCP Terraform workspaces. The shape is "help the model author IaC." That's a genuinely useful tool and a clear signal that Terraform-via-MCP is now a category, not a curiosity.&lt;/p&gt;

&lt;p&gt;But it doesn't help with the part of Terraform I spend the most time stressed about: reviewing somebody else's plan.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;terraform plan&lt;/code&gt; outputs are long. Real production plans run into the thousands of lines. The risky changes (an IAM grant going &lt;code&gt;*&lt;/code&gt;, a security group opening to &lt;code&gt;0.0.0.0/0&lt;/code&gt;, an RDS instance being replaced) hide between a hundred routine attribute updates. Code review tools don't help because the danger isn't in the HCL diff, it's in the planned actions.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/sanjeevkkansal/tf-review-mcp" rel="noopener noreferrer"&gt;&lt;code&gt;tf-review-mcp&lt;/code&gt;&lt;/a&gt;. It's an MCP server scoped to one job: parse &lt;code&gt;terraform show -json&lt;/code&gt; output and surface what a human reviewer actually cares about, structured for an LLM to quote and reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Two tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;review_plan(plan_json_path)&lt;/code&gt; returns a structured summary: action counts, high-blast-radius resource changes, stateful destroys, and diff-aware public-exposure findings.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;suggest_review_comments(plan_json_path)&lt;/code&gt; returns a list of &lt;code&gt;{address, severity, comment}&lt;/code&gt; objects ready to drop into a PR review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The server flags three things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High-risk resource types&lt;/strong&gt; (warn). A conservative built-in list across AWS, GCP, and Azure: IAM, KMS, RDS, security groups, S3, EKS, GKE, Cloud SQL, GCS, Cloud DNS, GCE firewalls, Key Vault. Easy to extend in source.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stateful destroys&lt;/strong&gt; (blocker). When a stateful resource like &lt;code&gt;aws_db_instance&lt;/code&gt;, &lt;code&gt;google_sql_database_instance&lt;/code&gt;, or &lt;code&gt;google_compute_instance&lt;/code&gt; is scheduled for delete or replace. The GCE case matters more than people realize: a &lt;code&gt;replace&lt;/code&gt; on a Compute Engine VM with local SSDs nukes both the boot disk and any local-SSD attachments. Silent data loss if a reviewer misses it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Public exposure changes&lt;/strong&gt; (blocker). Diff-aware. The server compares &lt;code&gt;before&lt;/code&gt; and &lt;code&gt;after&lt;/code&gt; on every &lt;code&gt;google_compute_firewall&lt;/code&gt; change and fires when &lt;code&gt;source_ranges&lt;/code&gt; newly contains &lt;code&gt;0.0.0.0/0&lt;/code&gt; or &lt;code&gt;::/0&lt;/code&gt;. That single check has caught more dumb mistakes for me than any static analyzer.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like
&lt;/h2&gt;

&lt;p&gt;Given a plan that deletes a Cloud SQL instance, replaces a GCE VM, and widens a firewall to the public internet, the server returns:&lt;br&gt;
&lt;/p&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;"counts"&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="nl"&gt;"create"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"update"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"replace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"delete"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stateful_destroys"&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="w"&gt;
    &lt;/span&gt;&lt;span class="p"&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;"google_compute_instance.op_geth"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&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="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;"google_sql_database_instance.indexer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"public_exposure_changes"&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="w"&gt;
    &lt;/span&gt;&lt;span class="p"&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;"google_compute_firewall.rpc_public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"finding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"source_ranges now includes 0.0.0.0/0 (public exposure)"&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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"notes"&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="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"2 stateful resource(s) scheduled for destroy/replace. Verify backups and migration plan before applying."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"1 firewall change(s) widen public exposure. Confirm intent before applying."&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;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;Asked to summarize, Claude reads this and produces:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Blocker-severity items (3)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;google_compute_instance.op_geth&lt;/code&gt;: Delete/recreate of a stateful compute instance. Confirm backup, migration, and rollback plan before merging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google_sql_database_instance.indexer&lt;/code&gt;: Cloud SQL instance is being deleted. Confirm backup, migration, and rollback plan before merging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google_compute_firewall.rpc_public&lt;/code&gt;: &lt;code&gt;source_ranges&lt;/code&gt; now includes &lt;code&gt;0.0.0.0/0&lt;/code&gt; (public exposure). Confirm this is intentional and matches firewall policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a PR review comment I'd actually merge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why MCP, not a CLI
&lt;/h2&gt;

&lt;p&gt;The same parser could be a CLI. The reason MCP matters is the conversational shape. When I'm reviewing a plan, I want to ask follow-up questions: "is the SQL delete a replace or a hard destroy?", "what changed about that firewall?", "draft me a PR comment for the IAM grant." The model can pull the right tool, narrow on the right resource, and write something useful, because the underlying tool returns &lt;em&gt;data&lt;/em&gt;, not prose. Lower hallucination surface. Higher signal per token.&lt;/p&gt;

&lt;p&gt;Most community Terraform-MCP experiments wrap the CLI ("run &lt;code&gt;terraform plan&lt;/code&gt; for me"). That's the wrong abstraction for review. You don't want the model running plan; you want it reasoning about a plan that already ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture and deployment
&lt;/h2&gt;

&lt;p&gt;The parser is in &lt;code&gt;review.py&lt;/code&gt; as pure functions with no MCP imports: plan JSON in, &lt;code&gt;ReviewSummary&lt;/code&gt; out. The MCP wrapper is in &lt;code&gt;server.py&lt;/code&gt;, about a hundred lines. The split means the parser can be unit-tested, dropped into CI, or audited without touching the transport.&lt;/p&gt;

&lt;p&gt;Deployment is local stdio. Always. The MCP client launches the server as a child process. Plan JSON never leaves your laptop. No auth, no shared state, no network listener.&lt;/p&gt;

&lt;p&gt;Plan JSON contains IAM relationships, account IDs, security group rules, and resource counts. Hosted "send us your plan" services are a recon goldmine waiting to happen. The architecture should make leakage impossible by default. A self-hosted HTTP variant for CI is a v2 idea, with auth and a path allowlist, not before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is differentiated
&lt;/h2&gt;

&lt;p&gt;HashiCorp's official server covers Registry lookup and HCP workspace management. Helpful for writing.&lt;/p&gt;

&lt;p&gt;This one covers plan review. Helpful for reviewing.&lt;/p&gt;

&lt;p&gt;Both ship as MCP servers. Both can be installed in the same client. The model picks the right tool per task. They're complementary by design, not competitive.&lt;/p&gt;

&lt;p&gt;I haven't found another MCP server scoped specifically to plan review with LLM-friendly structured output, as of writing. If one exists, I want to know.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;estimate_cost_delta&lt;/code&gt; wrapping Infracost.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_policy&lt;/code&gt; wrapping Conftest, so teams can bring their own Rego.&lt;/li&gt;
&lt;li&gt;Configurable &lt;code&gt;HIGH_RISK_TYPES&lt;/code&gt; via a YAML file so each team can codify its own blast-radius rules.&lt;/li&gt;
&lt;li&gt;More diff-aware checks: &lt;code&gt;aws_security_group&lt;/code&gt; ingress widening, IAM &lt;code&gt;*&lt;/code&gt; grants, &lt;code&gt;google_storage_bucket&lt;/code&gt; &lt;code&gt;force_destroy&lt;/code&gt; toggles.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The repo lives here: &lt;a href="https://github.com/sanjeevkkansal/tf-review-mcp" rel="noopener noreferrer"&gt;github.com/sanjeevkkansal/tf-review-mcp&lt;/a&gt;. It's MIT-licensed, v0.2, and very much open to PRs, especially if you have a war story about a plan that should have been caught.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sanjeev Kumar is an infrastructure engineer working on platform tooling, AI-assisted ops, and infra that doesn't wake people up at 3am.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>terraform</category>
      <category>mcp</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
