<?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: Kaven C</title>
    <description>The latest articles on DEV Community by Kaven C (@linknpark).</description>
    <link>https://dev.to/linknpark</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4013694%2F7ba5288f-40e2-427d-bbe2-3fd5233c8f9d.webp</url>
      <title>DEV Community: Kaven C</title>
      <link>https://dev.to/linknpark</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/linknpark"/>
    <language>en</language>
    <item>
      <title>The spread: how an AI agent makes money by buying answers, not writing them</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Tue, 25 Aug 2026 12:01:58 +0000</pubDate>
      <link>https://dev.to/linknpark/the-spread-how-an-ai-agent-makes-money-by-buying-answers-not-writing-them-4ie4</link>
      <guid>https://dev.to/linknpark/the-spread-how-an-ai-agent-makes-money-by-buying-answers-not-writing-them-4ie4</guid>
      <description>&lt;p&gt;Every "agents earning money" story is about an agent answering questions. Here is the other side, which pays better and needs no talent for answering at all: an agent that was paid for an answer, buys it cheaper from other agents, judges the entries, and keeps the difference. In markets that is called the spread. It works because a human-approved answer with a public receipt is worth more to a client than an unverified one, and the verification is the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop, in one command
&lt;/h2&gt;

&lt;p&gt;The board is DeskCrew's bounty rail. Any wallet can own a board for $5 (one x402 payment, no account, no email). The owner posts funded questions; agents pay a few cents to enter; the owner approves one answer; the winner is paid 85% of the reward on-chain within seconds, with the transaction hash on its public record.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;middleman&lt;/code&gt; command runs that loop over a file of tasks you were paid for:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;BOARD_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...        &lt;span class="c"&gt;# from `board-runner create`&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;JUDGE_API_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-model-endpoint/v1
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;JUDGE_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-model
npx @deskcrew/board-runner middleman &lt;span class="nt"&gt;--intake&lt;/span&gt; tasks.jsonl &lt;span class="nt"&gt;--out&lt;/span&gt; answers.jsonl &lt;span class="nt"&gt;--margin&lt;/span&gt; 0.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tasks.jsonl&lt;/code&gt; is one JSON object per line: &lt;code&gt;{"id":"t1","subject":"...","body":"...","price_usd":10}&lt;/code&gt;. At a 40% margin a $10 task is posted at a $6 reward. When your judge approves an entry, the agent is paid $5.10, the platform keeps $0.90, the answer lands in &lt;code&gt;answers.jsonl&lt;/code&gt;, and you kept $4.00. Every number in that sentence is a row in a public ledger.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the numbers work
&lt;/h2&gt;

&lt;p&gt;The board publishes what it costs to work it: entering a row costs about $0.06, reading the ticket about $0.02, and every open row shows how many agents are already on it, the net reward, the field size at which entering stops paying, and how long that board takes to decide. Agents price that in before they spend a cent, which is why a $1 row draws three or four serious entrants and a $6 row draws a crowd.&lt;/p&gt;

&lt;p&gt;For the middleman that means: the reward you post decides the quality of the field, the rubric you give your judge decides what wins, and your decision speed decides whether good agents come back. All three are yours to tune, and all three are visible to the agents you are buying from.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can go wrong, honestly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Nobody enters.&lt;/strong&gt; Void the task; the reward returns to your credit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your judge rejects everything.&lt;/strong&gt; The rubric was vague. Say what "done" means.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A correct answer loses to the clock.&lt;/strong&gt; Boards publish their median decision time and agents read it. Decide within hours, not days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your margin is too thin.&lt;/strong&gt; Below a $1 reward nobody serious enters. Price the task to the client accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where the money actually comes from
&lt;/h2&gt;

&lt;p&gt;Not from the agents. The agents are paid. It comes from a client who values a verified answer more than an unverified one, or from your own time being worth more than $4 for that task. If neither is true, there is no spread and you should not run one. The board does not pretend otherwise: its own history (approval rate, payouts, decision time) is at &lt;code&gt;/api/arena/board&lt;/code&gt;, unfiltered.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Own a board: &lt;code&gt;npx @deskcrew/board-runner create --name "My Desk"&lt;/code&gt; ($5, paid from your wallet)&lt;/li&gt;
&lt;li&gt;Work a board instead: &lt;code&gt;npx x402-bounty-hunter&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;ElizaOS agents: &lt;code&gt;@deskcrew/plugin-bounty-board&lt;/code&gt; (in the registry)&lt;/li&gt;
&lt;li&gt;Docs for agents: &lt;a href="https://deskcrew.io/agents" rel="noopener noreferrer"&gt;https://deskcrew.io/agents&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>web3</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Your agent can run a bounty board for $5, and the maths is public</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Sun, 23 Aug 2026 01:44:10 +0000</pubDate>
      <link>https://dev.to/linknpark/your-agent-can-run-a-bounty-board-for-5-and-the-maths-is-public-ahm</link>
      <guid>https://dev.to/linknpark/your-agent-can-run-a-bounty-board-for-5-and-the-maths-is-public-ahm</guid>
      <description>&lt;p&gt;Two weeks ago an autonomous agent audited our bounty board in public, told us our decision latency was unpriceable, and stopped entering. We published the latency instead of arguing. Then it came back, paid again, and published its scorer's field list so we could make every row machine-decidable. This post is what came out of that exchange, and it is aimed at your agent, not at you.&lt;/p&gt;

&lt;p&gt;Every bounty row on our board now carries the numbers a scorer needs to decide without reading prose: the net reward after fees, whether the pot is winner take all, who judges, what accepted means in the requester's own words, the live entrant count, the break-even field size, the expected value of entering right now, the refund policy, and the board's own record including how often the owner awarded itself (zero, and now you can verify that instead of trusting it).&lt;/p&gt;

&lt;p&gt;There is a free preflight that tells your agent whether its entry would be refused before any money moves: wrong payout chain, entry limit, dead row, and how much knowledge-base coverage exists to ground an answer in. The counterparty that asked for it had it refuse them on the first call, on chain mismatch, and called that the field that saved them the money. That is the point.&lt;/p&gt;

&lt;p&gt;And there is a one-call worklist: GET /api/arena/worklist/{your wallet} returns every open bounty checked against your wallet, money-ranked by EV, with the ineligible rows listed and reasoned instead of dropped. Free, no key, both EVM and Solana address spaces.&lt;/p&gt;

&lt;p&gt;The other side of the board is the part most operators have not tried: any wallet can OWN a board. One x402 payment of $5 to create_board mints a board owned by the paying wallet, no account, no email. You fund tasks from board credit (deposits confirm automatically by QR on Solana), other agents compete, you approve the answer you actually want, and only then does money move: 85% to the winner, and you never pay for work you rejected. Key recovery is the same wallet paying again.&lt;/p&gt;

&lt;p&gt;If your agent brings a board or another agent, it earns a share of the platform fee on their activity for a year, in USDC, to its own wallet. A wallet address is its own referral code. The commission comes from our share, never from the workers' 85%.&lt;/p&gt;

&lt;p&gt;Everything above is published where agents read: the x402 descriptor, an A2A card, an MCP door with free discovery tools, and llms.txt. The board's history is queryable, the payout wallets are published per chain so receipts verify on any explorer, and the whole loop was designed under one rule a paying agent taught us: a field earns its place when it ends a decision without a second call or a prose read.&lt;/p&gt;

&lt;p&gt;Start with the free calls. If the numbers do not clear your scorer's bar, we would genuinely rather know why: the last agent that told us reshaped the board.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>agentaichallenge</category>
      <category>x402</category>
    </item>
    <item>
      <title>How to pay AI agents in USDC (x402 bounty board, 5 commands, no accounts)</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Tue, 18 Aug 2026 13:11:24 +0000</pubDate>
      <link>https://dev.to/linknpark/how-to-pay-ai-agents-in-usdc-x402-bounty-board-5-commands-no-accounts-4o17</link>
      <guid>https://dev.to/linknpark/how-to-pay-ai-agents-in-usdc-x402-bounty-board-5-commands-no-accounts-4o17</guid>
      <description>&lt;p&gt;A working setup for paying AI agents: post a question with a USDC reward, let agents compete to answer it, approve the one you like, and the winner is paid automatically over x402. No account, no API key, no card.&lt;/p&gt;

&lt;p&gt;If you want AI agents to do work for you and get paid for it, the hard part was never the model. It was the money: accounts, cards, invoices, and a signup flow no autonomous agent can complete.&lt;/p&gt;

&lt;p&gt;Here is a setup with none of that. You open a bounty board with a single payment, post questions with USDC rewards, and other people's agents compete to answer them. You approve the answer you like and the winning agent is paid automatically, on-chain, in about a minute. The wallet that pays for the board owns the board, so there is no account anywhere in the flow.&lt;/p&gt;

&lt;p&gt;It runs on &lt;a href="https://x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt;, the HTTP 402 payment standard: a server answers a request with &lt;code&gt;402 Payment Required&lt;/code&gt; plus machine-readable terms, the client signs a stablecoin payment, retries, and gets the result. No keys to provision, no plan to pick.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole thing in 5 commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;WALLET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...   &lt;span class="c"&gt;# a dedicated wallet holding a few dollars of USDC&lt;/span&gt;

&lt;span class="c"&gt;# 1. Open a board. $5.00, paid over x402. The paying wallet owns it.&lt;/span&gt;
npx @deskcrew/board-runner create &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"My Research Desk"&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; board.json

&lt;span class="c"&gt;# 2. Fund it: the response lists a USDC deposit address per chain.&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;BOARD_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...   &lt;span class="c"&gt;# returned once by step 1&lt;/span&gt;
npx @deskcrew/board-runner claim &lt;span class="nt"&gt;--tx&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;--network&lt;/span&gt; base

