thunder-bridge 2.2.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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
 
@@ -1229,6 +1394,7 @@ var NoWalletAvailable = class extends Error {
1229
1394
 
1230
1395
  // ../core/lnurl.ts
1231
1396
  var VERIFY_WITHOUT_PREIMAGE = ["zeuspay.com", "zeusnuts.com", "ecash.love"];
1397
+ var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
1232
1398
  var RESOLVE_TIMEOUT_MS = 3e4;
1233
1399
  var NAMES_ONE_ACCOUNT = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
1234
1400
  var MIN_PACE_SECS = 1;
@@ -1290,6 +1456,12 @@ async function probe(send, address, amountMsat, deadline) {
1290
1456
  if (pay.tag !== "payRequest" || !pay.callback) {
1291
1457
  throw new WalletRefused("unreachable", `${address} answered with no payRequest`);
1292
1458
  }
1459
+ if (!namesTheAddress(pay.metadata, address)) {
1460
+ throw new WalletRefused(
1461
+ "cannot-prove-delivery",
1462
+ `${address} serves metadata that does not name it, so no invoice binds to it`
1463
+ );
1464
+ }
1293
1465
  if (amountMsat < pay.minSendable || amountMsat > pay.maxSendable) {
1294
1466
  throw new WalletRefused(
1295
1467
  "amount-not-accepted",
@@ -1382,6 +1554,19 @@ function ceilingAskedFor(headers) {
1382
1554
  }
1383
1555
  return Math.min(Math.max(perWindow / window, MIN_PER_SECOND), MAX_PER_SECOND);
1384
1556
  }
1557
+ function namesTheAddress(metadata, address) {
1558
+ if (typeof metadata !== "string") {
1559
+ return false;
1560
+ }
1561
+ try {
1562
+ const entries = JSON.parse(metadata);
1563
+ return Array.isArray(entries) && entries.some(
1564
+ (entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && entry[1].toLowerCase() === address.toLowerCase()
1565
+ );
1566
+ } catch {
1567
+ return false;
1568
+ }
1569
+ }
1385
1570
  function cannotReleaseAPreimage(host) {
1386
1571
  const lowered = host.toLowerCase();
1387
1572
  return VERIFY_WITHOUT_PREIMAGE.some(
@@ -1496,18 +1681,9 @@ function headersOf(raw) {
1496
1681
  return headers;
1497
1682
  }
1498
1683
 
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
1684
  // src/verify.ts
1508
1685
  var HTTP_TIMEOUT_MS2 = 15e3;
1509
1686
  var ISSUED_SKEW_SECS = 300;
1510
- var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
1511
1687
  var DEFAULT_WRAP_PROPORTION = 0.01;
1512
1688
  var DEFAULT_WRAP_BASE_MSAT = 1e3;
1513
1689
  async function proveOrigin(payment, asked, askedAt) {
@@ -1554,16 +1730,6 @@ async function proveOrigin(payment, asked, askedAt) {
1554
1730
  throw cheat("invoice_settled");
1555
1731
  }
1556
1732
  }
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
1733
  async function proveSettlement(payment, asked) {
1568
1734
  await proveOrigin(payment, asked);
1569
1735
  const verified = await reachable(payment.verifyUrl, payment);
@@ -1587,7 +1753,6 @@ function agreesWithItself(report) {
1587
1753
  }
1588
1754
  return preimageMatchesHash(report.preimage, report.paymentHash);
1589
1755
  }
1590
- var carriesProof = agreesWithItself;
1591
1756
  function wrapFeeCeiling(amountMsat, allowance) {
1592
1757
  const proportional = amountMsat * (allowance?.proportion ?? DEFAULT_WRAP_PROPORTION);
1593
1758
  return msat(Math.max(allowance?.baseMsat ?? DEFAULT_WRAP_BASE_MSAT, Math.floor(proportional)));
@@ -1869,10 +2034,6 @@ function isStatus(value) {
1869
2034
  }
1870
2035
 
1871
2036
  // 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
2037
  var CHALLENGE = "webhook-challenge";
1877
2038
  var VERIFY_CHALLENGE = "verify-challenge";
1878
2039
  async function readPayment(request, credential, options = {}) {
@@ -1896,24 +2057,14 @@ async function answerVerifyChallenge(request) {
1896
2057
  return nonce === null ? null : answered2(nonce);
1897
2058
  }
1898
2059
  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
2060
  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)
2061
+ const signer = await signerOf(
2062
+ { method: request.method, url: options.url ?? request.url, headers: request.headers },
2063
+ body,
2064
+ GATEWAY_SIGNS,
2065
+ options.toleranceSecs
1915
2066
  );
1916
- return signed ? body : null;
2067
+ return signer !== null && signer.keyid === credential.publicKey.toLowerCase() ? body : null;
1917
2068
  }
1918
2069
  function proved(read) {
1919
2070
  return read !== null && read.status === "paid" && !agreesWithItself(read) ? null : read;
@@ -1941,13 +2092,6 @@ function nonceOf2(text3, type) {
1941
2092
  return null;
1942
2093
  }
1943
2094
  }
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
2095
 
1952
2096
  // src/bank.ts
1953
2097
  var DEFAULT_CURRENCY = "CZK";
@@ -2026,7 +2170,7 @@ async function answeredAt(params, subject) {
2026
2170
  return polling.toString();
2027
2171
  }
2028
2172
  async function creditedPreimage(params) {
2029
- const since = unixNow() - (params.lookBackSecs ?? DEFAULT_LOOK_BACK_SECS);
2173
+ const since = unixNow2() - (params.lookBackSecs ?? DEFAULT_LOOK_BACK_SECS);
2030
2174
  const landed = (await params.statement(since)).some((credit) => pays(credit, params.asked));
2031
2175
  if (!landed) {
2032
2176
  return null;
@@ -2124,7 +2268,7 @@ function refuseUnusable(params, iban, currency) {
2124
2268
  function accountOf(iban) {
2125
2269
  return iban.replace(/\s+/g, "").toUpperCase();
2126
2270
  }
2127
- function unixNow() {
2271
+ function unixNow2() {
2128
2272
  return Math.floor(Date.now() / 1e3);
2129
2273
  }
2130
2274
 
@@ -2560,6 +2704,19 @@ function nwcRail(gateway, config) {
2560
2704
  // src/relay.ts
2561
2705
  var DEFAULT_POLL_EVERY_SECS3 = 5;
2562
2706
  var WALLET = "w";
2707
+ function verifiedThrough(path) {
2708
+ const through = path.verifyThrough ?? null;
2709
+ const mints = path.gatewayMints === true;
2710
+ if (through !== null && mints) {
2711
+ throw new Error("verifyThrough and gatewayMints are two different paths, name only one");
2712
+ }
2713
+ if (through === null && !mints) {
2714
+ throw new Error(
2715
+ "name verifyThrough, where your serve.lightningVerify is mounted, or gatewayMints: true to let the gateway mint and poll the wallet itself"
2716
+ );
2717
+ }
2718
+ return through;
2719
+ }
2563
2720
  function lightningVerifyEndpoint(config) {
2564
2721
  const paced = {
2565
2722
  "cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS3}`
@@ -2638,6 +2795,10 @@ function bankRail(gateway, config) {
2638
2795
  };
2639
2796
  }
2640
2797
  function lightningRail(gateway, config) {
2798
+ const through = verifiedThrough(config);
2799
+ return through === null ? mintedByGateway(gateway, config) : resolvedHere(gateway, config, through);
2800
+ }
2801
+ function mintedByGateway(gateway, config) {
2641
2802
  return async (order) => {
2642
2803
  const payment = await gateway.mint(
2643
2804
  {
@@ -2661,21 +2822,20 @@ function lightningRail(gateway, config) {
2661
2822
  };
2662
2823
  };
2663
2824
  }
2664
- function blindLightningRail(gateway, config) {
2825
+ function resolvedHere(gateway, config, through) {
2665
2826
  return async (order) => {
2666
2827
  const resolved2 = await invoiceFrom(
2667
2828
  config.paidTo,
2668
2829
  await msatForOrder(order, config.amount, config.rate),
2669
2830
  config.send
2670
2831
  );
2671
- const relay = config.relayThrough;
2672
2832
  const watched = await gateway.watch({
2673
2833
  paymentHash: resolved2.paymentHash,
2674
- verifyUrl: relay ? await relayedVerifyUrl(
2675
- relay.endpoint,
2834
+ verifyUrl: await relayedVerifyUrl(
2835
+ through.endpoint,
2676
2836
  { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
2677
- relay.secret
2678
- ) : resolved2.verifyUrl,
2837
+ through.secret
2838
+ ),
2679
2839
  expiresAt: resolved2.expiresAt,
2680
2840
  trigger: config.trigger,
2681
2841
  replay: config.replay,
@@ -2718,16 +2878,12 @@ var Rails = class {
2718
2878
  this.gateway = gateway;
2719
2879
  }
2720
2880
  gateway;
2721
- /** Lightning, with the gateway minting against a priority list of addresses */
2722
- lightning(config) {
2723
- return lightningRail(this.gateway, config);
2724
- }
2725
2881
  /**
2726
- * Lightning, with the invoice resolved here so the gateway is told neither the
2727
- * address nor the amount
2882
+ * Lightning against a priority list of addresses, verified through your
2883
+ * `serve.lightningVerify`, or minted by the gateway when you say `gatewayMints`
2728
2884
  */
2729
- blindLightning(config) {
2730
- return blindLightningRail(this.gateway, config);
2885
+ lightning(config) {
2886
+ return lightningRail(this.gateway, config);
2731
2887
  }
2732
2888
  /** A bank transfer, proved the way a Lightning payment is */
2733
2889
  bank(config) {
@@ -2793,11 +2949,12 @@ function waitingOf(options) {
2793
2949
  // src/trigger.ts
2794
2950
  var NONCE_BYTES3 = 16;
2795
2951
  function lnurlPayEndpoint(gateway, config) {
2952
+ const through = verifiedThrough(config);
2796
2953
  return async (request) => {
2797
2954
  const url = new URL(request.url);
2798
2955
  const asked = url.searchParams.get("to");
2799
2956
  try {
2800
- return asked === null ? await offer(gateway, config, url) : await mint(gateway, config, url, asked);
2957
+ return asked === null ? await offer(gateway, config, through, url) : await mint(gateway, config, through, url, asked);
2801
2958
  } catch (failure) {
2802
2959
  return refuse(failure instanceof Error ? failure.message : "the trigger could not be served");
2803
2960
  }
@@ -2815,10 +2972,10 @@ function watchTicketEndpoint(gateway, config) {
2815
2972
  function publicWatchTicketEndpoint(gateway, config) {
2816
2973
  return () => issue(gateway, config);
2817
2974
  }
2818
- async function offer(gateway, config, url) {
2975
+ async function offer(gateway, config, through, url) {
2819
2976
  const { least, most } = await spread(config.amount);
2820
- const winner = await quoted(gateway, config, least);
2821
- const nonce = randomNonce();
2977
+ const winner = await quoted(gateway, config, through, least);
2978
+ const nonce = randomNonce2();
2822
2979
  const callback = new URL(config.baseUrl ?? `${url.origin}${url.pathname}`);
2823
2980
  callback.searchParams.set("to", winner.address);
2824
2981
  callback.searchParams.set("least", String(least));
@@ -2833,8 +2990,8 @@ async function offer(gateway, config, url) {
2833
2990
  maxSendable: most
2834
2991
  });
2835
2992
  }
2836
- async function quoted(gateway, config, amountMsat) {
2837
- if (config.blind) {
2993
+ async function quoted(gateway, config, through, amountMsat) {
2994
+ if (through !== null) {
2838
2995
  const { won } = await quote(
2839
2996
  config.send ?? pinnedToTheAddressWeVerified,
2840
2997
  listed(config.paidTo),
@@ -2860,7 +3017,7 @@ async function spread(amount) {
2860
3017
  }
2861
3018
  return { least, most };
2862
3019
  }
2863
- async function mint(gateway, config, url, address) {
3020
+ async function mint(gateway, config, through, url, address) {
2864
3021
  const least = Number(url.searchParams.get("least"));
2865
3022
  const most = Number(url.searchParams.get("most"));
2866
3023
  const nonce = url.searchParams.get("n") ?? "";
@@ -2879,7 +3036,7 @@ async function mint(gateway, config, url, address) {
2879
3036
  least === most ? `this trigger costs exactly ${least} msat` : `this trigger takes between ${least} and ${most} msat`
2880
3037
  );
2881
3038
  }
2882
- const minted = config.blind ? await mintBlind(gateway, config, address, amountMsat) : await mintThroughGateway(gateway, config, address, amountMsat, nonce);
3039
+ const minted = through === null ? await mintThroughGateway(gateway, config, address, amountMsat, nonce) : await mintHere(gateway, config, through, address, amountMsat);
2883
3040
  return Response.json({
2884
3041
  status: "OK",
2885
3042
  pr: minted.bolt11,
@@ -2894,7 +3051,7 @@ async function mintThroughGateway(gateway, config, address, amountMsat, nonce) {
2894
3051
  );
2895
3052
  return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
2896
3053
  }
2897
- async function mintBlind(gateway, config, address, amountMsat) {
3054
+ async function mintHere(gateway, config, through, address, amountMsat) {
2898
3055
  const resolved2 = await resolve(
2899
3056
  config.send ?? pinnedToTheAddressWeVerified,
2900
3057
  [address],
@@ -2902,15 +3059,14 @@ async function mintBlind(gateway, config, address, amountMsat) {
2902
3059
  );
2903
3060
  const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
2904
3061
  const locked = config.sealed;
2905
- const relay = config.relayThrough;
2906
3062
  try {
2907
3063
  await gateway.watch({
2908
3064
  paymentHash: resolved2.paymentHash,
2909
- verifyUrl: relay ? await relayedVerifyUrl(
2910
- relay.endpoint,
3065
+ verifyUrl: await relayedVerifyUrl(
3066
+ through.endpoint,
2911
3067
  { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
2912
- relay.secret
2913
- ) : resolved2.verifyUrl,
3068
+ through.secret
3069
+ ),
2914
3070
  expiresAt: resolved2.expiresAt,
2915
3071
  trigger: config.watchSecret,
2916
3072
  replay: config.replay,
@@ -2932,7 +3088,7 @@ function alreadyWatched(refused) {
2932
3088
  function sign(secret, address, least, most, nonce) {
2933
3089
  return hmacHex(secret, `lnurl-callback|${address}|${least}|${most}|${nonce}`);
2934
3090
  }
2935
- function randomNonce() {
3091
+ function randomNonce2() {
2936
3092
  const bytes = crypto.getRandomValues(new Uint8Array(NONCE_BYTES3));
2937
3093
  let hex = "";
2938
3094
  for (const byte of bytes) {
@@ -2986,14 +3142,6 @@ var Serve = class {
2986
3142
  lightningVerify(config) {
2987
3143
  return lightningVerifyEndpoint(config);
2988
3144
  }
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
3145
  /** The verify endpoint a bank rail is polled at, answering off your own statement */
2998
3146
  bankVerify(config) {
2999
3147
  return bankVerifyEndpoint(config);
@@ -3015,7 +3163,7 @@ var Serve = class {
3015
3163
  */
3016
3164
  webhook(handlers) {
3017
3165
  const options = { toleranceSecs: handlers.toleranceSecs, url: handlers.url };
3018
- const once = onceWhileReplayable(handlers.toleranceSecs ?? DEFAULT_TOLERANCE_SECS);
3166
+ const once = onceWhileReplayable(handlers.toleranceSecs ?? TOLERANCE_SECS);
3019
3167
  return async (request) => {
3020
3168
  const credential = await this.credential(handlers);
3021
3169
  const challenge = await answerWebhookChallenge(request, credential, options);
@@ -3865,7 +4013,6 @@ export {
3865
4013
  WrapRefusedError,
3866
4014
  agreesWithItself,
3867
4015
  answerVerifyChallenge,
3868
- carriesProof,
3869
4016
  decodeInvoice,
3870
4017
  fiat,
3871
4018
  invoiceFrom,