Skip to content

feat(sell): x402scan discovery correctness for host-bound offers — companion to #668 #679

Description

@bussyjd

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 (feat(sell): host-based offer routing — bind offers to dedicated hostnames (one tunnel, N origins) #668). ✔
  • 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/discovery inferAuthMode; 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)

  1. 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.
  2. Discovery docs. feat(sell): host-based offer routing — bind offers to dedicated hostnames (one tunnel, N origins) #668 makes /skill.md, /api/services.json, and ERC-8004 host-aware, but x402scan reads /openapi.json + /.well-known/x402 at the origin root — those must be served free there. Today the operator wires that by hand.
  3. resource.url exactness. The x402-verifier's 402 must carry the public https origin+path. We had to force X-Forwarded-Proto: https and pin the service's PUBLIC_BASE_URL. For a host-bound offer (feat(sell): host-based offer routing — bind offers to dedicated hostnames (one tunnel, N origins) #668 spec.hostname) the verifier could derive the public origin automatically.
  4. No network lint. A stale/testnet payment.network silently makes an offer un-indexable (cf. x402: base-sepolia advertises wrong EIP-712 domain name "USD Coin" (on-chain is "USDC") #612). obol sell could warn when network ∉ {base, solana}.
  5. Templated path params. Operators get %7B…%7D resources on x402scan unless they use query params — worth a doc note / lint.

Proposal (companion to #668; small, declarative)

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


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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions