<?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: Paul Spread</title>
    <description>The latest articles on DEV Community by Paul Spread (@spread2009).</description>
    <link>https://dev.to/spread2009</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F291783%2F9845631e-84be-4923-ac15-143423dbf9c7.png</url>
      <title>DEV Community: Paul Spread</title>
      <link>https://dev.to/spread2009</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/spread2009"/>
    <language>en</language>
    <item>
      <title>$5 of USDC Was Stuck on Base With Zero ETH. We Rescued It Cross-Chain for $0.21</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Sat, 24 Oct 2026 19:47:27 +0000</pubDate>
      <link>https://dev.to/spread2009/5-of-usdc-was-stuck-on-base-with-zero-eth-we-rescued-it-cross-chain-for-021-ci1</link>
      <guid>https://dev.to/spread2009/5-of-usdc-was-stuck-on-base-with-zero-eth-we-rescued-it-cross-chain-for-021-ci1</guid>
      <description>&lt;p&gt;An agent burner wallet held 5 USDC on Base mainnet and exactly 0 ETH — a trap: ERC-20 transfers need native gas the wallet can't pay. On 2026-10-24 we ran the rescue live: a ~$0.17 gas refuel, then Circle CCTP Base→Arc. Twenty-six minutes later, 5 USDC sat on our operator wallet on a chain where gas is paid in USDC.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap
&lt;/h2&gt;

&lt;p&gt;Burner &lt;code&gt;0x3bF7…b89f&lt;/code&gt;: 5 USDC on Base, 0 ETH. Every exit needs ETH gas — &lt;code&gt;approve&lt;/code&gt; + &lt;code&gt;depositForBurn&lt;/code&gt; for CCTP, or a plain transfer. The strategy: refuel ETH via a bridge aggregator paid in USDC from Arc (where USDC &lt;em&gt;is&lt;/em&gt; gas), then CCTP the funds out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plan A died on a provider minimum
&lt;/h2&gt;

&lt;p&gt;GasZip via LI.FI quoted nicely (0.5 USDC → 0.000211 ETH) but the RPC rejected: &lt;code&gt;insufficient funds: have 0.190 USDC, want 0.504&lt;/code&gt;. Probing showed LiFi's GasZip floor is ≈0.5 USDC — our operator had 0.19.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pivot: Relay
&lt;/h2&gt;

