<?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: Low Key Builds</title>
    <description>The latest articles on DEV Community by Low Key Builds (@lowkeybuilds).</description>
    <link>https://dev.to/lowkeybuilds</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%2F4181324%2F391d601f-aae5-4a33-97a0-84240cadeb8d.png</url>
      <title>DEV Community: Low Key Builds</title>
      <link>https://dev.to/lowkeybuilds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lowkeybuilds"/>
    <language>en</language>
    <item>
      <title>I decoded every failed transaction in 40 Ethereum blocks and 150 Base blocks. Here is why they fail.</title>
      <dc:creator>Low Key Builds</dc:creator>
      <pubDate>Tue, 13 Oct 2026 09:42:39 +0000</pubDate>
      <link>https://dev.to/lowkeybuilds/i-decoded-every-failed-transaction-in-40-ethereum-blocks-and-150-base-blocks-here-is-why-they-fail-4e2f</link>
      <guid>https://dev.to/lowkeybuilds/i-decoded-every-failed-transaction-in-40-ethereum-blocks-and-150-base-blocks-here-is-why-they-fail-4e2f</guid>
      <description>&lt;p&gt;"Transaction failed" on a block explorer tells you almost nothing. "Fail with error 'execution reverted'" tells you less. So I took every failed transaction from a recent run of blocks on Ethereum mainnet and on Base, replayed each one against the state of the previous block, decoded the revert data, and counted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I built the decoder used here, &lt;a href="https://pitcrew.appin60.com" rel="noopener noreferrer"&gt;Pitcrew&lt;/a&gt;. Its free web version is linked at the end. The sampling script and raw results are linked below, so you can rerun the numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sample
