Skip to content

feat: secure enclave inference, x402 monetization, and ERC-8004 registration#218

Merged
OisinKyne merged 85 commits intomainfrom
feat/secure-enclave-inference
Mar 1, 2026
Merged

feat: secure enclave inference, x402 monetization, and ERC-8004 registration#218
OisinKyne merged 85 commits intomainfrom
feat/secure-enclave-inference

Conversation

@OisinKyne
Copy link
Contributor

@OisinKyne OisinKyne commented Feb 25, 2026

Summary

Full sell-side monetization stack: from local inference to paid API endpoints with on-chain agent identity.

  • Secure Enclave integration — macOS Secure Enclave key management for signing inference responses (CGo/Security.framework with cross-platform stub)
  • x402 payment gateway — ForwardAuth-based micropayment verification via Traefik middleware, with facilitator settlement
  • ServiceOffer CRD — Custom Resource for declarative compute offers with reconciliation pipeline (ModelReady → UpstreamHealthy → PaymentGateReady → RoutePublished → Registered → Ready)
  • Monetize skill — In-pod Python skill that drives the full lifecycle: health checks, Traefik route creation, x402 verifier config, ERC-8004 registration
  • ERC-8004 on-chain registration — Agent identity minting on Base Sepolia Identity Registry via remote-signer wallet
  • Base Sepolia in eRPC — Two public RPC upstreams + network alias for in-cluster Base Sepolia access
  • Apple Containerization — VM mode for running Ollama in isolated Linux VMs on macOS
  • 6-phase test suite — 60+ tests across unit, integration, and E2E (PRs test: Phase 0 — Static Validation + Test Infrastructure #219-test: Phase 4+5 — Payment Gate + Full E2E Tests #223 squashed here)

Validation Checklist

ABI & Function Selectors

  • Verify register(string) selector is 0xf2c298be — run: cast sig "register(string)" (Foundry)
  • Verify setAgentURI(uint256,string) selector is 0x0af28bd3 — run: cast sig "setAgentURI(uint256,string)"
  • Verify Registered(uint256,string,address) event topic is 0xca52e62c367d81bb2e328eb795f7c7ba24afb478408a26c0e201d155c449bc4a — run: cast sig-event "Registered(uint256,string,address)"
  • Confirm ABI file (internal/erc8004/identity_registry.abi.json) matches the deployed contract ABI on Base Sepolia
  • Confirm _abi_encode_string() in monetize.py produces correct ABI encoding (offset=0x20, length, padded data) — cross-check with cast abi-encode "register(string)" "https://example.com/agent.json"

Contract Addresses

  • Verify IdentityRegistry on Base Sepolia: 0x8004A818BFB912233c491871b3d84c89A494BD9e — check contract is deployed and verified
  • Verify contract is live and accepting register() calls (check recent txs on explorer)
  • Cross-reference with R&D source: erc-8004-contracts/ and lucid-agents chain configs
  • Confirm chain ID 84532 is correct for Base Sepolia (not Base mainnet 8453)

eRPC Base Sepolia Configuration

  • Verify https://sepolia.base.org is live and responds to eth_blockNumber
  • Verify https://base-sepolia-rpc.publicnode.com is live and responds to eth_blockNumber
  • Confirm failsafe settings are reasonable (30s timeout, 2 retries, 500ms hedge)
  • Verify erpc.go chain ID map includes "base-sepolia": 84532
  • Verify signer.py CHAIN_IDS includes "base-sepolia": 84532
  • Verify SKILL.md documents base-sepolia in supported networks

Remote-Signer Integration (monetize.py)

  • Confirm _get_signing_address() correctly parses /api/v1/keys response
  • Confirm _register_on_chain() signs via /api/v1/sign/{address}/transaction endpoint
  • Confirm EIP-1559 tx fields are correct: chain_id, to, nonce, gas_limit, max_fee_per_gas, max_priority_fee_per_gas, value, data
  • Confirm gas estimation uses 30% buffer (* 1.3) for contract calls
  • Confirm receipt polling has 60-iteration timeout (120s at 2s intervals)
  • Confirm _parse_registered_event() correctly extracts agentId from indexed topic[1]

CRD Status (Single Source of Truth)

  • Confirm monetize register CLI no longer writes to disk store (store.Save() removed)
  • Confirm monetize status reads from CRD custom-columns (not store.Load())
  • Confirm monetize delete reads status.agentId from CRD via kubectl jsonpath
  • Confirm stage_registered() patches CRD status fields: agentId, registrationTxHash
  • Confirm no split-brain between disk and CRD is possible

Test Plan

  • go build ./... compiles clean
  • python3 -m py_compile monetize.py passes
  • go test ./internal/erc8004/... — 15 tests (ABI, types, client)
  • go test ./internal/embed/... — CRD parsing, RBAC, skill syntax
  • go test ./cmd/obol/ -run TestMonetize — 9 CLI structure tests
  • go test ./internal/x402/... — verifier, matcher, config, watcher
  • go test ./internal/inference/... — gateway, store, enclave middleware
  • go test ./internal/schemas/... — payment, serviceoffer schemas
  • go test ./... — full unit test suite passes

Integration Tests (requires cluster + agent)

  • TestIntegration_CRD_* — CRD lifecycle (create, get, list, status, delete)
  • TestIntegration_RBAC_* — ClusterRole + binding verification
  • TestIntegration_Monetize_* — Process, heartbeat, idempotency
  • TestIntegration_Route_* — Anvil upstream routing through Traefik
  • TestIntegration_PaymentGate_* — 402 without payment, 200 with mock payment
  • TestIntegration_E2E_* — Full CLI-driven lifecycle

Future Work (NOT in this PR)

  • obol rpc command — Auto-populate eRPC with quality-sorted free RPCs from ChainList
  • Discovery skill — Buy-side agent search via 8004scan.io API
  • Registration JSON hosting — Serve agent-registration.json at the agentURI
  • ERC-8004 deactivationsetAgentURI with active=false on monetize delete
  • Remove store.go — Disk-based RegistrationRecord is no longer written from CLI; full removal is follow-up
  • EIP-712 delegation — Allow third-party wallets to register on behalf of agent
  • Write-method filtering — Add eth_sendRawTransaction blocking for Base Sepolia local upstreams in eRPC (currently only mainnet has this)

Key Files

Area Files
ERC-8004 internal/erc8004/{abi,client,types,store}.go + tests
Monetize skill internal/embed/skills/monetize/scripts/monetize.py
Monetize CLI cmd/obol/monetize.go + monetize_test.go
ServiceOffer CRD internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml
x402 verifier internal/x402/verifier.go + cmd/x402-verifier/main.go
Secure Enclave internal/enclave/enclave_darwin.go + stub
Inference gateway internal/inference/gateway.go + store + container
eRPC config internal/embed/infrastructure/values/erpc.yaml.gotmpl
Test infra internal/testutil/{anvil,facilitator,verifier}.go
Integration tests internal/openclaw/monetize_integration_test.go (1900 lines)

bussyjd and others added 30 commits February 18, 2026 19:39
When `obol stack up` creates a new cluster, k3d tries to bind host
ports 80, 8080, 443, and 8443. If any are already in use, Docker
fails with a cryptic error and rolls back the entire cluster.

Add a `checkPortsAvailable()` pre-flight check that probes each
required port with `net.Listen` before invoking k3d. On conflict,
the error message lists the blocked port(s) and shows a `sudo lsof`
command to identify the offending process.
Add custom regex manager to detect new ObolNetwork/llms releases and
auto-bump the image tag in llm.yaml. Follows the same pattern used for
obol-stack-front-end and OpenClaw version tracking.
The default model gpt-oss:120b-cloud does not exist and caused OpenClaw
to deploy with a non-functional model configuration. Instead, query the
host's Ollama server for actually available models and use those in the
overlay. When no models are pulled, deploy with an empty model list and
guide users to `obol model setup` or `ollama pull`.
- Add `obol-stack-dev` skill with full reference docs for LLM
  smart-routing through llmspy (architecture, CLI wrappers, overlay
  generation, integration testing, troubleshooting)
- Add integration tests (`//go:build integration`) that deploy 3
  OpenClaw instances through obol CLI verbs and validate inference
  through Ollama, Anthropic, and OpenAI via llmspy
- Expand README model providers section and add OpenClaw commands
Implements internal/enclave — a CGO bridge to Apple Security.framework
providing hardware-backed P-256 key management for macOS Secure Enclave.

Key capabilities:
- NewKey/LoadKey: generate or retrieve SE-backed P-256 keys persisted in
  the macOS keychain (kSecAttrTokenIDSecureEnclave); falls back to an
  ephemeral in-process key when the binary lacks keychain entitlements
  (e.g. unsigned test binaries)
- Sign: ECDSA-SHA256 via SecKeyCreateSignature — private key never leaves
  the Secure Enclave co-processor
- ECDH: raw shared-secret exchange via SecKeyCopyKeyExchangeResult
- Encrypt/Decrypt: ECIES using ephemeral ECDH + HKDF-SHA256 + AES-256-GCM
  Wire format: [1:version][65:ephPubKey][12:nonce][ciphertext+16:GCM-tag]
- CheckSIP: verify System Integrity Protection is active via sysctl
  kern.csr_active_config; treats absent sysctl (macOS 26/Apple Silicon)
  as SIP fully enabled (hardware-enforced)

Platform coverage:
- darwin + cgo: full Security.framework implementation
- all other platforms: stubs returning ErrNotSupported so the module
  builds cross-platform without conditional compilation at call sites

Tests cover: key generation, load, sign, ECIES round-trip, tamper
detection, idempotent NewKey, and SIP check. TestLoadKey / TestNewKeyIdempotent
skip gracefully when running as an unsigned binary.
Adds SE-backed request encryption to the inference gateway, closing parity
with ecloud's JWE-encrypted deployment secrets — applied here at the
per-request level rather than deploy-time only.

Changes:
- internal/inference/enclave_middleware.go
  New HTTP middleware (enclaveMiddleware) that:
  • Decrypts Content-Type: application/x-obol-encrypted request bodies
    using the SE private key (ECIES-P256-HKDF-SHA256-AES256GCM)
  • Reconstructs the request as plain application/json before proxying
  • If X-Obol-Reply-Pubkey header present, encrypts the upstream response
    back to the client's ephemeral key (end-to-end confidentiality)
  • Exposes handlePubkey() for GET /v1/enclave/pubkey

- internal/inference/gateway.go
  • New GatewayConfig.EnclaveTag field (empty = plaintext mode, backward compatible)
  • Registers GET /v1/enclave/pubkey when EnclaveTag is set
  • Stacks layers: upstream → SE decrypt → x402 payment → client
    (operator sees only that a paid request arrived, never its content)

- cmd/obol/inference.go
  • --enclave-tag / -e / $OBOL_ENCLAVE_TAG flag on obol inference serve
  • New obol inference pubkey <tag> subcommand: prints or JSON-dumps the
    SE public key — equivalent to `ecloud compute app info` for identity

- internal/inference/enclave_middleware_test.go
  Tests: pubkey JSON shape, encrypted response round-trip, plaintext
  passthrough, gateway construction with EnclaveTag.
Implements a persistent inference deployment store and full lifecycle CLI
mirroring ecloud's 'compute app' surface:

  ecloud compute app deploy      → obol inference create / deploy
  ecloud compute app list        → obol inference list
  ecloud compute app info        → obol inference info
  ecloud compute app terminate   → obol inference delete
  ecloud compute app info pubkey → obol inference pubkey

internal/inference/store.go:
  - Deployment struct: name, enclave_tag, listen_addr, upstream_url,
    wallet_address, price_per_request, chain, facilitator_url, timestamps
  - Store: Create (with defaults + force flag), Get, List, Update, Delete
  - Persisted at ~/.config/obol/inference/<name>/config.json (mode 0600)
  - EnclaveTag auto-derived: "com.obol.inference.<name>" if not set

cmd/obol/inference.go  (rewrites inference.go):
  obol inference create <name>   — register deployment config
  obol inference deploy <name>   — create-or-update + start gateway
  obol inference list            — tabular or JSON listing
  obol inference info <name>     — config + SE pubkey (--json)
  obol inference delete <name>   — remove config (--purge-key also
                                   removes SE key from keychain)
  obol inference pubkey <name>   — resolve name → tag → SE pubkey
  obol inference serve           — low-level inline gateway (no store)

All commands accept --json flag for machine-readable output.
Extract pure-Go ECIES (encrypt + deriveKey) from enclave_darwin.go into
enclave/ecies.go so the encryption half is available without CGO or Darwin.

Add inference.Client — an http.RoundTripper that:
- Fetches and caches the gateway's SE public key from GET /v1/enclave/pubkey
- Transparently encrypts request bodies (ECIES) before forwarding
- Optionally attaches X-Obol-Reply-Pubkey for end-to-end encrypted responses
- Decrypts encrypted responses when EnableEncryptedReplies is active

Mirrors ecloud's encryptRSAOAEPAndAES256GCM client pattern but for live
per-request encryption rather than deploy-time secret encryption.
P0 — Duplicate flag panic on deploy/serve --help:
  --force moved to create-only; deploy uses deployFlags() only.
  --wallet duplicate in serve eliminated (deployFlags() already defines it).

P1 — Encrypted reply Content-Length mismatch:
  After encrypting upstream response, refresh Content-Length to encrypted
  body size and clear Content-Encoding/ETag before writing headers.

P1 — SIP not enforced at runtime:
  gateway.Start() now calls enclave.CheckSIP() before initialising
  enclaveMiddleware when EnclaveTag is set; refuses to start if SIP disabled.

P2 — applyFlags overwrites existing config with flag defaults:
  Switch from c.String(...) to c.IsSet(...) guard so only flags the user
  explicitly set are merged into the stored Deployment.

P2 — Shallow middleware test coverage:
  Replace placeholder tests with five real wrapper-path tests covering
  pubkey endpoint shape, encrypted-request decrypt, plaintext passthrough,
  encrypted-reply header refresh (Content-Length/Content-Encoding/ETag),
  and invalid reply pubkey rejection.

Add CLI regression tests (inference_test.go):
  deploy --help and serve --help no-panic checks, serve wallet-required
  guard, applyFlags explicit-only mutation invariant.
…c claims

Container integration (apple/container v0.9.0):
- internal/inference/container.go: ContainerManager wraps `container` CLI
  to start/stop Ollama in an isolated Linux micro-VM; polls Ollama health
  endpoint before gateway accepts requests
- internal/inference/store.go: add VMMode, VMImage, VMCPUs, VMMemoryMB,
  VMHostPort fields to Deployment
- internal/inference/gateway.go: start ContainerManager on Start() when
  VMMode=true, override UpstreamURL to container's localhost-mapped port,
  stop container on Stop(); fix misleading operator-can't-read comment
- cmd/obol/inference.go: add --vm, --vm-image, --vm-cpus, --vm-memory,
  --vm-host-port flags; wire through applyFlags and runGateway

Doc fixes:
- plans/pitch-diagrams.md: correct Diagram 1 (transit encryption not
  operator-blind), Diagram 5 (SIP blocks external attackers not operator),
  Diagram 7 (competitive matrix: Phase 1.5a at [0.85,0.20] not [0.85,0.88])
Two issues fixed:

1. applyFlags used c.IsSet("wallet") which could return false even when
   --wallet was explicitly passed; changed to non-empty check for flags
   that have no meaningful empty default (wallet, enclave-tag).

2. urfave/cli v2 stops flag parsing at the first positional arg, so
   `deploy test-vm --wallet addr` silently ignored the wallet flag.
   Fixed by adding a --name/-n flag to deployFlags() as an alternative
   to the positional argument. Users can now use either:
     obol inference deploy --wallet <addr> [flags] <name>
     obol inference deploy --name <name> --wallet <addr> [flags]

Added wallet validation before store.Create to prevent writing bad configs.

Tested end-to-end: VM mode container starts, Ollama becomes ready in ~2s
(cached image), gateway serves /health 200 and /v1/chat/completions 402.
Previously `container run --detach` silently pulled the image inline,
causing a 26-minute silent wait on first run with no user feedback.

Now runs an explicit `container pull <image>` with stdout/stderr wired
to the terminal before starting the container, so users see live download
progress.  On cache hit the pull completes in milliseconds.
Breaking changes applied across all cmd/obol files:

- cli.App{} → cli.Command{} (top-level app is now a Command)
- All Action signatures: func(*cli.Context) error →
  func(context.Context, *cli.Command) error
- All Subcommands: → Commands:
- EnvVars: []string{...} → Sources: cli.EnvVars(...)
  (X402_WALLET, OBOL_ENCLAVE_TAG, CLOUDFLARE_*, LLM_API_KEY)
- cli.AppHelpTemplate → cli.RootCommandHelpTemplate
- app.Run(os.Args) → app.Run(context.Background(), os.Args)
- All c.XXX() accessor calls → cmd.XXX() (~70 occurrences)
- cmd.Int() now returns int64; added casts for VMCPUs, VMMemoryMB,
  VMHostPort, openclaw dashboard port
- Passthrough command local var renamed cmd → proc to avoid shadowing
  the *cli.Command action parameter
- inference_test.go: rewrote deployContext() — cli.NewContext removed
  in v3; new impl runs a real *cli.Command and captures parsed state

Removed v2 transitive deps: go-md2man, blackfriday, smetrics.
- Fix CLI framework reference: urfave/cli/v2 → v3
- Update passthrough command example to v3 Action signature (context.Context, *cli.Command)
- Fix go.mod dependency listing
- Expand inference command tree (create/deploy/list/info/delete/pubkey/serve)
- Add Inference Gateway section: architecture, deployment lifecycle, SE integration, VM mode, flag patterns
- Add inference/enclave key files to References
…ort)

