Hey everyone, I’m Emmanuel, a developer building on Celo. I wanted to introduce myself and share something I’ve been working on.
What is x402Go?
x402Go is an x402 facilitator on Celo that lets merchants and developers start accepting payments immediately, with no balance to fund and no credits to buy (basically pay as you go).
Instead of pre-funding an account to cover settlement costs, the fee comes out of each payment as it settles (about $0.003 per settled transaction). You only pay when you actually get paid.
Why build another facilitator?
Celo Core Co already runs a native x402 facilitator at x402.celo.org, and I think that’s great for the ecosystem. I built x402Go for a different kind of builder:
You may not have upfront funds to buy credits just to start.
You don’t want to lose payments because your credits ran out and you didn’t notice.
You don’t want to worry about withdrawing unused/overfunded credits.
With x402Go there’s no balance to maintain, so there’s nothing to run out of. Each payment pays for itself.
Supported tokens
We’re live on mainnet with USAT, USDC, USDT and cNGN. I’ll add more if there’s interest from users.
How it works
Each merchant gets their own relayer contract.
The client authorizes a payment with either ERC-3009 or Permit2.
The relayer pulls the funds and splits them in a single transaction: the net goes to the merchant, and the fee goes to the protocol.
What I’m looking for
Try it out. Grab an API key at x402go.online, follow the quickstart in the docs, and tell me where you got stuck.
Feedback on the model. Does the per-transaction fee work for your use case, especially for small payments? What would you want to see in the dashboard? What would stop you from using this?
Building with x402 on Celo? I’d like to hear what you’re working on and what’s missing from the tooling. Reply here or reach me on Telegram at @emmo00.
Thanks for reading. Questions, criticism and feature requests are all welcome.
Welcome Emmanuel, and nice to see a facilitator where the fee comes out of each payment. I read the relayer, the factory and the settle path in x402go-facilitator; the balance check on the Permit2 path is a good touch. Three things:
H-02 in your pre-blueprint audit still looks open in the live factory: createVault lets the operator deploy any merchant’s vault with any payout, and on the first payment the facilitator passes the payTo stored from the dashboard, which your own payout docs call a preference rather than an authority. After that, moving it needs the merchant’s ChangePayout signature, so the weaker path decides where every payment goes until the merchant notices. If the operator key leaks, it can deploy the vault of every merchant not yet paid on a network with its own payout. The audit’s first fix, operator-created vaults always paying the merchant and anything else going through changePayout, still looks like the simplest one.
On small payments, since you asked: the live fee is 35 (0.0035 USDC), and settle reverts when the value is at or below it, so a $0.001 or $0.003 call can’t be paid at all and a $0.01 one pays 35%. Worth stating the minimum price in the quickstart, since per-call prices that low are common with x402.
The vault calls the v,r,s version of transferWithAuthorization and the facilitator rejects anything but a 65-byte signature, so payers on smart accounts (Safe, most agent wallets) can’t pay. USDC on Celo also has the bytes version, which checks ERC-1271, if you want them in.
Thank you so much for taking the time to read through the contracts and the settle path. That kind of review is exactly what I was hoping for.
1. H-02 (vault creation and the initial payout).
Your concern is valid. Today createVault is optimized for cheaper relayer initialization, and the first payment uses the payTo stored from the dashboard. That means the initial payout is set by the weaker path until the merchant takes control through ChangePayout. Until now my mitigation has been to advise merchants to start with small payments before going live, but that’s guidance, not a guarantee, and it doesn’t cover the leaked-operator-key case you describe. I agree the merchant should have authority over the initial payout. I’ll work on this fix in the upcoming update, and your suggestion (operator-created vaults always pay the merchant, and anything else goes through changePayout) is the first option I’ll evaluate.
2. Small payments.
You’re right on both counts: a payment at or below the fee reverts at settle, and a flat fee takes a big share of very small payments, so the model fits payments of a few cents and up. The quickstart’s troubleshooting section (Quickstart - x402go Documentation) already says the amount must exceed the fee, but I’ll move the minimum price to the main quickstart and state the current fee there. Thanks for pointing out that per-call prices that low are common with x402.
3. Smart account signatures.
Thank you for this one. I hadn’t considered that Safe and most agent wallets would be locked out by the 65-byte v,r,s path. Supporting the bytes signature version of transferWithAuthorization with ERC-1271 checking is on my list for the upgrade, so agent wallets can pay.
Thanks again. If you’re open to it, I’d welcome a look at the fixes when they’re ready.
Glad it was useful. Sure, post the commit here when the update is up and I’ll read it.
One thing for the first fix: the payout is written once, when the vault is created, so a change in createVault covers vaults created after it, and the ones that already exist keep the payout they were given. The VaultCreated events show which existing vaults got a payout other than the merchant, so those are the merchants to ask to confirm it, or to move it with a changePayout signature.
Thank you, I’ll post the commit here when the update is up.
While I’m planning the update, I’d like your view on a design question. Each merchant relayer is deployed with CREATE2, so its address is deterministic, but the code behind it is fixed. If there’s a bug or a security issue, the only option is a new vault at a new address, and every merchant’s integration, and payout flow would have to move with it. I’d like a way to fix a relayer without changing its address.
The options I see:
Per-merchant proxies deployed with CREATE2 (UUPS or beacon). The address stays the same and the logic can change. The cost is an upgrade key. Whoever holds it can ship logic that redirects funds from every vault, which is a bigger risk than H-02.
Upgrades the merchant approves. Each vault upgrades only when the merchant signs, as they already do for ChangePayout. That limits the blast radius of a leaked key, but a merchant who never signs stays on the old code, and a critical bug could stay exposed for a long time.
Upgrade key behind a multisig and timelock. Merchants get a window to see a pending upgrade and exit before it takes effect. Cheaper to run than option 2, but it needs merchants or monitors actually watching.
Keep the relayers immutable and add a stable entry point. Merchants and payers use a router address that routes to the current vault version, and moving a merchant to a new version needs their signature. The address stays the same, but one more contract is on the payment path.
Happy to. Two things in your code shape the choice. The vault holds nothing between payments, it pulls and splits in the same call, and every payment is signed to the vault’s own address for an exact amount (the to of transferWithAuthorization, witness.to on Permit2). So there’s no balance and no standing approval for new logic to reach, and stopping a vault is enough to contain a bug in it while a fix is prepared.
That points to your option 2 plus a guardian that can only pause settle, not change code, payout or fees. A critical bug gets stopped in minutes, the fix still needs the merchant’s signature, and once a vault is paused a merchant who never signs stays paused instead of exposed. A leaked guardian key can stall payments but can’t take any. If you go with proxies, the CREATE2 address has to stop depending on the implementation: vaultOf hashes it in today, so a new version means new addresses whichever option you pick.
Whichever you pick, give setTokenFees the same treatment. It has no cap and no delay, and the factory owner is an EOA today. tokenFeeBPS is a uint16 in units of $0.0001, so that key can set the fee anywhere up to about $6.55 a payment right now: every payment at or below the fee reverts, and one just above it leaves the merchant almost nothing. A hard maximum in the contract and the same delay as upgrades would close it.
Nice write-up, and the per-settlement fee model is a neat way to remove the pre-funding hurdle. One thing that might help small merchants is a clear minimum price in the quickstart, since the flat fee makes very cheap calls either unpayable or a large percentage. A short table of fee percentage at a few price points would make it easy to judge fit.
However, I agree that it would be helpful to include a reference to it in the documentation as well, to make the information more accessible to developers.
Makes sense, one number can’t fit every token. It can be per token though: keep a max fee next to each token’s fee, set when the token is added, and have setTokenFees revert above it. Raising a max goes through the same delay as upgrades, lowering a fee stays instant. cNGN at 48000 and USDC at 35 then each get their own ceiling, and a leaked owner key can’t push either past it in one call.
The uint16 already works as a per-token ceiling today, 6.5535 whole tokens, which is under half a cent on cNGN but $6.55 on USDC, USDT and USAT, so the dollar tokens are where it matters most.