<?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: Zeke</title>
    <description>The latest articles on DEV Community by Zeke (@zekebuilds).</description>
    <link>https://dev.to/zekebuilds</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3866714%2F688cd691-92a0-4825-af29-ca57b7b020bb.png</url>
      <title>DEV Community: Zeke</title>
      <link>https://dev.to/zekebuilds</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zekebuilds"/>
    <language>en</language>
    <item>
      <title>I tried four ways to gate my MCP server. Only one didn't need a Stripe account.</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Mon, 01 Jun 2026 20:51:03 +0000</pubDate>
      <link>https://dev.to/zekebuilds/i-tried-four-ways-to-gate-my-mcp-server-only-one-didnt-need-a-stripe-account-5ffe</link>
      <guid>https://dev.to/zekebuilds/i-tried-four-ways-to-gate-my-mcp-server-only-one-didnt-need-a-stripe-account-5ffe</guid>
      <description>&lt;p&gt;Your MCP server is getting hammered by agents you didn't authorize. A free tier is open to the world. A premium tool is one runaway loop away from torching your LLM budget. You need a gate.&lt;/p&gt;

&lt;p&gt;So I went shopping. Four projects ship something that calls itself an MCP gate today. I wired each one against a test server, ran a handful of tool calls through it, and wrote down what hurt.&lt;/p&gt;

&lt;p&gt;Here's the honest comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  PayGated (paygated.dev)
&lt;/h2&gt;

&lt;p&gt;This is the closest thing captcha-mcp has to a named competitor. PayGated wraps MCP tools with a Stripe-backed payment gate. Self-hosted, MIT-licensed, "no monthly SaaS bills." It's a clean piece of work.&lt;/p&gt;

&lt;p&gt;The catch is Stripe itself. To accept payment, you need a Stripe account in good standing, which means KYC, a bank, and a country Stripe operates in. Your callers need a Stripe customer record too, so every agent identity needs an email and a card on file before it can pay you a dime.&lt;/p&gt;

&lt;p&gt;PayGated does OAuth 2.1 + PKCE for the auth half and machine-to-machine flow for headless agents. That works if your callers can hold an API key. It's also the only one in this list with a real revenue model out of the box if you're already inside the Stripe ecosystem.&lt;/p&gt;

&lt;p&gt;Good fit: US-based MCP devs whose callers are corporate agents with billing relationships.&lt;br&gt;
Bad fit: anonymous agents, weekend-project monetization, anyone outside Stripe's footprint.&lt;/p&gt;
&lt;h2&gt;
  
  
  APort (aport.io)
&lt;/h2&gt;

&lt;p&gt;APort isn't really a gate. It sits in front of the gate. The pitch is "verifiable credentials for AI agents" using W3C VC standards. An agent presents a passport, APort checks the signature against a registry, your MCP server reads the verification result from a pre-tool hook and decides whether to run the tool.&lt;/p&gt;

&lt;p&gt;That's a different layer of the stack. APort answers "who is this agent." It doesn't answer "did they pay" or "should I rate-limit them." You'd compose APort with something else that does the metering.&lt;/p&gt;

&lt;p&gt;This is less a competitor and more a partnership shape. If you're already paying per call with captcha-mcp, APort's audit log could record who paid for what under whose authority. Worth a look if your buyers care about provenance more than monetization.&lt;/p&gt;

&lt;p&gt;Good fit: enterprise contexts where auditors will ask which agent did what.&lt;br&gt;
Bad fit: you just want to stop abuse and don't have a passport infrastructure problem yet.&lt;/p&gt;
&lt;h2&gt;
  
  
  AgentSign (agentsign.dev)
&lt;/h2&gt;

&lt;p&gt;AgentSign is the Ed25519-passport flavor of the same identity layer. Every agent gets a signed identity document. The server verifies the signature, looks up a trust score, gates the tool on that score. Clean cryptographically, but again, it's identity not metering.&lt;/p&gt;

&lt;p&gt;There's no payment rail and no abuse-prevention mechanism if a signed agent decides to hammer your endpoint legitimately. The trust score is the only knob. That's a different product than "charge me 10 sats and let me through."&lt;/p&gt;

&lt;p&gt;I'd use it if I were building a multi-agent system and needed a who-signed-this layer. I would not use it to stop a runaway loop.&lt;/p&gt;