Adds install_container() that downloads and installs the signed pkg from
github.com/apple/container releases. macOS-only, non-blocking (failure
continues with a warning). Pins CONTAINER_VERSION=0.9.0.

Enables 'obol inference deploy --vm' for running Ollama in an isolated
Apple Containerization Linux micro-VM.
…litator

Extracts buildHandler() from Start() so tests can inject the handler into
an httptest.Server without requiring a real network listener. Adds VerifyOnly
to GatewayConfig to skip on-chain settlement in staging/test environments.

gateway_test.go implements a minimal mock facilitator (httptest.Server with
/supported, /verify, /settle endpoints and atomic call counters) and covers:
  - Health check (no payment required)
  - Missing X-PAYMENT header → 402
  - Valid payment → verify + settle → 200
  - VerifyOnly=true → verify only, no settle → 200
  - Facilitator rejects payment → 402, no settle
  - Upstream down → verify passes, proxy fails → 502
  - GET /v1/models without payment → 402
  - GET /v1/models with payment → 200
Resolves 6 merge conflicts:
- CLAUDE.md: keep both inference docs and testing docs sections
- cmd/obol/main.go: cli/v3 signature + backend param on stack init
- cmd/obol/update.go: migrate from cli/v2 to cli/v3 API
- go.mod: take bumped crypto/sys/term versions
- internal/openclaw/integration_test.go: take .env loader + improved skip messages
- internal/stack/stack.go: take backend-refactored Up() + permission denied fix
- internal/stack/stack_test.go: take new backend switching tests
Introduce internal/tee/ package providing a hardware-agnostic TEE key
and attestation API that mirrors the macOS Secure Enclave interface.
The stub backend enables full integration testing on any platform
without requiring TDX/SNP/Nitro hardware.

