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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions