You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#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.
An OpenAPI operation is treated as paid only if it carries x-payment-info ({ price: {mode,currency,amount}, protocols: [{x402:{}}] }). Operations marked security: [] or referencing an apiKey-typed security scheme are classified unprotected / apiKey and skipped (@agentcash/discoveryinferAuthMode; SKIP_AUTH_MODES = {unprotected, apiKey}). This is the biggest footgun: the conventional way to model x402 in OpenAPI — an apiKey securityScheme (header X-PAYMENT) — makes every paid endpoint invisible to x402scan.
It reads /openapi.json and /.well-known/x402 FREE at the origin root.
It probes each endpoint and requires the 402 challenge's resource.url to byte-match the probed URL (scheme included).
It indexes base + solana only (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-info story. A service whose OpenAPI marks paid ops with an apiKey security scheme (the natural modeling) is silently skipped. We had to drop security and emit x-payment-info per op to get listed.
obol sell validation / lint: warn if payment.network ∉ {base, solana}; optionally lint the upstream's /openapi.json for paid ops that are missing x-payment-info or that use security: [] / apiKey schemes (the two skip cases), and flag literal {path} / %7B%7D resources.
DNS UX (relates to feat(sell): host-based offer routing — bind offers to dedicated hostnames (one tunnel, N origins) #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.yml with 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-managedconfig.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: [].
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)
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./openapi.jsonand/.well-known/x402FREE at the origin root.resource.urlto byte-match the probed URL (scheme included).base+solanaonly (testnets are skipped).{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)
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./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 (feat(sell): host-based offer routing — bind offers to dedicated hostnames (one tunnel, N origins) #668spec.hostname) the verifier could derive the public origin automatically.payment.networksilently makes an offer un-indexable (cf. x402: base-sepolia advertises wrong EIP-712 domain name "USD Coin" (on-chain is "USDC") #612).obol sellcould warn whennetwork ∉ {base, solana}.%7B…%7Dresources on x402scan unless they use query params — worth a doc note / lint.Proposal (companion to #668; small, declarative)
spec.hostname), emit the 402resource.urlashttps://<hostname><path>(honoringX-Forwarded-Proto/X-Forwarded-Host), so probe == challenge holds by construction (closes gap 3 generically)./openapi.json+/.well-known/x402free at the offer origin root, alongside the feat(sell): host-based offer routing — bind offers to dedicated hostnames (one tunnel, N origins) #668/feat(sell): add storefront branding profile #663 host-aware/skill.md+/api/services.json. (Relates to Idea: Publish a swagger file on the tunnel? #563 "publish a swagger file on the tunnel" and Make an llms.txt for tunnel storefronts #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.x-payment-info(not apiKey security), free root/openapi.json+/.well-known/x402, exact httpsresource.url,base/solana, query-params over path-templates.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 classifiedapiKey/unprotectedand skipped — even though every endpoint correctly returned a 402 challenge. After emittingx-payment-info, dropping the apiKeysecurityscheme, serving free/openapi.json+/.well-known/x402at the host root, pinning the public httpsresource.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 viasecurity: [].References
/api/storefront.json· Architecture proposal: storefront paid chat via x402 batch-settlement channels #671 paid chat via batch settlement · sell --accept: cli/v3 comma-splits the value → "network is required" (multi-currency offers unreachable from CLI) #664sell --acceptCLI bug · Multiple storefronts in one stack #656 / Customisable storefronts #562 multiple storefronts · Make an llms.txt for tunnel storefronts #596 llms.txt for tunnel storefronts · Idea: Publish a swagger file on the tunnel? #563 publish a swagger file on the tunnel · x402: base-sepolia advertises wrong EIP-712 domain name "USD Coin" (on-chain is "USDC") #612 base-sepolia EIP-712 domainMerit-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