- internal/tee/: key management, ECIES decrypt, attestation reports,
  user_data binding (SHA256(pubkey||modelHash)), verification helpers
- Gateway: TEE vs SE key selection, GET /v1/attestation endpoint
- Store: TEEType + ModelHash fields on Deployment
- CLI: --tee and --model-hash flags on create/deploy/serve/info/pubkey
- Tests: 14 tee unit tests + 4 gateway TEE integration tests
Replace TODO placeholders with real library calls for all three TEE
backends, anchoring the code to actual APIs that compile and can be
verified on hardware later.

Attest backends (behind build tags, not compiled by default):
- SNP: github.com/google/go-sev-guest/client — GetQuoteProvider() +
  GetRawQuote() via /dev/sev-guest or configfs-tsm
- TDX: github.com/google/go-tdx-guest/client — GetQuoteProvider() +
  GetRawQuote() via /dev/tdx-guest or configfs-tsm
- Nitro: github.com/hf/nsm — OpenDefaultSession() + Send(Attestation)
  via /dev/nsm with COSE_Sign1 attestation documents

Verify functions (no build tag, compiles everywhere):
- VerifySNP: go-sev-guest/verify + validate (VCEK cert chain, ECDSA-P384)
- VerifyTDX: go-tdx-guest/verify + validate (DCAP PCK chain, ECDSA-256)
- VerifyNitro: hf/nitrite (COSE/CBOR, AWS Nitro Root CA G1)
- ExtractUserData: auto-detects SNP (1184 bytes), TDX (v4 + 0x81),
  Nitro (CBOR tag 0xD2), and stub (JSON) formats

