npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

moltspay

v2.4.0

Published

Payment infrastructure for AI Agents - Server & Client SDK

Readme

MoltsPay

npm version License: MIT TypeScript

Blockchain payment infrastructure for AI Agents. Turn any skill into a paid service with one JSON file.

MoltsPay enables agent-to-agent commerce using the x402 protocol - HTTP-native payments where AI agents can pay each other for services without human intervention. Built on USDC stablecoins with gasless transactions powered by Coinbase CDP.

Why MoltsPay?

| Problem | MoltsPay Solution | |---------|-------------------| | AI agents can't pay for services | x402 protocol - HTTP 402 Payment Required flow | | Blockchain payments need gas | Gasless - CDP facilitator handles all gas fees | | Complex wallet integration | One JSON file - add moltspay.services.json to any skill | | Payment verification is hard | Automatic on-chain verification included |

Features

  • Skill Integration - Add moltspay.services.json to any existing skill
  • x402 Protocol - HTTP-native payments (402 Payment Required)
  • Gasless - Both client and server pay no gas (facilitators handle it)
  • Payment Verification - Automatic on-chain verification
  • Secure Wallet - Spending limits, whitelist, and audit logging
  • Multi-chain - Base, Polygon, Solana, BNB, Tempo (mainnet & testnet)
  • Fiat Rail — Alipay (2.0.0) - Accept CNY via Alipay AI Pay from China mainland merchants. CLI-only (Node), browser unsupported. See docs/ALIPAY-RAIL.md
  • Fiat Rail — WeChat Pay (2.1.0) - Accept CNY via WeChat Pay v3 Native (scan-to-pay). SDK-managed recoverable sessions persist QR/order context, poll in the background, and fulfill idempotently. See docs/WECHAT-RAIL-DESIGN.md
  • Balance Rail — Password-Free (2.2.0) - Top up once, then pay with no signature or QR per transaction. Server-custodied SQLite ledger with atomic deducts, hard spending limits, and idempotent retries. See docs/WECHAT-BALANCE-PASSWORDLESS-DESIGN.md
  • Balance Identity & Authentication (2.4.0) - The balance rail gets a real user: accounts are anchored to the WeChat payer's openid at top-up, and each deduction is authorized by a per-request signature (auth_mode: off | shadow | enforce). Knowing a buyer_id is no longer enough to spend — closes the bearer hole. See Balance Authentication below
  • Agent-to-Agent - Complete A2A payment flow support
  • Multi-VM - EVM chains + Solana (SVM) with unified API
  • MCP Server - Expose wallet + payments to Claude Desktop, Cursor, and other MCP hosts

Installation

npm install moltspay@latest

Quick Start

For Service Providers (Selling)

1. Have an existing skill with exported functions:

// index.js (your existing skill)
export async function textToVideo({ prompt }) {
  // your implementation
  return { video_url: "https://..." };
}

2. Add moltspay.services.json:

{
  "provider": {
    "name": "My Video Service",
    "wallet": "0xYOUR_EVM_WALLET_ADDRESS",
    "solana_wallet": "YOUR_SOLANA_ADDRESS",
    "chains": ["base", "polygon", "solana", "bnb"]
  },
  "services": [{
    "id": "text-to-video",
    "function": "textToVideo",
    "price": 0.99,
    "currency": "USDC"
  }]
}

Note: solana_wallet is optional - only needed if accepting Solana payments.

3. Start server:

npx moltspay start ./my-skill --port 3000

That's it! Your skill now accepts x402 payments.

For Clients (Buying)

1. Initialize wallet (one time):

npx moltspay init                  # EVM wallet (Base, Polygon, BNB, etc.)
npx moltspay init --chain solana   # Solana wallet (mainnet & devnet)

2. Fund your wallet (US users):

npx moltspay fund 5
# Opens Coinbase Pay - use debit card or Apple Pay
# USDC arrives in < 1 minutes. No ETH needed!

Or send USDC directly to your wallet address from any exchange.

3. Use paid services:

# Pay on Base (default)
npx moltspay pay https://server.com text-to-video --prompt "a cat dancing"

# Pay on Polygon
npx moltspay pay https://server.com text-to-video --chain polygon --prompt "a cat dancing"

Testnet Quick Start

Want to test before using real money? Use our testnet faucets:

# 1. Create wallet (if you don't have one)
npx moltspay init                  # EVM wallet (Base, Polygon, BNB, etc.)
npx moltspay init --chain solana   # Solana wallet (mainnet & devnet)

# 2. Get free testnet tokens (pick one or all!)
npx moltspay faucet                        # Base Sepolia (1 USDC, once per 24h)
npx moltspay faucet --chain solana_devnet  # Solana devnet (1 USDC)
npx moltspay faucet --chain bnb_testnet    # BNB testnet (1 USDC + 0.001 tBNB for gas)
npx moltspay faucet --chain tempo_moderato # Tempo testnet (1 pathUSD)

# 3. Test payments on different chains
# Base Sepolia (CDP, gasless)
npx moltspay pay https://moltspay.com/a/zen7 text-to-video \
  --chain base_sepolia --prompt "a robot dancing"

# Solana devnet (SPL transfer)
npx moltspay pay https://moltspay.com/a/zen7 text-to-video \
  --chain solana_devnet --prompt "a cat playing piano"

# BNB testnet (gas sponsored)
npx moltspay pay https://moltspay.com/a/zen7 text-to-video \
  --chain bnb_testnet --prompt "a sunset timelapse"

# Tempo Moderato (gas-free, MPP protocol)
npx moltspay pay https://server.com service-id \
  --chain tempo_moderato --prompt "test"

For Web Apps (Browser)

[email protected] adds a browser client. Connect any EIP-1193 wallet (MetaMask, Rainbow, Frame, …) for EVM or a @solana/wallet-adapter wallet (Phantom, Solflare, Backpack, …) for Solana and pay for x402 services directly from the page — no private key ever in browser memory, no CLI wrapper.

Install:

npm install moltspay

Pay with MetaMask:

import { MoltsPayWebClient, eip1193Signer } from 'moltspay/web';

const client = new MoltsPayWebClient({
  signer: eip1193Signer(window.ethereum),
});

// Discover what the provider accepts
const { provider, services } = await client.getServices('https://provider.example.com');

// Run a paid call — user sees one wallet signature prompt
const result = await client.pay(
  'https://provider.example.com',
  'text-to-video',
  { prompt: 'a cat dancing' },
  { chain: 'base' }
);

Pay with Phantom (Solana):

import { MoltsPayWebClient, solanaSigner } from 'moltspay/web';
import { useWallet } from '@solana/wallet-adapter-react';

const wallet = useWallet();
const client = new MoltsPayWebClient({ signer: solanaSigner(wallet) });

await client.pay(serverUrl, 'text-to-video', params, { chain: 'solana_devnet' });

EVM + Solana in one client — use composeSigners so the same MoltsPayWebClient instance routes to whichever signer matches the picked chain:

import { MoltsPayWebClient, composeSigners, eip1193Signer, solanaSigner } from 'moltspay/web';

const client = new MoltsPayWebClient({
  signer: composeSigners(
    eip1193Signer(window.ethereum),
    solanaSigner(phantomAdapter),
  ),
});

Chain coverage. All 8 chains the CLI supports work from the browser with one signature prompt each:

| Chain | Scheme | User gas? | Notes | |---|---|---|---| | base / polygon / base_sepolia | EIP-3009 transferWithAuthorization | No (gasless) | Provider submits on success | | tempo_moderato | EIP-2612 permit | No (gasless) | Browser never switches to Tempo; server's settler submits permit + transferFrom | | bnb / bnb_testnet | MoltsPay PaymentIntent | One-time approve | Call client.approveBnb({ chain, spender, token }) once, then intent signatures are gasless | | solana / solana_devnet | SPL transfer | No if provider sets a fee payer | wallet.signTransaction signs, provider submits |

BNB approval flow. The first payment on BNB throws NeedsApprovalError with the details needed to approve — catch it, call approveBnb(), and retry:

try {
  await client.pay(url, service, params, { chain: 'bnb' });
} catch (err) {
  if (err instanceof NeedsApprovalError) {
    await client.approveBnb({
      chain: 'bnb',
      spender: err.details.spender,
      token: err.details.token,
    });
    // User paid ~0.001 BNB gas for the approve. Retry now succeeds without further approve.
    await client.pay(url, service, params, { chain: 'bnb' });
  }
}

Tempo note. Tempo Moderato pathUSD is a native precompile that implements EIP-2612 permit but not EIP-3009. The web client dispatches to the permit path automatically when the server advertises scheme: "permit". The user signs typed data; the provider's settler submits the permit() + transferFrom() transactions on-chain. MetaMask never prompts for a chain switch because the browser wallet doesn't touch Tempo at all.

Solana mainnet RPC. The public api.mainnet-beta.solana.com endpoint returns 403 to browser requests, so any Solana-mainnet traffic needs an authenticated RPC. Supply one via solanaRpc:

const client = new MoltsPayWebClient({
  signer: composeSigners(
    eip1193Signer(window.ethereum),
    solanaSigner(phantomAdapter),
  ),
  solanaRpc: {
    solana: 'https://mainnet.helius-rpc.com/?api-key=YOUR_KEY',
    // solana_devnet falls back to `api.devnet.solana.com` automatically
  },
});

Accepts any Helius / QuickNode / Alchemy / Triton / self-hosted URL. Per-chain — override only solana if you only use mainnet. Devnet (api.devnet.solana.com) still serves browsers and does not need an override.

Error classes — every error exposes a code field so you can branch without string-matching:

import {
  NeedsApprovalError,        // code: 'NEEDS_APPROVAL'       — BNB, call approveBnb()
  UnsupportedChainError,     // code: 'UNSUPPORTED_CHAIN'    — user picked a chain the server doesn't accept
  PaymentRejectedError,      // code: 'PAYMENT_REJECTED'     — user cancelled in the wallet
  InsufficientBalanceError,  // code: 'INSUFFICIENT_BALANCE' — not enough native gas for BNB approve
  SpendingLimitExceededError,// code: 'SPENDING_LIMIT_EXCEEDED' — only when you opt in to SpendingLedger
  ServerError,               // code: 'SERVER_ERROR'         — provider returned non-2xx
  MoltsPayError,             // base class
} from 'moltspay/web';

Spending limits (opt-in). Off by default. External wallets already prompt per-signature; per-browser localStorage limits don't sync across devices. If you want a session-level cap anyway:

const client = new MoltsPayWebClient({
  signer: eip1193Signer(window.ethereum),
  spendingLimits: { maxPerTx: 5, maxPerDay: 50 },
});

Provider CORS. Providers must enable CORS on MoltsPayServer for browser callers — without it the 402 challenge header is invisible to the browser:

new MoltsPayServer({
  // ...
  cors: true,                              // allow all origins, or
  cors: ['https://myapp.example.com'],     // explicit allowlist, or
  cors: { origins: [...], maxAge: 86400 }, // fine-grained
});

Servers advertising for the web need cors enabled; CLI callers are unaffected either way.

Security posture. No private key ever enters browser memory. No filesystem access, no ~/.moltspay/ — the wallet is always external. The signer object the client receives only has permission to sign typed data and (for BNB) submit one approve transaction, which the user explicitly confirms in the wallet UI.

Reference demo. examples/web/ is a runnable React + Vite app that exercises every path above. cd examples/web && npm install && npm run dev — it connects to https://moltspay.com/a/zen7 by default. See examples/web/README.md for the full matrix of tested wallets + chains.

MCP Server (For AI Assistants)

MoltsPay ships an MCP (Model Context Protocol) stdio server that lets MCP-compatible hosts (Cursor, Windsurf, Claude Code, Zed, etc.) browse services, check wallet status, and pay for x402 services on your behalf.

It is a thin wrapper around MoltsPayClient — wallet custody, spending limits, and all payment protocols (x402, MPP, Solana, BNB) are reused from the SDK.

Setup

1. Create a wallet and set spending limits (the MCP server refuses to start without a wallet):

npx moltspay init
npx moltspay config --max-per-tx 2 --max-per-day 10
npx moltspay fund 5    # or: npx moltspay faucet for testnet

2. Point your MCP host at the moltspay-mcp binary over stdio:

npx -y moltspay-mcp              # normal mode
npx -y moltspay-mcp --dry-run    # preview payments without signing

Each host has its own config file for registering stdio MCP servers — check your host's docs for the exact location. For a safer first run, use --dry-run so moltspay_pay returns a preview instead of spending real funds.

Tools

| Tool | What it does | Destructive? | |---|---|---| | moltspay_status | Wallet address, balances across all supported chains, spending limits | No | | moltspay_services | Fetch services manifest from a provider URL; optional query/maxPrice filter | No | | moltspay_pay | Execute an x402/MPP/SOL/BNB payment and return the service result | Yes | | moltspay_config | Read or update maxPerTx / maxPerDay limits | Updates config file |

Safety Layers

moltspay_pay is the only tool that moves money. Three guards stack on top of the MCP host's own tool-approval prompt:

  1. SDK spending limitsmaxPerTx / maxPerDay enforced before signing.
  2. Dry-run mode — launch with --dry-run and payments return a preview instead of signing.
  3. Confirmation gate — set MOLTSPAY_MCP_REQUIRE_CONFIRM=1 to require a second tool call (confirmed: true) for any payment exceeding maxPerTx / 10.

Private keys and mnemonics are never exposed over MCP — wallet creation stays on the CLI (npx moltspay init) by design. See docs/MCP-USAGE.md for full tool arguments and troubleshooting.