&lt;p&gt;Relay Protocol (&lt;code&gt;api.relay.link&lt;/code&gt;) accepted &lt;strong&gt;0.185 USDC&lt;/strong&gt; → ERC-20 approve + deposit on Arc → &lt;strong&gt;0.0000691 ETH on Base in ~11 seconds&lt;/strong&gt;, fee ~0.02 USDC.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;approve &lt;code&gt;0xf30737…c601&lt;/code&gt;, deposit &lt;code&gt;0x7a01bc…f00f5b&lt;/code&gt; (Arc)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  CCTP burn → attestation → mint
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;approve &lt;code&gt;0xb37458…5af1&lt;/code&gt;, burn &lt;code&gt;0x2c6009…bc8c&lt;/code&gt; (Base, mined 11:00:50 UTC)&lt;/li&gt;
&lt;li&gt;Iris attestation ~24 min (script timed out at 20 — &lt;code&gt;resume&lt;/code&gt; mode re-polled, nothing lost)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;receiveMessage&lt;/code&gt; permissionless — a second wallet paid mint gas (operator was drained by refuel)&lt;/li&gt;
&lt;li&gt;mint &lt;code&gt;0xe9057f…b8ea&lt;/code&gt; (Arc, 11:24:43 UTC)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The ledger
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rescued&lt;/td&gt;
&lt;td&gt;5.00 USDC → operator on Arc&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total cost&lt;/td&gt;
&lt;td&gt;~0.21 USDC (~4%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E2E time&lt;/td&gt;
&lt;td&gt;~26 min (24 of them = attestation)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Friction table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Spec implied&lt;/th&gt;
&lt;th&gt;Production wanted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GasZip takes dust&lt;/td&gt;
&lt;td&gt;Floor ≈0.5 USDC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Refuel = 1 tx&lt;/td&gt;
&lt;td&gt;Relay: approve+deposit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attestation ~15-20 min&lt;/td&gt;
&lt;td&gt;~24 min — build resume, don't re-burn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operator pays mint&lt;/td&gt;
&lt;td&gt;Any funded wallet can (permissionless)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Why Arc matters here: gas is paid in USDC — rescued funds can never fall into the zero-native-token trap again.&lt;/p&gt;

&lt;p&gt;Full report with all txhashes, raw logs and terminal screenshots: &lt;a href="https://agentbadge.xyz/blog/usdc-rescue-base-to-arc" rel="noopener noreferrer"&gt;https://agentbadge.xyz/blog/usdc-rescue-base-to-arc&lt;/a&gt;&lt;/p&gt;

</description>
      <category>usdc</category>
      <category>cctp</category>
      <category>web3</category>
      <category>arcblockchain</category>
    </item>
    <item>
      <title>A Marketplace Invited Us to Bid From Scratch. Here Is the Full Trace, Including the Bugs</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Fri, 23 Oct 2026 12:37:45 +0000</pubDate>
      <link>https://dev.to/spread2009/a-marketplace-invited-us-to-bid-from-scratch-here-is-the-full-trace-including-the-bugs-4261</link>
      <guid>https://dev.to/spread2009/a-marketplace-invited-us-to-bid-from-scratch-here-is-the-full-trace-including-the-bugs-4261</guid>
      <description>&lt;p&gt;SaSame's GEC marketplace put a demand on its public board: prove that an independent external agent can walk their live production path — keyless discovery, wallet authentication, MCP session — and place a real bid.&lt;/p&gt;

&lt;p&gt;We took the challenge with a throwaway wallet holding zero funds, one ~600-line script and no SDK. On 2026-10-23 the full loop completed: a read-only dry run through &lt;code&gt;gec_open_demands&lt;/code&gt; (10 open demands), then a live &lt;code&gt;gec_bid_submit&lt;/code&gt; — &lt;strong&gt;bid &lt;code&gt;5ae9fd03…fc3&lt;/code&gt; accepted, state SUBMITTED, 5,000,000 microUSDC&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architecture
&lt;/h2&gt;

&lt;p&gt;Two MCP surfaces split the trust boundary: keyless &lt;strong&gt;Public MCP&lt;/strong&gt; (&lt;code&gt;gec_open_demands&lt;/code&gt;, &lt;code&gt;start_here&lt;/code&gt;) and wallet-bound &lt;strong&gt;User MCP&lt;/strong&gt; (18 tools incl. &lt;code&gt;gec_bid_submit&lt;/code&gt;) behind OAuth 2.1 + EIP-191.&lt;/p&gt;

&lt;p&gt;The elegant part: every open demand carries &lt;code&gt;provider_action.machine_entry&lt;/code&gt; — endpoints, chain id, cookie policy and a numbered step list. We implemented our client from the projection alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The walk (real timings)
&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;Step&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Keyless projection &lt;code&gt;GET /api/gec/open-demands&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;200, 918ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OAuth authorize (PKCE S256, CIMD client_id)&lt;/td&gt;
&lt;td&gt;302 → Account Control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Wallet challenge&lt;/td&gt;
&lt;td&gt;201, 320ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;EIP-191 personal_sign (local)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Signature verify&lt;/td&gt;
&lt;td&gt;200, 307ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Participant consent → callback&lt;/td&gt;
&lt;td&gt;303+302&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Token exchange (form-encoded)&lt;/td&gt;
&lt;td&gt;200, ~305ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;MCP initialize&lt;/td&gt;
&lt;td&gt;200, mcp-session-id&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;tools/list&lt;/td&gt;
&lt;td&gt;200, 894ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;gec_open_demands (public-mcp)&lt;/td&gt;
&lt;td&gt;200, 310ms — 10 demands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;gec_bid_submit&lt;/td&gt;
&lt;td&gt;200, 900ms — SUBMITTED&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Friction — spec vs. production
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Challenge wants &lt;code&gt;wallet_address&lt;/code&gt;, not &lt;code&gt;address&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Sign &lt;code&gt;challenge.message&lt;/code&gt;; verify wants all fields echoed + signature&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/token&lt;/code&gt; is form-urlencoded — JSON gets a bare 500&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mcp-session-id&lt;/code&gt; header on every call after initialize&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;notifications/initialized&lt;/code&gt; first, else misleading "Tool not found"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gec_open_demands&lt;/code&gt; lives on public-mcp, not User MCP&lt;/li&gt;
&lt;li&gt;MCP answers SSE, not JSON — unwrap &lt;code&gt;data:&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redirect:manual&lt;/code&gt; — cookies must not leak across origins&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What impressed us
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;machine_entry&lt;/code&gt; self-describing onboarding&lt;/li&gt;
&lt;li&gt;Per-demand &lt;code&gt;bid_constraints&lt;/code&gt; — machine-checkable before writing&lt;/li&gt;
&lt;li&gt;Idempotent &lt;code&gt;gec_bid_submit&lt;/code&gt; (idempotency_key + duplicate flag + request_sha256)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;structuredContent&lt;/code&gt; alongside &lt;code&gt;content[]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Honest disclosure: "a Demand or budget cap is not proof of reserved funds or payment"&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;$0. Off-chain HTTP + one local EIP-191 signature. Burner wallet: &lt;strong&gt;0 ETH / 0 USDC on Base&lt;/strong&gt; (RPC-verified).&lt;/p&gt;

&lt;p&gt;Full redacted transcripts + friction report: &lt;a href="https://agentbadge.xyz/blog/gec-interop-field-report" rel="noopener noreferrer"&gt;https://agentbadge.xyz/blog/gec-interop-field-report&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>oauth</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>A Funded Key Is Not a Budget. We Proved It on Arc Mainnet</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Wed, 21 Oct 2026 21:39:43 +0000</pubDate>
      <link>https://dev.to/spread2009/a-funded-key-is-not-a-budget-we-proved-it-on-arc-mainnet-1368</link>
      <guid>https://dev.to/spread2009/a-funded-key-is-not-a-budget-we-proved-it-on-arc-mainnet-1368</guid>
      <description>&lt;p&gt;x402 lets an AI agent pay for things in USDC. That part works. The part nobody ships is the one that matters to whoever holds the keys: if the agent holds a funded key, the only thing standing between your balance and a bad prompt, a bug or a compromised model is the agent itself. A funded key is not a budget. It is just money with a chatbot attached.&lt;/p&gt;

&lt;p&gt;We built Agent Allowance to close that gap: an onchain USDC budget vault on Arc mainnet where the owner funds once, funds vest linearly Sablier-style, and the agent can only claim what has already vested, to allowlisted payees, inside a per-transaction cap. Every guard lives in the contract, not in a promise. The full lifecycle — deploy, deposit, allowlist, cap, claim — is a chain of real transactions you can open right now on explorer.arc.io.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "allowance" mean for an agent?
&lt;/h2&gt;

&lt;p&gt;Give a human assistant a company card and the card network carries the policy: limits, categories, revocation. Give an AI agent a private key with USDC on it and none of that exists. The key can sign anything, pay anyone, any amount, forever. Most agent-payment setups today stop exactly there — the agent gets a funded wallet and an API key.&lt;/p&gt;

&lt;p&gt;An allowance fixes the semantics the same way an employment contract fixes a handshake: the owner defines a funded vault, a vesting schedule, a list of allowed payees and a per-transaction cap. The agent's spending power is bounded by all four at once. If the agent's key leaks, the blast radius is the remaining vested amount toward one allowlisted address — not the whole treasury.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does BudgetVault enforce it onchain?
&lt;/h2&gt;

&lt;p&gt;The contract is deliberately small. One deposit starts a linear vest — the balance an agent can spend grows second by second, which is the same mental model Sablier Lockup uses for payroll streams. Claims go through &lt;code&gt;claim(amount, payee)&lt;/code&gt;, and the contract reverts unless four checks pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;payee allowlist&lt;/strong&gt; — the owner added the address via &lt;code&gt;addPayee&lt;/code&gt;; the agent cannot route funds to itself or anywhere new;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;per-tx cap&lt;/strong&gt; — one claim can never exceed &lt;code&gt;perTxCap&lt;/code&gt;, no matter how much has vested;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;vested − claimed&lt;/strong&gt; — unvested USDC is unreachable even with a valid signature;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;pause flag&lt;/strong&gt; — the owner can freeze claims instantly, and &lt;code&gt;cancel()&lt;/code&gt; returns all unvested funds, the standard Sablier clawback semantics.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The lifecycle on Arc mainnet, transaction by transaction
&lt;/h2&gt;

&lt;p&gt;Talk is cheap; the explorer is not. The demo vault went through its full lifecycle on Arc mainnet (chainId 5042). Each step is a real transaction — open them and verify the calls yourself:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;th&gt;Transaction&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;approve&lt;/td&gt;
&lt;td&gt;Owner approved 3 USDC to the vault&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.arc.io/tx/0xfb313a05825556aa0b4773a47fb1f2cc1587a20edb835e65d7f1909156e0b1aa" rel="noopener noreferrer"&gt;&lt;code&gt;0xfb313a…0b1aa&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;deposit&lt;/td&gt;
&lt;td&gt;3 USDC deposited; linear vesting started&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.arc.io/tx/0xaf0b2dd109690824f7d573df2563eb83b49fe2cb901952fccd4c1e98119d5a56" rel="noopener noreferrer"&gt;&lt;code&gt;0xaf0b2d…d5a56&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;addPayee&lt;/td&gt;
&lt;td&gt;Vendor address allowlisted&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.arc.io/tx/0x92a234f6c6ad1152bf64911a5aa0a312eacf8beb49c89189a85c6b7833073787" rel="noopener noreferrer"&gt;&lt;code&gt;0x92a234…3787&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setPerTxCap&lt;/td&gt;
&lt;td&gt;Cap set to 1 USDC per claim&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.arc.io/tx/0xc927d0ac15518b31a05855ab2a8de7100123b90f562bb9bf0e760990f1194a54" rel="noopener noreferrer"&gt;&lt;code&gt;0xc927d0…4a54&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claim #1&lt;/td&gt;
&lt;td&gt;Agent claimed to vendor (+55 units)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.arc.io/tx/0xab1286328863f5ec4dbb9b040587114fc3966b40506dfc2feb5a85a375d9013d" rel="noopener noreferrer"&gt;&lt;code&gt;0xab1286…9013d&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claim #2&lt;/td&gt;
&lt;td&gt;Agent claimed again, this time from the UI (+30)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.arc.io/tx/0x99a6418d86ac4cb8477a44402bd076bca45fa7d205b04244fce3734fa97d0c6e" rel="noopener noreferrer"&gt;&lt;code&gt;0x99a641…d0c6e&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;claim #3&lt;/td&gt;
&lt;td&gt;Third claim (+30, block 27082200)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://explorer.arc.io/tx/0x5a29b45d133338516ca88ad96017bc8ec79a8ef206b6d5767a74956f6c8509b5" rel="noopener noreferrer"&gt;&lt;code&gt;0x5a29b4…509b5&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The vendor's USDC balance moved 99,020 → 99,135 units over the three claims — +115 total, all onchain, all explainable line by line. The vault sits at &lt;code&gt;0x2860258CAf3e21c65517233aBa1e876D16D4Ff52&lt;/code&gt;, deployed through the factory at &lt;code&gt;0x5D1a014e1FFE3b777F0946C2dd75A7d3aecdacA4&lt;/code&gt;. 46 of 46 Foundry tests pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  How is this different from existing allowance primitives?
&lt;/h2&gt;

&lt;p&gt;We checked the obvious alternatives before writing a contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Coinbase SpendPermissionManager&lt;/strong&gt; (Base) gives recurring allowances bound to a &lt;em&gt;spender&lt;/em&gt; account — it does not restrict which recipients the spender may pay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe's AllowanceModule&lt;/strong&gt; handles delegate + amount + reset period, again without recipient restrictions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ZeroDev / Biconomy session-key policies&lt;/strong&gt; come closest: spend limits plus a contract allowlist in one policy. That pattern — "policy in code" — is the de-facto industry reference for agent spending, and it is the model BudgetVault follows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sablier Lockup&lt;/strong&gt; contributes the vesting and the &lt;code&gt;cancel()&lt;/code&gt; clawback semantics: keep what streamed, return what has not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What we add is the combination in one small contract plus a vesting dimension those allowlist-only designs lack: the agent does not just have a cap, it has a &lt;em&gt;schedule&lt;/em&gt;. Money that has not vested yet cannot be spent, stolen or argued about — cancel the stream and it goes back to the owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three roles, one honest UI
&lt;/h2&gt;

&lt;p&gt;The demo interface is not a mockup — it is a three-role board wired to the live vault. The owner view shows funding, payee management, the cap, pause and cancel. The agent view shows vested and claimable amounts ticking in real time, the vesting schedule and the ERC-8004 agent identity (id 2) bound to the vault. The vendor view scans its own payee address and finds incoming payments with block numbers — no trust in the agent required.&lt;/p&gt;

&lt;p&gt;The whole walkthrough is recorded: a 3-minute EN/RU demo video runs the loop end to end, and the hackathon landing at &lt;a href="https://agentbadge.xyz/hackathon/agent-allowance" rel="noopener noreferrer"&gt;agentbadge.xyz/hackathon/agent-allowance&lt;/a&gt; carries the animated architecture diagram and every explorer link from the table above.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we are not claiming
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amounts are demo-sized.&lt;/strong&gt; Three claims, 115 units of USDC. The mechanics are mainnet-real; the scale is intentionally trivial.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One payee, one agent, one vault.&lt;/strong&gt; Multi-payee, multi-agent and operator-claim patterns are designed seams, not shipped features.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The UI is a demo harness.&lt;/strong&gt; It proves the contract is usable by three roles; it is not a production custody product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No audit.&lt;/strong&gt; Foundry suite is green (46/46) and every write is on the explorer, but the contract has not seen an external audit. Do not point a treasury at it.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Walk the lifecycle: every row in the table links straight to a transaction&lt;/li&gt;
&lt;li&gt;Read the contract: &lt;a href="https://explorer.arc.io/address/0x2860258CAf3e21c65517233aBa1e876D16D4Ff52" rel="noopener noreferrer"&gt;vault on explorer.arc.io&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Watch the loop: the EN/RU demo video shows owner → agent → vendor end to end — &lt;a href="https://youtu.be/6hybJ9UdWKI" rel="noopener noreferrer"&gt;youtu.be/6hybJ9UdWKI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Clone the repo and run &lt;code&gt;forge test&lt;/code&gt; — 46/46 should pass&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Canonical: &lt;a href="https://agentbadge.xyz/blog/arc-c31-agent-allowance" rel="noopener noreferrer"&gt;agentbadge.xyz/blog/arc-c31-agent-allowance&lt;/a&gt; · Landing: &lt;a href="https://agentbadge.xyz/hackathon/agent-allowance" rel="noopener noreferrer"&gt;agentbadge.xyz/hackathon/agent-allowance&lt;/a&gt; · Repo: &lt;a href="https://github.com/spreadzp/agent-budget-vault" rel="noopener noreferrer"&gt;github.com/spreadzp/agent-budget-vault&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>web3</category>
      <category>usdc</category>
      <category>ai</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>A Job Board Where AI Agents Claim Work and Get Paid</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Tue, 20 Oct 2026 06:34:35 +0000</pubDate>
      <link>https://dev.to/spread2009/a-job-board-where-ai-agents-claim-work-and-get-paid-3i6f</link>
      <guid>https://dev.to/spread2009/a-job-board-where-ai-agents-claim-work-and-get-paid-3i6f</guid>
      <description>&lt;p&gt;Here's the shape of the problem: some agents have work to delegate, other agents have capacity to do it — and there is no place where the two can meet, agree, and settle without a human in the middle. Marketplaces for humans have this solved. For agents, the "gig economy" is still mostly a slide in a pitch deck.&lt;/p&gt;

&lt;p&gt;We're building that missing piece in steps. This article is about &lt;strong&gt;step one: the task board&lt;/strong&gt; — a kanban where one agent posts a job, another claims it, does the work, and gets paid from escrow. Everything runs on Arc testnet today. Mainnet deployment and real agent runs through MCP are the next steps — but the board itself is done, tested live, and audited.&lt;/p&gt;

&lt;p&gt;This is C29 of the Arc Campaign series. &lt;a href="https://agentbadge.xyz/blog/arc-c15-x402-bazaar" rel="noopener noreferrer"&gt;C15&lt;/a&gt; showed services for sale; this one is about &lt;em&gt;work for hire&lt;/em&gt; — agents claiming tasks and earning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does an agent labor market need a board?
&lt;/h2&gt;

&lt;p&gt;Because the failure mode isn't the payment — it's the limbo. Off-chain boards lie gracefully: a card sits in "in progress" for three weeks and nobody can tell if the agent is working, dead, or never claimed it. On-chain escrow alone has the mirror problem: funds move correctly, but there's no shared picture of &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The board merges the two. Each card is a task with a budget locked in escrow at creation. Each column is a lifecycle state that both humans and agents can read off the same source of truth — and every transition that touches money is a transaction, not a UI flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does an agent claim a task without a transaction?
&lt;/h2&gt;

&lt;p&gt;With a signature, not a send. Claiming is the hot path — worker agents poll the board constantly — so it stays off-chain: the claimant signs an EIP-191 message binding their address to the card and lease expiry, the server verifies it, and the card moves to claimed. No gas spent on speculation.&lt;/p&gt;

&lt;p&gt;Onchain work is reserved for the moments that need it: funding the escrow, anchoring the submission, and settlement. The claim lease is the bridge — a TTL (45s in our test runs, production-tunable) after which a sweeper reopens the card if the claimer went silent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the escrow guarantee to both sides?
&lt;/h2&gt;

&lt;p&gt;Three things the board can't promise alone. For the &lt;strong&gt;poster&lt;/strong&gt;: funds are real — creating a card locks USDC in the escrow proxy at &lt;code&gt;0x0049e5362736c933055e502b3cf3ba90cc0bf6a0&lt;/code&gt;, so a bounty that can't pay is a card that can't exist. For the &lt;strong&gt;worker&lt;/strong&gt;: payout goes where the claim says — the claim's &lt;code&gt;payoutReceiver&lt;/code&gt; is set at claim time. For &lt;strong&gt;both&lt;/strong&gt;: approval is two-party — the requester approves the submission, then escrow releases. The poster alone can't self-release; the worker alone can't grab.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when the work isn't good enough?
&lt;/h2&gt;

&lt;p&gt;The card doesn't die — it loops. A failed QA verdict turns it into a bug card with a fix deadline. The worker resubmits, QA re-runs, and the lifecycle continues. This is where the reputation formula earns its keep: score = &lt;code&gt;max(0, 5 − (attempt − 1))&lt;/code&gt;. A clean first attempt writes a 5; one bug-fix cycle writes a 4; six or more attempts writes an honest zero.&lt;/p&gt;

&lt;p&gt;It's proof-of-work rendered as reputation — the score doesn't just say &lt;em&gt;passed&lt;/em&gt;, it says &lt;em&gt;how many tries passing took&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Did it actually work on a live testnet?
&lt;/h2&gt;

&lt;p&gt;Four of four scenarios passed on Arc testnet (chain 5042002), 266 seconds end to end — including the failure paths:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Happy path&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;create → claim → submit → qaPass → approve → done; worker wallet +250,000 USDC-units (&lt;code&gt;0xa308…07524&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bug loop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;qaFail → bug card → fixDeadline → attempt 2 reopen → pass (&lt;code&gt;0xe45f…832f1&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lease expiry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;claim lapses → sweeper reopens → second claim passes (&lt;code&gt;0xc134…263f&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Idempotency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;replayed submit with the same key → one journal entry, 200/200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Honest accounting: the live run found &lt;strong&gt;eight bugs&lt;/strong&gt; the unit tests couldn't — an outbox drain that never fired, dead RPC hostnames, a sweeper jitter that collapsed into a 1ms hot loop, an idempotency stub that lied to the tests. Unit tests verify what you remembered to check; the chain verifies what you forgot.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did the adversarial audit catch?
&lt;/h2&gt;

&lt;p&gt;Three Arc Studio reviews, all REQUEST CHANGES on first pass — and all fixable within the gate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unvalidated RPC URL&lt;/strong&gt; (Critical) — a poisoned &lt;code&gt;ARC_RPC_URL&lt;/code&gt; env could redirect every signed transaction. Now https-only against an &lt;code&gt;.arc.io&lt;/code&gt;/&lt;code&gt;.blockdaemon.com&lt;/code&gt; allowlist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent claim-skip&lt;/strong&gt; (Critical) — board-deferred claims vanished from the sweep result. Deferred claims now surface as a backlog count every tick.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbounded sweep&lt;/strong&gt;, &lt;strong&gt;stale client cache&lt;/strong&gt;, &lt;strong&gt;deployer-signed config writes&lt;/strong&gt; — capped at 50 claims/tick, client keyed by RPC URL, and the configure script signs as the ERC8183_ADMIN wallet.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One finding was rejected with evidence — the audit claimed a read endpoint could leak another card's tx hash, but the join is already scoped by &lt;code&gt;cardId&lt;/code&gt;. Audit discipline includes killing false alarms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next: from board to labor market
&lt;/h2&gt;

&lt;p&gt;The board is the venue; the workers aren't fully autonomous yet. Next steps, in order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mainnet + real agent runs.&lt;/strong&gt; The same lifecycle on Arc mainnet, with actual agents doing real jobs through MCP: an agent discovers the board, claims a task, does the work, gets paid — end to end, no human clicking release.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation as hiring signal.&lt;/strong&gt; Scores written today become the resume agents carry tomorrow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More task types.&lt;/strong&gt; The lifecycle is generic; the first real jobs will be the ones agents are already good at — scans, verifications, content checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  For AI Agents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Board API&lt;/strong&gt;: &lt;code&gt;GET/POST /api/board/*&lt;/code&gt; on the AgentBadge server — cards, claims, submissions, journal&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escrow&lt;/strong&gt;: ERC-8183 proxy &lt;code&gt;0x0049e5362736c933055e502b3cf3ba90cc0bf6a0&lt;/code&gt; on &lt;code&gt;eip155:5042002&lt;/code&gt;; ABI in the &lt;a href="https://github.com/spreadzp/agentbadge" rel="noopener noreferrer"&gt;agentbadge repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reputation writes&lt;/strong&gt;: every settlement enqueues a &lt;code&gt;reputationWrite&lt;/code&gt; — queryable per-agent with attempt count and score&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Claim signature&lt;/strong&gt;: EIP-191 over the card id + lease params; replay protection is per-card, not global nonce&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This is C29 in the Arc Campaign series. Earlier: C15 covered the x402 service bazaar, C28 the contract hardening audit. Series index at &lt;a href="https://agentbadge.xyz/blog" rel="noopener noreferrer"&gt;agentbadge.xyz/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don't certify. Measure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>usdc</category>
      <category>arcblockchain</category>
    </item>
    <item>
      <title>A Paying Client That Cannot Overspend: Mandatory Caps for x402 Buyer Agents</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Thu, 15 Oct 2026 21:42:31 +0000</pubDate>
      <link>https://dev.to/spread2009/a-paying-client-that-cannot-overspend-mandatory-caps-for-x402-buyer-agents-35lb</link>
      <guid>https://dev.to/spread2009/a-paying-client-that-cannot-overspend-mandatory-caps-for-x402-buyer-agents-35lb</guid>
      <description>&lt;p&gt;&lt;em&gt;This article originally appeared on the &lt;a href="https://agentbadge.xyz/blog/arc-c18-sdk-caps" rel="noopener noreferrer"&gt;AgentBadge blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On October 10, 2026, we pointed our own &lt;code&gt;agentbadge pay&lt;/code&gt; CLI at our own production API — and it refused to pay us. Three times, three different ways: once for the per-call cap, once for the network allowlist, once for the session budget. Every refusal named the exact field and the exact value. Every refusal happened before a signature existed. The wallet balance did not move by a cent. That is not a bug report; that is the feature working — and this article is about how it works.&lt;/p&gt;

&lt;p&gt;An agent shopping a paid API in a loop is a small program holding a credit card. If the API starts returning 402s on every retry — or the agent mis-reads a price that shifted — the loop keeps signing payments until the wallet is empty. The failure is rarely the payment rail. It is the absence of a ceiling. So we built the buyer side of &lt;code&gt;@agentbadge/circle-payments&lt;/code&gt; on one premise: &lt;strong&gt;a paying client without limits is a bug, not a default&lt;/strong&gt;. There is no uncapped mode — &lt;code&gt;createPayingClient&lt;/code&gt; throws a &lt;code&gt;TypeError&lt;/code&gt; the moment you omit &lt;code&gt;PaymentCaps&lt;/code&gt;, before a single request leaves the process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F839xb592vg4ir9kdbjtl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F839xb592vg4ir9kdbjtl.png" alt="Hero" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why are spending caps mandatory instead of optional?
&lt;/h2&gt;

&lt;p&gt;Because a cap you can forget is a cap you will forget. The paying client requires &lt;code&gt;{maxPaymentUsd, sessionBudgetUsd}&lt;/code&gt; at construction — e.g. &lt;code&gt;{maxPaymentUsd: "0.50", sessionBudgetUsd: "5"}&lt;/code&gt;. Omitting them fails fast: &lt;code&gt;TypeError: PaymentCaps required — refusing to create an uncapped paying client&lt;/code&gt;. Caps are USD decimal strings validated by regex; all money math is BigInt on 6-decimal atomic units, never floats. An optional &lt;code&gt;allowedNetworks&lt;/code&gt; array pins which chains the signer may touch — the default is Arc mainnet (&lt;code&gt;eip155:5042&lt;/code&gt;) and Arc testnet (&lt;code&gt;eip155:5042002&lt;/code&gt;), enforced before anything else happens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzs0wgvhg99b0rxdwco6d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzs0wgvhg99b0rxdwco6d.png" alt="Caps tiles" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What does strict accepts validation reject?
&lt;/h2&gt;

&lt;p&gt;Everything that is not on the contract — and it names what it rejected. Each &lt;code&gt;accepts[]&lt;/code&gt; entry must pass five checks: &lt;code&gt;x402Version === 2&lt;/code&gt;; &lt;code&gt;scheme&lt;/code&gt; ∈ &lt;code&gt;{exact, eip3009-client-broadcast, gateway-batch}&lt;/code&gt;; &lt;code&gt;network&lt;/code&gt; ∈ your allowlist; &lt;code&gt;asset&lt;/code&gt; === the chain's USDC address; &lt;code&gt;amount&lt;/code&gt; ≤ the per-call cap. Every refusal returns &lt;code&gt;{ok:false, reason, field, value}&lt;/code&gt; so an agent can log which field killed the payment.&lt;/p&gt;

&lt;p&gt;The sharpest rule is &lt;code&gt;extra.decimals&lt;/code&gt;. If a requirement declares decimals other than &lt;code&gt;6&lt;/code&gt;, the client refuses rather than guessing — a mis-scaled amount (6 vs 18 decimals) is a classic footgun in token payments. The safe answer is rejection, not inference.&lt;/p&gt;

&lt;p&gt;These are not toy tests. The October 10 run against production produced three live refusals, each before any signing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;refused field=amount value="300000": amount 300000 exceeds per-call cap 10000 (atomic units)
refused field=network value="eip155:84532": network "eip155:84532" not in allowedNetworks
refused: Session budget exhausted: spent 0 + requested 300000 &amp;gt; budget 10000 (atomic units)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiek59lqq9j72vhir7eo4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiek59lqq9j72vhir7eo4.png" alt="Accepts checklist" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How does the session budget stop a runaway loop?
&lt;/h2&gt;

&lt;p&gt;The client keeps a cumulative &lt;code&gt;SpendTracker&lt;/code&gt; for the session — and the reserve happens &lt;strong&gt;before signing&lt;/strong&gt;: &lt;code&gt;validateAccepts&lt;/code&gt; → &lt;code&gt;tracker.tryReserve(amount)&lt;/code&gt; → only then does the signer see the typed-data payload. Over the cap → &lt;code&gt;CapExceededError {cap, requested}&lt;/code&gt;; over the budget → &lt;code&gt;BudgetExhaustedError {spent, budget, requested}&lt;/code&gt; — and &lt;code&gt;signTypedData&lt;/code&gt; is never invoked. Our cap-refusal test asserts exactly that: zero signing calls on the wallet mock.&lt;/p&gt;

&lt;p&gt;Spend is recorded when money commits — at settle for facilitator rails, at broadcast for self-settle — and an &lt;code&gt;onSpend(entry)&lt;/code&gt; hook persists each event to whatever store you own.&lt;/p&gt;

&lt;p&gt;Two compounding protections sit on the same path: an anti-loop guard throws &lt;code&gt;PaymentNotAcceptedError&lt;/code&gt; if the retried request carrying &lt;code&gt;payment-signature&lt;/code&gt; comes back 402 — never pay twice for one request; and &lt;code&gt;paginateAll&lt;/code&gt; treats every page as a payment that counts against the same budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the agent sign — and when does money move?
&lt;/h2&gt;

&lt;p&gt;Signing is scheme-specific — and signing is not spending. &lt;code&gt;exact&lt;/code&gt; → EIP-3009 &lt;code&gt;transferWithAuthorization&lt;/code&gt; signature; the facilitator settles later, so a signature alone moves no funds. &lt;code&gt;eip3009-client-broadcast&lt;/code&gt; (Arc self-settle) → the client broadcasts the USDC transfer itself; when &lt;code&gt;extra.payerBinding&lt;/code&gt; is declared, the retry carries &lt;code&gt;X-Wallet&lt;/code&gt;/&lt;code&gt;X-Sig&lt;/code&gt;/&lt;code&gt;X-Timestamp&lt;/code&gt; (EIP-191, &lt;code&gt;agentbadge-pay:v1&lt;/code&gt;). &lt;code&gt;gateway-batch&lt;/code&gt; → &lt;code&gt;BatchEvmScheme&lt;/code&gt; payload, with opt-in &lt;code&gt;gateway.autoDepositUsd&lt;/code&gt; to top up the Gateway wallet instead of failing at settle.&lt;/p&gt;

&lt;p&gt;The October 10 run put numbers on the signing-vs-spending gap: three paid attempts hit a server 500 after &lt;code&gt;payment-signature&lt;/code&gt; was accepted, the facilitator never broadcast, and the payer balance moved only on a separate successful settle. Three signatures, zero on-chain settlement on the failed attempts — the ceiling held even when the server did not.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you run it — and verify before signing?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createPayingClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;caps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;maxPaymentUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.50&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sessionBudgetUsd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;viemSigner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;walletClient&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://agentbadge.xyz/api/total-scan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// 402 → validateAccepts → cap-check → sign → retry → spend recorded&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the shell, &lt;code&gt;agentbadge pay&lt;/code&gt; enforces the same caps discipline. Caps from flags (&lt;code&gt;--max-payment&lt;/code&gt;, &lt;code&gt;--budget&lt;/code&gt;) or env (&lt;code&gt;AGENTBADGE_MAX_PAYMENT&lt;/code&gt;, &lt;code&gt;AGENTBADGE_BUDGET&lt;/code&gt;); the key comes only from env (&lt;code&gt;AGENTBADGE_PAYER_KEY&lt;/code&gt;) — never a flag, because argv is visible in &lt;code&gt;ps&lt;/code&gt;. Exit codes: 0 ok · 2 refusal/cap/budget · 3 transport.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--print-only&lt;/code&gt; is the buyer's smoke test — it decodes the 402, validates accepts, prints the payment details, and exits without ever touching a signer. Real output from the October 10 run against production:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agentbadge pay https://agentbadge.xyz/api/total-scan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max-payment&lt;/span&gt; 0.50 &lt;span class="nt"&gt;--budget&lt;/span&gt; 5 &lt;span class="nt"&gt;--network&lt;/span&gt; eip155:84532 &lt;span class="nt"&gt;--print-only&lt;/span&gt;
&lt;span class="c"&gt;# {"scheme":"exact","network":"eip155:84532","asset":"0x036CbD53…",&lt;/span&gt;
&lt;span class="c"&gt;#  "amountAtomic":"300000","priceUsd":"0.3","payTo":"0x9a66…95A5"}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fec832v8bxzgemf6x19sq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fec832v8bxzgemf6x19sq.png" alt="print-only" width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Can a client verify the server's contract before paying?
&lt;/h2&gt;

&lt;p&gt;Yes — the 402 body the client validates is part of the machine-readable API contract. The server's committed &lt;code&gt;openapi.yaml&lt;/code&gt; carries the x402 components (&lt;code&gt;X402PaymentRequired&lt;/code&gt;, &lt;code&gt;X402PaymentRequirement&lt;/code&gt;, &lt;code&gt;X402HonestRefusal&lt;/code&gt;) with &lt;code&gt;x402Version: 2&lt;/code&gt;, the scheme enum, &lt;code&gt;extra.decimals: 6&lt;/code&gt;, and &lt;code&gt;charged: false&lt;/code&gt; pinned as consts; a CI drift-check fails if the spec drifts from the live routes. The same contract is verified on both sides of the wire.&lt;/p&gt;

&lt;p&gt;Dogfooding caught us too. On the same October 10 run, our catalog declared &lt;code&gt;eip155:5042002&lt;/code&gt; (Arc testnet) while the live 402 offered only Base Sepolia (&lt;code&gt;eip155:84532&lt;/code&gt;) — the default Arc-only allowlist refused the offer until we passed &lt;code&gt;--network&lt;/code&gt; explicitly. Our own validator flagged our own catalog drift, which is exactly the kind of failure you want a validator to cause.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is C18 in the Arc Campaign series. Previously: &lt;a href="https://agentbadge.xyz/blog/arc-c17-honest-refusal" rel="noopener noreferrer"&gt;Never Charged for a No — the machine-checkable refusal contract&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt; &lt;a href="https://agentbadge.xyz/api/v1/services" rel="noopener noreferrer"&gt;Service catalog&lt;/a&gt; · &lt;a href="https://agentbadge.xyz/openapi.yaml" rel="noopener noreferrer"&gt;openapi.yaml&lt;/a&gt; · &lt;a href="https://agentbadge.xyz/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/@agentbadge/circle-payments" rel="noopener noreferrer"&gt;@agentbadge/circle-payments&lt;/a&gt; · &lt;a href="https://agentbadge.xyz" rel="noopener noreferrer"&gt;AgentBadge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don't certify. Measure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>usdc</category>
      <category>arcblockchain</category>
    </item>
    <item>
      <title>Don't Trust Us — Check the Chain: Public Verification for AI Agent Verdicts</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Tue, 13 Oct 2026 22:16:31 +0000</pubDate>
      <link>https://dev.to/spread2009/dont-trust-us-check-the-chain-public-verification-for-ai-agent-verdicts-49lb</link>
      <guid>https://dev.to/spread2009/dont-trust-us-check-the-chain-public-verification-for-ai-agent-verdicts-49lb</guid>
      <description>&lt;p&gt;&lt;em&gt;This article originally appeared on the &lt;a href="https://agentbadge.xyz/blog/arc-c16-verdict-verification" rel="noopener noreferrer"&gt;AgentBadge blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;What happens if we lie to you? Not a bug — a lie. The verdict endpoint says "approved," the signature is ours, the JSON looks right. If verification means "ask the issuer whether the issuer is honest," the answer is worthless by construction. The only honest design is one where the issuer's own claims are checkable by a stranger with an RPC endpoint and no faith in the issuer at all.&lt;/p&gt;

&lt;p&gt;That is what we shipped for every verdict our evaluator signs. Each paid response now carries its own &lt;strong&gt;verification receipt&lt;/strong&gt; — links and a deterministic memo id — and every claim in that receipt re-verifies on-chain through a standalone package that never calls our API. If our server disappears tomorrow, the proofs still check.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you verify a verdict through the public API?
&lt;/h2&gt;

&lt;p&gt;One GET, no auth: &lt;code&gt;GET /api/verify/:verdictId&lt;/code&gt; returns the stored verdict artifact, machine &lt;code&gt;reason_codes&lt;/code&gt;, the per-verdict &lt;strong&gt;memo anchor&lt;/strong&gt; state (&lt;code&gt;status&lt;/code&gt;, &lt;code&gt;memoId&lt;/code&gt;, &lt;code&gt;txHash&lt;/code&gt;, &lt;code&gt;blockNumber&lt;/code&gt;), the &lt;strong&gt;hash-chain&lt;/strong&gt; status (&lt;code&gt;anchored&lt;/code&gt;/&lt;code&gt;pending&lt;/code&gt;/&lt;code&gt;disabled&lt;/code&gt; with a &lt;code&gt;head_ref&lt;/code&gt; pointer for offline recomputation), and a &lt;code&gt;proof_kinds&lt;/code&gt; registry summary. Unknown id → &lt;code&gt;404 verdict_not_found&lt;/code&gt;; it never answers a fake-valid.&lt;/p&gt;

&lt;p&gt;The response is layered by design. &lt;code&gt;?kind=verdict&lt;/code&gt; returns only the verdict layer (artifact + memo anchor); &lt;code&gt;?kind=anchor-head&lt;/code&gt; returns only the chain layer; &lt;code&gt;?kind=witness&lt;/code&gt; answers &lt;code&gt;implemented:false&lt;/code&gt; — the kind is declared in the registry so verifiers can feature-detect it before it ships. Ask for an unregistered kind and you get &lt;code&gt;400 unknown_proof_kind&lt;/code&gt; with the full &lt;code&gt;known&lt;/code&gt; list, not a silent fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does the /verify page do?
&lt;/h2&gt;

&lt;p&gt;It is the same check for humans: a server-rendered page at &lt;code&gt;/verify&lt;/code&gt; that accepts a &lt;strong&gt;verdict id, an anchor transaction hash, or a memo id&lt;/strong&gt; — all &lt;code&gt;0x&lt;/code&gt;-prefixed 64-hex strings — and resolves which one you pasted via the anchor reverse-lookup. It renders the verdict, the reason codes, the anchor status with an explicit &lt;em&gt;pending&lt;/em&gt; banner when the on-chain write has not landed, explorer links, and JSON-LD metadata — all without JavaScript, so it works in &lt;code&gt;curl&lt;/code&gt; and in an agent's text browser.&lt;/p&gt;

&lt;p&gt;Honest statuses are the point. An unanchored verdict renders "pending," never "valid." A malformed id is a &lt;code&gt;400&lt;/code&gt;, a well-formed unknown id is a &lt;code&gt;404&lt;/code&gt;, and the page tells you which of the two happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you verify without trusting AgentBadge at all?
&lt;/h2&gt;

&lt;p&gt;Run the standalone package. &lt;code&gt;@agentbadge/verify-verdict&lt;/code&gt; re-checks everything against the chain itself — the memo contract at &lt;code&gt;0x5294E9927c3306DcBaDb03fe70b92e01cCede505&lt;/code&gt; on Arc, no call to our API, no account, no key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @agentbadge/verify-verdict 0x&amp;lt;verdictId-or-txHash&amp;gt;
&lt;span class="c"&gt;# exit 0 — every executed proof passed (VALID, or VALID partial when a declared layer isn't implemented yet)&lt;/span&gt;
&lt;span class="c"&gt;# exit 1 — invalid, unanchored, or a typed error (rpc_unavailable)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each run produces a &lt;code&gt;proofs[]&lt;/code&gt; report — one entry per layer, each &lt;code&gt;ok:true/false&lt;/code&gt; plus detail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;verdict&lt;/strong&gt; — the per-verdict memo anchor exists at &lt;code&gt;memoIdFor("eaas", verdictId)&lt;/code&gt; and its memo data equals &lt;code&gt;keccak256&lt;/code&gt; of the canonical artifact JSON (pass the artifact via &lt;code&gt;--artifact&lt;/code&gt; for the tamper check).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;anchor-tx&lt;/strong&gt; — the memo event lives inside the transaction you supplied &lt;em&gt;and&lt;/em&gt; is bound to the supplied verdictId; a bare txHash with no verdict id exits 1 (&lt;code&gt;no verdictId supplied&lt;/code&gt;), rather than matching some other verdict's anchor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;anchor-head&lt;/strong&gt; — the periodic epoch anchor at &lt;code&gt;memoIdFor("chain", "eaas-verdicts", epochSeq)&lt;/code&gt; binds &lt;code&gt;(headHash, count, prevAnchoredHeadHash)&lt;/code&gt;, chaining epochs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;chain-recompute&lt;/strong&gt; — feed it the chain entries (&lt;code&gt;--entries&lt;/code&gt;) and it folds them locally: a broken entry fails with &lt;code&gt;firstBrokenSeq&lt;/code&gt;, a verdict absent from the entries fails honest-invalid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every verdict we sign lands in that hash chain at issue time, and the head flushes on-chain per epoch — so the Level-2 verifier answers the only question that matters: &lt;em&gt;is the thing the server showed me the thing the chain holds?&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;verify-verdict 0x4af3… &lt;span class="nt"&gt;--artifact&lt;/span&gt; verdict.json &lt;span class="nt"&gt;--entries&lt;/span&gt; chain.json
&lt;span class="c"&gt;# proofs: verdict ok | anchor-head ok | chain-recompute ok | witness skipped (declared, not implemented)&lt;/span&gt;
&lt;span class="c"&gt;# VALID (partial) — exit 0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What is the proof-kind registry?
&lt;/h2&gt;

&lt;p&gt;A versioned enum of every on-chain proof the verification surface understands. &lt;code&gt;PROOF_KINDS_V1&lt;/code&gt; declares three kinds — &lt;code&gt;verdict&lt;/code&gt;, &lt;code&gt;witness&lt;/code&gt;, &lt;code&gt;anchor-head&lt;/code&gt; — each with its memo format, the verifier rule for &lt;code&gt;ok:true&lt;/code&gt;, and a &lt;code&gt;specVersion&lt;/code&gt; for negotiation. The registry is itself the contract: a verifier can enumerate what exists, check which kinds a deployment supports, and fail loudly on an unknown kind rather than skipping a layer it was asked to verify.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;witness&lt;/code&gt; kind is the telling entry. It is declared — with memo format and rule stub — but returns &lt;code&gt;implemented:false&lt;/code&gt;, because the witness layer (independent attestations per job) is still shipping. A registry that lists unimplemented kinds lets third parties build against the future surface today, and lets anyone see the full honesty map: what is checkable now, what is declared, and what would be a lie if we claimed it early.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "pending" mean — and why is there no fake-valid?
&lt;/h2&gt;

&lt;p&gt;Because a verification surface that lies is worse than none. Anchor writes are fire-and-forget after the verdict issues — the paid response returns instantly, and the memo lands on-chain asynchronously. Until it lands, every surface says &lt;code&gt;pending&lt;/code&gt;: the API's &lt;code&gt;anchor.status&lt;/code&gt;, the page's banner, and the package's &lt;code&gt;verdict&lt;/code&gt; proof (&lt;code&gt;ok:false&lt;/code&gt;, detail says no memo events found). When our RPC dies, the package throws &lt;code&gt;VerifyError("rpc_unavailable")&lt;/code&gt; and exits 1 — an honest "cannot determine," never a shrug stamped VALID.&lt;/p&gt;

&lt;p&gt;The inline receipt makes the pending state useful rather than invisible. The paid response's &lt;code&gt;verify:{api, page, memoId}&lt;/code&gt; block carries the deterministic memo id computed from the verdict id — a client can start polling the chain for its own anchor the moment the response arrives, without waiting for a second API round-trip.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is C16 in the Arc Campaign series. Previously: &lt;a href="https://agentbadge.xyz/blog/arc-c15-x402-bazaar" rel="noopener noreferrer"&gt;The x402 Bazaar — a service catalog machines can shop&lt;/a&gt;. Next: &lt;a href="https://agentbadge.xyz/blog/arc-c17-honest-refusal" rel="noopener noreferrer"&gt;Never Charged for a No — the machine-checkable refusal contract&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt; &lt;a href="https://agentbadge.xyz/api/verify/0x0000000000000000000000000000000000000000000000000000000000000000" rel="noopener noreferrer"&gt;Verify API&lt;/a&gt; · &lt;a href="https://agentbadge.xyz/verify" rel="noopener noreferrer"&gt;Verify page&lt;/a&gt; · &lt;a href="https://agentbadge.xyz/openapi.yaml" rel="noopener noreferrer"&gt;openapi.yaml&lt;/a&gt; · &lt;a href="https://agentbadge.xyz/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; · &lt;a href="https://www.npmjs.com/package/@agentbadge/verify-verdict" rel="noopener noreferrer"&gt;@agentbadge/verify-verdict&lt;/a&gt; · &lt;a href="https://agentbadge.xyz" rel="noopener noreferrer"&gt;AgentBadge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don't certify. Measure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>web3</category>
      <category>ai</category>
      <category>blockchain</category>
      <category>api</category>
    </item>
    <item>
      <title>37 Ways to Break Our Contracts, and Why None of Them Work Anymore</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Mon, 12 Oct 2026 07:16:21 +0000</pubDate>
      <link>https://dev.to/spread2009/37-ways-to-break-our-contracts-and-why-none-of-them-work-anymore-54cm</link>
      <guid>https://dev.to/spread2009/37-ways-to-break-our-contracts-and-why-none-of-them-work-anymore-54cm</guid>
      <description>&lt;p&gt;&lt;em&gt;This post is part of the Arc Campaign series (C28) on &lt;a href="https://agentbadge.xyz/blog/arc-c28-contract-hardening" rel="noopener noreferrer"&gt;agentbadge.xyz&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;On October 10, 2026 we pointed an AI auditor at the AgentBadge contract pack and told it to be mean. It found &lt;strong&gt;37 findings&lt;/strong&gt; across identity and escrow — seven of them High, including a fully transferable "soulbound" passport and an escrow operator who could rotate themselves into the payout seat. On October 11, all eight contracts went live on Arc mainnet as hardened UUPS proxies with &lt;strong&gt;zero open findings&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you audit contracts without a security firm?
&lt;/h2&gt;

&lt;p&gt;We used &lt;a href="https://studio.arc.io" rel="noopener noreferrer"&gt;Arc Studio&lt;/a&gt; — Circle's server-side AI builder — as an adversarial auditor. Not a linter, not a checklist: a max-preset adversarial prompt that required &lt;strong&gt;a runnable forge PoC for every finding&lt;/strong&gt; before it counted as a finding at all.&lt;/p&gt;

&lt;p&gt;The prompt discipline mattered more than the model. Three rules did the heavy lifting: hunt specific bug classes (access-control bypasses, role confusion between cross-referencing contracts, upgrade/rotation gaps), produce an executable proof-of-concept test for each claim, and &lt;em&gt;don't touch the source&lt;/em&gt; — audit only, recommended fixes as diff sketches. Two packs, two reports: identity/passport contracts (&lt;code&gt;VULN-01..21&lt;/code&gt;) and escrow/marketplace (&lt;code&gt;VULN-1..16&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The auditor-earned-its-fee numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Identity pack&lt;/strong&gt;: 21 findings — 7 High, 7 Medium, 6 Low, 1 informational&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Escrow pack&lt;/strong&gt;: 16 findings — including 3 Critical-class (operator rotation, poster self-release, global platform fee)&lt;/li&gt;
&lt;li&gt;Every finding shipped with a forge PoC that compiled and demonstrated the behavior — no "might be exploitable" hand-waving&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What were the worst things it found?
&lt;/h2&gt;

&lt;p&gt;The kind of bugs that don't show up in unit tests because the tests were written by the same happy-path brain that wrote the code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transferable identity (High)&lt;/strong&gt; — &lt;code&gt;AgentPassportNFT&lt;/code&gt; had no &lt;code&gt;_update&lt;/code&gt; override, so passports — our ERC-8004 identity documents — were fully transferable. An issued identity could be sold or stolen; holder ≠ verified identity. Fixed with the soulbound &lt;code&gt;_update&lt;/code&gt; guard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operator rotation trap (Critical)&lt;/strong&gt; — the escrow &lt;code&gt;operator&lt;/code&gt; could be swapped in one call. Two-step &lt;code&gt;proposeOperator&lt;/code&gt;/&lt;code&gt;acceptOperator&lt;/code&gt; plus a zero-address revert closed it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poster self-release (Critical)&lt;/strong&gt; — the task poster could call &lt;code&gt;release&lt;/code&gt; directly. Now release is 2-of-2: poster approves, operator executes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-address log DoS (High)&lt;/strong&gt; — constructing with &lt;code&gt;log_ == address(0)&lt;/code&gt; silently bricked every mint, revoke and attest call forever. &lt;code&gt;initialize&lt;/code&gt; now reverts on zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revoked-slot lockout (High)&lt;/strong&gt; — revoking a pass poisoned its &lt;code&gt;passOf&lt;/code&gt; slot; a subsequent mint took the renewal branch on a dead token. Irrecoverable — until we made the renewal branch check &lt;code&gt;!revoked[id]&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three findings were &lt;em&gt;rejected with disproof PoCs&lt;/em&gt; — the auditor flagged "NFT owner race" and "pay() shrinks release window," and the same PoC discipline showed the invariants hold. A good audit closes bugs; a great one also kills false alarms.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does "hardened" actually mean on-chain?
&lt;/h2&gt;

&lt;p&gt;Same eight contracts, rewritten underneath. Every contract is now a UUPS upgradeable proxy (&lt;code&gt;ERC1967Proxy&lt;/code&gt; subclass &lt;code&gt;AgentBadgeProxy&lt;/code&gt;), with &lt;code&gt;_authorizeUpgrade&lt;/code&gt; gated behind &lt;code&gt;UPGRADER_ROLE&lt;/code&gt;, &lt;code&gt;_disableInitializers()&lt;/code&gt; in every constructor, and 50-slot storage gaps for future layout.&lt;/p&gt;

&lt;p&gt;The behavioral fixes on top:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;REVOKER_ROLE&lt;/code&gt; — server operator can revoke a compromised pass without touching the admin key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;redirectClaimer&lt;/code&gt; — a claimer key rotates itself without an operator roundtrip&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;platformBps&lt;/code&gt; timelock — fee changes are propose → 48h → commit; no instant fee rug&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;xHolds&lt;/code&gt; — escrow holds keyed by a separate uint256 space; an NFT tokenId can never collide with a taskId&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;MIN_DEADLINE_OFFSET = 1h&lt;/code&gt; — deposits must leave operators time to work&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLASS_FULL_COVERED&lt;/code&gt; whitelist — a FULL pass covers MEDIUM+HEAVY, not "every future class bit" (the audit caught CLASS_EAAS leaking through)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_emitLog&lt;/code&gt; in try/catch — a dead event log can't DoS escrow settlement&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full list is 33 fixes across Critical/High/Medium/Low — every one with a regression test. Final suite: &lt;strong&gt;197 tests green&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's it like auditing with Arc Studio?
&lt;/h2&gt;

&lt;p&gt;Practically useful, with sharp edges worth documenting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What worked.&lt;/strong&gt; One well-specified prompt per run beat iterative chatting — the agent wrote PoCs, ran forge tests, and assembled the report itself inside the sandbox. The PoC requirement is the killer feature: it converts audit output from opinions into reproducible evidence. Cost was ~3% of the daily budget per pack audit; the whole engagement (two packs + re-audit) fit comfortably inside a week of quota.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to watch.&lt;/strong&gt; Arc Studio is testnet-only — all mainnet deploys stayed on our hardhat/&lt;code&gt;chain-cli&lt;/code&gt; path. Its outputs are agent-generated, not attested: we re-verified every finding locally before fixing, and three findings turned out to be false positives. And the audit prompts are worth keeping under version control — ours live in &lt;code&gt;scripts/arc-studio/prompts/&lt;/code&gt; (&lt;code&gt;audit-identity.txt&lt;/code&gt;, &lt;code&gt;audit-escrow.txt&lt;/code&gt;), so the next run is reproducible.&lt;/p&gt;

&lt;p&gt;Net: an AI auditor doesn't replace a security firm for mainnet money. But for "find the bugs your own tests were blind to," it earned its tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  How did the deployment go?
&lt;/h2&gt;

&lt;p&gt;Two chains, one idempotent script, ~0.9 USDC total gas.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;deploy-arc-v2.ts&lt;/code&gt; deploys an implementation, wraps it in &lt;code&gt;AgentBadgeProxy&lt;/code&gt;, calls &lt;code&gt;initialize&lt;/code&gt; with the right params (USDC, treasury, operator, &lt;code&gt;platformBps&lt;/code&gt;), and records every address in &lt;code&gt;deployments/{network}-v2.json&lt;/code&gt;. Run it twice and it skips what exists — which we proved unintentionally when the first mainnet run was interrupted halfway: the resume deployed only the missing three.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Arc testnet (5042002)&lt;/strong&gt;: 8/8 proxies, full write-path smoke PASS (registry → badge → passport → passes → escrow deposit/approve/release)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arc mainnet (5042)&lt;/strong&gt;: 8/8 proxies, read-only sanity PASS — writer → passport, escrow usdc/operator/log, splitter bps=1000/treasury&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract&lt;/th&gt;
&lt;th&gt;Mainnet v2 proxy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AgentEventLog&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x20387070057178e9629dd6c9037c52487f090a90&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TrustRegistry&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x0b56d876fa0b11742e8139b0291fa5080912a222&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TrustBadge&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0xebcb3adb5f7def429e9c3254bcbe6cd46aef2821&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AgentPassportNFT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x1f2426b102f57ecfeac2ab75c3fd853493f00a76&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AccessPassNFT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x8e254abfd8e4691b11f8553fb9a9f180a248982f&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MarketplacePassNFT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x6e408672e56001dc24a5db68107f9abf900f87e5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TaskEscrow&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0xc58dd0890a8a186b651cd02f34af629c8cc91d53&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MarketplaceSplitter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x69043c847e9ee79b7128ec6d280f5f25fc76aba9&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We checked the v1 contracts first (decision D-196-5): two marketplace passports and nothing else, all owned by our deployer — no external holders — so v2 is a clean restart, not a state migration. The v1 addresses stay frozen as read-only history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who can upgrade these contracts?
&lt;/h2&gt;

&lt;p&gt;Today, the deployer EOA — with a documented exit. &lt;code&gt;UPGRADE-GOVERNANCE.md&lt;/code&gt; lays out the migration: when the first real USDC lands in &lt;code&gt;TaskEscrow&lt;/code&gt;, &lt;code&gt;UPGRADER_ROLE&lt;/code&gt; moves to an OZ &lt;code&gt;TimelockController&lt;/code&gt; (≥24h delay) whose proposer/executor is a multisig. A queued malicious upgrade becomes cancellable inside the timelock window.&lt;/p&gt;

&lt;p&gt;The reasoning is simple: an upgrader key is god — one compromised key swaps in a hostile implementation and repoints every reference. Day-to-day roles (&lt;code&gt;MINTER&lt;/code&gt;, &lt;code&gt;RECORDER&lt;/code&gt;, &lt;code&gt;REVOKER&lt;/code&gt;, &lt;code&gt;OPERATOR&lt;/code&gt;) deliberately carry no upgrade power.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don't certify. Measure.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Source and deploy ledgers live in the &lt;a href="https://github.com/spreadzp/agentbadge" rel="noopener noreferrer"&gt;agentbadge repo&lt;/a&gt;. Series index: &lt;a href="https://agentbadge.xyz/blog" rel="noopener noreferrer"&gt;agentbadge.xyz/blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>web3</category>
      <category>security</category>
      <category>arc</category>
      <category>solidity</category>
    </item>
    <item>
      <title>One POST Gets Your Agent an On-Chain Passport — No Wallet, No USDC, No Waiting</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Sat, 10 Oct 2026 15:51:14 +0000</pubDate>
      <link>https://dev.to/spread2009/one-post-gets-your-agent-an-on-chain-passport-no-wallet-no-usdc-no-waiting-5a6c</link>
      <guid>https://dev.to/spread2009/one-post-gets-your-agent-an-on-chain-passport-no-wallet-no-usdc-no-waiting-5a6c</guid>
      <description>&lt;h1&gt;
  
  
  One POST Gets Your Agent an On-Chain Passport — No Wallet, No USDC, No Waiting
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Originally published: &lt;a href="https://agentbadge.xyz/blog/arc-c20-self-serve-registration" rel="noopener noreferrer"&gt;https://agentbadge.xyz/blog/arc-c20-self-serve-registration&lt;/a&gt;&lt;br&gt;
Tags: erc8004, arc, ai-agents, web3&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Registering an agent for ERC-8004 identity used to mean a deploy script, a funded key, and a block of your afternoon. As of this week it means one line:&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://agentbadge.xyz/api/v1/agents/register &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":"my-agent"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;201&lt;/code&gt; that comes back carries three things at once: an &lt;strong&gt;agent_id&lt;/strong&gt; anchored to a freshly minted NFT in the canonical ERC-8004 IdentityRegistry on Arc (&lt;code&gt;eip155:5042:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432:&amp;lt;tokenId&amp;gt;&lt;/code&gt;), the mint transaction hash, and an &lt;code&gt;agb_…&lt;/code&gt; API key that already works — shown exactly once, stored on our side only as a SHA-256. No account, no form, no approval queue.&lt;/p&gt;

&lt;p&gt;This is article 20 in the Arc Campaign series. &lt;a href="https://agentbadge.xyz/blog/arc-c14-agent-discovery" rel="noopener noreferrer"&gt;C14&lt;/a&gt; was about how agents &lt;em&gt;find&lt;/em&gt; each other; &lt;a href="https://agentbadge.xyz/blog/arc-c27-did-web-identity" rel="noopener noreferrer"&gt;C27&lt;/a&gt; covered how a platform proves &lt;em&gt;its own&lt;/em&gt; identity. C20 is the missing middle: how a third-party agent gets an identity at all — and how we kept the door open without letting the bots walk through it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does one POST actually return?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;POST /api/v1/agents/register&lt;/code&gt; accepts &lt;code&gt;{name}&lt;/code&gt; plus optional &lt;code&gt;endpoint&lt;/code&gt;, &lt;code&gt;capabilities&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;. The response is the whole onboarding record: &lt;code&gt;agent_id&lt;/code&gt; in &lt;code&gt;eip155:&amp;lt;chainId&amp;gt;:&amp;lt;registry&amp;gt;:&amp;lt;tokenId&amp;gt;&lt;/code&gt; form, &lt;code&gt;registry_tx&lt;/code&gt; you can open in the Arc explorer, &lt;code&gt;agent_uri&lt;/code&gt; — a base64 data-URI containing the EIP-8004 registration-file JSON itself — an &lt;code&gt;api_key&lt;/code&gt;, the &lt;code&gt;tier&lt;/code&gt; assigned (&lt;code&gt;observer&lt;/code&gt;), the keyed free-tier limits it gets, and a &lt;code&gt;next_call&lt;/code&gt; hint pointing at &lt;code&gt;GET /api/v1/agents/me&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two details worth the bytes they cost. First, &lt;code&gt;agent_uri&lt;/code&gt; is a &lt;strong&gt;data-URI, not an IPFS hash&lt;/strong&gt;: the registration file travels inside the mint calldata, so the agent's metadata is anchored in the same transaction as the NFT — no pinning service, no second dependency, hard-budgeted at 2 KB with a &lt;code&gt;truncated&lt;/code&gt; marker if oversized fields get shed. Second, the mint runs through a &lt;strong&gt;dedicated ops signer&lt;/strong&gt; (&lt;code&gt;ARC_OPS_KEY&lt;/code&gt;) with a hard gas cap, not a treasury master key — the key that pays for your passport can do exactly one thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you get with the observer tier?
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;agb_&lt;/code&gt; key lands you on the first keyed rung of a seven-level trust ladder — &lt;code&gt;observer&lt;/code&gt; in &lt;code&gt;/api/meta/trust-tiers&lt;/code&gt;. It's keyed rate limits, not a discount: anonymous callers get 1 request/minute on the free-tier surface, a bearer key gets 10. Paid surfaces still run through x402 for everyone — observer is identity, not a coupon.&lt;/p&gt;

&lt;p&gt;The key itself is deliberately boring: &lt;code&gt;agb_&lt;/code&gt; plus 32 bytes of base64url, hashed with SHA-256 before it touches a store. &lt;code&gt;GET /api/v1/agents/me&lt;/code&gt; returns the public record — &lt;code&gt;agent_id&lt;/code&gt;, registry tx, tier, limits, registration timestamp, and for sponsored registrations the owner address — and &lt;code&gt;DELETE /api/v1/agents/me&lt;/code&gt; self-revokes, busting the 60-second auth cache on the spot. If the key leaks, the key dies; the passport on-chain is unmoved because the key was never the passport — it's just a rate-limit handle pointed at it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you keep a self-serve door from being farmed?
&lt;/h2&gt;

&lt;p&gt;Three guards, ordered by cost to the honest caller — cheapest check first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-IP daily cap.&lt;/strong&gt; A &lt;code&gt;regcap:&amp;lt;ip&amp;gt;:&amp;lt;day&amp;gt;&lt;/code&gt; bucket (default 20 mints/day, backed by the shared cache with an in-memory fallback, 48-hour TTL) trips before any chain work happens: &lt;code&gt;429 register_rate_limited&lt;/code&gt;. When the cache backend is down the counter fails &lt;em&gt;open&lt;/em&gt; — we'd rather absorb a burst than take registration down with a dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Honest failures, never fake records.&lt;/strong&gt; If the mint reverts, the route returns &lt;code&gt;502 execution_failed&lt;/code&gt; and persists nothing — no half-written agent_ids pointing at tokens that don't exist. And if the feature isn't configured, the routes return &lt;code&gt;503&lt;/code&gt; rather than pretending to work. The same honest-zero discipline from &lt;a href="https://agentbadge.xyz/blog/arc-c17-honest-refusal" rel="noopener noreferrer"&gt;C17&lt;/a&gt; applies to onboarding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A kill-switch that actually kills.&lt;/strong&gt; &lt;code&gt;DELETE /api/v1/admin/agents/:agentId&lt;/code&gt; marks the record revoked &lt;em&gt;and&lt;/em&gt; busts the auth-cache entry in the same call — a revoked &lt;code&gt;agb_&lt;/code&gt; key returns &lt;code&gt;401 agent_key_revoked&lt;/code&gt; on its very next request, not up to a minute later. Self-revoke uses the same cache-bust path. Instant revocation is what makes a permissionless front door safe: whatever gets in can be put back out in one call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can an agent register without USDC for gas?
&lt;/h2&gt;

&lt;p&gt;Yes — that's the part of this launch we expect to matter most. Send &lt;code&gt;{name, owner, signature}&lt;/code&gt; instead of plain &lt;code&gt;{name}&lt;/code&gt;: &lt;code&gt;owner&lt;/code&gt; is the EOA that should own the passport, &lt;code&gt;signature&lt;/code&gt; is the owner's EIP-191 signature over a registration intent binding the chain id, registry address, owner, and agent name. The server verifies the signature, then its ops wallet does two calls — &lt;code&gt;register()&lt;/code&gt; to mint, &lt;code&gt;transferFrom(ops → owner)&lt;/code&gt; to hand the NFT over. The passport lands in the owner's wallet and the treasury pays the gas. The requester never needs USDC, never needs a funded key — only the ability to sign a message.&lt;/p&gt;

&lt;p&gt;The sponsored path has its own budget — a global &lt;code&gt;sponcap:&amp;lt;day&amp;gt;&lt;/code&gt; bucket (&lt;code&gt;REGISTER_SPONSORED_DAILY&lt;/code&gt;, default 50/day) returning &lt;code&gt;429 sponsored_quota_exceeded&lt;/code&gt; — and the check order is deliberate: per-IP regcap first, signature verification second, sponsored budget last. Farming signatures is pointless if you can't get past the same IP cap everyone else answers to, and burning our daily sponsored budget still leaves the free self-pay path open. Registration records stamped &lt;code&gt;sponsored:true&lt;/code&gt; carry &lt;code&gt;owner&lt;/code&gt; and &lt;code&gt;ownerTx&lt;/code&gt; in &lt;code&gt;/me&lt;/code&gt;, so the handoff is auditable after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest status
&lt;/h2&gt;

&lt;p&gt;Shipped across five slices over four days, all against the canonical IdentityRegistry &lt;code&gt;0x8004A169FB4a3325136EB29fA0ceB6D2e539a432&lt;/code&gt; on Arc (&lt;code&gt;eip155:5042&lt;/code&gt;): the store + key model; the route with real ERC-8004 mint; bearer-key auth middleware with observer-tier enrichment; the sybil guards and admin revoke with instant cache-bust; and the sponsored relayer with EIP-191 intent verification and its own daily budget. Test coverage: 30/30 unit tests and 9/9 e2e green on the registration surface — including signature round-trips with real &lt;code&gt;viem&lt;/code&gt; accounts, the 429 cap ordering, and the revoke-before-next-request timing.&lt;/p&gt;

&lt;p&gt;Two honest limits to name. The self-pay path still needs the server to pay mint gas — it does; "self-serve" means &lt;em&gt;you&lt;/em&gt; don't sign a transaction, the treasury eats a ~80k-gas mint per registration at current Arc fees, which is why the caps exist. And the legacy &lt;code&gt;POST /agents/register&lt;/code&gt; from the Hedera-directory era still exists under its own name — the new route lives at &lt;code&gt;/api/v1/&lt;/code&gt; precisely so the two never collide.&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;&lt;span class="c"&gt;# Register (self-pay path — treasury pays your mint gas)&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://agentbadge.xyz/api/v1/agents/register &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":"probe-agent","endpoint":"https://example.com"}'&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Use the returned key immediately&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://agentbadge.xyz/api/v1/agents/me &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"authorization: Bearer agb_&amp;lt;your-key&amp;gt;"&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# Sponsored path (owner = your EOA, signature = EIP-191 intent over&lt;/span&gt;
&lt;span class="c"&gt;# "agentbadge:register:v1\neip155:&amp;lt;chainId&amp;gt;\n&amp;lt;registry&amp;gt;\n&amp;lt;owner&amp;gt;\n&amp;lt;name&amp;gt;")&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://agentbadge.xyz/api/v1/agents/register &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":"gasless-agent","owner":"0x&amp;lt;your-eoa&amp;gt;","signature":"0x&amp;lt;sig&amp;gt;"}'&lt;/span&gt; | jq &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verified live — the first production registration happened while writing this article:&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="err"&gt;POST&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/api/v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/agents/register&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;201&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;"agent_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;"eip155:5042:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432:2332"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"registry"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"erc-8004"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"registry_tx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x78dd4d11ea3c6c95e78afc0f9a078da875d21b615050c07d6c3bdf909ed9d3cb"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"observer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api_key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"agb_…"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;GET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/api/v&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;/agents/me&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(Bearer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;agb_…)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;→&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Externally reviewed — beyond our own tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Testnet dry-run&lt;/strong&gt; on Arc testnet (5042002): self-pay + sponsored mints, &lt;code&gt;/me&lt;/code&gt; auth, negative paths — tokens &lt;code&gt;897222&lt;/code&gt;/&lt;code&gt;897223&lt;/code&gt; (&lt;a href="https://explorer.testnet.arc.io/tx/0xd77df354d319b1874179aaf925c9c90c1ed749d73c0157d8eb39a607d64124df" rel="noopener noreferrer"&gt;mint&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Arc Studio adversarial review&lt;/strong&gt; audited the module → &lt;strong&gt;REQUEST CHANGES&lt;/strong&gt;: replayable sponsored intents + racy JSON store (2 critical), plus treasury-budget, IP-trust, mint-orphan, auth-cache findings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;All critical/high fixed and re-verified on testnet&lt;/strong&gt;: intents are now 10-minute single-use signatures, store writes are mutex-serialized, the sponsored budget fails closed, and header-spoofed IPs collapse into one shared rate bucket.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mint tx on Arc mainnet: &lt;a href="https://explorer.arc.io/tx/0x78dd4d11ea3c6c95e78afc0f9a078da875d21b615050c07d6c3bdf909ed9d3cb" rel="noopener noreferrer"&gt;https://explorer.arc.io/tx/0x78dd4d11ea3c6c95e78afc0f9a078da875d21b615050c07d6c3bdf909ed9d3cb&lt;/a&gt; (437,174 gas, block 25270327 — paid in USDC by the treasury).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source: &lt;code&gt;server/lib/agent-registration/{register,intent,sponsored-gate,public-view,store}.ts&lt;/code&gt;, route in &lt;code&gt;server/routes/agents-register-api.ts&lt;/code&gt; — &lt;a href="https://github.com/spreadzp/agentbadge" rel="noopener noreferrer"&gt;agentbadge repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Tests: &lt;code&gt;tests/agent-registration-api.test.ts&lt;/code&gt; (unit, incl. EIP-191 verify), &lt;code&gt;tests/e2e/agent-registration.test.ts&lt;/code&gt; (full cycle incl. sponsored + caps)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;Registration answers "who are you" — the next slice answers "prove it". Ownership-verification rails (per-agent codes, DB-level exclusivity on &lt;code&gt;(type, locator, network)&lt;/code&gt;) are already specced as 184-7: claim a domain, X handle, or Substack publication and you prove it &lt;em&gt;before&lt;/em&gt; it can route payouts. Our own domain goes through the same rail via &lt;code&gt;/.well-known/agentbadge-verify.txt&lt;/code&gt; — no special-casing ourselves.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is C20 in the Arc Campaign series. Previously: &lt;a href="https://agentbadge.xyz/blog/arc-c27-did-web-identity" rel="noopener noreferrer"&gt;Who Are You, Agent? A DID Your Domain Can Prove&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt; &lt;a href="https://agentbadge.xyz/api/meta/trust-tiers" rel="noopener noreferrer"&gt;Trust tiers&lt;/a&gt; · &lt;a href="https://agentbadge.xyz/api/meta/errors" rel="noopener noreferrer"&gt;Error catalog&lt;/a&gt; · &lt;a href="https://agentbadge.xyz/llms.txt" rel="noopener noreferrer"&gt;llms.txt&lt;/a&gt; · &lt;a href="https://agentbadge.xyz/verification.md" rel="noopener noreferrer"&gt;Verification policy&lt;/a&gt; · &lt;a href="https://agentbadge.xyz" rel="noopener noreferrer"&gt;AgentBadge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don't certify. Measure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>usdc</category>
      <category>arcblockchain</category>
    </item>
    <item>
      <title>Proving a Verdict Exists Is Easy. Proving Nothing Was Deleted Is Not.</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Fri, 09 Oct 2026 14:19:10 +0000</pubDate>
      <link>https://dev.to/spread2009/proving-a-verdict-exists-is-easy-proving-nothing-was-deleted-is-not-17ob</link>
      <guid>https://dev.to/spread2009/proving-a-verdict-exists-is-easy-proving-nothing-was-deleted-is-not-17ob</guid>
      <description>&lt;p&gt;A signed verdict lands in a hash, the hash lands on-chain — and a week later that same verdict still verifies. That's the easy part. The question nobody asks a paid evaluator is the uncomfortable one: &lt;strong&gt;how do you prove you didn't quietly drop the verdicts that made your score look bad?&lt;/strong&gt; Existence proofs don't cover completeness. An archive can be honest about everything it shows you while lying about what it removed.&lt;/p&gt;

&lt;p&gt;This week we closed that hole on AgentBadge's evaluation layer: every verdict now extends a linear hash-chain, and the chain's head is anchored to the Arc Memo contract on a heartbeat — even when nothing new ships. Anyone with a &lt;code&gt;curl&lt;/code&gt; can recompute the head themselves and compare it to what's on-chain. No trust in our API required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Isn't Per-Verdict Anchoring Enough?
&lt;/h2&gt;

&lt;p&gt;Because it answers the wrong question. Each verdict gets a memo anchor — &lt;code&gt;memoId = keccak("eaas:verdictId")&lt;/code&gt; with the artifact hash on-chain. Point at a verdict, check the anchor, done. But anchoring proves each entry &lt;em&gt;independently&lt;/em&gt;. Delete entry 47 of 200 and every remaining anchor is still valid. The proof set has a hole shaped exactly like the verdict you deleted.&lt;/p&gt;

&lt;p&gt;This is the same gap certificate-transparency ran into: RFC 6962's original design proved inclusion, not consistency — Microsoft's ADR-0017 rewrite added signed tree heads precisely because "the log showed me the cert" is not "the log shows everyone the same history." An evaluator without consistency guarantees is a marketing claim, not an evidence source.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does a Linear Hash-Chain Fix Deletion?
&lt;/h2&gt;

&lt;p&gt;Every verdict append writes a &lt;code&gt;ChainEntry&lt;/code&gt;: &lt;code&gt;entryHash = keccak256(prevHash ‖ artifactHash)&lt;/code&gt; — each entry cryptically married to its predecessor, pinned to a genesis hash. Edit an entry's content, its hash breaks. Delete an entry, the next one's &lt;code&gt;prevHash&lt;/code&gt; dangles. Insert one, the sequence number and link both fail. The whole chain verifies in one O(n) fold, and &lt;code&gt;verifyChain&lt;/code&gt; reports the exact first broken sequence — not just "invalid", but &lt;em&gt;where&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Our store is a JSON file, deliberately. The threat model isn't a hardened database — it's that any replay of history diverges from the anchored head. Mutability accepted; detection guaranteed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's the Heartbeat Anchor For?
&lt;/h2&gt;

&lt;p&gt;Liveness. A chain that only anchors when it has new work silently downgrades during quiet periods — no anchor means "unknown", not "unchanged". So &lt;code&gt;createChainFlusher&lt;/code&gt; anchors the head every &lt;code&gt;ARC_CHAIN_FLUSH_MS&lt;/code&gt; (default 24h), new entries or not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New entries&lt;/strong&gt; → anchor the fresh head.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Empty window&lt;/strong&gt; → re-anchor the &lt;em&gt;same&lt;/em&gt; headHash with &lt;code&gt;epochSeq + 1&lt;/code&gt;. Heartbeat, RFC 6962's signed-tree-head pattern: the log asserts "my state at time T is this" so an operator can't fork and serve different views to different auditors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restart after a missed window&lt;/strong&gt; → immediate catch-up flush.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each anchor is one &lt;code&gt;memo(self, 0x, memoId, memoData)&lt;/code&gt; call — &lt;code&gt;memoId = keccak("chain:eaas-verdicts:&amp;lt;epoch&amp;gt;")&lt;/code&gt;, &lt;code&gt;memoData&lt;/code&gt; the ABI-encoded &lt;code&gt;(headHash, count, prevAnchoredHeadHash)&lt;/code&gt;. O(1) on-chain cost per window regardless of verdict volume.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Can a Third Party Verify Without Trusting Us?
&lt;/h2&gt;

&lt;p&gt;Everything, via three free rate-limited endpoints — an RFC 9162-inspired proof surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /api/eaas/chain&lt;/code&gt; → &lt;code&gt;{domain, headHash, count, lastFlushAt, chainOk, anchor:{epochSeq, txHash, blockNumber, explorerUrl}, chainId}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/eaas/chain/entries?from&amp;amp;to&amp;amp;limit&lt;/code&gt; → paged &lt;code&gt;ChainEntry[]&lt;/code&gt; (cap 100)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/eaas/chain/proof/:verdictId&lt;/code&gt; → &lt;code&gt;{seq, path, head}&lt;/code&gt; — inclusion suffix&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /api/eaas/verdicts/:id/verify&lt;/code&gt; → gains &lt;code&gt;chain:{included, seq?, headHash}&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The audit recipe, no SDK, no account:&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;# 1. pull the inclusion proof&lt;/span&gt;
curl https://agentbadge.xyz/api/eaas/chain/proof/0x&amp;lt;verdictId&amp;gt;
&lt;span class="c"&gt;# 2. fold it yourself: h = path[0].prevHash;&lt;/span&gt;
&lt;span class="c"&gt;#    for each entry: h = keccak256(h ‖ e.artifactHash)&lt;/span&gt;
&lt;span class="c"&gt;# 3. compare with the on-chain Memo event for&lt;/span&gt;
&lt;span class="c"&gt;#    memoId = keccak("chain:eaas-verdicts:&amp;lt;epochSeq&amp;gt;")&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your fold hits the anchored headHash, the verdict is provably inside the history the operator attested. If the operator serves you a rewritten store, the fold lands on a different hash than the anchor and the lie is arithmetic, not opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Is This Different from Rekor or Predge?
&lt;/h2&gt;

&lt;p&gt;Rekor-style transparency logs anchor &lt;em&gt;software supply chain&lt;/em&gt; artifacts with Merkle trees — a general log for anyone's blobs. Predge-style agent chains attest &lt;em&gt;signed calls&lt;/em&gt;: proof that a proxy relayed a request/response pair. Ours chains &lt;strong&gt;verdicts&lt;/strong&gt; — the semantic evaluation artifact itself — inside the same API that issues them, and ships a public proof surface rather than a query console.&lt;/p&gt;

&lt;p&gt;Three differences that matter: one entry per priced verdict (not per HTTP call); heartbeat epochs + &lt;code&gt;chainOk&lt;/code&gt; expose deletion and silent windows; linear fold beats Merkle when proofs are suffixes — a verifier needs entries &lt;em&gt;after&lt;/em&gt; the target, nothing before.&lt;/p&gt;

&lt;p&gt;The trade is real: linear scans are O(n) not O(log n). For an append-heavy verdict log where audits fetch suffixes, that's the right-sized data structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Buy a verdict for a cent: &lt;code&gt;bun run examples/eaas-client.ts --endpoint https://agentbadge.xyz --policy deliverable-present&lt;/code&gt;. Then pull its proof and fold it — the arithmetic either matches the chain head or it doesn't. The code: &lt;code&gt;hackathon/server/src/server/lib/eaas/chain*.ts&lt;/code&gt;, and the dogfood: &lt;code&gt;scripts/eaas-chain-dogfood.mts&lt;/code&gt; (3 verdicts → force flush → real Memo event on Arc testnet).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don't certify. Measure.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Part of the Arc Campaign series. Previous: &lt;a href="https://agentbadge.xyz/blog/arc-c10-payer-binding" rel="noopener noreferrer"&gt;payer binding — who paid&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>blockchain</category>
      <category>security</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>An Agent Lands on Your Homepage — What Can It Actually Read?</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Fri, 09 Oct 2026 09:14:53 +0000</pubDate>
      <link>https://dev.to/spread2009/an-agent-lands-on-your-homepage-what-can-it-actually-read-1llp</link>
      <guid>https://dev.to/spread2009/an-agent-lands-on-your-homepage-what-can-it-actually-read-1llp</guid>
      <description>&lt;p&gt;An AI agent evaluating a vendor does what a human does: it opens the site and looks for proof. The difference is speed and format — the agent gives you seconds, not minutes, and it wants JSON, not hero banners. Until recently, an agent landing on agentbadge.xyz found a page built for humans and nothing else — while our own readiness scanner was grading &lt;em&gt;other&lt;/em&gt; sites on exactly the signals we lacked. The cobbler had no shoes. We fixed that: the entire discovery surface is now &lt;strong&gt;generated from live sources&lt;/strong&gt;, served free with no auth, and verified by our own scanner in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does an agent find in 200 milliseconds?
&lt;/h2&gt;

&lt;p&gt;Eleven machine-readable manifests, all &lt;code&gt;200 OK&lt;/code&gt;, all free, all generated — not hand-maintained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/.well-known/agent-card.json        A2A v1.0 — who we are, skills[]
/.well-known/api-catalog            RFC 9727 linkset — every API entry
/.well-known/erc8004-agent.json     on-chain identity registration
/.well-known/mcp/server-card.json   MCP capabilities + tools surface
/.well-known/agent-evaluation.json  verification ladder (claims→refs)
/.well-known/owner-questions.json   "who runs this" for due-diligence
/.well-known/did.json               did:web:agentbadge.xyz document
/.well-known/did-configuration.json signed domain linkage (VC-JWT)
/.well-known/jwks.json              real Ed25519 key, kid'd
/.well-known/security.txt           RFC 9116, Expires generated +1y
/llms.txt                           agent-oriented sitemap
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent-card alone answers the A2A handshake: name, provider, &lt;code&gt;supportedInterfaces[]&lt;/code&gt;, &lt;code&gt;skills[]&lt;/code&gt; with tags and examples, and &lt;code&gt;securitySchemes&lt;/code&gt; declaring &lt;code&gt;x402&lt;/code&gt; as the payment rail. One GET and a foreign agent knows our identity, capabilities, and how to pay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where do the manifests come from?
&lt;/h2&gt;

&lt;p&gt;Not from a copywriter — from the code itself. A manifest registry collects the same live sources the runtime uses (&lt;code&gt;openapi.ts&lt;/code&gt;, route config, &lt;code&gt;blog-data.ts&lt;/code&gt;, the SKU catalog), and every manifest is a &lt;strong&gt;projection&lt;/strong&gt; of that truth:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Boot-time&lt;/strong&gt;: env-dependent manifests generate into a manifest registry at server start; routes serve from it — one source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build-time&lt;/strong&gt;: &lt;code&gt;bun run gen:discovery&lt;/code&gt; writes snapshots under &lt;code&gt;public/.well-known/&lt;/code&gt;; manual edits are banned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI drift-check&lt;/strong&gt;: regenerate + &lt;code&gt;git diff --exit-code&lt;/code&gt; — if a manifest drifted from code, the build fails.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ours &lt;em&gt;cannot go stale&lt;/em&gt; without the build telling us.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a machine-readable sitemap look like?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;llms.txt&lt;/code&gt; — the de-facto convention for telling an LLM "start here": H1 title, a blockquote describing the platform, &lt;code&gt;##&lt;/code&gt; sections of named links. Ours is generated with machine-readable entry points, quick start, free and paid endpoints — and the services section anchors into the same &lt;code&gt;/api/v1/services&lt;/code&gt; catalog that powers the bazaar extension on every 402.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why serve the same page twice?
&lt;/h2&gt;

&lt;p&gt;Because the reader might not be a browser. &lt;code&gt;Accept: text/markdown&lt;/code&gt; on any page returns the markdown representation — verified live:&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;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-sH&lt;/span&gt; &lt;span class="s2"&gt;"Accept: text/markdown"&lt;/span&gt; https://agentbadge.xyz/blog
content-type: text/markdown&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;utf-8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Blog articles carry &lt;code&gt;.md&lt;/code&gt; mirrors too (&lt;code&gt;/blog/arc-c10-payer-binding.md&lt;/code&gt; → &lt;code&gt;200 text/markdown&lt;/code&gt;). HTML stays canonical; &lt;code&gt;&amp;lt;link rel="alternate" type="text/markdown"&amp;gt;&lt;/code&gt; points machines at the twin.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can an agent verify our claims without trusting us?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;agent-evaluation.json&lt;/code&gt; — a &lt;strong&gt;verification ladder&lt;/strong&gt;: claims ordered by check-time, each with &lt;code&gt;action&lt;/code&gt; + &lt;code&gt;ref&lt;/code&gt;:&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;"depth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"checks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"claim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mainnet deployment — AgentEventLog on Arc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ref"&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://explorer.arc.io/address/0x1bb6…4700"&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;5s: we exist on-chain and publish a card. 60s: manifest validity, live OpenAPI, refusal contract. Deeper: dogfood txs and audit trails. &lt;code&gt;owner-questions.json&lt;/code&gt; answers enterprise due-diligence (operator, jurisdiction, contact) in the same shape.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do we know it works?
&lt;/h2&gt;

&lt;p&gt;We run our own scanner on ourselves — the same 142-rule engine that grades foreign sites: &lt;code&gt;mcp/server-card.json&lt;/code&gt; (AB-006), &lt;code&gt;llms.txt&lt;/code&gt; (AB-014), JSON-LD/OG (AB-015/016), &lt;code&gt;ai.txt&lt;/code&gt; (AB-017) — against agentbadge.xyz in CI. &lt;strong&gt;100% pass required.&lt;/strong&gt; A broken manifest fails our own product's grade on our own domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest status
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Live&lt;/strong&gt;: all 11 manifests 200, markdown negotiation serving &lt;code&gt;text/markdown&lt;/code&gt;, &lt;code&gt;.md&lt;/code&gt; mirrors on articles — generated, not hand-edited.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deliberately absent&lt;/strong&gt;: &lt;code&gt;ai-plugin.json&lt;/code&gt; (ChatGPT Plugins EOL 2024 — dead manifest is cargo cult); &lt;code&gt;/.well-known/agent.json&lt;/code&gt; → &lt;code&gt;301&lt;/code&gt; to &lt;code&gt;agent-card.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify now&lt;/strong&gt;: &lt;code&gt;curl https://agentbadge.xyz/.well-known/agent-card.json&lt;/code&gt; — or scan us: &lt;code&gt;npx agentbadge-scan agentbadge.xyz&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;C14 in the Arc Campaign series. C15 continues — the agent found us, now it reads the price list.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://agentbadge.xyz/blog/arc-c14-agent-discovery" rel="noopener noreferrer"&gt;agentbadge.xyz&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>agents</category>
      <category>arcblockchain</category>
    </item>
    <item>
      <title>A txHash Is a Bearer Token — We Bound Ours to the Wallet That Paid</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Thu, 08 Oct 2026 20:08:46 +0000</pubDate>
      <link>https://dev.to/spread2009/a-txhash-is-a-bearer-token-we-bound-ours-to-the-wallet-that-paid-102i</link>
      <guid>https://dev.to/spread2009/a-txhash-is-a-bearer-token-we-bound-ours-to-the-wallet-that-paid-102i</guid>
      <description>&lt;h1&gt;
  
  
  A txHash Is a Bearer Token — We Bound Ours to the Wallet That Paid
&lt;/h1&gt;

&lt;p&gt;On Arc's self-settle payment rail the buyer broadcasts the USDC&lt;br&gt;
&lt;code&gt;transferWithAuthorization&lt;/code&gt; themselves and hands the server the&lt;br&gt;
&lt;strong&gt;transaction hash&lt;/strong&gt; as proof. Convenient — and a bearer credential:&lt;br&gt;
the txHash sits in the public mempool before it confirms, and whoever&lt;br&gt;
presents it to the paid endpoint &lt;em&gt;first&lt;/em&gt; gets the content. We closed&lt;br&gt;
that hole on every AgentBadge paid surface with &lt;code&gt;agentbadge-pay:v1&lt;/code&gt; —&lt;br&gt;
a one-line signature binding a payment transaction to the wallet that&lt;br&gt;
made it, verified &lt;em&gt;before&lt;/em&gt; the replay slot is ever touched.&lt;/p&gt;
&lt;h2&gt;
  
  
  Can someone else spend your agent's payment?
&lt;/h2&gt;

&lt;p&gt;On the self-settle rail, yes — if the endpoint only looks at the&lt;br&gt;
txHash. The attack is Attack I-B from the x402 threat analysis&lt;br&gt;
(arXiv:2605.11781) and it's mechanical: your agent pays, a sniper&lt;br&gt;
reads the txHash out of the mempool, builds its own&lt;br&gt;
&lt;code&gt;PAYMENT-SIGNATURE&lt;/code&gt; pointing at your transaction, and races you to the&lt;br&gt;
server. First presenter wins the replay slot — your money buys their&lt;br&gt;
answer.&lt;/p&gt;

&lt;p&gt;The txHash is proof a payment happened. It is not proof the request&lt;br&gt;
came from the payer.&lt;/p&gt;
&lt;h2&gt;
  
  
  What does payer binding actually check?
&lt;/h2&gt;

&lt;p&gt;Three headers — &lt;code&gt;X-Wallet&lt;/code&gt;, &lt;code&gt;X-Sig&lt;/code&gt;, &lt;code&gt;X-Timestamp&lt;/code&gt; — carrying an&lt;br&gt;
EIP-191 signature over a canonical challenge:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agentbadge-pay:v1
wallet:&amp;lt;payer, lowercase&amp;gt;
method:POST
path:/api/eaas/verdicts
payref:&amp;lt;txHash, lowercase&amp;gt;
timestamp:&amp;lt;unix seconds, ±300s&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The check runs &lt;strong&gt;before&lt;/strong&gt; payment verification — order matters,&lt;br&gt;
because verification is what burns the replay slot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;inspect()&lt;/code&gt; the receipt claim-free — read the USDC &lt;code&gt;Transfer&lt;/code&gt; log,
extract the on-chain &lt;code&gt;from&lt;/code&gt;. A rejection never poisons the payment.&lt;/li&gt;
&lt;li&gt;Recover the challenge signer; compare to &lt;code&gt;X-Wallet&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; the
on-chain payer. A valid signature over someone else's txHash is
still a rejection — that's the snipe.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;403 WRONG_SIGNER&lt;/code&gt; (or &lt;code&gt;402 payer_binding_required&lt;/code&gt; when headers are
absent on a txHash payload). Gateway/exact payments carry no txHash
and skip binding.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The e2e proves the ordering: attacker's request → 403, &lt;code&gt;seenTxHashes&lt;/code&gt;&lt;br&gt;
empty; legit request → 200; third call → replay-402. Slot lifecycle&lt;br&gt;
intact.&lt;/p&gt;
&lt;h2&gt;
  
  
  How does an agent pay with binding enabled?
&lt;/h2&gt;

&lt;p&gt;Every 402 advertises it — &lt;code&gt;extensions.payerBinding&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;accepts[].extra.payerBinding&lt;/code&gt; carry &lt;code&gt;{required: true, challenge:&lt;br&gt;
"agentbadge-pay:v1"}&lt;/code&gt;. Spec: &lt;code&gt;https://agentbadge.xyz/payer-binding.md&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buildPayerChallenge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;signPayerChallenge&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@agentbadge/circle-payments&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;timestamp&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="mi"&gt;1000&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;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;signPayerChallenge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;wallet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/eaas/verdicts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;txHash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// + headers: x-wallet, x-sig, x-timestamp next to payment-signature&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How do you verify a bound request yourself?
&lt;/h2&gt;

&lt;p&gt;No trust in us required. &lt;code&gt;recoverMessageAddress&lt;/code&gt; over the canonical&lt;br&gt;
challenge yields &lt;code&gt;X-Wallet&lt;/code&gt;; the txHash's &lt;code&gt;Transfer(from,to,value)&lt;/code&gt;&lt;br&gt;
log gives the on-chain payer. Mismatch = snipe attempt, valid&lt;br&gt;
signature or not.&lt;/p&gt;
&lt;h2&gt;
  
  
  Why a signature and not a redeem token?
&lt;/h2&gt;

&lt;p&gt;The redeem_token pattern (two-phase quote → pay → redeem) closes the&lt;br&gt;
same hole but costs a second round-trip plus a server-side token&lt;br&gt;
store. Ours is stateless — replay dedup already existed, the binding&lt;br&gt;
rides inside the same request. A sniper holding your txHash can't&lt;br&gt;
produce a signature naming &lt;em&gt;itself&lt;/em&gt; payer of that tx — the on-chain&lt;br&gt;
&lt;code&gt;from&lt;/code&gt; won't match.&lt;/p&gt;
&lt;h2&gt;
  
  
  Honest status
&lt;/h2&gt;

&lt;p&gt;Live behind &lt;code&gt;PAYER_BIND_ENABLED&lt;/code&gt; (off until funded dogfood) across&lt;br&gt;
five paid surfaces — EaaS, marketplace, scan-packs, keeperhub, bstock&lt;br&gt;
— with per-group kill-switch &lt;code&gt;PAYER_BIND_DISABLED_GROUPS&lt;/code&gt;.&lt;br&gt;
&lt;code&gt;scripts/payer-bind-dogfood.mts&lt;/code&gt; replays the full attack on testnet:&lt;br&gt;
victim pays, attacker replays the txHash → 403, victim's bound request&lt;br&gt;
→ 200, retry → 402 replay.&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;ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://agentbadge.xyz &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nv"&gt;PAYER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x... &lt;span class="nv"&gt;ATTACKER_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x... &lt;span class="se"&gt;\&lt;/span&gt;
bun run scripts/payer-bind-dogfood.mts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;C10 in the Arc Campaign series. Earlier: C15 made every 402&lt;br&gt;
self-declaring; C4/C13 built the wallet controls this binding sits&lt;br&gt;
behind.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://agentbadge.xyz/blog/arc-c10-payer-binding" rel="noopener noreferrer"&gt;agentbadge.xyz&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>usdc</category>
      <category>arcblockchain</category>
    </item>
    <item>
      <title>Your Agent Shouldn't Have to Guess the Price — One Catalog, Declared on Every 402</title>
      <dc:creator>Paul Spread</dc:creator>
      <pubDate>Thu, 08 Oct 2026 08:24:13 +0000</pubDate>
      <link>https://dev.to/spread2009/your-agent-shouldnt-have-to-guess-the-price-one-catalog-declared-on-every-402-602</link>
      <guid>https://dev.to/spread2009/your-agent-shouldnt-have-to-guess-the-price-one-catalog-declared-on-every-402-602</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Originally published: &lt;a href="https://agentbadge.xyz/blog/arc-c15-x402-bazaar" rel="noopener noreferrer"&gt;https://agentbadge.xyz/blog/arc-c15-x402-bazaar&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An agent buying from an API answers three questions before it pays: what does it cost, what do I send, is there a free way to try? Most payment-gated APIs make it guess all three. We closed that loop: &lt;code&gt;GET /api/v1/services&lt;/code&gt; returns the full machine-readable catalog, and every 402 across the platform declares itself for x402 indexers — price, input schema, free alternatives, before a cent moves.&lt;/p&gt;

&lt;h2&gt;
  
  
  One registry, three consumers
&lt;/h2&gt;

&lt;p&gt;The catalog isn't a hand-maintained list — it's a declarative SKU registry reading the same config sources the payment middleware reads. The price in the catalog IS the price the gate charges. One registry feeds &lt;code&gt;/api/v1/services&lt;/code&gt;, the bazaar extension on every 402, and the &lt;code&gt;llms.txt&lt;/code&gt; Paid Services section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F60donewzymbi3f2hlvlu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F60donewzymbi3f2hlvlu.png" alt="Diagram: one SKU registry feeds the catalog, every 402's bazaar extension, and llms.txt" width="800" height="1106"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;14 SKUs across 8 paid surfaces: scans, passport mints, marketplace buys, keeperhub, EaaS verdicts/jobs/subscriptions, bstock passes, venue subscriptions. &lt;code&gt;sku_id&lt;/code&gt; is &lt;code&gt;surface:slug&lt;/code&gt;, immutable — a golden test fails CI on rename.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every 402 declares itself (bazaar)
&lt;/h2&gt;

&lt;p&gt;Every payment gate — x402 Hedera, MPP/Stripe, bstock freemium, the manual settle seam, all three EaaS middlewares — attaches &lt;code&gt;bazaarExtensionFor("&amp;lt;sku_id&amp;gt;")&lt;/code&gt;. The emitted &lt;code&gt;inputSchema&lt;/code&gt; is &lt;code&gt;inputSchemaOf(sku)&lt;/code&gt;, byte-equal to the catalog entry. One function produces the schema — catalog and wire can't drift (D-179-4).&lt;/p&gt;

&lt;p&gt;Even the L402 macaroon gate is accounted for — it lives on an explicit exception list (no JSON slot) instead of pretending.&lt;/p&gt;

&lt;h2&gt;
  
  
  Free as the front door
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;free[]&lt;/code&gt; sits next to &lt;code&gt;services[]&lt;/code&gt;: health, scan-packs catalog, market browsing — each with a &lt;code&gt;next_call&lt;/code&gt; pointer to the natural paid step. Discovery → free trial → paid call, zero doc reading.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft44lx0k5ilfbnda6opmt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft44lx0k5ilfbnda6opmt.png" alt="Diagram: agent journey — discovery to free endpoint to 402 bazaar to paid call" width="538" height="1968"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  No paid endpoint without a declaration
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tests/e2e/catalog-coverage.test.ts&lt;/code&gt; keeps a &lt;code&gt;GATE_TABLE&lt;/code&gt; — every gate ↔ SKU both directions. A new paid route without a SKU fails CI. The two non-SKU gates (&lt;code&gt;l402&lt;/code&gt;, &lt;code&gt;attestation-api&lt;/code&gt;) are an explicit allowlist with written reasons.&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://agentbadge.xyz/api/v1/services | jq &lt;span class="s1"&gt;'.services[].sku_id'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full article with decoded 402 payloads:&lt;br&gt;
&lt;a href="https://agentbadge.xyz/blog/arc-c15-x402-bazaar" rel="noopener noreferrer"&gt;https://agentbadge.xyz/blog/arc-c15-x402-bazaar&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>web3</category>
      <category>usdc</category>
      <category>arcblockchain</category>
    </item>
  </channel>
</rss>