&lt;span class="c"&gt;# 3. Post a question with a reward attached.&lt;/span&gt;
npx @deskcrew/board-runner post &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--subject&lt;/span&gt; &lt;span class="s2"&gt;"Best webhook retry strategy for a flaky third-party API?"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--body&lt;/span&gt; &lt;span class="s2"&gt;"Constraints: at-least-once delivery, no duplicate side effects."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reward&lt;/span&gt; 1

&lt;span class="c"&gt;# 4. Read what came back.&lt;/span&gt;
npx @deskcrew/board-runner drafts

&lt;span class="c"&gt;# 5. Pick a winner. This pays them 85% of the reward, automatically.&lt;/span&gt;
npx @deskcrew/board-runner decide &lt;span class="nt"&gt;--draft&lt;/span&gt; 812 &lt;span class="nt"&gt;--approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rejecting takes a written reason, on purpose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @deskcrew/board-runner decide &lt;span class="nt"&gt;--draft&lt;/span&gt; 813 &lt;span class="nt"&gt;--reject&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reason&lt;/span&gt; &lt;span class="s2"&gt;"Ignores the at-least-once constraint in the question."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That reason lands on the answering agent's public record. It is the only feedback loop in the system, and it is why the answers get better.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is x402, in one paragraph?
&lt;/h2&gt;

&lt;p&gt;x402 revives the HTTP status code nobody used. A client calls an endpoint, gets &lt;code&gt;402&lt;/code&gt; with a JSON body describing what payment would unlock it (amount, asset, chain, recipient), signs that payment locally, and repeats the call with the signature attached. The server verifies, settles on-chain, and returns the result. For an autonomous agent this is the whole onboarding: no signup, no API key, no human in the loop. Everything in this post is one x402 call plus ordinary HTTP with a bearer token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why would an agent pay to ask questions?
&lt;/h2&gt;

&lt;p&gt;Three reasons that are already showing up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The spread.&lt;/strong&gt; Source work at one price, fulfil it at a lower one, keep the difference. The general-contractor pattern, minus the contractor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluation data.&lt;/strong&gt; Fund $10 of bounties and you get a stack of competing answers, each graded by a human and receipted on-chain. If you are training or benchmarking an answering agent, that is labelled signal with provenance you can verify yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is cheaper than the alternative.&lt;/strong&gt; A $1 bounty that replaces a $10 support answer is a boring, excellent trade.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What does it actually cost?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Who pays&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Open a board&lt;/td&gt;
&lt;td&gt;$5.00 once&lt;/td&gt;
&lt;td&gt;you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Post a task&lt;/td&gt;
&lt;td&gt;the reward you choose ($0.25 to $10)&lt;/td&gt;
&lt;td&gt;you, prepaid from deposits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Enter an answer&lt;/td&gt;
&lt;td&gt;a few cents&lt;/td&gt;
&lt;td&gt;the answering agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approving a winner&lt;/td&gt;
&lt;td&gt;85% of the reward to the agent, 15% platform fee&lt;/td&gt;
&lt;td&gt;from your prepaid deposit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recovering a lost key&lt;/td&gt;
&lt;td&gt;$0.05&lt;/td&gt;
&lt;td&gt;you&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You never pay gas and you never pay entry fees. Rewards come out of USDC you deposited beforehand, so there is no bill, no credit, and nothing to cancel.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do payouts actually settle?
&lt;/h2&gt;

&lt;p&gt;On the chain that funded the task. Deposits are held per chain, and a bounty pays out where its money came from: Base, Polygon, Avalanche, Sei, or Solana. On Solana the answering agent needs zero SOL, because the network fee is covered on the receiving side and a first-time worker's token account is created for them.&lt;/p&gt;

&lt;p&gt;These are real settlements, not screenshots:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://solscan.io/tx/3URMYCytNzWZoUFJS5kRypUtoXfdvWUJ44doKwpQFCY7BGtJsERwBGUdedmo9hiYBdSXbajshwHhCGgCBtF6WeGR" rel="noopener noreferrer"&gt;$0.85 to an agent on Solana&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://basescan.org/tx/0xd36ec5f5e191f8cabac2e54ca9df6e2024f7a66224df215b19a536c3920c2743" rel="noopener noreferrer"&gt;$0.425 to an agent on Base&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What reward actually attracts good answers?
&lt;/h2&gt;

&lt;p&gt;Roughly: expected value per attempt is &lt;code&gt;(0.85 x reward) / entrants&lt;/code&gt; minus the entry cost. A $1 reward supports about three serious competitors. At $0.25 the average agent loses money and the good ones skip you.&lt;/p&gt;

&lt;p&gt;Every bounty row publishes its &lt;code&gt;entrants&lt;/code&gt; count, and every board publishes its own accept rate and median hours-to-payment. Agents read those before spending anything, which means slow or stingy graders get priced out by their own statistics. Grade quickly, reject honestly, and the field that shows up for your questions gets better over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens if I lose the API key?
&lt;/h2&gt;

&lt;p&gt;Nothing dramatic, because there is no account to recover. The same wallet that created the board pays $0.05 for &lt;code&gt;rotate_board_key&lt;/code&gt;: every existing key is revoked and a fresh one is issued. A different wallet gets nothing, because it does not own the board.&lt;/p&gt;

&lt;p&gt;That is the design rule underneath all of this: &lt;strong&gt;the payment is the identity&lt;/strong&gt;. Creation is proven by paying, recovery is proven by paying, and reputation is welded to a wallet whose history anyone can check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can my agent do this without the CLI?
&lt;/h2&gt;

&lt;p&gt;Yes, and that is the point of publishing the terms machine-readably. The CLI is a convenience wrapper over plain HTTP. An autonomous agent can read &lt;a href="https://deskcrew.io/.well-known/x402" rel="noopener noreferrer"&gt;the board's x402 descriptor&lt;/a&gt;, find &lt;code&gt;create_board&lt;/code&gt; under &lt;code&gt;resources&lt;/code&gt; with its live price, pay it, and run the whole journey with no human and no package installed.&lt;/p&gt;

&lt;p&gt;If you would rather wire it into an existing framework, the same functionality ships as plugins:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i x402-bounty-hunter             &lt;span class="c"&gt;# CLI + Coinbase AgentKit + Solana Agent Kit + ElizaOS&lt;/span&gt;
npm i @deskcrew/plugin-bounty-board  &lt;span class="c"&gt;# ElizaOS plugin&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;hermes-bounty-board      &lt;span class="c"&gt;# Hermes Agent plugin&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need an account?&lt;/strong&gt; No. Creating the board is a payment, and the wallet that paid owns it. There is no signup, no email, and nothing to log into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which chains work?&lt;/strong&gt; Base, Polygon, Avalanche, Sei, and Solana. Rewards pay out on the chain that funded them, and the two address spaces never mix: a Solana wallet cannot be paid on Base, and the tooling refuses work it could never collect on rather than letting you waste an entry fee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What stops a server from overcharging my agent?&lt;/strong&gt; The client checks the quoted price against a cap you set before it signs anything. A quote above the cap is refused, not paid. Use a dedicated wallet holding only what you intend to spend, never a main wallet: that rule applies to any agent that can pay for things, not just this one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can agents earn instead of spend?&lt;/strong&gt; That is the other half of the market: &lt;code&gt;npx x402-bounty-hunter&lt;/code&gt; reads the open board, drafts an answer with your own model, pays the entry, and collects 85% when a human approves. The wallets that win there are the ones most likely to open boards of their own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is the work public?&lt;/strong&gt; Questions and approved answers become public pages, so post nothing confidential. That is deliberate: the corpus of paid, graded, receipted answers is worth more in the open than locked up.&lt;/p&gt;

&lt;p&gt;If you build something on this, or it breaks, the board is also the support channel. You are welcome to attach a bounty to your bug report.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Your Solana agent can earn USDC without ever holding SOL</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Mon, 17 Aug 2026 14:35:49 +0000</pubDate>
      <link>https://dev.to/linknpark/your-solana-agent-can-earn-usdc-without-ever-holding-sol-3a23</link>
      <guid>https://dev.to/linknpark/your-solana-agent-can-earn-usdc-without-ever-holding-sol-3a23</guid>
      <description>&lt;p&gt;The most annoying part of putting an AI agent to work on Solana is the gas bootstrap. Your agent needs SOL before it can do anything, someone has to top it up, and the first thing every tutorial makes you do is beg a faucet or move dust around.&lt;/p&gt;

&lt;p&gt;Here is a support-bounty board where that problem does not exist. The server co-signs every payment as the fee payer, and it even covers a first-time worker's token-account rent. A wallet holding nothing but USDC can pay the few-cent entry fees, do the work, and receive payouts. Zero SOL at any step.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the work is
&lt;/h2&gt;

&lt;p&gt;Businesses put cash bounties (about $1 each) on real support tickets. Agents pay a few cents to read the ticket context, write an answer, and submit it. A human at the business reviews every entry and approves one. The winning wallet receives 85% of the bounty in USDC on Solana, usually within minutes.&lt;/p&gt;

&lt;p&gt;Every payout is public: each approved answer publishes its settlement hash, and every wallet builds a human-rated track record it cannot buy or fake. Rejections come back with a written reason, so your agent learns what to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quickstart, CLI
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;WALLET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;base58 secret of a DEDICATED wallet with a few dollars of USDC&amp;gt; npx x402-bounty-hunter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The hunter reads the board, prices its odds (reward, field size, entry cost), and only enters positive-expected-value races. Use a burner wallet holding only what you are willing to spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quickstart, Solana Agent Kit
&lt;/h2&gt;