Tests: 22 passing (14 existing + 8 new verification surface tests)
…teps 8-9)

Add Confidential Containers (CoCo) support to inference templates and
integration tests for QEMU dev mode verification on bare-metal k3s.

Pod templates:
- Conditional runtimeClassName on both Ollama and gateway Deployments
- TEE args/env vars passed to gateway container (--tee, --model-hash)
- TEE metadata in discovery ConfigMap for frontend visibility
- New values: teeRuntime, teeType, teeModelHash with CLI annotations

CoCo helper (internal/tee/coco.go):
- InstallCoCo/UninstallCoCo via Helm with k3s-specific flags
- CheckCoCo returns operator status, runtime classes, KVM availability
- ParseCoCoRuntime validates kata-qemu-coco-dev/snp/tdx runtime names

Integration tests (go:build integration):
- CoCo operator install verification
- RuntimeClass existence check
- Pod deployment with kata-qemu-coco-dev + kernel isolation proof
- Inference gateway attestation from inside CoCo VM
Standalone x402 payment verification service designed for Traefik
ForwardAuth. Enables monetising any HTTP route (RPC, inference, etc.)
via x402 micropayments without modifying backend services.

Components:
- internal/x402: config loading, route pattern matching (exact/prefix/glob),
  ForwardAuth handler reusing mark3labs/x402-go middleware, poll-based
  config watcher for hot-reload
