payments-gateway
by Rotwang9000
README.md
# payments-gateway
A generic MCP + REST payments gateway: charge for **your** services — and
let **your agents** accept payment for theirs — without ever holding a
spending key. Public, MIT.
Brand is config, not code. The same engine runs
[seneschal.space](https://seneschal.space) and Winbit32's
[mcp.winbit32.com](https://mcp.winbit32.com/mcp)
([winbit32MCP](https://github.com/FungeLLC/winbit32MCP) is a thin
deployment of this repo); point the env at your own name, prices and
addresses and it is your gateway.
## Two ways to get paid
**Direct payment with confirmation**
- **x402 (USDC)** — any REST route in your catalogue answers
`402 Payment Required`; agents pay per-call with
`transferWithAuthorization` on Base and retry. Micro-prices (a $0.001
"Penny Oracle" tier) up to per-route pricing, with Bazaar discovery.
- **Outbound ZEC with a human in the loop** — `make_payment` builds a
shielded transaction from a FROST vault, returns a `cosignUrl` deep link
+ `WB32COSIGN` QR, and the payment exists only once a human co-signs it
in their cosigner. The gateway's share alone cannot spend.
**Top-up and use**
- Credit-metered services (e.g. view-key payment watches): create once,
then meter down. Top up the meter with x402 (`/v1/private/topup*`,
fixed tiers or custom amounts) **or by paying in XMR/ZEC** to your
view-only receiving wallet — a quote locks the rate, a unique
amount-tag/memo identifies the payer, and the receive poller credits
the meter when funds land. No accounts, no cards, no custodial balance.
- **Spend that meter at ANY x402 endpoint** (`POST /v1/pay`, tool
`pay_x402`) — the payer relay. **DORMANT / EXPERIMENTAL — kept off.**
A funded account *could* pay a third-party x402 server through the
gateway (gateway fronts USDC from a hot float, settles the merchant's
402 challenge capped, returns the response, debits amount + fee), with
reserve→pay→settle→refund accounting and auditable `relay_payments`
receipts. It stays off (no host-injected `opts.x402Payer` ⇒ every call
503s) because this custodial *hold-then-transmit-to-third-parties* shape
is money-transmission-shaped. The **non-custodial successor** — the
user's OWN Vultisig vault swaps (Maya/NEAR-Intents) and signs the x402
payment, no custody by us — is planned in **winbit32**. The code here is
retained and tested but is not the product direction.
## Key custody
The **recommended** mode is split-key: the gateway process holds at most
**one FROST share of a t-of-n key** (a `.wult` file). Any tool that moves
funds returns a cosign QR / deep link; a human approves in a cosigner
(the fast-boot [winbit32.com/cosign](https://winbit32.com/cosign) app,
cosign.exe in the Winbit32 desktop, or any WB32COSIGN-speaking signer).
The human's share signature IS the approval; nothing the gateway can sign
alone.
That is the recommended mode, not the only one: signing tools also accept
directly supplied phrases or keys (operator config or explicit tool input)
for operators who accept holding key material. Docs and defaults steer
towards split-key; functionality is never gated on it.
For *accepting* payments the gateway is view-key-only: it can see funds
arrive but can never move them.
## Tool families
| Family | Tools | Keys needed |
| --- | --- | --- |
| accept | view-key watches + HMAC webhooks, x402 paywall, XMR/ZEC top-up quotes | view keys only |
| unlock *(opt-in)* | `paid_unlock_info/listing/browse/buy` (REST `/v1/unlock/*`) — pay-to-reveal a sealed secret ("paid private file"): non-custodial ZEC/XMR (auto-confirmed) or USDC, with an opt-in public shop feed | view keys + the existing master key (sealing) |
| make | `make_payment`, `make_payment_status`, `make_payment_info` | one FROST share + human cosign (recommended), or a directly supplied phrase/key |
| relay *(dormant)* | `pay_x402`, `pay_x402_info` (REST `POST /v1/pay`) — custodial spend-anywhere, kept OFF (money-transmission-shaped); non-custodial successor planned in winbit32 | host-injected funded payer (`opts.x402Payer`); a Base USDC float |
| wallet | balances, scan jobs, UTXOs, broadcast | view keys only |
| utility | phrase validate/complete/generate, Shamir split/combine | none (local, offline) |
| info | single-fact chain queries (height/fee/mempool) | none |
| zcash amounts | `zec_amount_advice`, `zec_split_plan`, `zec_popular_amounts` (REST `GET /v1/zec/amount-advice`, `/v1/zec/split-plan`, `/v1/zec/popular-amounts`) — "blend in" shield/deshield amount advice, a large-amount split planner, and the live popular-amount histogram | none (read); a zebra node + the index poller for live counts |
| zcash bus (opt-in) | `zec_bus_list`, `zec_bus_join`, `zec_bus_status`, `zec_bus_board`, `zec_bus_leave` (REST `GET /v1/zec/bus`, `GET /v1/zec/bus/:id`, `POST /v1/zec/bus/join`, `POST /v1/zec/bus/seat/:id/board\|leave`) — **non-custodial** mixing coordination: many users leave the Zcash pool with the same blend-in amount/route in the same window so their swaps look identical on-chain. The gateway holds no funds/keys and stores no destinations/txids — pure rendezvous. | `ZEC_BUS_ENABLED=1` + a writable `ZEC_BUS_DB` (no read-only fallback) |
| nfpt commerce | `nfpt_list_collections`, `nfpt_listings` (REST `GET /v1/nfpt/collections`, `/v1/nfpt/listings`, `/v1/nfpt/listings/:cid`) — browse the secresea NFPT marketplace. `nfpt_buy` / `nfpt_buy_status` (MCP only) start a co-signed shielded-ZEC purchase, same signer as `make_payment`. v1 is royalty-free (one recipient per tx). | reads: none; buy: one FROST share + human cosign (`MAKE_PAYMENT_WULT_PATH`) |
## Built on
One source of truth, assembled from already-public packages:
- [`x402-server-kit`](https://github.com/Rotwang9000/x402-server-kit) —
generic Fastify x402 paywall (facilitator selection, validated config,
Bazaar discovery);
- [`viewkey-watch`](https://github.com/Rotwang9000/viewkey-watch) —
Monero/Zcash view-key watch engine, credit meter and XMR/ZEC top-up
detection;
- [`@winbit32/wallet-kit`](https://www.npmjs.com/package/@winbit32/wallet-kit)
— scanner clients + the WB32COSIGN FROST/Orchard cosign client
(headless initiator pipeline) used by the wallet tools and
`make_payment`.
## What works today
| Capability | Status |
| --- | --- |
| Accept USDC per-call via x402 (HTTP 402 + `transferWithAuthorization` on Base) | ✅ |
| View-key payment **webhooks** for Monero/Zcash ("ping me when funds land") | ✅ |
| Credit-metered watches with USDC top-ups (fixed tiers + custom amounts) | ✅ |
| Fund a watch by paying in **XMR/ZEC** to the operator's view-only wallet | ✅ |
| One-off historical view-key scans (spendable/spent notes) | ✅ |
| Free view-key derivation from a phrase (rate-limited) | ✅ |
| Single-fact ("Penny Oracle") privacy-chain queries (height/fee/mempool) | ✅ |
| Zcash amount-privacy advisor (free): "blend in" shield/deshield amounts + round-trip self-dox warning + large-amount split planner, with a live on-chain popular-amount index ("N others used this") | ✅ |
| **Make** outbound **ZEC** payments via FROST co-signing, with `cosignUrl` deep links | ✅ |
| Wallet view-key tools (`*_zec_scan_*`, `*_zec_utxos`, `*_zec_broadcast`, `*_xmr_scan_*`) | ✅ |
| Utility tools: `phrase_validate/complete/generate`, `shamir_split/combine` — local + offline | ✅ |
| **Paid unlock** ("paid private file"): seal a secret, sell it for ZEC/XMR (view-key, auto-confirmed) or USDC (x402); opt-in public shop feed; browser-encrypt demo — opt-in | ✅ |
| **Donation overlay**: LiveZEC-style OBS alerts from a streamer's UFVK (no custody, ZEC prepaid meter) | ✅ |
| **Ziving** ([ziving.org](https://ziving.org)): JustGiving-style Zcash fundraising pages on the same UFVK scanner | ✅ |
| Platform-blind paid-file delivery over **Nym** (key + ciphertext browser-to-browser) | 🛣️ roadmap |
| Direct phrase/key signing mode; outbound USDC / XMR | 🛣️ roadmap |
## Running it standalone
```bash
npm ci
node bin/mcp.mjs # MCP server for agents (Streamable HTTP)
node bin/rest.mjs # REST + x402 paywall
node bin/private-watch-poller.mjs # watch poller (cron-style)
node bin/crypto-recv-poller.mjs # XMR/ZEC top-up poller
node bin/zec-shield-index-poller.mjs # Zcash shield-amount index (opt-in)
```
Agent config:
```json
{ "mcpServers": { "myservice": { "url": "https://mcp.example.com/mcp" } } }
```
## Embedding it
Mount the engine onto your own Fastify + MCP servers and inject your
config — your routes and the gateway's paid routes share one paywall:
```js
import {
buildConfig,
registerGatewayRoutes,
registerGatewayMcpTools
} from 'payments-gateway';
const cfg = buildConfig({ ...process.env, GATEWAY_SERVICE_NAME: 'myservice' });
// REST: your Fastify app gains the gateway's paid routes + paywall. To
// paywall your own routes too, build a combined x402Cfg from
// GATEWAY_PREMIUM_ROUTES.concat(yourRoutes) and pass it in opts.
registerGatewayRoutes(app, { config: cfg });
// MCP: your server gains the gateway tool families under your prefix.
registerGatewayMcpTools(mcpServer, { config: cfg, toolPrefix: 'myservice' });
```
Install as a dependency:
```bash
npm i github:Rotwang9000/payments-gateway
```
## Agent discovery (Gopher over HTTPS)
So agents can *find* your services cheaply — before they spend tokens on
an HTML page or a JSON index — the gateway ships a tiny, dependency-free
primitive for serving a [Gopher](https://datatracker.ietf.org/doc/html/rfc1436)
menu natively over HTTPS. The convention: publish a terse, drill-down
service index at **`/.well-known/agent.gopher`** with
`Content-Type: application/gopher; charset=utf-8`. It is a typed, navigable
cousin of [`llms.txt`](https://llmstxt.org) — a discovery layer, **not** a
replacement for MCP (which is how you *call* a tool).
The `gophermap` module builds, parses and sanitises menus (RFC 1436) in a
TLS-native "compact" mode that drops Gopher's redundant host/port fields:
```js
import {
info, menu, textItem, link, buildMenu
} from 'payments-gateway/gophermap';
// Compact mode is the default (host + port dropped — TLS supplies them).
const body = buildMenu([
info('My services — terse index for machines.'),
menu('Catalogue', '/.well-known/agent/catalogue'),
textItem('about', '/.well-known/agent/about'),
link('MCP server', 'https://mcp.example.com/')
]);
app.get('/.well-known/agent.gopher', (req, reply) =>
reply
.header('content-type', 'application/gopher; charset=utf-8')
.header('cache-control', 'public, max-age=600')
.send(body));
```
A line is just `<type><label>TAB<selector>` (`1` submenu, `0` text leaf,
`h` `URL:` link, `i` info), terminated by a line containing only `.`.
Across a real directory the compact menu measures ~29% smaller than the
equivalent JSON and ~32% smaller than minimal HTML, and progressive
disclosure (drilling into one branch) is ~5× cheaper than pulling a whole
index. Live example + a "publish your own" walkthrough:
[seneschal.space/gopher](https://seneschal.space/gopher) — served from
[`seneschal.space/.well-known/agent.gopher`](https://seneschal.space/.well-known/agent.gopher).
## Configuration
Environment-driven via `src/config.js` (`buildConfig(env)`). Key groups:
- **Server**: `GATEWAY_REST_PORT`, `GATEWAY_MCP_PORT`, `GATEWAY_REST_HOST`
- **Brand**: `GATEWAY_SERVICE_NAME`, `GATEWAY_TOOL_PREFIX`,
`GATEWAY_WEBHOOK_SIGNATURE_HEADER`
- **x402**: `X402_RECIPIENT_ADDRESS`, `X402_NETWORK`, `X402_FACILITATOR_URL`,
`X402_CDP_API_KEY_ID` / `X402_CDP_API_KEY_SECRET`, `X402_*_PRICE`
- **Scanner backend**: `NFPT_BASE_URL`, `NFPT_API_KEY`
- **Private watch**: `PRIVATE_WATCH_DB`, `PRIVATE_WATCH_ENCRYPTION_KEY`
- **Privacy RPC**: `MONERO_RPC_URL`, `ZCASH_RPC_URL`
- **Zcash shield-amount index** *(read always on; scanner opt-in)*:
`ZEC_SHIELD_INDEX_ENABLED`, `ZEC_SHIELD_INDEX_DB`,
`ZEC_SHIELD_INDEX_FROM_HEIGHT` / `ZEC_SHIELD_INDEX_WINDOW_BLOCKS`,
`ZEC_SHIELD_INDEX_MAX_BLOCKS_PER_TICK`, `ZEC_SHIELD_INDEX_MIN_BOUNDARY_ZAT`
(drives the `zec_amount_advice` / `zec_popular_amounts` tools + `/v1/zec/*`
routes; reads the zebra node, no view keys)
- **XMR/ZEC top-ups**: `XMR_RECV_ADDRESS` + `XMR_RECV_VIEW_KEY`,
`ZEC_RECV_ADDRESS` + `ZEC_RECV_UFVK`, `CRYPTO_TOPUP_*`
- **Make payments (ZEC co-sign)**: `MAKE_PAYMENT_WULT_PATH` (+ optional
`MAKE_PAYMENT_WULT_PASSWORD`), `MAKE_PAYMENT_WASM_DIR` (orchard-frost WASM
artefacts), `MAKE_PAYMENT_RELAY_URL` (default `https://cosign.winbit32.com`),
`MAKE_PAYMENT_PCZT_API_BASE`, `MAKE_PAYMENT_SCANNER_BASE`,
`MAKE_PAYMENT_BIRTHDAY_HEIGHT`, the safety rails
`MAKE_PAYMENT_MAX_ZEC` / `MAKE_PAYMENT_MAX_PENDING`, and
`COSIGN_APP_URL` for the human-facing cosigner deep links.
- **Paid unlock** ("paid private file"): `PAID_UNLOCK_ENABLED`,
`PAID_UNLOCK_DB`, `PAID_UNLOCK_FREE_CREATE_PER_IP_PER_HOUR`,
`PAID_UNLOCK_ORDER_TTL_SEC`. Reuses `PRIVATE_WATCH_ENCRYPTION_KEY` (sealing),
the `ZEC_RECV_*` / `XMR_RECV_*` wallet + `CRYPTO_*` oracle (native quotes) and
the x402 paywall (USDC buys).
A capability stays `503 *_not_configured` (or its tools are simply not
registered) until its keys/addresses are set — the `make_payment` tools
only exist when `MAKE_PAYMENT_WULT_PATH` is configured, and the paid-unlock
surface only mounts when `PAID_UNLOCK_ENABLED=1`.
## Paid unlock ("paid private file")
Opt-in pay-to-reveal: a seller seals a small secret (a file decryption key +
locator, a licence, a link) behind a price; a buyer pays **non-custodially** in
ZEC/XMR (detected with a view key — funds go straight to the seller, and the
receive-poller auto-confirms the order) or instantly in USDC over x402, then
pulls the secret. The file plaintext never touches the server (encrypt
in-browser, host only the ciphertext); the secret is sealed at rest with the
gateway master key. Sellers can opt a listing into a public **shop feed**
(`GET /v1/unlock/listings`) or keep it link-only (default). Full trust model,
API surface, the native auto-confirm reconciler and the WebCrypto browser demo:
[`docs/PAID_UNLOCK.md`](docs/PAID_UNLOCK.md).
## Donation overlay + Ziving
LiveZEC-style **on-stream Zcash donation alerts** and JustGiving-style
**campaign pages** ([ziving.org](https://ziving.org)), built on the same UFVK
scanner as Private Watch:
1. `POST /v1/overlay` or `POST /v1/ziving/page` with a read-only UFVK + receive
address → unguessable overlay id (OBS URL), one-shot owner token, and a ZEC
funding quote for the prepaid scanning meter (~$0.02/day).
2. Campaign pages: `GET /v1/ziving/page/:slug` + `/events`; site at
`https://ziving.org/p/<slug>` with manage UI at `/manage.html`.
3. OBS: `donation-overlay.html` / `ziving.org/overlay.html?slug=…`.
UFVKs are AES-256-GCM encrypted at rest; no accounts. Recommend a
donation-only wallet. Set `ZIVING_PAGE_URL_BASE` and `OVERLAY_PAGE_URL_BASE`.
## Deployments
- **[winbit32MCP](https://github.com/FungeLLC/winbit32MCP)** — the Winbit32
deployment, live at `https://mcp.winbit32.com/mcp` (tool prefix
`winbit32`).
- **seneschal.space** — embedded alongside its own DeFi feeds (combined
x402 route catalogue).
## Licence
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues