SaturnZap Lightning Wallet
Non-custodial Lightning Network wallet for autonomous AI agents. All output is JSON by default — no --json flag needed. Parse stdout for success ("status": "ok") and stderr for errors ("status": "error").
See {baseDir}/references/json-contracts.md for full JSON output shapes.
For deep agent integration scenarios, error recovery trees, and anti-patterns, see docs/agent-guide.md.
Quick Decision — 5 Most Common Agent Actions
# 1. First-time setup (idempotent, one command)
sz setup --auto
# 2. Pay for API access (L402, with spending cap)
sz fetch "https://api.example.com/data" --max-sats 100
# 3. Check if operational
sz status # parse: synced, peer_count > 0, usable_channel_count > 0
# 4. Receive payment (blocking)
sz invoice --amount-sats 1000 --memo "service fee" --wait
# 5. Send a Lightning payment (with cap)
sz pay --invoice "lnbc..." --max-sats 5000
When to Use
✅ USE this skill when:
- Paying for API access via Lightning (L402 / HTTP 402 auto-pay)
- Creating BOLT11 invoices to receive payments
- Paying BOLT11 invoices or sending keysend payments
- Checking wallet balance (on-chain + Lightning)
- Opening, closing, or listing Lightning channels
- Connecting to or managing Lightning peers
- Monitoring channel liquidity health
- Requesting inbound liquidity from an LSP
When NOT to Use
❌ DON'T use this skill when:
- On-chain-only Bitcoin transactions (use a Bitcoin wallet CLI)
- Custodial Lightning services (use hosted APIs like Strike, LNBits)
- Non-Lightning crypto payments (ETH, stablecoins, etc.)
- Mainnet is the default network. Use
--network signet for development and testing with free test coins.
Setup
# Install (one-time) — one-line installer handles uv + the vendored ldk-node wheel
curl -LsSf https://raw.githubusercontent.com/lqwdtech/SaturnZap/main/install.sh | sh
# Or, if you prefer the explicit uv invocation:
# uv tool install saturnzap \
# --find-links https://github.com/lqwdtech/SaturnZap/releases/expanded_assets/v1.3.1
# Set passphrase (required — encrypts the seed on disk)
export SZ_PASSPHRASE="your-secure-passphrase"
# Initialize wallet (first time only — generates BIP39 seed, starts node)
# RECOMMENDED for agent hosts: write the mnemonic to a mode-0600 file instead
# of stdout, so it never lands in tool-call transcripts or orchestrator logs.
sz init --backup-to ~/.saturnzap-mnemonic --no-mnemonic-stdout
# Or, for the LQWDClaw faucet on mainnet (sets a readable alias):
sz init --for-lqwd-faucet --backup-to ~/.saturnzap-mnemonic --no-mnemonic-stdout
# Interactive operators can omit the flags; the mnemonic prints once for backup:
# sz init
# Verify node is running
sz status
# Recommended: keep the node alive across reboots (systemd)
sz service install
Passphrase injection — pick the safest available option
In order of preference:
-
Systemd EnvironmentFile= (recommended for production agents).
sz service install writes /etc/saturnzap/saturnzap.env (mode 0600,
owner-only) and references it from the systemd unit. The unit file itself
contains no secrets.
-
Secret store / environment injection at the agent runtime layer
(Vault, 1Password CLI, doppler, AWS Secrets Manager, etc.) writing
SZ_PASSPHRASE into the process environment.
-
Shell export in a non-shared session — fine for local development.
-
openclaw.json (development/testing only). This stores the passphrase
at rest in a config file. Use only on isolated dev machines and only when
the file is mode-0600 in an encrypted home directory:
{
"skills": {
"entries": {
"saturnzap": {
"enabled": true,
"env": { "SZ_PASSPHRASE": "your-dev-passphrase" }
}
}
}
}
Do not use this path for wallets holding production funds.
Security Model — Why This Skill Is Flagged "Suspicious" (And Why That's Correct)
SaturnZap is a self-custodial Lightning wallet. The ClawHub security scanner
correctly flags four behaviors that are inherent to any self-custodial wallet. If
you're evaluating this skill, read this section before installing.
1. Installs a vendored wheel from GitHub Releases. The --find-links flag pulls
the ldk-node Python bindings wheel from the SaturnZap GitHub Releases page. This is
because ldk-node is not yet published to PyPI. The wheel is built from the official
LDK Node v0.7.0 source, vendored in-repo at vendor/ldk_node-0.7.0-py3-none-any.whl,
and its SHA256 is pinned in vendor/ldk_node.sha256 (verified in CI on every push).
2. Emits a BIP39 mnemonic on first init. sz init prints the 24-word recovery
phrase exactly once, to stdout, so the operator can back it up. It is never
re-emitted by any other command. It is also never sent over the network. Losing the
mnemonic means losing the funds; that is the self-custody tradeoff.
3. Passphrase injection via openclaw.json. The passphrase decrypts the seed
file (Fernet AES-128 + PBKDF2, 600k iterations). It must be available on every
wallet operation. Two options:
- Environment variable (recommended):
export SZ_PASSPHRASE=... in your shell
profile or systemd unit. Never written to disk by SaturnZap.
openclaw.json: convenient for agents, but the passphrase lives in that file
at rest. Use only if the file itself is protected (0600, encrypted home dir,
etc.).
4. Optional systemd service. sz service install is optional. It keeps the
Lightning node running across reboots, which is necessary for payments to clear
reliably. The service runs as the invoking user, writes state under
~/.local/share/saturnzap/<network>/, and does not escalate privileges beyond
opening port 9735 via UFW if UFW is active.
What this skill does NOT do:
- Transmit the seed or passphrase anywhere
- Contact any server except Esplora (Bitcoin chain data) and peers you explicitly add
- Run with elevated privileges
- Modify any system config outside the Lightning listen port
Operator responsibilities:
- Back up the 24-word mnemonic offline before funding the wallet
- Use a strong passphrase (12+ chars; minimum enforced at init)
- Set
SZ_MCP_MAX_SPEND_SATS to cap agent spending
- Review
docs/security-scenarios.md in the SaturnZap repo for the full threat model
For the full security architecture, see
docs/security-scenarios.md.
Node Management
# Guided first-run (idempotent — skips completed steps)
sz setup
# Non-interactive setup: init + address + request inbound from LQWD
sz setup --auto
# Initialize wallet (first time — writes seed, starts node)
sz init
# Start node (auto-starts from encrypted seed)
# NOTE: 'sz start' runs as a foreground daemon and blocks until SIGTERM/SIGINT.
# For agents/scripts, prefer 'sz service install' (systemd) or let commands
# auto-start the node on demand. Use 'sz start --foreground' for the legacy
# "print status and exit" behaviour.
sz start
# Stop node
sz stop
# Stop node and cooperatively close all channels first
sz stop --close-all
# Check node status (pubkey, sync state, peer/channel counts)
sz status
# Get connection URI to share with other wallets (pubkey@host:port)
sz connect-info
The node auto-starts when needed. Most commands call sz start internally if the node isn't running.
For agents: Prefer sz setup --auto over sz init — it's idempotent and handles the full first-run flow in one command.
Wallet
# Get a new on-chain address (for receiving signet faucet deposits)
sz address
# Send sats on-chain to an address
sz send <address> --amount 50000
# Send ALL on-chain sats to an address
sz send <address>
# Check balances (on-chain + lightning + per-channel breakdown)
sz balance
Payment commands (pay, keysend, send) include pre-flight balance checks — they return a clear INSUFFICIENT_FUNDS error with the current balance if funds are too low.
Payments
# Create invoice to receive 1000 sats
sz invoice --amount-sats 1000 --memo "payment for data"
# Create invoice and wait until paid (blocks until payment or expiry)
sz invoice --amount-sats 1000 --memo "service fee" --wait
# Create variable-amount invoice (payer chooses amount)
sz invoice --amount-sats 0 --memo "tips welcome"
# Pay a BOLT11 invoice (with optional spending cap)
sz pay --invoice "lnbc..." --max-sats 5000
# Send spontaneous keysend payment
sz keysend --pubkey "03abc..." --amount-sats 500
# View payment history
sz transactions --limit 10
Spending Caps
Always use --max-sats when paying invoices on behalf of a user to enforce a spending limit. If the invoice exceeds the cap, the payment is rejected with a SPENDING_CAP_EXCEEDED error.
L402 (HTTP 402 Auto-Pay)
Fetch a URL and automatically pay the Lightning invoice if the server returns HTTP 402:
# Basic L402 fetch
sz fetch "https://api.example.com/premium-data"
# With spending cap (recommended for agent use)
sz fetch "https://api.example.com/data" --max-sats 100
# POST with headers and body
sz fetch "https://api.example.com/submit" \
-X POST \
-H "Content-Type: application/json" \
-d '{"query": "analysis"}' \
--max-sats 500
# Custom timeout
sz fetch "https://api.example.com/slow" --timeout 60
The response includes the HTTP status, body (parsed as JSON if possible), and payment details if a 402 was paid.
Channels
# List all channels
sz channels list
# Open a channel to a specific peer
sz channels open --peer "03abc...@1.2.3.4:9735" --amount-sats 100000
# Open a channel via LQWD LSP (defaults to LQWD-AI-Grid on mainnet — LSPS1/LSPS2 JIT-capable)
sz channels open --lsp lqwd --amount-sats 100000
# Open a channel via a specific LQWD region (e.g. Japan)
sz channels open --lsp lqwd --region JP --amount-sats 200000
# Close a channel cooperatively
sz channels close --channel-id "abc123" --counterparty "03abc..."
# Force-close a channel
sz channels close --channel-id "abc123" --counterparty "03abc..." --force
# Wait for a channel to become usable (blocks until ready or timeout)
sz channels wait --channel-id "abc123" --timeout 300
Minimum channel sizes: LQWD nodes require at least 2,000,000 sats per channel. If the peer rejects, sz returns a CHANNEL_REJECTED error with the reason (e.g. "below min chan size").
Peers
# List connected peers
sz peers list
# Connect to a peer
sz peers add "03abc...@1.2.3.4:9735"
# Disconnect a peer
sz peers remove "03abc..."
# Trusted peers (waive anchor reserve + allow 0-conf channels).
# The full LQWD fleet is trusted by default on mainnet.
sz peers trusted-list
sz peers trust "03abc..."
sz peers untrust "03abc..."
Config
# Inspect and edit ~/.config/saturnzap/config.toml programmatically.
sz config list
sz config get node.alias
sz config set node.alias "my-agent"
sz config set node.listen_port 9735
sz config unset esplora_url
Changes apply on the next node start. Known keys: node.alias,
node.listen_port, node.min_confirms, node.trusted_peers_no_reserve,
esplora_url, network.
Liquidity
# Channel health report (scores, labels, recommendations)
sz liquidity status
# Request inbound liquidity from LQWD (auto-selects nearest region)
sz liquidity request-inbound --amount-sats 100000
# Request inbound from a specific region
sz liquidity request-inbound --amount-sats 100000 --region CA
Health scores range 0–100. Labels: healthy (40+), warning (20–40), critical (0–20).
Channels with offline peers are flagged as stale with force-close recommendations.
Service Management
# Install and start SaturnZap as a systemd service (persistent node)
sz service install
# Check service status
sz service status
# Remove the systemd service
sz service uninstall
When the systemd service is running, the node stays up between CLI calls (no per-command startup overhead).
Guardrails
- Always use
--max-sats on sz pay and sz fetch to enforce spending caps. Never pay unbounded invoices autonomously.
- Never expose the mnemonic — the seed phrase from
sz init must never appear in chat, logs, or tool output after initial display.
- Check balance first — payment commands now check balance automatically and return
INSUFFICIENT_FUNDS with the current balance. You can also run sz balance before large payments.
- Parse JSON output — all
sz commands output JSON to stdout on success, stderr on error. Use the status field to branch logic.
- Check for warnings —
sz pay, sz keysend, sz balance, and sz fetch may include a "warnings" array when channel capacity is low or no channels exist. Act on warnings: open a channel, request inbound liquidity, or log for review.
- Verify connectivity — after setup, run
sz connect-info --check to confirm the Lightning port is reachable. If "reachable" is false, the firewall may be blocking peers.
- Mainnet by default — SaturnZap defaults to Bitcoin mainnet (real sats). Use
--network signet for testing. Always use --max-sats spending caps on mainnet.
- Passphrase security —
SZ_PASSPHRASE encrypts the seed on disk. Keep it out of chat and logs.
Workflow Templates
Pay for API Access (L402)
# Check balance first
sz balance
# Fetch a paid endpoint with a spending cap
sz fetch "https://api.example.com/premium" --max-sats 100
Receive a Payment
# Create an invoice and wait until paid (recommended for agents)
sz invoice --amount-sats 1000 --memo "service fee" --wait
# Or: create invoice, share it, then poll manually
sz invoice --amount-sats 1000 --memo "service fee"
# Share the `invoice` field from the JSON output with the payer
sz transactions --limit 1
Check Channel Health
# Overall health report with recommendations
sz liquidity status
# Detailed balance breakdown
sz balance
Open a Channel and Send Payment
# Open a channel
sz channels open --lsp lqwd --amount-sats 100000
# Wait for channel to become usable
sz channels wait --timeout 300
# Pay an invoice once the channel is ready
sz pay --invoice "lnbc..." --max-sats 5000
Notes
- All output is JSON. Use
--pretty or SZ_PRETTY=1 for human-readable formatting.
- The node auto-starts from the encrypted seed when any command needs it. Explicit
sz start is rarely needed.
- LQWD region codes:
AI (LQWD-AI-Grid, default on mainnet, LSPS1/LSPS2 JIT-capable), NEAREST (timezone-based fallback), or a specific country: CA, US, SE, FR, GB, IE, IT, DE, BH, JP, AU, SG, HK, KR, ID, IN, BR, ZA.
sz fetch caches L402 tokens — repeated requests to the same endpoint reuse the token without re-paying.