Search projects, hackathons, sponsors and tech

    ← All projects

    SubEtha Router

    Payment method shouldn't decide which APIs AI agents can use. The Router opens all of them.

    Demo

    About

    THE PROBLEM AI agents are starting to pay for APIs per call, using HTTP 402: the server answers "payment required" and says exactly what it wants, the agent pays, and the data comes back. No API key, no signup. The trouble is how you pay. Protocols like x402 and MPP (Machine Payments Protocol) are multiplying the options, from stablecoins through Bitcoin Lightning, with Block, led by Jack Dorsey, contributing Lightning payments to x402. Each provider picks a protocol, a chain and a currency, and it only accepts that combination. I will call one such combination a "rail". More choice does not mean the API you need accepts what you hold. The first mismatch is the protocol itself: you are set up to pay with x402, but the API only speaks MPP, so there is no way to hand over the money at all. Even when the protocol matches, the chain can still divide you. You might hold USDC on Base while the API wants USDC on Solana. Same protocol, same currency, different chain, and the endpoint is still closed. Not because of the data, but because of the rail. WHY I BUILT THIS I ran into this while building SubEtha, another way for agents to pay, combining zERC-20 private transfers with x402. Building a new way to pay is only half the problem. A payment method is only as useful as the set of APIs that accept it. So I built SubEtha Router for this hackathon. It is not limited to SubEtha's private payments. If you pay with x402 or MPP, APIs that were closed to you become usable, without changing how you pay. HOW IT WORKS The Router makes two separate payments for one request. You pay the Router on the rail you already hold. The Router then pays the provider, from its own funds, on the rail that provider demands. Both directions settle on testnet today: pay in MPP and reach an x402-only provider, or pay in x402 and reach an MPP-only one. This is not a swap or a bridge. Nothing is converted and nothing crosses a chain. The two payments are independent, each funded in advance on its own chain, which is why the Router keeps a balance on both sides instead of passing your money through. USING IT The Router is already deployed and running. It is an MCP server, the standard Claude and other AI clients use to connect to tools, so there is nothing to install and nothing to run locally: you point your own AI at the hosted endpoint with one command and ask in plain language, "fetch the ETH price". The agent gets a quote, shows you the amount, and waits. The Router never holds your key. You sign each payment on your own machine and it receives only the signature. Nothing moves until you approve. That approval is tied to one specific quote, so an offer that changes afterwards is rejected rather than paid, and a durable log stops a repeated or concurrent request from paying twice. SCOPE, AND WHERE IT GROWS Payments are on testnet today, and the Router covers the payment methods and routes it supports rather than every method or every API. That set is meant to grow, and growth compounds: each rail added is reachable from every rail already there, so the value of holding any one of them rises with the count. TRY IT YOURSELF All of it is running right now. I host two sample providers, one accepting x402 and one accepting MPP, and the Router's MCP endpoint sits in front of them, all on public testnets. Bring Claude Code or any MCP-capable AI agent, connect in one command, and make a real payment yourself.

    How it's made

    Architecture. TypeScript on Node 22, ports and adapters. The core knows exactly two ports — inbound (take a payment) and outbound (make one) — and every protocol lives behind an adapter. x402 (@x402/core, @x402/evm, @x402/fetch) has both; MPP (mppx) has both. That is the entire reason both directions work: the core never learned a second protocol, it got a second pair of adapters. A third rail means writing adapters, not touching purchase logic. The asymmetry that shaped everything. x402 is signature-based — the payer signs an EIP-3009 TransferWithAuthorization, a facilitator submits it and pays gas, and the payer spends nothing until settlement. MPP is broadcast-based — the payer sends the transfer and pays the fee, so the money is gone the moment the credential exists, before the Router is asked to buy anything, with no refund on later failure. The same word "sign" means prepare on one rail and spend on the other. So the quote response carries a fundsMoveOnCredential boolean and the MPP path returns different guidance: get human approval before signing, not before purchase. Structured, because prose in a tool description gets skimmed. Safety. Approval binds to one quoteId; a purchase without it, with an expired one, or against an offer that changed after approval is rejected rather than paid at new terms. At-most-once is enforced by a durable journal holding an OS-level exclusive lock (flock(2), LOCK_EX|LOCK_NB via fs-ext), not an in-process mutex — a second Router process fails to start instead of racing the first. Repeats and concurrent requests re-send neither leg; an unconfirmable outcome stays unconfirmed rather than being blindly retried, because blind retry is how you pay twice. The outbound signer refuses typed data that doesn't match the approved quote, so a bug in the calling path can't redirect a payment — tested without spending anything. The key that isn't there. The hosted Router runs in Docker on EC2 behind Caddy (own TLS cert; fs-ext is compiled in a build stage so only the artifact ships). The container gets exactly one secret: the outbound key, which spends the operator's own funds. It never receives an inbound key — callers sign locally and send only the signature, and the source reads no private key from the environment for the inbound leg at all. A hosted Router cannot spend a caller's money even if it wanted to. MCP is served over Streamable HTTP (same wiring also available over stdio); statelessness cost us one relaxation — quote and purchase arrive as separate sessionless requests, so the typed client's "only execute quotes I issued" check became opt-in. That check is client-side convenience; the Router still rejects a wrong quoteId, an expired approval, an unissued nonce, an offer disagreeing with its config, and a repeated purchase. Providers. Both sample providers are ours: Hono with @x402/hono and mppx, on Cloudflare Workers — app.fetch is already the Workers entry point, so the same code runs under Node with no rewrite. CORS is allowed for unpaid reads only, from an allowlist, never on a request carrying a payment credential: a browser can read the 402 offer, it cannot be talked into paying. What bit us. Cloudflare's shared egress IPs are rate-limited by the public Tempo RPC, so the deployed MPP provider returned 500 while the transfer settled on chain — proven by contrast (25/25 from a residential IP, failures from Workers), fixed with an authenticated endpoint. The public x402 facilitator collides with its own pending transactions under rapid calls: 2 failures in 20 with no gap, 0 in 15 with three seconds between. Tempo fee variance is extreme — four identical 10000-unit payments cost 505, 945, 2283 and 23370 in fees, so we stopped quoting a range and now say it's unpredictable. And the one worth admitting: npm run sign was written for x402 and silently produced a wrong-chain signature for MPP, so the MPP route couldn't be bought through our own documented steps. It survived because x402 worked and nobody had run the other rail through the published instructions. Three separate places had the same bug for the same reason.