Pay for x402-priced APIs with feeless, sub-second Nano (XNO) micropayments from a local wallet.
Runs on your machine from its package; the steps are below.
At most: moves money, runs code or commands, deletes data, or sends messages for you.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
uvx feeless402
MCP client config
{
"mcpServers": {
"nano-pay": {
"command": "uvx",
"args": [
"feeless402"
]
}
}
}Self-custodied Nano (XNO) wallet + x402 payment client and merchant
server, built for AI agents. Client spends; nano-pay serve earns — paid
endpoints, on-ledger verification/settlement (no facilitator), a starter
faucet, and the railHint x402 extension that teaches visiting agents how
to onboard (see SPEC-railhint.md). Site: site/index.html + site/llms.txt.
The pitch, in one number: the same $5 buys 5,000 API calls paid as per-call USDC-on-Base x402 payments (merchants floor prices at 0.001 USDC), or hundreds of thousands of calls paid in Nano at true metered prices ($0.0000096/call observed live at nano-gpt.com, confirmed on-ledger). Top up once, micropay forever.
pip install feeless402 # needs Python 3.10+; pulls nanopy + requests
# (from a checkout: pip install -e .)
nano-pay init # creates ~/.nano-pay/wallet.json (chmod 600)
nano-pay topup 5 # quote: $5 USDC-BASE → ~12.3 XNO (NanSwap)
nano-pay topup 5 --execute # create order (set NANSWAP_API_KEY)
# → send USDC to the returned deposit address; XNO arrives in 30-60s
nano-pay receive # pocket incoming XNO
nano-pay quote https://nano-gpt.com/api/v1/chat/completions \
--json '{"model":"gpt-5-nano","messages":[{"role":"user","content":"hi"}]}'
nano-pay pay https://nano-gpt.com/api/v1/chat/completions \
--json '{"model":"gpt-5-nano","messages":[{"role":"user","content":"hi"}]}'
pay handles the whole x402 handshake: request → parse the 402 quote
(both the x402nano/PAYMENT-REQUIRED v2 dialect and the NanoGPT/accepts
v1 dialect) → price-cap check → sign a send state block locally → retry
with PAYMENT-SIGNATURE + X-PAYMENT headers. The server settles the
block via its facilitator; nothing is broadcast unless the server accepts.
If the merchant's reply is lost or is not a 2xx, the client asks the
ledger about the block hash it signed before reporting anything: the
receipt's settled is true, false, or "indeterminate", never a
guess. A block that landed is never re-paid — re-present the same one.
The client now does that re-presentation itself: every payment is journaled
next to the wallet (pending-payments.json) before it is sent, and a retry —
in the same call or after a crash — re-sends the same signed block with an
X-PAYMENT-PROOF header (the payer's signature over the block hash, method and
path). The server honors a proven re-presentation for 24 hours, so an observer
replaying a public block cannot use up the payer's retries. (v0.2.9; reported
privately by giskard09, who also re-verified
the fix with an independent harness — see advisory GHSA-cx37-j5vc-c967. v0.2.10
adds two follow-ups from that re-verification: a block of unknown age is never
treated as inside the window, and forwarded IP headers are trusted only from a
configured proxy.)
v0.2.12 closes the remaining ways a client could pay twice. A ledger that
cannot be reached is never read as "not paid"; a refusal gets the same
confirmation window as every other outcome; a payment record that cannot be
written or read stops the payment instead of being forgotten; and a payment
record is tied to the request's body and query, so two different operations
at the same price never share one. Also: the receipt checker raises
Unreachable instead of NotFound when no node answers, and a blank F402_*
setting means "use the default" (a blank top-up stays off). Reported by
pyfile-toolkit (#10–#14) and
enricoaboujaoude-droid (#15–#18);
fixes written independently against their reports and tests.
v0.2.13 makes the pay command tell the truth. Its paid field is now the
ledger's verdict (true, false or "indeterminate") instead of "a block
was signed", and pay exits 0 only when the call was served and the
payment is confirmed on the ledger. Anything else exits 1 and prints the
block hash on stderr. A nonzero exit does not mean nothing was paid:
check that block before paying again. quote still exits 0 for a valid 402.
Reported by vjohannesb (#19); fix written
independently against the report.
~/.nano-pay/wallet.json (0600).work_generate is tried first, local PoW is the
fallback (~25s). The CLI pre-caches work for your next block after it
has printed the result of the current one, so steady-state payments are
instant. Library callers opt in: send(), receive_all() and
request_with_payment() take prework= and default to off, because
pre-caching blocks for minutes and must never sit on a request path.--max-xno, default 0.05);
quote command inspects any endpoint's price without paying;
balance is always re-synced from the network, never trusted locally.topup quotes/creates swaps directly with
NanSwap's API (1,400+ input assets, ~$0.02 minimum). This tool never
holds or routes funds.An x402 payment signs a block the server broadcasts. If the server errors after receiving the block, it may still settle it late. The wallet re-syncs its frontier from the network before every operation, so a late settlement is picked up naturally; a competing block signed in the meantime simply makes one of the two invalid (funds are never at risk, but don't fire concurrent payments from one wallet).
Beta (v0.2.13). Proven on mainnet with real funds: live paid calls to
NanoGPT ($0.0000096/call, confirmed on-ledger), full merchant loop
(verify → settle → confirm, no facilitator), PoW-gated faucet claims,
and a complete stranger-agent lifecycle (fresh wallet → PoW claim →
paid API call → confirmed) in under 3 minutes. 71-test suite covers the
payment path offline (two faucet tests need the optional mcp extra).
Not audited — keep only working capital in it.
/premium reference endpoint is shelved under APIs and
this Python client under SDKs.com.feeless402/nano-pay.~/.nano-pay/*.json (0600).
No tool or log path ever prints it. Back it up offline.pay refuses quotes above --max-xno (default 0.05).
MCP x402_pay enforces the same cap parameter.accepts
always binds, never the hint.X-Real-IP /
X-Forwarded-For only from F402_TRUSTED_PROXIES (default
127.0.0.1,::1); any other peer is identified by its socket address.
If your reverse proxy runs on another host, add its address there, and
have it overwrite both headers (proxy_set_header X-Real-IP $remote_addr;)._seen_previous) and the replay
bound (_replays) are per-process, in-memory. Under uvicorn --workers N each worker keeps its own copy, so two workers can accept
the same payer frontier and one of the two blocks loses the fork race.
Serve a single process (scale with a lock in front, not with workers)
unless those guards are moved to shared storage. The ledger poll budget
is a single value for the whole path (verify.CONFIRM_WAIT_S), so the
merchant settle loop and the x402 client look at the chain for the same
time; a worker that gives up earlier than that reports "indeterminate"
for a payment that did land.Feeless402 is better because people took the time to read the code, break it, and say so. Thank you to:
pay
announced settlement for refused and unconfirmed payments (#19, v0.2.13).nano_pay.receipt (#4).Found something? Open an issue, or for anything that could cost a user money, report it privately through GitHub's security advisories.