@lightninglabs/lightning-mcp-server 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/.claude-plugin/marketplace.json +36 -0
  2. package/.claude-plugin/plugin.json +12 -0
  3. package/README.md +307 -0
  4. package/bin/lightning-mcp-server +15 -0
  5. package/docs/architecture.md +455 -0
  6. package/docs/commerce.md +357 -0
  7. package/docs/l402-and-lnget.md +267 -0
  8. package/docs/mcp-server.md +285 -0
  9. package/docs/quickref.md +263 -0
  10. package/docs/security.md +298 -0
  11. package/docs/two-agent-setup.md +394 -0
  12. package/package.json +52 -0
  13. package/postinstall.js +160 -0
  14. package/skills/aperture/SKILL.md +330 -0
  15. package/skills/aperture/scripts/install.sh +68 -0
  16. package/skills/aperture/scripts/setup.sh +155 -0
  17. package/skills/aperture/scripts/start.sh +81 -0
  18. package/skills/aperture/scripts/stop.sh +57 -0
  19. package/skills/aperture/templates/aperture-regtest.yaml +36 -0
  20. package/skills/aperture/templates/aperture.yaml.template +64 -0
  21. package/skills/aperture/templates/docker-compose-aperture.yml +59 -0
  22. package/skills/commerce/SKILL.md +211 -0
  23. package/skills/lib/config-gen.sh +127 -0
  24. package/skills/lib/rest.sh +69 -0
  25. package/skills/lightning-security-module/SKILL.md +253 -0
  26. package/skills/lightning-security-module/references/architecture.md +133 -0
  27. package/skills/lightning-security-module/scripts/docker-start.sh +117 -0
  28. package/skills/lightning-security-module/scripts/docker-stop.sh +53 -0
  29. package/skills/lightning-security-module/scripts/export-credentials.sh +268 -0
  30. package/skills/lightning-security-module/scripts/install.sh +178 -0
  31. package/skills/lightning-security-module/scripts/setup-signer.sh +307 -0
  32. package/skills/lightning-security-module/scripts/start-signer.sh +152 -0
  33. package/skills/lightning-security-module/scripts/stop-signer.sh +240 -0
  34. package/skills/lightning-security-module/templates/docker-compose-signer.yml +35 -0
  35. package/skills/lightning-security-module/templates/signer-lnd.conf.template +69 -0
  36. package/skills/lnd/SKILL.md +441 -0
  37. package/skills/lnd/profiles/debug.env +4 -0
  38. package/skills/lnd/profiles/default.env +3 -0
  39. package/skills/lnd/profiles/regtest.env +4 -0
  40. package/skills/lnd/profiles/taproot.env +3 -0
  41. package/skills/lnd/profiles/wumbo.env +3 -0
  42. package/skills/lnd/references/security.md +156 -0
  43. package/skills/lnd/scripts/create-wallet.sh +464 -0
  44. package/skills/lnd/scripts/docker-start.sh +256 -0
  45. package/skills/lnd/scripts/docker-stop.sh +109 -0
  46. package/skills/lnd/scripts/import-credentials.sh +145 -0
  47. package/skills/lnd/scripts/install.sh +195 -0
  48. package/skills/lnd/scripts/lncli.sh +150 -0
  49. package/skills/lnd/scripts/start-lnd.sh +241 -0
  50. package/skills/lnd/scripts/stop-lnd.sh +218 -0
  51. package/skills/lnd/scripts/unlock-wallet.sh +134 -0
  52. package/skills/lnd/templates/docker-compose-regtest.yml +122 -0
  53. package/skills/lnd/templates/docker-compose-watchonly.yml +71 -0
  54. package/skills/lnd/templates/docker-compose.yml +49 -0
  55. package/skills/lnd/templates/litd-regtest.conf.template +61 -0
  56. package/skills/lnd/templates/litd-watchonly.conf.template +57 -0
  57. package/skills/lnd/templates/litd.conf.template +88 -0
  58. package/skills/lnd/templates/lnd.conf.template +91 -0
  59. package/skills/lnget/SKILL.md +288 -0
  60. package/skills/lnget/scripts/install.sh +69 -0
  61. package/skills/macaroon-bakery/SKILL.md +179 -0
  62. package/skills/macaroon-bakery/scripts/bake.sh +337 -0
  63. package/skills/mcp-lnc/SKILL.md +280 -0
  64. package/skills/mcp-lnc/scripts/configure.sh +130 -0
  65. package/skills/mcp-lnc/scripts/install.sh +103 -0
  66. package/skills/mcp-lnc/scripts/setup-claude-config.sh +162 -0
  67. package/skills/mcp-lnc/templates/env.template +16 -0
  68. package/versions.env +23 -0
