Skip to content

Latest commit

 

History

History
357 lines (263 loc) · 10.1 KB

File metadata and controls

357 lines (263 loc) · 10.1 KB

Agent Commerce

Setting up buyer and seller agents for autonomous Lightning payments.

Lightning Agent Tools enables a payment pattern where agents buy and sell resources from each other over the Lightning Network without any pre-arranged billing relationship. A buyer agent uses lnget to fetch paid content. A seller agent uses aperture to gate access behind Lightning invoices. Both run their own lnd nodes for payment infrastructure. The three components compose into a self-contained commerce loop.

This document walks through setting up both sides and running them together.

The Commerce Loop

When a buyer agent fetches a resource from a seller, the following exchange happens automatically:

sequenceDiagram
    participant BA as Buyer Agent
    participant BL as Buyer lnd
    participant LN as Lightning Network
    participant SL as Seller lnd
    participant AP as Aperture
    participant BE as Backend Service

    BA->>AP: GET /api/data (via lnget)
    AP->>SL: Generate invoice (100 sats)
    SL-->>AP: BOLT11 invoice
    AP-->>BA: 402 + L402 challenge

    BA->>BL: Pay invoice (100 sats)
    BL->>LN: Route payment
    LN->>SL: Deliver payment
    SL-->>LN: Preimage
    LN-->>BL: Preimage
    BL-->>BA: Payment settled

    BA->>AP: GET /api/data + L402 token
    AP->>AP: Validate token
    AP->>BE: Proxy request
    BE-->>AP: Response data
    AP-->>BA: 200 OK + data
Loading

From the buyer agent's perspective, this is a single command:

lnget --max-cost 100 -q https://seller-host:8081/api/data | jq .

From the seller's perspective, aperture handles everything: invoice generation, challenge issuance, token validation, and request proxying. The backend service just serves HTTP requests.

Buyer Agent Setup

A buyer agent needs two components: an lnd node for payments and lnget for HTTP requests with automatic L402 handling.

Install

# Pulls Docker images by default; add --source to build from source.
skills/lnd/scripts/install.sh
skills/lnget/scripts/install.sh

Create and Start the Node

All commands below auto-detect Docker and launch containers. Pass --native to any script to use a locally built binary instead.

# Create a wallet (standalone mode for testing, watch-only for production)
skills/lnd/scripts/create-wallet.sh --mode standalone

# Start lnd (launches a litd Docker container)
skills/lnd/scripts/start-lnd.sh

# Verify it's running
skills/lnd/scripts/lncli.sh getinfo

For production deployments, use watch-only mode with a remote signer. See Security.

Fund the Wallet

The node needs on-chain bitcoin to open payment channels.

# Generate a funding address
skills/lnd/scripts/lncli.sh newaddress p2tr

# Send BTC to this address from an exchange or another wallet

# Check balance (wait for confirmations)
skills/lnd/scripts/lncli.sh walletbalance

Open a Channel

Lightning payments travel through channels. The buyer needs at least one channel with outbound capacity to reach the seller (or a path to the seller through the network).

# Connect to a well-connected node
skills/lnd/scripts/lncli.sh connect <pubkey>@<host>:9735

# Open a channel (amount in satoshis)
skills/lnd/scripts/lncli.sh openchannel --node_key=<pubkey> --local_amt=1000000

# Wait for the funding transaction to confirm (typically 3-6 blocks)
skills/lnd/scripts/lncli.sh pendingchannels
skills/lnd/scripts/lncli.sh listchannels

Configure lnget

# Initialize config (auto-detects local lnd)
lnget config init

# Verify backend connection
lnget ln status

Fetch Paid Resources

# Fetch with a spending cap
lnget --max-cost 500 https://api.example.com/paid-data.json

# Preview cost without paying
lnget --no-pay --json https://api.example.com/paid-data.json | jq '.invoice_amount_sat'

# Pipe to other tools
lnget -q https://api.example.com/market-data.json | jq '.price'

Seller Agent Setup

A seller agent needs three components: an lnd node for invoice generation, aperture as the L402 reverse proxy, and a backend HTTP service to serve the actual content.

Install

skills/lnd/scripts/install.sh
skills/aperture/scripts/install.sh

Create and Start the Node

skills/lnd/scripts/create-wallet.sh --mode standalone
skills/lnd/scripts/start-lnd.sh                            # Docker container by default
skills/lnd/scripts/lncli.sh getinfo

The seller's lnd node generates invoices for aperture. It needs inbound channel capacity to receive payments. Other nodes must have channels open to the seller, not just from the seller.

Start a Backend Service

Aperture proxies authenticated requests to a backend HTTP service. For testing, a simple file server works:

mkdir -p /tmp/api-data
echo '{"market_data": {"btc_usd": 104250, "timestamp": "2025-02-09T12:00:00Z"}}' \
    > /tmp/api-data/data.json
cd /tmp/api-data && python3 -m http.server 8080 &

In production, the backend is whatever service you want to monetize: a REST API, a data feed, an inference endpoint.

Configure and Start Aperture

# Generate aperture config connected to local lnd
skills/aperture/scripts/setup.sh --insecure --port 8081

# Start the L402 proxy
skills/aperture/scripts/start.sh

The --insecure flag disables TLS (suitable for development). For production, configure TLS with Let's Encrypt (--autocert) or your own certificates.

For regtest testing, a Docker Compose file at skills/aperture/templates/docker-compose-aperture.yml runs aperture alongside a busybox backend on the same Docker network as the litd regtest stack.

Aperture's config (~/.aperture/aperture.yaml) defines which paths are protected and how much they cost:

services:
  - name: "market-data"
    hostregexp: ".*"
    pathregexp: "^/api/.*$"
    address: "127.0.0.1:8080"
    protocol: http
    price: 100                    # satoshis per request
    authwhitelistpaths:
      - "^/health$"              # free health check endpoint

Each request to a path matching ^/api/.*$ costs 100 satoshis. Requests to /health pass through without payment. The address field points to the backend service.

Verify

# Should return 402 with L402 challenge
lnget -k --no-pay https://localhost:8081/api/data.json

Running Both Sides

With both agents running, the buyer fetches data from the seller:

# Buyer fetches paid data
lnget --max-cost 100 -q https://seller-host:8081/api/data.json | jq .

Verify the payment went through:

# On the buyer
lnget tokens list                                    # shows cached token
skills/lnd/scripts/lncli.sh channelbalance           # outbound decreased

# On the seller
skills/lnd/scripts/lncli.sh listinvoices             # shows settled invoice
skills/lnd/scripts/lncli.sh channelbalance           # inbound converted to local

Subsequent requests from the buyer to the same domain reuse the cached token without additional payment (until the token expires).

Deployment Diagram

A production deployment separates the signing infrastructure from the agent-facing components:

graph TB
    subgraph Buyer["Buyer Agent"]
        B_Agent["Agent Process"]
        B_lnget["lnget"]
        B_lnd["lnd (watch-only)"]
        B_Signer["lnd signer<br/>(separate machine)"]

        B_Agent --> B_lnget
        B_lnget --> B_lnd
        B_lnd <-->|"gRPC :10012"| B_Signer
    end

    subgraph Seller["Seller Agent"]
        S_Agent["Agent Process"]
        S_Aperture["aperture :8081"]
        S_Backend["Backend :8080"]
        S_lnd["lnd (watch-only)"]
        S_Signer["lnd signer<br/>(separate machine)"]

        S_Agent --> S_Aperture
        S_Aperture --> S_Backend
        S_Aperture --> S_lnd
        S_lnd <-->|"gRPC :10012"| S_Signer
    end

    LN["Lightning Network"]
    B_lnd <-->|":9735"| LN
    S_lnd <-->|":9735"| LN
Loading

Cost Management

Agents operating autonomously need spending guardrails at multiple levels:

Per-request limits. Set --max-cost on every lnget call. If the seller's price exceeds the limit, lnget refuses to pay and exits with code 2:

lnget --max-cost 500 https://api.example.com/data

Cost preview. Before committing to a purchase, preview what the server is charging:

lnget --no-pay --json https://api.example.com/data | jq '.invoice_amount_sat'

Spending tracking. Monitor cumulative spending through the token list:

lnget tokens list --json | jq '[.[] | .amount_paid_sat] | add'

Wallet balance monitoring. Check channel balance to ensure the agent has sufficient capacity:

skills/lnd/scripts/lncli.sh channelbalance
skills/lnd/scripts/lncli.sh walletbalance

Node-level controls. Use a pay-only macaroon (via macaroon-bakery) for the buyer agent. This restricts lnget to payment and invoice-decoding operations only. The agent cannot open channels, change configuration, or access any other node functionality:

skills/macaroon-bakery/scripts/bake.sh --role pay-only

For the seller agent, use an invoice-only macaroon for aperture. It only needs to create and look up invoices.

Production Considerations

Remote signer. Both buyer and seller nodes should run in watch-only mode with a remote signer in production. See Security.

Inbound liquidity. The seller's lnd node needs inbound channel capacity to receive payments. This means other nodes must open channels to the seller. Options include requesting inbound channels from LSPs (Lightning Service Providers), or using circular rebalancing to shift local capacity to the remote side.

Channel sizing. Size channels based on expected payment volume. A 1,000,000 sat channel can handle many 100-sat L402 payments before rebalancing is needed. Monitor channel balances and open new channels proactively.

TLS for aperture. Use --autocert with a domain name for automatic Let's Encrypt TLS, or provide your own certificate. Never run --insecure on public-facing endpoints.

Stopping everything.

skills/aperture/scripts/stop.sh
skills/lnd/scripts/stop-lnd.sh
skills/lnd/scripts/stop-lnd.sh --clean                     # also remove Docker volumes