ObolNetwork / ObolNetwork/obol-stack
feat(sell): x402scan discovery correctness for host-bound offers — companion to #668
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 11
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
Summary
#668 proposes binding each offer to its own hostname (one tunnel, N origins) — exactly the right substrate, because x402scan groups discovered resources per origin (scheme://host). This is a companion to #668: the set of discovery-correctness behaviors obol-stack should emit/validate by default so that a host-bound offer actually indexes cleanly on x402scan, rather than being silently skipped.
Grounded in (a) reading x402scan's discovery implementation (Merit-Systems/x402scan + @agentcash/discovery), and (b) field experience running three offers (an ~80-endpoint HTTP API, an OpenAI-compatible agent, and an ERC-8004 feed) behind a single cloudflared tunnel.
Why — x402scan's actual rules (from its source)
- Per-origin grouping → an offer needs its own hostname (#668). ✔
- An OpenAPI operation is treated as paid only if it carries
x-payment-info({ price: {mode,currency,amount}, protocols: [{x402:{}}] }). Operations markedsecurity: []or referencing an apiKey-typed security scheme are classifiedunprotected/apiKeyand skipped (@agentcash/discoveryinferAuthMode;SKIP_AUTH_MODES = {unprotected, apiKey}). This is the biggest footgun: the conventional way to model x402 in OpenAPI — an apiKeysecurityScheme(headerX-PAYMENT) — makes every paid endpoint invisible to x402scan. - It reads
/openapi.jsonand/.well-known/x402FREE at the origin root. - It probes each endpoint and requires the 402 challenge's
resource.urlto byte-match the probed URL (scheme included). - It indexes
base+solanaonly (testnets are skipped). - It does not expand
{path}templates — templated routes register as literal%7B…%7D(effectively uncallable); query-param endpoints index cleanly.
Gaps in obol-stack today (what an operator must hand-fix)
- No
x-payment-infostory. A service whose OpenAPI marks paid ops with an apiKeysecurityscheme (the natural modeling) is silently skipped. We had to dropsecurityand emitx-payment-infoper op to get listed. - Discovery docs. #668 makes
/skill.md,/api/services.json, and ERC-8004 host-aware, but x402scan reads/openapi.json+/.well-known/x402at the origin root — those must be served free there. Today the operator wires that by hand. resource.urlexactness. The x402-verifier's 402 must carry the public https origin+path. We had to forceX-Forwarded-Proto: httpsand pin the service'sPUBLIC_BASE_URL. For a host-bound offer (#668spec.hostname) the verifier could derive the public origin automatically.- No network lint. A stale/testnet
payment.networksilently makes an offer un-indexable (cf. #612).obol sellcould warn whennetwork ∉ {base, solana}. - Templated path params. Operators get
%7B…%7Dresources on x402scan unless they use query params — worth a doc note / lint.
Proposal (companion to #668; small, declarative)
- Verifier: when an offer is host-bound (#668
spec.hostname), emit the 402resource.urlashttps://<hostname><path>(honoringX-Forwarded-Proto/X-Forwarded-Host), so probe == challenge holds by construction (closes gap 3 generically). - Discovery: serve (or transparently proxy the upstream's)
/openapi.json+/.well-known/x402free at the offer origin root, alongside the #668/#663 host-aware/skill.md+/api/services.json. (Relates to #563 "publish a swagger file on the tunnel" and #596 "llms.txt for tunnel storefronts".) obol sellvalidation / lint: warn ifpayment.network ∉ {base, solana}; optionally lint the upstream's/openapi.jsonfor paid ops that are missingx-payment-infoor that usesecurity: []/ apiKey schemes (the two skip cases), and flag literal{path}/%7B%7Dresources.- Docs: an "x402scan discovery contract" operator checklist — per-origin host,
x-payment-info(not apiKey security), free root/openapi.json+/.well-known/x402, exact httpsresource.url,base/solana, query-params over path-templates. - DNS UX (relates to #668 step 5): prefer a remotely-managed (token) tunnel so adding a Public Hostname auto-creates its DNS record — removing the manual CNAME step. Today the default is a locally-managed
config.ymlwith hand-created DNS, which is also where the "old catch-all rule shadows new hosts" ordering footgun comes from.
Field context (verified)
Three offers behind one cloudflared (currently locally-managed config.yml, explicit per-host ingress, manually-created DNS). Before addressing the above, x402scan listed 0 of ~80 paid endpoints — all classified apiKey/unprotected and skipped — even though every endpoint correctly returned a 402 challenge. After emitting x-payment-info, dropping the apiKey security scheme, serving free /openapi.json + /.well-known/x402 at the host root, pinning the public https resource.url, and moving path params to query params, all paid endpoints index cleanly, while infra/meta endpoints (/healthz, /openapi.json, MCP plumbing) are intentionally excluded via security: [].
References
- #668 host-based offer routing (substrate) · #669 storefront surface contract · #663 storefront branding /
/api/storefront.json· #671 paid chat via batch settlement · #664sell --acceptCLI bug · #656 / #562 multiple storefronts · #596 llms.txt for tunnel storefronts · #563 publish a swagger file on the tunnel · #612 base-sepolia EIP-712 domain - x402scan discovery:
Merit-Systems/x402scan(apps/scan/src/lib/url.ts,apps/scan/src/lib/discovery/*,@agentcash/discoveryinferAuthMode).
Filed via Claude Code from field experience running multiple x402 offers on obol-stack + reading the x402scan discovery source. https://claude.ai/code/session_01VquWN9UMaSHH7MHGcG8bw1
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 #668 and the existing obol sell, verifier, and discovery entry points; compare the stated behavior with x402scan's apps/scan/src/lib/url.ts, apps/scan/src/lib/discovery/*, and @agentcash/discovery inferAuthMode. Done means the proposed host-bound resource URLs, free discovery routes, network and OpenAPI linting, operator checklist, and DNS behavior are defined and validated.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, openapi
- Domain
- api, backend-api-design, cli, networking
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100