@mcpsovereign/sdk 0.1.0 → 0.2.1
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/README.md +225 -276
- 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,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightning Network Utilities
|
|
3
|
+
*
|
|
4
|
+
* Lightning address validation, LNURL handling, and payment helpers
|
|
5
|
+
*/
|
|
6
|
+
// ============================================================
|
|
7
|
+
// LIGHTNING ADDRESS VALIDATION
|
|
8
|
+
// ============================================================
|
|
9
|
+
/**
|
|
10
|
+
* Parse and validate a Lightning address
|
|
11
|
+
* Format: username@domain.com
|
|
12
|
+
*/
|
|
13
|
+
export function parseLightningAddress(address) {
|
|
14
|
+
// Basic format check
|
|
15
|
+
const pattern = /^([a-zA-Z0-9._-]+)@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/;
|
|
16
|
+
const match = address.trim().toLowerCase().match(pattern);
|
|
17
|
+
if (!match) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const [, username, domain] = match;
|
|
21
|
+
return {
|
|
22
|
+
address: `${username}@${domain}`,
|
|
23
|
+
username,
|
|
24
|
+
domain,
|
|
25
|
+
valid: true,
|
|
26
|
+
lnurlPayUrl: `https://${domain}/.well-known/lnurlp/${username}`
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Verify a Lightning address is reachable and can receive payments
|
|
31
|
+
*/
|
|
32
|
+
export async function verifyLightningAddress(address) {
|
|
33
|
+
const parsed = parseLightningAddress(address);
|
|
34
|
+
if (!parsed) {
|
|
35
|
+
return {
|
|
36
|
+
valid: false,
|
|
37
|
+
error: 'Invalid Lightning address format. Expected: username@domain.com'
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
// Fetch LNURL-pay endpoint
|
|
42
|
+
const response = await fetch(parsed.lnurlPayUrl, {
|
|
43
|
+
headers: { 'Accept': 'application/json' }
|
|
44
|
+
});
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
return {
|
|
47
|
+
valid: false,
|
|
48
|
+
info: parsed,
|
|
49
|
+
error: `Lightning address not found at ${parsed.domain}`
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const data = await response.json();
|
|
53
|
+
// Verify it's a valid LNURL-pay endpoint
|
|
54
|
+
if (data.tag !== 'payRequest' && !data.callback) {
|
|
55
|
+
return {
|
|
56
|
+
valid: false,
|
|
57
|
+
info: parsed,
|
|
58
|
+
error: 'Invalid LNURL-pay response'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Parse metadata
|
|
62
|
+
let description;
|
|
63
|
+
if (data.metadata) {
|
|
64
|
+
try {
|
|
65
|
+
const meta = JSON.parse(data.metadata);
|
|
66
|
+
const textEntry = meta.find(([type]) => type === 'text/plain');
|
|
67
|
+
if (textEntry) {
|
|
68
|
+
description = textEntry[1];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Ignore metadata parsing errors
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
valid: true,
|
|
77
|
+
info: {
|
|
78
|
+
...parsed,
|
|
79
|
+
metadata: {
|
|
80
|
+
description,
|
|
81
|
+
minSendable: data.minSendable,
|
|
82
|
+
maxSendable: data.maxSendable,
|
|
83
|
+
nostrPubkey: data.nostrPubkey
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
return {
|
|
90
|
+
valid: false,
|
|
91
|
+
info: parsed,
|
|
92
|
+
error: `Could not reach ${parsed.domain}: ${error instanceof Error ? error.message : 'Network error'}`
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ============================================================
|
|
97
|
+
// COMMON LIGHTNING ADDRESS PROVIDERS
|
|
98
|
+
// ============================================================
|
|
99
|
+
export const KNOWN_PROVIDERS = {
|
|
100
|
+
'getalby.com': {
|
|
101
|
+
name: 'Alby',
|
|
102
|
+
domain: 'getalby.com',
|
|
103
|
+
signupUrl: 'https://getalby.com'
|
|
104
|
+
},
|
|
105
|
+
'walletofsatoshi.com': {
|
|
106
|
+
name: 'Wallet of Satoshi',
|
|
107
|
+
domain: 'walletofsatoshi.com',
|
|
108
|
+
signupUrl: 'https://walletofsatoshi.com'
|
|
109
|
+
},
|
|
110
|
+
'ln.tips': {
|
|
111
|
+
name: 'ln.tips',
|
|
112
|
+
domain: 'ln.tips',
|
|
113
|
+
signupUrl: 'https://ln.tips'
|
|
114
|
+
},
|
|
115
|
+
'stacker.news': {
|
|
116
|
+
name: 'Stacker News',
|
|
117
|
+
domain: 'stacker.news',
|
|
118
|
+
signupUrl: 'https://stacker.news'
|
|
119
|
+
},
|
|
120
|
+
'strike.me': {
|
|
121
|
+
name: 'Strike',
|
|
122
|
+
domain: 'strike.me',
|
|
123
|
+
signupUrl: 'https://strike.me'
|
|
124
|
+
},
|
|
125
|
+
'zbd.gg': {
|
|
126
|
+
name: 'ZBD',
|
|
127
|
+
domain: 'zbd.gg',
|
|
128
|
+
signupUrl: 'https://zbd.gg'
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Get provider info from a Lightning address
|
|
133
|
+
*/
|
|
134
|
+
export function getProviderFromAddress(address) {
|
|
135
|
+
const parsed = parseLightningAddress(address);
|
|
136
|
+
if (!parsed)
|
|
137
|
+
return null;
|
|
138
|
+
const known = KNOWN_PROVIDERS[parsed.domain];
|
|
139
|
+
if (known) {
|
|
140
|
+
return {
|
|
141
|
+
provider: known.name,
|
|
142
|
+
signupUrl: known.signupUrl
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
provider: parsed.domain,
|
|
147
|
+
signupUrl: `https://${parsed.domain}`
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
// ============================================================
|
|
151
|
+
// PAYMENT HELPERS
|
|
152
|
+
// ============================================================
|
|
153
|
+
/**
|
|
154
|
+
* Generate a payment request for a Lightning address
|
|
155
|
+
*/
|
|
156
|
+
export async function createPaymentRequest(address, amountSats, comment) {
|
|
157
|
+
const parsed = parseLightningAddress(address);
|
|
158
|
+
if (!parsed) {
|
|
159
|
+
return { success: false, error: 'Invalid Lightning address' };
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
// Get LNURL-pay callback
|
|
163
|
+
const lnurlResponse = await fetch(parsed.lnurlPayUrl);
|
|
164
|
+
const lnurlData = await lnurlResponse.json();
|
|
165
|
+
// Convert sats to millisats
|
|
166
|
+
const amountMsats = amountSats * 1000;
|
|
167
|
+
// Check limits
|
|
168
|
+
if (amountMsats < lnurlData.minSendable) {
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
error: `Amount too small. Minimum: ${lnurlData.minSendable / 1000} sats`
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (amountMsats > lnurlData.maxSendable) {
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
error: `Amount too large. Maximum: ${lnurlData.maxSendable / 1000} sats`
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
// Request invoice
|
|
181
|
+
const callbackUrl = new URL(lnurlData.callback);
|
|
182
|
+
callbackUrl.searchParams.set('amount', amountMsats.toString());
|
|
183
|
+
if (comment) {
|
|
184
|
+
callbackUrl.searchParams.set('comment', comment);
|
|
185
|
+
}
|
|
186
|
+
const invoiceResponse = await fetch(callbackUrl.toString());
|
|
187
|
+
const invoiceData = await invoiceResponse.json();
|
|
188
|
+
if (!invoiceData.pr) {
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
error: invoiceData.reason || 'Failed to generate invoice'
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
// Extract payment hash from invoice (it's in the BOLT11 encoded)
|
|
195
|
+
const paymentHash = extractPaymentHash(invoiceData.pr);
|
|
196
|
+
return {
|
|
197
|
+
success: true,
|
|
198
|
+
invoice: invoiceData.pr,
|
|
199
|
+
paymentHash
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: `Payment request failed: ${error instanceof Error ? error.message : 'Unknown error'}`
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Extract payment hash from BOLT11 invoice
|
|
211
|
+
* (Simplified - in production use a proper decoder)
|
|
212
|
+
*/
|
|
213
|
+
function extractPaymentHash(bolt11) {
|
|
214
|
+
// BOLT11 payment hash is typically in a specific position after decoding
|
|
215
|
+
// For simplicity, we'll generate a placeholder
|
|
216
|
+
// In production, use a library like bolt11 or lightningpay
|
|
217
|
+
try {
|
|
218
|
+
const hash = bolt11.substring(0, 64);
|
|
219
|
+
return hash;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// ============================================================
|
|
226
|
+
// CREDIT CONVERSION
|
|
227
|
+
// ============================================================
|
|
228
|
+
// mcpSovereign: 1,000 credits = $1 = 10,000 sats
|
|
229
|
+
export const CREDITS_PER_SAT = 0.1; // 1 sat = 0.1 credits
|
|
230
|
+
export const SATS_PER_CREDIT = 10; // 1 credit = 10 sats
|
|
231
|
+
export function creditsToSats(credits) {
|
|
232
|
+
return Math.ceil(credits * SATS_PER_CREDIT);
|
|
233
|
+
}
|
|
234
|
+
export function satsToCredits(sats) {
|
|
235
|
+
return Math.floor(sats * CREDITS_PER_SAT);
|
|
236
|
+
}
|
|
237
|
+
export function satsToDollars(sats, btcPrice = 100000) {
|
|
238
|
+
// 1 BTC = 100,000,000 sats
|
|
239
|
+
return (sats / 100_000_000) * btcPrice;
|
|
240
|
+
}
|
|
241
|
+
export function dollarsToSats(dollars, btcPrice = 100000) {
|
|
242
|
+
return Math.ceil((dollars / btcPrice) * 100_000_000);
|
|
243
|
+
}
|
|
244
|
+
// ============================================================
|
|
245
|
+
// FORMATTING
|
|
246
|
+
// ============================================================
|
|
247
|
+
export function formatSats(sats) {
|
|
248
|
+
if (sats >= 100_000_000) {
|
|
249
|
+
return `${(sats / 100_000_000).toFixed(2)} BTC`;
|
|
250
|
+
}
|
|
251
|
+
if (sats >= 1_000_000) {
|
|
252
|
+
return `${(sats / 1_000_000).toFixed(2)}M sats`;
|
|
253
|
+
}
|
|
254
|
+
if (sats >= 1_000) {
|
|
255
|
+
return `${(sats / 1_000).toFixed(1)}k sats`;
|
|
256
|
+
}
|
|
257
|
+
return `${sats} sats`;
|
|
258
|
+
}
|
|
259
|
+
export function formatCredits(credits) {
|
|
260
|
+
if (credits >= 1_000_000) {
|
|
261
|
+
return `${(credits / 1_000_000).toFixed(2)}M credits`;
|
|
262
|
+
}
|
|
263
|
+
if (credits >= 1_000) {
|
|
264
|
+
return `${(credits / 1_000).toFixed(1)}k credits`;
|
|
265
|
+
}
|
|
266
|
+
return `${credits} credits`;
|
|
267
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
export interface LNDConfig {
|
|
14
|
+
containerName: string;
|
|
15
|
+
network: 'mainnet' | 'testnet' | 'regtest';
|
|
16
|
+
alias: string;
|
|
17
|
+
restPort: number;
|
|
18
|
+
grpcPort: number;
|
|
19
|
+
p2pPort: number;
|
|
20
|
+
dataDir: string;
|
|
21
|
+
}
|
|
22
|
+
export interface LNDCredentials {
|
|
23
|
+
tlsCert: string;
|
|
24
|
+
adminMacaroon: string;
|
|
25
|
+
restHost: string;
|
|
26
|
+
grpcHost: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SetupResult {
|
|
29
|
+
success: boolean;
|
|
30
|
+
credentials?: LNDCredentials;
|
|
31
|
+
walletSeed?: string[];
|
|
32
|
+
error?: string;
|
|
33
|
+
logs: string[];
|
|
34
|
+
}
|
|
35
|
+
export declare class LNDSetup {
|
|
36
|
+
private config;
|
|
37
|
+
private logs;
|
|
38
|
+
constructor(config?: Partial<LNDConfig>);
|
|
39
|
+
private log;
|
|
40
|
+
/**
|
|
41
|
+
* Check if system is ready for LND setup
|
|
42
|
+
*/
|
|
43
|
+
checkPrerequisites(): Promise<{
|
|
44
|
+
ready: boolean;
|
|
45
|
+
docker: {
|
|
46
|
+
installed: boolean;
|
|
47
|
+
running: boolean;
|
|
48
|
+
version?: string;
|
|
49
|
+
};
|
|
50
|
+
compose: boolean;
|
|
51
|
+
diskSpace: {
|
|
52
|
+
available: number;
|
|
53
|
+
required: number;
|
|
54
|
+
sufficient: boolean;
|
|
55
|
+
};
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Install Docker if not present (Linux only)
|
|
59
|
+
*/
|
|
60
|
+
installDocker(): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Set up LND container
|
|
63
|
+
*/
|
|
64
|
+
setupLND(): Promise<SetupResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Wait for LND REST API to be available
|
|
67
|
+
*/
|
|
68
|
+
private waitForLND;
|
|
69
|
+
/**
|
|
70
|
+
* Check if wallet already exists
|
|
71
|
+
*/
|
|
72
|
+
private checkWalletExists;
|
|
73
|
+
/**
|
|
74
|
+
* Create new LND wallet
|
|
75
|
+
*/
|
|
76
|
+
private createWallet;
|
|
77
|
+
/**
|
|
78
|
+
* Wait for Neutrino to sync
|
|
79
|
+
*/
|
|
80
|
+
private waitForSync;
|
|
81
|
+
/**
|
|
82
|
+
* Extract LND credentials for SDK use
|
|
83
|
+
*/
|
|
84
|
+
private extractCredentials;
|
|
85
|
+
/**
|
|
86
|
+
* Generate a secure random password
|
|
87
|
+
*/
|
|
88
|
+
private generatePassword;
|
|
89
|
+
/**
|
|
90
|
+
* Get container status
|
|
91
|
+
*/
|
|
92
|
+
getStatus(): Promise<{
|
|
93
|
+
running: boolean;
|
|
94
|
+
synced: boolean;
|
|
95
|
+
blockHeight?: number;
|
|
96
|
+
peers?: number;
|
|
97
|
+
channels?: number;
|
|
98
|
+
balance?: {
|
|
99
|
+
confirmed: number;
|
|
100
|
+
unconfirmed: number;
|
|
101
|
+
};
|
|
102
|
+
}>;
|
|
103
|
+
/**
|
|
104
|
+
* Stop LND container
|
|
105
|
+
*/
|
|
106
|
+
stop(): Promise<boolean>;
|
|
107
|
+
/**
|
|
108
|
+
* Start LND container (if stopped)
|
|
109
|
+
*/
|
|
110
|
+
start(): Promise<boolean>;
|
|
111
|
+
getLogs(): string[];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Quick setup for agents who want their own node
|
|
115
|
+
*/
|
|
116
|
+
export declare function quickSetupLND(alias?: string): Promise<SetupResult>;
|
|
117
|
+
export default LNDSetup;
|