-
Notifications
You must be signed in to change notification settings - Fork 5
feat(x402): migrate payment middleware to X402_RELAY RPC service binding #87
Description
Summary
x402-api still uses the synchronous HTTP /settle path via X402PaymentVerifier for payment verification. landing-page and agent-news have already migrated to the X402_RELAY service binding with the RelayRPC entrypoint, which routes payments through a queue-backed async path with retry semantics. The HTTP path has no queue retry and is directly exposed to the nonce conflict failures documented in x402-sponsor-relay#236.
Evidence
src/middleware/x402.ts:364-379 (current HTTP path):
// Verify payment with settlement relay using v2 API
const verifier = new X402PaymentVerifier(c.env.X402_FACILITATOR_URL);
log.debug("Settling payment via settlement relay", {
relayUrl: c.env.X402_FACILITATOR_URL,
expectedRecipient: c.env.X402_SERVER_ADDRESS,
minAmount: paymentRequirements.amount,
asset,
network: networkV2,
});
let settleResult: SettlementResponseV2;
try {
settleResult = await verifier.settle(paymentPayload, {
paymentRequirements,
});wrangler.jsonc — services bindings (current, no X402_RELAY):
Compare with landing-page/wrangler.jsonc:31-35 (migrated pattern):
{
"binding": "X402_RELAY",
"service": "x402-sponsor-relay",
"entrypoint": "RelayRPC"
}Root Cause
x402-api is the last first-party consumer still on the HTTP /settle path. This means:
- It is directly exposed to transient nonce conflict errors that the RPC queue path absorbs.
- Any
/settletimeout or 5xx propagates as a payment failure to the end user with no retry. - The migration path is well-established —
landing-pagecompleted it and serves as the reference implementation.
The 22 conflicting_nonce errors logged on 2026-03-26 all originated from x402-api hitting the HTTP path during cron test boundaries when a stale nonce was briefly in the pool.
Proposed Fix
1. Add X402_RELAY service binding to wrangler.jsonc
Add to the top-level services array and duplicate to each environment block:
{
"binding": "X402_RELAY",
"service": "x402-sponsor-relay-staging", // staging env
"entrypoint": "RelayRPC"
}
// production: "service": "x402-sponsor-relay"2. Update src/middleware/x402.ts to use RPC
Replace the X402PaymentVerifier.settle() call with RPC:
// Before
const verifier = new X402PaymentVerifier(c.env.X402_FACILITATOR_URL);
settleResult = await verifier.settle(paymentPayload, { paymentRequirements });
// After
const relay = c.env.X402_RELAY;
settleResult = await relay.submitPayment(paymentPayload, { paymentRequirements });The RPC path (RelayRPC.submitPayment) enqueues the settlement and returns a job ID. A companion checkPayment(jobId) poll or webhook completes the flow. This matches the pattern already working in landing-page.
3. Add X402_RELAY to TypeScript env type
Update src/types.ts (or wherever Env is declared) to add the X402_RELAY service binding type so TypeScript resolves the new binding.
Files
src/middleware/x402.ts:364-379— replaceX402PaymentVerifier.settle()with RPC callwrangler.jsonc— addX402_RELAYservice binding to top-level and both environments
Reference
- landing-page migration:
landing-page/wrangler.jsonc:31-35 - Nonce conflict issue being mitigated: x402-sponsor-relay#236
Priority
Medium — aligns all first-party consumers on the same reliable path. The HTTP path continues to work but is exposed to reliability issues that the RPC path already handles.