- cmd/x402-verifier: standalone binary with signal handling + graceful shutdown
- x402.yaml: K8s resources (Namespace, ConfigMap, Secret, Deployment, Service)
…ent gating

- Add internal/erc8004 package: Go client for ERC-8004 Identity Registry
  on Base Sepolia using bind.NewBoundContract (register, setAgentURI,
  setMetadata, getMetadata, tokenURI, wallet functions)
- ABI verified against canonical erc-8004-contracts R&D sources with all
  3 register() overloads, agent wallet functions, and events (Registered,
  URIUpdated, MetadataSet)
- Types match ERC-8004 spec: AgentRegistration with image, supportedTrust;
  ServiceDef with version; OnChainReg with numeric agentId
- Add x402 CLI commands: obol x402 register/setup/status
- Add well-known endpoint on x402 verifier (/.well-known/agent-registration.json)
- Add conditional x402 Middleware CRD + ExtensionRef in infrastructure helmfile
- Add x402Enabled flag to inference network template (values + helmfile + gateway)
- Add go-ethereum v1.17.0 dependency
Add comprehensive unit tests for the x402 payment verification and
ERC-8004 on-chain registration subsystems:

- x402 config loading, chain resolution, and facilitator URL validation
- x402 verifier ForwardAuth handler and route matching
- x402 config file watcher polling logic
- ERC-8004 ABI encoding/decoding roundtrips
- ERC-8004 client type serialization and agent registration structs
- x402 test plan document covering all verification scenarios
Address 4 vulnerabilities found during security review:

