fix: x402 verifier TLS and facilitator compatibility - #320

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

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

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

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

FieldValue
Settlement tx0xd769953bab675ab97a5140dbf7b5583367c788ecb445251c19fea7194c231ec0
NetworkBase Sepolia (84532)
Facilitatorfacilitator.x402.rs (real public settlement)
Amount1000 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.
Comment threadinternal/model/model.go Fixed
…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
ContributorAuthor

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

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the fix/x402-facilitator-compat branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto 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

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: x402 verifier TLS and facilitator compatibility - #320

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

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

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

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

FieldValue
Settlement tx0xd769953bab675ab97a5140dbf7b5583367c788ecb445251c19fea7194c231ec0
NetworkBase Sepolia (84532)
Facilitatorfacilitator.x402.rs (real public settlement)
Amount1000 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.
Comment threadinternal/model/model.go Fixed
…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
ContributorAuthor

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

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the fix/x402-facilitator-compat branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto 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

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: x402 verifier TLS and facilitator compatibility - #320

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

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

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

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

FieldValue
Settlement tx0xd769953bab675ab97a5140dbf7b5583367c788ecb445251c19fea7194c231ec0
NetworkBase Sepolia (84532)
Facilitatorfacilitator.x402.rs (real public settlement)
Amount1000 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.
Comment threadinternal/model/model.go Fixed
…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
ContributorAuthor

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

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the fix/x402-facilitator-compat branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto 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

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: x402 verifier TLS and facilitator compatibility - #320

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

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

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

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

FieldValue
Settlement tx0xd769953bab675ab97a5140dbf7b5583367c788ecb445251c19fea7194c231ec0
NetworkBase Sepolia (84532)
Facilitatorfacilitator.x402.rs (real public settlement)
Amount1000 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.
Comment threadinternal/model/model.go Fixed
…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
ContributorAuthor

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

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the fix/x402-facilitator-compat branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto 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

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: x402 verifier TLS and facilitator compatibility - #320

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

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

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

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

FieldValue
Settlement tx0xd769953bab675ab97a5140dbf7b5583367c788ecb445251c19fea7194c231ec0
NetworkBase Sepolia (84532)
Facilitatorfacilitator.x402.rs (real public settlement)
Amount1000 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.
Comment threadinternal/model/model.go Fixed
…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
ContributorAuthor

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

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the fix/x402-facilitator-compat branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto 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

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: x402 verifier TLS and facilitator compatibility - #320

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

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

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

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

FieldValue
Settlement tx0xd769953bab675ab97a5140dbf7b5583367c788ecb445251c19fea7194c231ec0
NetworkBase Sepolia (84532)
Facilitatorfacilitator.x402.rs (real public settlement)
Amount1000 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.
Comment threadinternal/model/model.go Fixed
…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
ContributorAuthor

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

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the fix/x402-facilitator-compat branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto 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

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: x402 verifier TLS and facilitator compatibility - #320

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

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

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

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

FieldValue
Settlement tx0xd769953bab675ab97a5140dbf7b5583367c788ecb445251c19fea7194c231ec0
NetworkBase Sepolia (84532)
Facilitatorfacilitator.x402.rs (real public settlement)
Amount1000 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.
Comment threadinternal/model/model.go Fixed
…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
ContributorAuthor

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

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the fix/x402-facilitator-compat branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto 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

@bussyjd@github-advanced-security
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: x402 verifier TLS and facilitator compatibility - #320

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

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

Conversation

@bussyjd

Copy link
Copy Markdown
Contributor

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

FieldValue
Settlement tx0xd769953bab675ab97a5140dbf7b5583367c788ecb445251c19fea7194c231ec0
NetworkBase Sepolia (84532)
Facilitatorfacilitator.x402.rs (real public settlement)
Amount1000 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.
Comment threadinternal/model/model.go Fixed
…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
ContributorAuthor

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

@bussyjdbussyjd closed this Apr 8, 2026
@OisinKyne
OisinKyne deleted the fix/x402-facilitator-compat branch April 23, 2026 19:26
OisinKyne pushed a commit that referenced this pull request May 21, 2026
…est-pinned)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: ObolNetwork/obol-stack-front-end#322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
bussyjd added a commit that referenced this pull request May 21, 2026
…est-pinned) (#505)
Promotes the frontend pin to the v0.1.25-rc1 release candidate, which
combines 10 Dependabot bumps integrated via PR #322 on the frontend repo
(all 8 CI checks green on the integration branch).
Multi-arch index digest:
sha256:e7b38ca43771c29475d6831dbee53adb5d2685137ecb7d5878c82e4ecebee92a
Per-platform digests:
linux/amd64 sha256:2d39e666dd7f807e4a6b56e7f7f509cd96b8427fcabe413bbd9000da55f2cf55
linux/arm64 sha256:6afb996dd6c21e68d714f4aa1ef0c51b2a4553e24d72629529e053e611d889b3
Frontend release: https://github.com/ObolNetwork/obol-stack-front-end/releases/tag/v0.1.25-rc1
Frontend integration PR: https://github.com/ObolNetwork/obol-stack-front-end/pull/322
Dep bumps included:
@typescript-eslint/eslint-plugin 8.59.1 → 8.59.2 (#312)
@tanstack/react-query 5.100.9 → 5.100.10 (#313)
@playwright/test 1.59.1 → 1.60.0 (#314)
thread-stream 4.0.0 → 4.2.0 (#315)
@copilotkit/runtime 1.56.3 → 1.57.1 (#316)
@types/node 25.6.2 → 25.9.0 (#317)
viem 2.48.11 → 2.49.3 (#318)
lint-staged 16.4.0 → 17.0.5 (#319)
react-dom 19.2.5 → 19.2.6 (#320)
dompurify 3.4.2 → 3.4.5 (#321)
Co-authored-by: bussyjd <bussyjd@users.noreply.github.com>
Sign up for freeto 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

@bussyjd@github-advanced-security