thunder-bridge 1.1.0 → 1.3.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/server.cjs CHANGED
@@ -30,10 +30,19 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/server.ts
31
31
  var server_exports = {};
32
32
  __export(server_exports, {
33
+ askWallet: () => askWallet,
33
34
  blindLightningRail: () => blindLightningRail,
34
35
  invoiceFrom: () => invoiceFrom,
35
36
  lightningVerifyEndpoint: () => lightningVerifyEndpoint,
36
37
  lnurlPayEndpoint: () => lnurlPayEndpoint,
38
+ nwcConnection: () => nwcConnection,
39
+ nwcHoldInvoice: () => nwcHoldInvoice,
40
+ nwcInvoice: () => nwcInvoice,
41
+ nwcPay: () => nwcPay,
42
+ nwcRail: () => nwcRail,
43
+ nwcSettlement: () => nwcSettlement,
44
+ nwcVerifyEndpoint: () => nwcVerifyEndpoint,
45
+ nwcVerifyUrl: () => nwcVerifyUrl,
37
46
  relayedVerifyUrl: () => relayedVerifyUrl
38
47
  });
39
48
  module.exports = __toCommonJS(server_exports);
@@ -41,7 +50,9 @@ module.exports = __toCommonJS(server_exports);
41
50
  // ../core/bytes.ts
42
51
  function bytesToHex(bytes) {
43
52
  let hex = "";
44
- for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
53
+ for (const byte of bytes) {
54
+ hex += byte.toString(16).padStart(2, "0");
55
+ }
45
56
  return hex;
46
57
  }
