Two-Agent Secure Setup
February 10, 2026 ยท View on GitHub
Using one agent to set up the remote signer and another to run the watch-only node.
The recommended production deployment splits key management across two machines: a signer that holds private keys and a node that handles payments and channels. Each machine can be operated by its own agent. The signer agent runs on the secure machine and exports a credentials bundle. The node agent runs on the agent-facing machine, imports that bundle, and starts a watch-only lnd instance that delegates all signing back to the signer.
This walkthrough covers the full flow from both sides.
Overview
sequenceDiagram
participant SA as Signer Agent<br/>(secure machine)
participant Transfer as Credentials Transfer
participant NA as Node Agent<br/>(agent machine)
SA->>SA: Install lnd
SA->>SA: Create signer wallet
SA->>SA: Start signer lnd
SA->>SA: Export credentials bundle
SA->>Transfer: credentials-bundle.tar.gz.b64
Transfer->>NA: Copy bundle to agent machine
NA->>NA: Install lnd
NA->>NA: Import credentials bundle
NA->>NA: Create watch-only wallet
NA->>NA: Start watch-only lnd
NA->>NA: Verify connection to signer
The credentials bundle contains three files:
| File | What it is | What it's for |
|---|---|---|
accounts.json | Account xpubs (public keys only) | Creating the watch-only wallet |
tls.cert | Signer's TLS certificate | Authenticating the gRPC connection |
admin.macaroon | Signer's RPC token | Authorizing signing requests |
No private keys cross the wire. The bundle contains only public keys and authentication material.
Container Mode (Recommended)
Docker is the default deployment method. All scripts auto-detect containers
and route commands through docker exec.
Signer Machine
# Pull the lnd signer image.
skills/lightning-security-module/scripts/install.sh
# Create the signer wallet and export the credentials bundle.
# This launches the litd-signer container, creates the wallet via REST API,
# and exports the bundle to ~/.lnget/signer/credentials-bundle/.
skills/lightning-security-module/scripts/setup-signer.sh
# Start the signer container (if not already running from setup).
skills/lightning-security-module/scripts/start-signer.sh
Transfer the credentials bundle to the agent machine:
scp ~/.lnget/signer/credentials-bundle/credentials-bundle.tar.gz.b64 \
agent-machine:~/credentials-bundle.tar.gz.b64
Agent Machine
# Pull the litd image.
skills/lnd/scripts/install.sh
# Import the signer's credentials bundle.
skills/lnd/scripts/import-credentials.sh --bundle ~/credentials-bundle.tar.gz.b64
# Create the watch-only wallet (auto-detects the litd container).
skills/lnd/scripts/create-wallet.sh
# Start in watch-only mode (launches litd + signer containers via Docker Compose).
skills/lnd/scripts/start-lnd.sh --watchonly
# Verify.
skills/lnd/scripts/lncli.sh getinfo
skills/lnd/scripts/lncli.sh newaddress p2tr
The --watchonly flag uses docker-compose-watchonly.yml, which starts both a
litd container (watch-only) and connects it to the signer via the Docker
network. The signer's gRPC address is pre-configured in the watch-only template
(lnd.remotesigner.rpchost=signer:10012).
Container Lifecycle
# Stop containers.
skills/lnd/scripts/stop-lnd.sh
# Stop and remove volumes (destructive โ removes chain data).
skills/lnd/scripts/stop-lnd.sh --clean
# On signer machine.
skills/lightning-security-module/scripts/stop-signer.sh
skills/lightning-security-module/scripts/stop-signer.sh --clean
Scoping the Signer Macaroon (Container)
# On the signer machine, bake a scoped macaroon inside the container.
skills/macaroon-bakery/scripts/bake.sh --role signer-only --container litd-signer
# Re-export the credentials bundle with the scoped macaroon.
skills/lightning-security-module/scripts/export-credentials.sh --container litd-signer
Native Mode
For environments without Docker, all scripts accept a --native flag to use
locally installed binaries.
Part 1: Signer Agent (Secure Machine)
The signer agent runs on a machine with restricted access. This machine will hold all private keys and never connect to the Lightning Network directly.
Install lnd
skills/lightning-security-module/scripts/install.sh --source
This builds lnd from source with the signing-related build tags. The binary is the same as a regular lnd install, but the signer's configuration restricts it to signing operations only.
Create the signer wallet and export credentials
skills/lightning-security-module/scripts/setup-signer.sh --native
This does three things:
- Generates a new wallet with a random passphrase and 24-word seed mnemonic.
Both are saved to
~/.lnget/signer/with mode 0600. - Starts the signer lnd temporarily to extract account xpubs.
- Exports the credentials bundle to
~/.lnget/signer/credentials-bundle/.
The bundle directory contains the three files listed above plus a
base64-encoded tarball (credentials-bundle.tar.gz.b64) for easy transfer.
Start the signer
skills/lightning-security-module/scripts/start-signer.sh --native
The signer listens on port 10012 (gRPC) for signing requests from the
watch-only node. In native mode, REST binds to localhost:10013 only. It does
not connect to any peers and does not participate in the Lightning Network.
Transfer the bundle
The base64-encoded bundle needs to reach the agent machine. How you transfer it depends on your environment:
# Option 1: scp
scp ~/.lnget/signer/credentials-bundle/credentials-bundle.tar.gz.b64 \
agent-machine:~/credentials-bundle.tar.gz.b64
# Option 2: Print to terminal and copy-paste
cat ~/.lnget/signer/credentials-bundle/credentials-bundle.tar.gz.b64
The bundle contains a macaroon (bearer token) and a TLS certificate. Treat it like a password during transfer.
Optional: scope the signer macaroon
The exported admin.macaroon grants full RPC access to the signer. For
production, replace it with a signer-only macaroon that restricts the
watch-only node to signing and key derivation:
skills/macaroon-bakery/scripts/bake.sh --role signer-only \
--rpc-port 10012 --lnddir ~/.lnd-signer
Then re-export the credentials bundle with the scoped macaroon:
skills/lightning-security-module/scripts/export-credentials.sh
Part 2: Node Agent (Agent Machine)
The node agent runs on the machine where agents will operate. This machine will have no private keys.
Install lnd
skills/lnd/scripts/install.sh --source
Import the credentials bundle
skills/lnd/scripts/import-credentials.sh \
--bundle ~/credentials-bundle.tar.gz.b64
This unpacks the bundle into ~/.lnget/lnd/signer-credentials/, placing
accounts.json, tls.cert, and admin.macaroon where the watch-only node
expects them.
Create the watch-only wallet
skills/lnd/scripts/create-wallet.sh --native \
--signer-host <signer-ip>:10012
Replace <signer-ip> with the signer machine's IP address or hostname. This
creates a wallet that imports the account xpubs from the credentials bundle.
The wallet has no seed and no private keys. It generates a random passphrase
stored at ~/.lnget/lnd/wallet-password.txt (mode 0600).
The --signer-host flag tells the wallet creation process where to find the
signer for initial key verification.
Start the watch-only node
skills/lnd/scripts/start-lnd.sh --native \
--signer-host <signer-ip>:10012
The node starts with remotesigner.enable=true in its config, pointing at
the signer's gRPC address. It connects to the Bitcoin network via Neutrino,
syncs headers, and begins normal operation. Any transaction that requires a
signature (channel opens, closes, on-chain sends) is forwarded to the signer
over the authenticated gRPC connection.
Verify
skills/lnd/scripts/lncli.sh getinfo
The node should report synced status. To confirm the signer connection is working, try generating a new address (which requires key derivation from the signer):
skills/lnd/scripts/lncli.sh newaddress p2tr
If this returns an address, the watch-only node and signer are communicating correctly.
What Each Agent Can See
After setup, the two agents have different views of the system:
graph LR
subgraph Signer["Signer Agent"]
S_seed["Wallet seed (24 words)"]
S_keys["All private keys"]
S_pass["Wallet passphrase"]
S_mac["Signer macaroons"]
end
subgraph Node["Node Agent"]
N_xpubs["Account xpubs (public)"]
N_cert["Signer TLS cert"]
N_smac["Signer macaroon (scoped)"]
N_pass["Node wallet passphrase"]
N_mac["Node macaroons"]
N_state["Channel state, balances, peers"]
end
subgraph Neither["Neither Agent Has"]
X1["Cross-machine shell access"]
X2["Each other's wallet passphrase"]
end
The node agent can see balances, channel state, and payment history. It can initiate payments and open channels (the signing is handled transparently by the signer). It cannot extract private keys because they are not on its machine.
The signer agent can see its own wallet state but has no visibility into the watch-only node's channels or payment activity. It responds to signing requests but does not initiate any network activity.
Network Requirements
The watch-only node needs to reach the signer on port 10012 (gRPC over TLS). This is the only network path between the two machines.
graph LR
WO["Watch-Only Node"] -->|"TCP :10012<br/>gRPC/TLS"| S["Signer"]
WO -->|"TCP :9735<br/>Lightning P2P"| LN["Lightning Network"]
WO -->|"TCP :18333<br/>Neutrino"| BTC["Bitcoin Peers"]
S -.-x|"No outbound<br/>connections"| LN
S -.-x|"No outbound<br/>connections"| BTC
The signer should be firewalled to accept connections only from the watch-only node's IP on port 10012. It makes no outbound connections.
Ongoing Operations
Once both sides are running, day-to-day operations happen on the node agent's machine:
# Check node status
skills/lnd/scripts/lncli.sh getinfo
# Fund the wallet
skills/lnd/scripts/lncli.sh newaddress p2tr
skills/lnd/scripts/lncli.sh walletbalance
# Open channels
skills/lnd/scripts/lncli.sh connect <pubkey>@<host>:9735
skills/lnd/scripts/lncli.sh openchannel --node_key=<pubkey> --local_amt=1000000
# Send payments
skills/lnd/scripts/lncli.sh sendpayment --pay_req=<bolt11>
# Use lnget for L402
lnget --max-cost 500 https://api.example.com/data
The signer agent's role is maintenance: keeping the signer process running, rotating macaroons periodically, and backing up the seed.
# On the signer machine (container mode)
skills/lightning-security-module/scripts/start-signer.sh # delegates to docker-start.sh
skills/lightning-security-module/scripts/stop-signer.sh # delegates to docker-stop.sh
# Rotate the signer macaroon (container)
skills/macaroon-bakery/scripts/bake.sh --role signer-only --container litd-signer
skills/lightning-security-module/scripts/export-credentials.sh --container litd-signer
# Rotate the signer macaroon (native)
skills/macaroon-bakery/scripts/bake.sh --role signer-only \
--rpc-port 10012 --lnddir ~/.lnd-signer
skills/lightning-security-module/scripts/export-credentials.sh --native
# Then transfer the new bundle to the node agent and re-import
Credential Scoping for the Node Agent
After the watch-only node is running, bake scoped macaroons for the node
agent's specific tasks. Do not leave the node agent using admin.macaroon:
# On the node agent's machine
# For a buyer agent (pays for L402 resources)
skills/macaroon-bakery/scripts/bake.sh --role pay-only
# For a seller agent (generates invoices via aperture)
skills/macaroon-bakery/scripts/bake.sh --role invoice-only
# For a monitoring agent
skills/macaroon-bakery/scripts/bake.sh --role read-only
This gives you two layers of credential scoping: the signer macaroon limits what the watch-only node can ask the signer to do, and the node macaroon limits what the agent can ask the watch-only node to do.