<?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: Weston G</title>
    <description>The latest articles on DEV Community by Weston G (@weston_g).</description>
    <link>https://dev.to/weston_g</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%2F3925028%2Fb891451d-e418-44b3-a4cc-07e5d351995c.png</url>
      <title>DEV Community: Weston G</title>
      <link>https://dev.to/weston_g</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/weston_g"/>
    <language>en</language>
    <item>
      <title>How do you estimate LLM API costs before committing to a model?</title>
      <dc:creator>Weston G</dc:creator>
      <pubDate>Thu, 14 May 2026 17:26:12 +0000</pubDate>
      <link>https://dev.to/weston_g/how-do-you-estimate-llm-api-costs-before-committing-to-a-model-1hgi</link>
      <guid>https://dev.to/weston_g/how-do-you-estimate-llm-api-costs-before-committing-to-a-model-1hgi</guid>
      <description>&lt;p&gt;Quick question for anyone building with LLM APIs.&lt;/p&gt;

&lt;p&gt;The cost spread across current models is wild — GPT-4o vs Gemini 2.0 Flash is roughly a 30x difference per token. For most tasks, you could swap to a cheaper model and users wouldn't notice. But you only realize this late in the project, after the architecture is already set.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your process for thinking about costs before you start building?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I built a client-side token counter (&lt;a href="https://llmtokens.vercel.app" rel="noopener noreferrer"&gt;llmtokens.vercel.app&lt;/a&gt;) that shows real-time cost breakdowns across 25+ current models as you type — GPT-4o, Claude 4 Sonnet/Opus, Gemini 2.5 Pro/Flash, o3, DeepSeek, Llama, etc. It runs entirely in the browser, no signup.&lt;/p&gt;

&lt;p&gt;The goal was to make the cost conversation happen at architecture time, not bill-shock time.&lt;/p&gt;

&lt;p&gt;Curious what others do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you pick a model first and accept the cost, or estimate cost first and pick accordingly?&lt;/li&gt;
&lt;li&gt;Any heuristics for which tasks justify premium models vs. flash/mini tiers?&lt;/li&gt;
&lt;li&gt;Anything about cost estimation you wish you'd known earlier?&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I built a client-side LLM token counter because I kept guessing at prompt costs</title>
      <dc:creator>Weston G</dc:creator>
      <pubDate>Thu, 14 May 2026 09:33:18 +0000</pubDate>
      <link>https://dev.to/weston_g/i-built-a-client-side-llm-token-counter-because-i-kept-guessing-at-prompt-costs-44hd</link>
      <guid>https://dev.to/weston_g/i-built-a-client-side-llm-token-counter-because-i-kept-guessing-at-prompt-costs-44hd</guid>
      <description>&lt;p&gt;&lt;em&gt;Estimated read time: 4 minutes&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I was building a RAG pipeline last month. Standard stuff — system prompt, some retrieved chunks, user message. Somewhere around the third iteration of tweaking the system prompt I realized I had absolutely no idea what I was spending per request.&lt;/p&gt;

&lt;p&gt;The system prompt alone was a wall of text. Around 500 words. That's fine in isolation, but this thing was getting prepended to &lt;em&gt;every single request&lt;/em&gt;, and I had no intuition for what that translates to in dollars.&lt;/p&gt;

&lt;p&gt;So I opened the OpenAI Tokenizer, pasted the text, got a number. Then I had to open a calculator to multiply tokens × price. Then I wanted to compare to Claude's pricing. Then Gemini. By request five I was annoyed enough to just build the thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concrete example that made it real
&lt;/h2&gt;

&lt;p&gt;Here's a system prompt I actually had sitting in my codebase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are a helpful assistant for a B2B SaaS product. You help users understand 
their billing, navigate features, and troubleshoot common issues. Always be 
concise. When you don't know something, say so rather than guessing...
[+ ~450 more words of context, examples, and edge case instructions]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;~500 words. Let's call it 650 tokens (words aren't tokens — more on that below).&lt;/p&gt;

&lt;p&gt;At current pricing, that single system prompt costs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input price&lt;/th&gt;
&lt;th&gt;Cost per request&lt;/th&gt;
&lt;th&gt;Cost per 10k requests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4o&lt;/td&gt;
&lt;td&gt;$2.50/1M&lt;/td&gt;
&lt;td&gt;$0.001625&lt;/td&gt;
&lt;td&gt;$16.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-3.5 Turbo&lt;/td&gt;
&lt;td&gt;$0.50/1M&lt;/td&gt;
&lt;td&gt;$0.000325&lt;/td&gt;
&lt;td&gt;$3.25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude 3 Haiku&lt;/td&gt;
&lt;td&gt;$0.25/1M&lt;/td&gt;
&lt;td&gt;≈ $0.000163&lt;/td&gt;
&lt;td&gt;≈ $1.63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 1.5 Flash&lt;/td&gt;
&lt;td&gt;$0.075/1M&lt;/td&gt;
&lt;td&gt;≈ $0.0000488&lt;/td&gt;
&lt;td&gt;≈ $0.49&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's a 33x cost difference between GPT-4o and Gemini 1.5 Flash for the &lt;em&gt;exact same text&lt;/em&gt;. At 10k daily active users doing a few requests each, you're looking at the difference between a $50/day line item and a $1,600/day one.&lt;/p&gt;