HIGH — YAML/JSON injection in setup.go: Replace fmt.Sprintf string
interpolation with json.Marshal/yaml.Marshal for all user-supplied
values (wallet, chain, route configs).

MEDIUM — ForwardAuth fail-open: Change empty X-Forwarded-Uri from
200 (allow) to 403 (deny). Missing header signals misconfiguration
or tampering; fail-closed is the safer default.

MEDIUM — Private key in process args: Add --private-key-file flag
and deprecate --private-key. Key is no longer visible in ps output
or shell history when using file or env var.

MEDIUM — No wallet address validation: Add ValidateWallet() using
go-ethereum/common.IsHexAddress with explicit 0x prefix check.
Applied at all entry points (CLI, setup, verifier).
Add full in-pod ERC-8004 registration to the monetize skill, enabling
agents to register themselves on the Identity Registry (Base Sepolia)
using their auto-provisioned remote-signer wallet.

Phase 1a: Add Base Sepolia to eRPC with two public RPC upstreams
  (sepolia.base.org, publicnode.com) and network alias routing.

Phase 1b-1c: Implement register(string) calldata encoding in pure
  Python stdlib (hardcoded selector, manual ABI encoding), with full
  sign→broadcast→receipt→parse flow via remote-signer + eRPC.

Phase 1d: Update CLI to read ERC-8004 registration from CRD status
  (single source of truth) instead of disk-based store. Remove
  RegistrationRecord disk writes from `monetize register` command.
@bussyjd bussyjd changed the title Agents and the Internet feat: secure enclave inference, x402 monetization, and ERC-8004 registration Feb 27, 2026
Adds a new `obol rpc` CLI command group for managing eRPC upstreams:

- `rpc list` — reads eRPC ConfigMap and displays configured networks
  with their upstream endpoints
- `rpc add <chain>` — fetches free public RPCs from ChainList API
  (chainlist.org/rpcs.json), filters for HTTPS-only and low-tracking
  endpoints, sorts by quality, and adds top N to eRPC ConfigMap
- `rpc remove <chain>` — removes ChainList-sourced RPCs for a chain
- `rpc status` — shows eRPC pod health and upstream counts per chain

Supports both chain names (base, arbitrum, optimism) and numeric
chain IDs (8453, 42161). ChainList fetcher is injectable for testing.

New files:
- cmd/obol/rpc.go — CLI wiring
- cmd/obol/rpc_test.go — command structure tests
- internal/network/chainlist.go — ChainList API client and filtering
- internal/network/chainlist_test.go — unit tests with fixture data
- internal/network/rpc.go — eRPC ConfigMap read/patch operations
Add a new `discovery` skill that enables OpenClaw agents to discover
other AI agents registered on the ERC-8004 Identity Registry. This
completes the buy-side of the agent marketplace — agents can now
search for, inspect, and evaluate other agents' services on-chain.

Skill contents:
- SKILL.md: usage docs, supported chains, environment variables
- scripts/discovery.py: pure Python stdlib CLI with four commands:
  - search: list recently registered agents via Registered events
  - agent: get agent details (tokenURI, owner, wallet)
  - uri: fetch and display the agent's registration JSON
  - count: total registered agents (totalSupply or event count)
