straitsX MCP Card Gateway
by anishnar
README.md
# straitsX-mcp-demo
Your AI agent pays with crypto and gets a **single-use virtual Visa card** back.
This repo is the StraitsX Phase-3 reference stack for the SMU hackathon: an agent-side **card
gateway** (pure x402 client — signs EIP-3009 `transferWithAuthorization` for XSGD on Avalanche
Fuji), a **demo store** to spend the card at, a **conformance suite**, and a flagship **shopping
agent** example. Everything talks to the **real StraitsX sandbox** from the first minute — no
simulators, no rebuild for event day.
- Participant guide: [`docs/PARTICIPANT_GUIDE.md`](./docs/PARTICIPANT_GUIDE.md)
## 1. Quickstart (under 10 minutes)
```bash
git clone <this repo> && cd straitsX-mcp-demo
npm install
npm run setup # copies .env, generates your agent key, prints the ADDRESS only
```
**Send that address to the organizers** — they fund it with Fuji XSGD (and whitelist it if the
event requires it). While you wait:
```bash
npm run stack # demo store :4030 + gateway :4010 (issuer = live StraitsX sandbox)
npm run doctor # green/red health table: env, live SSE MCP, unpaid cardapi 402 probe, RPC, XSGD balance
```
Then plug in your agent (next section) and issue your first card **against the real sandbox**:
```bash
npx tsx examples/shopping-agent/index.ts --scripted # no LLM key needed
```
Unfunded wallet? The scripted agent automatically runs the safe dry run (discovery → 402 decode →
EIP-3009 signing, paid POST never sent) and prints how to get funded. Once the wallet holds XSGD,
the same command issues a real card.
Amounts are **human token units** everywhere participants type them: `12` means 12.00 SGD on both
sandbox and production. The gateway signs the exact atomic amount quoted by the live 402
challenge — never a number computed client-side.
## 2. Zero-config agent hookup (`.mcp.json`)
The repo root ships a committed [`.mcp.json`](./.mcp.json) registering the card gateway as a
stdio MCP server. **Claude Code picks it up automatically** when you open the repo — approve the
server when prompted and you have the tools `get_virtual_card`, `get_wallet_status`,
`approve_payment` / `reject_payment`.
> Ask: *"Use get_virtual_card to issue me a 12 SGD card as cardholder 'Team Rocket', then give
> me the settlement transaction link."*
## 3. Connect any other client
**Claude Desktop** (`claude_desktop_config.json`):
```json
{
"mcpServers": {
"card-gateway": {
"command": "npx",
"args": ["tsx", "/ABSOLUTE/PATH/TO/straitsX-mcp-demo/packages/card-gateway/bin/gateway.ts", "--stdio"]
}
}
}
```
**Cursor** (`.cursor/mcp.json`): same shape as the repo-root `.mcp.json` — copy it verbatim.
**OpenAI Agents SDK / LangGraph / anything HTTP** — skip MCP entirely and use the gateway REST
surface (`npm run stack` exposes it on `:4010`):
```bash
curl -X POST http://127.0.0.1:4010/card \
-H 'content-type: application/json' \
-d '{"amount": 12, "cardholder_name": "Team Rocket"}'
```
In an OpenAI Agents SDK / LangGraph tool, that curl is your tool body: one POST, JSON in
(`amount`, `cardholder_name`), JSON out (`card_opaque_id`, `settlement_tx`, view URL). The
gateway holds your key; the model never sees it.
**Raw wire (curl only, no gateway):** [`examples/curl/walkthrough.md`](./examples/curl/walkthrough.md)
walks the whole x402 exchange by hand — probe, 402 decode, EIP-3009 sign, paid POST, settlement
header, idempotent replay.
## 4. Profiles
`NETWORK_PROFILE` in `.env` selects a checked-in profile from [`profiles/`](./profiles/). Secrets
never live in profiles — only in your gitignored `.env`. Default: `sandbox-live`.
| Profile | Card issuer | Settlement | Who pays gas | Use when |
|---|---|---|---|---|
| `sandbox-live` | **LIVE StraitsX sandbox** | REAL Fuji XSGD, you sign x402 | StraitsX relayer | hackathon main track (default) |
| `event-prod` | **LIVE StraitsX prod** | REAL mainnet XSGD, you sign x402 | StraitsX relayer | real cards (whitelisted wallet) |
## 5. Sandbox vs Production (the live StraitsX endpoints)
**Sandbox — Fuji XSGD, you sign x402** (`NETWORK_PROFILE=sandbox-live`, the default):
- MCP: `https://card.straitsx.ai/sandbox/sse` (SSE transport)
- Tools: `get_card_sandbox { wallet_address, cardholder_name, amount_sgd (5–30) }`,
`view_card_sandbox { card_opaque_id, settlement_tx, wallet_address }` → fresh **one-time** iframe URL
- Card API (discovered via MCP): `POST https://card.straitsx.ai/sandbox/cardapi/issue_card` —
unpaid returns HTTP 402 with the challenge in the `PAYMENT-REQUIRED` header and body; the
gateway signs EIP-3009 for Fuji XSGD (`0xd769…c2A5`) using **only** challenge-derived values
and retries with `PAYMENT-SIGNATURE`.
- You need: a funded Fuji XSGD wallet (`AGENT_PRIVATE_KEY` in `.env` — organizers fund your
address, send it in).
**Production — mainnet XSGD, you sign x402, real cards** (`NETWORK_PROFILE=event-prod`):
Production is the **same flow as sandbox** — same non-custodial x402 exchange, same EIP-3009
signing, same tool shapes. Only two things change: the chain is Avalanche **mainnet** and your
wallet must be whitelisted.
- MCP: `https://card.straitsx.ai/production/sse` (SSE transport)
- Tools: `get_card_prod { wallet_address, cardholder_name, amount_sgd (5–30) }`,
`view_card_prod { card_opaque_id, settlement_tx, wallet_address }` → fresh **one-time** iframe URL
- Card API (discovered via MCP): unpaid returns HTTP 402 exactly as in sandbox; the gateway signs
EIP-3009 for **mainnet** XSGD (`0xb2F8…096E`) on `eip155:43114` using **only** challenge-derived
values and retries with `PAYMENT-SIGNATURE`.
- You need: a funded **mainnet** XSGD wallet (`AGENT_PRIVATE_KEY` in `.env`) whose address an
organizer has **whitelisted**. No passphrase, no API key.
> These are real cards settled with real XSGD. The 5–30 SGD cap and the whitelist are enforced
> server-side.
## 6. The flagship example
[`examples/shopping-agent/`](./examples/shopping-agent/) — one file, scripted mode (no LLM key),
a real Anthropic tool-use chat mode, and a `--handoff` mode that funds a card and hands a human a
checklist for any real-merchant checkout (automating a real merchant's checkout is deliberately
unsupported: merchant ToS + card material is one-view secret).
## 7. Safety rails
- **The gateway is a pure spec client.** `payTo`, `asset`, `amount`, network, and the EIP-712
domain come only from the live 402 challenge. Gateway env holds endpoints, its key, and caps —
CI greps enforce zero hardcoded addresses in `packages/card-gateway/src`.
- **Caps and gates**: `MAX_CARD_AMOUNT` (human units) rejects over-sized challenges;
`ALLOWED_NETWORKS` pins CAIP-2 networks; `ISSUER_ORIGIN_ALLOWLIST` pins where paid POSTs may go;
`AUTO_PAY=false` forces a two-step `approve_payment` flow.
- **Card secrecy**: card material and one-time view URLs render once; the gateway redacts card
material and `PAYMENT-SIGNATURE` values from all logs. Never paste card details into a chat.
- **Never paid-probe the live cardapi from tests/scripts.** Automated checks are limited to MCP
discovery, unpaid 402 probes, and local signing (an unfunded paid POST still burns StraitsX
relayer gas at settlement). `npm run live-check` is the safe end-to-end proof — it hard-sets
the dry-run stop.
- **Keys**: `npm run setup` generates a throwaway event key and prints the address only. Don't
reuse personal keys.
## 8. Hackathon rules of engagement
- One wallet per team; submit your address for whitelisting and funding when asked.
- Cards are single-use and one-view. Lost the view? Issue a new card — there is no re-render.
- Rate limits and per-card caps (5–30 SGD on both sandbox and prod) are enforced server-side; the
gateway's own caps should be at or below them.
- Be nice to the shared live endpoints — `npm run doctor` before asking for help.
## 9. Troubleshooting
`npm run doctor` first — it decodes the live 402 and tells you asset/amount/network the issuer
actually quoted. Then see the symptom→fix table in
[`docs/PARTICIPANT_GUIDE.md`](./docs/PARTICIPANT_GUIDE.md#5-troubleshooting). Error codes returned
by the gateway map 1:1 to the spec §6 catalog (each carries `how_to_fix`).
## 10. Repo map
```
packages/protocol wire types, codec, errors, EIP-3009, domains (single source of truth)
packages/card-gateway participant sidecar: MCP (stdio/HTTP) + REST, pure x402 client
packages/conformance MUST/SHOULD conformance runner (challenge-driven, live-safe by default)
apps/demo-store stateless Luhn-only checkout + GET /catalog
examples/ shopping agent (flagship), Claude Code, curl walkthroughs
profiles/ checked-in per-scenario env wiring (see §4)
docs/ participant guide, event-day runbook, open asks
```
## 11. Scripts
| Command | What it does |
|---|---|
| `npm run setup` | copy `.env`, generate agent key (prints address only) |
| `npm run stack` | boot the demo store + gateway (issuer = live StraitsX endpoints) |
| `npm run doctor` | health table incl. live-sandbox SSE + unpaid cardapi probe + XSGD balance |
| `npm run live-check` | safe live dry run: discovery → 402 → sign, paid POST never sent |
| `npm run test` / `typecheck` | vitest + strict TS |
| `npm run conformance -- --target <url>` | run the conformance suite against any issuer (live-safe by default) |
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues