<?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: jerrywrongalot-byte</title>
    <description>The latest articles on DEV Community by jerrywrongalot-byte (@jerrywrongalotbyte).</description>
    <link>https://dev.to/jerrywrongalotbyte</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%2F4023214%2F773b578d-6810-464e-801d-f71d6387f310.png</url>
      <title>DEV Community: jerrywrongalot-byte</title>
      <link>https://dev.to/jerrywrongalotbyte</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jerrywrongalotbyte"/>
    <language>en</language>
    <item>
      <title>I replaced a 4.8 MB payment library with 200 lines - building a paid API for AI agents with x402</title>
      <dc:creator>jerrywrongalot-byte</dc:creator>
      <pubDate>Thu, 09 Jul 2026 20:35:03 +0000</pubDate>
      <link>https://dev.to/jerrywrongalotbyte/i-replaced-a-48-mb-payment-library-with-200-lines-building-a-paid-api-for-ai-agents-with-x402-c9k</link>
      <guid>https://dev.to/jerrywrongalotbyte/i-replaced-a-48-mb-payment-library-with-200-lines-building-a-paid-api-for-ai-agents-with-x402-c9k</guid>
      <description>&lt;p&gt;DeadDrop is a zero-knowledge one-time-secret API: POST ciphertext, get a&lt;br&gt;
self-destructing link, pay $0.01 in USDC per call. No accounts, no API keys,&lt;br&gt;
no billing portal - because the customers are increasingly not humans.&lt;/p&gt;

&lt;p&gt;This post walks through the three design problems that made it interesting:&lt;br&gt;
honest zero-knowledge in a web app, an atomic "exactly once" read on&lt;br&gt;
Cloudflare, and a payment wall for machines - including why I deleted the&lt;br&gt;
official payment middleware and spoke the protocol directly in ~200 lines.&lt;/p&gt;

&lt;p&gt;Try it first if you like (the landing has a free in-browser demo of the&lt;br&gt;
crypto flow): &lt;a href="https://deaddrop.jerrywrongalot.workers.dev" rel="noopener noreferrer"&gt;https://deaddrop.jerrywrongalot.workers.dev&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Pasting an API key, a password, or a seed phrase into chat or email leaves&lt;br&gt;
it in logs, backups, and search indexes forever. The old answer is a&lt;br&gt;
one-time link: the secret is destroyed on first read.&lt;/p&gt;

&lt;p&gt;The newer, second problem: how do you charge for infrastructure like this&lt;br&gt;
when the caller might be an AI agent? Agents cannot fill out signup forms,&lt;br&gt;
verify email addresses, or type credit card numbers. Subscriptions and API&lt;br&gt;
keys assume a human with a browser and a wallet-on-file. I wanted the&lt;br&gt;
machine equivalent of a coin slot.&lt;/p&gt;

&lt;p&gt;That is what x402 is: HTTP status 402 (Payment Required), revived as a real&lt;br&gt;
protocol. The server answers an unpaid request with a challenge; the client&lt;br&gt;
signs a payment authorization and retries; the server verifies and settles&lt;br&gt;
through a "facilitator" service. No account anywhere. More on it below.&lt;/p&gt;
&lt;h2&gt;
  
  
  Zero-knowledge that survives scrutiny
&lt;/h2&gt;

&lt;p&gt;"We encrypt your data" is easy to say. The design has to survive the&lt;br&gt;
obvious follow-up questions.&lt;/p&gt;

&lt;p&gt;The core move: encryption happens in the browser (AES-256-GCM via Web&lt;br&gt;
Crypto), and the key travels in the URL fragment - the part after &lt;code&gt;#&lt;/code&gt;.&lt;br&gt;
Fragments never leave the client; browsers do not send them in requests.&lt;br&gt;
The server stores ciphertext only. We could not read the secrets even if&lt;br&gt;
compelled, because nothing decryptable ever reaches us.&lt;/p&gt;

