thunder-bridge 2.2.1 → 3.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.
package/dist/index.js CHANGED
@@ -125,9 +125,9 @@ function fillSchedule(schedule, reader, block) {
125
125
  schedule[at] = reader.getUint32(block + at * 4, false);
126
126
  }
127
127
  for (let at = 16; at < SCHEDULE_WORDS; at++) {
128
- const recent2 = schedule[at - 15];
128
+ const recent = schedule[at - 15];
129
129
  const distant = schedule[at - 2];
130
- const mixedRecent = rotate(recent2, 7) ^ rotate(recent2, 18) ^ recent2 >>> 3;
130
+ const mixedRecent = rotate(recent, 7) ^ rotate(recent, 18) ^ recent >>> 3;
131
131
  const mixedDistant = rotate(distant, 17) ^ rotate(distant, 19) ^ distant >>> 10;
132
132
  schedule[at] = schedule[at - 16] + mixedRecent + schedule[at - 7] + mixedDistant | 0;
133
133
  }
@@ -382,7 +382,7 @@ async function sealFor(purpose, secret, plaintext, { stable = false } = {}) {
382
382
  }
383
383
  async function unsealFor(purpose, secret, sealed) {
384
384
  if (!sealed.startsWith(`${PURPOSE_VERSION}.`)) {
385
- return await unseal(secret, sealed);
385
+ return null;
386
386
  }
387
387
  return await opened(
388
388
  await keyFor(secret, `${INFO}/${purpose}`),
@@ -925,13 +925,187 @@ function asBuffer2(bytes) {
925
925
  return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
926
926
  }
927
927
 
928
- // ../core/caller.ts
929
- var KEY_HEADER = "x-client-key";
930
- var SIGNATURE_HEADER = "x-signature";
931
- var TIMESTAMP_HEADER = "x-timestamp";
932
- var NONCE_HEADER = "x-nonce";
933
- var PREFIX = "ed25519=";
928
+ // ../core/signature.ts
929
+ import {
930
+ parseDictionary,
931
+ serializeDictionary,
932
+ serializeInnerList,
933
+ serializeString
934
+ } from "structured-headers";
935
+ var TOLERANCE_SECS = 300;
936
+ var LABEL = "thunder-bridge";
937
+ var ALGORITHM2 = "ed25519";
934
938
  var NONCE_BYTES = 16;
939
+ var SIGNATURE_BYTES2 = 64;
940
+ var PUBLIC_KEY = /^[0-9a-f]{64}$/i;
941
+ var CLIENT_SIGNS = {
942
+ tag: "thunder-bridge-client",
943
+ covers: ["@method", "@authority", "@path", "@query", "content-digest"]
944
+ };
945
+ var GATEWAY_SIGNS = {
946
+ tag: "thunder-bridge-gateway",
947
+ covers: ["@method", "@scheme", "@authority", "@path", "content-digest"]
948
+ };
949
+ function contentDigest(body) {
950
+ return serializeDictionary({
951
+ "sha-256": [Uint8Array.from(sha256(new TextEncoder().encode(body))), /* @__PURE__ */ new Map()]
952
+ });
953
+ }
954
+ function signatureBase(message, covered) {
955
+ const lines = [];
956
+ const seen = /* @__PURE__ */ new Set();
957
+ for (const [name, parameters] of covered[0]) {
958
+ if (typeof name !== "string" || parameters.size > 0 || seen.has(name)) {
959
+ return null;
960
+ }
961
+ seen.add(name);
962
+ const value = componentValue(message, name);
963
+ if (value === null) {
964
+ return null;
965
+ }
966
+ lines.push(`${serializeString(name)}: ${value}`);
967
+ }
968
+ lines.push(`"@signature-params": ${serializeInnerList(covered)}`);
969
+ return lines.join("\n");
970
+ }
971
+ async function signedBy(key, profile, message, body, { created = unixNow(), nonce = randomNonce() } = {}) {
972
+ const digest = contentDigest(body);
973
+ const covered = [
974
+ profile.covers.map((name) => [name, /* @__PURE__ */ new Map()]),
975
+ /* @__PURE__ */ new Map([
976
+ ["created", created],
977
+ ["nonce", nonce],
978
+ ["keyid", key.publicKeyHex],
979
+ ["alg", ALGORITHM2],
980
+ ["tag", profile.tag]
981
+ ])
982
+ ];
983
+ const base = signatureBase(
984
+ { ...message, headers: new Headers({ "content-digest": digest }) },
985
+ covered
986
+ );
987
+ if (base === null) {
988
+ throw new Error(`${message.url} cannot be signed as ${profile.tag}`);
989
+ }
990
+ const signature = Uint8Array.from(hexToBytes(await key.sign(new TextEncoder().encode(base))));
991
+ return {
992
+ "content-digest": digest,
993
+ "signature-input": serializeDictionary(/* @__PURE__ */ new Map([[LABEL, covered]])),
994
+ signature: serializeDictionary(/* @__PURE__ */ new Map([[LABEL, [signature, /* @__PURE__ */ new Map()]]]))
995
+ };
996
+ }
997
+ async function signerOf(message, body, profile, toleranceSecs = TOLERANCE_SECS) {
998
+ const found = chosen(message.headers, profile.tag);
999
+ if (found === null) {
1000
+ return null;
1001
+ }
1002
+ const [components, parameters] = found.covered;
1003
+ const named = components.map(([name]) => name);
1004
+ if (!profile.covers.every((name) => named.includes(name))) {
1005
+ return null;
1006
+ }
1007
+ const keyid = parameters.get("keyid");
1008
+ const created = parameters.get("created");
1009
+ const nonce = parameters.get("nonce");
1010
+ const alg = parameters.get("alg");
1011
+ if (typeof keyid !== "string" || !PUBLIC_KEY.test(keyid)) {
1012
+ return null;
1013
+ }
1014
+ if (typeof created !== "number" || !Number.isInteger(created)) {
1015
+ return null;
1016
+ }
1017
+ if (Math.abs(unixNow() - created) > toleranceSecs) {
1018
+ return null;
1019
+ }
1020
+ if (alg !== void 0 && alg !== ALGORITHM2 || nonce !== void 0 && typeof nonce !== "string") {
1021
+ return null;
1022
+ }
1023
+ if (!digestMatches(message.headers.get("content-digest"), body)) {
1024
+ return null;
1025
+ }
1026
+ const base = signatureBase(message, found.covered);
1027
+ if (base === null) {
1028
+ return null;
1029
+ }
1030
+ const signer = keyid.toLowerCase();
1031
+ const proven = await verifyHex(
1032
+ signer,
1033
+ bytesToHex(found.signature),
1034
+ new TextEncoder().encode(base)
1035
+ );
1036
+ return proven ? { keyid: signer, created, nonce: nonce ?? null } : null;
1037
+ }
1038
+ function componentValue(message, name) {
1039
+ let url;
1040
+ try {
1041
+ url = new URL(message.url);
1042
+ } catch {
1043
+ return null;
1044
+ }
1045
+ switch (name) {
1046
+ case "@method":
1047
+ return message.method;
1048
+ case "@authority":
1049
+ return (message.authority ?? url.host).toLowerCase();
1050
+ case "@scheme":
1051
+ return url.protocol.slice(0, -1).toLowerCase();
1052
+ case "@path":
1053
+ return url.pathname === "" ? "/" : url.pathname;
1054
+ case "@query":
1055
+ return url.search === "" ? "?" : url.search;
1056
+ default:
1057
+ return name.startsWith("@") ? null : message.headers.get(name)?.trim() ?? null;
1058
+ }
1059
+ }
1060
+ function chosen(headers, tag) {
1061
+ const inputs = dictionaryOf(headers.get("signature-input"));
1062
+ const signatures = dictionaryOf(headers.get("signature"));
1063
+ if (inputs === null || signatures === null) {
1064
+ return null;
1065
+ }
1066
+ for (const [label, member] of inputs) {
1067
+ if (!isInnerList(member) || member[1].get("tag") !== tag) {
1068
+ continue;
1069
+ }
1070
+ const value = signatures.get(label);
1071
+ if (value === void 0 || isInnerList(value) || !(value[0] instanceof ArrayBuffer)) {
1072
+ return null;
1073
+ }
1074
+ const signature = new Uint8Array(value[0]);
1075
+ return signature.length === SIGNATURE_BYTES2 ? { covered: member, signature } : null;
1076
+ }
1077
+ return null;
1078
+ }
1079
+ function digestMatches(field, body) {
1080
+ const digest = dictionaryOf(field)?.get("sha-256");
1081
+ if (digest === void 0 || isInnerList(digest) || !(digest[0] instanceof ArrayBuffer)) {
1082
+ return false;
1083
+ }
1084
+ const sent = new Uint8Array(digest[0]);
1085
+ const counted = sha256(new TextEncoder().encode(body));
1086
+ return sent.length === counted.length && sent.every((byte, at) => byte === counted[at]);
1087
+ }
1088
+ function dictionaryOf(field) {
1089
+ if (field === null) {
1090
+ return null;
1091
+ }
1092
+ try {
1093
+ return parseDictionary(field);
1094
+ } catch {
1095
+ return null;
1096
+ }
1097
+ }
1098
+ function isInnerList(member) {
1099
+ return Array.isArray(member[0]);
1100
+ }
1101
+ function randomNonce() {
1102
+ return bytesToHex(crypto.getRandomValues(new Uint8Array(NONCE_BYTES)));
1103
+ }
1104
+ function unixNow() {
1105
+ return Math.floor(Date.now() / 1e3);
1106
+ }
1107
+
1108
+ // ../core/caller.ts
935
1109
  var SIGNING_LABEL = "request-signing";
936
1110
  async function callerKey(secret) {
937
1111
  return await signingKeyFromSeed(hexToBytes(await hmacHex(secret, SIGNING_LABEL)));
@@ -940,20 +1114,11 @@ function paymentNamedBy(caller, paymentHash) {
940
1114
  return sha256Hex(`${caller}.payment-id.${paymentHash}`);
941
1115
  }
942
1116
  async function signedAs(key, method, path, body, host) {
943
- const timestamp = String(Math.floor(Date.now() / 1e3));
944
- const nonce = bytesToHex(crypto.getRandomValues(new Uint8Array(NONCE_BYTES)));
945
- return {
946
- [KEY_HEADER]: key.publicKeyHex,
947
- [TIMESTAMP_HEADER]: timestamp,
948
- [NONCE_HEADER]: nonce,
949
- [SIGNATURE_HEADER]: `${PREFIX}${await key.sign(spokenTo(host, timestamp, nonce, method, path, body))}`
950
- };
951
- }
952
- function spokenTo(host, timestamp, nonce, method, path, body) {
953
- return new TextEncoder().encode(
954
- ["v2", host.toLowerCase(), timestamp, nonce, method.toUpperCase(), path, sha256Hex(body)].join(
955
- "\n"
956
- )
1117
+ return await signedBy(
1118
+ key,
1119
+ CLIENT_SIGNS,
1120
+ { method, url: `https://${host}${path}`, authority: host },
1121
+ body
957
1122
  );
958
1123
  }
959
1124
 
@@ -1135,14 +1300,23 @@ async function addressesToReach(url) {
1135
1300
  return found;
1136
1301
  }
1137
1302
  async function resolved(url) {
1138
- const { isIP } = await import("net");
1303
+ const { isIP } = await import(
1304
+ /* webpackIgnore: true */
1305
+ "node:net"
1306
+ );
1139
1307
  const host = new URL(url).hostname.replace(/^\[|]$/g, "");
1140
1308
  const literal = isIP(host);
1141
1309
  if (literal !== 0) {
1142
1310
  return [{ address: host, family: literal }];
1143
1311
  }
1144
- const { Resolver } = await import("dns/promises");
1145
- const { setTimeout: sleep } = await import("timers/promises");
1312
+ const { Resolver } = await import(
1313
+ /* webpackIgnore: true */
1314
+ "node:dns/promises"
1315
+ );
1316
+ const { setTimeout: sleep } = await import(
1317
+ /* webpackIgnore: true */
1318
+ "node:timers/promises"
1319
+ );
1146
1320
  const resolver = new Resolver({ timeout: LOOKUP_TIMEOUT_MS, tries: 1 });
1147
1321
  const found = await Promise.race([
1148
1322
  Promise.all([
@@ -1229,6 +1403,7 @@ var NoWalletAvailable = class extends Error {
1229
1403
 
1230
1404
  // ../core/lnurl.ts
1231
1405
  var VERIFY_WITHOUT_PREIMAGE = ["zeuspay.com", "zeusnuts.com", "ecash.love"];
1406
+ var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
1232
1407
  var RESOLVE_TIMEOUT_MS = 3e4;
1233
1408
  var NAMES_ONE_ACCOUNT = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
1234
1409
  var MIN_PACE_SECS = 1;
@@ -1290,6 +1465,12 @@ async function probe(send, address, amountMsat, deadline) {
1290
1465
  if (pay.tag !== "payRequest" || !pay.callback) {
1291
1466
  throw new WalletRefused("unreachable", `${address} answered with no payRequest`);
1292
1467
  }
1468
+ if (!namesTheAddress(pay.metadata, address)) {
1469
+ throw new WalletRefused(
1470
+ "cannot-prove-delivery",
1471
+ `${address} serves metadata that does not name it, so no invoice binds to it`
1472
+ );
1473
+ }
1293
1474
  if (amountMsat < pay.minSendable || amountMsat > pay.maxSendable) {
1294
1475
  throw new WalletRefused(
1295
1476
  "amount-not-accepted",
@@ -1382,6 +1563,19 @@ function ceilingAskedFor(headers) {
1382
1563
  }
1383
1564
  return Math.min(Math.max(perWindow / window, MIN_PER_SECOND), MAX_PER_SECOND);
1384
1565
  }
1566
+ function namesTheAddress(metadata, address) {
1567
+ if (typeof metadata !== "string") {
1568
+ return false;
1569
+ }
1570
+ try {
1571
+ const entries = JSON.parse(metadata);
1572
+ return Array.isArray(entries) && entries.some(
1573
+ (entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && entry[1].toLowerCase() === address.toLowerCase()
1574
+ );
1575
+ } catch {
1576
+ return false;
1577
+ }
1578
+ }
1385
1579
  function cannotReleaseAPreimage(host) {
1386
1580
  const lowered = host.toLowerCase();
1387
1581
  return VERIFY_WITHOUT_PREIMAGE.some(
@@ -1430,8 +1624,14 @@ var pinnedToTheAddressWeVerified = async (url, sent, signal, at) => {
1430
1624
  if (first === void 0) {
1431
1625
  throw new Error(`${url} resolved to nothing worth connecting to`);
1432
1626
  }
1433
- const { request } = await import("https");
1434
- const { Readable } = await import("stream");
1627
+ const { request } = await import(
1628
+ /* webpackIgnore: true */
1629
+ "node:https"
1630
+ );
1631
+ const { Readable } = await import(
1632
+ /* webpackIgnore: true */
1633
+ "node:stream"
1634
+ );
1435
1635
  return new Promise((settle, fail) => {
1436
1636
  if (signal.aborted) {
1437
1637
  fail(signal.reason);
@@ -1496,18 +1696,9 @@ function headersOf(raw) {
1496
1696
  return headers;
1497
1697
  }
1498
1698
 
1499
- // ../core/delivery.ts
1500
- function deliverySigned(url, timestamp, body) {
1501
- const endpoint = new URL(url);
1502
- return new TextEncoder().encode(
1503
- ["v2", `${endpoint.origin}${endpoint.pathname}`, timestamp, body].join("\n")
1504
- );
1505
- }
1506
-
1507
1699
  // src/verify.ts
1508
1700
  var HTTP_TIMEOUT_MS2 = 15e3;
1509
1701
  var ISSUED_SKEW_SECS = 300;
1510
- var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
1511
1702
  var DEFAULT_WRAP_PROPORTION = 0.01;
1512
1703
  var DEFAULT_WRAP_BASE_MSAT = 1e3;
1513
1704
  async function proveOrigin(payment, asked, askedAt) {
@@ -1554,16 +1745,6 @@ async function proveOrigin(payment, asked, askedAt) {
1554
1745
  throw cheat("invoice_settled");
1555
1746
  }
1556
1747
  }
1557
- function namesTheAddress(metadata, address) {
1558
- try {
1559
- const entries = JSON.parse(metadata);
1560
- return Array.isArray(entries) && entries.some(
1561
- (entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && equalIgnoringCase(entry[1], address)
1562
- );
1563
- } catch {
1564
- return false;
1565
- }
1566
- }
1567
1748
  async function proveSettlement(payment, asked) {
1568
1749
  await proveOrigin(payment, asked);
1569
1750
  const verified = await reachable(payment.verifyUrl, payment);
@@ -1587,7 +1768,6 @@ function agreesWithItself(report) {
1587
1768
  }
1588
1769
  return preimageMatchesHash(report.preimage, report.paymentHash);
1589
1770
  }
1590
- var carriesProof = agreesWithItself;
1591
1771
  function wrapFeeCeiling(amountMsat, allowance) {
1592
1772
  const proportional = amountMsat * (allowance?.proportion ?? DEFAULT_WRAP_PROPORTION);
1593
1773
  return msat(Math.max(allowance?.baseMsat ?? DEFAULT_WRAP_BASE_MSAT, Math.floor(proportional)));
@@ -1869,10 +2049,6 @@ function isStatus(value) {
1869
2049
  }
1870
2050
 
1871
2051
  // src/webhook.ts
1872
- var SIGNATURE_HEADER2 = "x-signature-v2";
1873
- var TIMESTAMP_HEADER2 = "x-timestamp";
1874
- var GATEWAY_KEY_PREFIX = "ed25519=";
1875
- var DEFAULT_TOLERANCE_SECS = 300;
1876
2052
  var CHALLENGE = "webhook-challenge";
1877
2053
  var VERIFY_CHALLENGE = "verify-challenge";
1878
2054
  async function readPayment(request, credential, options = {}) {
@@ -1896,24 +2072,14 @@ async function answerVerifyChallenge(request) {
1896
2072
  return nonce === null ? null : answered2(nonce);
1897
2073
  }
1898
2074
  async function believable(request, credential, options) {
1899
- const signature = request.headers.get(SIGNATURE_HEADER2);
1900
- const timestamp = request.headers.get(TIMESTAMP_HEADER2);
1901
- if (signature === null || timestamp === null) {
1902
- return null;
1903
- }
1904
- if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) {
1905
- return null;
1906
- }
1907
- if (!signature.startsWith(GATEWAY_KEY_PREFIX)) {
1908
- return null;
1909
- }
1910
2075
  const body = await request.clone().text();
1911
- const signed = await verifyHex(
1912
- credential.publicKey.toLowerCase(),
1913
- signature.slice(GATEWAY_KEY_PREFIX.length).toLowerCase(),
1914
- deliverySigned(options.url ?? request.url, timestamp, body)
2076
+ const signer = await signerOf(
2077
+ { method: request.method, url: options.url ?? request.url, headers: request.headers },
2078
+ body,
2079
+ GATEWAY_SIGNS,
2080
+ options.toleranceSecs
1915
2081
  );
1916
- return signed ? body : null;
2082
+ return signer !== null && signer.keyid === credential.publicKey.toLowerCase() ? body : null;
1917
2083
  }
1918
2084
  function proved(read) {
1919
2085
  return read !== null && read.status === "paid" && !agreesWithItself(read) ? null : read;
@@ -1941,13 +2107,6 @@ function nonceOf2(text3, type) {
1941
2107
  return null;
1942
2108
  }
1943
2109
  }
1944
- function recent(timestamp, toleranceSecs) {
1945
- const sent = Number(timestamp);
1946
- if (!Number.isFinite(sent)) {
1947
- return false;
1948
- }
1949
- return Math.abs(Math.floor(Date.now() / 1e3) - sent) <= toleranceSecs;
1950
- }
1951
2110
 
1952
2111
  // src/bank.ts
1953
2112
  var DEFAULT_CURRENCY = "CZK";
@@ -2026,7 +2185,7 @@ async function answeredAt(params, subject) {
2026
2185
  return polling.toString();
2027
2186
  }
2028
2187
  async function creditedPreimage(params) {
2029
- const since = unixNow() - (params.lookBackSecs ?? DEFAULT_LOOK_BACK_SECS);
2188
+ const since = unixNow2() - (params.lookBackSecs ?? DEFAULT_LOOK_BACK_SECS);
2030
2189
  const landed = (await params.statement(since)).some((credit) => pays(credit, params.asked));
2031
2190
  if (!landed) {
2032
2191
  return null;
@@ -2124,7 +2283,7 @@ function refuseUnusable(params, iban, currency) {
2124
2283
  function accountOf(iban) {
2125
2284
  return iban.replace(/\s+/g, "").toUpperCase();
2126
2285
  }
2127
- function unixNow() {
2286
+ function unixNow2() {
2128
2287
  return Math.floor(Date.now() / 1e3);
2129
2288
  }
2130
2289
 
@@ -2560,6 +2719,19 @@ function nwcRail(gateway, config) {
2560
2719
  // src/relay.ts
2561
2720
  var DEFAULT_POLL_EVERY_SECS3 = 5;
2562
2721
  var WALLET = "w";
2722
+ function verifiedThrough(path) {
2723
+ const through = path.verifyThrough ?? null;
2724
+ const mints = path.gatewayMints === true;
2725
+ if (through !== null && mints) {
2726
+ throw new Error("verifyThrough and gatewayMints are two different paths, name only one");
2727
+ }
2728
+ if (through === null && !mints) {
2729
+ throw new Error(
2730
+ "name verifyThrough, where your serve.lightningVerify is mounted, or gatewayMints: true to let the gateway mint and poll the wallet itself"
2731
+ );
2732
+ }
2733
+ return through;
2734
+ }
2563
2735
  function lightningVerifyEndpoint(config) {
2564
2736
  const paced = {
2565
2737
  "cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS3}`
@@ -2638,6 +2810,10 @@ function bankRail(gateway, config) {
2638
2810
  };
2639
2811
  }
2640
2812
  function lightningRail(gateway, config) {
2813
+ const through = verifiedThrough(config);
2814
+ return through === null ? mintedByGateway(gateway, config) : resolvedHere(gateway, config, through);
2815
+ }
2816
+ function mintedByGateway(gateway, config) {
2641
2817
  return async (order) => {
2642
2818
  const payment = await gateway.mint(
2643
2819
  {
@@ -2661,21 +2837,20 @@ function lightningRail(gateway, config) {
2661
2837
  };
2662
2838
  };
2663
2839
  }
2664
- function blindLightningRail(gateway, config) {
2840
+ function resolvedHere(gateway, config, through) {
2665
2841
  return async (order) => {
2666
2842
  const resolved2 = await invoiceFrom(
2667
2843
  config.paidTo,
2668
2844
  await msatForOrder(order, config.amount, config.rate),
2669
2845
  config.send
2670
2846
  );
2671
- const relay = config.relayThrough;
2672
2847
  const watched = await gateway.watch({
2673
2848
  paymentHash: resolved2.paymentHash,
2674
- verifyUrl: relay ? await relayedVerifyUrl(
2675
- relay.endpoint,
2849
+ verifyUrl: await relayedVerifyUrl(
2850
+ through.endpoint,
2676
2851
  { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
2677
- relay.secret
2678
- ) : resolved2.verifyUrl,
2852
+ through.secret
2853
+ ),
2679
2854
  expiresAt: resolved2.expiresAt,
2680
2855
  trigger: config.trigger,
2681
2856
  replay: config.replay,
@@ -2718,16 +2893,12 @@ var Rails = class {
2718
2893
  this.gateway = gateway;
2719
2894
  }
2720
2895
  gateway;
2721
- /** Lightning, with the gateway minting against a priority list of addresses */
2722
- lightning(config) {
2723
- return lightningRail(this.gateway, config);
2724
- }
2725
2896
  /**
2726
- * Lightning, with the invoice resolved here so the gateway is told neither the
2727
- * address nor the amount
2897
+ * Lightning against a priority list of addresses, verified through your
2898
+ * `serve.lightningVerify`, or minted by the gateway when you say `gatewayMints`
2728
2899
  */
2729
- blindLightning(config) {
2730
- return blindLightningRail(this.gateway, config);
2900
+ lightning(config) {
2901
+ return lightningRail(this.gateway, config);
2731
2902
  }
2732
2903
  /** A bank transfer, proved the way a Lightning payment is */
2733
2904
  bank(config) {
@@ -2793,11 +2964,12 @@ function waitingOf(options) {
2793
2964
  // src/trigger.ts
2794
2965
  var NONCE_BYTES3 = 16;
2795
2966
  function lnurlPayEndpoint(gateway, config) {
2967
+ const through = verifiedThrough(config);
2796
2968
  return async (request) => {
2797
2969
  const url = new URL(request.url);
2798
2970
  const asked = url.searchParams.get("to");
2799
2971
  try {
2800
- return asked === null ? await offer(gateway, config, url) : await mint(gateway, config, url, asked);
2972
+ return asked === null ? await offer(gateway, config, through, url) : await mint(gateway, config, through, url, asked);
2801
2973
  } catch (failure) {
2802
2974
  return refuse(failure instanceof Error ? failure.message : "the trigger could not be served");
2803
2975
  }
@@ -2815,10 +2987,10 @@ function watchTicketEndpoint(gateway, config) {
2815
2987
  function publicWatchTicketEndpoint(gateway, config) {
2816
2988
  return () => issue(gateway, config);
2817
2989
  }
2818
- async function offer(gateway, config, url) {
2990
+ async function offer(gateway, config, through, url) {
2819
2991
  const { least, most } = await spread(config.amount);
2820
- const winner = await quoted(gateway, config, least);
2821
- const nonce = randomNonce();
2992
+ const winner = await quoted(gateway, config, through, least);
2993
+ const nonce = randomNonce2();
2822
2994
  const callback = new URL(config.baseUrl ?? `${url.origin}${url.pathname}`);
2823
2995
  callback.searchParams.set("to", winner.address);
2824
2996
  callback.searchParams.set("least", String(least));
@@ -2833,8 +3005,8 @@ async function offer(gateway, config, url) {
2833
3005
  maxSendable: most
2834
3006
  });
2835
3007
  }
2836
- async function quoted(gateway, config, amountMsat) {
2837
- if (config.blind) {
3008
+ async function quoted(gateway, config, through, amountMsat) {
3009
+ if (through !== null) {
2838
3010
  const { won } = await quote(
2839
3011
  config.send ?? pinnedToTheAddressWeVerified,
2840
3012
  listed(config.paidTo),
@@ -2860,7 +3032,7 @@ async function spread(amount) {
2860
3032
  }
2861
3033
  return { least, most };
2862
3034
  }
2863
- async function mint(gateway, config, url, address) {
3035
+ async function mint(gateway, config, through, url, address) {
2864
3036
  const least = Number(url.searchParams.get("least"));
2865
3037
  const most = Number(url.searchParams.get("most"));
2866
3038
  const nonce = url.searchParams.get("n") ?? "";
@@ -2879,7 +3051,7 @@ async function mint(gateway, config, url, address) {
2879
3051
  least === most ? `this trigger costs exactly ${least} msat` : `this trigger takes between ${least} and ${most} msat`
2880
3052
  );
2881
3053
  }
2882
- const minted = config.blind ? await mintBlind(gateway, config, address, amountMsat) : await mintThroughGateway(gateway, config, address, amountMsat, nonce);
3054
+ const minted = through === null ? await mintThroughGateway(gateway, config, address, amountMsat, nonce) : await mintHere(gateway, config, through, address, amountMsat);
2883
3055
  return Response.json({
2884
3056
  status: "OK",
2885
3057
  pr: minted.bolt11,
@@ -2894,7 +3066,7 @@ async function mintThroughGateway(gateway, config, address, amountMsat, nonce) {
2894
3066
  );
2895
3067
  return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
2896
3068
  }
2897
- async function mintBlind(gateway, config, address, amountMsat) {
3069
+ async function mintHere(gateway, config, through, address, amountMsat) {
2898
3070
  const resolved2 = await resolve(
2899
3071
  config.send ?? pinnedToTheAddressWeVerified,
2900
3072
  [address],
@@ -2902,15 +3074,14 @@ async function mintBlind(gateway, config, address, amountMsat) {
2902
3074
  );
2903
3075
  const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
2904
3076
  const locked = config.sealed;
2905
- const relay = config.relayThrough;
2906
3077
  try {
2907
3078
  await gateway.watch({
2908
3079
  paymentHash: resolved2.paymentHash,
2909
- verifyUrl: relay ? await relayedVerifyUrl(
2910
- relay.endpoint,
3080
+ verifyUrl: await relayedVerifyUrl(
3081
+ through.endpoint,
2911
3082
  { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
2912
- relay.secret
2913
- ) : resolved2.verifyUrl,
3083
+ through.secret
3084
+ ),
2914
3085
  expiresAt: resolved2.expiresAt,
2915
3086
  trigger: config.watchSecret,
2916
3087
  replay: config.replay,
@@ -2932,7 +3103,7 @@ function alreadyWatched(refused) {
2932
3103
  function sign(secret, address, least, most, nonce) {
2933
3104
  return hmacHex(secret, `lnurl-callback|${address}|${least}|${most}|${nonce}`);
2934
3105
  }
2935
- function randomNonce() {
3106
+ function randomNonce2() {
2936
3107
  const bytes = crypto.getRandomValues(new Uint8Array(NONCE_BYTES3));
2937
3108
  let hex = "";
2938
3109
  for (const byte of bytes) {
@@ -2986,14 +3157,6 @@ var Serve = class {
2986
3157
  lightningVerify(config) {
2987
3158
  return lightningVerifyEndpoint(config);
2988
3159
  }
2989
- /**
2990
- * What `lightningVerify` was called before 2.2.0
2991
- *
2992
- * @deprecated Use `lightningVerify`, beside `bankVerify` and `nwcVerify`
2993
- */
2994
- verify(config) {
2995
- return this.lightningVerify(config);
2996
- }
2997
3160
  /** The verify endpoint a bank rail is polled at, answering off your own statement */
2998
3161
  bankVerify(config) {
2999
3162
  return bankVerifyEndpoint(config);
@@ -3015,7 +3178,7 @@ var Serve = class {
3015
3178
  */
3016
3179
  webhook(handlers) {
3017
3180
  const options = { toleranceSecs: handlers.toleranceSecs, url: handlers.url };
3018
- const once = onceWhileReplayable(handlers.toleranceSecs ?? DEFAULT_TOLERANCE_SECS);
3181
+ const once = onceWhileReplayable(handlers.toleranceSecs ?? TOLERANCE_SECS);
3019
3182
  return async (request) => {
3020
3183
  const credential = await this.credential(handlers);
3021
3184
  const challenge = await answerWebhookChallenge(request, credential, options);
@@ -3865,7 +4028,6 @@ export {
3865
4028
  WrapRefusedError,
3866
4029
  agreesWithItself,
3867
4030
  answerVerifyChallenge,
3868
- carriesProof,
3869
4031
  decodeInvoice,
3870
4032
  fiat,
3871
4033
  invoiceFrom,