@@ -0,0 +1,156 @@
1
+ # LND Wallet Security Guide
2
+
3
+ ## Default Model: Watch-Only with Remote Signer
4
+
5
+ The default setup uses lnd's remote signer architecture. The agent machine runs
6
+ a **watch-only** lnd node that delegates all signing to a separate **signer**
7
+ node on a secured machine. No private keys exist on the agent machine.
8
+
9
+ ```
10
+ Agent Machine (watch-only) <--gRPC--> Signer Machine (keys)
11
+ - Runs neutrino - Holds seed
12
+ - Manages channels - Signs commitments
13
+ - Routes payments - Signs on-chain txs
14
+ - No key material - Minimal attack surface
15
+ ```
16
+
17
+ Set up the signer with the `lightning-security-module` skill, then import
18
+ credentials on the agent machine with `import-credentials.sh`.
19
+
20
+ **What stays on the signer:**
21
+ - 24-word seed mnemonic
22
+ - All private keys (funding, revocation, HTLC)
23
+ - Wallet database with key material
24
+
25
+ **What gets imported to the agent:**
26
+ - Account xpubs (public keys only — cannot spend)
27
+ - Signer TLS certificate (for authenticated gRPC)
28
+ - Signer macaroon (for RPC auth — scope down for production)
29
+
30
+ **Threat model:**
31
+ - Compromised agent machine cannot sign transactions or extract keys
32
+ - Attacker with agent access can see balances and channel state but not spend
33
+ - Signer compromise is full compromise — secure that machine accordingly
34
+
35
+ ## Standalone Model: Passphrase on Disk (Testing Only)
36
+
37
+ When running in standalone mode (`--mode standalone`), wallet credentials are
38
+ stored as files on disk for agent automation convenience:
39
+
40
+ | File | Contents | Permissions |
41
+ |------|----------|-------------|
42
+ | `~/.lnget/lnd/wallet-password.txt` | Wallet unlock passphrase | 0600 |
43
+ | `~/.lnget/lnd/seed.txt` | 24-word BIP39 mnemonic | 0600 |
44
+
45
+ **This is suitable for:**
46
+ - Testnet and signet development
47
+ - Small mainnet amounts for micropayments
48
+ - Quick agent testing where security is not a concern
49
+
50
+ **Risks:**
51
+ - Any process running as the same user can read the files
52
+ - Disk compromise exposes both passphrase and seed
53
+ - No protection against malicious software on the same machine
54
+
55
+ ### Wallet Passphrase
56
+
57
+ The wallet passphrase encrypts the wallet database on disk. Without it, the
58
+ wallet cannot be unlocked and funds cannot be spent.
59
+
60
+ **Auto-unlock:** The default lnd config includes `wallet-unlock-password-file`
61
+ which reads the passphrase on startup. This means the node is operational
62
+ immediately after a restart without manual intervention.
63
+
64
+ **Manual unlock:** Remove `wallet-unlock-password-file` from `lnd.conf` to
65
+ require manual unlock via `lncli unlock` or the REST API after each restart.
66
+
67
+ ### Seed Mnemonic
68
+
69
+ The 24-word seed is the master secret from which all keys are derived. With the
70
+ seed, the entire wallet can be reconstructed on any lnd instance.
71
+
72
+ **Current storage:** Plain text file at `~/.lnget/lnd/seed.txt` with mode 0600.
73
+
74
+ **To improve standalone security:**
75
+
76
+ 1. **Encrypted file:** Encrypt the seed file with a separate passphrase using
77
+ GPG or age. The agent would need the encryption passphrase only during
78
+ wallet recovery.
79
+
80
+ 2. **OS keychain:** Store the seed in the operating system's keychain (macOS
81
+ Keychain, Linux Secret Service). Requires keychain unlock but survives
82
+ disk inspection.
83
+
84
+ 3. **Migrate to remote signer:** The recommended path. Use the
85
+ `lightning-security-module` skill to move keys to a separate machine.
86
+
87
+ ## Macaroon Security
88
+
89
+ lnd uses macaroons for API authentication. Macaroons are bearer tokens with
90
+ cryptographic capabilities — whoever holds a macaroon can exercise its
91
+ permissions.
92
+
93
+ ### Built-in Macaroons
94
+
95
+ | Macaroon | Capabilities |
96
+ |----------|-------------|
97
+ | `admin.macaroon` | Full access (read, write, generate invoices, send payments) |
98
+ | `readonly.macaroon` | Read-only access (getinfo, balances, list operations) |
99
+ | `invoice.macaroon` | Create and manage invoices only |
100
+
101
+ ### Macaroon Bakery: Least-Privilege Access
102
+
103
+ **Never give agents the admin macaroon in production.** Use the
104
+ `macaroon-bakery` skill to bake custom macaroons with only the permissions each
105
+ agent needs. It supports preset roles (`pay-only`, `invoice-only`, `read-only`,
106
+ `channel-admin`, `signer-only`) and custom permission sets.
107
+
108
+ ```bash
109
+ # Bake a pay-only macaroon
110
+ skills/macaroon-bakery/scripts/bake.sh --role pay-only
111
+
112
+ # Inspect a macaroon's permissions
113
+ skills/macaroon-bakery/scripts/bake.sh --inspect <path-to-macaroon>
114
+ ```
115
+
116
+ See the `macaroon-bakery` skill for full usage, rotation, and best practices.
117
+
118
+ ### Macaroon Scoping for Remote Signer
119
+
120
+ When using the remote signer architecture, the macaroon included in the
121
+ credentials bundle grants the watch-only node access to the signer's RPC. For
122
+ production, replace the admin macaroon with a signing-only macaroon:
123
+
124
+ ```bash
125
+ # On the signer machine
126
+ skills/macaroon-bakery/scripts/bake.sh --role signer-only \
127
+ --rpc-port 10012 --lnddir ~/.lnd-signer
128
+ ```
129
+
130
+ Then re-export the credentials bundle with this macaroon instead of
131
+ `admin.macaroon`.
132
+
133
+ ## Production Checklist
134
+
135
+ - [x] Use remote signer (default mode) — keys never on agent machine
136
+ - [ ] Bake least-privilege macaroons for each agent role
137
+ - [ ] Replace admin macaroon in signer credentials with signing-only macaroon
138
+ - [ ] Set file permissions: `chmod 600` on all credential files
139
+ - [ ] Restrict signer RPC to specific IP addresses via firewall
140
+ - [ ] Use testnet/signet for development, mainnet only for production
141
+ - [ ] Monitor wallet balance for unexpected changes
142
+ - [ ] Keep lnd updated to latest stable release
143
+ - [ ] Backup seed on signer in a separate, secure location
144
+ - [ ] Set up macaroon rotation schedule
145
+ - [ ] Run signer on dedicated hardware or hardened VM
146
+
147
+ ## Future Enhancements
148
+
149
+ - **litd accounts:** Lightning Terminal's account system for spending limits and
150
+ fine-grained access control
151
+ - **Hardware signing devices:** Replace signer lnd with an HSM or hardware
152
+ wallet for tamper-resistant key storage
153
+ - **Multi-party signing:** Require multiple signers for high-value transactions
154
+ (threshold signatures)
155
+ - **Encrypted credential storage:** OS keychain integration for macaroons and
156
+ TLS credentials
@@ -0,0 +1,464 @@
1
+ #!/usr/bin/env bash
2
+ # Create an encrypted lnd / litd wallet with secure credential storage.
3
+ #
4
+ # Container mode (default — auto-detects litd container):
5
+ # create-wallet.sh # Auto-detect litd
6
+ # create-wallet.sh --container litd # Explicit container
7
+ # create-wallet.sh --container litd --mode standalone # Standalone in container
8
+ # create-wallet.sh --container litd-signer # Signer container
9
+ #
10
+ # Native mode:
11
+ # create-wallet.sh --native # Local lnd
12
+ # create-wallet.sh --native --signer-host 10.0.0.5:10012 # Watch-only native
13
+ # create-wallet.sh --native --mode standalone # Standalone native
14
+ # create-wallet.sh --native --recover --seed-file ~/seed.txt
15
+ #
16
+ # Remote mode:
17
+ # create-wallet.sh --rest-host remote.host --rest-port 8080
18
+ #
19
+ # Modes:
20
+ # watchonly (default) — imports accounts from signer, no seed on this machine.
21
+ # Requires signer credentials imported via import-credentials.sh.
22
+ # standalone — generates seed locally (keys on disk, less secure)
23
+ #
24
+ # Stores credentials at:
25
+ # ~/.lnget/lnd/wallet-password.txt (mode 0600)
26
+ # ~/.lnget/lnd/seed.txt (mode 0600, standalone mode only)
27
+
28
+ set -e
29
+
30
+ LNGET_LND_DIR="${LNGET_LND_DIR:-$HOME/.lnget/lnd}"
31
+ LND_DIR="${LND_DIR:-$HOME/.lnd}"
32
+ NETWORK="testnet"
33
+ PASSWORD=""
34
+ RECOVER=false
35
+ SEED_FILE=""
36
+ REST_PORT=8080
37
+ REST_HOST="localhost"
38
+ MODE="watchonly"
39
+ SIGNER_HOST="${LND_SIGNER_HOST:-}"
40
+ CONTAINER=""
41
+ NATIVE=false
42
+
43
+ # Parse arguments.
44
+ while [[ $# -gt 0 ]]; do
45
+ case $1 in
46
+ --mode)
47
+ MODE="$2"
48
+ shift 2
49
+ ;;
50
+ --signer-host)
51
+ SIGNER_HOST="$2"
52
+ shift 2
53
+ ;;
54
+ --password)
55
+ PASSWORD="$2"
56
+ shift 2
57
+ ;;
58
+ --network)
59
+ NETWORK="$2"
60
+ shift 2
61
+ ;;
62
+ --lnddir)
63
+ LND_DIR="$2"
64
+ shift 2
65
+ ;;
66
+ --recover)
67
+ RECOVER=true
68
+ shift
69
+ ;;
70
+ --seed-file)
71
+ SEED_FILE="$2"
72
+ shift 2
73
+ ;;
74
+ --rest-port)
75
+ REST_PORT="$2"
76
+ shift 2
77
+ ;;
78
+ --rest-host)
79
+ REST_HOST="$2"
80
+ shift 2
81
+ ;;
82
+ --container)
83
+ CONTAINER="$2"
84
+ shift 2
85
+ ;;
86
+ --native)
87
+ NATIVE=true
88
+ shift
89
+ ;;
90
+ -h|--help)
91
+ echo "Usage: create-wallet.sh [options]"
92
+ echo ""
93
+ echo "Create an encrypted lnd / litd wallet."
94
+ echo ""
95
+ echo "Connection options:"
96
+ echo " --container NAME Create wallet in a Docker container"
97
+ echo " --native Create wallet on local lnd process"
98
+ echo " --rest-host HOST REST API host (default: localhost)"
99
+ echo " --rest-port PORT REST API port (default: 8080)"
100
+ echo ""
101
+ echo "Wallet options:"
102
+ echo " --mode MODE Wallet mode: watchonly (default) or standalone"
103
+ echo " --signer-host HOST Signer RPC address (required for watchonly native)"
104
+ echo " --password PASS Wallet passphrase (auto-generated if omitted)"
105
+ echo " --network NETWORK Bitcoin network (default: testnet)"
106
+ echo " --lnddir DIR lnd data directory (default: ~/.lnd)"
107
+ echo " --recover Recover wallet from existing seed (standalone only)"
108
+ echo " --seed-file FILE Path to seed file for recovery (standalone only)"
109
+ echo ""
110
+ echo "Modes:"
111
+ echo " watchonly Import accounts from remote signer (no keys on this machine)"
112
+ echo " standalone Generate seed locally (keys on disk, use for testing)"
113
+ echo ""
114
+ echo "Container auto-detection order: litd > litd-shared > lnd > lnd-shared"
115
+ echo ""
116
+ echo "Environment:"
117
+ echo " LND_SIGNER_HOST Default signer host (overridden by --signer-host)"
118
+ exit 0
119
+ ;;
120
+ *)
121
+ echo "Unknown option: $1" >&2
122
+ exit 1
123
+ ;;
124
+ esac
125
+ done
126
+
127
+ # Validate mode.
128
+ if [ "$MODE" != "watchonly" ] && [ "$MODE" != "standalone" ]; then
129
+ echo "Error: Invalid mode '$MODE'. Use 'watchonly' or 'standalone'." >&2
130
+ exit 1
131
+ fi
132
+
133
+ # Auto-detect container if not native and no container specified.
134
+ if [ "$NATIVE" = false ] && [ -z "$CONTAINER" ] && [ "$REST_HOST" = "localhost" ]; then
135
+ if command -v docker &>/dev/null; then
136
+ for candidate in litd litd-shared lnd lnd-shared; do
137
+ if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$candidate"; then
138
+ CONTAINER="$candidate"
139
+ break
140
+ fi
141
+ done
142
+ fi
143
+
144
+ # If no container found and no explicit native flag, fall back to native.
145
+ if [ -z "$CONTAINER" ]; then
146
+ NATIVE=true
147
+ fi
148
+ fi
149
+
150
+ # Container mode: verify container is running.
151
+ if [ -n "$CONTAINER" ]; then
152
+ if ! docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$CONTAINER"; then
153
+ echo "Error: Container '$CONTAINER' is not running." >&2
154
+ echo "Start it with: skills/lnd/scripts/docker-start.sh" >&2
155
+ exit 1
156
+ fi
157
+ fi
158
+
159
+ # Watch-only mode validations (native only — container mode gets credentials
160
+ # from the signer container via Docker network).
161
+ if [ "$MODE" = "watchonly" ] && [ "$NATIVE" = true ]; then
162
+ CREDS_DIR="$LNGET_LND_DIR/signer-credentials"
163
+
164
+ if [ -z "$SIGNER_HOST" ]; then
165
+ echo "Error: --signer-host is required in watchonly native mode." >&2
166
+ echo "Example: create-wallet.sh --native --signer-host 10.0.0.5:10012" >&2
167
+ echo "For container mode, signer credentials are managed via Docker." >&2
168
+ exit 1
169
+ fi
170
+
171
+ if [ ! -f "$CREDS_DIR/accounts.json" ]; then
172
+ echo "Error: Signer credentials not found at $CREDS_DIR/accounts.json" >&2
173
+ echo "Run import-credentials.sh first to import the signer's credentials bundle." >&2
174
+ exit 1
175
+ fi
176
+
177
+ if [ ! -f "$CREDS_DIR/tls.cert" ] || [ ! -f "$CREDS_DIR/admin.macaroon" ]; then
178
+ echo "Error: Signer TLS cert or macaroon not found in $CREDS_DIR" >&2
179
+ echo "Run import-credentials.sh first." >&2
180
+ exit 1
181
+ fi
182
+ fi
183
+
184
+ # Watch-only mode in container mode: check for accounts.json on host or in
185
+ # the signer container.
186
+ if [ "$MODE" = "watchonly" ] && [ -n "$CONTAINER" ]; then
187
+ CREDS_DIR="$LNGET_LND_DIR/signer-credentials"
188
+
189
+ if [ ! -f "$CREDS_DIR/accounts.json" ]; then
190
+ echo "Error: Signer accounts not found at $CREDS_DIR/accounts.json" >&2
191
+ echo "" >&2
192
+ echo "Export credentials from the signer container first:" >&2
193
+ echo " skills/lightning-security-module/scripts/export-credentials.sh" >&2
194
+ exit 1
195
+ fi
196
+ fi
197
+
198
+ echo "=== Wallet Setup ==="
199
+ echo ""
200
+ if [ -n "$CONTAINER" ]; then
201
+ echo "Container: $CONTAINER"
202
+ else
203
+ echo "Mode: native"
204
+ fi
205
+ echo "Wallet: $MODE"
206
+ echo "Network: $NETWORK"
207
+ if [ "$NATIVE" = true ]; then
208
+ echo "lnd dir: $LND_DIR"
209
+ fi
210
+ echo "Creds dir: $LNGET_LND_DIR"
211
+ if [ "$MODE" = "watchonly" ] && [ -n "$SIGNER_HOST" ]; then
212
+ echo "Signer: $SIGNER_HOST"
213
+ fi
214
+ echo ""
215
+
216
+ if [ "$MODE" = "standalone" ]; then
217
+ echo "WARNING: Running in standalone mode. Private keys will be stored on this machine."
218
+ echo "For production use, set up a remote signer with the lightning-security-module skill."
219
+ echo ""
220
+ fi
221
+
222
+ # Create credential storage directory with restricted permissions.
223
+ mkdir -p "$LNGET_LND_DIR"
224
+ chmod 700 "$LNGET_LND_DIR"
225
+
226
+ PASSWORD_FILE="$LNGET_LND_DIR/wallet-password.txt"
227
+ SEED_OUTPUT="$LNGET_LND_DIR/seed.txt"
228
+
229
+ # Generate or use provided passphrase.
230
+ if [ -n "$PASSWORD" ]; then
231
+ echo "Using provided passphrase."
232
+ else
233
+ echo "Generating secure passphrase..."
234
+ PASSWORD=$(openssl rand -base64 32 | tr -d '/+=' | head -c 32)
235
+ fi
236
+
237
+ # Store passphrase with restricted permissions on host.
238
+ echo -n "$PASSWORD" > "$PASSWORD_FILE"
239
+ chmod 600 "$PASSWORD_FILE"
240
+ echo "Passphrase saved to $PASSWORD_FILE (mode 0600)"
241
+ echo ""
242
+
243
+ # Copy password file into container if applicable.
244
+ if [ -n "$CONTAINER" ]; then
245
+ docker cp "$PASSWORD_FILE" "$CONTAINER:/root/.lnd/wallet-password.txt"
246
+ echo "Password file copied into container."
247
+ fi
248
+
249
+ # Container port for docker port mapping (litd exposes REST on 8080).
250
+ CONTAINER_PORT=8080
251
+ source "$SCRIPT_DIR/../../lib/rest.sh"
252
+
253
+ # For native mode, check if lnd is running; start it temporarily if needed.
254
+ if [ "$NATIVE" = true ]; then
255
+ if ! lncli --network="$NETWORK" --lnddir="$LND_DIR" getinfo &>/dev/null 2>&1; then
256
+ # Check if lnd is at least responding on REST (running but wallet not
257
+ # created).
258
+ if rest_call GET "/v1/state" &>/dev/null; then
259
+ echo "lnd is running, waiting for wallet creation."
260
+ elif pgrep -x lnd &>/dev/null; then
261
+ echo "lnd is running but wallet is locked or not yet created."
262
+ else
263
+ echo "Starting lnd temporarily for wallet creation..."
264
+
265
+ # Build launch args based on mode.
266
+ LND_ARGS=(
267
+ --lnddir="$LND_DIR"
268
+ --bitcoin.active
269
+ "--bitcoin.$NETWORK"
270
+ --bitcoin.node=neutrino
271
+ --neutrino.addpeer=btcd0.lightning.computer
272
+ --neutrino.addpeer=mainnet1-btcd.zaphq.io
273
+ --db.backend=sqlite
274
+ "--restlisten=localhost:$REST_PORT"
275
+ --rpclisten=localhost:10009
276
+ --wallet-unlock-password-file="$PASSWORD_FILE"
277
+ --wallet-unlock-allow-create
278
+ )
279
+
280
+ # Watch-only mode: must enable remote signer before wallet
281
+ # creation. lnd requires remotesigner config to be present at
282
+ # startup to accept a watch-only wallet via initwallet.
283
+ if [ "$MODE" = "watchonly" ]; then
284
+ CREDS_DIR="$LNGET_LND_DIR/signer-credentials"
285
+ LND_ARGS+=(
286
+ --remotesigner.enable
287
+ "--remotesigner.rpchost=$SIGNER_HOST"
288
+ "--remotesigner.tlscertpath=$CREDS_DIR/tls.cert"
289
+ "--remotesigner.macaroonpath=$CREDS_DIR/admin.macaroon"
290
+ )
291
+ fi
292
+
293
+ lnd "${LND_ARGS[@]}" &
294
+ LND_PID=$!
295
+
296
+ echo "Waiting for lnd to start (PID: $LND_PID)..."
297
+ for i in {1..30}; do
298
+ if rest_call GET "/v1/state" &>/dev/null; then
299
+ break
300
+ fi
301
+ if ! kill -0 "$LND_PID" 2>/dev/null; then
302
+ echo "Error: lnd exited unexpectedly." >&2
303
+ exit 1
304
+ fi
305
+ sleep 2
306
+ echo " Waiting... ($i/30)"
307
+ done
308
+ echo ""
309
+ fi
310
+ fi
311
+ else
312
+ # Container mode: wait for REST API to be available inside container.
313
+ wait_for_rest
314
+ fi
315
+
316
+ # Branch based on mode.
317
+ if [ "$MODE" = "watchonly" ]; then
318
+ # --- Watch-only mode: import accounts from signer ---
319
+ CREDS_DIR="$LNGET_LND_DIR/signer-credentials"
320
+ ACCOUNTS_FILE="$CREDS_DIR/accounts.json"
321
+
322
+ echo "=== Creating Watch-Only Wallet ==="
323
+ echo "Importing accounts from: $ACCOUNTS_FILE"
324
+ echo ""
325
+
326
+ # Transform accounts from lncli format to initwallet format.
327
+ # lncli outputs: {accounts: [{name, address_type, extended_public_key,
328
+ # derivation_path, ...}]}
329
+ # initwallet expects: {watch_only: {accounts: [{purpose, coin_type,
330
+ # account, xpub}]}}
331
+ WO_ACCOUNTS=$(jq '[.accounts[] | {
332
+ purpose: (.derivation_path | split("/")[1] | rtrimstr("'"'"'") | tonumber),
333
+ coin_type: (.derivation_path | split("/")[2] | rtrimstr("'"'"'") | tonumber),
334
+ account: (.derivation_path | split("/")[3] | rtrimstr("'"'"'") | tonumber),
335
+ xpub: .extended_public_key
336
+ }]' "$ACCOUNTS_FILE")
337
+
338
+ ACCOUNT_COUNT=$(echo "$WO_ACCOUNTS" | jq 'length')
339
+ echo "Importing $ACCOUNT_COUNT accounts..."
340
+
341
+ PAYLOAD=$(jq -n \
342
+ --arg pass "$(echo -n "$PASSWORD" | base64)" \
343
+ --argjson accounts "$WO_ACCOUNTS" \
344
+ '{wallet_password: $pass, watch_only: {master_key_birthday_timestamp: "0", accounts: $accounts}}')
345
+
346
+ RESPONSE=$(rest_call POST "/v1/initwallet" "$PAYLOAD")
347
+
348
+ # Check for errors.
349
+ ERROR=$(echo "$RESPONSE" | jq -r '.message // empty' 2>/dev/null)
350
+ if [ -n "$ERROR" ]; then
351
+ echo "Error creating watch-only wallet: $ERROR" >&2
352
+ exit 1
353
+ fi
354
+
355
+ echo "Watch-only wallet created successfully!"
356
+ echo "No seed or private keys stored on this machine."
357
+ echo ""
358
+ echo "=== Credential Locations ==="
359
+ echo " Passphrase: $PASSWORD_FILE"
360
+ echo " Accounts: $ACCOUNTS_FILE"
361
+ echo ""
362
+ echo "Next steps:"
363
+ if [ -n "$CONTAINER" ]; then
364
+ echo " 1. Check status: skills/lnd/scripts/lncli.sh getinfo"
365
+ echo " 2. Fund wallet: skills/lnd/scripts/lncli.sh newaddress p2tr"
366
+ else
367
+ echo " 1. Start lnd: skills/lnd/scripts/start-lnd.sh --native --signer-host $SIGNER_HOST"
368
+ echo " 2. Fund wallet: skills/lnd/scripts/lncli.sh newaddress p2tr"
369
+ fi
370
+
371
+ elif [ "$MODE" = "standalone" ]; then
372
+ # --- Standalone mode: generate seed locally ---
373
+
374
+ if [ "$RECOVER" = true ]; then
375
+ echo "=== Recovering Wallet ==="
376
+ if [ -z "$SEED_FILE" ]; then
377
+ echo "Error: --seed-file required for recovery" >&2
378
+ exit 1
379
+ fi
380
+ if [ ! -f "$SEED_FILE" ]; then
381
+ echo "Error: Seed file not found: $SEED_FILE" >&2
382
+ exit 1
383
+ fi
384
+
385
+ # Read seed words from file.
386
+ SEED_WORDS=$(cat "$SEED_FILE" | tr '\n' ' ' | xargs)
387
+ SEED_JSON=$(echo "$SEED_WORDS" | tr ' ' '\n' | jq -R . | jq -s .)
388
+
389
+ # Build recovery request.
390
+ PAYLOAD=$(jq -n \
391
+ --arg pass "$(echo -n "$PASSWORD" | base64)" \
392
+ --argjson seed "$SEED_JSON" \
393
+ '{wallet_password: $pass, cipher_seed_mnemonic: $seed}')
394
+
395
+ RESPONSE=$(rest_call POST "/v1/initwallet" "$PAYLOAD")
396
+
397
+ # Check for errors.
398
+ ERROR=$(echo "$RESPONSE" | jq -r '.message // empty' 2>/dev/null)
399
+ if [ -n "$ERROR" ]; then
400
+ echo "Error recovering wallet: $ERROR" >&2
401
+ exit 1
402
+ fi
403
+
404
+ echo "Wallet recovered successfully."
405
+ else
406
+ echo "=== Creating New Wallet (Standalone) ==="
407
+
408
+ # Generate seed first.
409
+ echo "Generating wallet seed..."
410
+ SEED_RESPONSE=$(rest_call GET "/v1/genseed")
411
+
412
+ # Extract mnemonic.
413
+ MNEMONIC=$(echo "$SEED_RESPONSE" | jq -r '.cipher_seed_mnemonic[]' 2>/dev/null)
414
+ if [ -z "$MNEMONIC" ] || [ "$MNEMONIC" = "null" ]; then
415
+ echo "Error: Failed to generate seed." >&2
416
+ echo "Response: $SEED_RESPONSE" >&2
417
+ exit 1
418
+ fi
419
+
420
+ # Store seed with restricted permissions on host.
421
+ echo "$MNEMONIC" > "$SEED_OUTPUT"
422
+ chmod 600 "$SEED_OUTPUT"
423
+ echo "Seed mnemonic saved to $SEED_OUTPUT (mode 0600)"
424
+ echo ""
425
+
426
+ # Initialize wallet with password and seed.
427
+ SEED_JSON=$(echo "$MNEMONIC" | jq -R . | jq -s .)
428
+ PAYLOAD=$(jq -n \
429
+ --arg pass "$(echo -n "$PASSWORD" | base64)" \
430
+ --argjson seed "$SEED_JSON" \
431
+ '{wallet_password: $pass, cipher_seed_mnemonic: $seed}')
432
+
433
+ RESPONSE=$(rest_call POST "/v1/initwallet" "$PAYLOAD")
434
+
435
+ # Check for errors.
436
+ ERROR=$(echo "$RESPONSE" | jq -r '.message // empty' 2>/dev/null)
437
+ if [ -n "$ERROR" ]; then
438
+ echo "Error creating wallet: $ERROR" >&2
439
+ exit 1
440
+ fi
441
+
442
+ echo "Wallet created successfully!"
443
+ fi
444
+
445
+ echo ""
446
+ echo "=== Credential Locations ==="
447
+ echo " Passphrase: $PASSWORD_FILE"
448
+ if [ "$RECOVER" = false ]; then
449
+ echo " Seed: $SEED_OUTPUT"
450
+ fi
451
+ echo ""
452
+ echo "IMPORTANT: Both files are stored with restricted permissions (0600)."
453
+ echo "The seed mnemonic is your wallet backup. Keep it safe."
454
+ echo "For production use, set up a remote signer with the lightning-security-module skill."
455
+ echo ""
456
+ echo "Next steps:"
457
+ if [ -n "$CONTAINER" ]; then
458
+ echo " 1. Check status: skills/lnd/scripts/lncli.sh getinfo"
459
+ echo " 2. Fund wallet: skills/lnd/scripts/lncli.sh newaddress p2tr"
460
+ else
461
+ echo " 1. Start lnd: skills/lnd/scripts/start-lnd.sh --native --mode standalone"
462
+ echo " 2. Fund wallet: skills/lnd/scripts/lncli.sh newaddress p2tr"
463
+ fi
464
+ fi