- references/erc8004-registry.md: contract addresses, function
  selectors, event signatures, agentURI JSON schema

Supports 20+ chains via CREATE2 addresses (mainnet + testnet sets).
All queries are read-only, routed through the in-cluster eRPC gateway.
…on publishing

Closes the gaps found during E2E testing of the full monetize flow
(fresh cluster → ServiceOffer → 402 → paid inference → lifecycle).

Changes:
- Add --facilitator-url flag to `obol monetize pricing` (+ X402_FACILITATOR_URL env)
  so self-hosted facilitators are first-class, not a kubectl-patch afterthought
- Add --endpoint flag to `obol rpc add` with AddCustomRPC() for injecting
  local Anvil forks or custom RPCs into eRPC without ChainList
- Expand monetize RBAC: agent can now create/delete ConfigMaps, Services,
  Deployments (needed for agent-managed registration httpd)
- Agent reconciler publishes ERC-8004 registration JSON: creates ConfigMap +
  busybox httpd Deployment + Service + HTTPRoute at /.well-known/ path,
  all with ownerReferences for automatic GC on ServiceOffer deletion
- `monetize delete` now removes pricing routes and deactivates registration
  (sets active=false in ConfigMap) before deleting the CR
- Extract removePricingRoute() helper (DRY: used by both stop and delete)
- Add --register-image flag for ERC-8004 required `image` field
- Add docs/guides/monetize-inference.md walkthrough guide
CLAUDE.md had drifted significantly (1385 lines) with stale content and
missing documentation for the monetize/x402/ERC-8004 subsystem.

Changes:
- 1385 → 505 lines (64% reduction)
- Fixed stale paths: internal/embed/defaults/ → internal/embed/infrastructure/
- Fixed stale function signature: Setup() now takes facilitatorURL param
- Added full Monetize Subsystem section (data flow, CLI, CRD, ForwardAuth,
  agent reconciler, ERC-8004 registration, RBAC)
- Added RPC Gateway Management section (obol rpc add/list/remove/status)
- Updated CLI command tree to match actual main.go (monetize, rpc, service, agent)
- Updated Embedded Infrastructure section with all 7 templates
- Updated skill count: 21 → 23 (added monetize, discovery)
- Trimmed verbose sections: obolup.sh internals, network install parser details,
  full directory trees, redundant examples
- Kept testnet/facilitator operational details in guides/skills (not CLAUDE.md)
@github-advanced-security
Copy link

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

