x402
Monetize any user project/service with the x402 payment protocol on platform networks (Base + Monad + Robinhood + X Layer + Solana; Starchild platform billing: pay_per_use / lifetime / weekly / monthly / quarterly / yearly / prepaid, plus multi-plan services), limited-time free promotions (amount-0 verify, no settle/debit), and pay other agents' x402 services. Use when the user wants to charge for an API/service, run a free promo on a paid listing, accept USDC from other agents, or call a paid x402 endpoint.
Other options
Summary
Monetize any user project/service with the x402 payment protocol on platform networks (Base + Monad + Robinhood + X Layer + Solana; Starchild platform billing: pay_per_use / lifetime / weekly / monthly / quarterly / yearly / prepaid, plus multi-plan services), limited-time free promotions (amount-0 verify, no settle/debit), and pay other agents' x402 services. Use when the user wants to charge for an API/service, run a free promo on a paid listing, accept USDC from other agents, or call a paid x402 endpoint.
Raw SKILL.md
32.7K bytes---
name: x402
version: 2.27.0
description: |
Monetize any user project/service with the x402 payment protocol on platform
networks (Base + Monad + Robinhood + X Layer + Solana; Starchild platform billing: pay_per_use /
lifetime / weekly / monthly / quarterly / yearly / prepaid, plus multi-plan
services), limited-time free promotions (amount-0 verify, no settle/debit),
and pay other agents' x402 services.
Use when the user wants to charge for an API/service, run a free promo on a paid
listing, accept USDC from other agents, or call a paid x402 endpoint.
author: starchild
tags: [x402, payments, base, monad, robinhood, xlayer, usdc, usdg, monetization, api, subscription, metered, agent-commerce]
delivery: script
metadata:
starchild:
emoji: πΈ
skillKey: x402
requires:
bins: [python3]
---
# πΈ x402 Monetization Skill
Turn any local HTTP service into a paid service on platform networks (Base +
Monad + Robinhood + X Layer; x402 V2 protocol, `exact` scheme, USDC/USDG via EIP-3009 β
buyer pays zero gas), and act as a buyer paying other agents' x402 services
with the user's Privy wallet.
**Architecture: reverse-proxy sidecar.** The gateway (`gateway/app.py`) sits in
front of the user's untouched service. One unified gateway, three billing modes
as config presets β the error contract is identical across all modes.
```
buyer agent ββ402/PAYMENT-SIGNATUREββ> gateway :840x ββplain HTTPββ> user service :port
β
facilitator (verify + settle on Base/Monad/Robinhood/X Layer/Solana) ββUSDC/USDGββ> user's Privy wallet
```
## Reference files (MUST read before the matching task)
Detailed material lives in `skills/x402/references/` β it is part of this
skill. Do NOT guess or improvise what these files cover:
| Before you⦠| MUST first `read_file` |
|---|---|
| deploy ANY selling mode beyond basic pay_per_use (full commands, prepaid & multi-plan contracts, admin tokens, templates, gateway lifecycle, always-on/update-mode) | `skills/x402/references/selling.md` |
| enable / debug / explain **limited-time free promotion** (amount=0, no settle, P1βP5 ACL, buyer free flow, resource isolation, verify matrix) | `skills/x402/references/selling.md` β **Limited-time free promotion** |
| free-promo errors (still charged, upstream 401, amount not 0, wrong service unlocked) | `skills/x402/references/troubleshooting.md` β **Limited-time free promotion** |
| debug ANY other error (facilitator verify errors, error contract, security model, port ownership, proxy) | `skills/x402/references/troubleshooting.md` |
| use the session EOA signer, fund a buyer wallet, or pay on a non-Base-USDC chain/token | `skills/x402/references/buying-advanced.md` |
## Limited-time free promotion (seller quick rules)
For a **listed paid** marketplace service the owner can open a free window
(max 90 days). Full playbook: `references/selling.md` β **Limited-time free promotion**.
Agent MUST:
1. **Not blind-PUT** free-promo from a UI prompt β run P1βP5 self-check first.
2. Remember free = **wallet identity + amount 0 verify + no settle/debit**, not anonymous
and not a free lifetime subscription after the window.
3. On **platform gateway**: amount-0 challenge and skip settle/debit are built-in.
4. On **custom upstream keys (P3/P4)** or **self-built x402**: you MUST patch ACL /
force `amount="0"` / skip settle β see selling.md Step C.
5. Use x402 **`resource` (path)** for access isolation β never invent `service_id` on the wire.
6. After enable: verify free-status, 402 amount=0, free call without settlement, then
confirm paid path returns after cancel/expiry.
Buyer tip: if unpaid 402 shows `accepts[].amount == "0"`, sign **0** (no USDC needed);
do not force list-price signing during free.
## Sell β monetize a service (quick start)
```bash
FAC=https://starchild-x402-facilitator.fly.dev
# pay_per_use: verify -> settle on EVERY request (simplest mode)
# --networks defaults to "all" (Base + Monad + Robinhood + X Layer + Solana mainnet); the 402 challenge
# returns a multi-accepts list β the buyer picks one chain per payment.
python3 skills/x402/scripts/monetize.py --name my-api --upstream-port 5173 \
--mode pay_per_use --price 0.01 --facilitator $FAC
# lock to a single chain (custom)
python3 skills/x402/scripts/monetize.py --name my-api --upstream-port 5173 \
--mode pay_per_use --price 0.01 --networks eip155:8453 --facilitator $FAC
```
Platform modes follow the community-gateway billing contract: 402 JSON body
with `accepts` as a **list** (multi-accepts, one entry per network β the buyer
picks one chain per payment), `accepts[].pricingModel`, facilitator is the
single source of truth for "already paid", every settle auto-callbacks
community-gateway for records.
## Billing mode decision table
| Mode | Tier | Buyer UX | When |
|------|------|----------|------|
| `pay_per_use` | **platform** | X-PAYMENT each request, settled every call | simple data endpoints, agent-to-agent one-shots |
| `lifetime` | **platform** | pay once, permanent access (facilitator-verified) | one-time unlock, buyout pricing |
| `monthly` | **platform** | pay once per natural month | SaaS-style subscriptions |
| `weekly` / `quarterly` / `yearly` | **platform** | fixed-length pass: 7 / 90 / 365 days from newest payment | short trials, annual discounts |
| `prepaid` | **platform** | one on-chain deposit β off-chain debit per call | high-frequency / sub-cent / usage-metered APIs |
| `payperuse` | legacy | SDK V2 headers, pay per request | pre-2.0 deployments |
| `subscription` | extended | x402 top-up β API key + N credits, 1 credit/call | prepaid credits, avoids per-call payment latency |
| `metered` | extended | like subscription, route-weighted units | mixed cheap/expensive endpoints (LLM calls etc.) |
| `timepass` | extended | x402 payment β N-day pass on an API key | fixed-duration passes (non-natural-month) |
β οΈ lifetime/monthly/weekly/quarterly/yearly REQUIRE `--facilitator-admin-token`
(fail-closed at startup). Multi-plan: `--plan MODE=PRICE` (repeatable).
β **MUST read `references/selling.md` BEFORE deploying any of these modes** β
it has the exact commands, contract details, and template list.
Output includes `gateway_port`. **Expose the GATEWAY port, not the upstream**
(via `preview` or community-publish). `pay_to` defaults to the user's Privy
EVM wallet β revenue lands there directly.
Registry: `/data/workspace/.x402/services.json`; per-service config/log/state:
`/data/workspace/.x402/<name>/`.
## Networks & facilitators
The platform supports multiple chains. By default a service follows the
**platform mainnet full set** (`--networks all`, the default) β currently
Base + Monad + Robinhood + X Layer + Solana. The 402 challenge returns a multi-accepts list (one
entry per chain); the buyer picks one chain per payment. Lock to specific
chains with `--networks eip155:8453,eip155:143,eip155:4663,eip155:196` (custom).
| Network | CAIP-2 | Stablecoin | EIP-712 name | Gas |
|---------|--------|------------|--------------|-----|
| Base mainnet | `eip155:8453` | USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | `USD Coin` | ETH (platform-paid) |
| Base Sepolia (testnet) | `eip155:84532` | USDC `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | `USDC` | ETH (platform-paid) |
| Monad mainnet | `eip155:143` | USDC `0x754704bc059f8c67012fed69bc8a327a5aafb603` | `USDC` | MON (platform-paid) |
| Monad testnet | `eip155:10143` | USDC `0x534b2f3A21130d7a60830c2Df862319e593943A3` | `USDC` | MON (platform-paid) |
| Robinhood mainnet | `eip155:4663` | USDG `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` | `Global Dollar` | ETH (platform-paid) |
| Robinhood testnet | `eip155:46630` | USDG `0x7E955252E15c84f5768B83c41a71F9eba181802F` | `Global Dollar` | ETH (platform-paid) |
| X Layer mainnet | `eip155:196` | USDC `0x74b7F16337b8972027F6196A17a631aC6dE26d22` | `USD Coin` | OKB (platform-paid) |
| X Layer testnet | `eip155:1952` | USDC `0x74b7F16337b8972027F6196A17a631aC6dE26d22` | `USD Coin` | OKB (platform-paid) |
| Facilitator | Networks | When |
|-------------|----------|------|
| **platform** (`https://starchild-x402-facilitator.fly.dev`, the default for mainnet; override via `X402_FACILITATOR_URL` or `--facilitator`) | Base + Monad + Robinhood + X Layer + Solana mainnet | production β platform settler pays gas on every chain |
| `https://x402.org/facilitator` (default for testnet) | Base Sepolia + Monad + Robinhood + X Layer testnet | testing only β REJECTED for mainnet (startup guard) |
The platform facilitator handles /verify + /settle on every supported chain;
its settler key only pays gas β fund flow is fixed by the buyer's signature and
can never touch user funds. **Gas/fees are paid by the platform on every chain
(ETH on Base, MON on Monad, ETH on Robinhood, OKB on X Layer, SOL on Solana) β never passed to the service
provider.** Safety: mandatory `eth_call` simulation before spending gas,
per-payer rate limiting, authorization-nonce idempotency.
Testnet USDC (Base Sepolia): `0x036CbD53842c5426634e7929541eC2318f3dCF7e`,
faucet at faucet.circle.com. Prices auto-convert: `$0.01` β `10000` atomic USDC units.
### Multi-chain config (`networks_mode`)
| Config | Behavior |
|--------|----------|
| `--networks all` (default) or `networks_mode: all` | 402 accepts = platform mainnet full set (Base + Monad + Robinhood + X Layer + Solana); testnet full set when facilitator is x402.org |
| `--networks eip155:8453` or `networks_mode: custom` + `networks: [...]` | 402 accepts = exactly the listed chains (custom lock) |
| (no networks field) | same as `all` |
Extending the platform with a new chain only requires updating `ASSETS` +
`MAINNET_NETWORKS` in `platform_modes.py` (and the facilitator's `KNOWN_ASSETS`)
β every `all`-configured service picks up the new chain on its next 402, with
**zero business-table updates**.
β οΈ **Robinhood USDG note**: the USDG contract uses a Diamond proxy with a
non-standard EIP-712 domain. The facilitator reads `DOMAIN_SEPARATOR()` from
chain for verification. Buyer-side raw-digest signing is a TODO (see
`client.py` `_CHAIN_DOMAIN_SEP_CHAIN_IDS`); standard typed-data signing is
used for now and works if the on-chain domain matches the metadata.
Historical Base-only configs are migrated once (SQL/config sweep to
`networks_mode: all`); `resolve_networks` does NOT guess "bare Base means all".
## Keepalive (register once per machine)
One watchdog guards ALL x402 gateways (cloudflare-skill pattern: idempotent,
prints only on state change β silent scheduled task when healthy):
1. Boot: append to `/data/workspace/setup.sh`:
`bash /data/workspace/skills/x402/scripts/keepalive.sh || true`
2. Watchdog: `scheduled_task(action="schedule", schedule="every 10 minutes", command="bash skills/x402/scripts/keepalive.sh", deliver="origin")` β empty output = silent.
β οΈ Use the RELATIVE path (`skills/x402/...`), never `/data/workspace/skills/...` β the
scheduler's path sanitizer strips `workspace/` from absolute commands, mangling them
into a nonexistent `/data/skills/...` and the task fails every run. After registering,
verify with `get_log` that the first execution succeeds.
Gateway down β restarted from its config. Upstream down β reported but NOT
restarted (upstream has its own supervisor via previews β don't fight it).
A LISTED paid service must stay reachable 24/7 (idle suspend + auto-update
restarts work against this) β **read `references/selling.md` Β§ Always-on
availability BEFORE publishing** for the update-mode check/flip flow.
## Buy β pay other agents' x402 services
### Quick Start β preset CLI scripts (use these FIRST, no custom code)
One process per step, ONE JSON object on stdout β no per-call LLM reasoning
about balances/signatures/rail selection needed:
```bash
# 1. FREE probe: is it x402? which rails? (never pays)
python3 skills/x402/scripts/discover.py --url https://host/api/thing
python3 skills/x402/scripts/discover.py --query "weather api" # catalog search
# 2. Preflight: signer + policy + live USDC per rail + recommended_rail
python3 skills/x402/scripts/preflight.py --usd 0.05
# 3. Pay β resolve β probe β pick final rail β preflight THAT rail's own
# price+network β pay the SAME resolved URL (rail pinned; --max-usd is
# only a spend ceiling). Exit 2 = blocked/over-cap/network-not-accepted,
# nothing signed. --network not in accepts fails fast (no silent fallback).
python3 skills/x402/scripts/buy.py --url https://host/api/thing --max-usd 0.05
python3 skills/x402/scripts/buy.py --url https://host/x402/q \
--json '{"q":"hello"}' --max-usd 0.10 --network eip155:8453
```
Raw client (advanced / custom flows):
```bash
python3 skills/x402/client.py GET https://host/api/thing
X402_MAX_ATOMIC=50000 python3 skills/x402/client.py POST https://host/x402/topup
```
### Pre-flight FIRST (one round-trip, not serial walls)
Before asking the user to confirm any purchase, run
`client.payment_preflight(amount_atomic, networks=<the 402's accept
networks>)` and present ALL blockers together. It checks in one shot:
β signers reachable, β‘ wallet policy sanity β an ENABLED policy with EMPTY
rules is deny-all and rejects every signature (new Privy wallets should be
allow-all; if found, propose a policy card and get it signed BEFORE paying),
β’ USDC balance per candidate rail (direct RPC). If no rail is funded, offer
every option at once β pay from another funded chain, bridge, or
fiat-onramp β never a bare "fund the wallet" that leads to the next wall.
Never let the user fix funding, then discover a policy block, then a
dependency error in three separate round-trips.
**Dependencies**: the buyer path needs `web3>=7`. If the machine pins an
older web3 (trading bots often pin 6.x), NEVER upgrade it globally β run
`bash skills/x402/scripts/ensure_env.sh` (zero-interaction, idempotent): it
creates an isolated `.venv-x402` immune to `PIP_USER`/`PYTHONPATH`/user-site
interference (venv built `--without-pip` + `PYTHONNOUSERSITE=1`) and prints
the interpreter to use on the last stdout line. `payment_preflight` detects
the version conflict and points here. Do this at setup, not mid-purchase.
### Multi-chain selection (buyer receives multiple accepts)
When a service returns 402 with `accepts` as a **list** (one entry per
network, e.g. Base + Monad + Robinhood + X Layer + Solana), the buyer Agent does NOT need to ask the user
which chain to use β **chain selection is fully automatic** in both
`paid_request` and `bazaar_pay`. The logic:
1. **`payment_preflight`** (run BEFORE confirming): checks USDC balance on
every candidate rail and returns `funded_rails[]`. If multiple rails are
funded, the automatic selector picks the best one. If NO rail is funded,
present ALL funding options at once (bridge, on-ramp, pay from another
chain) β never ask "which chain?" when the answer is "none of them".
2. **Automatic rail ranking β balance-aware, Base default** (shared by
`bazaar.probe_402` display and `paid_request` payment β so the chain
shown at probe time IS the chain actually paid). Selection order:
- β **Funded rails first**: live USDC balance β₯ amount on that rail
(direct RPC, cached ~60s). A signature-friendly chain with 0 USDC is
never picked over a funded one, so settlement cannot fail for lack
of balance on the selected rail.
- β‘ **Base (`eip155:8453`) is the default chain** when funding ties
(platform wallets hold USDC on Base).
- β’ Static tiebreak (`network_rank`): Base (primary USDC chain) β Solana /
other EVM without EIP-7702 delegation (plain ECDSA, e.g. Monad) β EVM with
delegation code (Kernel EIP-1271). `signer_mode="eoa"`: Solana
excluded, all EVM equal.
- β£ Within the same rank, cheaper amount / first accept wins.
- Balance check failure = unknown, NOT unfunded (fail-open to ranking).
3. **User-specified chain** (`prefer_network`): when the user explicitly
asks to pay on a specific chain (e.g. "pay on Base", "use Monad"),
pass `prefer_network="eip155:8453"` (or `"eip155:143"` for Monad) to
`paid_request` or `bazaar_pay`. This overrides the automatic ranking
and selects the matching accept first (if present in the 402 challenge).
Also available via env `X402_PREFER_NETWORK`. If the specified chain is
not in the service's accepts, the automatic ranking takes over as
fallback. Common CAIP-2 ids: Base `eip155:8453`, Monad `eip155:143`,
Solana `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`.
```python
# User says "pay on Base"
paid_request("GET", url, prefer_network="eip155:8453")
# User says "pay on Monad"
bazaar_pay(url, prefer_network="eip155:143")
# User says "pay on Solana" / "use Solana"
paid_request("GET", url, prefer_network="solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp")
# Via env (CLI)
X402_PREFER_NETWORK=eip155:8453 python3 skills/x402/client.py GET https://host/api
```
4. **Prepaid deposit retry**: if the first payment triggers a 402
`insufficient_balance` (prepaid top-up needed), the retry stays on the
SAME chain the buyer already chose (`prefer_network` parameter in
`_pick_accept`) β it never jumps to a different chain mid-flow.
5. **Result verification**: every `paid_request` result includes `network`
(the chain actually used) and `signer_type` β always report these to
the user so they know where the payment landed.
`paid_request` auto-detects BOTH 402 flavors: V2 header challenge
(PAYMENT-REQUIRED β x402 SDK path) and the platform JSON-body challenge
(`accepts.pricingModel` β manual EIP-3009 sign β retry with `X-PAYMENT`).
For lifetime/monthly services, repeat calls within the paid period verify but
do NOT settle β the result has `paid: true` with no new on-chain tx.
**Buyer signer = Privy wallet by default** (`signer_mode="auto"`); smart
accounts are detected and signed via an ERC-1271-compatible path
automatically. **Multi-accept routing prefers rails where Privy signs a
plain signature** (max facilitator compatibility, no EOA funding needed):
β Base (primary USDC chain) β β‘ Solana / EVM chains where the payer has no
EIP-7702 code (plain ECDSA, e.g. Monad) β β’ EVM chains with delegation code (Kernel
EIP-1271, e.g. Base) as last resort β spec-correct but some seller
facilitators reject it; for Base-only sellers that do, use
`signer_mode="eoa"`. Do NOT revoke the wallet's delegation (it powers gas
sponsorship). `auto` is FAIL-CLOSED: if the Privy signer cannot be
initialized, `paid_request` raises instead of paying from a different
identity β allow the session-EOA fallback only explicitly via
`allow_fallback_eoa=True` / env `X402_FALLBACK_EOA=1` / `signer_mode="eoa"`.
Every result includes `signer_type` (`"privy"` | `"session_eoa"`); an
opted-in fallback also sets `signer_warning` β check them to confirm which
identity actually paid. β οΈ The two signers are DIFFERENT payer identities:
subscriptions/prepaid balances do NOT carry over between them.
Similarly, EVM and Solana prepaid balances are separate (different
pay_to addresses) β a deposit on Solana cannot be spent on EVM and
vice versa. The gateway sums both balances for display but debits
from the chain where the deposit was made.
β **EOA funding steps and signer internals: read
`references/buying-advanced.md` BEFORE using the session EOA.**
**Spend guard**: refuses to sign above `X402_MAX_ATOMIC` (default
1_000_000 = 1 USDC). β οΈ Signing = spending real money once settled β confirm
with the user before paying unfamiliar services or raising the cap. Paid
response bodies are returned in FULL; unpaid/error bodies are capped at 2000
chars (env `X402_BODY_MAX`, 0 = unlimited). Result includes
`settlement.transaction` (on-chain tx hash) β report it and verify per
transaction-verification rules.
### Payment ledger (every payment is recorded locally)
`client.py` appends every payment it signs to
`$WORKSPACE/.x402/payments.jsonl` (override path: env `X402_LEDGER`). Each
line is one JSON event: `signed` (authorization submitted β url, amount,
payTo, payer, caller) and `result` (HTTP status, paid, settlement tx). The
`caller` field identifies who spent the money (`SC_CALLER_ID` / `JOB_ID` /
pid), so payments made from background sessions are attributable too.
To answer "where did this USDC go": read the ledger first, then reconcile
against the wallet's on-chain USDC transfers β every outgoing transfer must
match a ledger line. Ledger writes are best-effort and never block a payment.
### Spending rules for automated sessions
- A background / scheduled / spawned session MUST NOT make x402 payments
unless its task explicitly grants a budget; set `X402_MAX_ATOMIC` to that
budget for the session.
- Every payment an automated session makes MUST appear in its final output
(amount, url, settlement tx) β a payment only in the ledger is auditable
but still counts as unreported work.
- On any 4xx payment rejection, do not retry with a fresh payment: each
retry can spend again. Diagnose first.
## Public paid URL (Cloudflare Monetization Gateway parity)
Make any local service a PUBLIC paid API (charge any caller for any resource,
no accounts / API keys needed β same capability set as Cloudflare's
Monetization Gateway, running on your own machine):
1. `python3 skills/x402/scripts/make_public.py --name my-api --upstream-port <port> --mode payperuse --route 'GET /api/*=$0.01' --pay-to <wallet>` β scaffolds `output/my-api/start.py` + config (defaults to `--networks all`: Base + Monad + Robinhood + X Layer + Solana)
2. `preview(action='serve', dir='output/my-api', command='python3 start.py', port=<gateway_port>)` β note: start the upstream in the same command if it isn't already running
3. `community-publish` skill β `publish_preview(preview_id, slug='my-api')` β public URL
4. Price discovery is built in: `GET <public-url>/.well-known/x402` returns machine-readable routes/prices/payTo/networks (Bazaar-compatible shape; `accepts` is a multi-chain list).
**A public URL is NOT a marketplace listing.** Steps 1β4 only make the
service reachable β the Service Marketplace will show nothing (or "free")
until you complete the LIST chain (community-publish skill):
5. `create_paid_service(name=..., service_type=..., api_endpoint=<public paid
route>, provider_wallet=..., pricing_model=..., price=...,
pricing_options=[...] for multi-plan)` β service record.
6. `publish_service(service_id)` β live paid listing. Review is ADVISORY:
optionally run `submit_for_review(service_id)` + `get_review_status()`
for a 5-check self-report (402 reachable, price consistency, x402
validity, response match, doc completeness) β show it to the owner; it
never blocks publishing.
β οΈ The LIST chain MUST go through the community-publish skill functions β
NEVER hand-build a gateway payload. `create_paid_service()` makes every paid
field a required argument; a hand-built payload with missing fields is
rejected by the gateway (and on older gateways silently created a FREE
service that later fails review with a misleading 400).
Skipping 5β6 is the #1 cause of "why does my paid service show as free /
not appear in the marketplace".
## Paid Project: two forms
A **paid project** is a Starchild project that charges for access. There
are two forms β the platform supports both, and they share the same
`service_type="paid_project"` + `project_slug` listing structure:
### Form 1: Entire page behind paywall (user implements access control)
The page itself requires payment to access. The user (service provider)
implements their own access control β a login-like component that checks a
payment credential before serving content.
**Platform responsibility:** publish the project + list the paid API
endpoint on the marketplace. The x402 gateway handles the payment protocol
(402 challenge β settle β credential).
**User responsibility:** implement the access control interceptor in their
own app:
- A paywall/login component on the frontend (credential input β localStorage)
- A backend endpoint that validates the credential and returns content
- The credential is issued by the user's own API after a successful x402
payment β the user's API returns the credential to the buyer
**Flow:**
```
1. Visitor opens the page β sees a paywall (user's frontend code)
2. Visitor pays via x402 (Agent or direct) β user's API returns a credential
3. Visitor enters the credential β user's backend validates it β serves content
4. Credential cached in localStorage β subsequent visits skip the paywall
```
**What the platform provides:**
- x402 gateway: handles the 402 payment challenge + on-chain settlement
- `/x402/topup` endpoint: buyer pays, gets an API key (timepass/subscription
modes) or the x402 signature IS the credential (platform modes)
- `/x402/balance` endpoint: the user's app CAN use this to check if an API
key is valid (optional β the user can also implement their own validation)
**What the user implements (their own code, their own logic):**
- Frontend: paywall UI, credential input, localStorage caching
- Backend: credential validation (can call gateway `/x402/balance`, or
implement their own validation logic, or use the x402 signature directly)
- The "how to pay" documentation on the paywall page
#### "How to pay with Agent" documentation
The user's paywall page should include a "How to pay" section explaining
how buyers can obtain an access credential via x402 payment. The Agent
should generate this documentation based on the service's actual pricing,
duration, and URL β do NOT copy a fixed template. The documentation should
cover:
- The price and payment networks (e.g. "$2.99 USDC/USDG on Base, Monad, or
Robinhood β buyer picks one chain per payment")
- How to pay via Agent (x402 client call to `/x402/topup`)
- How to pay directly (any x402-compatible client with `X-PAYMENT` header)
- What the buyer receives after payment (an `api_key` credential)
### Form 2: Free page + paid API (Flow D)
The page is free to browse (intro/docs/landing page), but API calls cost
money. This is Flow D β see the community-publish skill for details. The
upstream app serves the free intro page at `/` and the paid API at `/api/*`.
### Summary: paid project = free project + paid API (same pattern)
Both forms use `service_type="paid_project"` + `project_slug`. The
difference is only in what the user implements:
| Form | Page access | API access | User implements |
|---|---|---|---|
| Entire page paid | Paywall (user's access control) | Paid via x402 | Paywall UI + credential validation |
| Free page + paid API | Free (intro/docs page) | Paid via x402 | Nothing extra (gateway handles it) |
The platform (x402 gateway + community-gateway) handles the payment
protocol and marketplace listing in both cases. The user only needs to
implement the access control interceptor for Form 1.
## Consuming any x402 service from just a URL
Given ONLY a service URL (no docs, no guidance), onboard and verify it with
this sequence β everything needed is self-describing in the protocol:
1. **Discover** (no payment): `GET <url>` with no payment headers. A 402 response
IS the price sheet: `accepts` is a **list** (multi-accepts, one entry per
network) β each entry has `amount` (atomic USDC), `pricingModel`,
`network`, `asset`, `payTo`. Pick one chain to pay on. On multi-plan
services a `plans` map carries every option's accepts. Optionally
`GET <base>/.well-known/x402` for a machine-readable index of all
routes/prices/networks.
- If `amount == "0"` (or marketplace free-status is active), the service is in a
**limited-time free promo**: sign amount 0 for identity; expect **no settle**.
This is temporary β after `free_end` the same URL returns list price again.
2. **Probe plans** (no payment, multi-plan only): repeat the unpaid GET with
`X-Pricing-Model: <plan>` β each 402 quotes that plan's exact amount.
An unknown plan returns HTTP 400 listing the valid ones.
3. **Pay & call**: `client.paid_request("GET", url, max_amount_atomic=<cap>)`
handles the whole flow (402 β EIP-3009 sign β retry with X-PAYMENT).
Select a plan with `pricing_model="<plan>"`. Payer = the Privy wallet by
default (`signer_mode="auto"`). For **paid** quotes the wallet must hold USDC
on the service's network; for **free-promo amount 0** a signing wallet is enough
(no list-price USDC required). The session EOA needs funding ONLY if you pin
`signer_mode="eoa"` or the result reports `signer_type: "session_eoa"` (see
Buyer side above). cap = your spend guard (may be 0 during free). Confirm with
the user before paying real money. Check `signer_type` in the result: it tells
you WHICH identity actually paid. If the Privy signer is unavailable, `auto`
raises (fail-closed) rather than silently paying from the session EOA; a
`signer_warning` appears only when the fallback was explicitly allowed.
4. **Verify billing semantics** (subscription modes): call again β the result
must be 200 with NO new settlement (`paid: true`, no new tx). On multi-plan
services, requesting a different plan while holding one must also NOT
re-charge. `settlement.transaction` from step 3 is the on-chain proof β
report it and verify per transaction-verification rules. During free promo,
step 3 should also show **no** new settlement even on first visit.
The same sequence doubles as a smoke test of any x402 deployment: steps 1β2
are free and validate the challenge contract; steps 3β4 validate settlement
and access accounting end-to-end.
## Discover & pay β marketplace first, then CDP
Buyer flow is **marketplace-first**. Do not collapse tracks; do not scrape
third-party x402 directories.
| Step | Rule |
|---|---|
| **1. Find** | Prefer `discover_services(query)` or `community-publish.explore_marketplace`. CDP (`bazaar_search`) is fallback when marketplace has no hit. |
| **2. Resolve pay URL** | Listed services β `community.iamstarchild.com/proxy/{service_id}` (+ path) or internal `/{user}-{slug}/...`. Never pay the raw list external URL when a proxy exists. |
| **3. Pay** | `bazaar_pay(url)` re-resolves to marketplace proxy, then `probe_402` β `paid_request`. Community **transparent-proxies** and **books on HTTP 200**. Unlisted external URLs are **refused** β list the service on the marketplace first, then pay its proxy URL. |
```python
import sys; sys.path.insert(0, "/data/workspace/skills/x402")
from bazaar import discover_services, resolve_marketplace, probe_402, bazaar_pay
discover_services("weather", limit=5) # marketplace first, CDP fallback
resolve_marketplace("https://example.com/api") # β pay_url via community when listed
bazaar_pay(url, max_usd=0.01) # proxy-first pay; refuse non-standard
```
`probe_402` / `bazaar_pay` only pay `standard-v2` **exact** on known native
USDC rails (see `bazaar.PAYABLE_USDC`): Base, Polygon, Arbitrum, World Chain,
Solana mainnet, Monad, Avalanche, Ethereum, Optimism, Linea, Celo, Unichain.
Multi-accept β prefer Privy-native rails (Base β Solana/no-code EVM β delegated
EVM; see buyer signer section). The same selector (`client.network_rank`)
drives bazaar's `probe_402` ordering, so the rail shown at probe time is the
rail `auto` actually pays. `signer_mode="eoa"` never registers the SVM signer
and hard-filters Solana accepts β the pinned session-EOA payer identity is
never substituted. Results and ledger lines carry the ACTUAL selected
`network`/`payer`/`signer_type` (Solana settlements report the Privy Solana
address as payer). Solana signs via Privy `wallet_sol_sign` (base64
raw message). Not yet: EURC/alt-stables, testnets. Other shapes (`wrong-rail`,
`tx-hash`, `non-standard`, `no-payment`) refused before any signature. Buyer
signs; seller facilitator settles.
## Errors & diagnostics
Payment or gateway failing β **MUST read `references/troubleshooting.md`**
(error contract, facilitator verify errors, security model, port ownership
checks, proxy config). Quick e2e check anytime:
`python3 skills/x402/scripts/verify_setup.py` (fund-free; `--funded` adds a
real settlement test).
## Setup
Python SDK is `x402` v2.10+. Deps are NOT auto-installed: run
`bash skills/x402/setup.sh` once per machine (also append it to
`/data/workspace/setup.sh` so restarts reinstall).

