@mcpsovereign/sdk 0.1.0 → 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/dist/index.d.ts +55 -0
- package/dist/index.js +84 -0
- package/dist/setup.js +168 -16
- package/dist/wallet/index.d.ts +55 -0
- package/dist/wallet/index.js +189 -0
- package/dist/wallet/lightning.d.ts +48 -0
- package/dist/wallet/lightning.js +267 -0
- package/dist/wallet/lnd-setup.d.ts +117 -0
- package/dist/wallet/lnd-setup.js +501 -0
- package/dist/wallet/types.d.ts +89 -0
- package/dist/wallet/types.js +149 -0
- package/dist/wallet/wizard.d.ts +41 -0
- package/dist/wallet/wizard.js +498 -0
- package/package.json +5 -3
- package/dist/types.d.ts +0 -299
- package/dist/types.js +0 -95
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Automated LND Setup for mcpSovereign
|
|
3
|
+
*
|
|
4
|
+
* This module handles:
|
|
5
|
+
* 1. Docker installation check
|
|
6
|
+
* 2. LND container deployment (Neutrino mode - no full node needed)
|
|
7
|
+
* 3. Wallet creation
|
|
8
|
+
* 4. Credential extraction
|
|
9
|
+
* 5. Configuration for SDK
|
|
10
|
+
*
|
|
11
|
+
* Based on the same setup used for mcpsovereign.com
|
|
12
|
+
*/
|
|
13
|
+
import * as fs from 'fs';
|
|
14
|
+
import * as path from 'path';
|
|
15
|
+
import { spawn, exec } from 'child_process';
|
|
16
|
+
import { promisify } from 'util';
|
|
17
|
+
const execAsync = promisify(exec);
|
|
18
|
+
// ============================================================
|
|
19
|
+
// DEFAULT CONFIG
|
|
20
|
+
// ============================================================
|
|
21
|
+
const DEFAULT_CONFIG = {
|
|
22
|
+
containerName: 'sovereign-agent-lnd',
|
|
23
|
+
network: 'mainnet',
|
|
24
|
+
alias: 'SovereignAgent',
|
|
25
|
+
restPort: 8089,
|
|
26
|
+
grpcPort: 10009,
|
|
27
|
+
p2pPort: 9735,
|
|
28
|
+
dataDir: path.join(process.env.HOME || '.', '.sovereign-lnd')
|
|
29
|
+
};
|
|
30
|
+
// ============================================================
|
|
31
|
+
// DOCKER COMPOSE TEMPLATE
|
|
32
|
+
// ============================================================
|
|
33
|
+
function generateDockerCompose(config) {
|
|
34
|
+
return `# Generated by mcpSovereign SDK
|
|
35
|
+
# LND Lightning Node (Neutrino mode - no full Bitcoin node required)
|
|
36
|
+
|
|
37
|
+
services:
|
|
38
|
+
lnd:
|
|
39
|
+
image: lightninglabs/lnd:v0.18.0-beta
|
|
40
|
+
container_name: ${config.containerName}
|
|
41
|
+
restart: unless-stopped
|
|
42
|
+
volumes:
|
|
43
|
+
- ${config.dataDir}:/root/.lnd
|
|
44
|
+
ports:
|
|
45
|
+
- "${config.p2pPort}:9735"
|
|
46
|
+
- "${config.grpcPort}:10009"
|
|
47
|
+
- "${config.restPort}:8080"
|
|
48
|
+
command: >
|
|
49
|
+
lnd
|
|
50
|
+
--bitcoin.active
|
|
51
|
+
--bitcoin.${config.network}
|
|
52
|
+
--bitcoin.node=neutrino
|
|
53
|
+
--neutrino.addpeer=btcd-mainnet.lightning.computer
|
|
54
|
+
--neutrino.addpeer=mainnet1-btcd.zaphq.io
|
|
55
|
+
--neutrino.addpeer=mainnet2-btcd.zaphq.io
|
|
56
|
+
--fee.url=https://nodes.lightning.computer/fees/v1/btc-fee-estimates.json
|
|
57
|
+
--alias=${config.alias}
|
|
58
|
+
--rpclisten=0.0.0.0:10009
|
|
59
|
+
--restlisten=0.0.0.0:8080
|
|
60
|
+
--tlsextradomain=${config.containerName}
|
|
61
|
+
--tlsextraip=0.0.0.0
|
|
62
|
+
--accept-keysend
|
|
63
|
+
--accept-amp
|
|
64
|
+
--gc-canceled-invoices-on-startup
|
|
65
|
+
networks:
|
|
66
|
+
- sovereign-net
|
|
67
|
+
|
|
68
|
+
networks:
|
|
69
|
+
sovereign-net:
|
|
70
|
+
driver: bridge
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
// ============================================================
|
|
74
|
+
// SYSTEM CHECKS
|
|
75
|
+
// ============================================================
|
|
76
|
+
async function checkDocker() {
|
|
77
|
+
try {
|
|
78
|
+
const { stdout } = await execAsync('docker --version');
|
|
79
|
+
const version = stdout.trim();
|
|
80
|
+
// Check if Docker daemon is running
|
|
81
|
+
try {
|
|
82
|
+
await execAsync('docker ps');
|
|
83
|
+
return { installed: true, running: true, version };
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return { installed: true, running: false, version };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return { installed: false, running: false };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function checkDockerCompose() {
|
|
94
|
+
try {
|
|
95
|
+
await execAsync('docker compose version');
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
try {
|
|
100
|
+
await execAsync('docker-compose --version');
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ============================================================
|
|
109
|
+
// LND SETUP CLASS
|
|
110
|
+
// ============================================================
|
|
111
|
+
export class LNDSetup {
|
|
112
|
+
config;
|
|
113
|
+
logs = [];
|
|
114
|
+
constructor(config = {}) {
|
|
115
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
116
|
+
}
|
|
117
|
+
log(message) {
|
|
118
|
+
this.logs.push(`[${new Date().toISOString()}] ${message}`);
|
|
119
|
+
console.log(message);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Check if system is ready for LND setup
|
|
123
|
+
*/
|
|
124
|
+
async checkPrerequisites() {
|
|
125
|
+
this.log('Checking prerequisites...');
|
|
126
|
+
// Check Docker
|
|
127
|
+
const docker = await checkDocker();
|
|
128
|
+
this.log(`Docker: ${docker.installed ? 'Installed' : 'Not installed'} ${docker.running ? '(running)' : '(not running)'}`);
|
|
129
|
+
// Check Docker Compose
|
|
130
|
+
const compose = await checkDockerCompose();
|
|
131
|
+
this.log(`Docker Compose: ${compose ? 'Available' : 'Not available'}`);
|
|
132
|
+
// Check disk space (Neutrino needs ~1GB, we'll ask for 5GB to be safe)
|
|
133
|
+
let diskSpace = { available: 0, required: 5 * 1024 * 1024 * 1024, sufficient: false };
|
|
134
|
+
try {
|
|
135
|
+
const { stdout } = await execAsync(`df -B1 ${this.config.dataDir} 2>/dev/null || df -B1 ${process.env.HOME || '.'}`);
|
|
136
|
+
const lines = stdout.trim().split('\n');
|
|
137
|
+
if (lines.length > 1) {
|
|
138
|
+
const parts = lines[1].split(/\s+/);
|
|
139
|
+
diskSpace.available = parseInt(parts[3]) || 0;
|
|
140
|
+
diskSpace.sufficient = diskSpace.available > diskSpace.required;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
diskSpace.sufficient = true; // Assume OK if we can't check
|
|
145
|
+
}
|
|
146
|
+
this.log(`Disk space: ${Math.round(diskSpace.available / 1024 / 1024 / 1024)}GB available`);
|
|
147
|
+
return {
|
|
148
|
+
ready: docker.installed && docker.running && compose && diskSpace.sufficient,
|
|
149
|
+
docker,
|
|
150
|
+
compose,
|
|
151
|
+
diskSpace
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Install Docker if not present (Linux only)
|
|
156
|
+
*/
|
|
157
|
+
async installDocker() {
|
|
158
|
+
this.log('Attempting to install Docker...');
|
|
159
|
+
const platform = process.platform;
|
|
160
|
+
if (platform !== 'linux') {
|
|
161
|
+
this.log(`Automatic Docker installation not supported on ${platform}.`);
|
|
162
|
+
this.log('Please install Docker manually:');
|
|
163
|
+
this.log(' macOS: https://docs.docker.com/desktop/install/mac-install/');
|
|
164
|
+
this.log(' Windows: https://docs.docker.com/desktop/install/windows-install/');
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
// Use Docker's official install script
|
|
169
|
+
this.log('Running Docker install script...');
|
|
170
|
+
await execAsync('curl -fsSL https://get.docker.com | sh');
|
|
171
|
+
// Add current user to docker group
|
|
172
|
+
const user = process.env.USER || 'root';
|
|
173
|
+
await execAsync(`sudo usermod -aG docker ${user}`);
|
|
174
|
+
// Start Docker service
|
|
175
|
+
await execAsync('sudo systemctl start docker');
|
|
176
|
+
await execAsync('sudo systemctl enable docker');
|
|
177
|
+
this.log('Docker installed successfully!');
|
|
178
|
+
this.log('NOTE: You may need to log out and back in for group changes to take effect.');
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
this.log(`Docker installation failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Set up LND container
|
|
188
|
+
*/
|
|
189
|
+
async setupLND() {
|
|
190
|
+
const result = { success: false, logs: [] };
|
|
191
|
+
try {
|
|
192
|
+
// 1. Create data directory
|
|
193
|
+
this.log(`Creating data directory: ${this.config.dataDir}`);
|
|
194
|
+
fs.mkdirSync(this.config.dataDir, { recursive: true });
|
|
195
|
+
// 2. Generate docker-compose.yml
|
|
196
|
+
const composePath = path.join(this.config.dataDir, 'docker-compose.yml');
|
|
197
|
+
const composeContent = generateDockerCompose(this.config);
|
|
198
|
+
fs.writeFileSync(composePath, composeContent);
|
|
199
|
+
this.log('Generated docker-compose.yml');
|
|
200
|
+
// 3. Start the container
|
|
201
|
+
this.log('Starting LND container (this may take a minute)...');
|
|
202
|
+
await execAsync(`docker compose -f ${composePath} up -d`);
|
|
203
|
+
// 4. Wait for LND to initialize
|
|
204
|
+
this.log('Waiting for LND to initialize...');
|
|
205
|
+
await this.waitForLND();
|
|
206
|
+
// 5. Check if wallet exists
|
|
207
|
+
const walletExists = await this.checkWalletExists();
|
|
208
|
+
if (!walletExists) {
|
|
209
|
+
// 6. Create new wallet
|
|
210
|
+
this.log('Creating new Lightning wallet...');
|
|
211
|
+
const walletResult = await this.createWallet();
|
|
212
|
+
if (!walletResult.success) {
|
|
213
|
+
result.error = walletResult.error;
|
|
214
|
+
result.logs = this.logs;
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
217
|
+
result.walletSeed = walletResult.seed;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
this.log('Existing wallet found.');
|
|
221
|
+
}
|
|
222
|
+
// 7. Wait for sync
|
|
223
|
+
this.log('Waiting for Neutrino sync (this can take 5-15 minutes on first run)...');
|
|
224
|
+
await this.waitForSync();
|
|
225
|
+
// 8. Extract credentials
|
|
226
|
+
this.log('Extracting credentials...');
|
|
227
|
+
const credentials = await this.extractCredentials();
|
|
228
|
+
if (!credentials) {
|
|
229
|
+
result.error = 'Failed to extract LND credentials';
|
|
230
|
+
result.logs = this.logs;
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
result.success = true;
|
|
234
|
+
result.credentials = credentials;
|
|
235
|
+
result.logs = this.logs;
|
|
236
|
+
this.log('LND setup complete!');
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
result.error = error instanceof Error ? error.message : 'Unknown error';
|
|
241
|
+
result.logs = this.logs;
|
|
242
|
+
return result;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Wait for LND REST API to be available
|
|
247
|
+
*/
|
|
248
|
+
async waitForLND(maxAttempts = 30) {
|
|
249
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
250
|
+
try {
|
|
251
|
+
await execAsync(`docker exec ${this.config.containerName} lncli --network=${this.config.network} getinfo 2>&1`);
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
const errorMsg = error instanceof Error ? error.message : '';
|
|
256
|
+
// Wallet not created yet is OK - LND is up
|
|
257
|
+
if (errorMsg.includes('wallet not created') || errorMsg.includes('wallet locked')) {
|
|
258
|
+
return true;
|
|
259
|
+
}
|
|
260
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Check if wallet already exists
|
|
267
|
+
*/
|
|
268
|
+
async checkWalletExists() {
|
|
269
|
+
try {
|
|
270
|
+
const { stderr } = await execAsync(`docker exec ${this.config.containerName} lncli --network=${this.config.network} getinfo 2>&1`);
|
|
271
|
+
// If we get wallet locked, it exists
|
|
272
|
+
return !stderr.includes('wallet not created');
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
const errorMsg = error instanceof Error ? error.message : '';
|
|
276
|
+
return errorMsg.includes('wallet locked') || !errorMsg.includes('wallet not created');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Create new LND wallet
|
|
281
|
+
*/
|
|
282
|
+
async createWallet() {
|
|
283
|
+
return new Promise((resolve) => {
|
|
284
|
+
// Generate a random password
|
|
285
|
+
const password = this.generatePassword();
|
|
286
|
+
// Save password for auto-unlock
|
|
287
|
+
const passwordPath = path.join(this.config.dataDir, 'wallet_password');
|
|
288
|
+
fs.writeFileSync(passwordPath, password);
|
|
289
|
+
this.log('Creating wallet with secure password...');
|
|
290
|
+
// Use expect-style interaction
|
|
291
|
+
const lncli = spawn('docker', [
|
|
292
|
+
'exec', '-i', this.config.containerName,
|
|
293
|
+
'lncli', `--network=${this.config.network}`, 'create'
|
|
294
|
+
]);
|
|
295
|
+
let output = '';
|
|
296
|
+
let seed = [];
|
|
297
|
+
let step = 0;
|
|
298
|
+
lncli.stdout.on('data', (data) => {
|
|
299
|
+
const text = data.toString();
|
|
300
|
+
output += text;
|
|
301
|
+
// Step through the prompts
|
|
302
|
+
if (text.includes('wallet password')) {
|
|
303
|
+
if (step === 0) {
|
|
304
|
+
lncli.stdin.write(password + '\n');
|
|
305
|
+
step = 1;
|
|
306
|
+
}
|
|
307
|
+
else if (step === 1) {
|
|
308
|
+
lncli.stdin.write(password + '\n');
|
|
309
|
+
step = 2;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (text.includes('cipher seed mnemonic')) {
|
|
313
|
+
lncli.stdin.write('n\n'); // Don't use existing seed
|
|
314
|
+
step = 3;
|
|
315
|
+
}
|
|
316
|
+
if (text.includes('passphrase')) {
|
|
317
|
+
lncli.stdin.write('\n'); // No passphrase
|
|
318
|
+
step = 4;
|
|
319
|
+
}
|
|
320
|
+
// Extract seed words
|
|
321
|
+
const seedMatch = text.match(/\d+\.\s+(\w+)/g);
|
|
322
|
+
if (seedMatch) {
|
|
323
|
+
seed = seedMatch.map(s => s.replace(/\d+\.\s+/, ''));
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
lncli.stderr.on('data', (data) => {
|
|
327
|
+
output += data.toString();
|
|
328
|
+
});
|
|
329
|
+
lncli.on('close', (code) => {
|
|
330
|
+
if (code === 0 && seed.length === 24) {
|
|
331
|
+
// Save seed (in production, user should back this up and delete)
|
|
332
|
+
const seedPath = path.join(this.config.dataDir, 'BACKUP_SEED_DELETE_AFTER_BACKUP.txt');
|
|
333
|
+
fs.writeFileSync(seedPath, `IMPORTANT: Back up these 24 words and DELETE this file!\n\n${seed.join(' ')}`);
|
|
334
|
+
resolve({ success: true, seed });
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
resolve({ success: false, error: `Wallet creation failed: ${output}` });
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
lncli.on('error', (err) => {
|
|
341
|
+
resolve({ success: false, error: err.message });
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Wait for Neutrino to sync
|
|
347
|
+
*/
|
|
348
|
+
async waitForSync(maxMinutes = 30) {
|
|
349
|
+
const maxAttempts = maxMinutes * 6; // Check every 10 seconds
|
|
350
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
351
|
+
try {
|
|
352
|
+
const { stdout } = await execAsync(`docker exec ${this.config.containerName} lncli --network=${this.config.network} getinfo`);
|
|
353
|
+
const info = JSON.parse(stdout);
|
|
354
|
+
if (info.synced_to_chain) {
|
|
355
|
+
this.log('Neutrino sync complete!');
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
const progress = info.block_height ? `Block ${info.block_height}` : 'Syncing...';
|
|
359
|
+
if (i % 6 === 0) { // Log every minute
|
|
360
|
+
this.log(`Sync progress: ${progress}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Still starting up
|
|
365
|
+
}
|
|
366
|
+
await new Promise(resolve => setTimeout(resolve, 10000));
|
|
367
|
+
}
|
|
368
|
+
this.log('Warning: Sync timed out. Node may still be syncing in background.');
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Extract LND credentials for SDK use
|
|
373
|
+
*/
|
|
374
|
+
async extractCredentials() {
|
|
375
|
+
try {
|
|
376
|
+
// Copy cert and macaroon from container
|
|
377
|
+
const certPath = path.join(this.config.dataDir, 'tls.cert');
|
|
378
|
+
const macaroonPath = path.join(this.config.dataDir, 'admin.macaroon');
|
|
379
|
+
await execAsync(`docker cp ${this.config.containerName}:/root/.lnd/tls.cert ${certPath}`);
|
|
380
|
+
await execAsync(`docker cp ${this.config.containerName}:/root/.lnd/data/chain/bitcoin/${this.config.network}/admin.macaroon ${macaroonPath}`);
|
|
381
|
+
// Read and encode
|
|
382
|
+
const tlsCert = fs.readFileSync(certPath).toString('base64');
|
|
383
|
+
const adminMacaroon = fs.readFileSync(macaroonPath).toString('hex');
|
|
384
|
+
return {
|
|
385
|
+
tlsCert,
|
|
386
|
+
adminMacaroon,
|
|
387
|
+
restHost: `localhost:${this.config.restPort}`,
|
|
388
|
+
grpcHost: `localhost:${this.config.grpcPort}`
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
this.log(`Failed to extract credentials: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Generate a secure random password
|
|
398
|
+
*/
|
|
399
|
+
generatePassword() {
|
|
400
|
+
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
|
|
401
|
+
let password = '';
|
|
402
|
+
for (let i = 0; i < 24; i++) {
|
|
403
|
+
password += chars[Math.floor(Math.random() * chars.length)];
|
|
404
|
+
}
|
|
405
|
+
return password;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Get container status
|
|
409
|
+
*/
|
|
410
|
+
async getStatus() {
|
|
411
|
+
try {
|
|
412
|
+
// Check if container is running
|
|
413
|
+
const { stdout: psOutput } = await execAsync(`docker ps --filter "name=${this.config.containerName}" --format "{{.Status}}"`);
|
|
414
|
+
if (!psOutput.trim()) {
|
|
415
|
+
return { running: false, synced: false };
|
|
416
|
+
}
|
|
417
|
+
// Get node info
|
|
418
|
+
const { stdout } = await execAsync(`docker exec ${this.config.containerName} lncli --network=${this.config.network} getinfo`);
|
|
419
|
+
const info = JSON.parse(stdout);
|
|
420
|
+
// Get balance
|
|
421
|
+
const { stdout: balanceOut } = await execAsync(`docker exec ${this.config.containerName} lncli --network=${this.config.network} walletbalance`);
|
|
422
|
+
const balance = JSON.parse(balanceOut);
|
|
423
|
+
return {
|
|
424
|
+
running: true,
|
|
425
|
+
synced: info.synced_to_chain,
|
|
426
|
+
blockHeight: info.block_height,
|
|
427
|
+
peers: info.num_peers,
|
|
428
|
+
channels: info.num_active_channels,
|
|
429
|
+
balance: {
|
|
430
|
+
confirmed: parseInt(balance.confirmed_balance) || 0,
|
|
431
|
+
unconfirmed: parseInt(balance.unconfirmed_balance) || 0
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
catch {
|
|
436
|
+
return { running: false, synced: false };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Stop LND container
|
|
441
|
+
*/
|
|
442
|
+
async stop() {
|
|
443
|
+
try {
|
|
444
|
+
const composePath = path.join(this.config.dataDir, 'docker-compose.yml');
|
|
445
|
+
await execAsync(`docker compose -f ${composePath} down`);
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Start LND container (if stopped)
|
|
454
|
+
*/
|
|
455
|
+
async start() {
|
|
456
|
+
try {
|
|
457
|
+
const composePath = path.join(this.config.dataDir, 'docker-compose.yml');
|
|
458
|
+
await execAsync(`docker compose -f ${composePath} up -d`);
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
getLogs() {
|
|
466
|
+
return [...this.logs];
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
// ============================================================
|
|
470
|
+
// CONVENIENCE FUNCTIONS
|
|
471
|
+
// ============================================================
|
|
472
|
+
/**
|
|
473
|
+
* Quick setup for agents who want their own node
|
|
474
|
+
*/
|
|
475
|
+
export async function quickSetupLND(alias) {
|
|
476
|
+
const setup = new LNDSetup({
|
|
477
|
+
alias: alias || `SovereignAgent_${Date.now().toString(36)}`
|
|
478
|
+
});
|
|
479
|
+
// Check prerequisites
|
|
480
|
+
const prereqs = await setup.checkPrerequisites();
|
|
481
|
+
if (!prereqs.docker.installed) {
|
|
482
|
+
console.log('\nDocker is required for self-hosted Lightning.');
|
|
483
|
+
console.log('Would you like to install it? (This requires sudo on Linux)');
|
|
484
|
+
// In real implementation, this would be interactive
|
|
485
|
+
return {
|
|
486
|
+
success: false,
|
|
487
|
+
error: 'Docker not installed. Please install Docker first: https://docs.docker.com/get-docker/',
|
|
488
|
+
logs: setup.getLogs()
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
if (!prereqs.docker.running) {
|
|
492
|
+
return {
|
|
493
|
+
success: false,
|
|
494
|
+
error: 'Docker is installed but not running. Please start Docker.',
|
|
495
|
+
logs: setup.getLogs()
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
// Run setup
|
|
499
|
+
return setup.setupLND();
|
|
500
|
+
}
|
|
501
|
+
export default LNDSetup;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet Types for mcpSovereign
|
|
3
|
+
*
|
|
4
|
+
* Lightning-first wallet setup and management
|
|
5
|
+
*/
|
|
6
|
+
export type WalletProvider = 'alby' | 'phoenix' | 'walletofsatoshi' | 'muun' | 'bluewallet' | 'breez' | 'zeus' | 'custom';
|
|
7
|
+
export interface WalletProviderInfo {
|
|
8
|
+
id: WalletProvider;
|
|
9
|
+
name: string;
|
|
10
|
+
emoji: string;
|
|
11
|
+
description: string;
|
|
12
|
+
type: 'custodial' | 'non-custodial' | 'self-hosted';
|
|
13
|
+
platforms: ('web' | 'ios' | 'android' | 'desktop')[];
|
|
14
|
+
supportsLNURL: boolean;
|
|
15
|
+
supportsLightningAddress: boolean;
|
|
16
|
+
setupUrl: string;
|
|
17
|
+
difficulty: 'easy' | 'medium' | 'advanced';
|
|
18
|
+
recommended?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export declare const WALLET_PROVIDERS: Record<WalletProvider, WalletProviderInfo>;
|
|
21
|
+
export interface WalletConfig {
|
|
22
|
+
provider: WalletProvider;
|
|
23
|
+
lightningAddress?: string;
|
|
24
|
+
lnurlPay?: string;
|
|
25
|
+
nodeUri?: string;
|
|
26
|
+
setupComplete: boolean;
|
|
27
|
+
verified: boolean;
|
|
28
|
+
verifiedAt?: string;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
lastUsed?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface LightningAddressInfo {
|
|
33
|
+
address: string;
|
|
34
|
+
username: string;
|
|
35
|
+
domain: string;
|
|
36
|
+
valid: boolean;
|
|
37
|
+
lnurlPayUrl?: string;
|
|
38
|
+
metadata?: {
|
|
39
|
+
description?: string;
|
|
40
|
+
minSendable?: number;
|
|
41
|
+
maxSendable?: number;
|
|
42
|
+
nostrPubkey?: string;
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export interface WalletSetupProgress {
|
|
46
|
+
currentStep: number;
|
|
47
|
+
completed: boolean;
|
|
48
|
+
provider?: WalletProvider;
|
|
49
|
+
lightningAddress?: string;
|
|
50
|
+
verified: boolean;
|
|
51
|
+
skipped: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface WalletSetupStep {
|
|
54
|
+
id: string;
|
|
55
|
+
title: string;
|
|
56
|
+
description: string;
|
|
57
|
+
emoji: string;
|
|
58
|
+
action: string;
|
|
59
|
+
}
|
|
60
|
+
export declare const WALLET_SETUP_STEPS: WalletSetupStep[];
|
|
61
|
+
export interface PaymentRequest {
|
|
62
|
+
paymentHash: string;
|
|
63
|
+
paymentRequest: string;
|
|
64
|
+
amountSats: number;
|
|
65
|
+
description: string;
|
|
66
|
+
expiresAt: string;
|
|
67
|
+
status: 'pending' | 'paid' | 'expired' | 'failed';
|
|
68
|
+
}
|
|
69
|
+
export interface PaymentResult {
|
|
70
|
+
success: boolean;
|
|
71
|
+
paymentHash?: string;
|
|
72
|
+
preimage?: string;
|
|
73
|
+
feeSats?: number;
|
|
74
|
+
error?: string;
|
|
75
|
+
}
|
|
76
|
+
export interface WithdrawalRequest {
|
|
77
|
+
amountCredits: number;
|
|
78
|
+
amountSats: number;
|
|
79
|
+
lightningAddress: string;
|
|
80
|
+
status: 'pending' | 'processing' | 'complete' | 'failed';
|
|
81
|
+
}
|
|
82
|
+
export interface WithdrawalResult {
|
|
83
|
+
success: boolean;
|
|
84
|
+
withdrawalId?: string;
|
|
85
|
+
amountSats?: number;
|
|
86
|
+
feeSats?: number;
|
|
87
|
+
paymentHash?: string;
|
|
88
|
+
error?: string;
|
|
89
|
+
}
|