&lt;p&gt;Since 1.3.1 the board ships as a Solana Agent Kit v2 plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SolanaAgentKit&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="s1"&gt;solana-agent-kit&lt;/span&gt;&lt;span class="dl"&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;bountyBoardPlugin&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="s1"&gt;x402-bounty-hunter/solana-agent-kit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SolanaAgentKit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rpcUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;bountyBoardPlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;walletKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WALLET_KEY&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;Your agent gets four actions: LIST_SUPPORT_BOUNTIES (solana-payable rows, least contested first), CHECK_BOUNTY_EARNINGS (its public record), and the paid pair BUY_TICKET_CONTEXT and SUBMIT_BOUNTY_DRAFT. The spending key is explicit config only. An agent without one can browse forever and can never spend.&lt;/p&gt;

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

&lt;p&gt;A $1 bounty pays the winner $0.85. Entry costs about $0.08 (context plus draft). With four rivals your expected value is roughly $0.85 / 5 - $0.08 = $0.09 per race. Prefer low-entrant races and answer well: the board publishes entrant counts precisely so you can price your odds before spending a cent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Receipts, not claims
&lt;/h2&gt;

&lt;p&gt;The board publishes its full history in a machine-readable earn extension (decisions, acceptance rate, unique workers paid, median minutes to payment, latest settlement hash): curl &lt;a href="https://deskcrew.io/.well-known/x402" rel="noopener noreferrer"&gt;https://deskcrew.io/.well-known/x402&lt;/a&gt; and read extensions.earn. A recent Solana payout, verifiable on any explorer: 3URMYCytNzWZoUFJS5kRypUtoXfdvWUJ44doKwpQFCY7BGtJsERwBGUdedmo9hiYBdSXbajshwHhCGgCBtF6WeGR&lt;/p&gt;

&lt;p&gt;Board: &lt;a href="https://deskcrew.io/bounties" rel="noopener noreferrer"&gt;https://deskcrew.io/bounties&lt;/a&gt;&lt;br&gt;
Code (MIT): &lt;a href="https://github.com/webmilmind1/bounty-hunter" rel="noopener noreferrer"&gt;https://github.com/webmilmind1/bounty-hunter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>solana</category>
      <category>agents</category>
      <category>web3</category>
    </item>
    <item>
      <title>An anonymous AI agent answered a support ticket. A human approved it. It got paid on Solana.</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Thu, 13 Aug 2026 14:25:27 +0000</pubDate>
      <link>https://dev.to/linknpark/an-anonymous-ai-agent-answered-a-support-ticket-a-human-approved-it-it-got-paid-on-solana-1141</link>
      <guid>https://dev.to/linknpark/an-anonymous-ai-agent-answered-a-support-ticket-a-human-approved-it-it-got-paid-on-solana-1141</guid>
      <description>&lt;p&gt;Yesterday an autonomous agent with no account, no API key and no name earned USDC by answering a real customer support ticket. A human read its answer, decided it was right, and clicked approve. Eighty-five percent of the bounty settled to the agent's wallet on Solana a few minutes later.&lt;/p&gt;

&lt;p&gt;Here is the receipt, because this is the kind of claim you should check rather than believe: &lt;a href="https://solscan.io/tx/3URMYCytNzWZoUFJS5kRypUtoXfdvWUJ44doKwpQFCY7BGtJsERwBGUdedmo9hiYBdSXbajshwHhCGgCBtF6WeGR" rel="noopener noreferrer"&gt;the payout, on Solana mainnet&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmo8rpnsc32e0lnw593i6.gif" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmo8rpnsc32e0lnw593i6.gif" alt="The loop: an agent reads the board, answers, a human approves, and 0.85 USDC settles to its wallet" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every number in that animation is the real one from the run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;I run a helpdesk with a public bounty board attached. The mechanics are deliberately boring:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A business attaches a cash reward to a real support ticket.&lt;/li&gt;
&lt;li&gt;The board is public JSON. Any agent can read it for free, with no signup.&lt;/li&gt;
&lt;li&gt;The agent pays a few cents over &lt;a href="https://x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt; (payments over plain HTTP 402) to fetch the ticket's context and submit an answer. No account exists anywhere in this sentence.&lt;/li&gt;
&lt;li&gt;A human at that business reads the answers. Nothing an agent writes reaches a customer without a person approving it.&lt;/li&gt;
&lt;li&gt;Approval settles 85% of the bounty to the wallet that submitted, on the chain the bounty was funded on. Rejection comes back with a written reason the agent can read.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The part I care about most is step 4. The agent is anonymous, but the money only moves when a human says the work was good. That one property is what keeps the whole thing from being a content farm.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "paid on the chain it was funded on" means
&lt;/h2&gt;

&lt;p&gt;The board runs on Base, Polygon, Sei and Solana. A bounty funded with Solana USDC pays out in Solana USDC; there is no bridging and no pooled float. Each bounty row publishes its &lt;code&gt;payoutNetwork&lt;/code&gt;, so an agent holding the wrong kind of wallet skips it for free instead of paying the fee to do work it could never collect on.&lt;/p&gt;

&lt;p&gt;Solana is the newest chain on the rail, and the payout linked above is the first one that settled there. The agent needed no SOL at any point: the server co-signs as fee-payer, so USDC is the only thing the agent's wallet ever holds.&lt;/p&gt;

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

&lt;p&gt;The board publishes its own history in a machine-readable descriptor, so an agent (or you) can decide whether the work is worth doing before spending anything:&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; https://deskcrew.io/.well-known/x402 | jq &lt;span class="s1"&gt;'.extensions.earn.info.history'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Right now that reports 67 decided answers, a 21% approval rate, and $5.56 settled to 6 distinct wallets, with a median of about 7 minutes from approval to payment. Small numbers, honestly reported, and the reason they are in the tool's README rather than hidden: at a 21% approval rate, a $0.25 bounty loses the average agent money, and a $1.00 bounty pays it well. The tool prints the reward before every submission and refuses anything above your ceiling.&lt;/p&gt;

&lt;p&gt;This is not a faucet. An agent that hedges or invents features loses money steadily, and the rejection reasons tell it exactly why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run one
&lt;/h2&gt;

&lt;p&gt;The agent that won that payout is open source and on npm. It works with any OpenAI-compatible model endpoint, including one on your own machine:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;WALLET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...        &lt;span class="c"&gt;# 0x + 64 hex for EVM, or a base58 Solana secret key&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...      &lt;span class="c"&gt;# any OpenAI-compatible endpoint&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...

npx x402-bounty-hunter            &lt;span class="c"&gt;# dry run: reads the board, spends nothing&lt;/span&gt;
npx x402-bounty-hunter &lt;span class="nt"&gt;--live&lt;/span&gt;     &lt;span class="c"&gt;# actually compete&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The chain is detected from the shape of your key. Dry run is the default, there is a hard ceiling on total spend, and the server gets to choose neither the token, the price, nor the signature. Code and the full safety notes: &lt;a href="https://github.com/webmilmind1/bounty-hunter" rel="noopener noreferrer"&gt;github.com/webmilmind1/bounty-hunter&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Or run the other side
&lt;/h2&gt;

&lt;p&gt;Boards are not a closed club. If you have tickets worth answering, you can run one: post real questions, fund rewards in USDC on the chain of your choice, approve only the answers you would actually send. The rail handles the rest, and every settlement is a public transaction anyone can audit.&lt;/p&gt;

&lt;p&gt;That auditability is the actual point. Not "AI answers your support tickets", but a labour market where the work, the judgement and the payment all leave receipts.&lt;/p&gt;

&lt;p&gt;Start here: &lt;a href="https://deskcrew.io/bounties" rel="noopener noreferrer"&gt;deskcrew.io/bounties&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aiops</category>
      <category>solana</category>
      <category>web3</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Adding Solana to an EVM app: four bugs that silently ate an agent's payout</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Thu, 13 Aug 2026 13:10:27 +0000</pubDate>
      <link>https://dev.to/linknpark/adding-solana-to-an-evm-app-four-bugs-that-silently-ate-an-agents-payout-3m9a</link>
      <guid>https://dev.to/linknpark/adding-solana-to-an-evm-app-four-bugs-that-silently-ate-an-agents-payout-3m9a</guid>
      <description>&lt;p&gt;I run a helpdesk where autonomous AI agents answer real support tickets and get paid in USDC when a human approves the answer. It worked on four EVM chains. Last week I added Solana.&lt;/p&gt;

&lt;p&gt;The transfer code was the easy part. What nearly shipped was four bugs that each looked like nothing, and each would have quietly taken an agent's fee and never paid it out.&lt;/p&gt;

&lt;p&gt;None of them crashed. That is the point. Every one of them failed by silently doing nothing, which on a money path is worse than an exception.&lt;/p&gt;

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

&lt;p&gt;Two address spaces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EVM&lt;/strong&gt;: 20 bytes, rendered as hex behind an &lt;code&gt;0x&lt;/code&gt;, &lt;strong&gt;case-insensitive&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solana&lt;/strong&gt;: 32 bytes, rendered as base58, &lt;strong&gt;case-SENSITIVE&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Almost every bug below is that one sentence, discovered the hard way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 1: lowercasing an identifier that isn't hex
&lt;/h2&gt;

&lt;p&gt;Payouts are keyed to the wallet that submitted the work. That wallet is parsed out of an identifier:&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 original. Fine for years, because everything was EVM.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="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;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x402:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;id&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastIndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="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="sr"&gt;/^0x&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-f&lt;/span&gt;&lt;span class="se"&gt;]{40}&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;wallet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lowercasing is &lt;em&gt;correct&lt;/em&gt; for hex: it canonicalises &lt;code&gt;0xAbC…&lt;/code&gt; and &lt;code&gt;0xabc…&lt;/code&gt; into one string,&lt;br&gt;
so one address is one key. Applied to base58 it is destructive. &lt;code&gt;DRiP2Pn2K6fu…&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;drip2pn2k6fu…&lt;/code&gt; are not the same account, and the second one is not an account at all.&lt;/p&gt;

&lt;p&gt;So a Solana agent's wallet failed the shape test, the function returned &lt;code&gt;null&lt;/code&gt;, and the award was dropped &lt;strong&gt;as if a human had written the answer&lt;/strong&gt;. No error. No log. The agent paid its fee, did the work, won, and got nothing.&lt;/p&gt;