&lt;p&gt;And that's just the system prompt. Add the user message, the conversation history, the retrieved chunks... the numbers compound fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://code-two-delta.vercel.app" rel="noopener noreferrer"&gt;LLM Token Counter&lt;/a&gt;&lt;/strong&gt; — paste text, pick a model, get token count + cost estimate instantly. No account, no backend, your text never leaves the browser.&lt;/p&gt;

&lt;p&gt;Supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GPT-4o ($2.50/1M input)&lt;/li&gt;
&lt;li&gt;GPT-3.5 Turbo ($0.50/1M input)
&lt;/li&gt;
&lt;li&gt;Claude 3 Haiku ($0.25/1M input)&lt;/li&gt;
&lt;li&gt;Gemini 1.5 Flash ($0.075/1M input)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it works technically
&lt;/h2&gt;

&lt;p&gt;The core is &lt;a href="https://github.com/dqbd/tiktoken" rel="noopener noreferrer"&gt;js-tiktoken&lt;/a&gt;, which is a WASM port of OpenAI's tokenizer that runs entirely in the browser. For GPT models, this gives you the exact same token count you'd get from the API — same &lt;code&gt;cl100k_base&lt;/code&gt; encoding, same results.&lt;/p&gt;

&lt;p&gt;The interesting constraint: Claude and Gemini both use proprietary tokenizers that aren't publicly available as standalone libraries. Anthropic doesn't ship a JS tokenizer. Google's &lt;code&gt;@google/generative-ai&lt;/code&gt; SDK does tokenization via API call (which defeats the whole purpose of a client-side tool).&lt;/p&gt;

&lt;p&gt;My workaround: use &lt;code&gt;cl100k_base&lt;/code&gt; for Claude and Gemini too, and flag the result as approximate. For typical English prose, cl100k gives you counts within about 5% of what Anthropic and Google's actual tokenizers would return. For code or non-English text, the variance is higher.&lt;/p&gt;

&lt;p&gt;It's not perfect, but "within 5% for budgeting purposes" is way more useful than "no idea." I put a &lt;code&gt;≈&lt;/code&gt; symbol next to those counts so you know they're estimates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The whole counting function — it's genuinely this simple&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;countTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&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;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&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;enc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;encodingForModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tiktokenId&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;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;enc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;tokens&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Claude and Gemini I pass &lt;code&gt;"gpt-4"&lt;/code&gt; as the &lt;code&gt;tiktokenId&lt;/code&gt;, which maps to &lt;code&gt;cl100k_base&lt;/code&gt;. It works. It's a proxy. I'm calling it out rather than hiding it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why client-side matters here
&lt;/h2&gt;

&lt;p&gt;I thought about building this as a tiny serverless function. It would have been five minutes of work.&lt;/p&gt;

&lt;p&gt;But I kept coming back to the fact that the whole use case is "paste your prompts in." People's prompts contain confidential stuff — product context, internal workflows, sometimes actual user data they're debugging with. Shipping that to a server I control (even one that immediately discards it) felt wrong for a tool this casual.&lt;/p&gt;

&lt;p&gt;WASM in the browser means I'm not in that data path at all. The tokenizer runs locally, the number appears instantly, nothing is logged anywhere. That also makes it fast — no network round trip, no cold starts, results update as you type.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do (yet)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Output token pricing (completions cost differently, and estimating output is harder since you don't know the length)&lt;/li&gt;
&lt;li&gt;Batch cost estimator (X requests/day at Y avg tokens)&lt;/li&gt;
&lt;li&gt;GPT-4o with vision or audio pricing&lt;/li&gt;
&lt;li&gt;Newer models — I'll update the price table as things change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those would be useful to you, the &lt;a href="https://github.com/SolvoHQ/llm-token-counter" rel="noopener noreferrer"&gt;source is on GitHub&lt;/a&gt; and PRs are open.&lt;/p&gt;

&lt;h2&gt;
  
  
  The thing I learned building this
&lt;/h2&gt;

&lt;p&gt;Words ≠ tokens in ways that bite you. "tokenization" is 3 tokens in cl100k. A JSON object with lots of punctuation tokenizes way worse than the same information in prose. Code is somewhere in between.&lt;/p&gt;

&lt;p&gt;Rule of thumb I use now: English prose ≈ 0.75 tokens/word. Code ≈ 0.5–0.6 tokens/word. JSON with lots of keys ≈ 1.2–1.5 tokens/word equivalent. The counter makes it concrete — I've started pasting my prompts in before I finalize them.&lt;/p&gt;