&lt;/h2&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;Ethereum&lt;/th&gt;
&lt;th&gt;Base&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Blocks&lt;/td&gt;
&lt;td&gt;26,182,107 to 26,182,146 (40)&lt;/td&gt;
&lt;td&gt;52,543,866 to 52,544,015 (150)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transactions&lt;/td&gt;
&lt;td&gt;11,080&lt;/td&gt;
&lt;td&gt;30,809&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failed&lt;/td&gt;
&lt;td&gt;401 (3.6%)&lt;/td&gt;
&lt;td&gt;2,745 (8.9%)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Method: for each failed receipt, fetch the transaction, &lt;code&gt;eth_call&lt;/code&gt; it with the same from/to/data/value/gas against the parent block, then decode the revert data: &lt;code&gt;Error(string)&lt;/code&gt;, &lt;code&gt;Panic(uint256)&lt;/code&gt;, or a custom error selector looked up in the openchain signature database. Free public RPCs only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ethereum: why the 401 failed
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;50.1%&lt;/td&gt;
&lt;td&gt;201&lt;/td&gt;
&lt;td&gt;21,000-gas ETH send to an EIP-7702 account (all 0.0000001 ETH dust)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27.4%&lt;/td&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;td&gt;state changed earlier in the same block (lost a race: MEV/arb/front-run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.7%&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;reverted with no reason&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6.5%&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;slippage / price moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.5%&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;out of gas (gas limit too low)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.0%&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;balance or allowance too low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.7%&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;other require() message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.0%&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;custom error (needs ABI/signature lookup)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Half of all failures were one pattern: 21,000 gas sent to EIP-7702 accounts
&lt;/h3&gt;

&lt;p&gt;201 of the 401 failures were plain ETH sends (no calldata) with a gas limit of exactly 21,000, and every one used all of it. All 50 recipients had EIP-7702 delegation code (&lt;code&gt;0xef0100...&lt;/code&gt;), pointing at, among others, MetaMask's delegator and Uniswap's Calibur account.&lt;/p&gt;

&lt;p&gt;Since Pectra, an EOA can delegate to contract code. When you send it ETH, that code runs, so 21,000 gas is no longer enough. Anything that hard-codes 21,000 for "simple ETH transfers" (scripts, payout bots, older wallet code) will fail against these accounts every time, and retrying with the same limit changes nothing.&lt;/p&gt;

&lt;p&gt;To be honest about this sample: every one of these 201 sends was 0.0000001 ETH from 52 addresses retrying 5 to 8 times in eight minutes. That looks like automated dusting, not people losing money. The failure mode is still real for any payout code that sends ETH with a fixed 21,000 gas. &lt;strong&gt;Fix:&lt;/strong&gt; estimate gas for ETH sends, or detect &lt;code&gt;0xef0100&lt;/code&gt; code on the recipient and raise the limit.&lt;/p&gt;

&lt;h3&gt;
  
  
  A quarter could not be reproduced: you lost a race
&lt;/h3&gt;

&lt;p&gt;110 failures (27%) succeeded when replayed against the previous block's state. They failed because a transaction earlier in the same block changed something: a price, a reserve, a nonce. These are mostly arbitrage and MEV bots losing races, but if you see this on your own swap, someone else's transaction landed first.&lt;/p&gt;

&lt;h3&gt;
  
  
  The ones that hurt people: slippage, balance and allowance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Slippage guards (&lt;code&gt;INSUFFICIENT_OUTPUT_AMOUNT&lt;/code&gt;, &lt;code&gt;Too little received&lt;/code&gt;, &lt;code&gt;Min return not reached&lt;/code&gt;): 26&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ERC20: transfer amount exceeds balance&lt;/code&gt;, &lt;code&gt;STF&lt;/code&gt; (Uniswap's "safeTransferFrom failed", usually a missing approval or balance): 8&lt;/li&gt;
&lt;li&gt;Out of gas with a real call: 10&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;STF&lt;/code&gt; deserves a special mention because nobody knows what it means. It is Uniswap V3's &lt;code&gt;TransferHelper&lt;/code&gt; saying &lt;code&gt;transferFrom&lt;/code&gt; failed: the router could not pull your tokens. Check the allowance first, then the balance (and fee-on-transfer tokens).&lt;/p&gt;

&lt;h2&gt;
  
  
  Base: why the 2,745 failed
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Cause&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;58.9%&lt;/td&gt;
&lt;td&gt;1616&lt;/td&gt;
&lt;td&gt;state changed earlier in the same block (lost a race: MEV/arb/front-run)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15.0%&lt;/td&gt;
&lt;td&gt;413&lt;/td&gt;
&lt;td&gt;custom error (needs ABI/signature lookup)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14.4%&lt;/td&gt;
&lt;td&gt;394&lt;/td&gt;
&lt;td&gt;other require() message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8.1%&lt;/td&gt;
&lt;td&gt;221&lt;/td&gt;
&lt;td&gt;reverted with no reason&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.8%&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;td&gt;slippage / price moved&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;balance or allowance too low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.4%&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;out of gas (gas limit too low)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Base fails 2.5 times as often as Ethereum, and its failures are mostly bots. 59% are lost races: they succeed when replayed a block earlier, and 800 distinct senders produced 2,745 failures, with the top sender (&lt;code&gt;0xc0ffeebabe...&lt;/code&gt;) alone accounting for 98. Gas on Base is cheap enough that bots fire speculative transactions and accept that most will fail.&lt;/p&gt;

&lt;p&gt;The revert strings show it: the top messages after "no reason" are &lt;code&gt;sl&lt;/code&gt;, &lt;code&gt;SPL&lt;/code&gt;, &lt;code&gt;SPL1&lt;/code&gt;, &lt;code&gt;sbl1&lt;/code&gt;, &lt;code&gt;:(&lt;/code&gt; and &lt;code&gt;LEG_1_FAILED&lt;/code&gt;. Those are private bot contracts saving bytes, not anything a user would see.&lt;/p&gt;

&lt;p&gt;Human-facing failures are a small share on Base: slippage guards (&lt;code&gt;Min return not reached&lt;/code&gt;, &lt;code&gt;TooMuchSlippage&lt;/code&gt;, &lt;code&gt;QuoteBelowMin&lt;/code&gt;, &lt;code&gt;OutputTooLow&lt;/code&gt;) 2.8%, balance and allowance (&lt;code&gt;transferFrom reverted&lt;/code&gt;, &lt;code&gt;STF&lt;/code&gt;) 0.5%. 15% end in a custom error, and many of those selectors are not in any public signature database, so an explorer shows only hex. If you are a person debugging your own failed Base transaction, the most likely causes are still slippage and allowance. Just don't expect the network-wide numbers to look like your experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The script is about 80 lines of Node with no API keys: &lt;a href="https://github.com/lowkey-studio/x402doctor-data" rel="noopener noreferrer"&gt;failed_txs.mjs&lt;/a&gt;. It runs &lt;code&gt;eth_getBlockReceipts&lt;/code&gt; per block, then replays each failure at &lt;code&gt;block - 1&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decode your own
&lt;/h2&gt;

&lt;p&gt;If you have a failed or stuck transaction, paste the hash into the free checker: &lt;strong&gt;&lt;a href="https://pitcrew-api.appin60.com/app" rel="noopener noreferrer"&gt;https://pitcrew-api.appin60.com/app&lt;/a&gt;&lt;/strong&gt; (Ethereum, Base, Arbitrum, Optimism, Polygon, BSC; 20 free checks a day). It returns the decoded revert reason, gas used vs limit, token transfers and a plain-language fix. Stuck transactions get the nonce-gap and replace/cancel recipe. If your agent needs it, the same call is pay-per-use over x402 at $0.01 (&lt;code&gt;GET /tx/explain?chain=base&amp;amp;hash=0x...&lt;/code&gt;, USDC on Base or Arbitrum).&lt;/p&gt;

&lt;p&gt;Caveat: replaying against the parent block is an approximation. When an earlier transaction in the same block changed the state, the replay succeeds and the tool says so instead of guessing.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>web3</category>
      <category>blockchain</category>
      <category>debugging</category>
    </item>
    <item>
      <title>How email verifiers work (SMTP RCPT in 60 lines of Node), and why they fail on 56% of startups</title>
      <dc:creator>Low Key Builds</dc:creator>
      <pubDate>Tue, 13 Oct 2026 08:35:33 +0000</pubDate>
      <link>https://dev.to/lowkeybuilds/how-email-verifiers-work-smtp-rcpt-in-60-lines-of-node-and-why-they-fail-on-56-of-startups-4565</link>
      <guid>https://dev.to/lowkeybuilds/how-email-verifiers-work-smtp-rcpt-in-60-lines-of-node-and-why-they-fail-on-56-of-startups-4565</guid>
      <description>&lt;p&gt;Email verification tools mostly do one thing under the hood: they open an SMTP connection to the domain's mail server and ask whether a mailbox exists, then hang up before sending anything. Here is how that works, in about 60 lines of Node, and what happened when I ran it against 499 Y Combinator companies.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built &lt;a href="https://mailferret.appin60.com" rel="noopener noreferrer"&gt;MailFerret&lt;/a&gt;, a pay-per-call email finder for AI agents that uses this technique. The code below is the core idea, free to copy.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The SMTP conversation
&lt;/h2&gt;

&lt;p&gt;Every domain that receives email publishes MX records. Connect to the first one on port 25 and you get a short dialogue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;S: 220 mx.google.com ESMTP
C: EHLO probe.example.com
S: 250-mx.google.com at your service
C: MAIL FROM:&amp;lt;verify@example.com&amp;gt;
S: 250 2.1.0 OK
C: RCPT TO:&amp;lt;paul@supabase.com&amp;gt;
S: 250 2.1.5 OK                      &amp;lt;- mailbox exists
C: RCPT TO:&amp;lt;qzxv81kd@supabase.com&amp;gt;
S: 550 5.1.1 The email account that you tried to reach does not exist
C: QUIT
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;250&lt;/code&gt; on &lt;code&gt;RCPT TO&lt;/code&gt; means "I'll accept mail for this address". &lt;code&gt;550 5.1.1&lt;/code&gt; means "no such user". You never send &lt;code&gt;DATA&lt;/code&gt;, so no email is delivered.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimal Node implementation
&lt;/h2&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="nx"&gt;net&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;node:net&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;promises&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;dns&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;node:dns&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rcptCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;addresses&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;mx&lt;/span&gt; &lt;span class="o"&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;dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolveMx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&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;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&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="nx"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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;sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createConnection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&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;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buf&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;send&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&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;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;sock&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="mi"&gt;12000&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;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nx"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;close&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&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;d&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="nx"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;d&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;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trimEnd&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;pop&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="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\d{3}&lt;/span&gt;&lt;span class="sr"&gt; /&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;last&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;// wait for the final line of a multi-line reply&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="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;last&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;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="nx"&gt;buf&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="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EHLO probe.example.com&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="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MAIL FROM:&amp;lt;verify@example.com&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="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`RCPT TO:&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;addresses&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="s2"&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;4&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;// reply to QUIT&lt;/span&gt;
      &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&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="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;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`RCPT TO:&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;addresses&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;QUIT&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="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// First address is random: if the server accepts it, the domain is catch-all.&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;await&lt;/span&gt; &lt;span class="nf"&gt;rcptCheck&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resend.com&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;qzxv81kd3j@resend.com&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;zeno.rocha@resend.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
&lt;span class="c1"&gt;// { 'qzxv81kd3j@resend.com': 550, 'zeno.rocha@resend.com': 250 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To &lt;em&gt;find&lt;/em&gt; an address rather than check one, generate the usual patterns (&lt;code&gt;first.last&lt;/code&gt;, &lt;code&gt;first&lt;/code&gt;, &lt;code&gt;flast&lt;/code&gt;, &lt;code&gt;firstlast&lt;/code&gt;, &lt;code&gt;f.last&lt;/code&gt;...) and RCPT them in order in one session. The first &lt;code&gt;250&lt;/code&gt; on a domain that rejected the random address is your answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch: catch-all domains
&lt;/h2&gt;

&lt;p&gt;Some domains accept mail for any address. Send &lt;code&gt;RCPT TO:&amp;lt;anything@domain&amp;gt;&lt;/code&gt; and you get &lt;code&gt;250&lt;/code&gt;. On those domains SMTP verification can't tell real mailboxes from fake ones. That's why the snippet probes a random address first.&lt;/p&gt;

&lt;p&gt;How common is that? I took 499 random active companies from the public &lt;a href="https://github.com/yc-oss/api" rel="noopener noreferrer"&gt;yc-oss&lt;/a&gt; YC list and checked each website domain:&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;Count&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Google Workspace&lt;/td&gt;
&lt;td&gt;409&lt;/td&gt;
&lt;td&gt;82%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microsoft 365&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No MX on the website domain&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Catch-all&lt;/strong&gt; (of 456 with mail)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;254&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;56%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verifiable (random address rejected)&lt;/td&gt;
&lt;td&gt;177&lt;/td&gt;
&lt;td&gt;39%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refused to answer (Proofpoint, Cloudflare routing, SES inbound...)&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On Google Workspace alone, 60% of these startups had catch-all routing on. So for founders' addresses, more than half the "valid" results you get from any SMTP-based verifier are really "unknown". Full numbers and method: &lt;a href="https://mailferret.appin60.com/study" rel="noopener noreferrer"&gt;mailferret.appin60.com/study&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things that bite in production
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Port 25 is blocked&lt;/strong&gt; on most cloud hosts (AWS, GCP, Azure, Vercel, Workers). You need a box where outbound 25 is open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP reputation matters.&lt;/strong&gt; Cloudflare Email Routing answered our residential IP with &lt;code&gt;550&lt;/code&gt; in the banner; Proofpoint and some others just time out. Treat those as "unknown", not "invalid".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Greylisting&lt;/strong&gt; returns &lt;code&gt;4xx&lt;/code&gt; the first time. Retry later or report unknown. One useful exception: Google's &lt;code&gt;450 4.2.1 The user you are trying to contact is receiving mail at a rate...&lt;/code&gt; is only sent for mailboxes that exist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't hammer one server.&lt;/strong&gt; Batch all candidates into one session and cache catch-all results per domain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Large TXT records&lt;/strong&gt; (SPF/DMARC) can time out through some local stub resolvers; querying 1.1.1.1 / 8.8.8.8 directly fixed it for me.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy rejections aren't "user unknown".&lt;/strong&gt; Parse the text: &lt;code&gt;5.1.1&lt;/code&gt; / "does not exist" means no mailbox; "blocked", "Spamhaus", "policy" means they refused you.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  If you'd rather not run it yourself
&lt;/h2&gt;

&lt;p&gt;MailFerret wraps this as an HTTP API that AI agents pay per call with &lt;a href="https://x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt; (USDC on Base, no API key):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;POST /find&lt;/code&gt; &lt;code&gt;{first_name, last_name, domain}&lt;/code&gt;: $0.01, charged only when the mail server confirms the mailbox. Catch-all and not-found results are free.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /verify&lt;/code&gt; &lt;code&gt;{email}&lt;/code&gt;: $0.003.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST /domain&lt;/code&gt; &lt;code&gt;{domain}&lt;/code&gt;: $0.005 for mail provider, catch-all status, SaaS tools revealed by SPF, DMARC, public emails and the likely pattern.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From Claude Code or Codex with AgentCash: &lt;code&gt;npx agentcash fetch https://mailferret.appin60.com/find -m POST -b '{"first_name":"Zeno","last_name":"Rocha","domain":"resend.com"}'&lt;/code&gt;. There are 5 free lookups a day on the &lt;a href="https://mailferret.appin60.com" rel="noopener noreferrer"&gt;homepage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Cleaning a whole list instead? The homepage has a bulk CSV option: $4 per 1,000 addresses in USDC, quoted before you pay.&lt;/p&gt;

&lt;p&gt;Use it for legitimate B2B outreach, and follow CAN-SPAM/GDPR when you email anyone.&lt;/p&gt;

</description>
      <category>email</category>
      <category>node</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Who can upgrade this contract? Five RPC calls, five traps, and 146 Morpho vaults</title>
      <dc:creator>Low Key Builds</dc:creator>
      <pubDate>Tue, 13 Oct 2026 08:02:53 +0000</pubDate>
      <link>https://dev.to/lowkeybuilds/who-can-upgrade-this-contract-five-rpc-calls-five-traps-and-146-morpho-vaults-4gm1</link>
      <guid>https://dev.to/lowkeybuilds/who-can-upgrade-this-contract-five-rpc-calls-five-traps-and-146-morpho-vaults-4gm1</guid>
      <description>&lt;p&gt;In 2026, leaked or stolen admin keys overtook smart-contract bugs as the biggest cause of DeFi losses: at least $1.3B in January to August, by &lt;a href="https://hackmag.com/news/defi-private-keys" rel="noopener noreferrer"&gt;one count&lt;/a&gt;. Wasabi Protocol lost about $4.5M in April because a single deployer EOA could upgrade its contracts (&lt;a href="https://www.coindesk.com/tech/2026/04/30/wasabi-protocol-drained-for-usd4-5-million-in-apparent-admin-key-compromise" rel="noopener noreferrer"&gt;CoinDesk&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;An audit tells you whether the code is safe. It does not tell you &lt;strong&gt;who can replace the code tomorrow&lt;/strong&gt;. You can check that yourself in about five RPC calls. This post shows the calls with Foundry's &lt;code&gt;cast&lt;/code&gt;, the traps that produce wrong answers, and what the same check found across 146 Morpho vaults.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Is it a proxy?
&lt;/h2&gt;

&lt;p&gt;Most upgradeable contracts keep the implementation address in a fixed storage slot (EIP-1967):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;RPC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://base-rpc.publicnode.com
&lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913   &lt;span class="c"&gt;# USDC on Base&lt;/span&gt;

&lt;span class="c"&gt;# EIP-1967 implementation slot&lt;/span&gt;
cast storage &lt;span class="nv"&gt;$C&lt;/span&gt; 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;
&lt;span class="c"&gt;# EIP-1967 admin slot (transparent proxies)&lt;/span&gt;
cast storage &lt;span class="nv"&gt;$C&lt;/span&gt; 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;
&lt;span class="c"&gt;# EIP-1967 beacon slot&lt;/span&gt;
cast storage &lt;span class="nv"&gt;$C&lt;/span&gt; 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50 &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Trap 1: older proxies use different slots.&lt;/strong&gt; USDC returns zero for all three above, which looks like "not upgradeable". It is upgradeable: it uses the older ZeppelinOS slots, &lt;code&gt;keccak256("org.zeppelinos.proxy.implementation")&lt;/code&gt; = &lt;code&gt;0x7050c9e0…f8c3&lt;/code&gt; and &lt;code&gt;keccak256("org.zeppelinos.proxy.admin")&lt;/code&gt; = &lt;code&gt;0x10d6a54a…390b&lt;/code&gt;. Check both families.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 2: some admins aren't in storage at all.&lt;/strong&gt; Aave's pool proxies keep the admin as an immutable in the bytecode. You find the authority through the pool's &lt;code&gt;ADDRESSES_PROVIDER()&lt;/code&gt; and that contract's &lt;code&gt;owner()&lt;/code&gt;. Aragon apps (Lido's stETH, for example) have no admin slot either: upgrades go through the DAO kernel's ACL, which you reach with &lt;code&gt;kernel()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Who holds the admin?
&lt;/h2&gt;

&lt;p&gt;If there's an admin slot, read it. If it's a UUPS proxy (implementation set, no admin), the upgrade right usually sits behind &lt;code&gt;owner()&lt;/code&gt; or an AccessControl role:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;cast call &lt;span class="nv"&gt;$C&lt;/span&gt; &lt;span class="s2"&gt;"owner()(address)"&lt;/span&gt; &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;
&lt;span class="c"&gt;# AccessControlEnumerable: who holds DEFAULT_ADMIN_ROLE (0x00)?&lt;/span&gt;
cast call &lt;span class="nv"&gt;$C&lt;/span&gt; &lt;span class="s2"&gt;"getRoleMemberCount(bytes32)(uint256)"&lt;/span&gt; 0x0000000000000000000000000000000000000000000000000000000000000000 &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the admin is an OpenZeppelin &lt;code&gt;ProxyAdmin&lt;/code&gt; contract, it's only a hop: call &lt;code&gt;owner()&lt;/code&gt; on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What kind of key is it?
&lt;/h2&gt;

&lt;p&gt;This is the part that matters, and the part block explorers don't spell out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;A&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x...   &lt;span class="c"&gt;# the address you found&lt;/span&gt;
cast code &lt;span class="nv"&gt;$A&lt;/span&gt; &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;                             &lt;span class="c"&gt;# "0x" means an EOA: one private key&lt;/span&gt;
cast call &lt;span class="nv"&gt;$A&lt;/span&gt; &lt;span class="s2"&gt;"getThreshold()(uint256)"&lt;/span&gt; &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;   &lt;span class="c"&gt;# answers if it's a Safe&lt;/span&gt;
cast call &lt;span class="nv"&gt;$A&lt;/span&gt; &lt;span class="s2"&gt;"getOwners()(address[])"&lt;/span&gt; &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;
cast call &lt;span class="nv"&gt;$A&lt;/span&gt; &lt;span class="s2"&gt;"getMinDelay()(uint256)"&lt;/span&gt; &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;    &lt;span class="c"&gt;# OpenZeppelin TimelockController&lt;/span&gt;
cast call &lt;span class="nv"&gt;$A&lt;/span&gt; &lt;span class="s2"&gt;"delay()(uint256)"&lt;/span&gt; &lt;span class="nt"&gt;--rpc-url&lt;/span&gt; &lt;span class="nv"&gt;$RPC&lt;/span&gt;          &lt;span class="c"&gt;# Compound-style Timelock&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Trap 3: a Safe isn't automatically a multisig.&lt;/strong&gt; A 1-of-1 or 1-of-3 Safe means one signature moves everything. Recurse into its owners: a 1-of-2 Safe whose owners are a timelock and a 9-of-13 Safe is fine, while a 1-of-1 whose owner is an EOA is one key with extra steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 4: "EOA" doesn't mean "hot wallet".&lt;/strong&gt; Circle almost certainly keeps the USDC admin key in an HSM, and institutional curators often sign with MPC. That is invisible on-chain. What you &lt;em&gt;can&lt;/em&gt; say is that there is no on-chain multisig and no delay, so holders get no warning before an upgrade. Report it that way and don't claim more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trap 5: flaky public RPCs.&lt;/strong&gt; A rate-limited node returning an error is easy to read as "empty slot" or "function doesn't exist". Retry against a second endpoint before you conclude anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this found in Morpho vaults
&lt;/h2&gt;

&lt;p&gt;Morpho vaults are a good test because the risky action is specific: a curator who can enable a new market (V1) or add an adapter and raise its cap (V2) can route deposits to a new destination. Both versions put that behind a vault-level timelock.&lt;/p&gt;

&lt;p&gt;I read the configuration of the 146 Morpho vaults holding at least $2M (Morpho API) and resolved every owner and curator key on-chain (scanned 2026-10-13):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All 122 vaults listed in the Morpho app had a timelock on these actions&lt;/strong&gt;, typically 3 to 7 days. That's a real exit window for depositors.&lt;/li&gt;
&lt;li&gt;In 22 of those listed vaults (about $1.3B), the owner or curator is a single EOA or a Safe that resolves to one EOA signer. With the timelock in place, that's a governance-transparency point rather than an emergency, and some of these signers are probably MPC-backed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5 unlisted vaults (about $305M combined, $283M of it in one vault) have a zero timelock and a single-EOA owner or curator&lt;/strong&gt;, so one key can open a new market or adapter immediately. Unlisted vaults are typically private or still being set up, and the Morpho app doesn't surface them, but anything that integrates vault addresses directly should check this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full table, with every address, is at &lt;a href="https://onekeyaway.appin60.com/morpho/" rel="noopener noreferrer"&gt;https://onekeyaway.appin60.com/morpho/&lt;/a&gt; (raw JSON: &lt;a href="https://onekeyaway.appin60.com/morpho/vaults.json" rel="noopener noreferrer"&gt;vaults.json&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://onekeyaway.appin60.com" rel="noopener noreferrer"&gt;OneKeyAway&lt;/a&gt; to do the steps above in one click. Disclosure: it's my project.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A free checker on the page that runs in your browser over public RPC.&lt;/li&gt;
&lt;li&gt;Free email alerts for up to 3 contracts or Safes: you get an email when the implementation, admin, owner, Safe signers, threshold, modules or guard change. That's the job OpenZeppelin Defender's hosted Monitor did before it shut down on 2026-07-01.&lt;/li&gt;
&lt;li&gt;An API for agents and scripts: &lt;code&gt;GET https://onekeyaway.appin60.com/api/preview?chain=8453&amp;amp;address=0x…&lt;/code&gt; returns a free verdict, and &lt;code&gt;/api/control&lt;/code&gt; returns the full path (every admin, Safe threshold and signers, timelock delay) for $0.01 per call over x402 in USDC on Base or Arbitrum. There's no API key, and a failed call isn't charged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd rather do it by hand, the five &lt;code&gt;cast&lt;/code&gt; calls above are all you need. Check the key type, not just the address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Update: Safe modules (answering a reader's question)
&lt;/h2&gt;

&lt;p&gt;&lt;a class="mentioned-user" href="https://dev.to/bloqarl"&gt;@bloqarl&lt;/a&gt; pointed out a sixth trap in the comments: &lt;code&gt;getThreshold()&lt;/code&gt; can say 3-of-5, but an enabled module can call &lt;code&gt;execTransactionFromModule&lt;/code&gt; with no signatures at all, so check &lt;code&gt;getModulesPaginated(0x1, n)&lt;/code&gt; next to &lt;code&gt;getOwners()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I checked all 62 Safes that act as owner or curator of the 146 Morpho vaults. &lt;strong&gt;6 have a module enabled, and none has a transaction guard.&lt;/strong&gt; All 6 are 2-of-5 Safes curating kpk vaults (about $55M across six listed vaults on Ethereum and Base, plus a $4M Fluidkey vault on Base). Each module is a Zodiac Roles modifier that the Safe itself owns. That's a scoped-permissions setup: allow-listed operators can make specific calls without collecting signatures, and the Safe's signers configure what's allowed. It's a deliberate and fairly common pattern, but it does mean the 2-of-5 is not the whole story. What those roles permit is the next thing to read.&lt;/p&gt;

&lt;p&gt;OneKeyAway's Safe check now reports modules, guard, fallback handler and singleton, and the free alerts fire when any of them change.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>security</category>
      <category>web3</category>
      <category>defi</category>
    </item>
    <item>
      <title>Who holds the keys to Base's top 250 tokens? An on-chain check</title>
      <dc:creator>Low Key Builds</dc:creator>
      <pubDate>Tue, 13 Oct 2026 07:54:02 +0000</pubDate>
      <link>https://dev.to/lowkeybuilds/who-holds-the-keys-to-bases-top-250-tokens-an-on-chain-check-3fic</link>
      <guid>https://dev.to/lowkeybuilds/who-holds-the-keys-to-bases-top-250-tokens-an-on-chain-check-3fic</guid>
      <description>&lt;p&gt;I built a small on-chain checker that answers one question about an ERC-20 token: &lt;strong&gt;who can change it after you buy it?&lt;/strong&gt; To test it, I ran it over the 249 largest tokens in CoinGecko's Base-ecosystem category. Here is what came back, how the check works, and where it falls short.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I built the tool (ContractLens). The full per-token table and raw JSON are public: &lt;a href="https://contractlens.appin60.com/base-keys.html" rel="noopener noreferrer"&gt;Who holds the keys to Base's top 250 tokens&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;47&lt;/strong&gt; tokens are canonical bridged copies of L1 tokens (&lt;code&gt;OptimismMintableERC20&lt;/code&gt;, minted only by the OP-stack bridge at &lt;code&gt;0x4200…0010&lt;/code&gt;). A naive scanner flags all of them as "mintable", so I excluded them. That leaves &lt;strong&gt;202 native Base tokens&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;80 of 202 (40%)&lt;/strong&gt; are upgradeable proxies. Upgrade control: 25 Safe multisig, 12 timelock, &lt;strong&gt;13 a single EOA&lt;/strong&gt;, 9 renounced, 7 another contract, 14 not readable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;102&lt;/strong&gt; have a mint function, &lt;strong&gt;53&lt;/strong&gt; can pause transfers, &lt;strong&gt;15&lt;/strong&gt; can blacklist or freeze holders, and &lt;strong&gt;4&lt;/strong&gt; have an owner-adjustable fee.&lt;/li&gt;
&lt;li&gt;Owners: 76 Safe, 9 timelock, 20 single EOA, 13 renounced, 68 with no &lt;code&gt;owner()&lt;/code&gt; at all (mostly AccessControl).&lt;/li&gt;
&lt;li&gt;Upgradeable with a single EOA holding the upgrade key: USDC, CBBTC, USD0, EURC, GTBTC, CBXRP, AUSD, MAGIC, TGBP, HYPER, USUAL, VNX, CBADA.&lt;/li&gt;
&lt;li&gt;52 have no DEX pair on Base that DexScreener knows of, and 41 more have under $10k of Base liquidity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this means "scam". USDC, EURC and cbBTC are meant to be issuer-controlled, and an issuer can guard an EOA with HSMs and approval processes that the chain cannot show. The point is to &lt;em&gt;know&lt;/em&gt; who holds the keys, especially for the long tail.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the check works (about 2 seconds, no source code needed)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Proxy detection.&lt;/strong&gt; Read the EIP-1967 implementation, admin and beacon slots, the older ZeppelinOS slots (USDC on Base still uses them: &lt;code&gt;0x7050c9e0…&lt;/code&gt; for the implementation, &lt;code&gt;0x10d6a54a…&lt;/code&gt; for the admin), and match the EIP-1167 minimal-proxy bytecode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who is in charge.&lt;/strong&gt; Try &lt;code&gt;owner()&lt;/code&gt;, &lt;code&gt;getOwner()&lt;/code&gt;, &lt;code&gt;defaultAdmin()&lt;/code&gt;, &lt;code&gt;getRoleMember(DEFAULT_ADMIN_ROLE, i)&lt;/code&gt;. Classify each address: zero or &lt;code&gt;0x…dead&lt;/code&gt; means renounced, empty code means EOA, an EIP-7702 delegation prefix (&lt;code&gt;0xef0100&lt;/code&gt;) means an EOA too, &lt;code&gt;getThreshold()&lt;/code&gt; plus &lt;code&gt;getOwners()&lt;/code&gt; means a Safe m-of-n, &lt;code&gt;getMinDelay()&lt;/code&gt; means a timelock. If the proxy admin is an OpenZeppelin &lt;code&gt;ProxyAdmin&lt;/code&gt;, follow it to its owner.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What they can do.&lt;/strong&gt; Walk the runtime bytecode, collect every &lt;code&gt;PUSH4&lt;/code&gt; operand and match it against the selectors of known admin functions (&lt;code&gt;mint&lt;/code&gt;, &lt;code&gt;blacklist&lt;/code&gt;, &lt;code&gt;setBots&lt;/code&gt;, &lt;code&gt;pause&lt;/code&gt;, &lt;code&gt;setFee&lt;/code&gt;/&lt;code&gt;setTaxes&lt;/code&gt;, &lt;code&gt;enableTrading&lt;/code&gt;, &lt;code&gt;setMaxWallet&lt;/code&gt;…). For proxies, scan the implementation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bridged tokens.&lt;/strong&gt; If &lt;code&gt;BRIDGE()&lt;/code&gt;/&lt;code&gt;bridge()&lt;/code&gt;/&lt;code&gt;l2Bridge()&lt;/code&gt; returns a canonical bridge, mint is bridge-only and gets a low rating.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market.&lt;/strong&gt; DexScreener for liquidity, 24h buys and sells, and pair age.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The verdict separates &lt;strong&gt;control risk&lt;/strong&gt; (admin powers × who holds them) from &lt;strong&gt;market risk&lt;/strong&gt; (liquidity, age, buys with zero sells).&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A selector scan proves a function exists, not who may call it, and it misses admin functions with unusual names.&lt;/li&gt;
&lt;li&gt;There is no buy/sell simulation, so it cannot rule out a honeypot. Pair it with a simulator.&lt;/li&gt;
&lt;li&gt;AccessControl role holders that are not enumerable on-chain show as "not found". You would need &lt;code&gt;RoleGranted&lt;/code&gt; event history.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Free web form: &lt;a href="https://contractlens.appin60.com" rel="noopener noreferrer"&gt;https://contractlens.appin60.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remote MCP server (free, 50 checks/day): &lt;code&gt;https://contractlens-api.appin60.com/mcp&lt;/code&gt;, tool &lt;code&gt;check_token&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For agents: &lt;code&gt;GET https://contractlens-api.appin60.com/token?chainId=8453&amp;amp;address=0x…&lt;/code&gt; costs $0.005 USDC per call via &lt;a href="https://x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt; on Base or Arbitrum. No API key: the agent pays and gets JSON back. Chains: Ethereum, Base, Arbitrum, Optimism, Polygon, BNB.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Key-change alerts (waitlist):&lt;/strong&gt; I'm considering alerts that email you when a token you hold upgrades its implementation, changes owner or admin, or lowers its Safe threshold. The planned price is $9/month in USDC. If you'd pay for that, join the waitlist at the bottom of &lt;a href="https://contractlens.appin60.com/base-keys.html#alerts" rel="noopener noreferrer"&gt;the study page&lt;/a&gt;. Nothing is charged now.&lt;/p&gt;

&lt;p&gt;If a row in the table is wrong, tell me at &lt;a href="mailto:contractlens@appin60.com"&gt;contractlens@appin60.com&lt;/a&gt; and I will fix the checker.&lt;/p&gt;

</description>
      <category>ethereum</category>
      <category>web3</category>
      <category>security</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>I crawled all 31,620 x402 Bazaar listings: most have one payer, and boring chain reads win</title>
      <dc:creator>Low Key Builds</dc:creator>
      <pubDate>Tue, 13 Oct 2026 07:48:45 +0000</pubDate>
      <link>https://dev.to/lowkeybuilds/i-crawled-all-31620-x402-bazaar-listings-most-have-one-payer-and-boring-chain-reads-win-5c73</link>
      <guid>https://dev.to/lowkeybuilds/i-crawled-all-31620-x402-bazaar-listings-most-have-one-payer-and-boring-chain-reads-win-5c73</guid>
      <description>&lt;p&gt;I crawled every resource in the Coinbase x402 Bazaar on 2026-10-13 before choosing what to sell to AI agents. The discovery API is public (&lt;code&gt;GET https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources?limit=500&amp;amp;offset=N&lt;/code&gt;), and each entry carries a &lt;code&gt;quality&lt;/code&gt; block with &lt;code&gt;l30DaysUniquePayers&lt;/code&gt; and &lt;code&gt;l30DaysTotalCalls&lt;/code&gt;. Those two fields are the closest public measure I know of for what agents actually pay for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Most listings have one payer
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Distinct payers per resource, last 30 days&lt;/th&gt;
&lt;th&gt;Resources&lt;/th&gt;
&lt;th&gt;Share&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;84&lt;/td&gt;
&lt;td&gt;0.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;14,480&lt;/td&gt;
&lt;td&gt;45.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;9,759&lt;/td&gt;
&lt;td&gt;30.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3-4&lt;/td&gt;
&lt;td&gt;5,831&lt;/td&gt;
&lt;td&gt;18.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5-9&lt;/td&gt;
&lt;td&gt;1,057&lt;/td&gt;
&lt;td&gt;3.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10-49&lt;/td&gt;
&lt;td&gt;314&lt;/td&gt;
&lt;td&gt;1.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50+&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;0.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That is 31,620 unique resource URLs on 2,280 hosts. 77% of them had two payers or fewer in 30 days, and only 1.3% reached ten. As far as I can tell, the Bazaar only lists a route after a payment settles through the CDP facilitator, so the lone payer behind most listings is probably the seller's own test call. Supply is not the bottleneck in this market. Buyers are.&lt;/p&gt;

&lt;h2&gt;
  
  
  What gets more than one buyer
&lt;/h2&gt;

&lt;p&gt;I tagged resources by keywords in their URL and description. The categories overlap, and keyword tagging is crude, so read these as directions rather than measurements:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category (keyword match)&lt;/th&gt;
&lt;th&gt;Resources&lt;/th&gt;
&lt;th&gt;Mean payers&lt;/th&gt;
&lt;th&gt;Share with 10+ payers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Chain data / RPC (balance, block, gas, tx, wallet, contract...)&lt;/td&gt;
&lt;td&gt;7,288&lt;/td&gt;
&lt;td&gt;4.01&lt;/td&gt;
&lt;td&gt;2.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Novelty (jokes, facts, coin flips, horoscopes...)&lt;/td&gt;
&lt;td&gt;3,949&lt;/td&gt;
&lt;td&gt;3.43&lt;/td&gt;
&lt;td&gt;1.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;News / social&lt;/td&gt;
&lt;td&gt;1,477&lt;/td&gt;
&lt;td&gt;2.62&lt;/td&gt;
&lt;td&gt;1.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web search / scrape&lt;/td&gt;
&lt;td&gt;4,656&lt;/td&gt;
&lt;td&gt;2.44&lt;/td&gt;
&lt;td&gt;1.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prices / markets&lt;/td&gt;
&lt;td&gt;12,141&lt;/td&gt;
&lt;td&gt;2.19&lt;/td&gt;
&lt;td&gt;0.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM / inference&lt;/td&gt;
&lt;td&gt;2,366&lt;/td&gt;
&lt;td&gt;2.12&lt;/td&gt;
&lt;td&gt;0.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Token risk / safety&lt;/td&gt;
&lt;td&gt;3,351&lt;/td&gt;
&lt;td&gt;1.87&lt;/td&gt;
&lt;td&gt;0.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things surprised me:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Plain chain reads beat clever products.&lt;/strong&gt; The most-paid host after one outlier is &lt;code&gt;api.onesource.io&lt;/code&gt;, whose routes are thin wrappers over &lt;code&gt;eth_getBalance&lt;/code&gt;, &lt;code&gt;eth_blockNumber&lt;/code&gt;, &lt;code&gt;balanceOf&lt;/code&gt; and &lt;code&gt;eth_getLogs&lt;/code&gt;, priced at $0.001-$0.008. Each of its top routes had 200-560 distinct payers in 30 days. Meanwhile 3,351 token-risk and "safety" listings average fewer than two payers. Everyone is building the trust layer, and few agents are buying it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Novelty endpoints outsell most serious ones.&lt;/strong&gt; Single-route Vercel apps serving random facts, coin flips and horror stories had 45-57 distinct payers each, more than 99% of the catalog.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cheap wins, up to a point
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Price per call (USDC)&lt;/th&gt;
&lt;th&gt;Resources&lt;/th&gt;
&lt;th&gt;Mean payers&lt;/th&gt;
&lt;th&gt;Share with 5+ payers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;≤ $0.001&lt;/td&gt;
&lt;td&gt;2,246&lt;/td&gt;
&lt;td&gt;4.08&lt;/td&gt;
&lt;td&gt;13.1%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$0.0011-$0.005&lt;/td&gt;
&lt;td&gt;7,633&lt;/td&gt;
&lt;td&gt;3.74&lt;/td&gt;
&lt;td&gt;6.6%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$0.005-$0.01&lt;/td&gt;
&lt;td&gt;14,787&lt;/td&gt;
&lt;td&gt;1.83&lt;/td&gt;
&lt;td&gt;2.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;$0.0101-$0.05&lt;/td&gt;
&lt;td&gt;4,482&lt;/td&gt;
&lt;td&gt;3.45&lt;/td&gt;
&lt;td&gt;4.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;gt; $0.05&lt;/td&gt;
&lt;td&gt;2,462&lt;/td&gt;
&lt;td&gt;1.69&lt;/td&gt;
&lt;td&gt;3.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;$0.01 is the default price in most x402 examples, and 14,787 resources sit in that band. It has the weakest numbers in the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveat: payers are not necessarily customers
&lt;/h2&gt;

&lt;p&gt;Of the 242 resources with 20 or more payers, 101 had almost exactly one call per payer (calls ≤ 1.2 × payers). That pattern fits fleets of fresh wallets that each call once, such as testing harnesses, review bots, catalog crawlers that make paid checks, or activity farming. It fits a real agent with a working use case less well. A distinct payer is a wallet, and nobody outside can prove it is a separate customer. There is a second caveat: &lt;a href="https://github.com/x402-foundation/x402/issues/3226" rel="noopener noreferrer"&gt;x402 issue #3226&lt;/a&gt; shows that a facilitator can accept a payload for Bazaar cataloging at &lt;em&gt;verify&lt;/em&gt;, before anything settles, so some entries and counters may count payloads that were checked rather than payments that were made. Treat these numbers as an upper bound on real paid demand.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did with this
&lt;/h2&gt;

&lt;p&gt;I built the boring thing the data points to. &lt;a href="https://plainchain.appin60.com" rel="noopener noreferrer"&gt;Plainchain&lt;/a&gt; is ten GET endpoints for agents: gas with USD cost estimates, latest block, USD prices, wallet balances with USD totals, ERC-20 token data with DEX stats, "what is this address" (EOA, EIP-7702 delegate, Safe owners and threshold, proxy implementation, token), allowance checks, decoded transactions, x402 receipt verification from a tx hash, and verifiable block-hash randomness. It covers Base, Arbitrum, Ethereum and Optimism, at $0.001-$0.01 per call over x402. Disclosure: I built and run it. Every settlement it receives is public at &lt;a href="https://plainchain.appin60.com/stats" rel="noopener noreferrer"&gt;/stats&lt;/a&gt;, so its numbers can be checked against this post.&lt;/p&gt;

&lt;p&gt;Three implementation notes if you are shipping your own x402 seller:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A Cloudflare Pages project can run the whole seller.&lt;/strong&gt; Put a bundled &lt;code&gt;_worker.js&lt;/code&gt; in the static output folder. &lt;code&gt;@x402/hono&lt;/code&gt; works on Workers once you bundle it with esbuild, aliasing Node's &lt;code&gt;url&lt;/code&gt; module to a two-line shim. The CDP facilitator wants an Ed25519-signed JWT on every verify and settle call, and WebCrypto's &lt;code&gt;Ed25519&lt;/code&gt; produces it without &lt;code&gt;jose&lt;/code&gt; or &lt;code&gt;Buffer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bazaar validation uses AJV, which needs &lt;code&gt;eval&lt;/code&gt;.&lt;/strong&gt; That is not allowed on Workers, so the SDK prints "invalid bazaar extension" warnings at startup. The extension is still sent. Validate your schemas once in Node to be sure they are actually right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Registering on x402scan is free.&lt;/strong&gt; It uses a SIWX sign-in (&lt;code&gt;createSIWxPayload&lt;/code&gt; and &lt;code&gt;encodeSIWxHeader&lt;/code&gt; from &lt;code&gt;@x402/extensions/sign-in-with-x&lt;/code&gt;). The signing key needs no funds. If your OpenAPI spec has an &lt;code&gt;x-payment-info&lt;/code&gt; block and an input schema for each route, &lt;code&gt;register-origin&lt;/code&gt; picks up every route in one call.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The crawl summary is at &lt;a href="https://plainchain.appin60.com/data/bazaar-summary.json" rel="noopener noreferrer"&gt;plainchain.appin60.com/data/bazaar-summary.json&lt;/a&gt;. To reproduce it, page through the discovery endpoint with &lt;code&gt;limit=500&lt;/code&gt;. It takes about two minutes.&lt;/p&gt;

</description>
      <category>x402</category>
      <category>web3</category>
      <category>ai</category>
      <category>api</category>
    </item>
  </channel>
</rss>