&lt;p&gt;The parts people usually miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;XSS is the real enemy of fragment keys.&lt;/strong&gt; If injected script runs on
the reveal page, it can read &lt;code&gt;location.hash&lt;/code&gt;. So the reveal page ships a
hash-pinned Content-Security-Policy: the exact inline scripts are
SHA-256-pinned, no &lt;code&gt;unsafe-inline&lt;/code&gt;, no external script sources. An
attacker cannot add a script that exfiltrates the fragment without
breaking the CSP.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Link previewers must not burn the secret.&lt;/strong&gt; Slack, Telegram, and iMessage
all prefetch links. A GET on a secret URL therefore never consumes it -
it returns metadata or a viewer page. Revealing is an explicit POST, and
the response carries &lt;code&gt;Cache-Control: no-store&lt;/code&gt; plus &lt;code&gt;X-Robots-Tag:
noindex&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logs are a side channel.&lt;/strong&gt; The event log is a strict allowlisted union:
event names, sizes, TTLs, outcomes. No secret ids ever (the id IS the
read capability), and no IP addresses at all - not even hashed ones,
because the IPv4 space brute-forces in seconds, so a hashed IP is just an
IP with extra steps.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Exactly-once on Cloudflare: Durable Objects as an atomicity primitive
&lt;/h2&gt;

&lt;p&gt;"Destroyed after one read" is a concurrency claim, and eventually&lt;br&gt;
consistent storage cannot make it. With Workers KV, two clients revealing&lt;br&gt;
simultaneously could both win the race and both receive the secret.&lt;/p&gt;

&lt;p&gt;Durable Objects solve this structurally: one DO instance per secret, and&lt;br&gt;
workerd's input gates serialize access to it. The consume path is&lt;br&gt;
read-then-delete inside a single object - there is no interleaving in&lt;br&gt;
which two readers both see the ciphertext:&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;async&lt;/span&gt; &lt;span class="nf"&gt;consume&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="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ciphertext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ct&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;undefined&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="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteAll&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// one reader, then gone&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ct&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;TTL expiry is a DO alarm that burns whatever is left. Nothing to garbage&lt;br&gt;
collect, nothing to back up - by design there is nothing to recover.&lt;/p&gt;
&lt;h2&gt;
  
  
  Charging machines: the x402 flow
&lt;/h2&gt;

&lt;p&gt;An unpaid &lt;code&gt;POST /secret&lt;/code&gt; gets a 402 with a machine-readable 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;"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;"X-PAYMENT header is required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"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="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;"10000"&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;"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;"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="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;The client (a library like &lt;code&gt;x402-fetch&lt;/code&gt;, or an agent) signs an EIP-3009&lt;br&gt;
&lt;code&gt;transferWithAuthorization&lt;/code&gt; for exactly that amount - a gasless signature;&lt;br&gt;
the facilitator submits it on-chain and pays the gas - and retries with the&lt;br&gt;
signature in an &lt;code&gt;X-PAYMENT&lt;/code&gt; header. The server then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;verifies before doing any work&lt;/strong&gt; (facilitator &lt;code&gt;/verify&lt;/code&gt;),&lt;/li&gt;
&lt;li&gt;runs the handler,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;settles only after success&lt;/strong&gt; (facilitator &lt;code&gt;/settle&lt;/code&gt;), and&lt;/li&gt;
&lt;li&gt;attaches a receipt header with the on-chain transaction.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two properties fall out of the ordering: no failure mode charges a caller&lt;br&gt;
without delivering, and unpaid probes cost you almost nothing. Reading a&lt;br&gt;
secret is free - the recipient of a link needs no wallet, which matters if&lt;br&gt;
you want normal humans on the receiving end.&lt;/p&gt;

&lt;p&gt;The payer's identity is a wallet signature. That is the whole "account&lt;br&gt;
system".&lt;/p&gt;
&lt;h2&gt;
  
  
  The supply-chain story: 4.8 MB -&amp;gt; 90 KB, 32 audit findings -&amp;gt; 0
&lt;/h2&gt;

&lt;p&gt;The official Hono middleware for x402 worked in an afternoon. Then I looked&lt;br&gt;
at the deployed bundle: 4.8 MB for a service whose own code is a few&lt;br&gt;
hundred lines. &lt;code&gt;npm audit&lt;/code&gt;: 32 findings, 23 high, every one of them&lt;br&gt;
transitive.&lt;/p&gt;

&lt;p&gt;The cause: the payment package compiles client AND server features into&lt;br&gt;
shared chunks with module-level imports, so importing the server middleware&lt;br&gt;
drags in the entire client-side wallet stack - MetaMask connectors,&lt;br&gt;
WalletConnect, viem with WebSocket transports - code a server never&lt;br&gt;
executes. Tree-shaking cannot remove module-level imports, and there was no&lt;br&gt;
fixed upstream release. Dormant code, but it is all attack surface, it&lt;br&gt;
keeps every scanner red, and it 25x-ed the artifact.&lt;/p&gt;

