walras

walras

An x402 facilitator for Stellar, plus a Bazaar discovery layer: any seller whose payment settles through walras is automatically listed in a searchable catalog — no registration step exists, anywhere.

Apache-2.0. Built against the pinned spec commit x402-foundation/x402 @ 17fc9890ade45a570a019352a3573391ad5d1e1f.


Status — everything below ran live on stellar:testnet, evidence linked per row

Component State
Facilitator — POST /verify, POST /settle, GET /supported proven — stock-client conformance, tx ac50c091…cc155, S2-2/S2-3
x402 repo e2e suite against walras (--families=stellar --testnet) 4/4 passS2-4
Settle-gated automatic cataloging + GET /discovery/resources (seven filters) proven — pay → listed, zero registration; hostile writes soft-dropped with machine reasons; S3-3/S3-4
GET /discovery/search — BASELINE FTS5/BM25 ranking (D-026), cursor pagination, truthful partialResults proven — recall@5 0.93, MRR@10 0.91 on a 28-query eval; S4-3/S4-4
One-command demo: search → pay → tx hash → auto-listed, plus three negative paths provenS5-2/S5-3
MCP server — an agent completes discover→pay using only search_resources + paid_call proven — a generic MCP client paid an http listing and a live MCP tool (which its own settlement auto-cataloged); S6-3
A real production tool (Policywright, SCF #44) paid by an agent with zero prior integration proven — its synthesize tool, served via the stock @x402/mcp gate, was found (empty→pay-to-list), paid (tx 3ff7309b…bf04), cataloged by that payment, re-found and re-paid by id (tx 980c3c59…8cc4); a poison-mcp hijack of its listing was rejected bazaar_listing_owned_by_other_payee; S6-4
Measured settlement fee 0.0022973 XLM (22 973 stroops) on the single-submitter path (F-069); the fee-bump path charges 23 073 stroops, +100 (F-086) — S2-3, S5-2, S6-3, S7-1

The facilitator wraps @x402/stellar's ExactStellarScheme, which already enforces every MUST in the exact-Stellar scheme spec. walras adds the HTTP surface, the configuration surface, the discovery layer, and an error model that guarantees a machine-readable code and a non-null human-readable reason on every rejection path. It adds no payment validation of its own — see docs/ARCHITECTURE.md for where that line is drawn and why.

Quickstart: docs to a paid, discoverable endpoint

Prerequisites: Node ≥ 22, pnpm 10 (corepack enable gets you pnpm), git, curl. Every duration below was measured in a timed fresh-clone walkthrough (S5-5): ≈ 1 min 40 s of machine time end to end, plus one manual faucet visit — the whole "docs to a paid, discoverable endpoint" path lands around 5–8 minutes, far inside the hour bar.

1. Clone and install (measured: 5 s on a warm pnpm store, S5-5; expect a couple of minutes cold)

git clone https://github.com/kunaldrall29/walras && cd walras
pnpm install

2. Create and fund testnet accounts (measured: 25 s automated, S5-5, + one manual faucet visit, ~2–5 min)

cp .env.example .env
node scripts/setup-accounts.mjs

The script creates the three accounts (facilitator submitter, seller, buyer), funds them with XLM via Friendbot, sets up the USDC trustlines, and prints a ready-to-paste .env fragment. Paste it into .env. One step cannot be automated: fund the buyer address the script prints with testnet USDC at faucet.circle.com (select Stellar — captcha-gated, so it needs a human).

3. Preflight (measured: 3 s, S5-5)

pnpm preflight     # submitter funded? RPC reachable?

4. The demo (measured: 65 s incl. the first build, S5-5 — five real settlements on stellar:testnet)

./scripts/demo.sh

One command, end to end: boots the facilitator against a fresh, empty catalog, boots a stock @x402/express seller with 11 paid routes, then

  1. shows the catalog is empty — nothing is listed until something settles;
  2. pays /weather once through the stock @x402/fetch client — the settlement itself creates the listing (EXTENSION-RESPONSES: {"bazaar":{"status":"success"}});
  3. seeds a few more routes the same way;
  4. runs an agent that searches /discovery/search?query=current+weather+in+Zurich, pays the top-ranked result, and prints the settled tx hash, its stellar.expert link, and the fee Horizon actually charged;
  5. re-fetches the catalog entry whose lastUpdated that settlement just bumped.

Every payment is the unmodified stock SDK; the seller declares its discovery metadata in route config and never registers anything.

5. The negative paths — each ends in a machine-readable reason

./scripts/demo.sh --tampered        # requirements claim double the signed amount
                                    #   → invalid_exact_stellar_payload_wrong_amount
./scripts/demo.sh --expired         # auth entry allowed to expire, then replayed
                                    #   → invalid_exact_stellar_payload_simulation_failed
./scripts/demo.sh --poison-catalog  # attacker's REAL settled payment claims the
                                    # seller's URL → settlement succeeds on-chain, the
                                    # catalog write is refused with
                                    #   bazaar_listing_owned_by_other_payee
                                    # and the seller's listing is byte-identical after

--poison-catalog is the demo worth watching twice: a structurally valid, actually settled on-chain payment is still not enough to touch another seller's listing.

Endpoints

Method Path Notes
POST /verify x402 v2 spec §7.1 request and response shapes
POST /settle §7.2. Verifies independently; on success, catalogs the payer's declared resource and reports the outcome in EXTENSION-RESPONSES
GET /supported §7.3. kinds, extensions (advertises bazaar), signers
GET /discovery/resources Bazaar catalog: seven filters (type, payTo, scheme, network, extensions, limit, offset), items + offset pagination
GET /discovery/search Ranked search: required query + five filters, resources + cursor pagination, truthful partialResults
GET /health Operational readiness. Not part of x402

A payment the scheme rejects returns 200 carrying isValid: false (or success: false) with the reason code, matching the reference facilitator. 4xx is reserved for requests that could not be interpreted as an x402 exchange at all. Every rejection path — payment, catalog, or query — carries a machine-readable code and a non-null human-readable reason.

Fees are sponsored by the configured submitter account: the buyer needs the payment asset and never spends a sequence number or pays a network fee.

MCP server: the Bazaar as two agent tools

packages/mcp-server exposes the whole discover→pay loop to any MCP client (Claude Code, or anything speaking the protocol) as two tools over stdio:

Every failure, on every path, is a structured {errorCode, reason} — facilitator codes pass through verbatim; the server never throws free-text at an agent.

./scripts/mcp-demo.sh   # facilitator + stock seller + a PAID MCP TOOL seller, then a
                        # generic MCP client (zero walras imports) does:
                        # search → pay by id → pay an MCP tool by (url, toolName)
                        # → that settlement auto-catalogs the tool → re-found in
                        # search → re-paid by its minted id. Three on-chain receipts.

Point an interactive client at it with:

{ "command": "node", "args": ["packages/mcp-server/dist/index.js"],
  "env": { "FACILITATOR_URL": "http://127.0.0.1:4021", "CLIENT_STELLAR_PRIVATE_KEY": "S..." } }

Without CLIENT_STELLAR_PRIVATE_KEY the server runs search-only and paid_call says so (walras_mcp_wallet_not_configured).

Configuration

SUBMITTER_SECRET is the only required variable. The full table, including FEE_MODE, DB_PATH, and the fee-bump options, is in docs/ARCHITECTURE.md §3.5 and .env.example.

Development

pnpm test              # single-SDK assertion, then the workspace suites (204 tests)
pnpm typecheck
pnpm check:licenses    # gate G-LIC — no copyleft in the dependency tree
pnpm eval:search       # search-quality eval: 28 labeled queries against the corpus
pnpm fixtures          # regenerate test payment payloads (needs .env)

Tests are hermetic: no .env, no secrets, and an in-process Soroban RPC double stands in for the network. What that double does and does not model is documented in docs/ARCHITECTURE.md §4.1 — results obtained through it are labelled as modelled rather than observed on-chain.

demo/ holds the pieces scripts/demo.sh orchestrates — stock seller, stock buyer, discovery agent, and the two hostile clients — plus tap.mjs, the transparent logging proxy behind the evidence wire transcripts, and e2e-proxy/, which exposes walras to the x402 repo's own e2e harness.

Documentation

Start here by role: sell · buy / build an agent · operate a facilitator — or the quickstart.

File Contents
docs/FACTS.md Every protocol, library, and API claim, with source and date. Nothing is asserted anywhere in this repo without a VERIFIED row here
docs/DECISIONS.md Each point where the spec, the SDK, the reference operator, or the RFP disagree, and what walras does about it
docs/EVIDENCE.md Captured transcripts and measurements. Every "works" claim points here
docs/ARCHITECTURE.md As-built architecture: components, settlement path, indexing invariant, topologies, docs pipeline
docs/MODELS.md Domain and data models, payment lifecycle, catalog ERD
docs/THREAT-MODEL.md STRIDE-lite over the payment and discovery trust boundaries, with the test behind each control
docs/api/openapi.yaml Generated from the Fastify route schemas — pnpm docs:gen
docs/reference/config.md Generated configuration reference (facilitator + MCP server)
docs/reference/errors.md Generated error-code registry (all four taxonomies)
docs/runbook.md Operator runbook: backup, keys, monitoring, degraded modes
docs/glossary.md · docs/faq.md Terms with citations; the questions the guides can't answer inline
docs/litepaper/walras-litepaper.md The design paper (one-page abstract)
docs/rfp.md The SCF RFP this project answers, verbatim

pnpm docs:gen regenerates everything marked generated plus the diagram SVGs; pnpm docs:check is the CI gate (drift, OpenAPI lint, stale SVGs, links, claims audit).