ObolNetwork / ObolNetwork/obol-stack
feat(sell): host-based offer routing — bind offers to dedicated hostnames (one tunnel, N origins)
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 11
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
Summary
Today every obol sell offer is published under a shared path prefix on the single tunnel
hostname (/services/<name>/*). This proposes binding an offer to its own hostname so
multiple services can be routed by origin (Host header) — e.g. a.example.com → service A,
b.example.com → service B — through a single Cloudflare tunnel.
Background — the host-routing mechanism already exists
Host-based routing is already how obol-stack isolates internal services: Traefik's Gateway matches
HTTPRoute.spec.hostnames against the Host header.
- Frontend / eRPC pin
hostnames: ["obol.stack"]to keep them off the public tunnel —
internal/embed/infrastructure/base/templates/obol-frontend.yaml:41-42,erpc.yaml:41-42. - The storefront pins the tunnel hostname —
CreateStorefront,internal/tunnel/tunnel.go:838/
:934("hostnames": []string{hostname}).
A single cloudflared connector can serve many Public Hostnames (standard Cloudflare Zero Trust):
each *.example.com → http://traefik.traefik.svc.cluster.local:80 — the same origin all tunnel modes
already point at. cloudflared forwards the original Host header; Traefik routes by hostnames.
No second tunnel is required to route by origin.
The gap
ServiceOfferSpechas no hostname/domain field —internal/monetizeapi/types.go(onlyPath, ~:125).buildHTTPRouteemits only aPathPrefix /services/<name>match, with nohostnames—
internal/serviceoffercontroller/render.go:579-620.- No
--hostnameflag onobol sell http(confirmed — the lonehostnamehit insell.gois an
unrelated comment).
Proposed change
- CRD field — add
Hostname string(spec.hostname, FQDN-validated) toServiceOfferSpec
(internal/monetizeapi/types.go, alongsidePath) and the embedded CRD
(internal/embed/infrastructure/base/templates/serviceoffer-crd.yaml; validated by
internal/embed/embed_crd_test.go). - Renderer — in
buildHTTPRoute(internal/serviceoffercontroller/render.go:579), when
spec.hostname != "", setspec.hostnames: [hostname]and matchPathPrefix /(the whole origin
maps to this backend) instead of/services/<name>. Mirror the storefront pattern at
tunnel.go:934. Backend staysx402-verifier, so payment gating is preserved. Add a regression
test ininternal/serviceoffercontroller/render_test.go. - CLI — add
--hostnametoobol sell http(and likelysell agent/sell inference) in
cmd/obol/sell.go, threaded into the offer spec. - Discovery host-awareness —
/skill.md,/api/services.json, and ERC-8004 advertise
https://<hostname>/instead of<tunnelbase>/services/<name>when a hostname is set
(render.go:854,:977,:1138). - Operator action (no code) — add each Public Hostname →
http://traefik.traefik.svc.cluster.local:80
in the Cloudflare dashboard. Optional polish: automate viacloudflareClient.UpdateTunnelConfiguration
(internal/tunnel/cloudflare.go, called fromprovision.go:101) to push multiple ingress rules.
Acceptance criteria
obol sell http <name> --hostname a.example.com …publishes an HTTPRoute with
spec.hostnames: [a.example.com]matching/, backed byx402-verifier(still 402-gated).- Two offers on distinct hostnames route independently; behavior is unchanged (path-prefix) when
--hostnameis omitted. - Catalog /
/skill.md/ ERC-8004 advertise the hostname-based endpoint when set. render_test.gocovers both modes;embed_crd_test.goaccepts the new field.- Host-bound
/-prefix routes do not shadow the internalobol.stackroutes (different hostname) —
asserted by test.
Out of scope — Option B (several named tunnels)
Running multiple cloudflared connectors (distinct tokens/credentials per hostname) is a separate,
heavier change: the tunnel is hardcoded to one Helm release / Deployment / token Secret
(cloudflared, cloudflared-tunnel-token) plus a single Hostname in tunnel state (internal/tunnel/,
~10 references). It would require parameterizing the chart/release/secret and turning tunnel state into
a list. It is only needed for distinct connector trust/exposure boundaries per hostname (e.g. separate
Cloudflare accounts/zones) and still requires this issue's HTTPRoute change on top. Track separately
if a real need arises.
Security note
Host-bound offer routes remain gated — their backend is x402-verifier (render.go:608-611), so
payment enforcement is unchanged. The /-prefix only applies to the offer's own hostname, so it cannot
shadow obol.stack internal routes; a test should assert this.
Spec derived from a code walk of internal/tunnel/, internal/serviceoffercontroller/render.go,
internal/monetizeapi/types.go, and cmd/obol/sell.go. Filed via Claude Code:
https://claude.ai/code/session_01YUTW7NfxjUoVtyKgR6nPQC
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with internal/monetizeapi/types.go, internal/serviceoffercontroller/render.go and render_test.go, then trace the sell entry points in cmd/obol/sell.go and the embedded CRD template. Run the relevant renderer and CRD tests first. Done means hostname-bound offers, CLI propagation, discovery URLs, and legacy path-prefix behavior all satisfy the listed acceptance criteria.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, helm, kubernetes
- Domain
- backend, cli, infrastructure
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 52/100