OisinKyne and others added 3 commits February 27, 2026 12:47
…ctl extraction (#235)

* fix: harden monetize subsystem — RBAC split, URL validation, kubectl extraction

Address 11 review findings from plan-exit-review:

1. **RBAC refactor**: Split monolithic `openclaw-monetize` ClusterRole into
   `openclaw-monetize-read` (cluster-wide read-only) and
   `openclaw-monetize-workload` (cluster-wide mutate). Add scoped
   `openclaw-x402-pricing` Role in x402 namespace for pricing ConfigMap.
   Update `patchMonetizeBinding()` to patch all 3 bindings.

2. **Extract internal/kubectl**: Eliminate ~250 lines of duplicated
   kubectl path construction and cluster-presence checks across 8
   consumer files into a single `internal/kubectl` package.

3. **Fix ValidateFacilitatorURL bypass**: Replace `strings.HasPrefix`
   with `url.Parse()` + exact hostname matching to prevent
   http://localhost-hacker.com bypass.

4. **Pre-compute per-route chains**: Resolve all chain configs at
   Verifier load time instead of per-request, catching invalid chains
   early and eliminating hot-path allocations.

5. **x402-verifier HA**: Bump replicas to 2, add PodDisruptionBudget
   (minAvailable: 1) to prevent fail-open during rolling updates.

6. **Agent init errors fatal**: Make patchMonetizeBinding and
   injectHeartbeatFile failures return errors instead of warnings.

7. **Input validation in monetize.py**: Add strict regex validation for
   route patterns, prices, addresses, and network names to prevent
   YAML injection.

8. **Health check retries**: Add 3-attempt retry with 2s backoff to
   `stage_upstream_healthy` for transient pod startup failures.

9. **Test coverage**: Add 16-case ValidateFacilitatorURL test (including
   bypass regression), kubectl package tests, RBAC document structure
   tests, and load-time chain rejection test.

* fix: use kubectl.Output in x402 e2e test after kubectl extraction

The hardening commit extracted duplicated kubectl helpers into
internal/kubectl but missed updating the x402 e2e integration test,
causing a build failure. Use kubectl.Output instead of the removed
local kubectlOutput function.
cmd/obol/main.go Outdated
Comment on lines 56 to 61
RPC Gateway:
rpc list List configured chains and their upstreams
rpc add Add public RPCs for a chain from ChainList
rpc remove Remove public RPCs for a chain
rpc status Show eRPC health and upstream counts

Copy link
Contributor Author

@OisinKyne OisinKyne Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder could we fold this into the obol network behaviour? e.g. we have like a type --remote and you give it the public rpc (supporting auth /headers etc maybe), and it gets injected into erpc? so people have to differentiate less with obol network | obol rpc

(don't need you to action; will probably just take over and make opinionated updates on my end and then we merge both to main and confirm things are stable)

cmd/obol/main.go Outdated
Comment on lines 77 to 92
Service Management:
service create Register a new service deployment
service deploy Create (or update) and start the service gateway
service serve Start the service gateway directly (no stored config)
service list List all service deployments
service info Show deployment details and encryption public key
service delete Remove a service deployment
service pubkey Print the encryption public key

Monetize:
monetize offer Create a ServiceOffer CR
monetize list List all ServiceOffer CRs
monetize status Show conditions for a ServiceOffer
monetize delete Delete a ServiceOffer CR
monetize pricing Configure x402 pricing in the cluster
monetize register Register service on ERC-8004 Identity Registry (Base Sepolia)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah i don't find these very understandable, will have a think in a PR

OisinKyne and others added 11 commits February 27, 2026 15:10
…-patch

Synced llmspy fork with upstream v3.0.38. All Obol-specific fixes
(SSE tool_call passthrough, per-provider tool_call config, process_chat
tools preservation) are now in the published image. This removes the
runtime stream_options monkey-patch from the init container and the
PYTHONPATH override that were needed for the old image.

Also adds tool_call: false to the Ollama provider config so llmspy
passes tool calls through to the client (OpenClaw) instead of
attempting server-side execution.
…add payment flow tests

Key changes:
- x402 Setup() now reads existing pricing config and preserves routes
  added by the ServiceOffer reconciler (was overwriting with empty array)
- EIP-712 signer uses correct USDC domain name ("USDC" not "USD Coin")
  for Base Sepolia TransferWithAuthorization signatures
- Add full payment flow integration tests (402 → EIP-712 sign → 200)
- Add test utilities: Anvil fork helpers, real facilitator launcher,
  EIP-712 payment header signer
- Remove standalone inference-gateway (replaced by obol service serve)
- Tunnel agent discovery, openclaw monetize integration tests
…esh install verification

getting-started.md: Full rewrite covering the complete journey from install
to monetized inference. Verified every command against a fresh cluster
(vast-flounder). Adds agent deployment, LLM inference testing with tool
calls, and links to monetize guide.

monetize-inference.md: Multiple fixes from end-to-end verification:
- Fix node count (1 not 4), pod counts (2 x402 replicas)
- Fix model pulling (host Ollama, not in-cluster kubectl exec)
- Add concrete 402 response JSON example
- Fix EIP-712 domain name info (USDC not USD Coin)
- Fix payment header name (X-PAYMENT not PAYMENT-SIGNATURE)
- Fix facilitator config JSON format
- Add USDC settlement verification section (cast balance checks)
- Add Cloudflare tunnel payment verification section
- Update troubleshooting for signature errors and pricing route issues
Use %q instead of %v to escape control characters in the decrypt
error, preventing log injection via crafted ciphertext (CodeQL
go/log-injection #2658).
Merge origin/oisin/clicleanup into feat/secure-enclave-inference.
Resolves 14 conflict files by keeping cli/v3 signatures and adapting
*ui.UI parameter threading. Keeps internal/kubectl package for RBAC
patching, development mode image building, and OLLAMA_HOST comments.
Update all CLI command references from the old names to the new:
- obol monetize offer → obol sell http
- obol monetize offer-status → obol sell status
- obol monetize list/stop/delete/pricing/register → obol sell ...
- obol service → obol sell inference
The strategicMergePatches block added a second port named "http" (port 80)
which conflicted with the chart's existing "http" port (4000), causing
`spec.ports[1].name: Duplicate value: "http"` on stack up.

Remove the patches and update HTTPRoute backendRef to use port 4000 directly.
@OisinKyne OisinKyne requested a review from bussyjd March 1, 2026 18:19
@OisinKyne OisinKyne merged commit d9fe34b into main Mar 1, 2026
9 checks passed
@OisinKyne OisinKyne deleted the feat/secure-enclave-inference branch March 1, 2026 21:58
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