&lt;p&gt;What's your approach to tracking LLM costs during development? Are you eyeballing it, running it through the OpenAI playground, or do you have something more systematic set up? Curious what's actually working for people.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>webdev</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
    <item>
      <title>LLM Token Costs: Why Your Prompt Might Cost 10x More Than You Think</title>
      <dc:creator>Weston G</dc:creator>
      <pubDate>Thu, 14 May 2026 09:27:36 +0000</pubDate>
      <link>https://dev.to/weston_g/llm-token-costs-why-your-prompt-might-cost-10x-more-than-you-think-96f</link>
      <guid>https://dev.to/weston_g/llm-token-costs-why-your-prompt-might-cost-10x-more-than-you-think-96f</guid>
      <description>&lt;h1&gt;
  
  
  LLM Token Costs: Why Your Prompt Might Cost 10x More Than You Think
&lt;/h1&gt;

&lt;p&gt;If you're building with LLM APIs, you've probably wondered: &lt;em&gt;how many tokens is this prompt actually using?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I built a free tool to answer that: &lt;strong&gt;&lt;a href="https://code-two-delta.vercel.app" rel="noopener noreferrer"&gt;LLM Token Counter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Paste any text and instantly see the token count&lt;/li&gt;
&lt;li&gt;Compares costs across GPT-4o, GPT-3.5 Turbo, Claude 3 Haiku, and Gemini 1.5 Flash&lt;/li&gt;
&lt;li&gt;No login, no backend — runs entirely in your browser&lt;/li&gt;
&lt;li&gt;Uses the actual cl100k_base tokenizer (same as OpenAI) for accurate GPT counts&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The cost gap is real
&lt;/h2&gt;

&lt;p&gt;Consider a typical system prompt + user message (about 500 tokens):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Cost per 1M tokens&lt;/th&gt;
&lt;th&gt;Cost for 500 tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4o&lt;/td&gt;
&lt;td&gt;$2.50&lt;/td&gt;
&lt;td&gt;$0.00125&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-3.5 Turbo&lt;/td&gt;
&lt;td&gt;$0.50&lt;/td&gt;
&lt;td&gt;$0.00025&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude 3 Haiku&lt;/td&gt;
&lt;td&gt;$0.25&lt;/td&gt;
&lt;td&gt;$0.000125&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 1.5 Flash&lt;/td&gt;
&lt;td&gt;$0.075&lt;/td&gt;
&lt;td&gt;$0.0000375&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's a &lt;strong&gt;33x cost difference&lt;/strong&gt; between GPT-4o and Gemini 1.5 Flash for the same prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why client-side matters
&lt;/h2&gt;

&lt;p&gt;Most token counters require you to send your prompt to a server. This one runs entirely in your browser using &lt;a href="https://github.com/dqbd/tiktoken" rel="noopener noreferrer"&gt;js-tiktoken&lt;/a&gt; — your prompts never leave your machine.&lt;/p&gt;

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

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://code-two-delta.vercel.app" rel="noopener noreferrer"&gt;https://code-two-delta.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/SolvoHQ/llm-token-counter" rel="noopener noreferrer"&gt;github.com/SolvoHQ/llm-token-counter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Would love feedback — what models or features would make this more useful for your workflow?&lt;/p&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>openai</category>
      <category>developers</category>
    </item>
    <item>
      <title>Paste a wallet, get a personal airdrop verdict — and call the same logic from any LLM</title>
      <dc:creator>Weston G</dc:creator>
      <pubDate>Mon, 11 May 2026 20:06:05 +0000</pubDate>
      <link>https://dev.to/weston_g/paste-a-wallet-get-a-personal-airdrop-verdict-and-call-the-same-logic-from-any-llm-4ej0</link>
      <guid>https://dev.to/weston_g/paste-a-wallet-get-a-personal-airdrop-verdict-and-call-the-same-logic-from-any-llm-4ej0</guid>
      <description>&lt;h1&gt;
  
  
  Paste a wallet, get a personal airdrop verdict — and call the same logic from any LLM
&lt;/h1&gt;

&lt;p&gt;Two surfaces, one rule registry: a browser tool &lt;em&gt;and&lt;/em&gt; an MCP tool that share the exact same 14 hand-verified on-chain rules and return the exact same verdict shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Airdrop directories are full of "you might be eligible for 312 airdrops!" claims. They match on chain ("you've used Ethereum → you qualify for every Ethereum airdrop"), which is useless. The real eligibility lives on each project's contract or off-chain criteria.&lt;/p&gt;

&lt;p&gt;So I built a tool that does the opposite: a tiny number of hand-verified rules, each tied to a specific entry, evaluated against the wallet you paste. No signature, no signup, no server-side address logging — just RPC calls fanned out from the browser. And because the same rule registry feeds an MCP tool, any LLM client (Claude Desktop, Cursor, etc.) can call it directly without scraping the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  The browser tool
&lt;/h2&gt;