&lt;p&gt;Good fit: multi-agent systems with reputation tracking.&lt;br&gt;
Bad fit: rate-limiting, micropayments, anything where you want the abuser to pay the cost of abuse.&lt;/p&gt;
&lt;h2&gt;
  
  
  captcha-mcp (the one I'm shipping)
&lt;/h2&gt;

&lt;p&gt;This is the project I work on, so call this biased. The pitch: gate any MCP tool with proof-of-work for free callers and a 10-sat Lightning invoice for callers who want to skip the CPU work. No Stripe account on either side. No KYC. No user database.&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PayMCP&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paymcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CaptchaPowProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@powforge/captcha-paymcp-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LnbitsPaymentProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@powforge/paymcp-l402-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;PayMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CaptchaPowProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;captchaUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://captcha.powforge.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LnbitsPaymentProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;lnbitsUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LNBITS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;lnbitsApiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LNBITS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;satsAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tag a tool with &lt;code&gt;{ _meta: { price: 1 } }&lt;/code&gt; and it's gated. PoW path grinds a SHA-256 challenge in a few seconds of CPU. Lightning path mints a BOLT11 invoice and waits for the preimage. The calling agent picks whichever it can satisfy.&lt;/p&gt;

&lt;p&gt;The reason I built it this way: Stripe's KYC wall is fine for US SaaS but it kills the long tail. A solo dev in any country can spin up an LNBits wallet in a minute and start collecting sats. An agent author can fund a Lightning wallet with $1 and make 10,000 calls. Nobody's filling out a W-9.&lt;/p&gt;

&lt;p&gt;Good fit: anonymous agents, public APIs, micropayment-per-call, anyone outside Stripe's footprint.&lt;br&gt;
Bad fit: enterprise customers who want a credit card receipt, high-throughput callers who can't afford the PoW seconds.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision
&lt;/h2&gt;

&lt;p&gt;If your callers are corporate agents with billing relationships, PayGated. If they're anonymous and you want to monetize the long tail without paperwork, captcha-mcp. If your problem is "who is this agent" and not "did they pay," APort or AgentSign sit at a different layer and you'll probably end up running one of them next to a payment gate, not instead of one.&lt;/p&gt;

&lt;p&gt;I picked PoW plus Lightning because it's the only path I've seen that works for a developer in Argentina, a research bot in a CI pipeline, and a side-project MCP server that doesn't want a Stripe account. Your tradeoffs may land you somewhere else. Just know which gate you're picking and why.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@powforge/captcha-mcp&lt;/code&gt; on npm: &lt;a href="https://www.npmjs.com/package/@powforge/captcha-mcp" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@powforge/captcha-mcp&lt;/a&gt;&lt;br&gt;
Hosted captcha server: &lt;a href="https://captcha.powforge.dev" rel="noopener noreferrer"&gt;https://captcha.powforge.dev&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>lightning</category>
    </item>
    <item>
      <title>Why block hashes are dangerous for DLC randomness (and the fix)</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Mon, 01 Jun 2026 13:52:46 +0000</pubDate>
      <link>https://dev.to/zekebuilds/why-block-hashes-are-dangerous-for-dlc-randomness-and-the-fix-4dn</link>
      <guid>https://dev.to/zekebuilds/why-block-hashes-are-dangerous-for-dlc-randomness-and-the-fix-4dn</guid>
      <description>&lt;p&gt;A DLC (Discreet Log Contract) is only as fair as its randomness source. If you're using a Bitcoin block hash as your oracle input for anything with money on it, you've got a miner front-running problem that won't care how tight the rest of your contract is.&lt;/p&gt;

&lt;p&gt;This ain't theoretical. PancakeSwap lost $1.8M in 2021 to an attack that precomputed the block hash used as a random seed (SWC-120). DeFi, but the same class of attack applies to any protocol where the person producing the "randomness" can see the downstream payoff first.&lt;/p&gt;

&lt;p&gt;Block hashes are predictable to miners.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why block hashes fail for DLCs specifically
&lt;/h2&gt;

&lt;p&gt;Here's the attack in plain terms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A DLC has two outcomes: Alice wins if the coin lands heads, Bob wins tails. The oracle will sign "heads" or "tails" based on the block hash at time T.&lt;/li&gt;
&lt;li&gt;A miner with enough hashrate can, before publishing a block, check: does this hash make me money (if I'm a counterparty) or does it favor the other side? If it favors the other side, they can try again: mine another nonce, or selectively withhold.&lt;/li&gt;
&lt;li&gt;Even without being a counterparty, miners can be bribed to produce favorable hashes, or simply front-run if the DLC outcome is observable on-chain before the block is confirmed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Block hashes work fine for non-adversarial randomness. They're terrible for anything adversarial where the miner has a stake.&lt;/p&gt;

&lt;p&gt;The professional gambling industry learned this. Stake.com publishes "public seeding events" that mix a future block hash with additional entropy they commit to in advance. They separate who knows what from who controls what. That's the pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a verifiable beacon provides instead
&lt;/h2&gt;

&lt;p&gt;A proper randomness beacon closes the attack surface by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Collecting entropy from multiple independent contributors before the result is computed&lt;/li&gt;
&lt;li&gt;Computing the result only after contributions are frozen (end of epoch)&lt;/li&gt;
&lt;li&gt;Signing the result with a Schnorr key so you can verify it offline&lt;/li&gt;
&lt;li&gt;Providing a payment receipt (L402 macaroon + preimage) that ties your specific fetch to a specific epoch&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last point matters for DLCs. A DLC contract can reference the oracle's pubkey and the epoch ID in the announcement. When the epoch closes, the signed beacon is the attestation. Any counterparty can verify it independently with the oracle pubkey.&lt;/p&gt;

&lt;h2&gt;
  
  
  The PowForge draw beacon
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;/draw&lt;/code&gt; endpoint at &lt;code&gt;attest.powforge.dev&lt;/code&gt; does exactly this for Bitcoin. Every 5-minute epoch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contributors submit SHA-256 proof-of-work entropy (free)&lt;/li&gt;
&lt;li&gt;Oracle aggregates deterministically: &lt;code&gt;sha256("DRAW" || epoch_id || sha256(epoch_id || sorted_contribution_hashes))&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Oracle signs with BIP-340 Schnorr and seals the epoch&lt;/li&gt;
&lt;li&gt;Result is fixed forever and publicly verifiable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Schnorr format is the same family used by DLC oracle specs (dlcspecs/Oracle.md). You're not adding a new trust assumption. You're delegating randomness to a PoW-seeded ceremony that's independent of the miner who eventually mines your settlement block.&lt;/p&gt;

&lt;p&gt;Fetching a sealed beacon costs 50 sats via L402:&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="c"&gt;# Trigger the 402 to get the invoice&lt;/span&gt;
curl &lt;span class="nt"&gt;-si&lt;/span&gt; https://attest.powforge.dev/api/v1/draw/EPOCH_ID

&lt;span class="c"&gt;# Pay the 50-sat Lightning invoice, get a preimage&lt;/span&gt;
&lt;span class="c"&gt;# Then fetch with the credential&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://attest.powforge.dev/api/v1/draw/EPOCH_ID &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: L402 &amp;lt;macaroon&amp;gt;:&amp;lt;preimage&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"epoch_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5934397&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"contribution_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"oracle_pubkey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2bc78390c94d8bbb96ac3e6940462ba2812418d871e701c1a845fdb1dfd4a0e5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attestation"&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;"beacon_random"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"62eb805f...32 bytes hex..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"f2925b93...64 bytes hex..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"oracle_pubkey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2bc78390c94d8bbb96ac3e6940462ba2812418d871e701c1a845fdb1dfd4a0e5"&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;Verify offline (oracle_pubkey is x-only, 32 bytes, no prefix):&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;schnorr&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@noble/curves/secp256k1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// The oracle signs taggedHash("DLC/oracle/attestation/v0", beacon_bytes)&lt;/span&gt;
&lt;span class="c1"&gt;// not the raw beacon bytes directly. BIP-340 §3.2 tagged hash construction.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;taggedHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tagHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&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;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tagHash&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tagHash&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;attestation&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;result&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;beaconBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beacon_random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msgHash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;taggedHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DLC/oracle/attestation/v0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beaconBytes&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;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oracle_pubkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// already x-only&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schnorr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msgHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pubkey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using the beacon as a DLC oracle input
&lt;/h2&gt;

&lt;p&gt;In a dlcspecs-compliant DLC:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Pick the epoch ID for your event close time (5-minute windows, &lt;code&gt;Math.floor(Date.now() / 300000)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Include the oracle pubkey and epoch ID in your DLC announcement as the "event descriptor"&lt;/li&gt;
&lt;li&gt;At settlement, fetch the sealed beacon and use &lt;code&gt;attestation.beacon_random&lt;/code&gt; as the outcome scalar input&lt;/li&gt;
&lt;li&gt;Verify the Schnorr signature before settling (&lt;code&gt;attestation.signature&lt;/code&gt; against &lt;code&gt;attestation.oracle_pubkey&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The oracle pubkey is stable and published at &lt;code&gt;attest.powforge.dev&lt;/code&gt;. The beacon is seeded by contributors independent of both counterparties and miners. Nobody controls the output.&lt;/p&gt;




&lt;p&gt;If you're building DLCs and randomness is part of your oracle design, the block hash pattern has a known attack class. There's a Bitcoin-native alternative that costs 50 sats per event and verifies with any secp256k1 library.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endpoint: &lt;code&gt;https://attest.powforge.dev/api/v1/draw/{epoch_id}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Landing: &lt;code&gt;https://powforge.dev/draw/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>bitcoin</category>
      <category>dlc</category>
      <category>cryptography</category>
      <category>lightning</category>
    </item>
    <item>
      <title>Return a 402 instead of a 429 from your MCP server</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sun, 31 May 2026 20:01:16 +0000</pubDate>
      <link>https://dev.to/zekebuilds/return-a-402-instead-of-a-429-from-your-mcp-server-d7g</link>
      <guid>https://dev.to/zekebuilds/return-a-402-instead-of-a-429-from-your-mcp-server-d7g</guid>
      <description>&lt;p&gt;Last week I was reading through sentry-mcp issue #844 and watched a guy describe exactly the pain I keep running into. He had Cursor running parallel automation against the Sentry MCP, saturated the 60-request-per-minute bucket in seconds, and got back a 429 with no &lt;code&gt;Retry-After&lt;/code&gt; header. His agent just sat there. No backoff hint, no escape path, nothing to do but fail the run and ask a human to babysit it.&lt;/p&gt;

&lt;p&gt;That same week awslabs/mcp #2949 popped up where the MCP handshake itself was failing because &lt;code&gt;tools/list&lt;/code&gt; was tripping a 429 on the second call. And GLips/Figma-Context-MCP #258 had folks convinced the MCP was broken when really their parallel calls were just blowing through Figma's per-token limit on shared credentials.&lt;/p&gt;

&lt;p&gt;Same shape every time. The server says "no" in a way the agent cannot use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 429 is the wrong answer for agents
&lt;/h2&gt;

&lt;p&gt;The 429 status was designed for humans behind browsers. &lt;code&gt;Retry-After: 60&lt;/code&gt; works fine if a person can read a banner that says "try again in a minute." It does not work when you have an autonomous agent that needs to decide right now whether to wait, retry, escalate, or pay.&lt;/p&gt;

&lt;p&gt;Most MCP servers do not even send &lt;code&gt;Retry-After&lt;/code&gt;. The agent gets a 429 body, maybe some JSON, and zero machine-readable information about what would let it succeed. So it does the dumb thing. It retries immediately. Or worse, it gives up and the whole tool chain breaks.&lt;/p&gt;

&lt;p&gt;There is no payment path. There is no proof-of-work path. There is no "I will do something to earn the right to call you" path. Just a closed door.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 402 looks like on the wire
&lt;/h2&gt;

&lt;p&gt;HTTP 402 Payment Required has been sitting in the spec since 1997 waiting for someone to use it. With agents, it finally has a real job.&lt;/p&gt;

&lt;p&gt;A useful 402 response gives the caller a challenge it can solve programmatically. Two flavors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;402&lt;/span&gt; &lt;span class="ne"&gt;Payment Required&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
&lt;span class="na"&gt;WWW-Authenticate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PowChallenge realm="api", id="abc123", salt="...", difficulty=14&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"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;"pow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"salt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9f3c..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"difficulty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"signature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;Or for paid access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt; &lt;span class="m"&gt;402&lt;/span&gt; &lt;span class="ne"&gt;Payment Required&lt;/span&gt;
&lt;span class="na"&gt;WWW-Authenticate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;L402 macaroon="...", invoice="lnbc30n1p..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are deterministic. The agent reads the challenge, does the work (CPU cycles or a Lightning payment), submits the answer, and gets a token. No human in the loop. No guessing at backoff intervals. The server told the agent exactly what to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping an existing MCP server
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;@powforge/captcha-mcp&lt;/code&gt; is one way to do this without writing the crypto yourself. It exposes three tools: &lt;code&gt;challenge&lt;/code&gt;, &lt;code&gt;verify&lt;/code&gt;, and &lt;code&gt;status&lt;/code&gt;. The package wraps &lt;code&gt;captcha.powforge.dev&lt;/code&gt; as the backend so you do not have to host the puzzle service.&lt;/p&gt;

&lt;p&gt;Drop it into your Claude Code or Cursor config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"powforge-captcha"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@powforge/captcha-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then on your own backend, when a caller hits a rate-limited endpoint, return a 402 pointing at the verify path. After the agent solves the puzzle and gets a token, your backend checks it:&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;-X&lt;/span&gt; POST https://captcha.powforge.dev/api/token/verify &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&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;'{"token":"&amp;lt;token-from-verify-tool&amp;gt;"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Token good, request goes through. Token bad or expired, you 402 them again with a fresh challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the agent sees
&lt;/h2&gt;

&lt;p&gt;From the agent's point of view the loop is short:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call the tool. Get back a 402 with a challenge.&lt;/li&gt;
&lt;li&gt;Call the &lt;code&gt;challenge&lt;/code&gt; tool to get a fresh puzzle (or use the one from the 402 directly).&lt;/li&gt;
&lt;li&gt;Burn 5 to 10 seconds of CPU finding a nonce that produces a SHA-256 hash with 14 leading zero bits.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;verify&lt;/code&gt; with the nonce. Get back a 5-minute HMAC-signed access token.&lt;/li&gt;
&lt;li&gt;Retry the original call with the token. Get the real response.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent never had to ask a human. It never had to guess at a retry interval. It paid for access in CPU cycles and got through.&lt;/p&gt;

&lt;h2&gt;
  
  
  PoW or Lightning
&lt;/h2&gt;

&lt;p&gt;Pick based on who is calling. PoW (free tier, SHA-256, around 5 to 10 seconds of CPU at 14 leading zero bits) works great for sporadic agents, exploration runs, and free-tier users. The cost is real but small, and it scales with how much the caller wants the resource.&lt;/p&gt;

&lt;p&gt;L402 over Lightning (paid tier, 3 sats per call by default) makes more sense for high-volume callers who would rather pay cash than burn CPU. Most agent operators will happily drop a few sats to skip the puzzle.&lt;/p&gt;

&lt;p&gt;You can offer both from the same endpoint. The 402 response tells the agent what is available, and the agent picks based on its own constraints.&lt;/p&gt;

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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @powforge/captcha-mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Package and docs: &lt;a href="https://www.npmjs.com/package/@powforge/captcha-mcp" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@powforge/captcha-mcp&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you maintain an MCP server that is currently returning 429s, swap the response for a 402 with a real challenge. Your agent callers will thank you by actually completing their runs instead of hanging on a wait header they cannot read.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Three ways to gate an MCP server: OAuth, L402, and proof-of-work</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sun, 31 May 2026 02:39:09 +0000</pubDate>
      <link>https://dev.to/zekebuilds/three-ways-to-gate-an-mcp-server-oauth-l402-and-proof-of-work-3392</link>
      <guid>https://dev.to/zekebuilds/three-ways-to-gate-an-mcp-server-oauth-l402-and-proof-of-work-3392</guid>
      <description>&lt;p&gt;Somebody at Sentry filed a bug last month: Cursor Automations started hitting rate-limit errors almost immediately after authenticating. The bucket was sized for humans — 60 requests per 60 seconds — and an agent tore through it in seconds.&lt;/p&gt;

&lt;p&gt;That's the MCP auth problem in miniature. You've got a server exposing tools. Agents call those tools. You want to slow down abuse, charge per call, or just make sure you don't blow up your LLM budget on some runaway loop. How do you do that without wiring up a full OAuth stack that breaks the first time an agent doesn't have a browser to open?&lt;/p&gt;

&lt;p&gt;Three real options exist right now. Here's how they compare.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: OAuth 2.1 (the spec says so)
&lt;/h2&gt;

&lt;p&gt;The MCP spec mandates OAuth 2.1 for authorization. If you're building a production server for enterprise customers — actual humans with accounts — this is the right call. You get scoped access, token revocation, audit trails. SSO works. Compliance teams stop emailing you.&lt;/p&gt;

&lt;p&gt;The problem is agents. OAuth 2.1 has an authorization code flow that requires a redirect URI. An agent running headless doesn't have a browser. DPoP and Workload Identity Federation are on the MCP roadmap but not shipped yet. If you need auth today and your callers are mostly agents, OAuth puts you in a hole.&lt;/p&gt;

&lt;p&gt;Good fit: enterprise SaaS, human-driven clients, compliance-heavy contexts.&lt;br&gt;
Bad fit: anonymous agents, public APIs, pay-per-call services.&lt;/p&gt;
&lt;h2&gt;
  
  
  Option 2: L402 Lightning payments
&lt;/h2&gt;

&lt;p&gt;L402 is an HTTP extension where a server responds to an unauthorized request with &lt;code&gt;402 Payment Required&lt;/code&gt; and an invoice in the &lt;code&gt;WWW-Authenticate&lt;/code&gt; header. The client pays it over Lightning and retries with the preimage as a credential.&lt;/p&gt;

&lt;p&gt;Two npm packages ship L402 for MCP right now: &lt;code&gt;lightning-wallet-mcp&lt;/code&gt; and &lt;code&gt;l402-kit-mcp&lt;/code&gt;. The model is clean: each tool call costs a fixed number of sats. No accounts, no sessions, no user. An agent with a Lightning wallet (Alby, Phoenixd, NWC) can handle the whole flow programmatically.&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="c"&gt;# First call — server responds with 402&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-mcp-server/tools/call &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&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;'{"name": "expensive_tool", "arguments": {}}'&lt;/span&gt;
&lt;span class="c"&gt;# HTTP/1.1 402 Payment Required&lt;/span&gt;
&lt;span class="c"&gt;# WWW-Authenticate: L402 invoice="lnbc...", macaroon="..."&lt;/span&gt;

&lt;span class="c"&gt;# Pay the invoice over Lightning, get the preimage&lt;/span&gt;
&lt;span class="c"&gt;# Retry with credentials&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-mcp-server/tools/call &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: L402 &amp;lt;macaroon&amp;gt;:&amp;lt;preimage&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&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;'{"name": "expensive_tool", "arguments": {}}'&lt;/span&gt;
&lt;span class="c"&gt;# HTTP/1.1 200 OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good fit: agents with Lightning wallets, micropayment-per-call APIs, Bitcoin-native monetization.&lt;br&gt;
Bad fit: agents without wallets, free tiers, contexts where payment friction kills adoption.&lt;/p&gt;
&lt;h2&gt;
  
  
  Option 3: Proof-of-work
&lt;/h2&gt;

&lt;p&gt;PoW is the weird one. Instead of paying money, the caller burns CPU to solve a hashcash-style puzzle. The server sets a difficulty. The caller grinds until they find a nonce. No wallet required.&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="c"&gt;# Get a challenge&lt;/span&gt;
curl https://your-mcp-server/api/challenge
&lt;span class="c"&gt;# {"challenge": "abc123", "difficulty": 4, "algorithm": "sha256"}&lt;/span&gt;

&lt;span class="c"&gt;# Solve it&lt;/span&gt;
npx @powforge/captcha solve &lt;span class="nt"&gt;--challenge&lt;/span&gt; abc123 &lt;span class="nt"&gt;--difficulty&lt;/span&gt; 4
&lt;span class="c"&gt;# {"nonce": "7f3a...", "solution": "0000..."}&lt;/span&gt;

&lt;span class="c"&gt;# Submit with solution in header&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://your-mcp-server/tools/call &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-PoW-Solution: challenge=abc123,nonce=7f3a..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&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;'{"name": "tool", "arguments": {}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cost scales with difficulty. A legitimate caller solving once is fine. An abuser trying to hammer 10,000 calls hits a wall because each requires fresh compute. No central authority. No wallet. No redirect.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@powforge/captcha-mcp&lt;/code&gt; ships this as middleware for existing MCP servers. Wrap your server, set a difficulty, and callers solve before tool calls go through. It also supports L402 as an escape valve: pay sats instead of grinding if you'd rather not burn CPU.&lt;/p&gt;

&lt;p&gt;This is also a 429 fix. Instead of returning &lt;code&gt;429 Too Many Requests&lt;/code&gt; with no recovery path, the server hands the agent a puzzle — a machine-readable backoff signal an autonomous agent can satisfy without a human, an account, or a &lt;code&gt;Retry-After&lt;/code&gt; header that means nothing to code running in a loop.&lt;/p&gt;

&lt;p&gt;Good fit: anonymous agents, public APIs, abuse prevention without monetization, PoW-or-pay dual rail.&lt;br&gt;
Bad fit: high-throughput legitimate callers (CPU cost adds latency), real-time tools where every millisecond counts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;OAuth 2.1&lt;/th&gt;
&lt;th&gt;L402 Lightning&lt;/th&gt;
&lt;th&gt;Proof-of-Work&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Requires user account&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires Lightning wallet&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works headless today&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revenue per call&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (sats)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stateless&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An Astrix Security study this year found 88% of MCP servers require credentials of some kind but document almost none of it. The OpenClaw scan found 42,000+ unauthenticated MCP instances publicly exposed in January 2026. Most of those aren't enterprise installs — they're side projects and weekend builds.&lt;/p&gt;

&lt;p&gt;For that crowd, OAuth is overkill. L402 is elegant if the caller has a wallet. PoW gives you friction without a wallet requirement — callers can always get through, they just can't do it for free at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision tree
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Building for enterprise with human users? &lt;strong&gt;OAuth 2.1.&lt;/strong&gt; The spec points here and compliance teams need it.&lt;/li&gt;
&lt;li&gt;Monetizing per tool call, callers have Lightning wallets? &lt;strong&gt;L402.&lt;/strong&gt; Clean, stateless, Bitcoin-native.&lt;/li&gt;
&lt;li&gt;Public API, anonymous agents, no accounts? &lt;strong&gt;PoW&lt;/strong&gt; — or PoW with L402 escape valve.&lt;/li&gt;
&lt;li&gt;Unsure? Start with PoW for the free tier and L402 for a paid tier. Add OAuth when an enterprise customer asks for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Sentry rate-limit bug is fixable with any of these. But if you're building for agents first, OAuth is the last thing you reach for — not the first.&lt;/p&gt;




&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;@powforge/captcha-mcp&lt;/code&gt; (PoW + L402 middleware): &lt;a href="https://www.npmjs.com/package/@powforge/captcha-mcp" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@powforge/captcha-mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@powforge/captcha&lt;/code&gt; (standalone solver): &lt;a href="https://www.npmjs.com/package/@powforge/captcha" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@powforge/captcha&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sentry MCP issue #844: &lt;a href="https://github.com/getsentry/sentry-mcp/issues/844" rel="noopener noreferrer"&gt;https://github.com/getsentry/sentry-mcp/issues/844&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>bitcoin</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A verifiable Bitcoin randomness beacon, 50 sats per draw</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sat, 30 May 2026 23:55:29 +0000</pubDate>
      <link>https://dev.to/zekebuilds/a-verifiable-bitcoin-randomness-beacon-50-sats-per-draw-11mf</link>
      <guid>https://dev.to/zekebuilds/a-verifiable-bitcoin-randomness-beacon-50-sats-per-draw-11mf</guid>
      <description>&lt;p&gt;Randomness is one of those things you don't think about until you need to trust it. And then you think about it a lot.&lt;/p&gt;

&lt;p&gt;Pick a lottery, a game seed, a sampling job, a DLC oracle input. Anything where the output has money or fairness riding on it. The usual options ain't great. A single server tells you "trust me, this was random." A blockchain hash is only random after the block is mined, and the miner can see your bet before they publish. A VRF binds the output to one key holder, which is a single point of trust by another name.&lt;/p&gt;

&lt;p&gt;What you actually want is a draw where nobody can predict the result before it closes, nobody can change it after, and anybody can verify it offline. That's what the PowForge /draw beacon does. Multi-party entropy, Schnorr-signed, 50 sats per fetch.&lt;/p&gt;

&lt;h2&gt;
  
  
  How an epoch works
&lt;/h2&gt;

&lt;p&gt;Time is sliced into 5-minute windows. The current epoch ID is just:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;epoch_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During an epoch, anyone can contribute. You solve a small SHA-256 proof-of-work challenge (18 bits, takes a second on a laptop) and POST your contribution. It costs nothing and your x-only Schnorr pubkey gets bound into the contribution hash, so you can prove your entropy was included.&lt;/p&gt;

&lt;p&gt;When the window closes and at least 5 unique contributors have submitted, the oracle aggregates everything deterministically. The beacon formula is public:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sha256("DRAW" || epoch_id || sha256(epoch_id || sorted_contribution_hashes_concat))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sorted concatenation matters. It means the order contributors arrive doesn't change the output, but a single byte from any one of them shifts the whole 32-byte beacon. The oracle then signs the beacon with a BIP-340 Schnorr key and seals the epoch. After that, the result is fixed forever and nobody, including the oracle, can change it.&lt;/p&gt;

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

&lt;p&gt;Contributing requires mining the PoW, so the curl path for that one's easier with the npm client below. Reading a sealed beacon is straight L402:&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="c"&gt;# Step 1: request the beacon, oracle returns 402 + a Lightning invoice&lt;/span&gt;
curl &lt;span class="nt"&gt;-si&lt;/span&gt; https://attest.powforge.dev/api/v1/draw/5933907

&lt;span class="c"&gt;# HTTP/1.1 402 Payment Required&lt;/span&gt;
&lt;span class="c"&gt;# WWW-Authenticate: L402 invoice="lnbc500...", macaroon="AgE..."&lt;/span&gt;

&lt;span class="c"&gt;# Step 2: pay the invoice with any Lightning wallet (50 sats)&lt;/span&gt;
&lt;span class="c"&gt;# you receive a payment preimage, 32 bytes hex&lt;/span&gt;

&lt;span class="c"&gt;# Step 3: retry with the macaroon and preimage&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://attest.powforge.dev/api/v1/draw/5933907 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: L402 &amp;lt;macaroon&amp;gt;:&amp;lt;preimage&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response gives you &lt;code&gt;beacon_random&lt;/code&gt; (the 32-byte entropy) and &lt;code&gt;signature&lt;/code&gt; (BIP-340 Schnorr). Verify it against the oracle pubkey and you're done.&lt;/p&gt;

&lt;h2&gt;
  
  
  npm client
&lt;/h2&gt;

&lt;p&gt;There's a thin client that handles the PoW mining and L402 dance for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @powforge/attest-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AttestClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@powforge/attest-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AttestClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Contribute entropy to the current epoch (free, mines PoW automatically)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contrib&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contributeDrawEntropy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your64hexschnorrpubkeyhere...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;// your x-only Schnorr pubkey&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;contributed to epoch:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contrib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;epoch_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Fetch the sealed beacon for a closed epoch (pays 50 sats via L402)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;beacon&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getDrawBeacon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contrib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;epoch_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;l402Token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;beacon_random:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beacon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;beacon_random&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signature:    &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;beacon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole API surface. Contribute is free, fetch costs 50 sats and gives you a signed value any Bitcoin-native verifier can check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why you can trust the output
&lt;/h2&gt;

&lt;p&gt;Three properties carry the whole thing:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi-party by construction.&lt;/strong&gt; The oracle can't produce a beacon alone. Each epoch needs at least 5 independent contributions, and any one contributor can shift the result. Nobody can predict it before the epoch closes because nobody knows what the other contributors will submit until the seal happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schnorr verifiable offline.&lt;/strong&gt; The signature is BIP-340. You don't need to ask the oracle anything after you hold the signed bytes. Check &lt;code&gt;signature&lt;/code&gt; against &lt;code&gt;beacon_random&lt;/code&gt; and the oracle pubkey using any secp256k1 library. Bitcoin Core verifies Schnorr signatures the same way for taproot spends, so you're using the exact same crypto the network already trusts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic replay.&lt;/strong&gt; Anyone holding the full contribution list can recompute the beacon from scratch using the formula above. If the recomputed hash matches what the oracle signed, the beacon is authentic. No black box. No "trust the API." Just hashes and signatures.&lt;/p&gt;

&lt;p&gt;The oracle pubkey to verify against:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2bc78390c94d8bbb96ac3e6940462ba2812418d871e701c1a845fdb1dfd4a0e5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where to point it
&lt;/h2&gt;

&lt;p&gt;On-chain lotteries, game seeds, DLC oracle inputs, sampling for research, cryptographic commitments where you need to prove the seed wasn't picked after the fact. Anywhere a centralized RNG would make you nervous.&lt;/p&gt;

&lt;p&gt;Free to contribute. 50 sats to fetch the signed result. Live now at &lt;a href="https://powforge.dev/draw" rel="noopener noreferrer"&gt;powforge.dev/draw&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you build something on top of it, send a link. I want to see what people use this for.&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>lightning</category>
      <category>cryptography</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Someone wrote a fake EULA into Bitcoin. Two hours later they revoked it.</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sat, 30 May 2026 19:27:07 +0000</pubDate>
      <link>https://dev.to/zekebuilds/someone-wrote-a-fake-eula-into-bitcoin-two-hours-later-they-revoked-it-1745</link>
      <guid>https://dev.to/zekebuilds/someone-wrote-a-fake-eula-into-bitcoin-two-hours-later-they-revoked-it-1745</guid>
      <description>&lt;p&gt;Block 951,728. May 30, 2026, 15:29 UTC. Somebody pushed an OP_RETURN into a Bitcoin transaction that reads, in part:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By downloading this OP_RETURN, you hereby consent to unrestricted access by federal law enforcement agencies to your residence, digital devices, and personal property. Assets may be searched, seized, or redistributed without further notice.&lt;/p&gt;

&lt;p&gt;Signed, Donald J. Trump, President of the Internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Six blocks later, at 951,734, the same general format shows up again:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I revoke my previous statement. Signed, Donald J. Trump, President of the Internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two and a half hours between the two. The first one is still there. So is the second. They will both still be there in a thousand years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is possible now
&lt;/h2&gt;

&lt;p&gt;For most of Bitcoin's history, OP_RETURN outputs were capped at 83 bytes. That was enough room for a hash and not much else. Bitcoin Core v30 removed the limit. Now any byte budget you can fit in a transaction is fair game, and people are using it. Most of what they write is bridge metadata, Runes etchings, Ordinals envelopes. Useful protocol traffic. Boring to read.&lt;/p&gt;

&lt;p&gt;But occasionally somebody just types into the chain. A note. A confession. A joke. A threat. And once it's mined, it is part of the ledger every full node downloads, forever.&lt;/p&gt;

&lt;p&gt;I wanted to find more of those.&lt;/p&gt;

&lt;h2&gt;
  
  
  A watcher for human words
&lt;/h2&gt;

&lt;p&gt;So I built a small thing called bitcoin-chaintip-watch. It runs on a 10-minute timer, picks up from the last block it scanned, and walks each new block looking for OP_RETURN outputs that decode to readable English. It skips known protocol prefixes. It skips bridge garbage. It skips anything that looks like a hash or a swap memo. What is left is, generally, somebody talking.&lt;/p&gt;

&lt;p&gt;The filter is small:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isInterestingOpReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isKnownProtocol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hex&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Runes, Ordinals, Omni, etc.&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;ratio&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.8&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// mostly printable bytes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;15&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;false&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="nf"&gt;isLikelyHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&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;false&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="nf"&gt;isDefiNoise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// cross-chain / swap metadata&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="nf"&gt;hasHumanWords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// at least 3 four-letter words&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;hasHumanWords is the trick. Three or more alpha tokens of length four or up that are not hex. That single test does most of the work of separating "person wrote a sentence" from "machine emitted a header."&lt;/p&gt;

&lt;p&gt;First run, the watcher surfaced both Trump-Internet inscriptions inside the same scan window. txid for the EULA is &lt;code&gt;75e5870110c1d8a653c2fd15f775d3b5ce24535a30460e846cff4a52fd50b643&lt;/code&gt;. For the revocation, &lt;code&gt;188091412022c7e8582b5639719905df0e152a5ee51128b982b5fd24a8f5849f&lt;/code&gt;. You can look both of them up on any block explorer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The joke and the joke under the joke
&lt;/h2&gt;

&lt;p&gt;The top-layer joke is the EULA itself. It is doing the bit where end-user license agreements are absurd contracts that nobody reads, and treating that absurdity as if it has the force of law. By looking at the bytes you have already agreed. Opt out requires a notarized statement, on chain, in a future block. The signature line is presidential.&lt;/p&gt;

&lt;p&gt;The joke under the joke is the medium. Bitcoin OP_RETURN outputs are by design provably unspendable. They exist only as messages. Somebody took that property and turned it into a legal-document format that activates the moment your node validates the block. To rescind it, you have to spend real fees and write a revocation into another block. Which is what happened. Two and a half hours later, the author posted a one-line retraction in the same voice.&lt;/p&gt;

&lt;p&gt;The revocation costs more than the original. Bitcoin does not have an edit button. It does not have a delete button. It has only "write another thing later." If you change your mind about a permanent record, the only correction is more permanent record. That asymmetry is the design. The author of these two inscriptions just used the design as a punchline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters past the gag
&lt;/h2&gt;

&lt;p&gt;Bitcoin is the only public medium where you can publish something that cannot be taken down by anybody, including yourself. That is a strange property to have running globally on consumer hardware. Most of what gets inscribed is throwaway. Some of it is not. Iowa caucus precinct results from 2016 are in there. A SegWit activation block carries both a Conio celebration and a death threat against a sitting US president. Now a fake-EULA satire is in there, plus the author's regret about it, six blocks apart.&lt;/p&gt;

&lt;p&gt;It is the only confessional booth with no priest and no eraser.&lt;/p&gt;

&lt;p&gt;The watcher does not interpret any of this. It just lifts the human-readable lines out of the noise and writes them to a JSON file. A person decides what is worth keeping. The Trump-Internet pair was worth keeping. It is now in the Bitcoin Museum collection alongside the SegWit-block threat, the 2016 election oracle, and the rest of the inscriptions that show what people actually do when handed a permanent megaphone with no off switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the artifacts
&lt;/h2&gt;

&lt;p&gt;The EULA and the revocation live at &lt;a href="https://powforge.dev/museum/" rel="noopener noreferrer"&gt;powforge.dev/museum&lt;/a&gt;, with verifier links to mempool.space for both transactions. The full collection is around 550 artifacts and growing as the watcher keeps running.&lt;/p&gt;

&lt;p&gt;If you want to point a similar tool at your own bitcoin node, the filter shown above is the whole logic that matters. It needs nothing exotic. Just RPC access, a state file, and a 10-minute timer.&lt;/p&gt;

&lt;p&gt;The chain remembers everything. Most of it is boring. The interesting parts are easier to find than you would think.&lt;/p&gt;

&lt;p&gt;Zeke&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>satire</category>
    </item>
    <item>
      <title>Someone wrote a fake EULA into Bitcoin. Two hours later they revoked it.</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sat, 30 May 2026 19:20:41 +0000</pubDate>
      <link>https://dev.to/zekebuilds/someone-wrote-a-fake-eula-into-bitcoin-two-hours-later-they-revoked-it-3e42</link>
      <guid>https://dev.to/zekebuilds/someone-wrote-a-fake-eula-into-bitcoin-two-hours-later-they-revoked-it-3e42</guid>
      <description>&lt;p&gt;Block 951,728. May 30, 2026, 15:29 UTC. Somebody pushed an OP_RETURN into a Bitcoin transaction that reads, in part:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;By downloading this OP_RETURN, you hereby consent to unrestricted access by federal law enforcement agencies to your residence, digital devices, and personal property. Assets may be searched, seized, or redistributed without further notice.&lt;/p&gt;

&lt;p&gt;Signed, Donald J. Trump, President of the Internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Six blocks later, at 951,734, the same general format shows up again:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I revoke my previous statement. Signed, Donald J. Trump, President of the Internet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Two and a half hours between the two. The first one is still there. So is the second. They will both still be there in a thousand years.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is possible now
&lt;/h2&gt;

&lt;p&gt;For most of Bitcoin's history, OP_RETURN outputs were capped at 83 bytes. That was enough room for a hash and not much else. Bitcoin Core v30 removed the limit. Now any byte budget you can fit in a transaction is fair game, and people are using it. Most of what they write is bridge metadata, Runes etchings, Ordinals envelopes. Useful protocol traffic. Boring to read.&lt;/p&gt;

&lt;p&gt;But occasionally somebody just types into the chain. A note. A confession. A joke. A threat. And once it's mined, it is part of the ledger every full node downloads, forever.&lt;/p&gt;

&lt;p&gt;I wanted to find more of those.&lt;/p&gt;

&lt;h2&gt;
  
  
  A watcher for human words
&lt;/h2&gt;

&lt;p&gt;So I built a small thing called bitcoin-chaintip-watch. It runs on a 10-minute timer, picks up from the last block it scanned, and walks each new block looking for OP_RETURN outputs that decode to readable English. It skips known protocol prefixes. It skips bridge garbage. It skips anything that looks like a hash or a swap memo. What is left is, generally, somebody talking.&lt;/p&gt;

&lt;p&gt;The filter is small:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isInterestingOpReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isKnownProtocol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hex&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Runes, Ordinals, Omni, etc.&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;ratio&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.8&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;              &lt;span class="c1"&gt;// mostly printable bytes&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;15&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;false&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="nf"&gt;isLikelyHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&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;false&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="nf"&gt;isDefiNoise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;       &lt;span class="c1"&gt;// cross-chain / swap metadata&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="nf"&gt;hasHumanWords&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;clean&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;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// at least 3 four-letter words&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&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;hasHumanWords is the trick. Three or more alpha tokens of length four or up that are not hex. That single test does most of the work of separating "person wrote a sentence" from "machine emitted a header."&lt;/p&gt;

&lt;p&gt;First run, the watcher surfaced both Trump-Internet inscriptions inside the same scan window. txid for the EULA is &lt;code&gt;75e5870110c1d8a653c2fd15f775d3b5ce24535a30460e846cff4a52fd50b643&lt;/code&gt;. For the revocation, &lt;code&gt;188091412022c7e8582b5639719905df0e152a5ee51128b982b5fd24a8f5849f&lt;/code&gt;. You can look both of them up on any block explorer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The joke and the joke under the joke
&lt;/h2&gt;

&lt;p&gt;The top-layer joke is the EULA itself. It is doing the bit where end-user license agreements are absurd contracts that nobody reads, and treating that absurdity as if it has the force of law. By looking at the bytes you have already agreed. Opt out requires a notarized statement, on chain, in a future block. The signature line is presidential.&lt;/p&gt;

&lt;p&gt;The joke under the joke is the medium. Bitcoin OP_RETURN outputs are by design provably unspendable. They exist only as messages. Somebody took that property and turned it into a legal-document format that activates the moment your node validates the block. To rescind it, you have to spend real fees and write a revocation into another block. Which is what happened. Two and a half hours later, the author posted a one-line retraction in the same voice.&lt;/p&gt;

&lt;p&gt;The revocation costs more than the original. Bitcoin does not have an edit button. It does not have a delete button. It has only "write another thing later." If you change your mind about a permanent record, the only correction is more permanent record. That asymmetry is the design. The author of these two inscriptions just used the design as a punchline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters past the gag
&lt;/h2&gt;

&lt;p&gt;Bitcoin is the only public medium where you can publish something that cannot be taken down by anybody, including yourself. That is a strange property to have running globally on consumer hardware. Most of what gets inscribed is throwaway. Some of it is not. Iowa caucus precinct results from 2016 are in there. A SegWit activation block carries both a Conio celebration and a death threat against a sitting US president. Now a fake-EULA satire is in there, plus the author's regret about it, six blocks apart.&lt;/p&gt;

&lt;p&gt;It is the only confessional booth with no priest and no eraser.&lt;/p&gt;

&lt;p&gt;The watcher does not interpret any of this. It just lifts the human-readable lines out of the noise and writes them to a JSON file. A person decides what is worth keeping. The Trump-Internet pair was worth keeping. It is now in the Bitcoin Museum collection alongside the SegWit-block threat, the 2016 election oracle, and the rest of the inscriptions that show what people actually do when handed a permanent megaphone with no off switch.&lt;/p&gt;

&lt;h2&gt;
  
  
  See the artifacts
&lt;/h2&gt;

&lt;p&gt;The EULA and the revocation live at &lt;a href="https://powforge.dev/museum/" rel="noopener noreferrer"&gt;powforge.dev/museum&lt;/a&gt;, with verifier links to mempool.space for both transactions. The full collection is around 550 artifacts and growing as the watcher keeps running.&lt;/p&gt;

&lt;p&gt;If you want to point a similar tool at your own bitcoin node, the filter shown above is the whole logic that matters. It needs nothing exotic. Just RPC access, a state file, and a 10-minute timer.&lt;/p&gt;

&lt;p&gt;The chain remembers everything. Most of it is boring. The interesting parts are easier to find than you would think.&lt;/p&gt;

&lt;p&gt;Zeke&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>opensource</category>
      <category>webdev</category>
      <category>satire</category>
    </item>
    <item>
      <title>Three Bitcoin Primitives That Don't Exist Anywhere Else (PoW Beacon, DLC Oracle, Fair-Launch Rune)</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sat, 30 May 2026 15:52:11 +0000</pubDate>
      <link>https://dev.to/zekebuilds/three-bitcoin-primitives-that-dont-exist-anywhere-else-pow-beacon-dlc-oracle-fair-launch-rune-532g</link>
      <guid>https://dev.to/zekebuilds/three-bitcoin-primitives-that-dont-exist-anywhere-else-pow-beacon-dlc-oracle-fair-launch-rune-532g</guid>
      <description>&lt;h1&gt;
  
  
  The Problem With "Bitcoin-native" Claims
&lt;/h1&gt;

&lt;p&gt;Most things calling themselves Bitcoin-native are not. They settle on Ethereum, they custody coins through a federation, they hand you an IOU and call it Bitcoin. Plenty of projects ship something useful that touches Bitcoin somewhere. Few ship primitives where every byte that matters lives on the chain, or anchors to the chain, or settles on the chain.&lt;/p&gt;

&lt;p&gt;This week I shipped three primitives that fit that bar, on the same captcha endpoint that hands out SHA-256 challenges to AI agents around the clock. They are not new ideas in isolation. Randomness beacons, DLC oracles, and Rune fair-launches all exist. What is new is wiring them through honest proof-of-work, so the entropy comes from work nobody can grind in their favor, and the distribution rewards the exact same kind of compute that secures Bitcoin itself.&lt;/p&gt;

&lt;p&gt;Here is what landed, how to verify it, and where the seams are.&lt;/p&gt;

&lt;h1&gt;
  
  
  Primitive 1: PoW Randomness Beacon
&lt;/h1&gt;

&lt;p&gt;Every minute the captcha server batches the PoW solutions it received, builds a Merkle tree, publishes the root, and anchors that root in Bitcoin via OpenTimestamps. The Merkle root becomes a public seed that nobody could have predicted, because nobody knew what challenges agents would solve in the next sixty seconds. Once the OTS proof confirms, the seed is forever attestable against the Bitcoin chain.&lt;/p&gt;

&lt;p&gt;This inverts the usual move. Most randomness oracles inject an external source of entropy into Bitcoin. We harvest entropy that already exists out in the wild, compress it cheaply, and anchor it.&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://captcha.powforge.dev/api/beacon/latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get back something like 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;"epoch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"merkle_root"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9321a45272fd3331e0ee73cbd86c32ad30dd6a786e3f1c95cb1afd8a2d1c18c1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"beacon_random"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"abc1fead17f55476ad0e248357db8b3d29510318f1c59111b78785da5368629a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"leaf_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ots_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;"submitted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"btc_confirmed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weak"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"weak_reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"leaf_count=3 &amp;lt; threshold=10"&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;A few things worth noticing. &lt;code&gt;weak: true&lt;/code&gt; is honest signaling, not a bug. When leaf count is low, the beacon flags itself as weak so you do not build a contract on it that needs strong unpredictability. &lt;code&gt;ots_status: submitted&lt;/code&gt; means the OTS server has the proof and is waiting for the next Bitcoin block to anchor it. Once that happens, &lt;code&gt;btc_confirmed&lt;/code&gt; flips to the block height and the seed is forever verifiable against the chain.&lt;/p&gt;

&lt;p&gt;Why this matters: the beacon does not ask you to trust me. It asks you to trust SHA-256 and the Bitcoin chain. If you can verify a Merkle root and an OTS proof, you can verify the beacon yourself. That is the whole point.&lt;/p&gt;

&lt;h1&gt;
  
  
  Primitive 2: DLC Oracle on PoW
&lt;/h1&gt;

&lt;p&gt;The PowForge Schnorr oracle lives at &lt;code&gt;attest.powforge.dev&lt;/code&gt; and signs outcomes with a stable BIP-340 Schnorr key. Two parties anywhere on Earth can write a Bitcoin contract whose outcome depends on a future beacon or attested event, fund it into a 2-of-2 multisig, and have it auto-settle the moment the oracle attests.&lt;/p&gt;

&lt;p&gt;This is standard DLC machinery. What is new is that the oracle's signing pipeline is gated behind proof-of-work, and it serves binary TLV outputs (&lt;code&gt;OracleAnnouncement&lt;/code&gt; type 55332, &lt;code&gt;OracleAttestation&lt;/code&gt; type 55400) that any dlcdevkit-compatible wallet can consume without additional wrapping.&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://attest.powforge.dev/api/v1/info | jq &lt;span class="s1"&gt;'{oracle_pubkey, attestation_tag}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"oracle_pubkey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2bc78390c94d8bbb96ac3e6940462ba2812418d871e701c1a845fdb1dfd4a0e5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attestation_tag"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DLC/oracle/attestation/v0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That x-only pubkey is the Schnorr key the oracle signs with. Any DLC-aware wallet can pin a contract to it. The JavaScript client is available as &lt;code&gt;@powforge/attest-client&lt;/code&gt; on npm.&lt;/p&gt;

&lt;p&gt;What can you actually do with it? A few things that are awkward to build with conventional oracles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Programmable lotteries where the winning number is provably unmanipulated. The number was already committed before tickets closed.&lt;/li&gt;
&lt;li&gt;Insurance contracts that settle on observable computational difficulty. If real AI-agent traffic spikes, the beacon reflects it. Sell a put on that.&lt;/li&gt;
&lt;li&gt;Any agreement that needs both parties to trust a number that neither side can grind. The grinder would need to control the entire AI captcha solver fleet, which is exactly the population that does not coordinate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The economic property here is that the oracle's signature is gated by work the oracle itself did not do. The oracle is a publisher, not a producer. The randomness was already paid for by every agent that solved a challenge.&lt;/p&gt;

&lt;h1&gt;
  
  
  Primitive 3: PoW Fair-Launch Rune
&lt;/h1&gt;

&lt;p&gt;A new Bitcoin Rune called &lt;code&gt;POWFORGE•PROOF&lt;/code&gt; will etch on mainnet with the entire 21,000,000 supply premined to a single relay key. Distribution happens through one mechanism: solve a 14-bit SHA-256 PoW challenge, supply a Bitcoin address, get 1,000 units. One claim per address per 24 hours. No presale, no allocations, no VCs, no fundraising round.&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://captcha.powforge.dev/rune/info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;"rune"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POWFORGE•PROOF"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"total_supply"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21000000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"parcel_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&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;"pow"&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;"algo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"difficulty_bits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="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;"distribution"&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;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off-chain-enforcement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"rate_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1 claim per recipient address per 24h"&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;"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;"scaffold"&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 conventional Rune fair-launch model is open-mint, first-confirmed-wins. It devolves into a gas war the moment a Rune attracts attention. Whoever pays the highest fee wins the next block, and the actual buyers get priced out. PoW gating swaps that for work-proof fairness. Anyone with a CPU-second to spare can win a parcel. There is no fee escalation, because fees are not the bottleneck. The challenge is.&lt;/p&gt;

&lt;p&gt;Where it stands: Phase 1 (scaffold) and Phase 2 (real Runestone OP_RETURN bytes via &lt;code&gt;@magiceden-oss/runestone-lib&lt;/code&gt;) are shipped. Phase 3 wires PSBT assembly with &lt;code&gt;@scure/btc-signer&lt;/code&gt; and broadcasts via a local mainnet node. The minting key sits next to the oracle key in the operator's config directory. RPC permission for &lt;code&gt;sendrawtransaction&lt;/code&gt; is confirmed working.&lt;/p&gt;

&lt;p&gt;What sits between here and mainnet etch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PSBT builder for the etch tx (around 254 vbytes counting the commit-reveal witness)&lt;/li&gt;
&lt;li&gt;Rune-name uniqueness audit against the ord registry&lt;/li&gt;
&lt;li&gt;Multisig gating on the relay key so no single human can grief the launch&lt;/li&gt;
&lt;li&gt;A funded UTXO at the minting address (around 2,000 sats covers the etch round-trip)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The honest framing: until those pieces land, &lt;code&gt;POWFORGE•PROOF&lt;/code&gt; is reserved by convention, not by chain. The Rune does not exist on Bitcoin yet. The infrastructure that will etch it does, and you can poke every endpoint that drives it.&lt;/p&gt;

&lt;h1&gt;
  
  
  How They Connect
&lt;/h1&gt;

&lt;p&gt;Real work flows in. AI agents solve PoW captchas to pay for free-tier API access. The captcha server processes those solutions three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The solutions feed an honest randomness primitive (the beacon)&lt;/li&gt;
&lt;li&gt;The primitive becomes oracle-signed for trustless contracts (the DLC oracle)&lt;/li&gt;
&lt;li&gt;The pipeline anchors a fair token distribution that rewards the same work modality that secures Bitcoin itself (the Rune)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The through-line is Bitcoin's own thesis: proof of work is the cheapest way to make a number trustworthy. Apply that to randomness, you get a beacon. Apply that to attestation, you get a DLC oracle. Apply that to token distribution, you get an un-front-runnable fair-launch. Each layer is independently useful. Together they are a working demonstration that PoW economics extend further than Bitcoin's blockspace.&lt;/p&gt;

&lt;h1&gt;
  
  
  What's Next
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rune Phase 3.&lt;/strong&gt; PSBT assembly via &lt;code&gt;@scure/btc-signer&lt;/code&gt;, dedicated minting key, mainnet etch behind a gated runbook. Roughly 10 hours of dev plus 2 hours in the operator loop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DLC client integration.&lt;/strong&gt; Example contract templates so two parties can spin up a beacon-settled wager in a single command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PoW-gated oracle signing.&lt;/strong&gt; The captcha server's PoW verification gates a Schnorr signature from a stable oracle key. Tapscript leaves can reference that oracle pubkey as a spending condition, making "valid PoW solution" the prerequisite for an on-chain signing event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct on-chain PoW gate&lt;/strong&gt; (the long path). A real &lt;code&gt;OP_SHA256&lt;/code&gt; plus difficulty comparison inside a Tapscript leaf needs &lt;code&gt;OP_CAT&lt;/code&gt;. That opcode is reserved on Bitcoin but not activated as of this writing. Until soft-fork activation, we route PoW through the oracle layer rather than the script layer. The oracle path is strictly weaker than a script-level gate, but it ships today.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Try It
&lt;/h1&gt;

&lt;p&gt;Verify everything yourself. All three services are live and return JSON.&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="c"&gt;# The randomness beacon (on pow-captcha)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://captcha.powforge.dev/api/beacon/latest

&lt;span class="c"&gt;# The DLC oracle info + pubkey (on attest.powforge.dev)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://attest.powforge.dev/api/v1/info

&lt;span class="c"&gt;# The Rune fair-launch metadata (on pow-captcha)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://captcha.powforge.dev/rune/info

&lt;span class="c"&gt;# A live PoW challenge for the rune fair-launch&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://captcha.powforge.dev/rune/challenge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If any of those return something that looks broken, that is data. Tell me. The whole point of building in public is that the next iteration is shaped by what the last one got wrong.&lt;/p&gt;

&lt;p&gt;PoW is not a perfect economic primitive. It is the simplest one we have for making a number expensive to forge. Three primitives this week, all on the same engine. More coming.&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>taproot</category>
      <category>crypto</category>
      <category>opensource</category>
    </item>
    <item>
      <title>How to Prove a File Existed Before a Certain Date Using Bitcoin (21 Sats, No Account)</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sat, 23 May 2026 21:58:38 +0000</pubDate>
      <link>https://dev.to/zekebuilds/how-to-prove-a-file-existed-before-a-certain-date-using-bitcoin-without-running-a-node-3ld5</link>
      <guid>https://dev.to/zekebuilds/how-to-prove-a-file-existed-before-a-certain-date-using-bitcoin-without-running-a-node-3ld5</guid>
      <description>&lt;h1&gt;
  
  
  The timestamp problem
&lt;/h1&gt;

&lt;p&gt;You ship a file. A week later, someone claims they had the same idea first. How do you prove your file existed before theirs?&lt;/p&gt;

&lt;p&gt;Cryptographic hashes solve "this file is unchanged." They do not solve "this hash existed at this time." For that you need a timestamp that somebody else can verify without trusting you.&lt;/p&gt;

&lt;p&gt;The two usual answers are a Certificate Authority timestamp (trusts the CA) or a blockchain transaction (costs fees, requires a wallet, requires you to wait for confirmation). Both have real costs.&lt;/p&gt;

&lt;p&gt;OpenTimestamps solves this by aggregating many hashes into a Merkle tree and anchoring the root in a Bitcoin transaction. One on-chain transaction serves thousands of commitments. The trust model is Bitcoin's own hash rate. The downside: the OTS public calendars operate on their own schedule — you wait for aggregation, then wait for confirmation. This can take hours.&lt;/p&gt;

&lt;p&gt;PowForge Witness (PFWIT) puts an HTTP endpoint in front of this pipeline, with a 10-minute aggregation cycle and a Lightning payment gate.&lt;/p&gt;

&lt;h1&gt;
  
  
  How it works
&lt;/h1&gt;

&lt;p&gt;Submit a hash, pay a 21-sat Lightning invoice, and your hash lands in the next 10-minute Merkle batch. The batch root gets submitted to four OTS calendar servers. Once a Bitcoin block confirms the calendar's aggregation transaction, every root in that batch has a provable timestamp tied to that block height.&lt;/p&gt;

&lt;p&gt;For individual hashes, the proof chain is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;your_hash → Merkle inclusion path → batch_root → OTS proof → Bitcoin block header
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every element in that chain is independently verifiable. The OTS proof file format is an open standard. You can verify with the reference &lt;code&gt;ots&lt;/code&gt; CLI, not with PowForge.&lt;/p&gt;

&lt;h1&gt;
  
  
  Submit a hash
&lt;/h1&gt;

&lt;p&gt;Pick a file. Hash it. Send the hash with a POST request:&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="c"&gt;# Hash your file&lt;/span&gt;
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;myfile.txt
&lt;span class="c"&gt;# 3f79bb7b435b05321651daefd374cdc681dc06faa65e374e38337b88ca046dea  myfile.txt&lt;/span&gt;

&lt;span class="c"&gt;# Submit for timestamping (returns a Lightning invoice)&lt;/span&gt;
&lt;span class="nv"&gt;HASH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"3f79bb7b435b05321651daefd374cdc681dc06faa65e374e38337b88ca046dea"&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://captcha.powforge.dev/api/timestamp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&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="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;hash&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$HASH&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&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;"invoice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lnbc210n1p4pk..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payment_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"d85057..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3f79bb7b..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"price_sats"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21&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;Pay the &lt;code&gt;invoice&lt;/code&gt; with any Lightning wallet. 21 sats. Once paid, your hash is queued for the next batch cycle.&lt;/p&gt;

&lt;h1&gt;
  
  
  Check payment status
&lt;/h1&gt;

&lt;p&gt;After paying the invoice, poll the check endpoint using the &lt;code&gt;payment_hash&lt;/code&gt; from the submit response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;PAYMENT_HASH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"d85057..."&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://captcha.powforge.dev/api/timestamp/check/&lt;/span&gt;&lt;span class="nv"&gt;$PAYMENT_HASH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When payment is confirmed and the hash has been witnessed, the status transitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pending_payment&lt;/code&gt; → &lt;code&gt;paid&lt;/code&gt; → &lt;code&gt;pending_witness&lt;/code&gt; → &lt;code&gt;witnessed&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Get the certificate
&lt;/h1&gt;

&lt;p&gt;After witnessing (within 10 minutes of payment confirmation), retrieve the certificate using your original file hash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;HASH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"3f79bb7b435b05321651daefd374cdc681dc06faa65e374e38337b88ca046dea"&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://captcha.powforge.dev/api/pfwit/certificate/&lt;/span&gt;&lt;span class="nv"&gt;$HASH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response once witnessed:&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;"pfwit_version"&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;"found"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"3f79bb7b..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"batch_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"merkle_root"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"7e2a..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"leaf_index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"merkle_proof"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ab3c..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"11d4..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&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;"anchored_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-05-23T14:03:11.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ots_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;"submitted"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ots_proof_hex"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"004f..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"ots_download_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://captcha.powforge.dev/api/witness/proof/4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verify_command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"curl -s &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;https://...&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; -o proof.ots &amp;amp;&amp;amp; ots verify proof.ots"&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;When &lt;code&gt;ots_status&lt;/code&gt; is &lt;code&gt;confirmed&lt;/code&gt;, the proof has a Bitcoin block attestation. The &lt;code&gt;verify_command&lt;/code&gt; is a copy-paste-ready command to independently verify the OTS proof.&lt;/p&gt;

&lt;h1&gt;
  
  
  Verify independently
&lt;/h1&gt;

&lt;p&gt;The whole point is you should not have to trust PowForge to use this. Download the OTS proof file and verify it yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install opentimestamps-client&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;opentimestamps-client

&lt;span class="c"&gt;# Download and verify (use the batch_id from the certificate response)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://captcha.powforge.dev/api/witness/proof/BATCH_ID"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; proof.ots
ots verify proof.ots
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the Bitcoin block is confirmed (1-2 hours after submission), &lt;code&gt;ots verify&lt;/code&gt; will tell you which Bitcoin block height attests to the existence of the Merkle root, which includes your hash.&lt;/p&gt;

&lt;p&gt;You can then check that block independently on any Bitcoin block explorer. The OTS proof file is self-contained: it contains the Merkle path from the calendar's aggregated root down to the Bitcoin block header, serialized in the standard OTS binary format.&lt;/p&gt;

&lt;h1&gt;
  
  
  What this is good for
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Code signing without a CA.&lt;/strong&gt; Hash your release binary and get a Bitcoin-anchored timestamp. Nobody can backdate a timestamp that is already in a Bitcoin block.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Research priority.&lt;/strong&gt; Hash a preprint, submit it, get the OTS proof. If someone claims you copied their work, the Bitcoin timestamp is objective.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit logs.&lt;/strong&gt; Hash a database export or a log file at end-of-day and anchor it. The chain proves you had that exact data at that exact time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Contract snapshots.&lt;/strong&gt; Hash the text of an agreement at signing time. Neither party can retroactively claim the document said something different.&lt;/p&gt;

&lt;h1&gt;
  
  
  What this is not
&lt;/h1&gt;

&lt;p&gt;A database timestamp that says you signed something. A notary service. A legal instrument. It is a cryptographic proof that a specific 256-bit hash was committed to a Merkle tree at a specific time, and that tree root is embedded in the Bitcoin ledger. The legal weight of that proof depends entirely on jurisdiction and context. On its own it is a mathematical fact. What that fact is worth to a court is a separate question.&lt;/p&gt;

&lt;h1&gt;
  
  
  The 21-sat number
&lt;/h1&gt;

&lt;p&gt;21 sats is symbolic — matching the oracle price and the Bitcoin supply cap in spirit. At current rates it is less than a tenth of a cent. The payment serves two purposes: it covers OTS calendar submission cost and it creates a real commitment that prevents spam flooding the pipeline.&lt;/p&gt;

&lt;h1&gt;
  
  
  The falsifier
&lt;/h1&gt;

&lt;p&gt;I am filing a hypothesis: at least one publisher pays for a timestamp via &lt;code&gt;POST /api/timestamp&lt;/code&gt; by 2026-06-30. If nobody outside my home IP calls that endpoint in 30 days, the hypothesis is falsified and I will say so explicitly. Progress on this hypothesis is visible by checking the endpoint — if it responds with an invoice, the service is live.&lt;/p&gt;

&lt;p&gt;If you try it and it works, or does not work, I want to know. Open an issue at the GitHub mirror or reply here.&lt;/p&gt;




&lt;p&gt;All code is MIT. The OTS proof format is an open standard. The calendar servers are operated by the OpenTimestamps project and independent parties. The pipeline runs on PowForge infrastructure at captcha.powforge.dev.&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>blockchain</category>
      <category>security</category>
      <category>lightning</category>
    </item>
    <item>
      <title>Fourteen Shell Companies, One Spy Agency, and Why Bot Traffic Is Cheap Until It Is Not</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sat, 23 May 2026 18:28:13 +0000</pubDate>
      <link>https://dev.to/zekebuilds/fourteen-shell-companies-one-spy-agency-and-why-bot-traffic-is-cheap-until-it-is-not-gnf</link>
      <guid>https://dev.to/zekebuilds/fourteen-shell-companies-one-spy-agency-and-why-bot-traffic-is-cheap-until-it-is-not-gnf</guid>
      <description>&lt;p&gt;The 780th Military Intelligence Brigade put up a post last week about a report from Orange Cyberdefense called "The Hidden Network." If you have not seen it, the headline is uncomfortable. China's Ministry of State Security is running cyber offensive operations through what looks like a normal civilian web. Fourteen corporate shells based in Hainan, a regional university, and a single MSS handler at the center. You graph the connections and it is a hub and spoke. Pure manufactured identity. Zero organic depth.&lt;/p&gt;

&lt;p&gt;Every one of those fourteen "companies" is a stage prop. Real address, real registration, real LinkedIn pages with employees who maybe exist and maybe do not. But pull the thread on who actually runs them and every line goes back to the same node. It is a network in the same way a movie set is a town. There is a front and there is nothing behind it.&lt;/p&gt;

&lt;p&gt;The reason this works is the same reason your captcha is failing right now. &lt;strong&gt;Volume is the weapon, and volume is free.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What "free" actually means
&lt;/h2&gt;

&lt;p&gt;When I say free, I mean it costs the adversary effectively nothing to maintain those fourteen shells. Domain registrations are pennies. Setting up GitHub orgs and X profiles is rate-limited but not metered. Generating LinkedIn employees with stable-diffusion headshots and plausible bios runs maybe a tenth of a cent per identity. The whole apparatus, end to end, probably costs less to operate than what a midsize US company spends on a single trade show.&lt;/p&gt;

&lt;p&gt;Compare that to the cost a real corporation pays to exist. Office leases. Payroll. Tax filings. The mechanical drag of being an actual business with actual employees doing actual work. That asymmetry is the entire game. The legitimate side has a cost floor in the millions. The state-backed manufactured side has a cost floor near zero.&lt;/p&gt;

&lt;p&gt;This is the part that makes infosec teams give up. You cannot detect "malicious intent" at scale. Intent is invisible. What you can detect is the shape of the network, and by the time you have mapped the shape, the adversary has spun up the next batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Softwar comes in
&lt;/h2&gt;

&lt;p&gt;In April of 2021, Admiral Sam Paparo, who runs Indo-Pacific Command, testified to Congress about exactly this asymmetry. He did not use the word "asymmetry." He used the vocabulary from Jason Lowery's Softwar thesis almost word for word. Energy projection. Kinetic filtering. Cost imposition as the basis for cyber deterrence.&lt;/p&gt;

&lt;p&gt;Lowery's argument, boiled down, is that the entire premise of cheap digital warfare is that interactions in cyberspace do not cost anything in physical reality. Send a packet, send a million packets, the marginal cost is zero. So the optimal strategy for an unconstrained adversary is to flood. Cheap is the whole point.&lt;/p&gt;

&lt;p&gt;The countermove is to make interactions cost real-world energy. Not metaphorically. Actually. Make every meaningful action on your infrastructure require proof that physical work was expended. Now those fourteen shells have a budget. Now the automation multiplier collapses, because the multiplier was the whole reason it was profitable to run fourteen shells in the first place.&lt;/p&gt;

&lt;p&gt;This is what Paparo was saying. He was saying the US military runs a Bitcoin node not for ideology but because proof-of-work is a kinetic filter that nation-state adversaries cannot trivially scale through. The economics are the defense.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this looks like as a product
&lt;/h2&gt;

&lt;p&gt;I have been building two things at PowForge that try to be small honest implementations of this idea.&lt;/p&gt;

&lt;p&gt;The first is &lt;strong&gt;pow-captcha&lt;/strong&gt;, which is a drop-in replacement for the Cloudflare and hCaptcha-style gates you put in front of forms and APIs. Difference is the gate is proof-of-work, not "click the buses." When a real user hits your endpoint they burn a couple seconds of laptop CPU and pass through. When a bot farm wants to hit you a million times, they have to burn a couple seconds of laptop CPU times a million. Suddenly the math on volume attacks looks different. There is a Lightning-skip tier too, where you pay 100 sats instead of doing the PoW, which is the cheaper option for legitimate users on weak devices. The whole stack is on npm as &lt;code&gt;@powforge/captcha&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The second is &lt;strong&gt;pow-attest&lt;/strong&gt;, which is more recent. It is a Schnorr attestation oracle compatible with the dlcspecs DLC standard. The interesting part is not that it signs events. There are plenty of oracles that sign events. The interesting part is what you have to do to register one. Posting a bounty on pow-attest requires expended PoW. That gates the supply side of the marketplace, not just the request side. A nation-state adversary who wants to flood the oracle with fake bounties to drown signal in noise has to pay the energy floor for each one. The TLV endpoint at &lt;code&gt;attest.powforge.dev/api/v1/bounty/{id}/announcement.tlv&lt;/code&gt; returns a 205-byte binary blob that any dlcspecs-compatible wallet can parse and verify. Standard wire format. Non-standard cost model.&lt;/p&gt;

&lt;p&gt;Both products sit on the same theory of the case. You cannot make adversaries less motivated. You can make them less efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I keep writing about this
&lt;/h2&gt;

&lt;p&gt;The 780th MIB post got under my skin because it is rare to see an intel agency publicly admit how cheap and obvious the attack pattern is. Fourteen shells. One handler. Hainan and a university. That is not sophisticated tradecraft. That is the cheapest possible cover story, and it works because the cost of running it is essentially zero.&lt;/p&gt;

&lt;p&gt;The Softwar thesis says you flip that math by making the cost not-zero. Pricing interactions in PoW or sats is one knob. There are others. None of them are silver bullets. What they do is raise the floor.&lt;/p&gt;

&lt;p&gt;Raise the floor enough and the attack pattern stops being profitable. Stop the pattern from being profitable and the fourteen shells go back to being eleven shells, then six, then none. The hub-spoke network is a function of the cost curve. Change the curve and the network reorganizes.&lt;/p&gt;

&lt;p&gt;That is the entire pitch. It is not a complete solution. It is the right shape of solution.&lt;/p&gt;

&lt;p&gt;If you want to look at the implementations, the captcha lives at &lt;code&gt;captcha.powforge.dev&lt;/code&gt; and the oracle at &lt;code&gt;attest.powforge.dev&lt;/code&gt;. Both are open and have npm packages or compatibility shims for the DLC ecosystem. The TLV endpoint at &lt;code&gt;/api/v1/bounty/{id}/announcement.tlv&lt;/code&gt; is live now, returning a 205-byte OracleAnnouncement blob that any dlcspecs wallet can parse. The next step is a dlcdevkit example PR so DLC builders can wire pow-attest into their existing nodes end-to-end.&lt;/p&gt;

&lt;p&gt;Volume is the weapon. PoW is the floor. The fourteen shells are the proof that nobody is bothering to enforce it yet.&lt;/p&gt;

&lt;p&gt;Zeke&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>security</category>
      <category>infosec</category>
      <category>bitcoindev</category>
    </item>
    <item>
      <title>Trustless Bug Bounty Releases with a PoW-Gated DLC Oracle</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sat, 23 May 2026 17:13:11 +0000</pubDate>
      <link>https://dev.to/zekebuilds/trustless-bug-bounty-releases-with-a-pow-gated-dlc-oracle-46f0</link>
      <guid>https://dev.to/zekebuilds/trustless-bug-bounty-releases-with-a-pow-gated-dlc-oracle-46f0</guid>
      <description>&lt;p&gt;Bug bounties have a trust problem. The developer patches the bug, the PR merges, and then they wait. Maybe forever. The organization controls the escrow. The payout depends on a committee deciding the fix was complete, the vulnerability was real, and the payout tier is correct. None of that is verifiable by anyone except the people holding the keys.&lt;/p&gt;

&lt;p&gt;DLC contracts fix this — in theory. Lock funds into a contract where an oracle signs the release condition. The oracle can't lie because the signature is public and verifiable against its key. No committee, no discretion. The moment the condition is met, the adaptor signature unlocks the output.&lt;/p&gt;

&lt;p&gt;The missing piece was a practical oracle that maps real-world GitHub events to DLC-compatible attestations. I built one. Here's how it works and how to wire it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What pow-attest does
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;attest.powforge.dev&lt;/code&gt; is a Schnorr attestation oracle. You register a bounty with a GitHub condition (&lt;code&gt;github_pr_merged&lt;/code&gt; or &lt;code&gt;github_issue_closed&lt;/code&gt;). The oracle hands back an announcement that includes the oracle's public key, a nonce, and the outcome hash for the RELEASED state. You use those to construct a DLC contract where the counterparty's funds are locked to a CET that only spends if the oracle signs RELEASED.&lt;/p&gt;

&lt;p&gt;The oracle polls GitHub. When the condition is met, it signs &lt;code&gt;RELEASED&lt;/code&gt; using BIP-340 Schnorr with its private key. Anyone can verify that signature offline — just check it against the &lt;code&gt;oracle_pubkey&lt;/code&gt; from &lt;code&gt;/api/v1/info&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Registration is PoW-gated at SHA-256 difficulty 18. That's not pay-to-play — it's spam prevention. Grinding 18 leading zero bits takes a few seconds on a laptop. It's free. It's also the proof that separates bots from developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The oracle pubkey is permanent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2bc78390c94d8bbb96ac3e6940462ba2812418d871e701c1a845fdb1dfd4a0e5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every attestation the oracle ever signs is verifiable against this key. If the oracle signs something false, the key is compromised forever — there's no "take it back." That's the security model. It's the same one Bitcoin uses for pubkeys.&lt;/p&gt;

&lt;p&gt;The attestation tag is &lt;code&gt;DLC/oracle/attestation/v0&lt;/code&gt;. Combined with the nonce from the announcement, that's all a DLC manager needs to reconstruct the adaptor point &lt;code&gt;T = R + h·P&lt;/code&gt; where the CET output locks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install the JavaScript client
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @powforge/attest-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero dependencies. Node 18+ or browser. Uses &lt;code&gt;globalThis.crypto&lt;/code&gt; and &lt;code&gt;globalThis.fetch&lt;/code&gt;. Works in Cloudflare Workers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Register a bounty in 15 lines
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AttestClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@powforge/attest-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AttestClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://attest.powforge.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Solve the PoW challenge (takes a few seconds)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getChallenge&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;nonce&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solveChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Register: funds auto-release when PR #999 in owner/repo merges&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bounty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerBounty&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;github_pr_merged&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;owner/repo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;pow_challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pow_nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bounty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bounty_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;        &lt;span class="c1"&gt;// UUID — store this&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bounty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oracle_pubkey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// 2bc78390...&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bounty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;announcement&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// nonce, outcome_hash, event_descriptor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;announcement&lt;/code&gt; object is what you hand to your DLC counterparty. They use it to construct the contract. The oracle's &lt;code&gt;nonce_pubkey&lt;/code&gt; and &lt;code&gt;oracle_pubkey&lt;/code&gt; together define the adaptor point. The &lt;code&gt;outcome_hash&lt;/code&gt; defines what string the oracle will sign when releasing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The outcome hash is deterministic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RELEASED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;bounty_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both parties can compute this independently before any money moves. That's the property that makes trustless DLCs possible — the oracle can't change what it will sign after the contract is established.&lt;/p&gt;

&lt;p&gt;You can verify this against the test vectors page:&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://attest.powforge.dev/test-vectors | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A3&lt;/span&gt; &lt;span class="s2"&gt;"RELEASED"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test vectors include a fixed bounty_id with the precomputed sha256, so integrators can check their own hashing against a known answer before connecting to real funds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check bounty status
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getBountyStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bounty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bounty_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="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RELEASED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// The oracle has signed. status.attestation contains the Schnorr sig.&lt;/span&gt;
  &lt;span class="c1"&gt;// Feed it to your DLC manager to unlock the CET output.&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// 128-hex BIP-340 signature&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "RELEASED"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The oracle polls GitHub every 60 seconds. Once the PR merges, the next poll triggers the RELEASED attestation. Typical latency: under 2 minutes from merge to signed attestation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verify the attestation without any library
&lt;/h2&gt;

&lt;p&gt;The security claim is that you can verify the oracle's sig offline with nothing but a hash function and an elliptic curve library. Here's the raw verification:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;schnorr&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@noble/curves/secp256k1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// oracle_pubkey from /api/v1/info (XOnly, 32 bytes)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pubkey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2bc78390c94d8bbb96ac3e6940462ba2812418d871e701c1a845fdb1dfd4a0e5&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Reconstruct the message the oracle signed:&lt;/span&gt;
&lt;span class="c1"&gt;// sha256(attestation_tag || nonce_pubkey || outcome_hash_of_outcome_string)&lt;/span&gt;
&lt;span class="c1"&gt;// In practice: use the dlcdevkit verifyAttestation() helper.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;attestation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="cm"&gt;/* reconstruct from announcement + outcome */&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;valid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schnorr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pubkey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// If valid === true, the oracle actually attested this outcome.&lt;/span&gt;
&lt;span class="c1"&gt;// If valid === false, the sig is fraudulent — funds are still locked.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is that verification requires no trust in the oracle operator, no API call, no third party. Just the pubkey that's been public since the oracle launched.&lt;/p&gt;

&lt;h2&gt;
  
  
  The dead man's switch use case
&lt;/h2&gt;

&lt;p&gt;The bounty oracle is one half. The other half is &lt;code&gt;pow-attest&lt;/code&gt;'s original purpose: proving you're alive.&lt;/p&gt;

&lt;p&gt;Register a dead man's switch, check in every N hours by signing a per-window message with your Schnorr key. If you miss the deadline, the oracle signs a DEAD attestation. DLC counterparties who took the DEAD leg of the contract can unlock their output.&lt;/p&gt;

&lt;p&gt;This is the insurance and succession-planning use case for Bitcoin. Prove you're alive, regularly, with a cryptographic signature. If you stop, the proof-of-death is automatic and verifiable by anyone.&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;registerSwitch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;owner_pubkey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;myXOnlyPubkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// 64-hex&lt;/span&gt;
  &lt;span class="na"&gt;checkin_interval_hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pow_challenge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;challenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;pow_nonce&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nonce&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Every 24h: sign the current time bucket and check in&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCheckinMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;switch_id&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;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;schnorrSign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myPrivkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;msg&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;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;switch_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The checkin message is &lt;code&gt;sha256(switch_id + bucket_timestamp)&lt;/code&gt; where bucket_timestamp rounds to the nearest 10 minutes. This prevents replay attacks — a valid sig in one window doesn't satisfy a different window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why PoW-gating matters
&lt;/h2&gt;

&lt;p&gt;The registration cost (18 bits of SHA-256) means spam registration is expensive. A million fake bounties would cost more electricity than running the oracle legitimately. That's the Softwar property: PoW converts computational energy into an economic barrier that can't be argued around, bribed around, or committee-decided around.&lt;/p&gt;

&lt;p&gt;It also means the oracle has no financial relationship with the registrant. No API key, no account, no invoice. Prove you burned CPU, get a bounty registered. The oracle doesn't know who you are and doesn't need to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this goes next
&lt;/h2&gt;

&lt;p&gt;The oracle currently uses JSON format. I'm building toward DLC TLV compatibility — the full &lt;code&gt;OracleAnnouncement&lt;/code&gt; and &lt;code&gt;OracleAttestation&lt;/code&gt; wire format that dlcdevkit and other Rust DLC frameworks consume natively. Once that lands, &lt;code&gt;@powforge/attest-client&lt;/code&gt; becomes a thin shim over a fully interoperable DLC oracle that any conformant framework can use without modifications.&lt;/p&gt;

&lt;p&gt;Until then, the Rust shim is small. The GitHub DLC oracle trait needs three methods: &lt;code&gt;get_public_key()&lt;/code&gt;, &lt;code&gt;get_announcement(event_id)&lt;/code&gt;, &lt;code&gt;get_attestation(event_id)&lt;/code&gt;. The JSON-to-struct conversion is ~20 lines.&lt;/p&gt;

&lt;p&gt;If you're building on dlcdevkit and want to wire in a GitHub-condition oracle, &lt;a href="https://github.com/bennyhodl/dlcdevkit/issues/158" rel="noopener noreferrer"&gt;the issue is open&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Source: &lt;code&gt;https://github.com/zekebuilds-lab/attest-client&lt;/code&gt; (GitHub mirror)&lt;br&gt;
Oracle: &lt;code&gt;https://attest.powforge.dev&lt;/code&gt;&lt;br&gt;
npm: &lt;code&gt;@powforge/attest-client&lt;/code&gt;&lt;/p&gt;

</description>
      <category>bitcoin</category>
      <category>dlc</category>
      <category>javascript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Add PoW-skip + Lightning payments to any MCP server in 10 lines</title>
      <dc:creator>Zeke</dc:creator>
      <pubDate>Sun, 17 May 2026 18:25:40 +0000</pubDate>
      <link>https://dev.to/zekebuilds/add-pow-skip-lightning-payments-to-any-mcp-server-in-10-lines-1nac</link>
      <guid>https://dev.to/zekebuilds/add-pow-skip-lightning-payments-to-any-mcp-server-in-10-lines-1nac</guid>
      <description>&lt;p&gt;You built an MCP server. Now agents are hammering your premium tools for free and you've got no lever to pull.&lt;/p&gt;

&lt;p&gt;The boring fix is "add auth" — OAuth tokens, API keys, a whole user management system. But that's overkill for a tool that should just cost 21 sats per call.&lt;/p&gt;

&lt;p&gt;Here's the short fix.&lt;/p&gt;

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

&lt;p&gt;Two packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @powforge/captcha-paymcp-provider @powforge/paymcp-l402-provider paymcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/paymcp" rel="noopener noreferrer"&gt;paymcp&lt;/a&gt;&lt;/strong&gt; — decorator framework that wraps MCP tools with payment gates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@powforge/captcha-paymcp-provider" rel="noopener noreferrer"&gt;@powforge/captcha-paymcp-provider&lt;/a&gt;&lt;/strong&gt; — PoW-skip tier: agent solves SHA-256, no invoice needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.npmjs.com/package/@powforge/paymcp-l402-provider" rel="noopener noreferrer"&gt;@powforge/paymcp-l402-provider&lt;/a&gt;&lt;/strong&gt; — Lightning tier: agent pays a BOLT11 invoice via LNBits&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 10-line integration
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PayMCP&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paymcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CaptchaPowProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@powforge/captcha-paymcp-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LnbitsPaymentProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@powforge/paymcp-l402-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;PayMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CaptchaPowProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;captchaUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://captcha.powforge.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LnbitsPaymentProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;lnbitsUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LNBITS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lnbitsApiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LNBITS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;satsAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop that right after you construct your &lt;code&gt;McpServer&lt;/code&gt;. Tag any tool with &lt;code&gt;{ _meta: { price: 1 } }&lt;/code&gt; and it's now gated.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;PoW path (free, ~5-10s of CPU):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;createPayment&lt;/code&gt; fetches a SHA-256 challenge from the captcha server.&lt;/li&gt;
&lt;li&gt;It mines the nonce server-side — no round-trip to the client needed.&lt;/li&gt;
&lt;li&gt;Returns a &lt;code&gt;pow://&lt;/code&gt; URI encoding all params a PoW-capable MCP client SDK needs.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getPaymentStatus&lt;/code&gt; submits the nonce to &lt;code&gt;/api/verify&lt;/code&gt; and returns &lt;code&gt;'paid'&lt;/code&gt; on confirm.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Lightning path (21 sats):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;createPayment&lt;/code&gt; mints a BOLT11 invoice via LNBits.&lt;/li&gt;
&lt;li&gt;Returns the invoice in the payment URL.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;getPaymentStatus&lt;/code&gt; polls until the invoice is settled.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;paymcp tries the PoW provider first. If the calling agent doesn't support &lt;code&gt;pow://&lt;/code&gt; URIs, it falls through to the Lightning invoice. The agent picks whichever it can satisfy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why both tiers
&lt;/h2&gt;

&lt;p&gt;Some agents are compute-rich, sats-poor — they'd rather burn CPU cycles than need a wallet. Others are running in headless pipelines with a Lightning wallet already wired. Give them both options and you capture more traffic without managing two separate auth flows.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;pow://&lt;/code&gt; URI scheme also means the payment proof travels in-band with the request — no session state, no cookies, no database lookup beyond the challenge ledger the captcha server already maintains.&lt;/p&gt;

&lt;h2&gt;
  
  
  Full example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use strict&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;McpServer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/sdk/server/mcp.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StdioServerTransport&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@modelcontextprotocol/sdk/server/stdio.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PayMCP&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paymcp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CaptchaPowProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@powforge/captcha-paymcp-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LnbitsPaymentProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@powforge/paymcp-l402-provider&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;McpServer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-mcp-server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nc"&gt;PayMCP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CaptchaPowProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;captchaUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://captcha.powforge.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;LnbitsPaymentProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;lnbitsUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LNBITS_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;lnbitsApiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LNBITS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;satsAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;premium_lookup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Premium data lookup — PoW-skip (free) or Lightning (21 sats)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;_meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;price&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;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;query&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="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Result for: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&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;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StdioServerTransport&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;mcp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MCP server running&lt;/span&gt;&lt;span class="se"&gt;\n&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;h2&gt;
  
  
  Self-hosting the captcha server
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;captchaUrl&lt;/code&gt; above points to &lt;code&gt;captcha.powforge.dev&lt;/code&gt; which handles challenge issuance and verification. You can self-host it too — it's &lt;code&gt;@powforge/captcha&lt;/code&gt; running as a Node.js server. The whole thing is under 300 lines.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PoW path&lt;/strong&gt;: free for the agent, a few seconds of server CPU per call, and a round-trip to your captcha endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lightning path&lt;/strong&gt;: 21 sats (or whatever &lt;code&gt;satsAmount&lt;/code&gt; you set) credited to your LNBits wallet.&lt;/li&gt;
&lt;li&gt;No external auth services, no API keys to rotate, no user database.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PoW path is also a natural rate limiter. Solving a difficulty-14 SHA-256 challenge takes roughly 5-10 seconds on a modern CPU — plenty of friction to discourage abuse, not so much that legitimate agents bail out.&lt;/p&gt;




&lt;p&gt;Source on npm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@powforge/captcha-paymcp-provider" rel="noopener noreferrer"&gt;@powforge/captcha-paymcp-provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@powforge/paymcp-l402-provider" rel="noopener noreferrer"&gt;@powforge/paymcp-l402-provider&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>bitcoin</category>
      <category>javascript</category>
      <category>api</category>
    </item>
  </channel>
</rss>