Payment Protocols

MoltsPay supports multiple payment protocols, each optimized for different chains:

| Protocol | Chains | Gas | Description | |----------|--------|-----|-------------| | x402 + CDP | Base, Polygon | Gasless (CDP pays) | HTTP 402 + EIP-3009 signatures | | x402 + SOL | Solana | Gasless (server pays) | HTTP 402 + SPL transfer | | x402 + BNB | BNB | Gasless (server pays) | HTTP 402 + EIP-712 intent signing | | MPP | Tempo | Gas-free native | HTTP 402 + WWW-Authenticate | | x402 + Alipay | Alipay | No blockchain gas | HTTP 402 + Alipay AI Pay proof | | x402 + WeChat Pay | WeChat Pay Native | No blockchain gas | HTTP 402 + scan-to-pay QR, poll-based verify |

How x402 Protocol Works

Client                         Server                      CDP Facilitator
  |                              |                              |
  | POST /execute                |                              |
  | -------------------------------------------------->   |                              |
  |                              |                              |
  | 402 + payment requirements   |                              |
  | <--------------------------------------------------   |                              |
  |                              |                              |
  | [Sign EIP-3009 - NO GAS]     |                              |
  |                              |                              |
  | POST + X-Payment header      |                              |
  | -------------------------------------------------->   | Verify signature             |
  |                              | -------------------------------------------------->   |
  |                              |                              |
  |                              | Execute transfer (pays gas)  |
  |                              | <--------------------------------------------------   |
  |                              |                              |
  | 200 OK + result              |                              |
  | <--------------------------------------------------   |                              |

Key insight: Client signs a payment authorization, server submits it. Neither party pays gas - the CDP facilitator handles settlement.

How MPP Protocol Works (Tempo)

MPP (Machine Payments Protocol) is simpler - client executes the transfer directly:

Client                         Server
  |                              |
  | POST /service                |
  | -------------------------------------------------->   |
  |                              |
  | 402 + WWW-Authenticate       |
  | <--------------------------------------------------   |
  |                              |
  | [Execute TIP-20 transfer]    |
  | [No gas needed on Tempo]     |
  |                              |
  | POST + Authorization: Payment|
  | -------------------------------------------------->   |
  |                              |
  | [Server verifies on-chain]   |
  |                              |
  | 200 OK + Payment-Receipt     |
  | <--------------------------------------------------   |

Key insight: On Tempo, the client executes the transfer directly (gas-free), then retries with the transaction hash. No CDP facilitator needed.

How Solana Protocol Works

Client                         Server (Fee Payer)          Solana Network
  |                              |                              |
  | POST /execute                |                              |
  | -------------------------------------------------->   |                              |
  |                              |                              |
  | 402 + payment requirements   |                              |
  | (includes solana_wallet)     |                              |
  | <--------------------------------------------------   |                              |
  |                              |                              |
  | [Sign SPL Transfer]          |                              |
  | [NO GAS - just signing]      |                              |
  |                              |                              |
  | POST + X-Payment (signature) |                              |
  | -------------------------------------------------->   | Execute transfer             |
  |                              | (server pays ~$0.001 SOL)    |
  |                              | -------------------------------------------------->   |
  |                              |                              |
  | 200 OK + result              |                              |
  | <--------------------------------------------------   |                              |

Key insight: Client only signs the SPL transfer (gasless). Server acts as fee payer and executes the transaction on-chain.

How BNB Protocol Works

Client                         Server                      BNB Network
  |                              |                              |
  | POST /execute                |                              |
  | -------------------------------------------------->   |                              |
  |                              |                              |
  | 402 + payment requirements   |                              |
  | (includes bnbSpender)        |                              |
  | <--------------------------------------------------   |                              |
  |                              |                              |
  | [Sign EIP-712 intent]        |                              |
  | [NO GAS - just signing]      |                              |
  |                              |                              |
  | POST + X-Payment (signature) |                              |
  | -------------------------------------------------->   | Execute transferFrom         |
  |                              | (server pays ~$0.0001 gas)   |
  |                              | -------------------------------------------------->   |
  |                              |                              |
  | 200 OK + result              |                              |
  | <--------------------------------------------------   |                              |

Key insight: Client only signs an intent (gasless). Server executes the actual transfer and pays the minimal gas (~$0.0001). This is the "pay-for-success" model - payment only happens if service succeeds.

Skill Structure

MoltsPay reads your skill's existing structure:

my-skill/
+------ package.json           # MoltsPay reads "main" field
+------ index.js               # Your existing exports
+------ moltspay.services.json # Only file you add!

Your functions stay untouched. Just add the JSON config.

Services Manifest Schema

{
  "$schema": "https://moltspay.com/schemas/services.json",
  "provider": {
    "name": "Service Name",
    "description": "Optional description",
    "wallet": "0x...",
    "chains": ["base", "polygon"]
  },
  "services": [{
    "id": "service-id",
    "name": "Human Readable Name",
    "description": "What it does",
    "function": "exportedFunctionName",
    "price": 0.99,
    "currency": "USDC",
    "input": {
      "prompt": { "type": "string", "required": true }
    },
    "output": {
      "result_url": { "type": "string" }
    }
  }]
}

Multi-Chain Configuration

Accept payments on multiple chains by specifying a chains array:

{
  "provider": {
    "wallet": "0x...",
    "chains": ["base", "polygon"]
  }
}

Clients can then choose which chain to pay on:

npx moltspay pay https://server.com service-id --chain polygon --prompt "..."

If no --chain is specified, the client uses the first chain in the provider's list.

Validate Your Config

npx moltspay validate ./my-skill

Server Setup

1. Get CDP credentials from https://portal.cdp.coinbase.com/

2. Create ~/.moltspay/.env:

CDP_API_KEY_ID=your-key-id
CDP_API_KEY_SECRET=your-secret

3. Configure chains in your manifest:

{
  "provider": {
    "wallet": "0x...",
    "chains": [
      { "chain": "base", "network": "eip155:8453", "tokens": ["USDC"] },
      { "chain": "base_sepolia", "network": "eip155:84532", "tokens": ["USDC"] }
    ]
  }
}

4. Start server:

npx moltspay start ./my-skill --port 3000

Server does NOT need a private key - the x402 facilitator handles settlement.

Chain Auto-Detection

The server automatically detects which chain to verify payments on based on the client's payment header:

  • Client pays with --chain base -> Server verifies on Base mainnet
  • Client pays with --chain base_sepolia -> Server verifies on Base Sepolia

No USE_MAINNET env var needed! Just configure your accepted chains in the manifest.

Testnet Setup (Providers)

To accept testnet payments, add base_sepolia to your chains array:

{
  "provider": {
    "wallet": "0x...",
    "chains": ["base", "base_sepolia"]
  }
}

Clients can then pay using --chain base_sepolia and get free testnet USDC via npx moltspay faucet.

CLI Reference