&lt;p&gt;Live at &lt;a href="https://web3-discover.vercel.app/tools/eligibility" rel="noopener noreferrer"&gt;web3-discover.vercel.app/tools/eligibility&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Paste an EVM and/or Solana address. The page evaluates 14 rules against 42 curated entries and buckets the results into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;✅ Likely eligible&lt;/strong&gt; — rule passes (e.g. &lt;code&gt;eth_getTransactionCount&lt;/code&gt; on Linea ≥ 1, or you hold ≥ 1 PENDLE)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🟡 Keep farming&lt;/strong&gt; — partial: you've started but not crossed the threshold (e.g. 2 Arbitrum tx when 5 are needed for Reya / Ostium)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⏭ Not relevant&lt;/strong&gt; — rule fails outright&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔍 Manual check&lt;/strong&gt; — entry lives on a chain we can't reach from the browser (Monad, MegaETH, Hyperliquid, Sui-based) or has off-chain criteria (KYC, Discord, Galxe quests)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try &lt;a href="https://web3-discover.vercel.app/tools/eligibility?evm=0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045&amp;amp;sol=9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM" rel="noopener noreferrer"&gt;vitalik.eth + a public Solana wallet&lt;/a&gt; — resolves in 2-5 seconds, 9 eligible at the time of writing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the browser side stays cheap
&lt;/h3&gt;

&lt;p&gt;Every entry-page CTA deep-links to &lt;code&gt;/tools/eligibility?focus=&amp;lt;slug&amp;gt;&lt;/code&gt;. A banner at the top of the results page names the entry the visitor came from, then after evaluation the matching card is scrolled into view and pulse-highlighted with a CSS keyframe. This costs zero extra requests — the focus logic is pure DOM-after-render.&lt;/p&gt;

&lt;p&gt;Total RPC requests = one batched JSON-RPC POST per EVM chain (so 6 max for an EVM address) plus per-mint Solana calls. Each batch bundles &lt;code&gt;eth_getTransactionCount&lt;/code&gt; + every &lt;code&gt;eth_call(balanceOf)&lt;/code&gt; we need for that chain, so adding more rules on Ethereum costs zero additional requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP tool
&lt;/h2&gt;