47
58
  function hexToBytes(hex) {
@@ -55,29 +66,6 @@ function isHex(text2) {
55
66
  return /^[0-9a-f]+$/i.test(text2) && text2.length % 2 === 0;
56
67
  }
57
68
 
58
- // ../core/hmac.ts
59
- async function hmacHex(secret, payload) {
60
- const raw = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
61
- const key = await crypto.subtle.importKey(
62
- "raw",
63
- asBuffer(raw),
64
- { name: "HMAC", hash: "SHA-256" },
65
- false,
66
- ["sign"]
67
- );
68
- const message = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
69
- return bytesToHex(new Uint8Array(await crypto.subtle.sign("HMAC", key, asBuffer(message))));
70
- }
71
- function equalInConstantTime(one, other) {
72
- if (one.length !== other.length) return false;
73
- let difference = 0;
74
- for (let at = 0; at < one.length; at++) difference |= one.charCodeAt(at) ^ other.charCodeAt(at);
75
- return difference === 0;
76
- }
77
- function asBuffer(bytes) {
78
- return bytes instanceof Uint8Array && bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
79
- }
80
-
81
69
  // ../core/sha256.ts
82
70
  var ROUND_CONSTANTS = new Uint32Array([
83
71
  1116352408,
@@ -159,11 +147,11 @@ var BLOCK_BYTES = 64;
159
147
  var LENGTH_BYTES = 8;
160
148
  var SCHEDULE_WORDS = 64;
161
149
  function sha256(bytes) {
162
- const padded = withPadding(bytes);
163
- const reader = new DataView(padded.buffer);
150
+ const padded2 = withPadding(bytes);
151
+ const reader = new DataView(padded2.buffer);
164
152
  const state = Uint32Array.from(INITIAL_STATE);
165
153
  const schedule = new Uint32Array(SCHEDULE_WORDS);
166
- for (let block = 0; block < padded.length; block += BLOCK_BYTES) {
154
+ for (let block = 0; block < padded2.length; block += BLOCK_BYTES) {
167
155
  fillSchedule(schedule, reader, block);
168
156
  compress(state, schedule);
169
157
  }
@@ -173,16 +161,18 @@ function sha256Hex(text2) {
173
161
  return bytesToHex(sha256(new TextEncoder().encode(text2)));
174
162
  }
175
163
  function withPadding(bytes) {
176
- const padded = new Uint8Array(
164
+ const padded2 = new Uint8Array(
177
165
  bytes.length + 1 + LENGTH_BYTES + BLOCK_BYTES - 1 & ~(BLOCK_BYTES - 1)
178
166
  );
179
- padded.set(bytes);
180
- padded[bytes.length] = 128;
181
- new DataView(padded.buffer).setUint32(padded.length - 4, bytes.length * 8, false);
182
- return padded;
167
+ padded2.set(bytes);
168
+ padded2[bytes.length] = 128;
169
+ new DataView(padded2.buffer).setUint32(padded2.length - 4, bytes.length * 8, false);
170
+ return padded2;
183
171
  }
184
172
  function fillSchedule(schedule, reader, block) {
185
- for (let at = 0; at < 16; at++) schedule[at] = reader.getUint32(block + at * 4, false);
173
+ for (let at = 0; at < 16; at++) {
174
+ schedule[at] = reader.getUint32(block + at * 4, false);
175
+ }
186
176
  for (let at = 16; at < SCHEDULE_WORDS; at++) {
187
177
  const recent = schedule[at - 15];
188
178
  const distant = schedule[at - 2];
@@ -216,12 +206,16 @@ function compress(state, schedule) {
216
206
  a = carried + (mixedA + majority | 0) | 0;
217
207
  }
218
208
  const rolling = [a, b, c, d, e, f, g, h];
219
- for (let at = 0; at < state.length; at++) state[at] = state[at] + rolling[at] | 0;
209
+ for (let at = 0; at < state.length; at++) {
210
+ state[at] = state[at] + rolling[at] | 0;
211
+ }
220
212
  }
221
213
  function asBytes(state) {
222
214
  const digest = new Uint8Array(state.length * 4);
223
215
  const writer = new DataView(digest.buffer);
224
- for (let at = 0; at < state.length; at++) writer.setUint32(at * 4, state[at], false);
216
+ for (let at = 0; at < state.length; at++) {
217
+ writer.setUint32(at * 4, state[at], false);
218
+ }
225
219
  return digest;
226
220
  }
227
221
  function rotate(value, by) {
@@ -262,20 +256,30 @@ function decodeInvoice(bolt11) {
262
256
  };
263
257
  }
264
258
  function preimageMatchesHash(preimage, paymentHash) {
265
- if (!isHex(preimage)) return false;
259
+ if (!isHex(preimage)) {
260
+ return false;
261
+ }
266
262
  return bytesToHex(sha256(hexToBytes(preimage))) === paymentHash.toLowerCase();
267
263
  }
268
264
  function splitBech32(bolt11) {
269
265
  const lower = bolt11.toLowerCase();
270
- if (isBolt12Encoding(lower)) return null;
266
+ if (isBolt12Encoding(lower)) {
267
+ return null;
268
+ }
271
269
  const separator = lower.lastIndexOf("1");
272
- if (separator < 1) return null;
270
+ if (separator < 1) {
271
+ return null;
272
+ }
273
273
  const dataChars = lower.slice(separator + 1);
274
- if (dataChars.length < CHECKSUM_WORDS) return null;
274
+ if (dataChars.length < CHECKSUM_WORDS) {
275
+ return null;
276
+ }
275
277
  const words = [];
276
278
  for (const char of dataChars) {
277
279
  const value = BECH32_CHARSET.indexOf(char);
278
- if (value === -1) return null;
280
+ if (value === -1) {
281
+ return null;
282
+ }
279
283
  words.push(value);
280
284
  }
281
285
  return { hrp: lower.slice(0, separator), words: words.slice(0, -CHECKSUM_WORDS) };
@@ -291,25 +295,35 @@ function taggedFields(words) {
291
295
  const lengthWords = words[cursor + 1] * 32 + words[cursor + 2];
292
296
  const dataStart = cursor + 1 + TAGGED_FIELD_LENGTH_WORDS;
293
297
  const dataEnd = dataStart + lengthWords;
294
- if (dataEnd > signatureStart) return fields;
295
- if (!fields.has(words[cursor])) fields.set(words[cursor], words.slice(dataStart, dataEnd));
298
+ if (dataEnd > signatureStart) {
299
+ return fields;
300
+ }
301
+ if (!fields.has(words[cursor])) {
302
+ fields.set(words[cursor], words.slice(dataStart, dataEnd));
303
+ }
296
304
  cursor = dataEnd;
297
305
  }
298
306
  return fields;
299
307
  }
300
308
  function hexTag(fields, tag, expectedWords) {
301
309
  const words = fields.get(tag);
302
- if (words === void 0 || words.length !== expectedWords) return null;
310
+ if (words === void 0 || words.length !== expectedWords) {
311
+ return null;
312
+ }
303
313
  return bytesToHex(wordsToBytes(words));
304
314
  }
305
315
  function expiryOf(words, fields) {
306
- if (words.length < TIMESTAMP_WORDS + SIGNATURE_WORDS) return null;
316
+ if (words.length < TIMESTAMP_WORDS + SIGNATURE_WORDS) {
317
+ return null;
318
+ }
307
319
  const expiry = fields.get(EXPIRY_TAG);
308
320
  return readNumber(words.slice(0, TIMESTAMP_WORDS)) + (expiry ? readNumber(expiry) : DEFAULT_EXPIRY_SECS);
309
321
  }
310
322
  function amountFromHrp(hrp) {
311
323
  const match = HRP_AMOUNT.exec(hrp);
312
- if (match === null) return null;
324
+ if (match === null) {
325
+ return null;
326
+ }
313
327
  const [, digits, multiplier] = match;
314
328
  return multiplier ? Number(digits) * HRP_MULTIPLIER_MSAT[multiplier] : Number(digits) * MSAT_PER_BTC;
315
329
  }
@@ -331,172 +345,751 @@ function wordsToBytes(words) {
331
345
  return Uint8Array.from(bytes);
332
346
  }
333
347
 
334
- // ../core/url.ts
335
- var PRIVATE_SUFFIXES = ["localhost", "local", "internal", "lan", "arpa", "test", "invalid"];
336
- function publicHttps(raw) {
337
- let url;
348
+ // ../core/refusal.ts
349
+ var WalletRefused = class extends Error {
350
+ reason;
351
+ constructor(reason, message) {
352
+ super(message);
353
+ this.reason = reason;
354
+ }
355
+ };
356
+ var NoWalletAvailable = class extends Error {
357
+ wallets;
358
+ constructor(wallets) {
359
+ super(wallets.map((wallet) => `${wallet.address} ${wallet.reason}`).join(", "));
360
+ this.wallets = wallets;
361
+ }
362
+ };
363
+
364
+ // ../core/sealed.ts
365
+ var VERSION = "v1";
366
+ var IV_BYTES = 12;
367
+ var MIN_SECRET_CHARS = 32;
368
+ var MAX_PLAIN_BYTES = 3e3;
369
+ var INFO = new TextEncoder().encode("thunder-bridge/sealed");
370
+ async function seal(secret, plaintext) {
371
+ const body = new TextEncoder().encode(plaintext);
372
+ if (body.length > MAX_PLAIN_BYTES) {
373
+ throw new Error(`sealed takes at most ${MAX_PLAIN_BYTES} bytes, this was ${body.length}`);
374
+ }
375
+ const key = await keyFor(secret);
376
+ const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
377
+ const cipher = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, body));
378
+ const joined2 = new Uint8Array(iv.length + cipher.length);
379
+ joined2.set(iv);
380
+ joined2.set(cipher, iv.length);
381
+ return `${VERSION}.${toBase64Url(joined2)}`;
382
+ }
383
+ async function unseal(secret, sealed) {
384
+ const key = await keyFor(secret);
385
+ if (!sealed.startsWith(`${VERSION}.`)) {
386
+ return null;
387
+ }
388
+ const joined2 = fromBase64Url(sealed.slice(VERSION.length + 1));
389
+ if (joined2 === null || joined2.length <= IV_BYTES) {
390
+ return null;
391
+ }
338
392
  try {
339
- url = new URL(raw);
393
+ const body = await crypto.subtle.decrypt(
394
+ { name: "AES-GCM", iv: joined2.slice(0, IV_BYTES) },
395
+ key,
396
+ joined2.slice(IV_BYTES)
397
+ );
398
+ return new TextDecoder().decode(body);
340
399
  } catch {
341
- return false;
400
+ return null;
342
401
  }
343
- return url.protocol === "https:" && hostIsPublic(url.hostname.toLowerCase());
344
402
  }
345
- function sameOrigin(one, other) {
403
+ async function keyFor(secret) {
404
+ if (secret.length < MIN_SECRET_CHARS) {
405
+ throw new Error(`the sealing secret needs ${MIN_SECRET_CHARS} characters of randomness`);
406
+ }
407
+ const material = await crypto.subtle.importKey(
408
+ "raw",
409
+ new TextEncoder().encode(secret),
410
+ "HKDF",
411
+ false,
412
+ ["deriveKey"]
413
+ );
414
+ return crypto.subtle.deriveKey(
415
+ { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: INFO },
416
+ material,
417
+ { name: "AES-GCM", length: 256 },
418
+ false,
419
+ ["encrypt", "decrypt"]
420
+ );
421
+ }
422
+ function toBase64Url(bytes) {
423
+ let binary = "";
424
+ for (const byte of bytes) {
425
+ binary += String.fromCharCode(byte);
426
+ }
427
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
428
+ }
429
+ function fromBase64Url(text2) {
430
+ if (text2.length === 0 || !/^[A-Za-z0-9_-]+$/.test(text2)) {
431
+ return null;
432
+ }
346
433
  try {
347
- return new URL(one).origin === new URL(other).origin;
434
+ const binary = atob(text2.replace(/-/g, "+").replace(/_/g, "/"));
435
+ const bytes = new Uint8Array(binary.length);
436
+ for (let at = 0; at < bytes.length; at++) {
437
+ bytes[at] = binary.charCodeAt(at);
438
+ }
439
+ return bytes;
348
440
  } catch {
349
- return false;
441
+ return null;
350
442
  }
351
443
  }
352
- function publicAddress(literal) {
353
- return literal.includes(":") ? ipv6IsGlobal(literal) : ipv4IsGlobal(literal);
444
+
445
+ // src/nostr.ts
446
+ var import_chacha = require("@noble/ciphers/chacha.js");
447
+ var import_secp256k1 = require("@noble/curves/secp256k1.js");
448
+ var import_hkdf = require("@noble/hashes/hkdf.js");
449
+ var import_hmac = require("@noble/hashes/hmac.js");
450
+ var import_sha2 = require("@noble/hashes/sha2.js");
451
+ var NIP44_SALT = new TextEncoder().encode("nip44-v2");
452
+ var NIP44_VERSION = 2;
453
+ var NONCE_BYTES = 32;
454
+ var MAC_BYTES = 32;
455
+ var MESSAGE_KEYS_BYTES = 76;
456
+ var CHACHA_KEY_END = 32;
457
+ var CHACHA_NONCE_END = 44;
458
+ var LENGTH_PREFIX_BYTES = 2;
459
+ var SMALLEST_PAD = 32;
460
+ var CHUNKS_STAY_SMALL_TO = 256;
461
+ var MAX_PLAINTEXT_BYTES = 65535;
462
+ function publicKeyFor(privateKeyHex) {
463
+ return bytesToHex(import_secp256k1.schnorr.getPublicKey(hexToBytes(privateKeyHex)));
464
+ }
465
+ function conversationKeyFor(privateKeyHex, publicKeyHex) {
466
+ const shared = import_secp256k1.secp256k1.getSharedSecret(
467
+ hexToBytes(privateKeyHex),
468
+ hexToBytes(`02${publicKeyHex}`)
469
+ );
470
+ return (0, import_hkdf.extract)(import_sha2.sha256, shared.subarray(1, 33), NIP44_SALT);
354
471
  }
355
- function hostIsPublic(host) {
356
- if (host.startsWith("[")) return publicAddress(host.slice(1, -1));
357
- const name = host.replace(/\.$/, "");
358
- if (/^\d+\.\d+\.\d+\.\d+$/.test(name)) return publicAddress(name);
359
- const labels = name.split(".");
360
- if (labels.length < 2 || labels.some((label) => label === "")) return false;
361
- return !PRIVATE_SUFFIXES.includes(labels[labels.length - 1]);
472
+ function signEvent(privateKeyHex, draft) {
473
+ const pubkey = publicKeyFor(privateKeyHex);
474
+ const id = idOf(pubkey, draft);
475
+ return {
476
+ id,
477
+ pubkey,
478
+ created_at: draft.created_at,
479
+ kind: draft.kind,
480
+ tags: draft.tags,
481
+ content: draft.content,
482
+ sig: bytesToHex(import_secp256k1.schnorr.sign(hexToBytes(id), hexToBytes(privateKeyHex)))
483
+ };
362
484
  }
363
- function ipv4IsGlobal(literal) {
364
- const parts = literal.split(".").map(Number);
365
- if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
485
+ function verifyEvent(event) {
486
+ try {
487
+ return idOf(event.pubkey, event) === event.id && import_secp256k1.schnorr.verify(hexToBytes(event.sig), hexToBytes(event.id), hexToBytes(event.pubkey));
488
+ } catch {
366
489
  return false;
367
490
  }
368
- const [a, b] = parts;
369
- if (a === 0 || a === 10 || a === 127 || a >= 240) return false;
370
- if (a === 100 && b >= 64 && b < 128) return false;
371
- if (a === 169 && b === 254) return false;
372
- if (a === 172 && b >= 16 && b < 32) return false;
373
- if (a === 192 && b === 168) return false;
374
- return true;
375
491
  }
376
- function ipv6IsGlobal(literal) {
377
- const groups = ipv6Groups(literal.toLowerCase());
378
- if (groups.length !== 8 || groups.some(Number.isNaN)) return false;
379
- const leading = groups.slice(0, 5).every((group) => group === 0);
380
- if (leading && groups[5] === 0 && groups[6] === 0 && groups[7] <= 1) return false;
381
- if (leading && groups[5] === 65535) {
382
- const [high, low] = [groups[6], groups[7]];
383
- return ipv4IsGlobal(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
384
- }
385
- const first = groups[0];
386
- if ((first & 65024) === 64512) return false;
387
- if ((first & 65472) === 65152) return false;
388
- return true;
492
+ function idOf(pubkey, draft) {
493
+ return bytesToHex(
494
+ (0, import_sha2.sha256)(
495
+ new TextEncoder().encode(
496
+ JSON.stringify([0, pubkey, draft.created_at, draft.kind, draft.tags, draft.content])
497
+ )
498
+ )
499
+ );
389
500
  }
390
- function ipv6Groups(address) {
391
- const [head = "", tail] = address.split("::");
392
- const left = head.split(":").filter((group) => group !== "");
393
- const right = (tail ?? "").split(":").filter((group) => group !== "");
394
- const gap = tail === void 0 ? [] : Array(8 - left.length - right.length).fill("0");
395
- return [...left, ...gap, ...right].map((group) => parseInt(group, 16));
501
+ function encryptNip44(conversationKey, plaintext) {
502
+ return sealNip44(conversationKey, plaintext, crypto.getRandomValues(new Uint8Array(NONCE_BYTES)));
396
503
  }
397
-
398
- // ../core/outbound.ts
399
- var HTTP_TIMEOUT_MS = 15e3;
400
- var LOOKUP_TIMEOUT_MS = 5e3;
401
- var HOPS_FOLLOWED = 2;
402
- var REDIRECTS = [301, 302, 303, 307, 308];
403
- var KEEPS_THE_METHOD = [307, 308];
404
- var CREDENTIALS = ["authorization", "cookie", "proxy-authorization"];
405
- var BODY_LIMIT_BYTES = 262144;
406
- var throughFetch = (url, sent, signal) => fetch(url, {
407
- method: sent.method ?? "GET",
408
- headers: sent.headers ?? {},
409
- body: sent.body,
410
- redirect: "manual",
411
- signal
412
- });
413
- var send = throughFetch;
414
- async function ask(url, sent = {}) {
415
- const capped = AbortSignal.timeout(HTTP_TIMEOUT_MS);
416
- const signal = sent.deadline ? AbortSignal.any([sent.deadline, capped]) : capped;
417
- let target = url;
418
- let carried = sent;
419
- for (let hop = 0; hop <= HOPS_FOLLOWED; hop += 1) {
420
- const at = await addressesToReach(target);
421
- const response = await send(target, carried, signal, at);
422
- if (!REDIRECTS.includes(response.status)) return await answerOf(response);
423
- const location = response.headers.get("location");
424
- if (location === null) {
425
- throw new Error(`${target} answered ${response.status} without saying where to`);
426
- }
427
- await response.body?.cancel();
428
- const next = new URL(location, target).toString();
429
- carried = KEEPS_THE_METHOD.includes(response.status) ? carried : withoutBody(carried);
430
- carried = sameOrigin(target, next) ? carried : withoutCredentials(carried);
431
- target = next;
504
+ function sealNip44(conversationKey, plaintext, nonce) {
505
+ const keys = messageKeysFrom(conversationKey, nonce);
506
+ const ciphertext = (0, import_chacha.chacha20)(keys.chachaKey, keys.chachaNonce, padded(plaintext));
507
+ const mac = (0, import_hmac.hmac)(import_sha2.sha256, keys.hmacKey, joined(nonce, ciphertext));
508
+ return toBase64(joined(Uint8Array.of(NIP44_VERSION), nonce, ciphertext, mac));
509
+ }
510
+ function decryptNip44(conversationKey, payload) {
511
+ if (payload.startsWith("#")) {
512
+ throw new Error("nip44 payload announces an encryption version we do not speak");
432
513
  }
433
- throw new Error(`${url} redirected more than ${HOPS_FOLLOWED} times`);
514
+ const raw = fromBase64(payload);
515
+ if (raw.length < 1 + NONCE_BYTES + MAC_BYTES) {
516
+ throw new Error(`nip44 payload is ${raw.length} bytes, too short to hold a nonce and a mac`);
517
+ }
518
+ if (raw[0] !== NIP44_VERSION) {
519
+ throw new Error(`nip44 payload is version ${raw[0]}, not ${NIP44_VERSION}`);
520
+ }
521
+ const nonce = raw.subarray(1, 1 + NONCE_BYTES);
522
+ const ciphertext = raw.subarray(1 + NONCE_BYTES, raw.length - MAC_BYTES);
523
+ const mac = raw.subarray(raw.length - MAC_BYTES);
524
+ const keys = messageKeysFrom(conversationKey, nonce);
525
+ if (!sameBytes((0, import_hmac.hmac)(import_sha2.sha256, keys.hmacKey, joined(nonce, ciphertext)), mac)) {
526
+ throw new Error("nip44 payload failed its mac, so it was not written with this key");
527
+ }
528
+ return unpadded((0, import_chacha.chacha20)(keys.chachaKey, keys.chachaNonce, ciphertext));
434
529
  }
435
- async function addressesToReach(url) {
436
- if (!publicHttps(url)) throw new Error(`${url} is not a public https URL`);
437
- const found = await resolved(url);
438
- if (found.length === 0 || !found.every((one) => publicAddress(one.address))) {
439
- throw new Error(`${url} resolves to an address we do not reach`);
530
+ function messageKeysFrom(conversationKey, nonce) {
531
+ if (conversationKey.length !== NONCE_BYTES || nonce.length !== NONCE_BYTES) {
532
+ throw new Error("a nip44 conversation key and nonce are both 32 bytes");
440
533
  }
441
- return found;
534
+ const keys = (0, import_hkdf.expand)(import_sha2.sha256, conversationKey, nonce, MESSAGE_KEYS_BYTES);
535
+ return {
536
+ chachaKey: keys.subarray(0, CHACHA_KEY_END),
537
+ chachaNonce: keys.subarray(CHACHA_KEY_END, CHACHA_NONCE_END),
538
+ hmacKey: keys.subarray(CHACHA_NONCE_END)
539
+ };
442
540
  }
443
- async function resolved(url) {
444
- const { lookup } = await import("dns/promises");
445
- const { setTimeout: sleep } = await import("timers/promises");
446
- const host = new URL(url).hostname.replace(/^\[|]$/g, "");
447
- const found = await Promise.race([
448
- lookup(host, { all: true, verbatim: true }).catch(() => []),
449
- sleep(LOOKUP_TIMEOUT_MS, null, { ref: false })
450
- ]);
451
- return found ?? [];
541
+ function paddedLengthFor(unpadded2) {
542
+ if (unpadded2 <= SMALLEST_PAD) {
543
+ return SMALLEST_PAD;
544
+ }
545
+ const nextPower = 1 << Math.floor(Math.log2(unpadded2 - 1)) + 1;
546
+ const chunk = nextPower <= CHUNKS_STAY_SMALL_TO ? SMALLEST_PAD : nextPower / 8;
547
+ return chunk * (Math.floor((unpadded2 - 1) / chunk) + 1);
452
548
  }
453
- async function answerOf(response) {
454
- const { status, ok, headers } = response;
455
- const reader = response.body?.getReader();
456
- if (!reader) return { status, ok, body: "", truncated: false, headers };
457
- const read = [];
458
- let bytes = 0;
459
- while (bytes <= BODY_LIMIT_BYTES) {
460
- const { done, value } = await reader.read();
461
- if (done) return { status, ok, body: text(read), truncated: false, headers };
462
- read.push(value);
463
- bytes += value.length;
549
+ function padded(plaintext) {
550
+ const body = new TextEncoder().encode(plaintext);
551
+ if (body.length === 0 || body.length > MAX_PLAINTEXT_BYTES) {
552
+ throw new Error(`nip44 takes 1 to ${MAX_PLAINTEXT_BYTES} bytes, this was ${body.length}`);
464
553
  }
465
- await reader.cancel();
466
- return { status, ok, body: text(read), truncated: true, headers };
554
+ const buffer = new Uint8Array(LENGTH_PREFIX_BYTES + paddedLengthFor(body.length));
555
+ buffer.set(Uint8Array.of(body.length >>> 8, body.length & 255));
556
+ buffer.set(body, LENGTH_PREFIX_BYTES);
557
+ return buffer;
558
+ }
559
+ function unpadded(buffer) {
560
+ const length = (buffer[0] ?? 0) << 8 | (buffer[1] ?? 0);
561
+ if (length === 0 || buffer.length !== LENGTH_PREFIX_BYTES + paddedLengthFor(length)) {
562
+ throw new Error("nip44 plaintext does not match the length its own padding declares");
563
+ }
564
+ return new TextDecoder().decode(
565
+ buffer.subarray(LENGTH_PREFIX_BYTES, LENGTH_PREFIX_BYTES + length)
566
+ );
467
567
  }
468
- function withoutBody(sent) {
469
- const headers = { ...sent.headers };
470
- delete headers["content-type"];
471
- return { ...sent, method: "GET", headers, body: void 0 };
568
+ function joined(...parts) {
569
+ const whole = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
570
+ let at = 0;
571
+ for (const part of parts) {
572
+ whole.set(part, at);
573
+ at += part.length;
574
+ }
575
+ return whole;
472
576
  }
473
- function withoutCredentials(sent) {
474
- const headers = { ...sent.headers };
475
- for (const named of CREDENTIALS) delete headers[named];
476
- return { ...sent, headers };
577
+ function sameBytes(left, right) {
578
+ let different = left.length ^ right.length;
579
+ for (let at = 0; at < left.length && at < right.length; at++) {
580
+ different |= (left[at] ?? 0) ^ (right[at] ?? 0);
581
+ }
582
+ return different === 0;
477
583
  }
478
- function text(chunks) {
479
- const decoder = new TextDecoder();
480
- let decoded = "";
481
- for (const chunk of chunks) decoded += decoder.decode(chunk, { stream: true });
482
- return decoded + decoder.decode();
584
+ function toBase64(bytes) {
585
+ let binary = "";
586
+ for (const byte of bytes) {
587
+ binary += String.fromCharCode(byte);
588
+ }
589
+ return btoa(binary);
483
590
  }
484
-
485
- // ../core/refusal.ts
486
- var WalletRefused = class extends Error {
487
- reason;
488
- constructor(reason, message) {
489
- super(message);
490
- this.reason = reason;
591
+ function fromBase64(text2) {
592
+ const binary = atob(text2);
593
+ const bytes = new Uint8Array(binary.length);
594
+ for (let at = 0; at < bytes.length; at++) {
595
+ bytes[at] = binary.charCodeAt(at);
491
596
  }
492
- };
493
- var NoWalletAvailable = class extends Error {
494
- wallets;
495
- constructor(wallets) {
496
- super(wallets.map((wallet) => `${wallet.address} ${wallet.reason}`).join(", "));
497
- this.wallets = wallets;
597
+ return bytes;
598
+ }
599
+
600
+ // ../core/ed25519.ts
601
+ var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
602
+
603
+ // src/webhook.ts
604
+ var VERIFY_CHALLENGE = "verify-challenge";
605
+ function nonceOf(text2, type) {
606
+ try {
607
+ const said = JSON.parse(text2);
608
+ if (said["type"] !== type || typeof said["nonce"] !== "string") {
609
+ return null;
610
+ }
611
+ return said["nonce"];
612
+ } catch {
613
+ return null;
498
614
  }
615
+ }
616
+ function answerVerifyChallenge(body) {
617
+ const nonce = nonceOf(body, VERIFY_CHALLENGE);
618
+ return nonce === null ? null : JSON.stringify({ nonce });
619
+ }
620
+ async function answerVerifyChallengeRequest(request) {
621
+ if (request.method !== "POST") {
622
+ return null;
623
+ }
624
+ const answer = answerVerifyChallenge(await request.clone().text());
625
+ return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
626
+ }
627
+
628
+ // src/nwc.ts
629
+ var REQUEST_KIND = 23194;
630
+ var RESPONSE_KIND = 23195;
631
+ var NIP44_V2 = "nip44_v2";
632
+ var SCHEME = "nostr+walletconnect:";
633
+ var HEX_32_BYTES = /^[0-9a-f]{64}$/i;
634
+ var DEFAULT_ASK_TIMEOUT_MS = 1e4;
635
+ var PAY_TIMEOUT_MS = 9e4;
636
+ var DEFAULT_POLL_EVERY_SECS = 5;
637
+ var HASH = "h";
638
+ var REFUSAL_BY_CODE = {
639
+ INSUFFICIENT_BALANCE: "amount-not-accepted",
640
+ QUOTA_EXCEEDED: "amount-not-accepted",
641
+ RESTRICTED: "address-unusable",
642
+ UNAUTHORIZED: "address-unusable",
643
+ UNSUPPORTED_ENCRYPTION: "address-unusable"
499
644
  };
645
+ function nwcConnection(uri) {
646
+ const parsed = parsedUri(uri);
647
+ const walletPubkey = parsed.pathname.replace(/^\/+/, "") || parsed.host;
648
+ const secret = parsed.searchParams.get("secret") ?? "";
649
+ const relays = parsed.searchParams.getAll("relay");
650
+ if (!HEX_32_BYTES.test(walletPubkey)) {
651
+ throw new WalletRefused("address-unusable", "an nwc uri names a 32-byte wallet pubkey");
652
+ }
653
+ if (!HEX_32_BYTES.test(secret)) {
654
+ throw new WalletRefused("address-unusable", "an nwc uri carries a 32-byte hex secret");
655
+ }
656
+ if (relays.length === 0 || !relays.every(isSecureRelay)) {
657
+ throw new WalletRefused("address-unusable", "every nwc relay has to be a wss URL");
658
+ }
659
+ return { walletPubkey: walletPubkey.toLowerCase(), relays, secret: secret.toLowerCase() };
660
+ }
661
+ async function nwcInvoice(connection, amountMsat, description, timeoutMs = DEFAULT_ASK_TIMEOUT_MS) {
662
+ const answered2 = await askWallet(
663
+ connection,
664
+ "make_invoice",
665
+ { amount: amountMsat, description },
666
+ timeoutMs
667
+ );
668
+ const bolt11 = String(answered2.invoice ?? "");
669
+ const issued = decodeInvoice(bolt11);
670
+ if (issued.paymentHash === null || issued.expiresAt === null) {
671
+ throw new WalletRefused("invoice-refused", "the nwc wallet issued an invoice we cannot decode");
672
+ }
673
+ if (issued.amountMsat !== amountMsat) {
674
+ throw new WalletRefused(
675
+ "invoice-refused",
676
+ `the nwc wallet issued an invoice for ${issued.amountMsat} msat, not ${amountMsat}`
677
+ );
678
+ }
679
+ return { bolt11, paymentHash: issued.paymentHash, expiresAt: issued.expiresAt };
680
+ }
681
+ async function nwcHoldInvoice(connection, held, timeoutMs = DEFAULT_ASK_TIMEOUT_MS) {
682
+ const answered2 = await askWallet(
683
+ connection,
684
+ "make_hold_invoice",
685
+ {
686
+ amount: held.amountMsat,
687
+ description: held.description,
688
+ payment_hash: held.paymentHash,
689
+ expiry: held.expirySecs,
690
+ ...held.minCltvExpiryDelta === void 0 ? {} : { min_cltv_expiry_delta: held.minCltvExpiryDelta }
691
+ },
692
+ timeoutMs
693
+ );
694
+ const bolt11 = String(answered2.invoice ?? "");
695
+ const issued = decodeInvoice(bolt11);
696
+ if (issued.paymentHash === null || issued.expiresAt === null) {
697
+ throw new WalletRefused("invoice-refused", "the nwc wallet held an invoice we cannot decode");
698
+ }
699
+ if (issued.paymentHash.toLowerCase() !== held.paymentHash.toLowerCase()) {
700
+ throw new WalletRefused(
701
+ "invoice-refused",
702
+ `the nwc wallet held ${issued.paymentHash} rather than the ${held.paymentHash} asked for`
703
+ );
704
+ }
705
+ if (issued.amountMsat !== held.amountMsat) {
706
+ throw new WalletRefused(
707
+ "invoice-refused",
708
+ `the nwc wallet held an invoice for ${issued.amountMsat} msat, not ${held.amountMsat}`
709
+ );
710
+ }
711
+ return { bolt11, paymentHash: issued.paymentHash, expiresAt: issued.expiresAt };
712
+ }
713
+ async function nwcSettlement(connection, paymentHash, timeoutMs = DEFAULT_ASK_TIMEOUT_MS) {
714
+ const answered2 = await askWallet(
715
+ connection,
716
+ "lookup_invoice",
717
+ { payment_hash: paymentHash },
718
+ timeoutMs
719
+ );
720
+ const preimage = answered2.preimage;
721
+ if (typeof preimage !== "string" || preimage.length === 0) {
722
+ return null;
723
+ }
724
+ if (!preimageMatchesHash(preimage, paymentHash)) {
725
+ throw new Error(`the nwc wallet returned a preimage that does not hash to ${paymentHash}`);
726
+ }
727
+ return preimage;
728
+ }
729
+ async function nwcPay(connection, bolt11, timeoutMs = PAY_TIMEOUT_MS) {
730
+ const owed = decodeInvoice(bolt11);
731
+ if (owed.paymentHash === null) {
732
+ throw new WalletRefused("invoice-refused", "the invoice to pay carries no payment hash");
733
+ }
734
+ const answered2 = await askWallet(connection, "pay_invoice", { invoice: bolt11 }, timeoutMs);
735
+ const preimage = answered2.preimage;
736
+ if (typeof preimage !== "string" || !preimageMatchesHash(preimage, owed.paymentHash)) {
737
+ throw new Error(`the nwc wallet paid and returned no preimage hashing to ${owed.paymentHash}`);
738
+ }
739
+ return preimage;
740
+ }
741
+ function nwcVerifyEndpoint(config) {
742
+ const paced = {
743
+ "cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
744
+ };
745
+ return async (request) => {
746
+ const consented = await answerVerifyChallengeRequest(request);
747
+ if (consented !== null) {
748
+ return consented;
749
+ }
750
+ const sealed = new URL(request.url).searchParams.get(HASH);
751
+ if (sealed === null) {
752
+ return Response.json({ settled: false }, { status: 400 });
753
+ }
754
+ const paymentHash = await unseal(config.secret, sealed);
755
+ if (paymentHash === null) {
756
+ return Response.json({ settled: false }, { status: 403 });
757
+ }
758
+ const preimage = await nwcSettlement(config.connection, paymentHash, config.askTimeoutMs).catch(
759
+ () => void 0
760
+ );
761
+ if (preimage === void 0) {
762
+ return Response.json({ settled: false }, { status: 502 });
763
+ }
764
+ return Response.json({ settled: preimage !== null, preimage }, { headers: paced });
765
+ };
766
+ }
767
+ async function nwcVerifyUrl(endpoint, paymentHash, secret) {
768
+ const sealed = new URL(endpoint);
769
+ sealed.searchParams.set(HASH, await seal(secret, paymentHash));
770
+ return sealed.toString();
771
+ }
772
+ async function askWallet(connection, method, params, timeoutMs = DEFAULT_ASK_TIMEOUT_MS) {
773
+ const key = conversationKeyFor(connection.secret, connection.walletPubkey);
774
+ const request = signEvent(connection.secret, {
775
+ kind: REQUEST_KIND,
776
+ tags: [
777
+ ["p", connection.walletPubkey],
778
+ ["encryption", NIP44_V2]
779
+ ],
780
+ content: encryptNip44(key, JSON.stringify({ method, params })),
781
+ created_at: Math.floor(Date.now() / 1e3)
782
+ });
783
+ const answer = await overFirstRelayThatAnswers(connection, request, timeoutMs);
784
+ return resultOf(JSON.parse(decryptNip44(key, answer.content)), method);
785
+ }
786
+ function resultOf(said, method) {
787
+ const failure = said.error;
788
+ if (failure) {
789
+ throw new WalletRefused(
790
+ REFUSAL_BY_CODE[failure.code ?? ""] ?? "invoice-refused",
791
+ `the nwc wallet refused ${method}: ${failure.code ?? "unknown"} ${failure.message ?? ""}`.trim()
792
+ );
793
+ }
794
+ const result = said.result;
795
+ if (result === null || typeof result !== "object") {
796
+ throw new WalletRefused("invoice-refused", `the nwc wallet answered ${method} with no result`);
797
+ }
798
+ return result;
799
+ }
800
+ async function overFirstRelayThatAnswers(connection, request, timeoutMs) {
801
+ const deadline = AbortSignal.timeout(timeoutMs);
802
+ let lastFailure = null;
803
+ for (const relay of connection.relays) {
804
+ if (deadline.aborted) {
805
+ break;
806
+ }
807
+ try {
808
+ return await overRelay(relay, connection, request, deadline);
809
+ } catch (failure) {
810
+ lastFailure = failure;
811
+ }
812
+ }
813
+ throw new WalletRefused("unreachable", `no nwc relay answered: ${String(lastFailure)}`);
814
+ }
815
+ function overRelay(relay, connection, request, deadline) {
816
+ return new Promise((resolve2, reject) => {
817
+ const socket = new WebSocket(relay);
818
+ const subscription = request.id.slice(0, 16);
819
+ const expired = () => {
820
+ close();
821
+ reject(new Error(`${relay} did not answer before the deadline`));
822
+ };
823
+ deadline.addEventListener("abort", expired);
824
+ function close() {
825
+ deadline.removeEventListener("abort", expired);
826
+ socket.close();
827
+ }
828
+ socket.addEventListener("open", () => {
829
+ socket.send(
830
+ JSON.stringify([
831
+ "REQ",
832
+ subscription,
833
+ {
834
+ kinds: [RESPONSE_KIND],
835
+ authors: [connection.walletPubkey],
836
+ "#p": [publicKeyFor(connection.secret)],
837
+ "#e": [request.id]
838
+ }
839
+ ])
840
+ );
841
+ socket.send(JSON.stringify(["EVENT", request]));
842
+ });
843
+ socket.addEventListener("error", () => {
844
+ close();
845
+ reject(new Error(`${relay} could not be reached`));
846
+ });
847
+ socket.addEventListener("message", (delivered) => {
848
+ const frame = String(delivered.data);
849
+ const rejection = rejectionOf(frame, request.id);
850
+ if (rejection !== null) {
851
+ close();
852
+ reject(new Error(`${relay} refused the request: ${rejection}`));
853
+ return;
854
+ }
855
+ const answer = answeredEvent(frame, subscription, connection, request.id);
856
+ if (answer === null) {
857
+ return;
858
+ }
859
+ close();
860
+ resolve2(answer);
861
+ });
862
+ });
863
+ }
864
+ function rejectionOf(frame, requestId) {
865
+ const parsed = parsedFrame(frame);
866
+ if (parsed === null || parsed[0] !== "OK" || parsed[1] !== requestId || parsed[2] !== false) {
867
+ return null;
868
+ }
869
+ return typeof parsed[3] === "string" && parsed[3].length > 0 ? parsed[3] : "no reason given";
870
+ }
871
+ function answeredEvent(frame, subscription, connection, requestId) {
872
+ const parsed = parsedFrame(frame);
873
+ if (parsed === null || parsed[0] !== "EVENT" || parsed[1] !== subscription) {
874
+ return null;
875
+ }
876
+ const event = parsed[2];
877
+ const answers = event?.kind === RESPONSE_KIND && event.pubkey === connection.walletPubkey && Array.isArray(event.tags) && event.tags.some((tag) => tag?.[0] === "e" && tag[1] === requestId) && verifyEvent(event);
878
+ return answers ? event : null;
879
+ }
880
+ function parsedFrame(frame) {
881
+ try {
882
+ const parsed = JSON.parse(frame);
883
+ return Array.isArray(parsed) ? parsed : null;
884
+ } catch {
885
+ return null;
886
+ }
887
+ }
888
+ function parsedUri(uri) {
889
+ try {
890
+ const parsed = new URL(uri);
891
+ if (parsed.protocol !== SCHEME) {
892
+ throw new Error(parsed.protocol);
893
+ }
894
+ return parsed;
895
+ } catch {
896
+ throw new WalletRefused("address-unusable", `an nwc uri starts with ${SCHEME}//`);
897
+ }
898
+ }
899
+ function isSecureRelay(relay) {
900
+ try {
901
+ return new URL(relay).protocol === "wss:";
902
+ } catch {
903
+ return false;
904
+ }
905
+ }
906
+
907
+ // ../core/url.ts
908
+ var PRIVATE_SUFFIXES = ["localhost", "local", "internal", "lan", "arpa", "test", "invalid"];
909
+ function publicHttps(raw) {
910
+ let url;
911
+ try {
912
+ url = new URL(raw);
913
+ } catch {
914
+ return false;
915
+ }
916
+ return url.protocol === "https:" && hostIsPublic(url.hostname.toLowerCase());
917
+ }
918
+ function sameOrigin(one, other) {
919
+ try {
920
+ return new URL(one).origin === new URL(other).origin;
921
+ } catch {
922
+ return false;
923
+ }
924
+ }
925
+ function publicAddress(literal) {
926
+ return literal.includes(":") ? ipv6IsGlobal(literal) : ipv4IsGlobal(literal);
927
+ }
928
+ function hostIsPublic(host) {
929
+ if (host.startsWith("[")) {
930
+ return publicAddress(host.slice(1, -1));
931
+ }
932
+ const name = host.replace(/\.$/, "");
933
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(name)) {
934
+ return publicAddress(name);
935
+ }
936
+ const labels = name.split(".");
937
+ if (labels.length < 2 || labels.some((label) => label === "")) {
938
+ return false;
939
+ }
940
+ return !PRIVATE_SUFFIXES.includes(labels[labels.length - 1]);
941
+ }
942
+ function ipv4IsGlobal(literal) {
943
+ const parts = literal.split(".").map(Number);
944
+ if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
945
+ return false;
946
+ }
947
+ const [a, b] = parts;
948
+ if (a === 0 || a === 10 || a === 127 || a >= 240) {
949
+ return false;
950
+ }
951
+ if (a === 100 && b >= 64 && b < 128) {
952
+ return false;
953
+ }
954
+ if (a === 169 && b === 254) {
955
+ return false;
956
+ }
957
+ if (a === 172 && b >= 16 && b < 32) {
958
+ return false;
959
+ }
960
+ if (a === 192 && b === 168) {
961
+ return false;
962
+ }
963
+ return true;
964
+ }
965
+ function ipv6IsGlobal(literal) {
966
+ const groups = ipv6Groups(literal.toLowerCase());
967
+ if (groups.length !== 8 || groups.some(Number.isNaN)) {
968
+ return false;
969
+ }
970
+ const leading = groups.slice(0, 5).every((group) => group === 0);
971
+ if (leading && groups[5] === 0 && groups[6] === 0 && groups[7] <= 1) {
972
+ return false;
973
+ }
974
+ if (leading && groups[5] === 65535) {
975
+ const [high, low] = [groups[6], groups[7]];
976
+ return ipv4IsGlobal(`${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`);
977
+ }
978
+ const first = groups[0];
979
+ if ((first & 65024) === 64512) {
980
+ return false;
981
+ }
982
+ if ((first & 65472) === 65152) {
983
+ return false;
984
+ }
985
+ return true;
986
+ }
987
+ function ipv6Groups(address) {
988
+ const [head = "", tail] = address.split("::");
989
+ const left = head.split(":").filter((group) => group !== "");
990
+ const right = (tail ?? "").split(":").filter((group) => group !== "");
991
+ const gap = tail === void 0 ? [] : Array(8 - left.length - right.length).fill("0");
992
+ return [...left, ...gap, ...right].map((group) => parseInt(group, 16));
993
+ }
994
+
995
+ // ../core/outbound.ts
996
+ var HTTP_TIMEOUT_MS = 15e3;
997
+ var LOOKUP_TIMEOUT_MS = 5e3;
998
+ var HOPS_FOLLOWED = 2;
999
+ var REDIRECTS = [301, 302, 303, 307, 308];
1000
+ var KEEPS_THE_METHOD = [307, 308];
1001
+ var CREDENTIALS = ["authorization", "cookie", "proxy-authorization"];
1002
+ var BODY_LIMIT_BYTES = 262144;
1003
+ var throughFetch = (url, sent, signal) => fetch(url, {
1004
+ method: sent.method ?? "GET",
1005
+ headers: sent.headers ?? {},
1006
+ body: sent.body,
1007
+ redirect: "manual",
1008
+ signal
1009
+ });
1010
+ var send = throughFetch;
1011
+ async function ask(url, sent = {}) {
1012
+ const capped = AbortSignal.timeout(HTTP_TIMEOUT_MS);
1013
+ const signal = sent.deadline ? AbortSignal.any([sent.deadline, capped]) : capped;
1014
+ let target = url;
1015
+ let carried = sent;
1016
+ for (let hop = 0; hop <= HOPS_FOLLOWED; hop += 1) {
1017
+ const at = await addressesToReach(target);
1018
+ const response = await send(target, carried, signal, at);
1019
+ if (!REDIRECTS.includes(response.status)) {
1020
+ return await answerOf(response);
1021
+ }
1022
+ const location = response.headers.get("location");
1023
+ if (location === null) {
1024
+ throw new Error(`${target} answered ${response.status} without saying where to`);
1025
+ }
1026
+ await response.body?.cancel();
1027
+ const next = new URL(location, target).toString();
1028
+ carried = KEEPS_THE_METHOD.includes(response.status) ? carried : withoutBody(carried);
1029
+ carried = sameOrigin(target, next) ? carried : withoutCredentials(carried);
1030
+ target = next;
1031
+ }
1032
+ throw new Error(`${url} redirected more than ${HOPS_FOLLOWED} times`);
1033
+ }
1034
+ async function addressesToReach(url) {
1035
+ if (!publicHttps(url)) {
1036
+ throw new Error(`${url} is not a public https URL`);
1037
+ }
1038
+ const found = await resolved(url);
1039
+ if (found.length === 0 || !found.every((one) => publicAddress(one.address))) {
1040
+ throw new Error(`${url} resolves to an address we do not reach`);
1041
+ }
1042
+ return found;
1043
+ }
1044
+ async function resolved(url) {
1045
+ const { lookup } = await import("dns/promises");
1046
+ const { setTimeout: sleep } = await import("timers/promises");
1047
+ const host = new URL(url).hostname.replace(/^\[|]$/g, "");
1048
+ const found = await Promise.race([
1049
+ lookup(host, { all: true, verbatim: true }).catch(() => []),
1050
+ sleep(LOOKUP_TIMEOUT_MS, null, { ref: false })
1051
+ ]);
1052
+ return found ?? [];
1053
+ }
1054
+ async function answerOf(response) {
1055
+ const { status, ok, headers } = response;
1056
+ const reader = response.body?.getReader();
1057
+ if (!reader) {
1058
+ return { status, ok, body: "", truncated: false, headers };
1059
+ }
1060
+ const read = [];
1061
+ let bytes = 0;
1062
+ while (bytes <= BODY_LIMIT_BYTES) {
1063
+ const { done, value } = await reader.read();
1064
+ if (done) {
1065
+ return { status, ok, body: text(read), truncated: false, headers };
1066
+ }
1067
+ read.push(value);
1068
+ bytes += value.length;
1069
+ }
1070
+ await reader.cancel();
1071
+ return { status, ok, body: text(read), truncated: true, headers };
1072
+ }
1073
+ function withoutBody(sent) {
1074
+ const headers = { ...sent.headers };
1075
+ delete headers["content-type"];
1076
+ return { ...sent, method: "GET", headers, body: void 0 };
1077
+ }
1078
+ function withoutCredentials(sent) {
1079
+ const headers = { ...sent.headers };
1080
+ for (const named of CREDENTIALS) {
1081
+ delete headers[named];
1082
+ }
1083
+ return { ...sent, headers };
1084
+ }
1085
+ function text(chunks) {
1086
+ const decoder = new TextDecoder();
1087
+ let decoded = "";
1088
+ for (const chunk of chunks) {
1089
+ decoded += decoder.decode(chunk, { stream: true });
1090
+ }
1091
+ return decoded + decoder.decode();
1092
+ }
500
1093
 
501
1094
  // ../core/lnurl.ts
502
1095
  var VERIFY_WITHOUT_PREIMAGE = ["zeuspay.com", "zeusnuts.com", "ecash.love"];
@@ -517,11 +1110,15 @@ async function firstThatServes(addresses, attempt) {
517
1110
  const deadline = AbortSignal.timeout(RESOLVE_TIMEOUT_MS);
518
1111
  const refusals = [];
519
1112
  for (const address of addresses) {
520
- if (deadline.aborted) break;
1113
+ if (deadline.aborted) {
1114
+ break;
1115
+ }
521
1116
  try {
522
1117
  return { won: await attempt(address, deadline), refusals };
523
1118
  } catch (refusal) {
524
- if (!(refusal instanceof WalletRefused)) throw refusal;
1119
+ if (!(refusal instanceof WalletRefused)) {
1120
+ throw refusal;
1121
+ }
525
1122
  console.warn(`wallet ${address} refused, ${refusal.reason}: ${refusal.message}`);
526
1123
  refusals.push({ address, reason: refusal.reason });
527
1124
  }
@@ -533,10 +1130,7 @@ async function probe(address, amountMsat, deadline) {
533
1130
  if (cannotReleaseAPreimage(domain)) {
534
1131
  throw new WalletRefused("cannot-prove-delivery", `${address} never releases a preimage`);
535
1132
  }
536
- const pay = await answered(
537
- `https://${domain}/.well-known/lnurlp/${user}`,
538
- deadline
539
- );
1133
+ const pay = await answered(`https://${domain}/.well-known/lnurlp/${user}`, deadline);
540
1134
  if (pay.tag !== "payRequest" || !pay.callback) {
541
1135
  throw new WalletRefused("unreachable", `${address} answered with no payRequest`);
542
1136
  }
@@ -551,7 +1145,9 @@ async function probe(address, amountMsat, deadline) {
551
1145
  async function resolveAddress(address, amountMsat, deadline) {
552
1146
  const { pay } = await probe(address, amountMsat, deadline);
553
1147
  const invoice = await answered(withAmount(pay.callback, amountMsat), deadline);
554
- if (!invoice.pr) throw new WalletRefused("unreachable", `${address} returned no invoice`);
1148
+ if (!invoice.pr) {
1149
+ throw new WalletRefused("unreachable", `${address} returned no invoice`);
1150
+ }
555
1151
  if (!invoice.verify || !publicHttps(invoice.verify)) {
556
1152
  throw new WalletRefused(
557
1153
  "cannot-prove-delivery",
@@ -599,7 +1195,9 @@ async function checkSettled(verifyUrl, paymentHash) {
599
1195
  pace: paceAskedFor(answer.headers),
600
1196
  ceiling: ceilingAskedFor(answer.headers)
601
1197
  };
602
- if (!answer.said.settled || !answer.said.preimage) return { preimage: null, ...asked };
1198
+ if (!answer.said.settled || !answer.said.preimage) {
1199
+ return { preimage: null, ...asked };
1200
+ }
603
1201
  if (!preimageMatchesHash(answer.said.preimage, paymentHash)) {
604
1202
  throw new Error(`verify returned a preimage that does not hash to ${paymentHash}`);
605
1203
  }
@@ -607,15 +1205,21 @@ async function checkSettled(verifyUrl, paymentHash) {
607
1205
  }
608
1206
  function paceAskedFor(headers) {
609
1207
  const asked = /max-age\s*=\s*(\d+)/i.exec(headers.get("cache-control") ?? "");
610
- if (!asked) return null;
1208
+ if (!asked) {
1209
+ return null;
1210
+ }
611
1211
  return Math.min(Math.max(Number(asked[1]), MIN_PACE_SECS), MAX_PACE_SECS);
612
1212
  }
613
1213
  function ceilingAskedFor(headers) {
614
1214
  const asked = /^\s*(\d+)\s*(?:;\s*w\s*=\s*(\d+))?/i.exec(headers.get("ratelimit-limit") ?? "");
615
- if (!asked) return null;
1215
+ if (!asked) {
1216
+ return null;
1217
+ }
616
1218
  const perWindow = Number(asked[1]);
617
1219
  const window = asked[2] === void 0 ? 1 : Number(asked[2]);
618
- if (perWindow === 0 || window === 0) return null;
1220
+ if (perWindow === 0 || window === 0) {
1221
+ return null;
1222
+ }
619
1223
  return Math.min(Math.max(perWindow / window, MIN_PER_SECOND), MAX_PER_SECOND);
620
1224
  }
621
1225
  function cannotReleaseAPreimage(host) {
@@ -633,97 +1237,55 @@ function splitAddress(address) {
633
1237
  if (!user || !domain || rest.length > 0 || !NAMES_ONE_ACCOUNT.test(user) || !isNothingButAHost(domain) || !publicHttps(`https://${domain}/`)) {
634
1238
  throw new WalletRefused("address-unusable", `${address} is not a usable lightning address`);
635
1239
  }
636
- return [user, domain];
637
- }
638
- function isNothingButAHost(domain) {
639
- try {
640
- const parsed = new URL(`https://${domain}/`);
641
- return parsed.host === domain.toLowerCase() && parsed.pathname === "/" && parsed.search === "" && parsed.hash === "";
642
- } catch {
643
- return false;
644
- }
645
- }
646
- async function fetchJson(url, deadline) {
647
- return (await answeredJson(url, deadline)).said;
648
- }
649
- async function answeredJson(url, deadline) {
650
- const answer = await ask(url, { headers: { accept: "application/json" }, deadline });
651
- if (!answer.ok) throw new Error(`${url} answered ${answer.status}`);
652
- if (answer.truncated) {
653
- throw new Error(`${url} answered with more than the ${BODY_LIMIT_BYTES} bytes we read`);
654
- }
655
- return { said: JSON.parse(answer.body), headers: answer.headers };
656
- }
657
-
658
- // ../core/sealed.ts
659
- var VERSION = "v1";
660
- var IV_BYTES = 12;
661
- var MIN_SECRET_CHARS = 32;
662
- var MAX_PLAIN_BYTES = 3e3;
663
- var INFO = new TextEncoder().encode("thunder-bridge/sealed");
664
- async function seal(secret, plaintext) {
665
- const body = new TextEncoder().encode(plaintext);
666
- if (body.length > MAX_PLAIN_BYTES) {
667
- throw new Error(`sealed takes at most ${MAX_PLAIN_BYTES} bytes, this was ${body.length}`);
668
- }
669
- const key = await keyFor(secret);
670
- const iv = crypto.getRandomValues(new Uint8Array(IV_BYTES));
671
- const cipher = new Uint8Array(await crypto.subtle.encrypt({ name: "AES-GCM", iv }, key, body));
672
- const joined = new Uint8Array(iv.length + cipher.length);
673
- joined.set(iv);
674
- joined.set(cipher, iv.length);
675
- return `${VERSION}.${toBase64Url(joined)}`;
1240
+ return [user, domain];
676
1241
  }
677
- async function unseal(secret, sealed) {
678
- const key = await keyFor(secret);
679
- if (!sealed.startsWith(`${VERSION}.`)) return null;
680
- const joined = fromBase64Url(sealed.slice(VERSION.length + 1));
681
- if (joined === null || joined.length <= IV_BYTES) return null;
1242
+ function isNothingButAHost(domain) {
682
1243
  try {
683
- const body = await crypto.subtle.decrypt(
684
- { name: "AES-GCM", iv: joined.slice(0, IV_BYTES) },
685
- key,
686
- joined.slice(IV_BYTES)
687
- );
688
- return new TextDecoder().decode(body);
1244
+ const parsed = new URL(`https://${domain}/`);
1245
+ return parsed.host === domain.toLowerCase() && parsed.pathname === "/" && parsed.search === "" && parsed.hash === "";
689
1246
  } catch {
690
- return null;
1247
+ return false;
691
1248
  }
692
1249
  }
693
- async function keyFor(secret) {
694
- if (secret.length < MIN_SECRET_CHARS) {
695
- throw new Error(`the sealing secret needs ${MIN_SECRET_CHARS} characters of randomness`);
1250
+ async function fetchJson(url, deadline) {
1251
+ return (await answeredJson(url, deadline)).said;
1252
+ }
1253
+ async function answeredJson(url, deadline) {
1254
+ const answer = await ask(url, { headers: { accept: "application/json" }, deadline });
1255
+ if (!answer.ok) {
1256
+ throw new Error(`${url} answered ${answer.status}`);
696
1257
  }
697
- const material = await crypto.subtle.importKey(
1258
+ if (answer.truncated) {
1259
+ throw new Error(`${url} answered with more than the ${BODY_LIMIT_BYTES} bytes we read`);
1260
+ }
1261
+ return { said: JSON.parse(answer.body), headers: answer.headers };
1262
+ }
1263
+
1264
+ // ../core/hmac.ts
1265
+ async function hmacHex(secret, payload) {
1266
+ const raw = typeof secret === "string" ? new TextEncoder().encode(secret) : secret;
1267
+ const key = await crypto.subtle.importKey(
698
1268
  "raw",
699
- new TextEncoder().encode(secret),
700
- "HKDF",
701
- false,
702
- ["deriveKey"]
703
- );
704
- return crypto.subtle.deriveKey(
705
- { name: "HKDF", hash: "SHA-256", salt: new Uint8Array(0), info: INFO },
706
- material,
707
- { name: "AES-GCM", length: 256 },
1269
+ asBuffer(raw),
1270
+ { name: "HMAC", hash: "SHA-256" },
708
1271
  false,
709
- ["encrypt", "decrypt"]
1272
+ ["sign"]
710
1273
  );
1274
+ const message = typeof payload === "string" ? new TextEncoder().encode(payload) : payload;
1275
+ return bytesToHex(new Uint8Array(await crypto.subtle.sign("HMAC", key, asBuffer(message))));
711
1276
  }
712
- function toBase64Url(bytes) {
713
- let binary = "";
714
- for (const byte of bytes) binary += String.fromCharCode(byte);
715
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
716
- }
717
- function fromBase64Url(text2) {
718
- if (text2.length === 0 || !/^[A-Za-z0-9_-]+$/.test(text2)) return null;
719
- try {
720
- const binary = atob(text2.replace(/-/g, "+").replace(/_/g, "/"));
721
- const bytes = new Uint8Array(binary.length);
722
- for (let at = 0; at < bytes.length; at++) bytes[at] = binary.charCodeAt(at);
723
- return bytes;
724
- } catch {
725
- return null;
1277
+ function equalInConstantTime(one, other) {
1278
+ if (one.length !== other.length) {
1279
+ return false;
1280
+ }
1281
+ let difference = 0;
1282
+ for (let at = 0; at < one.length; at++) {
1283
+ difference |= one.charCodeAt(at) ^ other.charCodeAt(at);
726
1284
  }
1285
+ return difference === 0;
1286
+ }
1287
+ function asBuffer(bytes) {
1288
+ return bytes instanceof Uint8Array && bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
727
1289
  }
728
1290
 
729
1291
  // src/errors.ts
@@ -756,8 +1318,124 @@ var NoWalletAvailableError = class extends ProblemError {
756
1318
  }
757
1319
  };
758
1320
 
1321
+ // src/qr.ts
1322
+ var import_uqr = require("uqr");
1323
+ function encodeForQr(destination) {
1324
+ const scanned = isLnAddress(destination) ? destination : destination.toUpperCase();
1325
+ return `LIGHTNING:${scanned}`;
1326
+ }
1327
+ function isLnAddress(destination) {
1328
+ const at = destination.indexOf("@");
1329
+ return at > 0 && destination.slice(at + 1).includes(".");
1330
+ }
1331
+
1332
+ // src/relay.ts
1333
+ var DEFAULT_POLL_EVERY_SECS2 = 5;
1334
+ var WALLET = "w";
1335
+ function lightningVerifyEndpoint(config) {
1336
+ const paced = {
1337
+ "cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS2}`
1338
+ };
1339
+ return async (request) => {
1340
+ const consented = await answerVerifyChallengeRequest(request);
1341
+ if (consented !== null) {
1342
+ return consented;
1343
+ }
1344
+ const sealed = new URL(request.url).searchParams.get(WALLET);
1345
+ if (sealed === null) {
1346
+ return Response.json({ settled: false }, { status: 400 });
1347
+ }
1348
+ const opened = await unseal(config.secret, sealed);
1349
+ if (opened === null) {
1350
+ return Response.json({ settled: false }, { status: 403 });
1351
+ }
1352
+ const wallet = JSON.parse(opened);
1353
+ const asked = await checkSettled(wallet.url, wallet.hash).catch(() => null);
1354
+ if (asked === null) {
1355
+ return Response.json({ settled: false }, { status: 502 });
1356
+ }
1357
+ return Response.json(
1358
+ { settled: asked.preimage !== null, preimage: asked.preimage },
1359
+ { headers: paced }
1360
+ );
1361
+ };
1362
+ }
1363
+ async function relayedVerifyUrl(endpoint, wallet, secret) {
1364
+ const relayed = new URL(endpoint);
1365
+ relayed.searchParams.set(WALLET, await seal(secret, JSON.stringify(wallet)));
1366
+ return relayed.toString();
1367
+ }
1368
+
1369
+ // src/rail.ts
1370
+ var LIGHTNING = "lightning";
1371
+ function blindLightningRail(config) {
1372
+ return async (order) => {
1373
+ const resolved2 = await invoiceFrom(config.lnAddresses, await config.amountMsat(order));
1374
+ const relay = config.relayVerifyThrough;
1375
+ const watched = await config.gateway.watchPayment({
1376
+ paymentHash: resolved2.paymentHash,
1377
+ verifyUrl: relay ? await relayedVerifyUrl(
1378
+ relay.endpoint,
1379
+ { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
1380
+ relay.secret
1381
+ ) : resolved2.verifyUrl,
1382
+ expiresAt: resolved2.expiresAt,
1383
+ trigger: config.trigger,
1384
+ replay: config.replay,
1385
+ sealed: await config.sealed?.(order),
1386
+ webhookUrl: config.webhookUrl
1387
+ });
1388
+ return {
1389
+ id: watched.id,
1390
+ rail: config.name ?? LIGHTNING,
1391
+ scan: resolved2.bolt11,
1392
+ qr: encodeForQr(resolved2.bolt11),
1393
+ expiresAt: resolved2.expiresAt
1394
+ };
1395
+ };
1396
+ }
1397
+ function nwcRail(config) {
1398
+ return async (order) => {
1399
+ const invoice = await nwcInvoice(
1400
+ config.connection,
1401
+ await config.amountMsat(order),
1402
+ config.description?.(order) ?? order.reference
1403
+ );
1404
+ const watched = await config.gateway.watchPayment({
1405
+ paymentHash: invoice.paymentHash,
1406
+ verifyUrl: await nwcVerifyUrl(
1407
+ config.verifyThrough.endpoint,
1408
+ invoice.paymentHash,
1409
+ config.verifyThrough.secret
1410
+ ),
1411
+ expiresAt: invoice.expiresAt,
1412
+ trigger: config.trigger,
1413
+ replay: config.replay,
1414
+ sealed: await config.sealed?.(order),
1415
+ webhookUrl: config.webhookUrl
1416
+ });
1417
+ return {
1418
+ id: watched.id,
1419
+ rail: config.name ?? LIGHTNING,
1420
+ scan: invoice.bolt11,
1421
+ qr: encodeForQr(invoice.bolt11),
1422
+ expiresAt: invoice.expiresAt
1423
+ };
1424
+ };
1425
+ }
1426
+ async function invoiceFrom(lnAddresses, amountMsat) {
1427
+ try {
1428
+ return await resolve(lnAddresses, amountMsat);
1429
+ } catch (refused) {
1430
+ if (refused instanceof NoWalletAvailable) {
1431
+ throw new NoWalletAvailableError({ title: refused.message }, refused.wallets);
1432
+ }
1433
+ throw refused;
1434
+ }
1435
+ }
1436
+
759
1437
  // src/trigger.ts
760
- var NONCE_BYTES = 16;
1438
+ var NONCE_BYTES2 = 16;
761
1439
  function lnurlPayEndpoint(config) {
762
1440
  return async (request) => {
763
1441
  const url = new URL(request.url);
@@ -794,7 +1472,9 @@ async function mint(config, url, address) {
794
1472
  const nonce = url.searchParams.get("n") ?? "";
795
1473
  const signature = url.searchParams.get("sig") ?? "";
796
1474
  const expected = await sign(config.secret, address, amountMsat, nonce);
797
- if (!equalInConstantTime(signature, expected)) return refuse("this callback was not signed here");
1475
+ if (!equalInConstantTime(signature, expected)) {
1476
+ return refuse("this callback was not signed here");
1477
+ }
798
1478
  const wanted = url.searchParams.get("amount");
799
1479
  if (wanted !== null && Number(wanted) !== amountMsat) {
800
1480
  return refuse(`this trigger costs exactly ${amountMsat} msat`);
@@ -810,7 +1490,7 @@ async function mint(config, url, address) {
810
1490
  async function mintThroughGateway(config, address, amountMsat, nonce) {
811
1491
  const payment = await config.gateway.createPayment(
812
1492
  { lnAddresses: [address], amountMsat },
813
- { idempotencyKey: nonce, trigger: config.watchSecret }
1493
+ { idempotencyKey: nonce, trigger: config.watchSecret, replay: config.replay }
814
1494
  );
815
1495
  return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
816
1496
  }
@@ -824,10 +1504,13 @@ async function mintBlind(config, address, amountMsat) {
824
1504
  verifyUrl: resolved2.verifyUrl,
825
1505
  expiresAt: resolved2.expiresAt,
826
1506
  trigger: config.watchSecret,
1507
+ replay: config.replay,
827
1508
  sealed: locked ? await seal(locked.secret, JSON.stringify(locked.data(minted))) : void 0
828
1509
  });
829
1510
  } catch (refused) {
830
- if (!alreadyWatched(refused)) throw refused;
1511
+ if (!alreadyWatched(refused)) {
1512
+ throw refused;
1513
+ }
831
1514
  }
832
1515
  return { bolt11: resolved2.bolt11, verifyUrl: resolved2.verifyUrl };
833
1516
  }
@@ -838,121 +1521,30 @@ function sign(secret, address, amountMsat, nonce) {
838
1521
  return hmacHex(secret, `${address}|${amountMsat}|${nonce}`);
839
1522
  }
840
1523
  function randomNonce() {
841
- const bytes = crypto.getRandomValues(new Uint8Array(NONCE_BYTES));
1524
+ const bytes = crypto.getRandomValues(new Uint8Array(NONCE_BYTES2));
842
1525
  let hex = "";
843
- for (const byte of bytes) hex += byte.toString(16).padStart(2, "0");
1526
+ for (const byte of bytes) {
1527
+ hex += byte.toString(16).padStart(2, "0");
1528
+ }
844
1529
  return hex;
845
1530
  }
846
1531
  function refuse(reason) {
847
1532
  return Response.json({ status: "ERROR", reason });
848
1533
  }
849
-
850
- // ../core/ed25519.ts
851
- var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
852
-
853
- // src/webhook.ts
854
- var VERIFY_CHALLENGE = "verify-challenge";
855
- function nonceOf(text2, type) {
856
- try {
857
- const said = JSON.parse(text2);
858
- if (said["type"] !== type || typeof said["nonce"] !== "string") return null;
859
- return said["nonce"];
860
- } catch {
861
- return null;
862
- }
863
- }
864
- function answerVerifyChallenge(body) {
865
- const nonce = nonceOf(body, VERIFY_CHALLENGE);
866
- return nonce === null ? null : JSON.stringify({ nonce });
867
- }
868
- async function answerVerifyChallengeRequest(request) {
869
- if (request.method !== "POST") return null;
870
- const answer = answerVerifyChallenge(await request.clone().text());
871
- return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
872
- }
873
-
874
- // src/qr.ts
875
- var import_uqr = require("uqr");
876
- function encodeForQr(destination) {
877
- const scanned = isLnAddress(destination) ? destination : destination.toUpperCase();
878
- return `LIGHTNING:${scanned}`;
879
- }
880
- function isLnAddress(destination) {
881
- const at = destination.indexOf("@");
882
- return at > 0 && destination.slice(at + 1).includes(".");
883
- }
884
-
885
- // src/relay.ts
886
- var DEFAULT_POLL_EVERY_SECS = 5;
887
- var WALLET = "w";
888
- function lightningVerifyEndpoint(config) {
889
- const paced = {
890
- "cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
891
- };
892
- return async (request) => {
893
- const consented = await answerVerifyChallengeRequest(request);
894
- if (consented !== null) return consented;
895
- const sealed = new URL(request.url).searchParams.get(WALLET);
896
- if (sealed === null) return Response.json({ settled: false }, { status: 400 });
897
- const opened = await unseal(config.secret, sealed);
898
- if (opened === null) return Response.json({ settled: false }, { status: 403 });
899
- const wallet = JSON.parse(opened);
900
- const asked = await checkSettled(wallet.url, wallet.hash).catch(() => null);
901
- if (asked === null) return Response.json({ settled: false }, { status: 502 });
902
- return Response.json(
903
- { settled: asked.preimage !== null, preimage: asked.preimage },
904
- { headers: paced }
905
- );
906
- };
907
- }
908
- async function relayedVerifyUrl(endpoint, wallet, secret) {
909
- const relayed = new URL(endpoint);
910
- relayed.searchParams.set(WALLET, await seal(secret, JSON.stringify(wallet)));
911
- return relayed.toString();
912
- }
913
-
914
- // src/rail.ts
915
- var LIGHTNING = "lightning";
916
- function blindLightningRail(config) {
917
- return async (order) => {
918
- const resolved2 = await invoiceFrom(config.lnAddresses, await config.amountMsat(order));
919
- const relay = config.relayVerifyThrough;
920
- const watched = await config.gateway.watchPayment({
921
- paymentHash: resolved2.paymentHash,
922
- verifyUrl: relay ? await relayedVerifyUrl(
923
- relay.endpoint,
924
- { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
925
- relay.secret
926
- ) : resolved2.verifyUrl,
927
- expiresAt: resolved2.expiresAt,
928
- trigger: config.trigger,
929
- sealed: await config.sealed?.(order),
930
- webhookUrl: config.webhookUrl
931
- });
932
- return {
933
- id: watched.id,
934
- rail: config.name ?? LIGHTNING,
935
- scan: resolved2.bolt11,
936
- qr: encodeForQr(resolved2.bolt11),
937
- expiresAt: resolved2.expiresAt
938
- };
939
- };
940
- }
941
- async function invoiceFrom(lnAddresses, amountMsat) {
942
- try {
943
- return await resolve(lnAddresses, amountMsat);
944
- } catch (refused) {
945
- if (refused instanceof NoWalletAvailable) {
946
- throw new NoWalletAvailableError({ title: refused.message }, refused.wallets);
947
- }
948
- throw refused;
949
- }
950
- }
951
1534
  // Annotate the CommonJS export names for ESM import in node:
952
1535
  0 && (module.exports = {
1536
+ askWallet,
953
1537
  blindLightningRail,
954
1538
  invoiceFrom,
955
1539
  lightningVerifyEndpoint,
956
1540
  lnurlPayEndpoint,
1541
+ nwcConnection,
1542
+ nwcHoldInvoice,
1543
+ nwcInvoice,
1544
+ nwcPay,
1545
+ nwcRail,
1546
+ nwcSettlement,
1547
+ nwcVerifyEndpoint,
1548
+ nwcVerifyUrl,
957
1549
  relayedVerifyUrl
958
1550
  });