# === Client Commands ===
npx moltspay init                    # Create wallet (EVM + Solana)
npx moltspay fund <amount>           # Fund wallet via Coinbase (US)
npx moltspay faucet                  # Get free testnet USDC (Base Sepolia)
npx moltspay faucet --chain solana_devnet   # Get Solana devnet USDC
npx moltspay faucet --chain bnb_testnet     # Get BNB testnet USDC + tBNB
npx moltspay faucet --chain tempo_moderato  # Get Tempo testnet tokens
npx moltspay status                  # Check balance (all chains)
npx moltspay transfer <to> <amount>  # Transfer USDC/USDT out to any address (e.g. an exchange)
npx moltspay config                  # Update limits
npx moltspay services <url>          # List provider's services
npx moltspay pay <url> <service>     # Pay and execute service

# === Transfer / Withdraw (2.4.0) ===
# Move USDC/USDT out of your wallet to any address (e.g. an exchange deposit address).
npx moltspay transfer <to> <amount>                       # 5 USDC on Base (default), interactive confirm
npx moltspay transfer <to> 10 --token USDT --chain bnb     # USDT on BNB Chain
npx moltspay transfer <to> 5 --chain polygon --yes --json  # non-interactive (agents/scripts)
# EVM chains only (base/polygon/bnb/...); a normal on-chain transfer, so the wallet needs a
# little native gas (ETH/BNB/POL). The transfer network must match the receiver's deposit network.

# === Service Discovery ===
npx moltspay services                           # List all from registry
npx moltspay services https://provider.com      # List from specific provider
npx moltspay services -q "video"                # Search by keyword
npx moltspay services --max-price 1.00          # Filter by max price
npx moltspay services --type api_service        # Filter by type
npx moltspay services --tag ai                  # Filter by tag
npx moltspay services --json                    # Output as JSON

# === Pay with Chain Selection ===
npx moltspay pay <url> <service> --chain base          # Pay on Base (default)
npx moltspay pay <url> <service> --chain polygon       # Pay on Polygon
npx moltspay pay <url> <service> --chain base_sepolia  # Pay on Base testnet
npx moltspay pay <url> <service> --chain solana        # Pay on Solana
npx moltspay pay <url> <service> --chain solana_devnet # Pay on Solana devnet
npx moltspay pay <url> <service> --chain bnb           # Pay on BNB
npx moltspay pay <url> <service> --chain bnb_testnet   # Pay on BNB testnet
npx moltspay pay <url> <service> --chain tempo_moderato # Pay on Tempo
npx moltspay pay <url> <service> --rail alipay         # Pay with Alipay AI Pay
npx moltspay pay <url> <service> --rail wechat         # Pay with WeChat Pay Native QR
npx moltspay pay <url> <service> --rail balance        # Pay password-free from prepaid balance

# === BNB One-Time Approve ===
# BNB payments need a one-time spender approval first (the spender comes from the 402 response).
npx moltspay approve --spender <address> --chain bnb   # Approve, then `pay --chain bnb` is gasless
npx moltspay approve --spender <address>               # --chain defaults to bnb_testnet

# === WeChat Recoverable Sessions ===
npx moltspay wechat start <url> <service> --prompt "..."   # Start order, return QR metadata immediately
npx moltspay wechat status <session-or-out_trade_no>        # Query/recover a pending session
npx moltspay wechat fulfill <session-or-out_trade_no>       # Idempotently fulfill if paid
npx moltspay wechat cancel <session-or-out_trade_no>        # Mark local session cancelled
npx moltspay wechat list                                   # List persisted WeChat sessions

# === Custodial Balance (Password-Free) ===
npx moltspay balance set-buyer <id>                        # Persist the buyer id (account label)
npx moltspay balance query <url>                           # Balance, limits, today's spend, bound signer/openid
npx moltspay balance transactions <url>                    # Ledger history, newest first
npx moltspay balance topup <url> <amount> --rail crypto --tx-hash 0x...   # Credit from a settled payment (crypto/wechat/alipay)

# --- Identity & auth (2.4.0) ---
npx moltspay balance whoami [url]                          # Show local signer address (and, vs a server, the account's bound signer/openid)
npx moltspay balance bind <url>                            # Bind the local signer to an account (runs a minimal top-up to establish identity)

# --- Recoverable WeChat-funded top-up (for chat agents; non-blocking) ---
npx moltspay balance topup-order <url> --pack 2.00         # Mint a WeChat pack order, emit the QR, exit immediately
npx moltspay balance topup-confirm <out_trade_no>          # Confirm + credit a pending order later (idempotent on out_trade_no)
npx moltspay balance topup-status <out_trade_no>           # Query one pending order
npx moltspay balance topup-list                            # List pending top-up orders
npx moltspay balance topup-pack <url>                      # Blocking variant: mint pack, wait for the scan, credit inline

# === Server Commands ===
npx moltspay start <skill-dir>       # Start server
npx moltspay stop                    # Stop server
npx moltspay validate <path>         # Validate manifest

# === Options ===
--port <port>                        # Server port (default 3000)
--chain <chain>                      # Chain: base, polygon, solana, bnb, tempo_moderato, + testnets
--rail <rail>                        # Rail: alipay, wechat, or balance (password-free)
--token <token>                      # Token: USDC, USDT
--max-per-tx <amount>                # Spending limit per transaction
--max-per-day <amount>               # Daily spending limit
--config-dir <dir>                   # Custom wallet directory

Programmatic Usage

Client

import { MoltsPayClient } from 'moltspay/client';

// Initialize client (uses wallet from ~/.moltspay/wallet.json)
const client = new MoltsPayClient();

// Standard service call (params wrapped in { params: {...} })
const result = await client.pay(
  'https://server.com',
  'text-to-video',
  { prompt: 'a cat dancing' },
  { chain: 'base' }
);

console.log(result.video_url);

Custom Input Formats (rawData)

Some services have custom input formats instead of the standard { params: { prompt } }. Use rawData: true to send your data at the top level:

// Service expects: { text: "...", target_lang: "..." }
// NOT: { params: { text: "...", target_lang: "..." } }

const result = await client.pay(
  'https://server.com',
  'translate',
  { text: 'Hello world', target_lang: 'es' },
  { 
    chain: 'base_sepolia',
    rawData: true  // Send data at top level
  }
);

// Server receives: { service: "translate", text: "Hello world", target_lang: "es", chain: "base_sepolia" }

PayOptions Reference

interface PayOptions {
  token?: 'USDC' | 'USDT';     // Token to pay with (default: USDC)
  autoSelect?: boolean;         // Auto-select token based on balance
  chain?: string;               // Chain: base, polygon, solana, bnb, tempo_moderato, + testnets
  rawData?: boolean;            // Send data at top level (for custom input formats)
}

CLI Equivalent

# Standard format (uses { params: { prompt } })
npx moltspay pay https://server.com text-to-video --prompt "a cat dancing"