&lt;p&gt;Same logic, exposed at &lt;code&gt;https://web3-discover.vercel.app/api/mcp&lt;/code&gt; as the fourth tool of the web3-discover MCP server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://web3-discover.vercel.app/api/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "jsonrpc": "2.0",
    "id": 7,
    "method": "tools/call",
    "params": {
      "name": "check_eligibility",
      "arguments": { "evm": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Returns the same verdict shape — &lt;code&gt;eligible[]&lt;/code&gt;, &lt;code&gt;partial[]&lt;/code&gt;, &lt;code&gt;manual[]&lt;/code&gt;, &lt;code&gt;unavailable[]&lt;/code&gt;, &lt;code&gt;skip[]&lt;/code&gt; — but as machine-readable JSON each LLM client can render however it likes:&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;"inputs"&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;"evm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0xd8dA…6045"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&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;"rulesEvaluated"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalTracked"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&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="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"eligible"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"partial"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"manual"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"unavailable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;"eligible"&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="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"slug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"base-coinbase-l2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"project"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Base"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"chain"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Base (Coinbase L2)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"verdictDetail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"70 tx on Base (≥ 1 required)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"officialUrl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://base.org"&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;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;h3&gt;
  
  
  Drop-in for Claude Desktop / Cursor
&lt;/h3&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;"mcpServers"&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;"web3-discover"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp-remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://web3-discover.vercel.app/api/mcp"&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="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;Then ask: &lt;em&gt;"What airdrops am I eligible for? My wallet is 0x…"&lt;/em&gt; and the model calls &lt;code&gt;check_eligibility&lt;/code&gt; directly. No scraping, no flaky HTML parsing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one-file pattern that keeps both surfaces honest
&lt;/h2&gt;

&lt;p&gt;Two surfaces means two ways for the rules to drift. The fix is one source of truth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/data/
  eligibility-rules.json   # source of truth
  eligibility-rules.ts     # typed wrapper for the Astro page
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And during prebuild:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// scripts/build-airdrops-snapshot.mjs&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copyFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/data/eligibility-rules.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;api/_eligibility-rules.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The MCP serverless function reads the JSON at cold start; the Astro page imports the typed &lt;code&gt;.ts&lt;/code&gt; wrapper. Same data, two consumers, zero drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters as a wedge
&lt;/h2&gt;

&lt;p&gt;Most directory sites optimise for "discover something new". This optimises for the moment after you've already heard of a project: &lt;em&gt;"do I, this wallet I'm holding, actually need to act on this?"&lt;/em&gt; That question doesn't compete with newsletters or Twitter — it complements them.&lt;/p&gt;

&lt;p&gt;And exposing the same logic over MCP means the directory gets pulled into LLM chats &lt;em&gt;as the user is making decisions&lt;/em&gt;, not after the fact. The two surfaces feed each other: the browser tool drives organic / shared discovery; the MCP tool drives in-chat utility when a user has Claude open anyway.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Browser:&lt;/strong&gt; &lt;a href="https://web3-discover.vercel.app/tools/eligibility" rel="noopener noreferrer"&gt;web3-discover.vercel.app/tools/eligibility&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP:&lt;/strong&gt; &lt;code&gt;https://web3-discover.vercel.app/api/mcp&lt;/code&gt; — JSON-RPC 2.0, &lt;code&gt;tools/list&lt;/code&gt; returns four tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source:&lt;/strong&gt; the curated entries themselves live in a separate CC0 repo at &lt;a href="https://github.com/SolvoHQ/web3-discover-data" rel="noopener noreferrer"&gt;SolvoHQ/web3-discover-data&lt;/a&gt;, auto-refreshed daily&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Open to pull requests adding new rules — each one is ~5 lines in the JSON registry.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>crypto</category>
      <category>mcp</category>
    </item>
    <item>
      <title>I shipped an MCP server for crypto airdrops — install in 1 config line</title>
      <dc:creator>Weston G</dc:creator>
      <pubDate>Mon, 11 May 2026 14:13:59 +0000</pubDate>
      <link>https://dev.to/weston_g/i-shipped-an-mcp-server-for-crypto-airdrops-install-in-1-config-line-1pl7</link>
      <guid>https://dev.to/weston_g/i-shipped-an-mcp-server-for-crypto-airdrops-install-in-1-config-line-1pl7</guid>
      <description>&lt;p&gt;I shipped a tiny MCP server last week that turns a curated crypto airdrop directory into 3 tools any LLM client (Claude Desktop, Cursor, Continue, Windsurf) can call directly.&lt;/p&gt;

&lt;p&gt;This post is the install snippet + a screenshot of it returning real data — so you can decide in 30 seconds whether it's worth one config line.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;p&gt;Three tools, no signup, no API key, no rate limit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;list_active_airdrops(chain?, risk?, sort_by?, limit?)&lt;/code&gt; — browse 32 vetted active airdrops, filter by chain (Solana, Base, Ethereum…) or risk level (verified / unverified)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_airdrop(slug)&lt;/code&gt; — full details for one campaign: action steps, weekly effort, cost floor, risk notes, official URL&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;check_wallet(addr)&lt;/code&gt; — paste an EVM or Solana address, fan out to 7 public RPCs (Ethereum, Base, Linea, Arbitrum, Polygon, BSC, Solana), and surface every tracked airdrop whose chain you've already touched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hosted, JSON-RPC 2.0 over HTTP, CORS-open. Endpoint: &lt;code&gt;https://web3-discover.vercel.app/api/mcp&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install in Claude Desktop or Cursor
&lt;/h2&gt;

&lt;p&gt;Add this one block to your MCP config:&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;"mcpServers"&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;"web3-discover"&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;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp-remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://web3-discover.vercel.app/api/mcp"&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="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;That's it. Restart your client. Ask: &lt;em&gt;"What active airdrops can I farm on Solana this week with under $100 capital?"&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it returns (live)
&lt;/h2&gt;

&lt;p&gt;Here's the actual JSON-RPC 2.0 response from a real &lt;code&gt;tools/call&lt;/code&gt; against the live endpoint — pulled the moment I wrote this article:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnsbbeb4nn5vw1r3t7tjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnsbbeb4nn5vw1r3t7tjf.png" alt="web3-discover MCP returning 3 real airdrop entries via JSON-RPC 2.0" width="800" height="873"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Verify it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://web3-discover.vercel.app/api/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"list_active_airdrops","arguments":{"limit":3,"sort_by":"added"}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get back structured entries: slug, project, chain, blurb, deadline, effort estimate, cost floor, risk flag (&lt;code&gt;verified&lt;/code&gt; means the team has publicly confirmed TGE plans; &lt;code&gt;unverified&lt;/code&gt; means speculative), and the official URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is interesting (beyond "another MCP server")
&lt;/h2&gt;

&lt;p&gt;I run a curated airdrop directory at &lt;a href="https://web3-discover.vercel.app" rel="noopener noreferrer"&gt;web3-discover.vercel.app&lt;/a&gt;. Every entry is hand-vetted, scam-filtered, and re-checked weekly. The web surface works well for humans who already know about it — but most retail farmers ask LLMs first now.&lt;/p&gt;

&lt;p&gt;So I exposed the directory as MCP tools. Three things this unlocks that the web UI alone can't:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Natural-language filtering.&lt;/strong&gt; "What's verified-only, ongoing, costs gas only?" — the LLM picks &lt;code&gt;list_active_airdrops&lt;/code&gt;, filters by &lt;code&gt;risk=verified&lt;/code&gt;, sorts by &lt;code&gt;deadline&lt;/code&gt;, narrates the result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wallet-aware advice.&lt;/strong&gt; &lt;code&gt;check_wallet&lt;/code&gt; returns which chains your wallet has touched plus the entries on those chains. The LLM combines that with your stated risk tolerance and tells you what to prioritize, not just what exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composition with other MCP servers.&lt;/strong&gt; If you've got a wallet MCP server + this one, the LLM can chain "what airdrops am I close to qualifying for, given my actual on-chain history?"&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No tracking. The directory has no signup. The check_wallet tool just reads from public RPCs — no address is logged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why hosted, not stdio?
&lt;/h2&gt;

&lt;p&gt;Most MCP servers ship as &lt;code&gt;npx some-package&lt;/code&gt; that runs on your machine. I went hosted (HTTP transport via &lt;code&gt;mcp-remote&lt;/code&gt; bridge) for one reason: &lt;strong&gt;the data changes weekly&lt;/strong&gt;. A stdio package would have to re-download the directory or hit an API anyway. Hosting it means the data refreshes when I do a freshness sweep, not when you remember to &lt;code&gt;npm update&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Tradeoff: you trust me to keep the endpoint up. I run a freshness sweep weekly (the directory page shows a "last verified" date per entry). If you'd rather self-host, the &lt;a href="https://github.com/SolvoHQ/web3-discover" rel="noopener noreferrer"&gt;source is MIT&lt;/a&gt; and the MCP handler is a single ~250-line file (&lt;code&gt;api/mcp.mjs&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  What works, what doesn't, what's next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Works today:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All 3 tools verified end-to-end on Claude Desktop and Cursor with &lt;code&gt;mcp-remote&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;32 active entries, 7-chain wallet check&lt;/li&gt;
&lt;li&gt;No auth, no rate limit at current load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Doesn't yet:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No eligibility scoring — &lt;code&gt;check_wallet&lt;/code&gt; is a presence check, not an "are you actually eligible?" check. Most airdrops have additional criteria (volume, tx count, NFT holdings) that I'd need to wire per-entry.&lt;/li&gt;
&lt;li&gt;No notifications — if a deadline shifts or an entry gets removed, you'll only see it next time you query.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Next:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;subscribe_deadlines(addr)&lt;/code&gt; returning an iCalendar feed so deadlines land in your calendar&lt;/li&gt;
&lt;li&gt;Per-entry eligibility checks where the airdrop has a public scoring API&lt;/li&gt;
&lt;li&gt;Adding the directory as an MCP resource (not just tools) so LLMs can browse without explicit calls&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Drop the config block above into Claude Desktop (&lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt; on macOS) or Cursor (Settings → MCP). Ask "What airdrops are worth farming this week?"&lt;/p&gt;

&lt;p&gt;If it's useful, the directory itself lives at &lt;a href="https://web3-discover.vercel.app" rel="noopener noreferrer"&gt;web3-discover.vercel.app&lt;/a&gt; — same data, human-shaped.&lt;/p&gt;

&lt;p&gt;If something breaks or you want a tool added, &lt;a href="https://github.com/SolvoHQ/web3-discover/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built with Astro + Vercel serverless. Three days, ~250 LOC for the MCP handler. The hardest part wasn't the code — it was making sure the underlying directory data was actually honest. (No paid placements pretending to be listings.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>crypto</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Building a daily-refreshed, scam-flagged airdrop directory with Astro + IndexNow + GoatCounter</title>
      <dc:creator>Weston G</dc:creator>
      <pubDate>Mon, 11 May 2026 12:57:42 +0000</pubDate>
      <link>https://dev.to/weston_g/building-a-daily-refreshed-scam-flagged-airdrop-directory-with-astro-indexnow-goatcounter-l80</link>
      <guid>https://dev.to/weston_g/building-a-daily-refreshed-scam-flagged-airdrop-directory-with-astro-indexnow-goatcounter-l80</guid>
      <description>&lt;p&gt;I've been building &lt;strong&gt;&lt;a href="https://web3-discover.vercel.app" rel="noopener noreferrer"&gt;web3-discover&lt;/a&gt;&lt;/strong&gt; — a hand-vetted, scam-flagged directory of currently-claimable web3 airdrops — and I wanted to share the static-site + agent-driven pipeline behind it, because the architecture is unusual: there's no backend, no database, no editor login. Everything is git-tracked Markdown, every deploy is a static build, and content is refreshed daily by an autonomous workflow rather than human curators.&lt;/p&gt;

&lt;p&gt;Live URL: &lt;strong&gt;&lt;a href="https://web3-discover.vercel.app" rel="noopener noreferrer"&gt;https://web3-discover.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The wedge (why "fewer entries" is the feature)
&lt;/h2&gt;

&lt;p&gt;The crypto-airdrop site space is dominated by SEO-juiced aggregators listing 300+ "airdrops" — most of which are expired, scams, or paid placements pretending to be listings. The wedge is the opposite: ~30 hand-vetted entries, each with a concrete action, cost floor, deadline, risk flag, and a &lt;code&gt;lastChecked&lt;/code&gt; date. If we can't verify an entry, we drop it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack (and why each piece)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Astro 5&lt;/strong&gt; — chosen specifically because content collections give you a typed schema for markdown frontmatter, and the output is dead-static HTML. No client JS in the critical path. Every entry page is a 9KB document.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/content/config.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;airdrops&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defineCollection&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Ethereum&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="s1"&gt;Solana&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="s1"&gt;Base&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="s1"&gt;Arbitrum&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;costFloor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;     &lt;span class="c1"&gt;// "$0", "$50", "gas-only"&lt;/span&gt;
    &lt;span class="na"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;      &lt;span class="c1"&gt;// ISO date or "ongoing"&lt;/span&gt;
    &lt;span class="na"&gt;riskFlag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;verified&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="s1"&gt;unverified&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="s1"&gt;suspected-scam&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;lastChecked&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;officialUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;addedOn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When an entry breaks schema, the build fails loudly. That's the whole content-CI layer — no DB migrations to run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Vercel&lt;/strong&gt; for hosting — &lt;code&gt;vercel --prod&lt;/code&gt; from CLI. Free tier handles plenty of traffic. Sitemap pinned to &lt;code&gt;@astrojs/sitemap@3.2.1&lt;/code&gt; because newer versions broke our Astro 5 build chain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON-LD&lt;/strong&gt; for structured data — &lt;code&gt;Organization&lt;/code&gt; + &lt;code&gt;WebSite&lt;/code&gt; on every page (in &lt;code&gt;Base.astro&lt;/code&gt; layout), &lt;code&gt;ItemList&lt;/code&gt; on the airdrops index page, &lt;code&gt;Article&lt;/code&gt; schema on each entry page (with &lt;code&gt;datePublished&lt;/code&gt; from frontmatter &lt;code&gt;addedOn&lt;/code&gt;, publisher logo, &lt;code&gt;mainEntityOfPage&lt;/code&gt;, &lt;code&gt;dateModified&lt;/code&gt; sourced from &lt;code&gt;lastChecked&lt;/code&gt;). Verified live via curl — the JSON parses cleanly with &lt;code&gt;python3 -m json.tool&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RSS&lt;/strong&gt; at &lt;code&gt;/rss.xml&lt;/code&gt; using &lt;code&gt;@astrojs/rss&lt;/code&gt;. Each item carries the full action/effort/cost/deadline/risk block — readable in a feed reader without clicking through. GUIDs are entry URLs (canonical).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IndexNow&lt;/strong&gt; for crawler push notifications — three endpoints pinged on every content update:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;https://api.indexnow.org/indexnow&lt;/code&gt; → 202&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https://www.bing.com/indexnow&lt;/code&gt; → 200&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;https://yandex.com/indexnow&lt;/code&gt; → 202 (&lt;code&gt;{"success":true}&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The verification key is a file at &lt;code&gt;https://web3-discover.vercel.app/&amp;lt;hex&amp;gt;.txt&lt;/code&gt;. &lt;strong&gt;Don't delete that file — Bing re-validates on subsequent pings.&lt;/strong&gt; I learned that the hard way trying to "clean up" the public dir.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GoatCounter&lt;/strong&gt; for analytics. Privacy-friendly, no cookies, no consent banner needed. Embed is a 5-line &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;. The dashboard lives at &lt;code&gt;&amp;lt;workspace&amp;gt;.goatcounter.com&lt;/code&gt;. Counts pageviews + referrers, exactly what I need to see whether a distribution channel actually works.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent-refresh loop (the unusual part)
&lt;/h2&gt;

&lt;p&gt;The site is maintained by an autonomous workflow (Claude Code agent ticks running on a 30-second heartbeat). Every wake the agent reads the current Markdown entries, checks &lt;code&gt;lastChecked&lt;/code&gt; against &lt;code&gt;Date.now() - 7d&lt;/code&gt;, and queues stale entries for re-verification:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetch the project's official Twitter or docs page.&lt;/li&gt;
&lt;li&gt;Re-confirm the deadline string.&lt;/li&gt;
&lt;li&gt;Re-confirm the action steps still match what the project requires.&lt;/li&gt;
&lt;li&gt;If anything has changed: update the frontmatter, bump &lt;code&gt;lastChecked&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the project has TGE'd (token generation event passed) or the airdrop is over: remove the entry from &lt;code&gt;src/content/airdrops/&lt;/code&gt; entirely.&lt;/li&gt;
&lt;li&gt;Re-build, redeploy, ping IndexNow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the first sweep (22 → 32 entries) the agent caught three of my own false assumptions: Monad / MegaETH / Plasma are post-TGE, not pre-TGE. Sonic Labs Season 2 ended November 2025 — dropped. The point isn't that the agent is smarter than me; it's that running the verification loop on a 7-day cycle instead of "when I feel like it" surfaces drift fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-entry conversion pivot (a real lesson)
&lt;/h2&gt;

&lt;p&gt;Initial v1 had a &lt;code&gt;/tools/swap&lt;/code&gt; page with Jupiter Plugin (Solana) and Jumper deeplink (EVM) as a monetization path — integrator-fees on swaps. Every airdrop entry page had a "Sell when claimed" CTA pointing at &lt;code&gt;/tools/swap?token=X&amp;amp;chain=Y&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The bug: &lt;strong&gt;the URL params were read by NO code.&lt;/strong&gt; Every sell-CTA from 32 entry pages dumped users on a USDC-default swap. The single biggest pre-traffic conversion gap, sitting in production for ~24 hours before I caught it.&lt;/p&gt;

&lt;p&gt;Fix: a slug→target registry mapping each entry's &lt;code&gt;chain + symbol&lt;/code&gt; to a concrete token address. For Solana, that's the SPL mint passed into &lt;code&gt;Jupiter.formProps.initialInputMint&lt;/code&gt;. For EVM, &lt;code&gt;?fromChain=…&amp;amp;fromToken=…&lt;/code&gt; on the Jumper deeplink. 12 verified Solana mints + 20 EVM token addresses are now hardcoded into a TypeScript const. Building this took ~25 minutes. The fact that an agent let it ship undetected for 24h is the cautionary tale — verification-before-completion is non-negotiable when your reviewer-of-last-resort is the same process that shipped the change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guides pillar (SEO + risk-awareness)
&lt;/h2&gt;

&lt;p&gt;Three evergreen guides live at &lt;code&gt;/guides/scams&lt;/code&gt;, &lt;code&gt;/guides/taxes&lt;/code&gt;, &lt;code&gt;/guides/wallet-hygiene&lt;/code&gt;. Every airdrop entry internally links to &lt;strong&gt;two&lt;/strong&gt; risk-matched guides (a &lt;code&gt;suspected-scam&lt;/code&gt; entry links to &lt;code&gt;/guides/scams&lt;/code&gt;; a high-deadline entry links to &lt;code&gt;/guides/wallet-hygiene&lt;/code&gt;). Indexable surface went from 35 to 39 URLs, every entry page gains internal-link equity.&lt;/p&gt;

&lt;p&gt;The choice was deliberate: I'd rather have 3 deep guides than 30 shallow ones. Crawlers reward dwell time, and a 1500-word scams guide with concrete patterns ("if the claim URL doesn't match the project's official Twitter pinned tweet, walk away") does that better than 30x 200-word listicles.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell anyone building in this category
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Anti-features are the differentiation.&lt;/strong&gt; "No wallet connect, no claim links, no paid listings" is not a copy gimmick — it's the reason I trust my own site, and it's the reason a user might. The competitive moat in any junk-flooded category is being the one that says "no" to the easy money.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Static + agent-refresh &amp;gt; headless CMS.&lt;/strong&gt; A headless CMS gives editors a UI. I don't have editors. The git tree is the database; the deploy is the publish step; the agent is the editor. If your contributor list is 1, this collapses to dramatically less infra than "Strapi + Postgres + worker queue".&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JSON-LD is free SEO money.&lt;/strong&gt; Article schema with &lt;code&gt;dateModified&lt;/code&gt; sourced from &lt;code&gt;lastChecked&lt;/code&gt; tells Google "this is a fresh page". Cost: 12 lines per layout file. Benefit: every entry shows the freshness signal in SERPs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;IndexNow ≠ Google Search Console.&lt;/strong&gt; GSC verification is an interactive OAuth flow with no zero-account path I could find. IndexNow accepts a static key file and gets you Bing + Yandex (the latter is non-trivial for crypto SEO, where Russian and Eastern European traffic is real). Don't burn hours on GSC; ship IndexNow and move on.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Site: &lt;strong&gt;&lt;a href="https://web3-discover.vercel.app" rel="noopener noreferrer"&gt;https://web3-discover.vercel.app&lt;/a&gt;&lt;/strong&gt; — currently 32 active entries, deadline-sorted, refreshed today.&lt;/p&gt;

&lt;p&gt;If you spot an entry that's wrong, broken, or scammy I should flag harder — open an issue at the repo (linked in the footer). The whole content tree is public Markdown — feedback round-trips fast.&lt;/p&gt;

&lt;p&gt;Tags: &lt;code&gt;#astro&lt;/code&gt;, &lt;code&gt;#web3&lt;/code&gt;, &lt;code&gt;#staticsite&lt;/code&gt;, &lt;code&gt;#seo&lt;/code&gt;, &lt;code&gt;#crypto&lt;/code&gt;&lt;/p&gt;

</description>
      <category>astro</category>
      <category>web3</category>
      <category>webdev</category>
      <category>crypto</category>
    </item>
  </channel>
</rss>
