thunder-bridge 2.2.0 → 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/bank.js CHANGED
@@ -121,6 +121,14 @@ var INITIAL_STATE = new Uint32Array([
121
121
  1541459225
122
122
  ]);
123
123
 
124
+ // ../core/signature.ts
125
+ import {
126
+ parseDictionary,
127
+ serializeDictionary,
128
+ serializeInnerList,
129
+ serializeString
130
+ } from "structured-headers";
131
+
124
132
  // ../core/sealed.ts
125
133
  var MIN_SECRET_CHARS = 32;
126
134
  function refuseAWeakSecret(secret) {
@@ -223,7 +231,7 @@ function fioStatement(config) {
223
231
  const paceMs = tokenWindowMs / usedAt.size;
224
232
  let lastRead = Number.NEGATIVE_INFINITY;
225
233
  let credits = [];
226
- return async (sinceUnix) => {
234
+ const read = async (sinceUnix) => {
227
235
  const now = Date.now();
228
236
  if (now - lastRead < paceMs) {
229
237
  return credits;
@@ -249,10 +257,17 @@ function fioStatement(config) {
249
257
  if (!answer.ok) {
250
258
  throw new Error(`fio answered ${answer.status} reading the statement`);
251
259
  }
252
- const read = await answer.json();
253
- credits = (read.accountStatement?.transactionList?.transaction ?? []).filter(isCredit).map(asCredit);
260
+ const statement = await answer.json();
261
+ const reads = statement.accountStatement?.info?.iban;
262
+ if (config.iban !== void 0 && accountOf(reads ?? "") !== accountOf(config.iban)) {
263
+ throw new Error(
264
+ `a fio token reads ${reads ?? "an account it does not name"}, not ${config.iban}`
265
+ );
266
+ }
267
+ credits = (statement.accountStatement?.transactionList?.transaction ?? []).filter(isCredit).map(asCredit);
254
268
  return credits;
255
269
  };
270
+ return Object.assign(read, { freshEverySecs: Math.ceil(paceMs / MILLIS) });
256
271
  }
257
272
  function longestUnused(usedAt) {
258
273
  let token = "";
package/dist/index.cjs CHANGED
@@ -41,7 +41,6 @@ __export(src_exports, {
41
41
  WrapRefusedError: () => WrapRefusedError,
42
42
  agreesWithItself: () => agreesWithItself,
43
43
  answerVerifyChallenge: () => answerVerifyChallenge,
44
- carriesProof: () => carriesProof,
45
44
  decodeInvoice: () => decodeInvoice,
46
45
  fiat: () => fiat,
47
46
  invoiceFrom: () => invoiceFrom,
@@ -185,9 +184,9 @@ function fillSchedule(schedule, reader, block) {
185
184
  schedule[at] = reader.getUint32(block + at * 4, false);
186
185
  }
187
186
  for (let at = 16; at < SCHEDULE_WORDS; at++) {
188
- const recent2 = schedule[at - 15];
187
+ const recent = schedule[at - 15];
189
188
  const distant = schedule[at - 2];
190
- const mixedRecent = rotate(recent2, 7) ^ rotate(recent2, 18) ^ recent2 >>> 3;
189
+ const mixedRecent = rotate(recent, 7) ^ rotate(recent, 18) ^ recent >>> 3;
191
190
  const mixedDistant = rotate(distant, 17) ^ rotate(distant, 19) ^ distant >>> 10;
192
191
  schedule[at] = schedule[at - 16] + mixedRecent + schedule[at - 7] + mixedDistant | 0;
193
192
  }
@@ -442,7 +441,7 @@ async function sealFor(purpose, secret, plaintext, { stable = false } = {}) {
442
441
  }
443
442
  async function unsealFor(purpose, secret, sealed) {
444
443
  if (!sealed.startsWith(`${PURPOSE_VERSION}.`)) {
445
- return await unseal(secret, sealed);
444
+ return null;
446
445
  }
447
446
  return await opened(
448
447
  await keyFor(secret, `${INFO}/${purpose}`),
@@ -985,13 +984,182 @@ function asBuffer2(bytes) {
985
984
  return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
986
985
  }
987
986
 
988
- // ../core/caller.ts
989
- var KEY_HEADER = "x-client-key";
990
- var SIGNATURE_HEADER = "x-signature";
991
- var TIMESTAMP_HEADER = "x-timestamp";
992
- var NONCE_HEADER = "x-nonce";
993
- var PREFIX = "ed25519=";
987
+ // ../core/signature.ts
988
+ var import_structured_headers = require("structured-headers");
989
+ var TOLERANCE_SECS = 300;
990
+ var LABEL = "thunder-bridge";
991
+ var ALGORITHM2 = "ed25519";
994
992
  var NONCE_BYTES = 16;
993
+ var SIGNATURE_BYTES2 = 64;
994
+ var PUBLIC_KEY = /^[0-9a-f]{64}$/i;
995
+ var CLIENT_SIGNS = {
996
+ tag: "thunder-bridge-client",
997
+ covers: ["@method", "@authority", "@path", "@query", "content-digest"]
998
+ };
999
+ var GATEWAY_SIGNS = {
1000
+ tag: "thunder-bridge-gateway",
1001
+ covers: ["@method", "@scheme", "@authority", "@path", "content-digest"]
1002
+ };
1003
+ function contentDigest(body) {
1004
+ return (0, import_structured_headers.serializeDictionary)({
1005
+ "sha-256": [Uint8Array.from(sha256(new TextEncoder().encode(body))), /* @__PURE__ */ new Map()]
1006
+ });
1007
+ }
1008
+ function signatureBase(message, covered) {
1009
+ const lines = [];
1010
+ const seen = /* @__PURE__ */ new Set();
1011
+ for (const [name, parameters] of covered[0]) {
1012
+ if (typeof name !== "string" || parameters.size > 0 || seen.has(name)) {
1013
+ return null;
1014
+ }
1015
+ seen.add(name);
1016
+ const value = componentValue(message, name);
1017
+ if (value === null) {
1018
+ return null;
1019
+ }
1020
+ lines.push(`${(0, import_structured_headers.serializeString)(name)}: ${value}`);
1021
+ }
1022
+ lines.push(`"@signature-params": ${(0, import_structured_headers.serializeInnerList)(covered)}`);
1023
+ return lines.join("\n");
1024
+ }
1025
+ async function signedBy(key, profile, message, body, { created = unixNow(), nonce = randomNonce() } = {}) {
1026
+ const digest = contentDigest(body);
1027
+ const covered = [
1028
+ profile.covers.map((name) => [name, /* @__PURE__ */ new Map()]),
1029
+ /* @__PURE__ */ new Map([
1030
+ ["created", created],
1031
+ ["nonce", nonce],
1032
+ ["keyid", key.publicKeyHex],
1033
+ ["alg", ALGORITHM2],
1034
+ ["tag", profile.tag]
1035
+ ])
1036
+ ];
1037
+ const base = signatureBase(
1038
+ { ...message, headers: new Headers({ "content-digest": digest }) },
1039
+ covered
1040
+ );
1041
+ if (base === null) {
1042
+ throw new Error(`${message.url} cannot be signed as ${profile.tag}`);
1043
+ }
1044
+ const signature = Uint8Array.from(hexToBytes(await key.sign(new TextEncoder().encode(base))));
1045
+ return {
1046
+ "content-digest": digest,
1047
+ "signature-input": (0, import_structured_headers.serializeDictionary)(/* @__PURE__ */ new Map([[LABEL, covered]])),
1048
+ signature: (0, import_structured_headers.serializeDictionary)(/* @__PURE__ */ new Map([[LABEL, [signature, /* @__PURE__ */ new Map()]]]))
1049
+ };
1050
+ }
1051
+ async function signerOf(message, body, profile, toleranceSecs = TOLERANCE_SECS) {
1052
+ const found = chosen(message.headers, profile.tag);
1053
+ if (found === null) {
1054
+ return null;
1055
+ }
1056
+ const [components, parameters] = found.covered;
1057
+ const named = components.map(([name]) => name);
1058
+ if (!profile.covers.every((name) => named.includes(name))) {
1059
+ return null;
1060
+ }
1061
+ const keyid = parameters.get("keyid");
1062
+ const created = parameters.get("created");
1063
+ const nonce = parameters.get("nonce");
1064
+ const alg = parameters.get("alg");
1065
+ if (typeof keyid !== "string" || !PUBLIC_KEY.test(keyid)) {
1066
+ return null;
1067
+ }
1068
+ if (typeof created !== "number" || !Number.isInteger(created)) {
1069
+ return null;
1070
+ }
1071
+ if (Math.abs(unixNow() - created) > toleranceSecs) {
1072
+ return null;
1073
+ }
1074
+ if (alg !== void 0 && alg !== ALGORITHM2 || nonce !== void 0 && typeof nonce !== "string") {
1075
+ return null;
1076
+ }
1077
+ if (!digestMatches(message.headers.get("content-digest"), body)) {
1078
+ return null;
1079
+ }
1080
+ const base = signatureBase(message, found.covered);
1081
+ if (base === null) {
1082
+ return null;
1083
+ }
1084
+ const signer = keyid.toLowerCase();
1085
+ const proven = await verifyHex(
1086
+ signer,
1087
+ bytesToHex(found.signature),
1088
+ new TextEncoder().encode(base)
1089
+ );
1090
+ return proven ? { keyid: signer, created, nonce: nonce ?? null } : null;
1091
+ }
1092
+ function componentValue(message, name) {
1093
+ let url;
1094
+ try {
1095
+ url = new URL(message.url);
1096
+ } catch {
1097
+ return null;
1098
+ }
1099
+ switch (name) {
1100
+ case "@method":
1101
+ return message.method;
1102
+ case "@authority":
1103
+ return (message.authority ?? url.host).toLowerCase();
1104
+ case "@scheme":
1105
+ return url.protocol.slice(0, -1).toLowerCase();
1106
+ case "@path":
1107
+ return url.pathname === "" ? "/" : url.pathname;
1108
+ case "@query":
1109
+ return url.search === "" ? "?" : url.search;
1110
+ default:
1111
+ return name.startsWith("@") ? null : message.headers.get(name)?.trim() ?? null;
1112
+ }
1113
+ }
1114
+ function chosen(headers, tag) {
1115
+ const inputs = dictionaryOf(headers.get("signature-input"));
1116
+ const signatures = dictionaryOf(headers.get("signature"));
1117
+ if (inputs === null || signatures === null) {
1118
+ return null;
1119
+ }
1120
+ for (const [label, member] of inputs) {
1121
+ if (!isInnerList(member) || member[1].get("tag") !== tag) {
1122
+ continue;
1123
+ }
1124
+ const value = signatures.get(label);
1125
+ if (value === void 0 || isInnerList(value) || !(value[0] instanceof ArrayBuffer)) {
1126
+ return null;
1127
+ }
1128
+ const signature = new Uint8Array(value[0]);
1129
+ return signature.length === SIGNATURE_BYTES2 ? { covered: member, signature } : null;
1130
+ }
1131
+ return null;
1132
+ }
1133
+ function digestMatches(field, body) {
1134
+ const digest = dictionaryOf(field)?.get("sha-256");
1135
+ if (digest === void 0 || isInnerList(digest) || !(digest[0] instanceof ArrayBuffer)) {
1136
+ return false;
1137
+ }
1138
+ const sent = new Uint8Array(digest[0]);
1139
+ const counted = sha256(new TextEncoder().encode(body));
1140
+ return sent.length === counted.length && sent.every((byte, at) => byte === counted[at]);
1141
+ }
1142
+ function dictionaryOf(field) {
1143
+ if (field === null) {
1144
+ return null;
1145
+ }
1146
+ try {
1147
+ return (0, import_structured_headers.parseDictionary)(field);
1148
+ } catch {
1149
+ return null;
1150
+ }
1151
+ }
1152
+ function isInnerList(member) {
1153
+ return Array.isArray(member[0]);
1154
+ }
1155
+ function randomNonce() {
1156
+ return bytesToHex(crypto.getRandomValues(new Uint8Array(NONCE_BYTES)));
1157
+ }
1158
+ function unixNow() {
1159
+ return Math.floor(Date.now() / 1e3);
1160
+ }
1161
+
1162
+ // ../core/caller.ts
995
1163
  var SIGNING_LABEL = "request-signing";
996
1164
  async function callerKey(secret) {
997
1165
  return await signingKeyFromSeed(hexToBytes(await hmacHex(secret, SIGNING_LABEL)));
@@ -1000,20 +1168,11 @@ function paymentNamedBy(caller, paymentHash) {
1000
1168
  return sha256Hex(`${caller}.payment-id.${paymentHash}`);
1001
1169
  }
1002
1170
  async function signedAs(key, method, path, body, host) {
1003
- const timestamp = String(Math.floor(Date.now() / 1e3));
1004
- const nonce = bytesToHex(crypto.getRandomValues(new Uint8Array(NONCE_BYTES)));
1005
- return {
1006
- [KEY_HEADER]: key.publicKeyHex,
1007
- [TIMESTAMP_HEADER]: timestamp,
1008
- [NONCE_HEADER]: nonce,
1009
- [SIGNATURE_HEADER]: `${PREFIX}${await key.sign(spokenTo(host, timestamp, nonce, method, path, body))}`
1010
- };
1011
- }
1012
- function spokenTo(host, timestamp, nonce, method, path, body) {
1013
- return new TextEncoder().encode(
1014
- ["v2", host.toLowerCase(), timestamp, nonce, method.toUpperCase(), path, sha256Hex(body)].join(
1015
- "\n"
1016
- )
1171
+ return await signedBy(
1172
+ key,
1173
+ CLIENT_SIGNS,
1174
+ { method, url: `https://${host}${path}`, authority: host },
1175
+ body
1017
1176
  );
1018
1177
  }
1019
1178
 
@@ -1289,6 +1448,7 @@ var NoWalletAvailable = class extends Error {
1289
1448
 
1290
1449
  // ../core/lnurl.ts
1291
1450
  var VERIFY_WITHOUT_PREIMAGE = ["zeuspay.com", "zeusnuts.com", "ecash.love"];
1451
+ var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
1292
1452
  var RESOLVE_TIMEOUT_MS = 3e4;
1293
1453
  var NAMES_ONE_ACCOUNT = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
1294
1454
  var MIN_PACE_SECS = 1;
@@ -1350,6 +1510,12 @@ async function probe(send, address, amountMsat, deadline) {
1350
1510
  if (pay.tag !== "payRequest" || !pay.callback) {
1351
1511
  throw new WalletRefused("unreachable", `${address} answered with no payRequest`);
1352
1512
  }
1513
+ if (!namesTheAddress(pay.metadata, address)) {
1514
+ throw new WalletRefused(
1515
+ "cannot-prove-delivery",
1516
+ `${address} serves metadata that does not name it, so no invoice binds to it`
1517
+ );
1518
+ }
1353
1519
  if (amountMsat < pay.minSendable || amountMsat > pay.maxSendable) {
1354
1520
  throw new WalletRefused(
1355
1521
  "amount-not-accepted",
@@ -1442,6 +1608,19 @@ function ceilingAskedFor(headers) {
1442
1608
  }
1443
1609
  return Math.min(Math.max(perWindow / window, MIN_PER_SECOND), MAX_PER_SECOND);
1444
1610
  }
1611
+ function namesTheAddress(metadata, address) {
1612
+ if (typeof metadata !== "string") {
1613
+ return false;
1614
+ }
1615
+ try {
1616
+ const entries = JSON.parse(metadata);
1617
+ return Array.isArray(entries) && entries.some(
1618
+ (entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && entry[1].toLowerCase() === address.toLowerCase()
1619
+ );
1620
+ } catch {
1621
+ return false;
1622
+ }
1623
+ }
1445
1624
  function cannotReleaseAPreimage(host) {
1446
1625
  const lowered = host.toLowerCase();
1447
1626
  return VERIFY_WITHOUT_PREIMAGE.some(
@@ -1556,18 +1735,9 @@ function headersOf(raw) {
1556
1735
  return headers;
1557
1736
  }
1558
1737
 
1559
- // ../core/delivery.ts
1560
- function deliverySigned(url, timestamp, body) {
1561
- const endpoint = new URL(url);
1562
- return new TextEncoder().encode(
1563
- ["v2", `${endpoint.origin}${endpoint.pathname}`, timestamp, body].join("\n")
1564
- );
1565
- }
1566
-
1567
1738
  // src/verify.ts
1568
1739
  var HTTP_TIMEOUT_MS2 = 15e3;
1569
1740
  var ISSUED_SKEW_SECS = 300;
1570
- var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
1571
1741
  var DEFAULT_WRAP_PROPORTION = 0.01;
1572
1742
  var DEFAULT_WRAP_BASE_MSAT = 1e3;
1573
1743
  async function proveOrigin(payment, asked, askedAt) {
@@ -1614,16 +1784,6 @@ async function proveOrigin(payment, asked, askedAt) {
1614
1784
  throw cheat("invoice_settled");
1615
1785
  }
1616
1786
  }
1617
- function namesTheAddress(metadata, address) {
1618
- try {
1619
- const entries = JSON.parse(metadata);
1620
- return Array.isArray(entries) && entries.some(
1621
- (entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && equalIgnoringCase(entry[1], address)
1622
- );
1623
- } catch {
1624
- return false;
1625
- }
1626
- }
1627
1787
  async function proveSettlement(payment, asked) {
1628
1788
  await proveOrigin(payment, asked);
1629
1789
  const verified = await reachable(payment.verifyUrl, payment);
@@ -1647,7 +1807,6 @@ function agreesWithItself(report) {
1647
1807
  }
1648
1808
  return preimageMatchesHash(report.preimage, report.paymentHash);
1649
1809
  }
1650
- var carriesProof = agreesWithItself;
1651
1810
  function wrapFeeCeiling(amountMsat, allowance) {
1652
1811
  const proportional = amountMsat * (allowance?.proportion ?? DEFAULT_WRAP_PROPORTION);
1653
1812
  return msat(Math.max(allowance?.baseMsat ?? DEFAULT_WRAP_BASE_MSAT, Math.floor(proportional)));
@@ -1929,10 +2088,6 @@ function isStatus(value) {
1929
2088
  }
1930
2089
 
1931
2090
  // src/webhook.ts
1932
- var SIGNATURE_HEADER2 = "x-signature-v2";
1933
- var TIMESTAMP_HEADER2 = "x-timestamp";
1934
- var GATEWAY_KEY_PREFIX = "ed25519=";
1935
- var DEFAULT_TOLERANCE_SECS = 300;
1936
2091
  var CHALLENGE = "webhook-challenge";
1937
2092
  var VERIFY_CHALLENGE = "verify-challenge";
1938
2093
  async function readPayment(request, credential, options = {}) {
@@ -1956,24 +2111,14 @@ async function answerVerifyChallenge(request) {
1956
2111
  return nonce === null ? null : answered2(nonce);
1957
2112
  }
1958
2113
  async function believable(request, credential, options) {
1959
- const signature = request.headers.get(SIGNATURE_HEADER2);
1960
- const timestamp = request.headers.get(TIMESTAMP_HEADER2);
1961
- if (signature === null || timestamp === null) {
1962
- return null;
1963
- }
1964
- if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) {
1965
- return null;
1966
- }
1967
- if (!signature.startsWith(GATEWAY_KEY_PREFIX)) {
1968
- return null;
1969
- }
1970
2114
  const body = await request.clone().text();
1971
- const signed = await verifyHex(
1972
- credential.publicKey.toLowerCase(),
1973
- signature.slice(GATEWAY_KEY_PREFIX.length).toLowerCase(),
1974
- deliverySigned(options.url ?? request.url, timestamp, body)
2115
+ const signer = await signerOf(
2116
+ { method: request.method, url: options.url ?? request.url, headers: request.headers },
2117
+ body,
2118
+ GATEWAY_SIGNS,
2119
+ options.toleranceSecs
1975
2120
  );
1976
- return signed ? body : null;
2121
+ return signer !== null && signer.keyid === credential.publicKey.toLowerCase() ? body : null;
1977
2122
  }
1978
2123
  function proved(read) {
1979
2124
  return read !== null && read.status === "paid" && !agreesWithItself(read) ? null : read;
@@ -2001,13 +2146,6 @@ function nonceOf2(text3, type) {
2001
2146
  return null;
2002
2147
  }
2003
2148
  }
2004
- function recent(timestamp, toleranceSecs) {
2005
- const sent = Number(timestamp);
2006
- if (!Number.isFinite(sent)) {
2007
- return false;
2008
- }
2009
- return Math.abs(Math.floor(Date.now() / 1e3) - sent) <= toleranceSecs;
2010
- }
2011
2149
 
2012
2150
  // src/bank.ts
2013
2151
  var DEFAULT_CURRENCY = "CZK";
@@ -2049,7 +2187,7 @@ function bankVerifyEndpoint(config) {
2049
2187
  throw new Error(`${config.iban} is not an IBAN, so this endpoint answers for no account`);
2050
2188
  }
2051
2189
  const paced = {
2052
- "cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
2190
+ "cache-control": `max-age=${config.pollEverySecs ?? config.statement.freshEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
2053
2191
  };
2054
2192
  return async (request) => {
2055
2193
  const consented = await answerVerifyChallenge(request);
@@ -2086,7 +2224,7 @@ async function answeredAt(params, subject) {
2086
2224
  return polling.toString();
2087
2225
  }
2088
2226
  async function creditedPreimage(params) {
2089
- const since = unixNow() - (params.lookBackSecs ?? DEFAULT_LOOK_BACK_SECS);
2227
+ const since = unixNow2() - (params.lookBackSecs ?? DEFAULT_LOOK_BACK_SECS);
2090
2228
  const landed = (await params.statement(since)).some((credit) => pays(credit, params.asked));
2091
2229
  if (!landed) {
2092
2230
  return null;
@@ -2184,7 +2322,7 @@ function refuseUnusable(params, iban, currency) {
2184
2322
  function accountOf(iban) {
2185
2323
  return iban.replace(/\s+/g, "").toUpperCase();
2186
2324
  }
2187
- function unixNow() {
2325
+ function unixNow2() {
2188
2326
  return Math.floor(Date.now() / 1e3);
2189
2327
  }
2190
2328
 
@@ -2620,6 +2758,19 @@ function nwcRail(gateway, config) {
2620
2758
  // src/relay.ts
2621
2759
  var DEFAULT_POLL_EVERY_SECS3 = 5;
2622
2760
  var WALLET = "w";
2761
+ function verifiedThrough(path) {
2762
+ const through = path.verifyThrough ?? null;
2763
+ const mints = path.gatewayMints === true;
2764
+ if (through !== null && mints) {
2765
+ throw new Error("verifyThrough and gatewayMints are two different paths, name only one");
2766
+ }
2767
+ if (through === null && !mints) {
2768
+ throw new Error(
2769
+ "name verifyThrough, where your serve.lightningVerify is mounted, or gatewayMints: true to let the gateway mint and poll the wallet itself"
2770
+ );
2771
+ }
2772
+ return through;
2773
+ }
2623
2774
  function lightningVerifyEndpoint(config) {
2624
2775
  const paced = {
2625
2776
  "cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS3}`
@@ -2698,6 +2849,10 @@ function bankRail(gateway, config) {
2698
2849
  };
2699
2850
  }
2700
2851
  function lightningRail(gateway, config) {
2852
+ const through = verifiedThrough(config);
2853
+ return through === null ? mintedByGateway(gateway, config) : resolvedHere(gateway, config, through);
2854
+ }
2855
+ function mintedByGateway(gateway, config) {
2701
2856
  return async (order) => {
2702
2857
  const payment = await gateway.mint(
2703
2858
  {
@@ -2721,21 +2876,20 @@ function lightningRail(gateway, config) {
2721
2876
  };
2722
2877
  };
2723
2878
  }
2724
- function blindLightningRail(gateway, config) {
2879
+ function resolvedHere(gateway, config, through) {
2725
2880
  return async (order) => {
2726
2881
  const resolved2 = await invoiceFrom(
2727
2882
  config.paidTo,
2728
2883
  await msatForOrder(order, config.amount, config.rate),
2729
2884
  config.send
2730
2885
  );
2731
- const relay = config.relayThrough;
2732
2886
  const watched = await gateway.watch({
2733
2887
  paymentHash: resolved2.paymentHash,
2734
- verifyUrl: relay ? await relayedVerifyUrl(
2735
- relay.endpoint,
2888
+ verifyUrl: await relayedVerifyUrl(
2889
+ through.endpoint,
2736
2890
  { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
2737
- relay.secret
2738
- ) : resolved2.verifyUrl,
2891
+ through.secret
2892
+ ),
2739
2893
  expiresAt: resolved2.expiresAt,
2740
2894
  trigger: config.trigger,
2741
2895
  replay: config.replay,
@@ -2778,16 +2932,12 @@ var Rails = class {
2778
2932
  this.gateway = gateway;
2779
2933
  }
2780
2934
  gateway;
2781
- /** Lightning, with the gateway minting against a priority list of addresses */
2782
- lightning(config) {
2783
- return lightningRail(this.gateway, config);
2784
- }
2785
2935
  /**
2786
- * Lightning, with the invoice resolved here so the gateway is told neither the
2787
- * address nor the amount
2936
+ * Lightning against a priority list of addresses, verified through your
2937
+ * `serve.lightningVerify`, or minted by the gateway when you say `gatewayMints`
2788
2938
  */
2789
- blindLightning(config) {
2790
- return blindLightningRail(this.gateway, config);
2939
+ lightning(config) {
2940
+ return lightningRail(this.gateway, config);
2791
2941
  }
2792
2942
  /** A bank transfer, proved the way a Lightning payment is */
2793
2943
  bank(config) {
@@ -2853,11 +3003,12 @@ function waitingOf(options) {
2853
3003
  // src/trigger.ts
2854
3004
  var NONCE_BYTES3 = 16;
2855
3005
  function lnurlPayEndpoint(gateway, config) {
3006
+ const through = verifiedThrough(config);
2856
3007
  return async (request) => {
2857
3008
  const url = new URL(request.url);
2858
3009
  const asked = url.searchParams.get("to");
2859
3010
  try {
2860
- return asked === null ? await offer(gateway, config, url) : await mint(gateway, config, url, asked);
3011
+ return asked === null ? await offer(gateway, config, through, url) : await mint(gateway, config, through, url, asked);
2861
3012
  } catch (failure) {
2862
3013
  return refuse(failure instanceof Error ? failure.message : "the trigger could not be served");
2863
3014
  }
@@ -2875,10 +3026,10 @@ function watchTicketEndpoint(gateway, config) {
2875
3026
  function publicWatchTicketEndpoint(gateway, config) {
2876
3027
  return () => issue(gateway, config);
2877
3028
  }
2878
- async function offer(gateway, config, url) {
3029
+ async function offer(gateway, config, through, url) {
2879
3030
  const { least, most } = await spread(config.amount);
2880
- const winner = await quoted(gateway, config, least);
2881
- const nonce = randomNonce();
3031
+ const winner = await quoted(gateway, config, through, least);
3032
+ const nonce = randomNonce2();
2882
3033
  const callback = new URL(config.baseUrl ?? `${url.origin}${url.pathname}`);
2883
3034
  callback.searchParams.set("to", winner.address);
2884
3035
  callback.searchParams.set("least", String(least));
@@ -2893,8 +3044,8 @@ async function offer(gateway, config, url) {
2893
3044
  maxSendable: most
2894
3045
  });
2895
3046
  }
2896
- async function quoted(gateway, config, amountMsat) {
2897
- if (config.blind) {
3047
+ async function quoted(gateway, config, through, amountMsat) {
3048
+ if (through !== null) {
2898
3049
  const { won } = await quote(
2899
3050
  config.send ?? pinnedToTheAddressWeVerified,
2900
3051
  listed(config.paidTo),
@@ -2920,7 +3071,7 @@ async function spread(amount) {
2920
3071
  }
2921
3072
  return { least, most };
2922
3073
  }
2923
- async function mint(gateway, config, url, address) {
3074
+ async function mint(gateway, config, through, url, address) {
2924
3075
  const least = Number(url.searchParams.get("least"));
2925
3076
  const most = Number(url.searchParams.get("most"));
2926
3077
  const nonce = url.searchParams.get("n") ?? "";
@@ -2939,7 +3090,7 @@ async function mint(gateway, config, url, address) {
2939
3090
  least === most ? `this trigger costs exactly ${least} msat` : `this trigger takes between ${least} and ${most} msat`
2940
3091
  );
2941
3092
  }
2942
- const minted = config.blind ? await mintBlind(gateway, config, address, amountMsat) : await mintThroughGateway(gateway, config, address, amountMsat, nonce);
3093
+ const minted = through === null ? await mintThroughGateway(gateway, config, address, amountMsat, nonce) : await mintHere(gateway, config, through, address, amountMsat);
2943
3094
  return Response.json({
2944
3095
  status: "OK",
2945
3096
  pr: minted.bolt11,
@@ -2954,7 +3105,7 @@ async function mintThroughGateway(gateway, config, address, amountMsat, nonce) {
2954
3105
  );
2955
3106
  return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
2956
3107
  }
2957
- async function mintBlind(gateway, config, address, amountMsat) {
3108
+ async function mintHere(gateway, config, through, address, amountMsat) {
2958
3109
  const resolved2 = await resolve(
2959
3110
  config.send ?? pinnedToTheAddressWeVerified,
2960
3111
  [address],
@@ -2962,15 +3113,14 @@ async function mintBlind(gateway, config, address, amountMsat) {
2962
3113
  );
2963
3114
  const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
2964
3115
  const locked = config.sealed;
2965
- const relay = config.relayThrough;
2966
3116
  try {
2967
3117
  await gateway.watch({
2968
3118
  paymentHash: resolved2.paymentHash,
2969
- verifyUrl: relay ? await relayedVerifyUrl(
2970
- relay.endpoint,
3119
+ verifyUrl: await relayedVerifyUrl(
3120
+ through.endpoint,
2971
3121
  { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
2972
- relay.secret
2973
- ) : resolved2.verifyUrl,
3122
+ through.secret
3123
+ ),
2974
3124
  expiresAt: resolved2.expiresAt,
2975
3125
  trigger: config.watchSecret,
2976
3126
  replay: config.replay,
@@ -2992,7 +3142,7 @@ function alreadyWatched(refused) {
2992
3142
  function sign(secret, address, least, most, nonce) {
2993
3143
  return hmacHex(secret, `lnurl-callback|${address}|${least}|${most}|${nonce}`);
2994
3144
  }
2995
- function randomNonce() {
3145
+ function randomNonce2() {
2996
3146
  const bytes = crypto.getRandomValues(new Uint8Array(NONCE_BYTES3));
2997
3147
  let hex = "";
2998
3148
  for (const byte of bytes) {
@@ -3046,14 +3196,6 @@ var Serve = class {
3046
3196
  lightningVerify(config) {
3047
3197
  return lightningVerifyEndpoint(config);
3048
3198
  }
3049
- /**
3050
- * What `lightningVerify` was called before 2.2.0
3051
- *
3052
- * @deprecated Use `lightningVerify`, beside `bankVerify` and `nwcVerify`
3053
- */
3054
- verify(config) {
3055
- return this.lightningVerify(config);
3056
- }
3057
3199
  /** The verify endpoint a bank rail is polled at, answering off your own statement */
3058
3200
  bankVerify(config) {
3059
3201
  return bankVerifyEndpoint(config);
@@ -3075,7 +3217,7 @@ var Serve = class {
3075
3217
  */
3076
3218
  webhook(handlers) {
3077
3219
  const options = { toleranceSecs: handlers.toleranceSecs, url: handlers.url };
3078
- const once = onceWhileReplayable(handlers.toleranceSecs ?? DEFAULT_TOLERANCE_SECS);
3220
+ const once = onceWhileReplayable(handlers.toleranceSecs ?? TOLERANCE_SECS);
3079
3221
  return async (request) => {
3080
3222
  const credential = await this.credential(handlers);
3081
3223
  const challenge = await answerWebhookChallenge(request, credential, options);
@@ -3926,7 +4068,6 @@ var Gateways = class {
3926
4068
  WrapRefusedError,
3927
4069
  agreesWithItself,
3928
4070
  answerVerifyChallenge,
3929
- carriesProof,
3930
4071
  decodeInvoice,
3931
4072
  fiat,
3932
4073
  invoiceFrom,
package/dist/index.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { R as Resolved } from './qr-CF-YeXU1.cjs';
2
- import { T as ThunderBridge, r as ThunderBridgeOptions, W as WaitOptions } from './bank-Bg6RjuO-.cjs';
3
- export { A as AttendOptions, s as BankRailConfig, d as BankVerifyConfig, t as BlindLightningRailConfig, u as CreateOptions, F as FollowOptions, H as Handler, L as Leg, v as LightningRailConfig, w as LightningVerifyConfig, M as Minted, N as NwcConnection, h as NwcRailConfig, i as NwcVerifyConfig, O as Order, P as PaymentRequest, x as PaymentRequestInit, y as PaymentRequestOptions, z as Provable, D as Proven, R as Rail, E as RailConfig, G as Rails, I as Range, J as Relayed, K as SelfConsistent, Q as Send, U as Serve, V as TicketOptions, X as TriggerConfig, Y as WatchTicketConfig, Z as WebhookCredential, _ as WebhookHandlers, $ as WebhookOptions, a0 as WrapAllowance, a1 as agreesWithItself, a2 as answerVerifyChallenge, a3 as carriesProof, a4 as invoiceFrom, a5 as proveOrigin, a6 as proveSettlement, a7 as proveWrapped, a8 as relayedVerifyUrl, a9 as wrapFeeCeiling } from './bank-Bg6RjuO-.cjs';
2
+ import { T as ThunderBridge, f as ThunderBridgeOptions, W as WaitOptions } from './nwc-h_-cGvMb.cjs';
3
+ export { A as AttendOptions, g as BankRailConfig, d as BankVerifyConfig, h as CreateOptions, F as FollowOptions, H as Handler, L as Leg, i as LightningRailConfig, j as LightningRailSettings, k as LightningVerifyConfig, M as Minted, N as NwcConnection, l as NwcRailConfig, m as NwcVerifyConfig, O as Order, P as PaymentRequest, n as PaymentRequestInit, o as PaymentRequestOptions, p as Provable, R as Rail, q as RailConfig, r as Rails, s as Range, t as Relayed, u as SelfConsistent, v as Send, w as Serve, x as TicketOptions, y as TriggerConfig, z as TriggerSettings, V as VerifyPath, D as VerifyThrough, E as WatchTicketConfig, G as WebhookCredential, I as WebhookHandlers, J as WebhookOptions, K as WrapAllowance, Q as agreesWithItself, U as answerVerifyChallenge, X as invoiceFrom, Y as proveOrigin, Z as proveSettlement, _ as proveWrapped, $ as relayedVerifyUrl, a0 as wrapFeeCeiling } from './nwc-h_-cGvMb.cjs';
4
4
  import { H as Handover, P as Payment, e as Held } from './types-DYZ9EkmJ.cjs';
5
5
  export { A as Amount, C as Charge, F as FiatOptions, f as MintedPayment, g as Msat, h as PaymentStatus, i as Priced, Q as Quote, S as Settlement, j as SocketTicket, W as WalletFailure, l as WalletReason, n as WatchedPayment, o as fiat, p as msat, s as sats } from './types-DYZ9EkmJ.cjs';
6
6
  import { G as GatewayCheatError } from './errors-DJmsalYZ.cjs';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { R as Resolved } from './qr-CF-YeXU1.js';
2
- import { T as ThunderBridge, r as ThunderBridgeOptions, W as WaitOptions } from './bank-B5MxT_6u.js';
3
- export { A as AttendOptions, s as BankRailConfig, d as BankVerifyConfig, t as BlindLightningRailConfig, u as CreateOptions, F as FollowOptions, H as Handler, L as Leg, v as LightningRailConfig, w as LightningVerifyConfig, M as Minted, N as NwcConnection, h as NwcRailConfig, i as NwcVerifyConfig, O as Order, P as PaymentRequest, x as PaymentRequestInit, y as PaymentRequestOptions, z as Provable, D as Proven, R as Rail, E as RailConfig, G as Rails, I as Range, J as Relayed, K as SelfConsistent, Q as Send, U as Serve, V as TicketOptions, X as TriggerConfig, Y as WatchTicketConfig, Z as WebhookCredential, _ as WebhookHandlers, $ as WebhookOptions, a0 as WrapAllowance, a1 as agreesWithItself, a2 as answerVerifyChallenge, a3 as carriesProof, a4 as invoiceFrom, a5 as proveOrigin, a6 as proveSettlement, a7 as proveWrapped, a8 as relayedVerifyUrl, a9 as wrapFeeCeiling } from './bank-B5MxT_6u.js';
2
+ import { T as ThunderBridge, f as ThunderBridgeOptions, W as WaitOptions } from './nwc-BivXGSPY.js';
3
+ export { A as AttendOptions, g as BankRailConfig, d as BankVerifyConfig, h as CreateOptions, F as FollowOptions, H as Handler, L as Leg, i as LightningRailConfig, j as LightningRailSettings, k as LightningVerifyConfig, M as Minted, N as NwcConnection, l as NwcRailConfig, m as NwcVerifyConfig, O as Order, P as PaymentRequest, n as PaymentRequestInit, o as PaymentRequestOptions, p as Provable, R as Rail, q as RailConfig, r as Rails, s as Range, t as Relayed, u as SelfConsistent, v as Send, w as Serve, x as TicketOptions, y as TriggerConfig, z as TriggerSettings, V as VerifyPath, D as VerifyThrough, E as WatchTicketConfig, G as WebhookCredential, I as WebhookHandlers, J as WebhookOptions, K as WrapAllowance, Q as agreesWithItself, U as answerVerifyChallenge, X as invoiceFrom, Y as proveOrigin, Z as proveSettlement, _ as proveWrapped, $ as relayedVerifyUrl, a0 as wrapFeeCeiling } from './nwc-BivXGSPY.js';
4
4
  import { H as Handover, P as Payment, e as Held } from './types-DYZ9EkmJ.js';
5
5
  export { A as Amount, C as Charge, F as FiatOptions, f as MintedPayment, g as Msat, h as PaymentStatus, i as Priced, Q as Quote, S as Settlement, j as SocketTicket, W as WalletFailure, l as WalletReason, n as WatchedPayment, o as fiat, p as msat, s as sats } from './types-DYZ9EkmJ.js';
6
6
  import { G as GatewayCheatError } from './errors-VX0R18oE.js';