@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.
- package/.claude-plugin/marketplace.json +36 -0
- package/.claude-plugin/plugin.json +12 -0
- package/README.md +307 -0
- package/bin/lightning-mcp-server +15 -0
- package/docs/architecture.md +455 -0
- package/docs/commerce.md +357 -0
- package/docs/l402-and-lnget.md +267 -0
- package/docs/mcp-server.md +285 -0
- package/docs/quickref.md +263 -0
- package/docs/security.md +298 -0
- package/docs/two-agent-setup.md +394 -0
- package/package.json +52 -0
- package/postinstall.js +160 -0
- package/skills/aperture/SKILL.md +330 -0
- package/skills/aperture/scripts/install.sh +68 -0
- package/skills/aperture/scripts/setup.sh +155 -0
- package/skills/aperture/scripts/start.sh +81 -0
- package/skills/aperture/scripts/stop.sh +57 -0
- package/skills/aperture/templates/aperture-regtest.yaml +36 -0
- package/skills/aperture/templates/aperture.yaml.template +64 -0
- package/skills/aperture/templates/docker-compose-aperture.yml +59 -0
- package/skills/commerce/SKILL.md +211 -0
- package/skills/lib/config-gen.sh +127 -0
- package/skills/lib/rest.sh +69 -0
- package/skills/lightning-security-module/SKILL.md +253 -0
- package/skills/lightning-security-module/references/architecture.md +133 -0
- package/skills/lightning-security-module/scripts/docker-start.sh +117 -0
- package/skills/lightning-security-module/scripts/docker-stop.sh +53 -0
- package/skills/lightning-security-module/scripts/export-credentials.sh +268 -0
- package/skills/lightning-security-module/scripts/install.sh +178 -0
- package/skills/lightning-security-module/scripts/setup-signer.sh +307 -0
- package/skills/lightning-security-module/scripts/start-signer.sh +152 -0
- package/skills/lightning-security-module/scripts/stop-signer.sh +240 -0
- package/skills/lightning-security-module/templates/docker-compose-signer.yml +35 -0
- package/skills/lightning-security-module/templates/signer-lnd.conf.template +69 -0
- package/skills/lnd/SKILL.md +441 -0
- package/skills/lnd/profiles/debug.env +4 -0
- package/skills/lnd/profiles/default.env +3 -0
- package/skills/lnd/profiles/regtest.env +4 -0
- package/skills/lnd/profiles/taproot.env +3 -0
- package/skills/lnd/profiles/wumbo.env +3 -0
- package/skills/lnd/references/security.md +156 -0
- package/skills/lnd/scripts/create-wallet.sh +464 -0
- package/skills/lnd/scripts/docker-start.sh +256 -0
- package/skills/lnd/scripts/docker-stop.sh +109 -0
- package/skills/lnd/scripts/import-credentials.sh +145 -0
- package/skills/lnd/scripts/install.sh +195 -0
- package/skills/lnd/scripts/lncli.sh +150 -0
- package/skills/lnd/scripts/start-lnd.sh +241 -0
- package/skills/lnd/scripts/stop-lnd.sh +218 -0
- package/skills/lnd/scripts/unlock-wallet.sh +134 -0
- package/skills/lnd/templates/docker-compose-regtest.yml +122 -0
- package/skills/lnd/templates/docker-compose-watchonly.yml +71 -0
- package/skills/lnd/templates/docker-compose.yml +49 -0
- package/skills/lnd/templates/litd-regtest.conf.template +61 -0
- package/skills/lnd/templates/litd-watchonly.conf.template +57 -0
- package/skills/lnd/templates/litd.conf.template +88 -0
- package/skills/lnd/templates/lnd.conf.template +91 -0
- package/skills/lnget/SKILL.md +288 -0
- package/skills/lnget/scripts/install.sh +69 -0
- package/skills/macaroon-bakery/SKILL.md +179 -0
- package/skills/macaroon-bakery/scripts/bake.sh +337 -0
- package/skills/mcp-lnc/SKILL.md +280 -0
- package/skills/mcp-lnc/scripts/configure.sh +130 -0
- package/skills/mcp-lnc/scripts/install.sh +103 -0
- package/skills/mcp-lnc/scripts/setup-claude-config.sh +162 -0
- package/skills/mcp-lnc/templates/env.template +16 -0
- 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
|