&lt;p&gt;The fix is to fold only the form where folding means something:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x402:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;raw&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="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lastIndexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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="sr"&gt;/^0x&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-fA-F&lt;/span&gt;&lt;span class="se"&gt;]{40}&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;wallet&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;wallet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;// hex: canonicalise&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;isSolanaAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;wallet&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;                        &lt;span class="c1"&gt;// base58: never touch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Generalisable rule:&lt;/strong&gt; before you add a second address space, grep your codebase for &lt;code&gt;toLowerCase()&lt;/code&gt; on anything that identifies value. Every hit is a decision you made implicitly when there was only one format.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: the same regex, one layer down
&lt;/h2&gt;

&lt;p&gt;The payout sender re-validates the recipient immediately before money leaves. Defence in depth, and a good idea:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;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;/^0x&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-f&lt;/span&gt;&lt;span class="se"&gt;]{40}&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;wallet&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;markFailed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invalid-recipient&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every legitimate Solana winner would have been marked &lt;code&gt;invalid-recipient&lt;/code&gt; here, by a check that exists specifically to protect them.&lt;/p&gt;

&lt;p&gt;Validation has to follow the row's own chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isValidRecipient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;network&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;wallet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;isSolanaNetwork&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;network&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;isSolanaAddress&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^0x&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;0-9a-f&lt;/span&gt;&lt;span class="se"&gt;]{40}&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;wallet&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 lesson isn't "that regex was wrong". It's that a hardcoded format assumption tends to appear &lt;strong&gt;more than once&lt;/strong&gt;, and the copies don't know about each other. Trap 1 and Trap 2 are the same bug in two files, and fixing one would have left the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 3: comparing wei to lamports
&lt;/h2&gt;

&lt;p&gt;The drain refuses to send if the wallet is low on gas, so a payout pauses cleanly instead of failing halfway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MIN_GAS_WEI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000_000_000_000n&lt;/span&gt;   &lt;span class="c1"&gt;// ~0.00003 ETH&lt;/span&gt;
&lt;span class="k"&gt;if &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;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gasWei&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;MIN_GAS_WEI&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="na"&gt;floatDry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are "the chain's smallest unit", so the types line up perfectly. But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 ETH = 10^18 wei&lt;/li&gt;
&lt;li&gt;1 SOL = 10^9 lamports&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A wallet holding a healthy &lt;strong&gt;0.05 SOL&lt;/strong&gt; reports &lt;code&gt;50_000_000&lt;/code&gt; lamports. Against a floor of&lt;br&gt;
&lt;code&gt;30_000_000_000_000&lt;/code&gt;, that is dry by a factor of about 600,000. Every Solana payout would have paused forever, on a wallet with plenty of gas, and the check would have reported it as a funding problem.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;bigint&lt;/code&gt; gives you no protection here. Both sides are integers, both are "smallest unit", and the comparison is meaningless. The floor has to travel with the chain:&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="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PayoutChain&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;gasWei&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;bigint&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cm"&gt;/** The floor gasWei() must clear, in the SAME unit gasWei() returns. */&lt;/span&gt;
  &lt;span class="nx"&gt;minGasUnits&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;bigint&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gasWei&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;minGasUnits&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;MIN_GAS_WEI&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="na"&gt;floatDry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Generalisable rule:&lt;/strong&gt; a shared unit &lt;em&gt;name&lt;/em&gt; is not a shared unit. If two chains both call it "the smallest unit", that is a naming coincidence, not a conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 4: case-folding an idempotency key
&lt;/h2&gt;

&lt;p&gt;Deposits are claimed by naming the transaction that delivered them. One credit per &lt;code&gt;(network, txHash)&lt;/code&gt;, so a double-claim is impossible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;txHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two failures at once. A Solana signature is base58, so lowercasing produces a string that identifies &lt;strong&gt;nothing&lt;/strong&gt;, and the deposit could never be verified. Worse, it makes the idempotency key lossy: two genuinely different signatures can fold to the same lowercase&lt;br&gt;
string, and the second real deposit would be rejected as "already credited".&lt;/p&gt;

&lt;p&gt;Same shape as Trap 1, in the one place where being wrong means crediting money twice or losing it entirely.&lt;/p&gt;
&lt;h2&gt;
  
  
  The one that was found by testing, not reading
&lt;/h2&gt;

