<?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,format=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>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>
    <item>
      <title>I built an "agent door" — how AI agents pay per action on my helpdesk (MCP + x402)</title>
      <dc:creator>Kaven C</dc:creator>
      <pubDate>Fri, 03 Jul 2026 13:30:43 +0000</pubDate>
      <link>https://dev.to/linknpark/i-built-an-agent-door-how-ai-agents-pay-per-action-on-my-helpdesk-mcp-x402-3ng8</link>
      <guid>https://dev.to/linknpark/i-built-an-agent-door-how-ai-agents-pay-per-action-on-my-helpdesk-mcp-x402-3ng8</guid>
      <description>&lt;p&gt;Most "AI + payments" stories are about humans buying things through an agent. I wanted the opposite: an &lt;strong&gt;anonymous AI agent that shows up, does one unit of work, and pays for it — no account, no API key, no human in the loop.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So I built it into a helpdesk. Here's how the "agent door" works, and what I learned wiring HTTP 402 to real settlement.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with giving agents API keys
&lt;/h2&gt;

&lt;p&gt;The default way to let an agent use your API is: a human signs up, adds a card, generates a key, and manages it. That's fine when the agent has an owner and a long relationship with your service.&lt;/p&gt;

&lt;p&gt;It's terrible when the agent is &lt;em&gt;ephemeral&lt;/em&gt; — it needs one action, right now, and will never come back. Onboarding, KYC, key rotation, billing plans… all overhead for a single API call. There's no clean "walk up and pay for exactly what you use" primitive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The primitive: HTTP 402 + x402
&lt;/h2&gt;

&lt;p&gt;HTTP has always had a status code reserved for this: &lt;strong&gt;&lt;code&gt;402 Payment Required&lt;/code&gt;&lt;/strong&gt;. It was never really used — until &lt;a href="https://x402.org" rel="noopener noreferrer"&gt;x402&lt;/a&gt; gave it a concrete meaning: the server responds &lt;code&gt;402&lt;/code&gt; with machine-readable payment terms, the client pays (USDC on-chain), and retries the same request with an &lt;code&gt;X-PAYMENT&lt;/code&gt; header. Settlement happens per request.&lt;/p&gt;

&lt;p&gt;No account. No API key. The payer's wallet &lt;em&gt;is&lt;/em&gt; the identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The door
&lt;/h2&gt;

&lt;p&gt;My helpdesk (&lt;a href="https://deskcrew.io" rel="noopener noreferrer"&gt;DeskCrew&lt;/a&gt;) exposes an &lt;strong&gt;MCP server&lt;/strong&gt; per workspace, so an AI agent can operate the desk — search the knowledge base, open and triage tickets, draft and post replies. Read tools are free; write/AI tools are priced.&lt;/p&gt;

&lt;p&gt;You can hit it right now, no signup:&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/api/mcp/deskcrew &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'content-type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":1,"method":"tools/list"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns the tool catalog with per-tool prices. Call a &lt;em&gt;paid&lt;/em&gt; tool and you get the challenge:&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;"x402Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Payment required for tool 'create_ticket'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accepts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"scheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"base"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maxAmountRequired"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"20000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"asset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"payTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"maxTimeoutSeconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&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="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;…one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;entry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;per&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;chain:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;polygon&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;avalanche&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sei&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;solana&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;&lt;code&gt;20000&lt;/code&gt; is &lt;code&gt;$0.02&lt;/code&gt; in USDC's 6 decimals. The agent signs a payment authorization for that amount, retries with &lt;code&gt;X-PAYMENT&lt;/code&gt;, and the tool runs once settlement confirms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually had to get right
&lt;/h2&gt;

&lt;p&gt;The happy path is easy. The interesting part is everything that isn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The amount, recipient, and asset come from the server — never the client.&lt;/strong&gt; The client sends only a &lt;em&gt;signed&lt;/em&gt; authorization. The server builds the payment terms (price from a server-side table, &lt;code&gt;payTo&lt;/code&gt; = a cold wallet, the USDC contract pinned per chain) and verifies the signature satisfies &lt;em&gt;those&lt;/em&gt; terms. Otherwise an agent could "pay" 1 wei, redirect payment to itself, or hand you a fake token.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Don't run a side-effecting action before you're paid — but don't double-charge either.&lt;/strong&gt; &lt;code&gt;verify()&lt;/code&gt; only &lt;em&gt;reads&lt;/em&gt; the payer's balance; it's not a reservation. So N concurrent requests from one wallet funded for a single call can all pass verify. I gate it two ways: an &lt;strong&gt;at-most-one-in-flight lease per wallet&lt;/strong&gt;, and a claimed &lt;strong&gt;nonce&lt;/strong&gt; (EIP-3009) inserted &lt;em&gt;before&lt;/em&gt; the action runs, so a replay or a concurrent duplicate can't execute the tool twice for one payment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. For live external effects (like sending a customer an email), settle &lt;em&gt;first&lt;/em&gt;.&lt;/strong&gt; The fairness default is "only charge on success" — so most tools run, then settle. But for the one action where "ran but not charged" is real abuse (a free outbound email from your domain), I flipped the order: confirm payment on-chain, &lt;em&gt;then&lt;/em&gt; send. The safe failure direction is "paid but not delivered," never "delivered for free."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Anonymous ≠ unbounded.&lt;/strong&gt; Per-wallet rate/spend caps, reputation, and a deliverability breaker bound abuse. High-value actions are gated behind &lt;em&gt;earned&lt;/em&gt; reputation, so a day-one anonymous wallet can't email your customers — it gets a draft in a human approval queue instead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Self-host the money path.&lt;/strong&gt; I run my own relayer + facilitator, so there's no third party between the agent and settlement. USDC lands in a cold wallet at my prices; even a leaked hot key can only move the payer's funds &lt;em&gt;to my wallet, at my price&lt;/em&gt; (the settle is economically bound).&lt;/p&gt;

&lt;h2&gt;
  
  
  Was it worth it?
&lt;/h2&gt;

&lt;p&gt;For a helpdesk specifically: an agent resolving a support task mid-workflow can now pay for exactly the tools it uses — no account, no key. Whether &lt;em&gt;pay-per-action&lt;/em&gt; beats &lt;em&gt;just issue the agent a key&lt;/em&gt; is genuinely situational, and I'd love the internet's opinion on where the line is.&lt;/p&gt;

&lt;p&gt;But the pattern is bigger than helpdesks. &lt;code&gt;402&lt;/code&gt; + a signed micropayment is the missing "walk up and pay" primitive for the agent web. It's early and rough, but it works today — you just curled it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try it:&lt;/strong&gt; &lt;a href="https://deskcrew.io" rel="noopener noreferrer"&gt;deskcrew.io&lt;/a&gt; · the door: &lt;code&gt;POST https://deskcrew.io/api/mcp/deskcrew&lt;/code&gt; · MCP spec + tools: &lt;a href="https://github.com/webmilmind1/deskcrew-mcp" rel="noopener noreferrer"&gt;github.com/webmilmind1/deskcrew-mcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer anything about the x402 wiring in the comments.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>webdev</category>
      <category>crypto</category>
    </item>
  </channel>
</rss>