# Custom format (uses rawData, sends at top level)
npx moltspay pay https://server.com translate --data '{"text": "Hello", "target_lang": "es"}'

Server

import { MoltsPayServer } from 'moltspay/server';

const server = new MoltsPayServer('./moltspay.services.json');

// Register custom handler (optional - usually loaded from skill)
server.skill('text-to-video', async (params) => {
  // implementation
  return { video_url: '...' };
});

server.listen(3000);

Supported Chains

| Chain | ID | Type | Facilitator | Gas Model | |-------|-----|------|-------------|-----------| | Base | 8453 | Mainnet | CDP | Gasless (CDP pays) | | Polygon | 137 | Mainnet | CDP | Gasless (CDP pays) | | Base Sepolia | 84532 | Testnet | CDP | Gasless (CDP pays) | | Solana | - | Mainnet | SOL | Gasless (server pays) | | Solana Devnet | - | Testnet | SOL | Gasless (server pays) | | BNB | 56 | Mainnet | BNB | Gasless (server pays) | | BNB Testnet | 97 | Testnet | BNB | Gasless (server pays) | | Tempo Moderato | 42431 | Testnet | Tempo | Gas-free native |

Notes:

  • Ethereum mainnet NOT recommended (gas too expensive)
  • Each chain uses a specialized facilitator for optimal UX

Facilitator Architecture

A facilitator is the entity that executes the on-chain payment and pays the gas fees. MoltsPay supports two types:

| Type | Facilitator | Who Pays Gas? | Trust Model | |------|-------------|---------------|-------------| | External | CDP (Coinbase) | Coinbase | Trust Coinbase infrastructure | | Self-hosted | SOL, BNB, Tempo | Your server | Trust your own server |

External Facilitator (CDP):

  • Uses Coinbase Developer Platform as a third-party settlement service
  • Coinbase handles all on-chain execution and gas fees
  • Requires CDP API credentials
  • Supported chains: Base, Polygon

Self-hosted Facilitator (SOL, BNB, Tempo):

  • Your MoltsPay server acts as the facilitator
  • Server pays gas fees (~$0.001 per tx)
  • No external dependency - fully self-sovereign
  • You control the entire payment flow

Why Self-hosted is More Decentralized:

| Aspect | CDP (External) | Self-hosted | |--------|----------------|-------------| | Single point of failure | Coinbase down = everyone stuck | Each provider independent | | Censorship risk | Coinbase can block accounts | Cannot be censored | | Dependency | Relies on third-party | Fully autonomous |

This self-hosted approach is a key innovation: any service provider can become their own facilitator without relying on third-party infrastructure. Unlike CDP where all users depend on Coinbase, self-hosted facilitators create a truly decentralized network with no single point of failure.

Note: Clients never need to know the facilitator's private keys. They only sign their own payments - the facilitator handles settlement transparently.

Facilitators Explained

| Facilitator | Chains | How It Works | |-------------|--------|--------------| | CDP | Base, Polygon | Client signs EIP-3009, Coinbase executes transfer | | SOL | Solana | Client signs SPL transfer, server executes as fee payer | | BNB | BNB | Client signs EIP-712 intent, server executes transfer | | Tempo | Tempo Moderato | Client executes TIP-20 transfer (native gas-free) |

Solana Support