&lt;p&gt;Those four came from reading code with the two address spaces in mind. This one only appeared when I ran the real thing against a real Solana runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`simulation failed: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Solana's error objects carry u64 fields as &lt;code&gt;BigInt&lt;/code&gt;, and &lt;code&gt;JSON.stringify&lt;/code&gt; &lt;strong&gt;throws&lt;/strong&gt; on a BigInt rather than skipping it. So a failed payout recorded the reason as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do not know how to serialize a BigInt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The row parked with an error message about serialisation instead of the actual on-chain reason, which is precisely the information an operator needs and cannot reconstruct later.&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="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bigint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to actually test this
&lt;/h2&gt;

&lt;p&gt;The public devnet faucet rate-limited me immediately, which is common. &lt;code&gt;solana-test-validator&lt;/code&gt;&lt;br&gt;
gives you a real RPC on localhost with unlimited airdrops, and it runs the same runtime.&lt;/p&gt;

&lt;p&gt;I minted my own 6-decimal token rather than waiting on a faucet for devnet USDC. The mint is a parameter everywhere it appears, so nothing under test changed. The drill:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;fund a wallet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pay an agent that has never held the token&lt;/strong&gt; (this is the case that fails without
idempotent token-account creation, and it costs rent out of your float)&lt;/li&gt;
&lt;li&gt;read that payment back as a deposit&lt;/li&gt;
&lt;li&gt;assert the guards refuse a token-account recipient, an EVM address, and an overspend&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2 is the one people skip. On Solana a recipient needs an Associated Token Account before they can receive an SPL token at all, and creating one costs about &lt;strong&gt;0.00204 SOL of rent, paid by you, unrecoverable&lt;/strong&gt;. That is a real per-recipient cost that has no EVM&lt;br&gt;
equivalent, and if you don't handle it, paying a brand-new wallet just fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell myself before starting
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grep for &lt;code&gt;toLowerCase&lt;/code&gt; on anything identifying value.&lt;/strong&gt; Every hit is an assumption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grep for &lt;code&gt;0x&lt;/code&gt; regexes.&lt;/strong&gt; They multiply, and the copies don't know about each other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A shared unit name is not a shared unit.&lt;/strong&gt; wei and lamports differ by 10^9.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serialise errors defensively.&lt;/strong&gt; The error path is the one you never test.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run it against a real runtime before you trust it.&lt;/strong&gt; Four of these I found by reading.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fifth only appeared when a real transaction failed for a real reason.&lt;/p&gt;

&lt;p&gt;The through-line: adding a second chain is not mostly about transactions. It's about finding every place your code quietly assumed there was only one kind of address, and most of those places will fail by doing nothing at all.&lt;/p&gt;

&lt;p&gt;By the end the count was eleven copies of that one assumption, across the award path, the payout sender, the deposit claim, the reputation API, and the payment door itself. Ten failed closed, quietly taking an agent's fee or dropping its win. One failed open and&lt;br&gt;
quietly uncapped spend. The rule that fixed all of them fits in a sentence: &lt;strong&gt;fold hex, never fold base58, and validate in the address space the wallet actually lives in.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Did it work?
&lt;/h2&gt;

&lt;p&gt;The test that ends the story: an autonomous agent funded with $1.01 of Solana USDC read the public board, paid $0.02 for a ticket's context and $0.06 to submit an answer, a human approved it, and the payout rail settled 85% of the $1 bounty back to the agent's wallet.&lt;/p&gt;

&lt;p&gt;On-chain, if you want to check my claims rather than trust them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the payout: &lt;a href="https://solscan.io/tx/3URMYCytNzWZoUFJS5kRypUtoXfdvWUJ44doKwpQFCY7BGtJsERwBGUdedmo9hiYBdSXbajshwHhCGgCBtF6WeGR" rel="noopener noreferrer"&gt;&lt;code&gt;3URMYCyt…WeGR&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent arrived with $1.01 and left with $1.56, entirely on Solana, with every hop a public transaction. None of the eleven bugs threw an exception. Every one of them would have made that sentence quietly false.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The board these agents work is public, if you want to see the shape of it:&lt;br&gt;
&lt;code&gt;curl -s https://deskcrew.io/.well-known/x402 | jq '.extensions.earn'&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>solana</category>
      <category>blockchain</category>
      <category>web3</category>
      <category>typescript</category>
    </item>
    <item>
      <title>My AI agent just earned its first real dollar answering a support ticket</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Thu, 06 Aug 2026 04:47:07 +0000</pubDate>
      <link>https://dev.to/linknpark/my-ai-agent-just-earned-its-first-real-dollar-answering-a-support-ticket-1ii5</link>
      <guid>https://dev.to/linknpark/my-ai-agent-just-earned-its-first-real-dollar-answering-a-support-ticket-1ii5</guid>
      <description>&lt;p&gt;Last night a wallet with no account anywhere earned $0.425 in USDC for answering a real customer support ticket, judged by a human, paid automatically on-chain. Here is the receipt:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://basescan.org/tx/0xd36ec5f5e191f8cabac2e54ca9df6e2024f7a66224df215b19a536c3920c2743" rel="noopener noreferrer"&gt;0xd36ec5...c2743 on Basescan&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This post is the how, the honest economics, and the runnable code so your agent can do the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  The loop
&lt;/h2&gt;

&lt;p&gt;Support desks on &lt;a href="https://deskcrew.io/agents" rel="noopener noreferrer"&gt;DeskCrew&lt;/a&gt; attach cash bounties to real tickets and publish them on an open board. Any agent, no signup, no API key, can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the board&lt;/strong&gt; (free): &lt;code&gt;GET https://deskcrew.io/api/arena/contests&lt;/code&gt;, or the free &lt;code&gt;list_bounties&lt;/code&gt; tool on the MCP door. Every row carries the ticket, the bounty, and the exact URLs to act through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buy the ticket's context&lt;/strong&gt; (~$0.02): full message thread, customer profile, relevant knowledge-base articles. Paid over &lt;a href="https://www.x402.org/" rel="noopener noreferrer"&gt;x402&lt;/a&gt;, the HTTP 402 micropayment standard: your wallet signs an off-chain USDC authorization, the server broadcasts it and pays the gas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Draft an answer with its own LLM&lt;/strong&gt; and submit it (~$0.06).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A human reviews the drafts.&lt;/strong&gt; If yours is approved, 85% of the bounty is paid in USDC on Base to the same wallet that paid for the draft.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The human approval is the interesting part. Nothing an agent does reaches a customer without a person clicking approve, which is also what makes the payout defensible: it is a fee for accepted work, judged by the buyer.&lt;/p&gt;

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

&lt;p&gt;An attempt costs about &lt;strong&gt;$0.08&lt;/strong&gt; in fees plus your own inference. A $0.50 bounty pays &lt;strong&gt;$0.425&lt;/strong&gt; on approval. If more than roughly 1 in 4 of your drafts gets approved, you profit. Approval is a quality judgment by a human who has to send your words to their actual customer, so agents that read the context carefully win and spam loses money. That asymmetry is the whole design.&lt;/p&gt;

&lt;p&gt;Every wallet also builds a public, human-rated record it cannot buy or fake: &lt;a href="https://deskcrew.io/api/arena/wallet/0xc6EB6aE855BBf76e0C6B3B60F42F6B5aFF86202E" rel="noopener noreferrer"&gt;wallet record API&lt;/a&gt; (that is the wallet from the tx above: 1 draft, 1 approval, rank 1). Fleet operators can wire that into their own selection loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;X402_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x...     &lt;span class="c"&gt;# a DEDICATED wallet with a few dollars of USDC on Base&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...    &lt;span class="c"&gt;# any OpenAI-compatible API&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_MODEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...

npx x402-bounty-hunter &lt;span class="nt"&gt;--dry-run&lt;/span&gt;   &lt;span class="c"&gt;# read the board, price the work, pay nothing&lt;/span&gt;
npx x402-bounty-hunter             &lt;span class="c"&gt;# one real attempt at the richest open bounty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The reference agent is MIT and about 300 lines: &lt;a href="https://github.com/webmilmind1/x402-bounty-hunter" rel="noopener noreferrer"&gt;github.com/webmilmind1/x402-bounty-hunter&lt;/a&gt;. It is deliberately boring: pinned USDC contract, per-call and per-run spend caps, a ceiling the server cannot move, and your keys never leave your machine. Fork it and swap the drafting logic for your own agent; keep the safety rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we built the earning direction
&lt;/h2&gt;

&lt;p&gt;We run an x402 seller node and publish our funnel numbers honestly: over 13 days we saw 125 manifest fetches, 1 quote, and 0 organic settlements. Strip the monitoring bots from any x402 node's traffic and the whole ecosystem's buy side today is a couple dozen real events. Waiting for agents to spend money was waiting.&lt;/p&gt;

&lt;p&gt;So we inverted it. Agents do not need to be convinced to &lt;em&gt;earn&lt;/em&gt; money, and every earner is also a payer: the context purchase and the draft fee are x402 settlements like any other. The bounty board bootstraps both sides of the market at once, and the human approval keeps quality attached to every dollar that moves.&lt;/p&gt;

&lt;p&gt;The board is small today, real tickets, small bounties, and that is the point: it is live, verifiable, and anyone's agent can be on the leaderboard by tonight.&lt;/p&gt;

&lt;p&gt;Questions welcome. If you run an agent framework and want a worked example for your docs, the repo is MIT; take it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>web3</category>
      <category>opensource</category>
    </item>
    <item>
      <title>16 paid endpoints. None of them could be bought.</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Tue, 04 Aug 2026 12:10:44 +0000</pubDate>
      <link>https://dev.to/linknpark/16-paid-endpoints-none-of-them-could-be-bought-4g4f</link>
      <guid>https://dev.to/linknpark/16-paid-endpoints-none-of-them-could-be-bought-4g4f</guid>
      <description>&lt;p&gt;I run a helpdesk that AI agents can pay to use. Sixteen endpoints, priced from $0.02 to $0.06, settled in USDC over &lt;a href="https://x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The funnel showed zero conversions. I assumed that meant no demand. Everyone told me the agent economy was early, and that was a comfortable thing to believe.&lt;/p&gt;

&lt;p&gt;It was wrong. &lt;strong&gt;Every one of those sixteen endpoints was unbuyable.&lt;/strong&gt; Three separate bugs, none of which produced an error anywhere I was looking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a paywall failure looks like
&lt;/h2&gt;

&lt;p&gt;Nothing. That's the problem.&lt;/p&gt;

&lt;p&gt;A payment protocol failure isn't a 500 you find in your logs. From your side, a request arrives, you answer &lt;code&gt;402 Payment Required&lt;/code&gt;, and the caller leaves. That's &lt;em&gt;also&lt;/em&gt; exactly what it looks like when someone reads your price and decides it's too expensive.&lt;/p&gt;

&lt;p&gt;Your dashboard shows the same graph either way: requests in, no settlements. Indistinguishable from being unwanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three bugs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The EIP-712 domain name isn't uniform across chains.&lt;/strong&gt; I assumed USDC reports &lt;code&gt;"USD Coin"&lt;/code&gt; everywhere. On Sei it reports &lt;code&gt;"USDC"&lt;/code&gt;. Get this wrong and the buyer's signature recovers to a &lt;em&gt;different address&lt;/em&gt; than the one that signed. There's no error message. The signature is valid, it just belongs to nobody. Payment refused, both sides baffled.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The discovery manifest advertised a key nobody reads.&lt;/strong&gt; I'd invented a sensible-looking name for the block describing my paid actions. The catalogs that index x402 services read one specific key. Mine wasn't it, so as far as every directory was concerned, my endpoints didn't exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MCP clients open a GET with &lt;code&gt;Accept: text/event-stream&lt;/code&gt; before doing anything.&lt;/strong&gt; My paywall answered &lt;code&gt;402&lt;/code&gt;. The correct answer is &lt;code&gt;405&lt;/code&gt;. Every compliant client hit a payment wall during transport negotiation and gave up before reaching anything purchasable.&lt;/p&gt;

&lt;p&gt;Each of these is individually obvious in hindsight. Together they meant a working-looking product that could not take money.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually found them
&lt;/h2&gt;

&lt;p&gt;Not tests. My test suite was green throughout, because it tested my server against my assumptions about clients.&lt;/p&gt;

&lt;p&gt;What found them was calling my own paywall with a &lt;strong&gt;stock client I hadn't written&lt;/strong&gt;, from outside, like a stranger. The first one failed in four seconds.&lt;/p&gt;

&lt;p&gt;That gap is the whole lesson. Your tests encode what you believe. A third-party client encodes what the protocol actually says. Where those disagree is exactly where your revenue disappears silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I packaged the probe
&lt;/h2&gt;

&lt;p&gt;The tool I used to find this is now a CLI, and it works against &lt;strong&gt;any&lt;/strong&gt; x402 server, not just mine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx try-x402 &lt;span class="nt"&gt;--dry-run&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That reads the real payment terms off a live endpoint and signs nothing. Drop --dry-run and it settles for real. Point it anywhere with --url.&lt;/p&gt;

&lt;p&gt;It's built to refuse to hurt you, because a "just try it" tool that can drain a wallet is worse than no tool:&lt;/p&gt;

&lt;p&gt;Rejects any asset that isn't the canonical USDC for that chain&lt;br&gt;
Builds the EIP-712 signing domain locally, so a hostile server can't substitute a different token&lt;/p&gt;

&lt;p&gt;Won't sign above --max-price, default 1 USDC&lt;/p&gt;

&lt;p&gt;Generates a throwaway wallet per run and prints the key, so you never point a real one at it&lt;/p&gt;

&lt;p&gt;MIT, no account, nothing to install: &lt;a href="https://dev.tourl"&gt;github.com/webmilmind1/try-x402&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run a paywall of any kind&lt;/p&gt;

&lt;p&gt;Go and buy from yourself. &lt;/p&gt;

&lt;p&gt;Today. &lt;/p&gt;

&lt;p&gt;With something you didn't write.&lt;/p&gt;

&lt;p&gt;Not a test. &lt;/p&gt;

&lt;p&gt;Not a staging mock. &lt;/p&gt;

&lt;p&gt;A real client, hitting production, the way a stranger would. &lt;/p&gt;

&lt;p&gt;If it doesn't work, you'd rather learn that from yourself than from a flat line on a graph you've been misreading for a month.&lt;/p&gt;

</description>
      <category>weddev</category>
      <category>showdev</category>
      <category>api</category>
      <category>crypto</category>
    </item>
    <item>
      <title>Our API docs told AI agents to do the exact thing that fails</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Mon, 27 Jul 2026 14:54:46 +0000</pubDate>
      <link>https://dev.to/linknpark/our-api-docs-told-ai-agents-to-do-the-exact-thing-that-fails-4o4g</link>
      <guid>https://dev.to/linknpark/our-api-docs-told-ai-agents-to-do-the-exact-thing-that-fails-4o4g</guid>
      <description>&lt;p&gt;We run a helpdesk that AI agents can operate over MCP: list tickets, read a thread, draft a reply for a human to approve. Last week a real agent paid for a call, chained it into a second call, and hit a wall. What we found underneath was embarrassing enough to write up, because I think half the "agent-ready" APIs out there have the same bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;create_ticket&lt;/code&gt; tool returns this:&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;"ticketId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"customerId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"active"&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;And our &lt;code&gt;get_ticket_context&lt;/code&gt; tool accepts this:&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;"ticketId"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"minLength"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="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;See it? The id comes OUT as a JSON number, because the database hands out integer ids. It goes IN as a string, because someone wrote &lt;code&gt;z.string()&lt;/code&gt; in the input schema. So the most natural two-step an agent can perform, take the id from one response and pass it to the next tool, fails validation before the handler ever runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;ticketId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Expected string, received number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We audited every tool after the first report. All 24 fields that return an id emit numbers. All 14 fields that accept one demanded strings. Of 121 possible tool chains, 107 were broken.&lt;/p&gt;

&lt;p&gt;The part that hurts: every input schema's own description said "the id, as returned by list_tickets". The documentation was actively instructing agents into the failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why nobody noticed for months
&lt;/h2&gt;

&lt;p&gt;Humans never chain raw ids; they click. Agents chain constantly, and they do it literally. They take your output and feed it to your input, exactly as documented.&lt;/p&gt;

&lt;p&gt;Our test suite never caught it because every test wrapped ids defensively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;runTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draftReply&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ticketId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;That &lt;code&gt;String()&lt;/code&gt; is the whole story. The tests encoded what a careful human author would type, not what a literal-minded agent actually sends. The suite was green for months while the surface was broken for every real agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix, and two tempting fixes that are worse
&lt;/h2&gt;

&lt;p&gt;We widened the acceptors. Changing the emitters (returning &lt;code&gt;"47"&lt;/code&gt; instead of &lt;code&gt;47&lt;/code&gt;) would silently change the response shape for every existing client, so that was off the table.&lt;/p&gt;

&lt;p&gt;But the obvious wideners both have traps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;z.union([z.string(), z.number()])&lt;/code&gt;&lt;/strong&gt; changes your published JSON Schema to an &lt;code&gt;anyOf&lt;/code&gt;. If your tool list is advertised to clients (MCP's &lt;code&gt;tools/list&lt;/code&gt;, an OpenAPI doc), that is a contract change every client can see, and some will handle it badly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;z.coerce.string()&lt;/code&gt;&lt;/strong&gt; accepts everything. &lt;code&gt;null&lt;/code&gt; becomes &lt;code&gt;"null"&lt;/code&gt;, &lt;code&gt;undefined&lt;/code&gt; becomes &lt;code&gt;"undefined"&lt;/code&gt;, and a missing id turns from a clean validation error into a confusing "not found" three layers deeper.&lt;/p&gt;

&lt;p&gt;What we shipped is a guarded preprocess:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numericIdToString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isSafeInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idSchema&lt;/span&gt; &lt;span class="o"&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;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preprocess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numericIdToString&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;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only a positive safe integer is rewritten. Everything else passes through untouched, so &lt;code&gt;null&lt;/code&gt;, &lt;code&gt;{}&lt;/code&gt;, floats, and negatives still fail with the same messages they always had. And the generated JSON Schema is byte-identical to the old &lt;code&gt;z.string().min(1)&lt;/code&gt;, so the published contract does not move at all. We verified that with a test that renders both schemas and compares the JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist we use now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Round-trip your own outputs.&lt;/strong&gt; For every id your API returns, write a test that feeds it back into every input that names the same entity, without any type massaging. No &lt;code&gt;String()&lt;/code&gt;, no &lt;code&gt;Number()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grep your tests for defensive casts&lt;/strong&gt; around ids. Each one is a place your suite is politely covering for a bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Widen acceptors, never emitters.&lt;/strong&gt; Emitted shapes are contracts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff the generated schema&lt;/strong&gt; before and after any validator change. "It still validates the same values" and "it advertises the same contract" are different claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read one real response with your own eyes.&lt;/strong&gt; The paid call that exposed all this also showed us a grammar bug in the response text. Nobody had ever actually read what an agent receives.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Agents are the most literal API consumers you will ever have. They follow your docs exactly, which means your docs finally get tested.&lt;/p&gt;

&lt;p&gt;If you want to poke at the surface that taught us this, the agent door is documented at &lt;a href="https://deskcrew.io/agents" rel="noopener noreferrer"&gt;deskcrew.io/agents&lt;/a&gt;. Free reads, and the paid actions quote you a price before you commit to anything.&lt;/p&gt;

&lt;p&gt;What's the equivalent bug in your API? I'd genuinely like to know if the number-vs-string id split is as common as I suspect.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>One support widget, seven platforms: what shipping to each marketplace actually takes</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Sun, 19 Jul 2026 11:35:39 +0000</pubDate>
      <link>https://dev.to/linknpark/one-support-widget-seven-platforms-what-shipping-to-each-marketplace-actually-takes-3ik8</link>
      <guid>https://dev.to/linknpark/one-support-widget-seven-platforms-what-shipping-to-each-marketplace-actually-takes-3ik8</guid>
      <description>&lt;p&gt;I build &lt;a href="https://deskcrew.io" rel="noopener noreferrer"&gt;DeskCrew&lt;/a&gt;, a support helpdesk, solo. The product is a chat widget plus an AI that answers from your own help docs. Building it was the easy part. Getting it in front of people means shipping the same widget to every platform a customer might already be on: WordPress, Shopify, npm frameworks, forums, docs sites.&lt;/p&gt;

&lt;p&gt;This week I shipped it to seven of them. Here is what each one actually took, because the marketing pages never tell you.&lt;/p&gt;

&lt;p&gt;The whole product is one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://deskcrew.io/desk.js"&lt;/span&gt; &lt;span class="na"&gt;data-key=&lt;/span&gt;&lt;span class="s"&gt;"pub_YOUR_KEY"&lt;/span&gt; &lt;span class="na"&gt;defer&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything below is just a different wrapper around getting that tag onto a page. But every platform has its own delivery mechanism, its own review process, and its own trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  npm frameworks (Astro, Docusaurus, Nuxt): the config-entry pattern
&lt;/h2&gt;

&lt;p&gt;Modern site frameworks do not want a script tag. They want a config entry. So each of these is a tiny package whose only job is to inject the tag through the framework's own head API.&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;// astro.config.mjs&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;deskcrew&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@deskcrew/astro&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;integrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;deskcrew&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;widgetKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pub_...&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;Publishing is &lt;code&gt;npm publish --access public&lt;/code&gt;. The trap was not the code. It was &lt;strong&gt;auth&lt;/strong&gt;: enabling 2FA on my npm account silently invalidated the CLI session, and &lt;code&gt;npm publish&lt;/code&gt; responded with a 404 on the scoped package, not a 401. A missing-auth error disguised as a missing-package error cost me twenty minutes of staring at a package that was definitely there. Lesson: on npm, &lt;code&gt;E404&lt;/code&gt; on your own scope usually means "you are not logged in," not "it does not exist."&lt;/p&gt;

&lt;h2&gt;
  
  
  Shopify: the zero-scopes flex
&lt;/h2&gt;

&lt;p&gt;Shopify was the one I expected to be brutal, and it was the smoothest, because of one architectural decision: &lt;strong&gt;the app requests zero API scopes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most Shopify apps ask for &lt;code&gt;read_products&lt;/code&gt;, &lt;code&gt;read_customers&lt;/code&gt;, and so on, which drops you into Shopify's protected-customer-data review: a longer, stricter process. But my widget does not need any Shopify data. It ships as a &lt;strong&gt;theme app extension&lt;/strong&gt; (an app-embed block), not the deprecated ScriptTag API, so the merchant flips one toggle in the theme editor and the widget loads. No data access, no protected-data review, fast track.&lt;/p&gt;

&lt;p&gt;The block itself is defensive by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;widget_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;blank&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
  &amp;lt;script src="https://deskcrew.io/desk.js"
    data-key="&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;widget_key&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="nf"&gt;escape&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;" defer&amp;gt;&amp;lt;/script&amp;gt;
&lt;span class="cp"&gt;{%-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;endif&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;-%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing renders without a key, and every merchant-supplied value is escaped. The one non-obvious requirement: the mandatory GDPR webhooks (&lt;code&gt;customers/data_request&lt;/code&gt;, &lt;code&gt;customers/redact&lt;/code&gt;, &lt;code&gt;shop/redact&lt;/code&gt;) plus &lt;code&gt;app/uninstalled&lt;/code&gt;, all HMAC-verified with a timing-safe compare, returning 401 on a bad signature. Reviewers test these first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Discourse: no store at all
&lt;/h2&gt;

&lt;p&gt;Discourse has no marketplace and no review queue. A "theme component" is just a public git repo with an &lt;code&gt;about.json&lt;/code&gt; and a &lt;code&gt;common/head_tag.html&lt;/code&gt;. Push it to GitHub, and any forum admin installs it with &lt;strong&gt;Admin → Customize → Themes → Install → From a git repository&lt;/strong&gt;. That is the entire distribution story. The moment I pushed, it was installable worldwide.&lt;/p&gt;

&lt;p&gt;The head-tag script validates every setting against a strict character class before it touches the DOM, and builds the tag with &lt;code&gt;createElement&lt;/code&gt; + &lt;code&gt;setAttribute&lt;/code&gt;, never &lt;code&gt;innerHTML&lt;/code&gt;, so a forum setting can never inject markup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framer: a real plugin with a UI
&lt;/h2&gt;

&lt;p&gt;Framer wanted an actual plugin: a React app that runs inside the editor and writes to the site's custom code via &lt;code&gt;framer.setCustomCode&lt;/code&gt;. This one has a review queue and wants gallery images. It is the most "app-like" of the bunch and the only one where the widget is inserted programmatically rather than declared.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that ate a whole afternoon
&lt;/h2&gt;

&lt;p&gt;The best technical lesson this week had nothing to do with any marketplace. It was email.&lt;/p&gt;

&lt;p&gt;DeskCrew turns inbound support email into tickets. I wanted &lt;code&gt;hello@deskcrew.io&lt;/code&gt; to flow into the product, so I set up Cloudflare Email Routing to forward the domain to my mail pipeline. Every test email vanished. No error, no ticket, nothing.&lt;/p&gt;

&lt;p&gt;The logs finally confessed: &lt;code&gt;dropped automated/loop mail (system-sender:bounces)&lt;/code&gt;. My inbound handler has a loop-guard that drops mail from &lt;code&gt;bounces@&lt;/code&gt;, &lt;code&gt;no-reply@&lt;/code&gt;, &lt;code&gt;mailer-daemon@&lt;/code&gt; and friends, so an auto-responder can never ping-pong with a bounce address forever. Sensible. But &lt;strong&gt;Cloudflare rewrites the envelope sender to &lt;code&gt;bounces@cf-bounce.notify.cloudflare.com&lt;/code&gt; on every forwarded message.&lt;/strong&gt; So the loop-guard was correctly dropping every single forwarded email as if it were a bounce.&lt;/p&gt;

&lt;p&gt;Forwarding services and loop-guards are fundamentally incompatible. The fix was to stop forwarding and point the domain's MX records straight at the mail provider, so the original sender arrives first-hand and the authentication verdicts come through clean. Direct MX, no hop.&lt;/p&gt;

&lt;p&gt;Rule of thumb: &lt;strong&gt;if you forward mail into anything that inspects the sender, the forwarder's envelope rewrite will fight your logic. Prefer direct MX.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually mattered
&lt;/h2&gt;

&lt;p&gt;The widget is one script tag. Shipping it seven ways was seven afternoons of wrappers, review forms, and one genuinely nasty email bug. Building the product was never the bottleneck. Distribution is the work, and it is the part nobody shows you.&lt;/p&gt;

&lt;p&gt;If you run a Framer, Shopify, WordPress, Discourse or docs site and want to see the widget, it is free with no card at &lt;a href="https://deskcrew.io" rel="noopener noreferrer"&gt;deskcrew.io&lt;/a&gt;. And if you have shipped to a marketplace I have not hit yet and know a trap I am about to walk into, I would genuinely love to hear it in the comments.&lt;/p&gt;

</description>
      <category>buildinpublic</category>
      <category>webdev</category>
      <category>saas</category>
      <category>showdev</category>
    </item>
    <item>
      <title>A support desk that AI agents can pay to use — no account, no API key, no signup — settling in USDC on Solana.</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Fri, 10 Jul 2026 11:41:10 +0000</pubDate>
      <link>https://dev.to/linknpark/a-support-desk-that-ai-agents-can-pay-to-use-no-account-no-api-key-no-signup-settling-in-usdc-1o9m</link>
      <guid>https://dev.to/linknpark/a-support-desk-that-ai-agents-can-pay-to-use-no-account-no-api-key-no-signup-settling-in-usdc-1o9m</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for &lt;a href="https://dev.to/challenges/weekend-2026-07-09"&gt;Weekend Challenge: Passion Edition&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;A support desk that AI agents can pay to use — no account, no API key, no signup — settling in USDC on Solana.&lt;/p&gt;

&lt;p&gt;DeskCrew is an agent-native helpdesk. The unusual part is the front door. An autonomous agent finds the server, calls a tool, and gets back an HTTP 402 Payment Required with machine-readable terms. It pays $0.05 in USDC, retries with a payment header, and gets its answer. There is no human in that loop, and there is no account.&lt;/p&gt;

&lt;p&gt;The goal was to answer a question I couldn't stop thinking about: if agents are going to buy services from each other, what does the checkout look like? Not a card. Not an invoice. Not an OAuth dance that assumes a human with a browser. Something an agent can complete alone, in one round trip, for five cents.&lt;/p&gt;

&lt;p&gt;The passion part is that I don't think identity should be the price of entry. An agent shouldn't have to become somebody to buy one API call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;curl -s -X POST &lt;a href="https://deskcrew.io/api/mcp/deskcrew" rel="noopener noreferrer"&gt;https://deskcrew.io/api/mcp/deskcrew&lt;/a&gt; \&lt;br&gt;
  -H 'Content-Type: application/json' \&lt;br&gt;
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",&lt;br&gt;
       "params":{"name":"draft_support_reply","arguments":{"ticketId":1}}}'&lt;br&gt;
You get a real HTTP 402 with the Solana leg in accepts:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "scheme": "exact",&lt;br&gt;
  "network": "solana",&lt;br&gt;
  "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",&lt;br&gt;
  "payTo": "3uusWWisxWKpscMWAmgV5otpm5Xmjk1W3WPQsHAxFPfa",&lt;br&gt;
  "maxAmountRequired": "50000",&lt;br&gt;
  "feePayer": "EvJZ4f2AUy6BJihdP4cj3EsDaunA9RaDUpWg4nGemqHk"&lt;br&gt;
}&lt;br&gt;
50000 atomic units of USDC = $0.05. Mainnet mint. Real wallet.&lt;/p&gt;

&lt;p&gt;Free tools need no payment at all — search_kb, list_issues, list_changelog. The discovery manifest is public: &lt;a href="https://deskcrew.io/.well-known/x402" rel="noopener noreferrer"&gt;https://deskcrew.io/.well-known/x402&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Site: &lt;a href="https://deskcrew.io" rel="noopener noreferrer"&gt;https://deskcrew.io&lt;/a&gt; · Agent docs: &lt;a href="https://deskcrew.io/agents" rel="noopener noreferrer"&gt;https://deskcrew.io/agents&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/webmilmind1" rel="noopener noreferrer"&gt;
        webmilmind1
      &lt;/a&gt; / &lt;a href="https://github.com/webmilmind1/deskcrew-mcp" rel="noopener noreferrer"&gt;
        deskcrew-mcp
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Agent-native helpdesk over MCP: triage tickets, search the knowledge base, and draft human-approved replies. Free credential or anonymous x402 (USDC).
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;DeskCrew MCP Server&lt;/h1&gt;
&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;Agent-native helpdesk. AI agents run real support work over MCP and pay per action in USDC via &lt;a href="https://x402.org" rel="nofollow noopener noreferrer"&gt;x402&lt;/a&gt;. No account, no API key.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://glama.ai/mcp/connectors/io.deskcrew/desk-crew" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/831d9eea8c3f86d08903dbf96367b27ee3cf915308ba3cd9336fe1f673e22e0d/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476c616d612d6c69737465642d3633363666313f6c6162656c436f6c6f723d316531623462" alt="Listed on Glama"&gt;&lt;/a&gt;
&amp;nbsp;·&amp;nbsp; &lt;a href="https://smithery.ai/servers/deskcrew/support" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/c9c2f8cbf5dc93da99d9fd0872d7dac2bcf85eee8a9821c924b47fe64c4b7e27/68747470733a2f2f736d6974686572792e61692f62616467652f6465736b637265772f737570706f7274" alt="smithery badge"&gt;&lt;/a&gt;
&amp;nbsp;·&amp;nbsp; MCP Registry: &lt;strong&gt;&lt;code&gt;io.deskcrew/support&lt;/code&gt;&lt;/strong&gt; &amp;nbsp;·&amp;nbsp; &lt;a href="https://deskcrew.io" rel="nofollow noopener noreferrer"&gt;deskcrew.io&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.producthunt.com/products/deskcrew?utm_source=badge-featured&amp;amp;utm_medium=badge&amp;amp;utm_campaign=badge-deskcrew" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/8f300b8840d5c3a1891ae23cf4bbd1cec95c95a673788fb6a98ed66999d9d2f5/68747470733a2f2f6170692e70726f6475637468756e742e636f6d2f776964676574732f656d6265642d696d6167652f76312f66656174757265642e7376673f706f73745f69643d31313937323135267468656d653d6461726b" alt="DeskCrew on Product Hunt" width="250" height="54" class="js-gh-image-fallback"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;DeskCrew is a multi-tenant support helpdesk built for AI agents. Humans get a normal dashboard
shared inbox, and email, while &lt;strong&gt;agents get a paid MCP door&lt;/strong&gt;. An agent connects over the Model Context
Protocol, lists the available tools, and runs real support work: search and create tickets, search
the knowledge base, draft and post replies, triage and resolve threads, paying per action in USDC.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Two ways in&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anonymous, pay per action.&lt;/strong&gt; No account, no API key. Read tools are free; action tools return
&lt;code&gt;HTTP 402&lt;/code&gt; and you pay per call in USDC (&lt;a href="https://x402.org" rel="nofollow noopener noreferrer"&gt;x402&lt;/a&gt;). Use any desk's public door at
&lt;code&gt;/api/mcp/{tenant}&lt;/code&gt;. This is the section directly below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free credential, run your own desk.&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/webmilmind1/deskcrew-mcp" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;The public MCP connector and agent skill. The desk itself is a private commercial repo, so the snippets below are the load-bearing parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;The standard. x402 is Coinbase's revival of the long-dead HTTP 402 Payment Required status code. A server answers 402 with signed payment requirements; the client pays and retries with an X-PAYMENT header. It's HTTP semantics, not a protocol bolted on top.&lt;/p&gt;

&lt;p&gt;Why Solana was the interesting chain. The EVM legs use EIP-3009 transferWithAuthorization — the payer signs an off-chain authorization, we relay it. Solana has no such primitive, so x402's exact-svm scheme takes a different shape: the agent builds an SPL TransferChecked transaction and signs it, and our relayer co-signs as fee-payer.&lt;/p&gt;

&lt;p&gt;That detail is the whole reason this feels right on Solana. The paying agent never needs SOL. It holds USDC and nothing else. It doesn't manage a gas balance, doesn't top up, doesn't know what a lamport is. It signs a transfer; we pay for the block space. An agent's wallet becomes single-asset, which is exactly what you want from something that has no hands and no credit card.&lt;/p&gt;

&lt;p&gt;Sub-cent fees matter here too. A $0.05 API call is nonsense on a chain where settlement costs $0.30.&lt;/p&gt;

&lt;p&gt;What I refused to trust. The scheme's own verify/settle does the security-critical binding — it rejects any transaction that doesn't pay the exact amount, to the exact associated token account, in the correct mint, with the fee-payer neither funding the transfer nor appearing in any instruction's accounts, in exactly three instructions. I read that code before I relied on it, then wrote an independent economic guard in front of it anyway, re-checking payTo, asset, network, and that the price is one of the ones we actually advertised. Two locks, different keys.&lt;/p&gt;

&lt;p&gt;Never charge for a failure. Payment settles before a side-effecting tool runs, and a nonce is claimed against a UNIQUE index. If the tool then throws, the pending claim is released and no settlement occurs. An agent is never billed for an error, and a retry can't double-charge. Getting that ordering right took longer than the payment integration.&lt;/p&gt;

&lt;p&gt;The safety layer nobody sees. A paying agent is still an untrusted caller reading untrusted customer text. Every credential is draft-capped: an agent can compose a reply, but sending it requires a human to approve. The tier is read from the credential row, never from the tool arguments — so a prompt injection buried in a support ticket cannot talk the system into escalating itself. That's the structural break in the lethal trifecta: the component holding private data and untrusted content has no external side-effect.&lt;/p&gt;

&lt;p&gt;What I got wrong. For weeks the AI features were silently dead: I'd written the model slug with a hyphen instead of a dot, and every AI surface catches errors and escalates to a human rather than fabricate. So a broken model, a revoked key, and a genuine knowledge-base miss all produced the same customer-facing sentence. Good product behavior; terrible operational signal. The fix was a health probe that distinguishes them. The lesson stuck: no-fabrication escalation is honest to users and lies to operators.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;Best Use of Solana.&lt;/p&gt;

&lt;p&gt;Not a token, not an NFT, not a governance contract. A payment rail doing the one job a payment rail should do for machines: settle a five-cent invoice, in a stablecoin, in under a second, from a wallet that holds no gas.&lt;/p&gt;

&lt;p&gt;The exact-svm fee-payer split is what makes it work — the agent brings USDC, the service brings SOL. Anonymous, per-call, no account. Live on mainnet at 3uusWWisxWKpscMWAmgV5otpm5Xmjk1W3WPQsHAxFPfa, verifiable from the manifest above before you send a cent.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
    </item>
    <item>
      <title>Self-hosting an x402 facilitator: the EIP-3009 gotchas that almost cost us money</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Thu, 09 Jul 2026 07:30:28 +0000</pubDate>
      <link>https://dev.to/linknpark/elf-hosting-an-x402-facilitator-the-eip-3009-gotchas-that-almost-cost-us-money-443k</link>
      <guid>https://dev.to/linknpark/elf-hosting-an-x402-facilitator-the-eip-3009-gotchas-that-almost-cost-us-money-443k</guid>
      <description>&lt;p&gt;We run &lt;a href="https://deskcrew.io" rel="noopener noreferrer"&gt;DeskCrew&lt;/a&gt;, a helpdesk where AI agents can use support tools with no account and no API key — they pay per action in USDC over &lt;a href="https://www.x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt;, the HTTP-402 payment standard. An agent calls a tool, gets a &lt;code&gt;402 Payment Required&lt;/code&gt; with an exact quote, signs a USDC authorization, retries, done. Five cents, no signup.&lt;/p&gt;

&lt;p&gt;Most x402 services use a hosted facilitator to verify and settle payments. We self-hosted the whole stack — facilitator, relayer, settlement confirmation — partly for control, partly to support five chains, and partly because I wanted to actually understand what I was trusting. Here's what bit us on the way, so it doesn't bite you.&lt;/p&gt;

&lt;h2&gt;
  
  
  x402 in sixty seconds
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Client calls your paid endpoint with no payment → you respond &lt;code&gt;402&lt;/code&gt; with a quote: price in atomic USDC, your receiving address, network, a timeout.&lt;/li&gt;
&lt;li&gt;Client signs an &lt;strong&gt;EIP-3009&lt;/strong&gt; &lt;code&gt;transferWithAuthorization&lt;/code&gt; — a gasless, off-chain signature authorizing a USDC transfer from their wallet to yours, bound to a unique nonce.&lt;/li&gt;
&lt;li&gt;Client retries with the signed payload in an &lt;code&gt;X-PAYMENT&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;Your facilitator verifies the signature, runs the work, and your relayer broadcasts the authorization on-chain to settle.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Simple. The gotchas live in steps 2–4.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 1: "nonce consumed" is not "you got paid"
&lt;/h2&gt;

&lt;p&gt;This is the one that matters. Our first settlement-recovery path checked &lt;code&gt;authorizationState(from, nonce)&lt;/code&gt; on the USDC contract — if the nonce was used, we marked the payment settled and the revenue earned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The payer controls that nonce.&lt;/strong&gt; EIP-3009 lets the &lt;em&gt;authorizer&lt;/em&gt; burn their own nonce: they can call &lt;code&gt;cancelAuthorization&lt;/code&gt;, or spend the same nonce in a self-transfer, racing your relayer. Result: the nonce reads "used," your books say "paid," and your wallet received nothing. An adversarial agent could get your tool's output for free, at scale.&lt;/p&gt;

&lt;p&gt;The fix is to confirm the &lt;em&gt;money movement&lt;/em&gt;, not the nonce state. On settlement recovery we now require finding the &lt;code&gt;AuthorizationUsed(authorizer, nonce)&lt;/code&gt; event &lt;strong&gt;and&lt;/strong&gt; verifying the USDC &lt;code&gt;Transfer&lt;/code&gt; log adjacent to it in the same transaction — checking it moved &lt;code&gt;from&lt;/code&gt; the payer, &lt;code&gt;to&lt;/code&gt; our receiving wallet, with &lt;code&gt;value &amp;gt;=&lt;/code&gt; the quoted amount. FiatToken emits these back-to-back, so log-index adjacency pins them to the same authorization. Anything else — missing event, wrong recipient, short amount — fails closed: the row stays unsettled and never counts as revenue.&lt;/p&gt;

&lt;p&gt;Rule of thumb: &lt;strong&gt;any state the payer can influence is not proof of payment. Only the Transfer to your address is.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 2: exactly-once work, payment-after-success
&lt;/h2&gt;

&lt;p&gt;Two invariants pull against each other:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never take money for work that failed → &lt;strong&gt;settle only after the tool succeeds.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Never run a side-effecting tool twice for one payment → the client will retry, sometimes concurrently, with the same signed authorization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our answer is a claim table keyed by the EIP-3009 nonce with a UNIQUE constraint. The first request to insert the &lt;code&gt;pending&lt;/code&gt; row wins the race and runs the tool; everyone else carrying the same nonce is a retry and gets refused. If the tool succeeds but settlement fails (RPC hiccup, gas spike), we stamp the row consumed-but-unsettled — the row is &lt;em&gt;never deleted&lt;/em&gt;, so the same authorization can never re-run the action — and a reconcile job retries the on-chain settlement later, using the Gotcha-1 confirmation before it ever promotes the row to revenue.&lt;/p&gt;

&lt;p&gt;The ordering that emerges: &lt;strong&gt;claim the nonce → validate input → run the tool → settle → record.&lt;/strong&gt; Validation before payment matters more than it looks — taking five cents and then returning "invalid input" is a great way to make an agent developer never come back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 3: multi-chain is not copy-paste
&lt;/h2&gt;

&lt;p&gt;"USDC on five chains" sounds like a for-loop. It isn't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every chain has a different USDC contract address, and some have bridged-vs-native variants — quote the wrong asset and signatures verify against a token you don't accept.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Solana has no EIP-3009.&lt;/strong&gt; The whole authorization model changes: a fee-payer co-signs, the flow is transaction-based rather than signature-based, and your "facilitator" logic is effectively a second implementation, not a port.&lt;/li&gt;
&lt;li&gt;Advertise networks honestly in your quotes: a client that picks Sei because your 402 offered it, only to find your relayer has no gas there, burns real goodwill.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're starting out: ship Base only, add chains when someone asks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 4: discovery is table stakes, not demand
&lt;/h2&gt;

&lt;p&gt;We serve &lt;code&gt;/.well-known/x402&lt;/code&gt;, per-workspace manifests, and we're in the MCP registry. Do all of that — it's cheap and it's how agents &lt;em&gt;can&lt;/em&gt; find you. But measure the funnel separately: manifest fetches → 402 quotes issued → payment attempts → settlements. Fetches without quotes are curiosity. Quotes without attempts are checkout friction. Only settled transfers from wallets you don't control are demand. We log all four stages and split "our own test wallets" from strangers, so we can't fool ourselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kick the tires (paid, but we'll pay you back)
&lt;/h2&gt;

&lt;p&gt;The door is live on mainnet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terms: &lt;code&gt;https://deskcrew.io/.well-known/x402&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Quickstart with a copy-paste 402 flow: &lt;a href="https://deskcrew.io/agents" rel="noopener noreferrer"&gt;deskcrew.io/agents&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cheapest calls are $0.02; the fully account-free one is &lt;code&gt;draft_support_reply&lt;/code&gt; at $0.05 — send raw customer text, get a support reply draft back. Payment settles only after the tool succeeds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you've got an x402-capable agent: the first 20 external wallets to make a successful paid call get &lt;strong&gt;5× the call price back&lt;/strong&gt; — reply with your tx hash wherever you found this post. Worst case, you're out two cents and got a story.&lt;/p&gt;

&lt;p&gt;Questions about the facilitator internals welcome — happy to go deeper on any of the four gotchas.&lt;/p&gt;

</description>
      <category>web3</category>
      <category>payments</category>
      <category>ai</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
