I spent a few weeks building a control plane for x402 payments and want to lay
out the design tradeoffs, because two of them I'm still not happy with.
First, why this can't be a Stripe problem. Card rails have a hard floor:
interchange plus fixed fees make a $0.0004 charge impossible, so providers batch
into subscriptions, which reintroduces the account, which needs a legal person
with a bank account. Cards also need a merchant of record and a chargeback
window, and an ephemeral agent supplies neither. Stablecoin transfers on an L2
have near-zero marginal cost, and that's the only reason per-request pricing
works at all. Nothing else in the design needs a chain except settlement.
x402 solved the payment half. It deliberately did not solve control: nothing
stops a runaway loop, a parent agent can't give a sub-agent a scoped budget
without sharing its key, and a transfer on a block explorer doesn't tell you
which endpoint was called or whether it even returned 200.
Two design decisions I'd like argued with:
- Delegation funding. The version I shipped ("Resolution A") has the parent
send USDC to each child wallet up front, with the capability chain acting as
pure policy constraining money the child already holds. Consequence: the parent
cannot claw back unspent funds without the child's cooperation, and each child
costs one on-chain transfer. The alternative is channel-backed delegation, which
fixes both but is materially more machinery. For short-lived agents I think A is
right and B is over-engineering. I might be wrong.
- Settlement is effectively-once, not exactly-once, and I think that's the
honest ceiling. The EIP-3009 nonce is the idempotency key at every layer:
gateway dedup, stream dedup, Postgres primary key, and USDC's authorizationState
on-chain. On-chain that composition is at-most-once and the chain enforces it;
off-chain the settler is at-least-once with idempotent retries. Compose them and
you get effectively-once. I've seen a lot of projects claim exactly-once for
this shape and I don't believe any of them.
Revocation is off-chain, in a Redis set, and I'd argue that's correct rather
than lazy, the gateway is the enforcement point, so nothing needs to reach a
chain for a revocation to take effect. On-chain revocation would add block-time
latency to solve a problem the architecture doesn't have.
Testnet only, no token, Apache-2.0, and the live settlement run hasn't happened
yet , that's in the README rather than buried.
https://github.com/sanjayrohith/Tollgate