Skip to content

fix: x402 verifier TLS and facilitator compatibility#320

Closed
bussyjd wants to merge 4 commits intomainfrom
fix/x402-facilitator-compat
Closed

fix: x402 verifier TLS and facilitator compatibility#320
bussyjd wants to merge 4 commits intomainfrom
fix/x402-facilitator-compat

Conversation

@bussyjd
Copy link
Copy Markdown
Collaborator

@bussyjd bussyjd commented Apr 8, 2026

Summary

  • CA certificate bundle: The x402-verifier distroless image has no CA store. obol sell pricing now populates the ca-certificates ConfigMap from the host's cert bundle so TLS verification of facilitator.x402.rs works out of the box.
  • Missing Description field: The facilitator rejects verify requests without description in PaymentRequirement. Added it from the route pattern.

Validated on Base Sepolia testnet

Real x402 payment flow between two ARM64 nodes running Nemotron 120B (120B parameter model, tensor-parallel across two GPUs):

Alice (seller): obolup.shobol stack upobol sell pricingobol sell http nemotron → tunnel
Bob (buyer): discover .well-known → probe 402 → sign EIP-712 → pay → HTTP 200 + real inference

On-chain receipt

Field Value
Settlement tx 0xd769953bab675ab97a5140dbf7b5583367c788ecb445251c19fea7194c231ec0
Network Base Sepolia (84532)
Facilitator facilitator.x402.rs (real public settlement)
Amount 1000 micro-USDC (0.001 USDC)
Seller balance delta +0.001 USDC
Buyer balance delta -0.001 USDC

Test plan

  • go build ./... compiles
  • go test ./internal/x402/... passes
  • Real Base Sepolia payment: 402 → sign → 200 + on-chain settlement
  • Inference from Nemotron 120B served through the paid route

bussyjd added 2 commits April 8, 2026 14:20
Two fixes validated with real Base Sepolia x402 payments between
two DGX Spark nodes running Nemotron 120B inference.

1. **CA certificate bundle**: The x402-verifier runs in a distroless
   container with no CA store. TLS verification of the public
   facilitator (facilitator.x402.rs) fails with "x509: certificate
   signed by unknown authority". Fix: `obol sell pricing` now reads
   the host CA bundle and patches it into the `ca-certificates`
   ConfigMap mounted by the verifier.

2. **Missing Description field**: The facilitator rejects verify
   requests that lack a `description` field in PaymentRequirement
   with "invalid_format". Fix: populate Description from the route
   pattern when building the payment requirement.

## Validated testnet flow

### Alice (seller)

```
obolup.sh                    # bootstrap dependencies
obol stack init && obol stack up
obol model setup custom --name nemotron-120b \
  --endpoint http://host.k3d.internal:8000/v1 \
  --model "nvidia/NVIDIA-Nemotron-3-Super-120B-A12B-NVFP4"
obol sell pricing --wallet 0xC0De...97E --chain base-sepolia
obol sell http nemotron \
  --wallet 0xC0De...97E --chain base-sepolia \
  --per-request 0.001 --namespace llm \
  --upstream litellm --port 4000 \
  --health-path /health/readiness \
  --register --register-name "Nemotron 120B on DGX Spark"
obol tunnel restart
```

### Bob (buyer)

```
# 1. Discover
curl $TUNNEL/.well-known/agent-registration.json
# → name: "Nemotron 120B on DGX Spark", x402Support: true

# 2. Probe
curl -X POST $TUNNEL/services/nemotron/v1/chat/completions
# → 402: payTo=0xC0De...97E, amount=1000, network=base-sepolia

# 3. Sign EIP-712 TransferWithAuthorization + pay
python3 bob_buy.py
# → 200: "The meaning of life is to discover and pursue purpose"
```

### On-chain receipts (Base Sepolia)

| Tx | Description |
|----|-------------|
| 0xd769953b...c231ec0 | x402 settlement: Bob→Alice 0.001 USDC via ERC-3009 |

Balance change: Alice +0.001 USDC, Bob -0.001 USDC.
Facilitator: https://facilitator.x402.rs (real public settlement).
Replace the third-party facilitator.x402.rs with the Obol-operated
facilitator at x402.gcp.obol.tech. This gives us control over
uptime, chain support, and monitoring (Grafana dashboards already
deployed in obol-infrastructure).

Introduces DefaultFacilitatorURL constant in internal/x402 and
updates all references: CLI flag default, config loader, standalone
inference gateway, and deployment store.

Companion PR in obol-infrastructure adds Base Sepolia (84532) to
the facilitator's chain config alongside Base Mainnet (8453).
Address #321 — LiteLLM reliability improvements:

1. Hot-add models via /model/new API instead of restarting the
   deployment. ConfigMap still patched for persistence. Restart
   only triggered when API keys change (Secret mount requires it).

2. Scale to 2 replicas with RollingUpdate (maxUnavailable: 0,
   maxSurge: 1) so a new pod is ready before any old pod terminates.

3. PodDisruptionBudget (minAvailable: 1) prevents both replicas
   from being down simultaneously during voluntary disruptions.

4. preStop hook (sleep 10) gives EndpointSlice time to deregister
   the terminating pod before SIGTERM — prevents in-flight request
   drops during rolling updates.

5. Reloader annotation on litellm-secrets — Stakater Reloader
   triggers rolling restart on API key rotation, no manual restart.

6. terminationGracePeriodSeconds: 60 — long inference requests
   (e.g. Nemotron 120B at 30s+) have time to complete.
…uoting'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: JeanDaniel Bussy <SilverSurfer972@gmail.com>
@bussyjd
Copy link
Copy Markdown
Collaborator Author

bussyjd commented Apr 8, 2026

Split into separate PRs: #322 (TLS fix), #323 (facilitator default), #324 (LiteLLM reliability), #325 (obolup.sh)

@bussyjd bussyjd closed this Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants