fulgur-bridge-client 0.0.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.
@@ -0,0 +1,127 @@
1
+ type DonationStatus = "invoice_created" | "payment_received" | "paying_out" | "success" | "failed" | "expired";
2
+ /**
3
+ * Donation as returned by the gateway REST API.
4
+ *
5
+ * Field names use snake_case to match the JSON wire format. Pass server
6
+ * responses directly without mapping.
7
+ */
8
+ interface Donation {
9
+ id: string;
10
+ ln_address: string;
11
+ amount_msat: number;
12
+ status: DonationStatus;
13
+ payment_hash: string;
14
+ bolt11: string;
15
+ payout_fee_msat: number | null;
16
+ error_message: string | null;
17
+ webhook_url: string | null;
18
+ created_at: string;
19
+ updated_at: string;
20
+ }
21
+ interface CreateDonationParams {
22
+ /**
23
+ * Payment destination: Lightning Address (`user@domain.com`)
24
+ * or BOLT12 offer (`lno1...`).
25
+ */
26
+ destination: string;
27
+ /** Amount in satoshis. Minimum 1 sat. */
28
+ amountSat: number;
29
+ /**
30
+ * Optional webhook URL. Receives a POST with the donation payload
31
+ * immediately after successful payout. Retries on failure:
32
+ * 10s, 1m, 2m, 5m, 10m, 30m, 1h, then hourly up to 1 week.
33
+ */
34
+ webhookUrl?: string;
35
+ /**
36
+ * Optional HMAC-SHA256 secret. When set, the gateway signs webhook
37
+ * payloads and sends the hex signature in the `X-Signature-256` header.
38
+ */
39
+ webhookSecret?: string;
40
+ }
41
+ interface CreateDonationResult {
42
+ id: string;
43
+ bolt11: string;
44
+ paymentHash: string;
45
+ }
46
+ interface FeeEstimate {
47
+ amount_sat: number;
48
+ receive_fee_sat: number;
49
+ send_fee_estimate_sat: number;
50
+ total_fee_estimate_sat: number;
51
+ payout_estimate_sat: number;
52
+ }
53
+ interface ListDonationsParams {
54
+ limit?: number;
55
+ offset?: number;
56
+ }
57
+ interface WaitForPaymentOptions {
58
+ /** Called on each status transition. */
59
+ onStatusChange?: (status: DonationStatus) => void;
60
+ /** Timeout in milliseconds. Rejects with error on expiry. */
61
+ timeoutMs?: number;
62
+ }
63
+ interface PowChallenge {
64
+ challenge: string;
65
+ difficulty: number;
66
+ expires_in_secs: number;
67
+ }
68
+ /** Webhook POST body. See README for headers and retry schedule. */
69
+ interface WebhookPayload {
70
+ event: "donation.success";
71
+ donation: Donation;
72
+ }
73
+
74
+ /** Gateway REST + WebSocket client. Handles PoW automatically. */
75
+ declare class RestGatewayClient {
76
+ private baseUrl;
77
+ constructor(gatewayUrl: string);
78
+ /** Returns null when the gateway doesn't require PoW for this IP. */
79
+ getChallenge(): Promise<PowChallenge | null>;
80
+ /** Create a donation. Solves PoW challenge if the gateway requires one. */
81
+ createDonation(params: CreateDonationParams): Promise<CreateDonationResult>;
82
+ getDonation(id: string): Promise<Donation>;
83
+ listDonations(params?: ListDonationsParams): Promise<Donation[]>;
84
+ estimateFees(amountSat: number): Promise<FeeEstimate>;
85
+ /** Subscribe via WS, resolve on terminal status. */
86
+ waitForPayment(donationId: string, options?: WaitForPaymentOptions): Promise<DonationStatus>;
87
+ }
88
+
89
+ /**
90
+ * Hashcash PoW solver. Finds nonce where SHA256(challenge:nonce) has
91
+ * N leading zero bits. Auto-picks the fastest runtime strategy:
92
+ * node:crypto > pure JS SHA-256 > crypto.subtle (browser, async).
93
+ */
94
+ declare function solvePow(challenge: string, difficulty: number): Promise<number>;
95
+ declare function verifyPow(challenge: string, nonce: number, difficulty: number): Promise<boolean>;
96
+
97
+ interface QrOptions {
98
+ /** SVG width/height in pixels. Default: 256. */
99
+ size?: number;
100
+ /** Dark module color. Default: "#000". */
101
+ color?: string;
102
+ /** Prepend `LIGHTNING:` prefix to BOLT11 for wallet compat. Default: true. */
103
+ withPrefix?: boolean;
104
+ }
105
+ /** Generate an SVG QR code. Works with both BOLT11 invoices and BOLT12 offers. */
106
+ declare function invoiceToSvg(invoice: string, options?: QrOptions): string;
107
+ /** SVG data URL for use in `<img src="...">`. */
108
+ declare function invoiceToDataUrl(invoice: string, options?: QrOptions): string;
109
+
110
+ /** BOLT12 offer strings start with "lno1". */
111
+ declare function isBolt12Offer(s: string): boolean;
112
+ /** Basic format check (user@domain). Does not verify the domain. */
113
+ declare function isLnAddress(s: string): boolean;
114
+ type DestinationType = "bolt12Offer" | "lnAddress" | "unknown";
115
+ declare function detectDestinationType(s: string): DestinationType;
116
+
117
+ /** Verify `X-Signature-256` header. Pass the raw body, not parsed JSON. */
118
+ declare function verifyWebhookSignature(body: string | Buffer | Uint8Array, signature: string, secret: string): Promise<boolean>;
119
+ /** Verify + parse in one step. Returns null on bad signature. */
120
+ declare function parseWebhook(body: string | Buffer | Uint8Array, signature: string, secret: string): Promise<WebhookPayload | null>;
121
+ /**
122
+ * Verify + parse from a Fetch API `Request` (Hono, Next.js, SvelteKit, etc.).
123
+ * Requires `node:crypto`. Won't work in browsers or Cloudflare Workers.
124
+ */
125
+ declare function parseWebhookRequest(request: Request, secret: string): Promise<WebhookPayload | null>;
126
+
127
+ export { type CreateDonationParams, type CreateDonationResult, type DestinationType, type Donation, type DonationStatus, type FeeEstimate, type ListDonationsParams, type PowChallenge, type QrOptions, RestGatewayClient, type WaitForPaymentOptions, type WebhookPayload, detectDestinationType, invoiceToDataUrl, invoiceToSvg, isBolt12Offer, isLnAddress, parseWebhook, parseWebhookRequest, solvePow, verifyPow, verifyWebhookSignature };
package/dist/index.js ADDED
@@ -0,0 +1,426 @@
1
+ // src/pow.ts
2
+ function leadingZeroBits(hash) {
3
+ let count = 0;
4
+ for (const byte of hash) {
5
+ if (byte === 0) {
6
+ count += 8;
7
+ } else {
8
+ count += Math.clz32(byte) - 24;
9
+ break;
10
+ }
11
+ }
12
+ return count;
13
+ }
14
+ var _nodeCrypto;
15
+ async function getNodeCrypto() {
16
+ if (_nodeCrypto !== void 0) return _nodeCrypto;
17
+ try {
18
+ _nodeCrypto = await import("crypto");
19
+ return _nodeCrypto;
20
+ } catch {
21
+ _nodeCrypto = null;
22
+ return null;
23
+ }
24
+ }
25
+ function solveWithNodeCrypto(crypto2, challenge, difficulty) {
26
+ const prefix = `${challenge}:`;
27
+ for (let nonce = 0; ; nonce++) {
28
+ const hash = crypto2.createHash("sha256").update(`${prefix}${nonce}`).digest();
29
+ if (leadingZeroBits(hash) >= difficulty) return nonce;
30
+ }
31
+ }
32
+ var K = new Uint32Array([
33
+ 1116352408,
34
+ 1899447441,
35
+ 3049323471,
36
+ 3921009573,
37
+ 961987163,
38
+ 1508970993,
39
+ 2453635748,
40
+ 2870763221,
41
+ 3624381080,
42
+ 310598401,
43
+ 607225278,
44
+ 1426881987,
45
+ 1925078388,
46
+ 2162078206,
47
+ 2614888103,
48
+ 3248222580,
49
+ 3835390401,
50
+ 4022224774,
51
+ 264347078,
52
+ 604807628,
53
+ 770255983,
54
+ 1249150122,
55
+ 1555081692,
56
+ 1996064986,
57
+ 2554220882,
58
+ 2821834349,
59
+ 2952996808,
60
+ 3210313671,
61
+ 3336571891,
62
+ 3584528711,
63
+ 113926993,
64
+ 338241895,
65
+ 666307205,
66
+ 773529912,
67
+ 1294757372,
68
+ 1396182291,
69
+ 1695183700,
70
+ 1986661051,
71
+ 2177026350,
72
+ 2456956037,
73
+ 2730485921,
74
+ 2820302411,
75
+ 3259730800,
76
+ 3345764771,
77
+ 3516065817,
78
+ 3600352804,
79
+ 4094571909,
80
+ 275423344,
81
+ 430227734,
82
+ 506948616,
83
+ 659060556,
84
+ 883997877,
85
+ 958139571,
86
+ 1322822218,
87
+ 1537002063,
88
+ 1747873779,
89
+ 1955562222,
90
+ 2024104815,
91
+ 2227730452,
92
+ 2361852424,
93
+ 2428436474,
94
+ 2756734187,
95
+ 3204031479,
96
+ 3329325298
97
+ ]);
98
+ function sha256js(bytes) {
99
+ const len = bytes.length;
100
+ const bitLen = len * 8;
101
+ const padLen = len + 9 + 63 & ~63;
102
+ const buf = new Uint8Array(padLen);
103
+ buf.set(bytes);
104
+ buf[len] = 128;
105
+ const dv = new DataView(buf.buffer);
106
+ dv.setUint32(padLen - 4, bitLen, false);
107
+ let h0 = 1779033703, h1 = 3144134277, h2 = 1013904242, h3 = 2773480762;
108
+ let h4 = 1359893119, h5 = 2600822924, h6 = 528734635, h7 = 1541459225;
109
+ const w = new Uint32Array(64);
110
+ for (let off = 0; off < padLen; off += 64) {
111
+ for (let i = 0; i < 16; i++) w[i] = dv.getUint32(off + i * 4, false);
112
+ for (let i = 16; i < 64; i++) {
113
+ const s0 = (w[i - 15] >>> 7 | w[i - 15] << 25) ^ (w[i - 15] >>> 18 | w[i - 15] << 14) ^ w[i - 15] >>> 3;
114
+ const s1 = (w[i - 2] >>> 17 | w[i - 2] << 15) ^ (w[i - 2] >>> 19 | w[i - 2] << 13) ^ w[i - 2] >>> 10;
115
+ w[i] = w[i - 16] + s0 + w[i - 7] + s1 | 0;
116
+ }
117
+ let a = h0, b = h1, c = h2, d = h3, e = h4, f = h5, g = h6, h = h7;
118
+ for (let i = 0; i < 64; i++) {
119
+ const S1 = (e >>> 6 | e << 26) ^ (e >>> 11 | e << 21) ^ (e >>> 25 | e << 7);
120
+ const ch = e & f ^ ~e & g;
121
+ const t1 = h + S1 + ch + K[i] + w[i] | 0;
122
+ const S0 = (a >>> 2 | a << 30) ^ (a >>> 13 | a << 19) ^ (a >>> 22 | a << 10);
123
+ const maj = a & b ^ a & c ^ b & c;
124
+ const t2 = S0 + maj | 0;
125
+ h = g;
126
+ g = f;
127
+ f = e;
128
+ e = d + t1 | 0;
129
+ d = c;
130
+ c = b;
131
+ b = a;
132
+ a = t1 + t2 | 0;
133
+ }
134
+ h0 = h0 + a | 0;
135
+ h1 = h1 + b | 0;
136
+ h2 = h2 + c | 0;
137
+ h3 = h3 + d | 0;
138
+ h4 = h4 + e | 0;
139
+ h5 = h5 + f | 0;
140
+ h6 = h6 + g | 0;
141
+ h7 = h7 + h | 0;
142
+ }
143
+ const out = new Uint8Array(32);
144
+ const odv = new DataView(out.buffer);
145
+ odv.setUint32(0, h0);
146
+ odv.setUint32(4, h1);
147
+ odv.setUint32(8, h2);
148
+ odv.setUint32(12, h3);
149
+ odv.setUint32(16, h4);
150
+ odv.setUint32(20, h5);
151
+ odv.setUint32(24, h6);
152
+ odv.setUint32(28, h7);
153
+ return out;
154
+ }
155
+ function solveWithPureJs(challenge, difficulty) {
156
+ const encoder = new TextEncoder();
157
+ const prefix = encoder.encode(`${challenge}:`);
158
+ for (let nonce = 0; ; nonce++) {
159
+ const suffix = encoder.encode(String(nonce));
160
+ const msg = new Uint8Array(prefix.length + suffix.length);
161
+ msg.set(prefix);
162
+ msg.set(suffix, prefix.length);
163
+ if (leadingZeroBits(sha256js(msg)) >= difficulty) return nonce;
164
+ }
165
+ }
166
+ async function solveWithSubtle(challenge, difficulty) {
167
+ const BATCH_SIZE = 5e3;
168
+ const encoder = new TextEncoder();
169
+ for (let start = 0; ; start += BATCH_SIZE) {
170
+ for (let nonce = start; nonce < start + BATCH_SIZE; nonce++) {
171
+ const data = encoder.encode(`${challenge}:${nonce}`);
172
+ const buffer = await crypto.subtle.digest("SHA-256", data);
173
+ if (leadingZeroBits(new Uint8Array(buffer)) >= difficulty) return nonce;
174
+ }
175
+ await new Promise((r) => setTimeout(r, 0));
176
+ }
177
+ }
178
+ var isBrowser = typeof window !== "undefined" && typeof window.document !== "undefined";
179
+ async function solvePow(challenge, difficulty) {
180
+ const nodeCrypto = await getNodeCrypto();
181
+ if (nodeCrypto) return solveWithNodeCrypto(nodeCrypto, challenge, difficulty);
182
+ if (!isBrowser) return solveWithPureJs(challenge, difficulty);
183
+ return solveWithSubtle(challenge, difficulty);
184
+ }
185
+ async function verifyPow(challenge, nonce, difficulty) {
186
+ const nodeCrypto = await getNodeCrypto();
187
+ if (nodeCrypto) {
188
+ const hash = nodeCrypto.createHash("sha256").update(`${challenge}:${nonce}`).digest();
189
+ return leadingZeroBits(hash) >= difficulty;
190
+ }
191
+ const encoder = new TextEncoder();
192
+ return leadingZeroBits(sha256js(encoder.encode(`${challenge}:${nonce}`))) >= difficulty;
193
+ }
194
+
195
+ // src/rest.ts
196
+ var RestGatewayClient = class {
197
+ baseUrl;
198
+ constructor(gatewayUrl) {
199
+ this.baseUrl = gatewayUrl.replace(/\/$/, "");
200
+ }
201
+ /** Returns null when the gateway doesn't require PoW for this IP. */
202
+ async getChallenge() {
203
+ const resp = await fetch(`${this.baseUrl}/api/v1/pow-challenge`);
204
+ if (resp.status === 400) return null;
205
+ if (!resp.ok) throw new Error(`Challenge request failed: ${resp.status}`);
206
+ return resp.json();
207
+ }
208
+ /** Create a donation. Solves PoW challenge if the gateway requires one. */
209
+ async createDonation(params) {
210
+ const challenge = await this.getChallenge();
211
+ let pow;
212
+ if (challenge) {
213
+ const nonce = await solvePow(challenge.challenge, challenge.difficulty);
214
+ pow = { challenge: challenge.challenge, nonce };
215
+ }
216
+ const body = {
217
+ ln_address: params.destination,
218
+ amount_msat: params.amountSat * 1e3
219
+ };
220
+ if (pow) body.pow = pow;
221
+ if (params.webhookUrl) body.webhook_url = params.webhookUrl;
222
+ if (params.webhookSecret) body.webhook_secret = params.webhookSecret;
223
+ const resp = await fetch(`${this.baseUrl}/api/v1/donations`, {
224
+ method: "POST",
225
+ headers: { "Content-Type": "application/json" },
226
+ body: JSON.stringify(body)
227
+ });
228
+ if (!resp.ok) {
229
+ const err = await resp.json().catch(() => ({ error: "unknown" }));
230
+ throw new Error(
231
+ `Create donation failed (${resp.status}): ${err.error || JSON.stringify(err)}`
232
+ );
233
+ }
234
+ const data = await resp.json();
235
+ return {
236
+ id: data.id,
237
+ bolt11: data.bolt11,
238
+ paymentHash: data.payment_hash
239
+ };
240
+ }
241
+ async getDonation(id) {
242
+ const resp = await fetch(`${this.baseUrl}/api/v1/donations/${id}`);
243
+ if (!resp.ok) {
244
+ const err = await resp.json().catch(() => ({ error: "unknown" }));
245
+ throw new Error(
246
+ `Get donation failed (${resp.status}): ${err.error || JSON.stringify(err)}`
247
+ );
248
+ }
249
+ return resp.json();
250
+ }
251
+ async listDonations(params) {
252
+ const query = new URLSearchParams();
253
+ if (params?.limit != null) query.set("limit", String(params.limit));
254
+ if (params?.offset != null) query.set("offset", String(params.offset));
255
+ const qs = query.toString();
256
+ const resp = await fetch(
257
+ `${this.baseUrl}/api/v1/donations${qs ? `?${qs}` : ""}`
258
+ );
259
+ if (!resp.ok) {
260
+ const err = await resp.json().catch(() => ({ error: "unknown" }));
261
+ throw new Error(
262
+ `List donations failed (${resp.status}): ${err.error || JSON.stringify(err)}`
263
+ );
264
+ }
265
+ const data = await resp.json();
266
+ return data.donations;
267
+ }
268
+ async estimateFees(amountSat) {
269
+ const resp = await fetch(
270
+ `${this.baseUrl}/api/v1/estimate-fees?amount_msat=${amountSat * 1e3}`
271
+ );
272
+ if (!resp.ok) {
273
+ const err = await resp.json().catch(() => ({ error: "unknown" }));
274
+ throw new Error(
275
+ `Fee estimate failed (${resp.status}): ${err.error || JSON.stringify(err)}`
276
+ );
277
+ }
278
+ return resp.json();
279
+ }
280
+ /** Subscribe via WS, resolve on terminal status. */
281
+ waitForPayment(donationId, options) {
282
+ return new Promise((resolve, reject) => {
283
+ const wsUrl = this.baseUrl.replace(/^http/, "ws") + `/ws/donations/${donationId}`;
284
+ const ws = new WebSocket(wsUrl);
285
+ const TERMINAL = /* @__PURE__ */ new Set(["success", "failed", "expired"]);
286
+ let settled = false;
287
+ let timer;
288
+ const settle = (fn) => {
289
+ if (!settled) {
290
+ settled = true;
291
+ clearTimeout(timer);
292
+ fn();
293
+ }
294
+ };
295
+ ws.addEventListener("message", (event) => {
296
+ const msg = JSON.parse(String(event.data));
297
+ const status = msg.status;
298
+ options?.onStatusChange?.(status);
299
+ if (TERMINAL.has(status)) {
300
+ settle(() => resolve(status));
301
+ ws.close();
302
+ }
303
+ });
304
+ ws.addEventListener("error", () => {
305
+ settle(() => reject(new Error("WebSocket connection failed")));
306
+ });
307
+ ws.addEventListener("close", () => {
308
+ settle(() => reject(new Error("WebSocket closed before terminal status")));
309
+ });
310
+ if (options?.timeoutMs) {
311
+ timer = setTimeout(() => {
312
+ ws.close();
313
+ settle(() => reject(new Error("waitForPayment timed out")));
314
+ }, options.timeoutMs);
315
+ }
316
+ });
317
+ }
318
+ };
319
+
320
+ // src/qr.ts
321
+ import { encode } from "uqr";
322
+
323
+ // src/lightning.ts
324
+ function isBolt12Offer(s) {
325
+ return s.startsWith("lno1");
326
+ }
327
+ function isLnAddress(s) {
328
+ const at = s.indexOf("@");
329
+ if (at < 1) return false;
330
+ const domain = s.slice(at + 1);
331
+ return domain.includes(".") && !domain.startsWith(".") && !domain.endsWith(".");
332
+ }
333
+ function detectDestinationType(s) {
334
+ if (isBolt12Offer(s)) return "bolt12Offer";
335
+ if (isLnAddress(s)) return "lnAddress";
336
+ return "unknown";
337
+ }
338
+
339
+ // src/qr.ts
340
+ var SAFE_COLOR = /^#[0-9a-fA-F]{3,8}$|^rgb\(\d|^rgba\(\d|^[a-z]+$/;
341
+ function invoiceToSvg(invoice, options) {
342
+ const size = options?.size ?? 256;
343
+ const color = options?.color ?? "#000";
344
+ if (!SAFE_COLOR.test(color)) throw new Error(`Invalid color: ${color}`);
345
+ const withPrefix = options?.withPrefix ?? true;
346
+ let content;
347
+ if (withPrefix && !isBolt12Offer(invoice)) {
348
+ content = `LIGHTNING:${invoice.toUpperCase()}`;
349
+ } else {
350
+ content = invoice;
351
+ }
352
+ const { data } = encode(content);
353
+ const modules = data.length;
354
+ const cellSize = size / modules;
355
+ let path = "";
356
+ for (let y = 0; y < modules; y++) {
357
+ for (let x = 0; x < modules; x++) {
358
+ if (data[y][x]) {
359
+ path += `M${x * cellSize},${y * cellSize}h${cellSize}v${cellSize}h-${cellSize}z`;
360
+ }
361
+ }
362
+ }
363
+ return [
364
+ `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${size} ${size}" width="${size}" height="${size}">`,
365
+ `<rect width="100%" height="100%" fill="white"/>`,
366
+ `<path d="${path}" fill="${color}"/>`,
367
+ `</svg>`
368
+ ].join("");
369
+ }
370
+ function invoiceToDataUrl(invoice, options) {
371
+ const svg = invoiceToSvg(invoice, options);
372
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
373
+ }
374
+
375
+ // src/webhook.ts
376
+ var _crypto;
377
+ async function getNodeCrypto2() {
378
+ if (_crypto) return _crypto;
379
+ if (_crypto === null) {
380
+ throw new Error(
381
+ "Webhook verification requires Node.js, Bun, or Deno (node:crypto not available)"
382
+ );
383
+ }
384
+ try {
385
+ _crypto = await import("crypto");
386
+ return _crypto;
387
+ } catch {
388
+ _crypto = null;
389
+ throw new Error(
390
+ "Webhook verification requires Node.js, Bun, or Deno (node:crypto not available)"
391
+ );
392
+ }
393
+ }
394
+ async function verifyWebhookSignature(body, signature, secret) {
395
+ const crypto2 = await getNodeCrypto2();
396
+ const expected = crypto2.createHmac("sha256", secret).update(body).digest();
397
+ try {
398
+ return crypto2.timingSafeEqual(expected, Buffer.from(signature, "hex"));
399
+ } catch {
400
+ return false;
401
+ }
402
+ }
403
+ async function parseWebhook(body, signature, secret) {
404
+ if (!await verifyWebhookSignature(body, signature, secret)) return null;
405
+ const text = typeof body === "string" ? body : new TextDecoder().decode(body);
406
+ return JSON.parse(text);
407
+ }
408
+ async function parseWebhookRequest(request, secret) {
409
+ const signature = request.headers.get("x-signature-256");
410
+ if (!signature) return null;
411
+ const body = await request.text();
412
+ return parseWebhook(body, signature, secret);
413
+ }
414
+ export {
415
+ RestGatewayClient,
416
+ detectDestinationType,
417
+ invoiceToDataUrl,
418
+ invoiceToSvg,
419
+ isBolt12Offer,
420
+ isLnAddress,
421
+ parseWebhook,
422
+ parseWebhookRequest,
423
+ solvePow,
424
+ verifyPow,
425
+ verifyWebhookSignature
426
+ };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "fulgur-bridge-client",
3
+ "version": "0.0.1",
4
+ "description": "JavaScript client for the Fulgur Bridge Lightning donation proxy. REST API + WebSocket with PoW anti-spam.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.5.0",
27
+ "tsup": "^8.0.0",
28
+ "typescript": "^5.5.0",
29
+ "vitest": "^2.0.0"
30
+ },
31
+ "keywords": [
32
+ "lightning",
33
+ "bitcoin",
34
+ "donation",
35
+ "bolt12",
36
+ "lnurl",
37
+ "proof-of-work",
38
+ "websocket"
39
+ ],
40
+ "license": "MIT",
41
+ "dependencies": {
42
+ "uqr": "^0.1.2"
43
+ }
44
+ }