Solana uses the SolanaFacilitator with SPL token transfers. Key differences:

  • Gasless for users - Server acts as fee payer (~$0.001 SOL per tx)
  • Separate wallet - Solana uses ed25519 keys (different from EVM's secp256k1)
  • Wallet stored at ~/.moltspay/wallet-solana.json
  • Token - Official Circle USDC SPL token
# Initialize includes Solana wallet automatically
npx moltspay init

# Check Solana balance
npx moltspay status

# Get free devnet USDC
npx moltspay faucet --chain solana_devnet

# Pay on Solana
npx moltspay pay https://server.com service-id --chain solana --prompt "test"

USDC Addresses: | Network | Mint Address | |---------|--------------| | Mainnet | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v | | Devnet | 4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU |

BNB Chain Support

BNB uses the BNBFacilitator with a pre-approval flow. Since CDP doesn't support BNB, we use a different approach:

  1. Client approves a spender once (a one-time on-chain approve, costs ~0.001 BNB gas)
  2. Client signs an EIP-712 payment intent (no gas needed)
  3. Server validates and executes the transfer
  4. Server sponsors gas (~$0.0001 per tx)
# Get free testnet USDC + tBNB for gas
npx moltspay faucet --chain bnb_testnet

# One-time approve (required before the first BNB payment).
# The spender address comes from the server's 402 response; a first `pay` also
# prints it if you haven't approved yet.
npx moltspay approve --spender 0xSPENDER --chain bnb_testnet

# Pay on BNB (client pays no gas after the approve!)
npx moltspay pay https://server.com service-id --chain bnb_testnet --prompt "test"

# Check BNB balance
npx moltspay status

Important: BNB tokens use 18 decimals (not 6 like Base/Polygon).

Token Addresses (BNB Mainnet): | Token | Address | |-------|---------| | USDC | 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d | | USDT | 0x55d398326f99059fF775485246999027B3197955 |

Tempo Testnet

Tempo Moderato is a gas-free testnet that supports the MPP (Machine Payments Protocol). Perfect for testing agent-to-agent payments without any gas fees.

Tempo Stablecoins:

  • pathUSD (USDC equivalent): 0x20c0000000000000000000000000000000000000
  • alphaUSD (USDT equivalent): 0x20c0000000000000000000000000000000000001
# Get free Tempo testnet tokens
npx moltspay faucet --chain tempo_moderato

# Pay on Tempo (gas-free!)
npx moltspay pay https://server.com service-id --chain tempo_moderato --prompt "test"

# Check Tempo balance
npx moltspay status

Explorer: https://explore.testnet.tempo.xyz

Fiat Rail (Alipay / CNY)

Since 2.0.0, MoltsPay supports a fiat payment rail via Alipay AI Pay, settling in CNY alongside the existing USDC crypto rails. It uses the same HTTP 402 flow, so a service can price in USDC, CNY, or both — a purely additive change that leaves existing crypto-only services untouched.

Unlike the crypto rails, alipay is a fiat rail, not a blockchain: there is no gas and no on-chain settlement — the buyer pays in the Alipay app and the merchant settles in CNY. The scheme is alipay-aipay, signed with RSA2.

⚠️ Node CLI only. The Alipay client wraps the alipay-bot CLI and requires Node ≥ 22. The browser build (moltspay/web) throws UnsupportedChainError for alipay. Provider/server config is 100% native TypeScript.

Provider Setup (Selling in CNY)

Add "alipay" to chains, configure provider.alipay with your merchant keys, and add a per-service alipay block with the CNY price. Crypto and fiat can coexist:

{
  "provider": {
    "name": "My Service",
    "wallet": "0x...",
    "chains": ["base", "alipay"],
    "alipay": {
      "seller_id": "2088xxxxxxxxxxxx",
      "app_id": "2021xxxxxxxxxxxx",
      "seller_name": "My Shop",
      "service_id_default": "API_XXXXXXXXXXXXXXXX",
      "private_key_path": "./cert/app_private_key.pem",
      "alipay_public_key_path": "./cert/alipay_public_key.pem"
    }
  },
  "services": [{
    "id": "my-service",
    "function": "handleRequest",
    "price": 0.50,
    "currency": "USDC",
    "alipay": { "price_cny": "7.00", "goods_name": "My Service" }
  }]
}

provider.alipay (required when chains includes alipay):

| Field | Req | Description | |-------|-----|-------------| | seller_id | ✅ | Merchant Alipay ID (16 digits) | | app_id | ✅ | Application ID from the Alipay Open Platform (16 digits) | | seller_name | ✅ | Merchant display name (shown in the 402 challenge) | | service_id_default | ✅ | Default service_id for this provider (e.g. API_XXXXXXXXXXXXXXXX) | | private_key_path | ✅ | Path to the RSA2 merchant private key PEM (relative to the manifest) | | alipay_public_key_path | ✅ | Path to the Alipay platform public key PEM | | gateway_url | — | Open API gateway (default production; use the sandbox URL for testing) | | sign_type | — | Signature algorithm — only RSA2 is supported |

services[].alipay (set on each service that accepts CNY):

| Field | Req | Description | |-------|-----|-------------| | price_cny | ✅ | CNY price as a decimal string in yuan, e.g. "7.00" = 7 CNY (NOT cents) | | goods_name | ✅ | Goods name shown to the buyer in the Alipay app | | service_id | — | Per-service override (defaults to provider.alipay.service_id_default) |

⚠️ price (USDC) and price_cny (CNY) are two independent prices — MoltsPay does no FX conversion. You decide whether USDC 0.50 is equivalent to CNY 7.00. The merchant-backend unit price must match price_cny or the challenge is rejected.

Client Usage (Paying in CNY)

The Alipay client relies on the alipay-bot CLI, which is auto-provisioned on install from the official Alipay CDN (postinstall, best-effort). Set MOLTSPAY_SKIP_CLI_INSTALL=1 to skip it, or install it manually later:

# Manual / CI install of the bundled CLI
npx -y @alipay/agent-payment install-cli
alipay-bot --version          # expect >= 0.3.15

# One-time: open & authorize the Alipay wallet (scan the returned QR in the app)
npx moltspay alipay apply
npx moltspay alipay bind -c "<auth-code>"
npx moltspay alipay check

# Pay a service in CNY
npx moltspay pay https://server.com my-service --rail alipay --prompt "..."

🔐 Keep keys safe. The RSA2 private key authorizes collection on your merchant account. Store the PEM files outside version control and reference them by path in the manifest.

See docs/ALIPAY-RAIL.md for the full reference (402 flow, error codes, end-to-end example, and known issues).

Fiat Rail (WeChat Pay / CNY)

Since 2.1.0, MoltsPay supports a second fiat payment rail via WeChat Pay v3 Native (scan-to-pay), settling in CNY alongside USDC and Alipay. It uses the same HTTP 402 flow, so a service can price in USDC, Alipay-CNY, WeChat-CNY, or any combination — purely additive.

Like alipay, wechat is a fiat rail, not a blockchain. The scheme is wechatpay-native, requests are signed with SHA256-RSA (WECHATPAY2-SHA256-RSA2048), and amounts are sent to WeChat in fen (the manifest still uses yuan decimal strings for consistency).

⚠️ Not a fully autonomous payer. WeChat Pay has no agent-payment product equivalent to Alipay's alipay-bot. The flow is: the server issues a payer-agnostic code_url (Native, no openid — anyone can scan), the SDK client persists the session and polls, a human scans and pays, and the server verifies the order (trade_state === SUCCESS). It is one code, one payment — issue a new code to collect again. See docs/WECHAT-RAIL-DESIGN.md.

Provider Setup (Selling in CNY)

Add "wechat" to chains, configure provider.wechat with your merchant credentials, and add a per-service wechat block with the CNY price:

{
  "provider": {
    "name": "My Service",
    "wallet": "0x...",
    "chains": ["base", "wechat"],
    "wechat": {
      "mchid": "1900000001",
      "appid": "wx8888888888888888",
      "serial_no": "5157F09EFDC096DE15EBE81A47057A72...",
      "private_key_path": "./cert/wechat_apiclient_key.pem",
      "platform_public_key_path": "./cert/wechat_platform_cert.pem",
      "apiv3_key": "your32byteapiv3keyhere0123456789",
      "notify_url": "https://your.host/wechat/notify"
    }
  },
  "services": [{
    "id": "my-service",
    "function": "handleRequest",
    "price": 0.50,
    "currency": "USDC",
    "wechat": { "price_cny": "7.00", "description": "My Service" }
  }]
}

provider.wechat (required when chains includes wechat):

| Field | Req | Description | |-------|-----|-------------| | mchid | ✅ | Merchant id | | appid | ✅ | App id (official account / mini-program / app) | | serial_no | ✅ | Merchant API certificate serial number | | private_key_path | ✅ | Path to the merchant RSA private key PEM (relative to the manifest) | | notify_url | ✅ | Async result notify URL (required by Native order create even when polling) | | platform_public_key_path | — | WeChat platform certificate / public key PEM — enables response signature verification | | apiv3_key | — | APIv3 key (32 bytes) — only needed for callback decryption (Phase 2) | | api_base | — | Open API base URL (default https://api.mch.weixin.qq.com) |

services[].wechat (set on each service that accepts CNY):

| Field | Req | Description | |-------|-----|-------------| | price_cny | ✅ | CNY price as a decimal string in yuan, e.g. "7.00" = 7 CNY (NOT fen) | | description | ✅ | Order description shown to the payer in the WeChat app |

⚠️ price (USDC) and price_cny (CNY) are independent prices — MoltsPay does no FX conversion.

How It Works

  1. An unpaid request to /execute returns 402 with a wechatpay-native entry in accepts[], carrying extra.code_url (weixin://wxpay/bizpayurl?pr=...) and extra.out_trade_no.
  2. The SDK client persists a payment session before surfacing the QR. A channel runtime can show the returned PNG/MEDIA image and continue independently of the original tool call.
  3. A human scans and pays. The Native code_url is a QR payload, not a normal HTTPS checkout link.
  4. The SDK client polls by re-requesting /execute with an X-Payment payload echoing out_trade_no; the server verifies via order query and, on SUCCESS, runs the skill and confirms settlement.
  5. If the process is interrupted, the session can be resumed by payment_session_id or out_trade_no; fulfill is idempotent and returns the stored result once completed.

Buyer SDK Usage

Blocking terminal-style payment:

import { MoltsPayClient } from 'moltspay/client';

const client = new MoltsPayClient();
const result = await client.pay(
  'https://server.com',
  'my-service',
  { prompt: 'hello' },
  {
    rail: 'wechat',
    onPaymentPending: ({ paymentUrl, tradeNo }) => {
      console.log('Render this as a QR:', paymentUrl, tradeNo);
    },
  },
);

Recoverable channel/session payment:

const session = await client.startWechatPayment(
  'https://server.com',
  'my-service',
  { prompt: 'hello' },
  {
    rail: 'wechat',
    autoPoll: true,
    onPaymentPending: ({ paymentUrl, tradeNo }) => {
      // Render paymentUrl as QR and send it to the current channel.
    },
    onWechatPaymentCompleted: async (completed) => {
      // Send completed.resultBody, parsed as needed, back to the channel.
    },
    onWechatPaymentFailed: async (failed) => {
      // Notify timeout/cancel/failure.
    },
  },
);

console.log(session.paymentSessionId, session.outTradeNo);

CLI recoverable flow:

npx moltspay wechat start https://server.com my-service --prompt "hello" --json
npx moltspay wechat status mpay_sess_...
npx moltspay wechat fulfill mpay_sess_...

See examples/wechat-native-pay.ts for a runnable scenario-A demo (mock by default; WECHAT_REAL=1 hits the live gateway).

🔐 Keep keys safe. The merchant private key and APIv3 key authorize collection on your merchant account. Store them outside version control and reference them by path.

Balance Rail (Password-Free Payments)

Since 2.2.0, MoltsPay supports a third payment mode: a custodial balance rail (password-free). A buyer tops up once — via any existing rail — and subsequent purchases are deducted from a server-custodied balance: no signature, no QR scan, no password per transaction. It uses the same HTTP 402 flow (scheme/network balance), so a service can offer crypto, Alipay-CNY, WeChat-CNY, and balance simultaneously — purely additive.

Unlike the other rails, nothing settles externally at purchase time: the ledger lives in SQLite on the provider server (Node's built-in node:sqlite, zero new dependencies). Enabling the rail requires Node.js >= 22.5 on the server; servers that don't enable it keep the package's node >= 18 floor.

⚠️ Custodial trust model. Buyer funds are held by the provider. Since 2.4.0, spending is authorized by a per-request signature, not a bare buyer_id — see Balance Authentication below. With auth_mode: enforce, knowing a buyer_id is not enough to spend. (auth_mode defaults to off for backward compatibility, where the id retains bearer semantics.) See docs/WECHAT-BALANCE-PASSWORDLESS-DESIGN.md.

Balance Authentication (2.4.0)

Before 2.4.0 the balance rail identified a buyer by a single plaintext buyer_id: anyone who knew it could spend that balance (bearer semantics), and the same person writing it two ways split into two accounts. 2.4.0 gives the rail a real identity, in two complementary halves:

  • Identity anchor = WeChat openid. On a WeChat-funded top-up, the server extracts payer.openid from the (signature-verified) order query and records it on the account. Accounts are anchored to the person who actually paid, not to a self-reported string — this removes the account-splitting class of bug at the source. Binding is observational and never overwrites a conflicting openid.
  • Spend credential = a per-request signature. Each deduction carries an EIP-191 signature over a canonical message (domain moltspay-balance-auth:v1 / balance-deduct / buyer_id / request_id / service / timestamp). The server recovers the signer address, TOFU-binds it to the account on first use, and verifies it thereafter. The amount is deliberately not signed (the service id fixes the price server-side); a request_id + a ±5-minute timestamp window bound replay.

provider.balance.auth_mode — staged rollout, not on by default:

| Mode | Behavior | |------|----------| | off (default) | Signature is not checked. Backward-compatible; buyer_id keeps bearer semantics. | | shadow | Verify and log what it would reject, without blocking. Use this to confirm every client is signing before tightening. | | enforce | Reject unsigned / invalid / wrong-signer requests with 401. |

The client signs with its EVM wallet, or — for balance-only clients with no crypto wallet — a per-configDir identity key auto-created at <configDir>/balance-identity.key (0600). Inspect and bind identity with moltspay balance whoami / moltspay balance bind.

Foundation — WeChat response verification. The openid anchor is only trustworthy if the order-query response is verified against the WeChat platform certificate. Configure provider.wechat.platform_public_key_path (present ⇒ every response is verified, and a verification failure throws); without it, the identity anchor is not trustworthy.

Trust model — one agent key spends for all its users. The shipped model is agent-custodial: a single agent key is bound to every account it tops up (signer_address is one-to-many). Accounts stay separated by openid, but whoever holds the agent's key can spend every bound user's balance — protect balance-identity.key accordingly. There is currently no revocation/freeze API (operator-only, via the ledger DB); per-user key isolation is a future upgrade.

WeChat-Funded Password-Free (Scan Once, Then Password-Free)

Since 2.3.0, the WeChat rail can fund the balance instead of charging per transaction. The buyer scans once to load a top-up pack (e.g. ¥20); every subsequent purchase deducts from the balance with no scan, no password, until it runs low and one more scan tops it up. WeChat has no autonomous payer, so "password-free" lives on the balance side — the first purchase against an empty balance still needs one scan, but it buys a pack, not a single item.

Set the ledger currency to CNY (WeChat payer_total credits 1:1 as fen — no FX), define top-up packs, and the client handles the rest:

{
  "provider": {
    "name": "My Service",
    "wallet": "0x...",
    "chains": ["balance", "wechat"],
    "balance": {
      "db_path": "./data/balance-cny.sqlite",
      "currency": "CNY",
      "single_limit": "50.00",
      "daily_limit": "200.00",
      "topup_packs": ["20.00", "50.00"],
      "default_pack": "20.00",
      "auto_topup_max": "50.00"
    },
    "wechat": {
      "mchid": "1900000001",
      "appid": "wx8888888888888888",
      "serial_no": "...",
      "private_key_path": "/abs/apiclient_key.pem",
      "notify_url": "https://your-host/wechat/notify",
      "platform_public_key_path": "/abs/wechat_platform_cert.pem",
      "apiv3_key": "<32-byte apiv3 key>"
    }
  },
  "services": [{
    "id": "my-service",
    "function": "handleRequest",
    "price": 3.99,
    "currency": "CNY",
    "balance": { "price": "3.99" }
  }]
}

New provider.balance fields (2.3.0):

| Field | Req | Description | |-------|-----|-------------| | currency | — | Set to "CNY" to fund via WeChat 1:1 (fen). A ledger remembers its currency and refuses to start on a mismatch | | topup_packs | — | Offered recharge amounts (yuan strings), e.g. ["20.00","50.00"] | | default_pack | — | Pack the client auto-selects when funds are short (must be in topup_packs) | | auto_topup_max | — | Ceiling on client auto-top-up without explicit pack selection |

Top-ups are confirmed by polling the WeChat order query (trade_state === SUCCESS) and credited idempotently on out_trade_no. The async callback webhook (POST /wechat/notify) is not implemented yet — polling is the only confirmation path today, which is safe but adds latency. Configure platform_public_key_path regardless: it makes the server verify WeChat's response signatures, which is what makes the payer.openid behind balance identity trustworthy. Usage — a single pay command:

npx moltspay balance set-buyer my-buyer-id
# First purchase: prints a pack QR, waits for the scan, credits, completes.
# Later purchases: password-free, no QR — deducted from the balance.
npx moltspay pay https://server.com my-service '{"prompt":"hello"}'

Money-Safety Design

  • Integer-cent accounting — no floating-point drift.
  • Atomic check-and-deduct — a single SQLite transaction (UPDATE ... WHERE balance_sat >= ?); a balance can never go negative under concurrency.
  • Triple idempotency, each enforced by a unique index: deducts replay on the client's request_id, top-ups replay on the external settlement reference, refunds replay per deduct. No retry can double-charge, double-credit, or double-refund.
  • Server-side hard limits per buyer: per-transaction (default 5.00) and daily (default 10.00).
  • Automatic refund — the deduct lands before the skill runs; if the skill fails, the server refunds it in the same request.
  • Pure 402 challenges — unlike the QR rails, a 402 response mints nothing, so unpaid challenges have no side effects.
  • Full audit trail — every top-up, deduct, and refund is a permanent ledger row, queryable per buyer.

Provider Setup

Add "balance" to chains, configure provider.balance, and add a per-service balance block (its price defaults to the service's top-level price):

{
  "provider": {
    "name": "My Service",
    "wallet": "0x...",
    "chains": ["base", "balance"],
    "balance": {
      "db_path": "./data/balance.sqlite",
      "currency": "USD",
      "single_limit": "5.00",
      "daily_limit": "10.00"
    }
  },
  "services": [{
    "id": "my-service",
    "function": "handleRequest",
    "price": 3.99,
    "currency": "USDC",
    "balance": { "price": "3.99" }
  }]
}

provider.balance (required when chains includes balance):

| Field | Req | Description | |-------|-----|-------------| | db_path | ✅ | SQLite ledger file path (relative to the manifest); created on first start | | currency | — | Ledger quote currency (default USD) | | single_limit | — | Default per-transaction limit for new buyers (default "5.00") | | daily_limit | — | Default daily limit for new buyers (default "10.00") |

services[].balance:

| Field | Req | Description | |-------|-----|-------------| | price | — | Price in the ledger currency as a decimal string; defaults to the service's price |

The server also exposes balance-management endpoints:

| Endpoint | Description | |----------|-------------| | GET /balance?buyer_id= | Balance, limits, and today's spend | | POST /balance/topup | Verify an externally settled payment and credit the ledger | | POST /balance/refund | Reverse a deduct (operator/agent use) | | GET /balance/transactions?buyer_id= | Ledger history, newest first |

⚠️ Protect these endpoints (reverse-proxy auth / IP allowlist). In particular, the alipay top-up path is operator-trusted in this MVP: Alipay AI-Pay verification requires the buyer-side payment_proof, so the server credits a reported trade_no without gateway verification. The crypto path verifies the tx_hash on-chain (receipt + transfer to the provider wallet + amount) and the wechat path queries the order for SUCCESS; all three are idempotent on the external reference.

How It Works

  1. An unpaid request to /execute returns 402 with a balance entry in accepts[] (pure — nothing is minted).
  2. The client re-requests with an X-Payment payload carrying { buyer_id, request_id }, where request_id is a fresh client-generated UUID.
  3. The server deducts atomically first (idempotent on request_id), then runs the skill, and refunds automatically if the skill fails. This is the inverse of the QR rails (verify paid → run → confirm).
  4. The response carries the ledger transaction id in X-Payment-Response.

Buyer Usage

# One-time: persist your buyer id (account label). Under auth_mode=enforce, spending also
# requires the bound signing key — see "Balance Authentication" above.
npx moltspay balance set-buyer my-buyer-id

# Top up once, via any settled rail
npx moltspay balance topup https://server.com 10.00 --rail crypto --tx-hash 0xabc... --chain base
npx moltspay balance topup https://server.com 10.00 --rail wechat --out-trade-no MP1719...
npx moltspay balance topup https://server.com 10.00 --rail alipay --trade-no 2026...

# Then pay password-free — no wallet, no QR
npx moltspay pay https://server.com my-service '{"prompt":"hello"}' --rail balance

# Check balance and history
npx moltspay balance query https://server.com
npx moltspay balance transactions https://server.com

SDK equivalent:

import { MoltsPayClient } from 'moltspay/client';

const client = new MoltsPayClient();
client.setBuyerId('my-buyer-id'); // persisted in config.json

await client.topupBalance('https://server.com', {
  rail: 'crypto', amount: '10.00', txHash: '0xabc...', chain: 'base',
});

const result = await client.pay('https://server.com', 'my-service',
  { prompt: 'hello' }, { rail: 'balance' });

const info = await client.getBuyerBalance('https://server.com');
console.log(info.balance, info.today_spent);

Live Example: Zen7 Video Generation

Live service at https://moltspay.com/a/zen7

Services:

  • text-to-video - $0.01 USDC - Generate video from text prompt
  • image-to-video - $1.49 USDC - Animate a static image

Supported Chains: Base, Polygon, Solana, BNB, Tempo (mainnet & testnet)

Try it:

# List services
npx moltspay services https://moltspay.com/a/zen7

# Pay on Base (default)
npx moltspay pay https://moltspay.com/a/zen7 text-to-video --prompt "a happy cat"

# Pay on different chains
npx moltspay pay https://moltspay.com/a/zen7 text-to-video --chain polygon --prompt "a happy cat"
npx moltspay pay https://moltspay.com/a/zen7 text-to-video --chain bnb_testnet --prompt "a happy cat"
npx moltspay pay https://moltspay.com/a/zen7 text-to-video --chain solana_devnet --prompt "a happy cat"

Use Cases

  • AI Video Generation - Pay per video generated
  • Image Processing - Pay for AI image editing/enhancement
  • Data APIs - Monetize proprietary datasets
  • Compute Services - Sell GPU time to other agents
  • Content Generation - AI writing, music, code generation

Related Projects

Community & Support

Join our Discord for help, feedback, and updates:

--> MoltsPay Discord

Or visit the #moltspay-support channel directly.

Links

  • Website: https://moltspay.com
  • Discord: https://discord.gg/QwCJgVBxVK
  • npm: https://www.npmjs.com/package/moltspay
  • PyPI: https://pypi.org/project/moltspay/
  • x402 Protocol: https://www.x402.org/
  • Coinbase CDP: https://portal.cdp.coinbase.com/

License

MIT