ResourcesOpen Source AI SkillsLightning (Bitcoin) Payments for Agents

Lightning (Bitcoin) Payments for Agents

Skill Lightning Bitcoin Self-Custody

Set up a self-custodial Lightning Address (like alice@yourdomain.com) backed by your own wallet. Your agent handles the LNURL proxy, wallet API, and public exposure — you get a human-readable payment endpoint where every sat goes directly to your keys.

Core dependency: github.com/ACINQ/phoenixd — self-custodial Lightning node, single binary, zero config. Docs · Releases

Download

⬇ Download lightning-address.skill

Install with OpenClaw:

openclaw skill install lightning-address.skill

Or drop the file into your ~/.openclaw/skills/ directory.

What This Skill Does

A Lightning Address is a human-readable payment endpoint. When someone pays alice@example.com, their wallet resolves it via LNURL-pay (LUD-16) to create a fresh invoice from the recipient's wallet. No intermediary holds funds.

This skill walks your agent through the full setup:

How It Works

Sender wallet → GET example.com/.well-known/lnurlp/alice
                        ↓
              Static JSON (returns callback URL)
                        ↓
Sender wallet → GET callback?amount=<msats>
                        ↓
              LNURL proxy → wallet createinvoice API
                        ↓
              Returns Lightning invoice (bolt11)
                        ↓
Sender wallet pays → sats arrive in your wallet

Two components: a static file on the domain (the pointer) and a live LNURL proxy on your machine (the invoice generator). The domain owner and wallet owner can be different people.

Wallet Options

Wallet Self-Custody Setup Best For
Phoenixd ✅ Yes (BIP39) ~5 min Agents, servers, automation
Alby Hub Yes (LDK) ~15 min GUI users, Nostr/NWC
LND Yes Hours Full control, routing
LNbits Depends ~20 min Multi-user, hackathons

Phoenixd trade-off: ACINQ is your sole channel partner — they see payment amounts and timing. Acceptable for a spending wallet; not for serious privacy. The only mitigation is running a full LND node with multiple channel partners.

When Someone Else Owns the Domain

You don't need to own the domain. You run the wallet and proxy on your machine; the domain owner adds one static file. That's it.

What You Do

What to Send the Domain Owner

Add one file to your website at .well-known/lnurlp/yourname with this content:

{
  "callback": "https://YOUR-CALLBACK-URL/lnurlp/yourname/callback",
  "metadata": "[[\"text/identifier\",\"yourname@theirdomain.com\"],[\"text/plain\",\"Pay yourname\"]]",
  "tag": "payRequest",
  "minSendable": 1000,
  "maxSendable": 100000000
}

Serve it over HTTPS as JSON. If on GitHub Pages, just commit the file.

The domain owner does not need to run any software, receive any credentials, or have access to your wallet. They can revoke the address by deleting the file — same trust model as email routing.

Step 1: Install Phoenixd

Download the latest release for your platform from github.com/ACINQ/phoenixd/releases.

# macOS arm64
wget https://github.com/ACINQ/phoenixd/releases/download/v0.7.3/phoenixd-0.7.3-macos-arm64.zip
unzip phoenixd-0.7.3-macos-arm64.zip
mv phoenixd phoenix-cli ~/.local/bin/

# Linux x86_64
wget https://github.com/ACINQ/phoenixd/releases/download/v0.7.3/phoenixd-0.7.3-linux-x64.zip
unzip phoenixd-0.7.3-linux-x64.zip
mv phoenixd phoenix-cli ~/.local/bin/

# First run — generates seed, creates config
phoenixd
# Type "I understand" when prompted about fee credit

⚠️ Critical: Back up ~/.phoenix/seed.dat immediately. This 12-word BIP39 phrase is your only recovery path. Store it encrypted, offline, separate from the machine running phoenixd.

Who Built Phoenixd

ACINQ — founded 2014, France. One of the three original Lightning Network implementations (alongside Lightning Labs and Blockstream). $10M funded. Their Lightning node (Eclair) has been running since 2017. Phoenix Wallet is one of the most respected self-custodial Lightning wallets on mobile. They co-authored the Lightning Protocol 1.0 specification.

Step 2: Understand the Costs

Phoenixd uses ACINQ as a Lightning Service Provider (LSP). When you receive your first payment large enough to cover fees, ACINQ opens a channel:

Setting Channel Capacity Fee (1% + mining) Approx USD
2M sats (default) ~$1,414 ~20,137 sats ~$14
5M sats ~$3,535 ~50,137 sats ~$35
10M sats ~$7,070 ~100,137 sats ~$71

Important details:

Source: phoenix.acinq.co/server/auto-liquidity

Step 3: Phoenixd API Reference

All API calls use HTTP Basic Auth (empty username, password from ~/.phoenix/phoenix.conf).

# Check balance
curl -s http://localhost:9740/getbalance -u :$PASSWORD

# Get node info
curl -s http://localhost:9740/getinfo -u :$PASSWORD

# Create invoice (receive payment)
curl -s -X POST http://localhost:9740/createinvoice \
  -u :$PASSWORD \
  -d description='Coffee fund' \
  -d amountSat=5000

# Pay invoice (send payment)
curl -s -X POST http://localhost:9740/payinvoice \
  -u :$PASSWORD \
  -d invoice=lnbc...

# List incoming payments
curl -s http://localhost:9740/payments/incoming -u :$PASSWORD

# List outgoing payments
curl -s http://localhost:9740/payments/outgoing -u :$PASSWORD

The API is fully documented at phoenix.acinq.co/server/api.

Step 4: LNURL Proxy (Full Implementation)

The LNURL proxy is a lightweight Python HTTP server that translates LNURL-pay callback requests into phoenixd invoice creation calls. Zero dependencies — stdlib only.

#!/usr/bin/env python3
"""LNURL-pay proxy for phoenixd. Zero dependencies."""
import http.server, json, urllib.request, urllib.parse, base64, os

PHOENIXD_URL = os.environ.get("PHOENIXD_URL", "http://localhost:9740")
PHOENIXD_PASS = os.environ.get("PHOENIXD_PASS", "")  # limited API password
LN_ADDRESS = os.environ.get("LN_ADDRESS", "agent@yourdomain.com")
PORT = int(os.environ.get("PORT", "8089"))

class LNURLHandler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        path = self.path.split("?")[0]
        params = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)

        # Callback endpoint: wallet sends amount, we create invoice
        if path.endswith("/callback") and "amount" in params:
            amount_msat = int(params["amount"][0])
            description = json.dumps([
                ["text/identifier", LN_ADDRESS],
                ["text/plain", f"Pay {LN_ADDRESS}"]
            ])

            # Create invoice via phoenixd
            auth = base64.b64encode(f":{PHOENIXD_PASS}".encode()).decode()
            data = urllib.parse.urlencode({
                "description": description,
                "amountSat": amount_msat // 1000
            }).encode()

            req = urllib.request.Request(
                f"{PHOENIXD_URL}/createinvoice",
                data=data, method="POST",
                headers={"Authorization": f"Basic {auth}"}
            )
            resp = json.loads(urllib.request.urlopen(req).read())

            self.send_response(200)
            self.send_header("Content-Type", "application/json")
            self.send_header("Access-Control-Allow-Origin", "*")
            self.end_headers()
            self.wfile.write(json.dumps({
                "pr": resp["serialized"],
                "routes": []
            }).encode())
        else:
            self.send_response(404)
            self.end_headers()

    def log_message(self, fmt, *args): pass  # silence logs

if __name__ == "__main__":
    server = http.server.HTTPServer(("0.0.0.0", PORT), LNURLHandler)
    print(f"LNURL proxy on :{PORT} → {PHOENIXD_URL}")
    server.serve_forever()

Run it:

PHOENIXD_PASS="your-limited-password" \
LN_ADDRESS="alice@yourdomain.com" \
python3 lnurl-proxy.py

Step 5: Expose Your Proxy Publicly

Your LNURL proxy needs a public HTTPS endpoint so sender wallets can reach it. Three options:

Option A: Tailscale Funnel (Recommended)

Free, stable URL that survives reboots. No account besides Tailscale.

# One-time setup
tailscale funnel --bg --set-path /ln http://127.0.0.1:8089

# Verify
tailscale serve status
# Your URL: https://your-machine.tail12345.ts.net/ln

Option B: Cloudflare Tunnel

Free tier available. Requires Cloudflare account and domain on Cloudflare DNS.

cloudflared tunnel --url http://localhost:8089

⚠️ Quick tunnels generate random domains that break on restart. Use named tunnels for persistence.

Option C: VPS Reverse Proxy

Run nginx/caddy on a VPS pointing to your home machine via WireGuard/Tailscale. Most control, most setup.

Step 6: DNS / Static File Configuration

Create the well-known file on your domain. For alice@example.com, the file goes at:

https://example.com/.well-known/lnurlp/alice

Contents:

{
  "callback": "https://your-public-endpoint/lnurlp/alice/callback",
  "metadata": "[[\"text/identifier\",\"alice@example.com\"],[\"text/plain\",\"Pay alice\"]]",
  "tag": "payRequest",
  "minSendable": 1000,
  "maxSendable": 100000000
}

GitHub Pages: Commit the file to your repo at .well-known/lnurlp/alice (no extension). Add an empty .nojekyll file so GitHub serves dotfiles. Ensure the response Content-Type is application/json.

Verify:

curl -s https://example.com/.well-known/lnurlp/alice | python3 -m json.tool

Step 7: End-to-End Testing

# 1. Verify static file resolves
curl -s https://yourdomain.com/.well-known/lnurlp/alice

# 2. Test callback directly (1000 msats = 1 sat)
curl -s "https://your-public-endpoint/lnurlp/alice/callback?amount=1000"
# Should return: {"pr": "lnbc...", "routes": []}

# 3. Test with a real wallet
# Open Phoenix, Muun, Breez, or any LNURL-compatible wallet
# Enter: alice@yourdomain.com
# Send a small amount (100 sats)

# 4. Verify receipt
curl -s http://localhost:9740/payments/incoming -u :$PASSWORD | python3 -m json.tool

Deep Dive: All Wallet Options Compared

We evaluated every option in real-world testing. Here's what we found from primary source docs and hands-on experience:

Phoenixd (ACINQ) — Recommended for Agents

Alby Hub (LDK) — Best for Humans, Decent for Agents

Alby Account (Custodial) — Cheapest Quick-Start

Zeus Wallet (Olympus LSP) — Mobile-First

LND (Lightning Labs) — Full Sovereignty

Our Decision Path (Real-World)

  1. Started with phoenixd (simplest self-custodial) → hit ACINQ server bug
  2. Explored Alby Hub as alternative → minimum on-chain deposits too expensive for testing ($106+)
  3. Set up Alby Account (custodial) as cheap stopgap → worked instantly, free
  4. Phoenixd resolved after ~12 hours → now primary wallet
  5. Alby Account remains available as custodial fallback

Lesson: Have a backup plan. Self-custodial Lightning depends on your LSP being operational. If your sole channel partner goes down, you need either a second node with a different LSP, or a custodial fallback for continuity.

Security Model

Component Access Level Purpose
Full API password Can send payments Agent uses for purchases
Limited API password Read + create invoices only Dashboard, LNURL proxy
Seed phrase Full fund recovery Offline backup only

Spending limits: Keep the wallet balance small ($20-100). This is a spending wallet, not savings. Fund it periodically in small amounts.

Trust tradeoffs:

Persistent Services (Survive Reboots)

Both phoenixd and the LNURL proxy must run as system services. Without this, the Lightning Address breaks on reboot.

macOS (launchd)

Create ~/Library/LaunchAgents/ai.openclaw.lnurl-proxy.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>ai.openclaw.lnurl-proxy</string>
    <key>ProgramArguments</key>
    <array>
        <string>/usr/bin/python3</string>
        <string>/path/to/lnurl-proxy.py</string>
    </array>
    <key>EnvironmentVariables</key>
    <dict>
        <key>PHOENIXD_PASS</key>
        <string>your-limited-password</string>
        <key>LN_ADDRESS</key>
        <string>alice@yourdomain.com</string>
    </dict>
    <key>RunAtLoad</key><true/>
    <key>KeepAlive</key><true/>
    <key>ThrottleInterval</key><integer>10</integer>
</dict>
</plist>
launchctl load ~/Library/LaunchAgents/ai.openclaw.lnurl-proxy.plist

Linux (systemd)

Create /etc/systemd/system/lnurl-proxy.service:

[Unit]
Description=LNURL-pay proxy for phoenixd
After=network.target

[Service]
ExecStart=/usr/bin/python3 /path/to/lnurl-proxy.py
Environment=PHOENIXD_PASS=your-limited-password
Environment=LN_ADDRESS=alice@yourdomain.com
Restart=always
RestartSec=10
User=your-user

[Install]
WantedBy=multi-user.target
sudo systemctl enable --now lnurl-proxy

Key settings: KeepAlive / Restart=always ensures the proxy restarts on crash. ThrottleInterval / RestartSec prevents crash loops. Without persistence, the Lightning Address returns empty responses, causing lnurlError: EOF while parsing in sender wallets.

Agent Spending Workflows

Once your agent has a funded Lightning wallet, here's what it can do:

Approval gates: For agent spending, always implement a human approval step before paying any invoice. Screenshot the order summary, show it to the human, wait for explicit approval. Never let an agent pay invoices autonomously without a review gate — one wrong API call can send real money.

What's in the Skill Package

lightning-address/
├── SKILL.md                           # Core setup instructions
├── scripts/
│   └── lnurl-proxy.py                 # Parameterized LNURL proxy server
└── references/
    ├── wallet-options.md              # Full wallet comparison
    └── exposure-methods.md            # Tailscale, Cloudflare, VPS options

Requirements

Freedom Tech Perspective

Lightning Addresses are the bridge between human-readable identity and self-custodial payments. The key insight: the address format looks like email, but the payment flow is peer-to-peer.

Every Lightning Address that runs on someone's own infrastructure is one fewer person relying on custodial services. Scale that and you have a payment network that governments can't easily shut down — not because it's hidden, but because it's everywhere.