&lt;p&gt;So I deleted both packages and spoke the protocol directly. The server side&lt;br&gt;
of x402 is genuinely small: build the challenge JSON, decode a base64&lt;br&gt;
header, and make two REST calls -&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST {facilitator}/verify  {x402Version, paymentPayload, paymentRequirements}
POST {facilitator}/settle  {x402Version, paymentPayload, paymentRequirements}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;with verify-before-work and settle-after-success ordering. About 200
lines including error taxonomy and telemetry hooks. To make sure I was not
quietly diverging from the ecosystem, I pinned it against a byte-exact 402
challenge captured from the official middleware in production before the
swap, plus the upstream client source. The live challenge after the swap
was identical.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Results: dependencies -&amp;gt; just hono. Bundle 4,821 KB -&amp;gt; 90 KB. Worker&lt;br&gt;
startup 18 ms -&amp;gt; 5 ms. Audit findings -&amp;gt; zero. All cryptographic&lt;br&gt;
verification and on-chain settlement stays delegated to the facilitator -&lt;br&gt;
the 200 lines contain no key handling and no hand-rolled crypto.&lt;/p&gt;

&lt;p&gt;The general lesson: payment SDKs are often client+server monoliths. The&lt;br&gt;
server half of a payment protocol is usually a thin REST client wearing a&lt;br&gt;
big trench coat.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part most launch posts skip: a security gate
&lt;/h2&gt;

&lt;p&gt;Before pointing this at mainnet, I ran a principle-driven security review&lt;br&gt;
against a 65-principle checklist and treated it as a real gate with a&lt;br&gt;
human signature - not vibes. First verdict: BLOCKED, two highs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;No end-to-end paid proof.&lt;/strong&gt; Closed with a real testnet payment from a
standard client, settled on-chain, receipt verified, one-time semantics
held under the paid path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mainnet facilitator auth unwired.&lt;/strong&gt; The production facilitator
authenticates with per-request Ed25519 JWTs. Web Crypto in workerd
signs Ed25519 natively, so this needed zero new dependencies. The nice
trick: you can prove the whole auth + protocol path WITHOUT owning any
USDC - sign a real payment from an empty wallet and check that the
facilitator returns a structured "insufficient balance"-class verdict.
An auth failure returns a completely different shape. Nothing is
charged; the path is proven on both testnet and mainnet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then the human signs, then you flip. If you are shipping anything that&lt;br&gt;
moves money, some version of this gate is the cheapest insurance you will&lt;br&gt;
ever buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  What $0.01 actually buys
&lt;/h2&gt;

&lt;p&gt;People ask why it is not free. A cent per secret buys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No accounts or billing infra&lt;/strong&gt; - no signup wall for humans, no OAuth
dance for agents, no invoices, no card-on-file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Aligned incentives&lt;/strong&gt; - free services die or monetize your data. A
priced service does neither, and this one cannot even read what it
stores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spam economics&lt;/strong&gt; - bots do not pay coin slots. The paid gate plus rate
limiting bounds abuse without CAPTCHAs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No payment gatekeeper on the receiving end&lt;/strong&gt; - receiving USDC needs an
address, not a merchant account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DeadDrop is live: &lt;a href="https://deaddrop.jerrywrongalot.workers.dev" rel="noopener noreferrer"&gt;https://deaddrop.jerrywrongalot.workers.dev&lt;/a&gt; - the landing&lt;br&gt;
demo runs the full encrypt/decrypt flow in your browser for free, and the&lt;br&gt;
API costs a cent per secret if you want to use it for real (your agent can&lt;br&gt;
pay for itself over x402). There is an agent-readable spec at /llms.txt and&lt;br&gt;
an open-source MCP server (&lt;a href="https://github.com/jerrywrongalot-byte/deaddrop-mcp" rel="noopener noreferrer"&gt;https://github.com/jerrywrongalot-byte/deaddrop-mcp&lt;/a&gt;)&lt;br&gt;
if you want Claude to drop and reveal secrets natively.&lt;/p&gt;

&lt;p&gt;I would love feedback on the crypto design, the x402 flow, and whether&lt;br&gt;
per-call micropayments feel right for infrastructure like this.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>secrets</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
