Agent-First Pay: Multi-Chain Payments for AI Agents
A single agent-oriented payment interface that spans Cashu, Lightning, Solana, EVM, and Bitcoin with spend limits and JSONL output.
Your agent needs to pay for an API call. It shells out to a wallet CLI, parses a mix of stdout text and stderr warnings, checks an exit code that means something different per tool, and prays the transaction ID is somewhere in the output. Then it needs to check whether the payment actually confirmed — which means calling a different tool, or hitting a block explorer API, or parsing another blob of human-readable text.
Now multiply that by five networks. Cashu has its own token format. Lightning uses BOLT11 invoices and LNURL. Solana and EVM each have their own RPC semantics. Bitcoin on-chain needs UTXO management. Five different CLIs, five different output formats, five different ways to say “insufficient funds.”
afpay is a single binary that speaks all five networks through one interface. Every output is structured JSONL. Every error has a stable error_code. Every secret field uses the _secret suffix and is auto-redacted. The agent writes one integration and gets Cashu, Lightning, Solana, EVM, and Bitcoin.
One CLI, five networks
# Cashu — ecash micropayments
afpay send --network cashu --amount 21
# {"code":"cashu_send","token":"cashuBo2F...","amount_sats":21,"mint":"https://mint.minibits.cash/Bitcoin"}
# Lightning — pay an invoice
afpay send --network ln --to lnbc1pjk...
# {"code":"ok","result":{"preimage":"a1b2...","fee_msats":1000},"trace":{"duration_ms":820}}
# Solana — send USDC
afpay send --network sol --to 7xKX... --amount 1000000 --token usdc
# {"code":"ok","result":{"transaction_id":"5xYz...","status":"confirmed"},"trace":{"duration_ms":2100}}
# EVM — send on Base
afpay send --wallet evm-base --to 0xAbc... --amount 1000000 --token usdc
# {"code":"ok","result":{"transaction_id":"0x9f8...","status":"confirmed","fee":{"value":"0.000042","token":"gwei"}},...}
# Bitcoin — on-chain
afpay send --network btc --to tb1q... --amount 5000
# {"code":"ok","result":{"transaction_id":"abc123...","status":"pending"},"trace":{"duration_ms":340}}
Same structure every time. code tells the agent what happened. error_code (when present) is stable and machine-readable — insufficient_balance, invoice_expired, limit_exceeded. The agent branches on codes, not on parsed English.
Spend limits: the agent can’t drain the wallet
The first question anyone asks: “What stops the agent from sending everything?” Spend limits.
afpay limit add --scope network --network cashu --window 1h --max-spend 10000
afpay limit add --scope wallet --wallet w_1a2b --window 24h --max-spend 50000
afpay limit add --scope global-usd-cents --window 24h --max-spend 500000
Multi-tier sliding windows — per-wallet, per-network, or global (cross-network, requires exchange rate). All rules are checked before every send. Any breach rejects the transaction with limit_exceeded before anything touches the network.
In RPC and REST server modes, limits are enforced server-side. The agent talks to the daemon over gRPC or HTTP; it cannot modify the daemon’s limit config. The operator sets the rules; the agent operates within them.
Receive with wait: block until paid
Agents don’t poll well. They need to issue an invoice and know when it’s paid:
# Generate address and wait for incoming funds
afpay receive --network sol --wallet sol-main --wait --amount 1000000 --token usdc \
--wait-timeout-s 120 --wait-poll-interval-ms 2000
afpay returns the receive address immediately, then polls the chain until the payment arrives or the timeout expires. One command, one structured result when the funds land. Works for Solana, EVM, and Bitcoin on-chain.
For Lightning, the receive flow returns a BOLT11 invoice with a quote_id. The agent can check status or claim funds once paid:
afpay receive --network ln --amount 500
# {"code":"ok","result":{"invoice":"lnbc5u1p...","quote_id":"q_abc123"}}
# Later: claim the payment
afpay receive --network cashu --ln-quote-id q_abc123Transaction history: incremental sync
Agents need to audit what happened. history update pulls only new events since the last sync — no re-downloading the full chain history on every call:
afpay history update --network sol
# {"code":"ok","result":{"wallet":"sol-main","new_records":3,"total_records":47},"trace":{"duration_ms":1200}}
afpay history list --network sol --limit 5
# {"code":"ok","result":{"records":[{"transaction_id":"5xYz...","direction":"outbound","amount":{"value":"1000000","token":"usdc"},"status":"confirmed",...},...]}}
afpay history status --transaction-id 5xYz...
# {"code":"ok","result":{"transaction_id":"5xYz...","status":"confirmed","confirmations":12,...}}
History is stored locally (redb or PostgreSQL). history list never hits the network — it queries the local store. history update does the chain sync. The agent controls when network calls happen.
Deployment: single machine to multi-server
Single machine
All networks in one process. Simplest setup:
afpay --mode mcp # MCP server for Claude Desktop
afpay --mode rest # REST API (curl-accessible)
afpay --mode interactive # REPL for humans
Add to Claude Desktop:
{
"mcpServers": {
"afpay": { "command": "afpay", "args": ["--mode", "mcp"] }
}
}Multi-server (cascading RPC)
Networks run as independent daemons. A coordinator forwards requests over encrypted gRPC:
Agent (Claude)
│ MCP (stdio)
▼
afpay --mode mcp ← coordinator
│ gRPC (AES-256-GCM PSK)
├──→ afpay --mode rpc (wallet-server) ← VPS-A: ln + cashu
└──→ afpay --mode rpc (chain-server) ← VPS-B: sol + evm + btc
Each RPC daemon enforces its own spend limits independently. The coordinator config maps networks to named nodes:
[afpay_rpc.wallet-server]
endpoint = "vps-a:9400"
endpoint_secret = "abc..."
[afpay_rpc.chain-server]
endpoint = "vps-b:9400"
endpoint_secret = "def..."
[providers]
ln = "wallet-server"
cashu = "wallet-server"
sol = "chain-server"
evm = "chain-server"
btc = "chain-server"
Fault isolation — one network crashing doesn’t affect others. Minimal attack surface — each container only has the SDK for its network. Independent scaling — hot wallets on fast VPS, cold storage on secure hardware.
Docker
Single-container deployment with supervisord:
docker compose -f docker/docker-compose.yml up --build
AFPAY_MODE selects rest/rpc/mcp. Secrets auto-generated on first run. Works with Podman too.
Design constraints
Pure Rust, zero C dependencies. No unwrap/expect/panic anywhere — enforced via #![deny(...)]. All secret fields use the _secret suffix from Agent-First Data and are auto-redacted in output and logs.
Feature flags control compilation. Need only Cashu? cargo build --features cashu. Need a pure RPC coordinator with no wallet SDK? cargo build --no-default-features --features mcp. The binary only includes the network SDKs you actually use.
Install
brew install cmnspore/tap/afpay # macOS/Linux
scoop bucket add cmnspore https://github.com/cmnspore/scoop-bucket && scoop install afpay # Windows
cargo install agent-first-pay # any platform