thunder-bridge 0.8.9 → 1.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.
@@ -23,7 +23,6 @@ interface CreatePaymentParams {
23
23
  lnAddresses: string[];
24
24
  amountMsat: number;
25
25
  webhookUrl?: string;
26
- webhookSecret?: string;
27
26
  }
28
27
  /**
29
28
  * `lnAddresses` is the same priority list `createPayment` takes, and quoting it
@@ -78,7 +77,6 @@ interface WatchPaymentParams {
78
77
  trigger?: string;
79
78
  sealed?: string;
80
79
  webhookUrl?: string;
81
- webhookSecret?: string;
82
80
  }
83
81
  /** Why one wallet in the list could not be used */
84
82
  type WalletReason = "address-unusable" | "unreachable" | "amount-not-accepted" | "cannot-prove-delivery" | "invoice-refused";
@@ -86,6 +84,17 @@ interface WalletFailure {
86
84
  address: string;
87
85
  reason: WalletReason;
88
86
  }
87
+ /**
88
+ * What a delivery carries. Everything needed to act on a settlement and to check
89
+ * it, and nothing else, so a retry is the same size every time
90
+ */
91
+ interface Settlement {
92
+ id: string;
93
+ status: PaymentStatus;
94
+ paymentHash: string;
95
+ preimage: string | null;
96
+ settledAt: number;
97
+ }
89
98
 
90
99
  interface ThunderBridgeOptions {
91
100
  /**
@@ -101,6 +110,13 @@ interface ThunderBridgeOptions {
101
110
  * through a ticket
102
111
  */
103
112
  token?: string;
113
+ /**
114
+ * The same long lived server side secret your rail derives its preimages from.
115
+ * Given here, every call carries a signature the gateway reads as your identity,
116
+ * so a payment you create is handed back to you and to nobody else. Withheld,
117
+ * you are anonymous and any holder of an id can read what it names
118
+ */
119
+ secret?: string;
104
120
  }
105
121
  interface WaitOptions {
106
122
  /** Give up when this aborts, `AbortSignal.timeout(ms)` covers the usual case */
@@ -159,7 +175,9 @@ declare class ThunderBridge {
159
175
  private readonly baseUrl;
160
176
  private readonly verify;
161
177
  private readonly token;
178
+ private readonly secret;
162
179
  private strangers;
180
+ private speaks;
163
181
  constructor(baseUrl: string, options?: ThunderBridgeOptions);
164
182
  /**
165
183
  * Whether a token was given, which is what makes an instance yours: a gateway
@@ -262,6 +280,14 @@ declare class ThunderBridge {
262
280
  * the watcher needs goes in `sealed`, which the gateway cannot read
263
281
  */
264
282
  watchPayment(params: WatchPaymentParams): Promise<TriggerEvent>;
283
+ /**
284
+ * What this payment is called, which you can work out before any gateway has
285
+ * heard of it. Every gateway you hand the same invoice to answers with the same
286
+ * name, so watching at several of them adds up to one payment rather than
287
+ * several, and no gateway's key is in the answer. Null when no secret was given,
288
+ * because then the gateway names the payment and only it can
289
+ */
290
+ nameFor(paymentHash: string): Promise<string | null>;
265
291
  /**
266
292
  * Follow every payment made to one trigger, replayed from the recent ones on
267
293
  * connect and then live, reconnecting on its own until the returned function
@@ -272,6 +298,7 @@ declare class ThunderBridge {
272
298
  private wsTicket;
273
299
  private sending;
274
300
  private reading;
301
+ private speaking;
275
302
  private proven;
276
303
  private checked;
277
304
  }
@@ -324,7 +351,6 @@ interface BankRailConfig {
324
351
  /** Up to ten digits, for accounting systems that still want one */
325
352
  variableSymbol?: (order: Order) => string | undefined;
326
353
  webhookUrl?: string;
327
- webhookSecret?: string;
328
354
  /** Register on a gateway you do not own anyway, on the terms `bankTransfer` sets out */
329
355
  allowPublicGateway?: boolean;
330
356
  /** What `Leg.rail` reads, for a shop running more than one account */
@@ -345,7 +371,6 @@ interface LightningRailConfig {
345
371
  */
346
372
  idempotencyKey?: (order: Order) => string | undefined;
347
373
  webhookUrl?: string;
348
- webhookSecret?: string;
349
374
  /** What `Leg.rail` reads, for a shop running more than one wallet */
350
375
  name?: string;
351
376
  }
@@ -361,7 +386,6 @@ interface BlindLightningRailConfig {
361
386
  /** Only a watched leg has anywhere to carry this */
362
387
  sealed?: (order: Order) => string | Promise<string>;
363
388
  webhookUrl?: string;
364
- webhookSecret?: string;
365
389
  /**
366
390
  * Where your own `lightningVerifyEndpoint` is mounted, and the secret it
367
391
  * unseals with. Set both and the gateway is handed your URL rather than the
@@ -397,4 +421,4 @@ declare function lightningRail(config: LightningRailConfig): Rail;
397
421
  */
398
422
  declare function blindLightningRail(config: BlindLightningRailConfig): Rail;
399
423
 
400
- export { type BankRailConfig as B, type CreatePaymentParams as C, type FollowOptions as F, type Leg as L, type Order as O, type Payment as P, type Quote as Q, type Rail as R, ThunderBridge as T, type WalletFailure as W, type TriggerEvent as a, type CreateOptions as b, type CreateQuoteParams as c, type LightningRailConfig as d, type PaymentKind as e, type PaymentStatus as f, type ThunderBridgeOptions as g, type WaitOptions as h, type WalletReason as i, type WatchPaymentParams as j, bankRail as k, lightningRail as l, type BlindLightningRailConfig as m, blindLightningRail as n };
424
+ export { type BankRailConfig as B, type CreatePaymentParams as C, type FollowOptions as F, type Leg as L, type Order as O, type Payment as P, type Quote as Q, type Rail as R, type Settlement as S, ThunderBridge as T, type WatchPaymentParams as W, type ThunderBridgeOptions as a, type TriggerEvent as b, type WaitOptions as c, type WalletFailure as d, type CreateOptions as e, type CreateQuoteParams as f, type LightningRailConfig as g, type PaymentKind as h, type PaymentStatus as i, type WalletReason as j, bankRail as k, lightningRail as l, type BlindLightningRailConfig as m, blindLightningRail as n };
@@ -23,7 +23,6 @@ interface CreatePaymentParams {
23
23
  lnAddresses: string[];
24
24
  amountMsat: number;
25
25
  webhookUrl?: string;
26
- webhookSecret?: string;
27
26
  }
28
27
  /**
29
28
  * `lnAddresses` is the same priority list `createPayment` takes, and quoting it
@@ -78,7 +77,6 @@ interface WatchPaymentParams {
78
77
  trigger?: string;
79
78
  sealed?: string;
80
79
  webhookUrl?: string;
81
- webhookSecret?: string;
82
80
  }
83
81
  /** Why one wallet in the list could not be used */
84
82
  type WalletReason = "address-unusable" | "unreachable" | "amount-not-accepted" | "cannot-prove-delivery" | "invoice-refused";
@@ -86,6 +84,17 @@ interface WalletFailure {
86
84
  address: string;
87
85
  reason: WalletReason;
88
86
  }
87
+ /**
88
+ * What a delivery carries. Everything needed to act on a settlement and to check
89
+ * it, and nothing else, so a retry is the same size every time
90
+ */
91
+ interface Settlement {
92
+ id: string;
93
+ status: PaymentStatus;
94
+ paymentHash: string;
95
+ preimage: string | null;
96
+ settledAt: number;
97
+ }
89
98
 
90
99
  interface ThunderBridgeOptions {
91
100
  /**
@@ -101,6 +110,13 @@ interface ThunderBridgeOptions {
101
110
  * through a ticket
102
111
  */
103
112
  token?: string;
113
+ /**
114
+ * The same long lived server side secret your rail derives its preimages from.
115
+ * Given here, every call carries a signature the gateway reads as your identity,
116
+ * so a payment you create is handed back to you and to nobody else. Withheld,
117
+ * you are anonymous and any holder of an id can read what it names
118
+ */
119
+ secret?: string;
104
120
  }
105
121
  interface WaitOptions {
106
122
  /** Give up when this aborts, `AbortSignal.timeout(ms)` covers the usual case */
@@ -159,7 +175,9 @@ declare class ThunderBridge {
159
175
  private readonly baseUrl;
160
176
  private readonly verify;
161
177
  private readonly token;
178
+ private readonly secret;
162
179
  private strangers;
180
+ private speaks;
163
181
  constructor(baseUrl: string, options?: ThunderBridgeOptions);
164
182
  /**
165
183
  * Whether a token was given, which is what makes an instance yours: a gateway
@@ -262,6 +280,14 @@ declare class ThunderBridge {
262
280
  * the watcher needs goes in `sealed`, which the gateway cannot read
263
281
  */
264
282
  watchPayment(params: WatchPaymentParams): Promise<TriggerEvent>;
283
+ /**
284
+ * What this payment is called, which you can work out before any gateway has
285
+ * heard of it. Every gateway you hand the same invoice to answers with the same
286
+ * name, so watching at several of them adds up to one payment rather than
287
+ * several, and no gateway's key is in the answer. Null when no secret was given,
288
+ * because then the gateway names the payment and only it can
289
+ */
290
+ nameFor(paymentHash: string): Promise<string | null>;
265
291
  /**
266
292
  * Follow every payment made to one trigger, replayed from the recent ones on
267
293
  * connect and then live, reconnecting on its own until the returned function
@@ -272,6 +298,7 @@ declare class ThunderBridge {
272
298
  private wsTicket;
273
299
  private sending;
274
300
  private reading;
301
+ private speaking;
275
302
  private proven;
276
303
  private checked;
277
304
  }
@@ -324,7 +351,6 @@ interface BankRailConfig {
324
351
  /** Up to ten digits, for accounting systems that still want one */
325
352
  variableSymbol?: (order: Order) => string | undefined;
326
353
  webhookUrl?: string;
327
- webhookSecret?: string;
328
354
  /** Register on a gateway you do not own anyway, on the terms `bankTransfer` sets out */
329
355
  allowPublicGateway?: boolean;
330
356
  /** What `Leg.rail` reads, for a shop running more than one account */
@@ -345,7 +371,6 @@ interface LightningRailConfig {
345
371
  */
346
372
  idempotencyKey?: (order: Order) => string | undefined;
347
373
  webhookUrl?: string;
348
- webhookSecret?: string;
349
374
  /** What `Leg.rail` reads, for a shop running more than one wallet */
350
375
  name?: string;
351
376
  }
@@ -361,7 +386,6 @@ interface BlindLightningRailConfig {
361
386
  /** Only a watched leg has anywhere to carry this */
362
387
  sealed?: (order: Order) => string | Promise<string>;
363
388
  webhookUrl?: string;
364
- webhookSecret?: string;
365
389
  /**
366
390
  * Where your own `lightningVerifyEndpoint` is mounted, and the secret it
367
391
  * unseals with. Set both and the gateway is handed your URL rather than the
@@ -397,4 +421,4 @@ declare function lightningRail(config: LightningRailConfig): Rail;
397
421
  */
398
422
  declare function blindLightningRail(config: BlindLightningRailConfig): Rail;
399
423
 
400
- export { type BankRailConfig as B, type CreatePaymentParams as C, type FollowOptions as F, type Leg as L, type Order as O, type Payment as P, type Quote as Q, type Rail as R, ThunderBridge as T, type WalletFailure as W, type TriggerEvent as a, type CreateOptions as b, type CreateQuoteParams as c, type LightningRailConfig as d, type PaymentKind as e, type PaymentStatus as f, type ThunderBridgeOptions as g, type WaitOptions as h, type WalletReason as i, type WatchPaymentParams as j, bankRail as k, lightningRail as l, type BlindLightningRailConfig as m, blindLightningRail as n };
424
+ export { type BankRailConfig as B, type CreatePaymentParams as C, type FollowOptions as F, type Leg as L, type Order as O, type Payment as P, type Quote as Q, type Rail as R, type Settlement as S, ThunderBridge as T, type WatchPaymentParams as W, type ThunderBridgeOptions as a, type TriggerEvent as b, type WaitOptions as c, type WalletFailure as d, type CreateOptions as e, type CreateQuoteParams as f, type LightningRailConfig as g, type PaymentKind as h, type PaymentStatus as i, type WalletReason as j, bankRail as k, lightningRail as l, type BlindLightningRailConfig as m, blindLightningRail as n };
package/dist/server.cjs CHANGED
@@ -402,20 +402,22 @@ var REDIRECTS = [301, 302, 303, 307, 308];
402
402
  var KEEPS_THE_METHOD = [307, 308];
403
403
  var CREDENTIALS = ["authorization", "cookie", "proxy-authorization"];
404
404
  var BODY_LIMIT_BYTES = 262144;
405
+ var throughFetch = (url, sent, signal) => fetch(url, {
406
+ method: sent.method ?? "GET",
407
+ headers: sent.headers ?? {},
408
+ body: sent.body,
409
+ redirect: "manual",
410
+ signal
411
+ });
412
+ var send = throughFetch;
405
413
  async function ask(url, sent = {}) {
406
414
  const capped = AbortSignal.timeout(HTTP_TIMEOUT_MS);
407
415
  const signal = sent.deadline ? AbortSignal.any([sent.deadline, capped]) : capped;
408
416
  let target = url;
409
417
  let carried = sent;
410
418
  for (let hop = 0; hop <= HOPS_FOLLOWED; hop += 1) {
411
- await refuseUnlessPublic(target);
412
- const response = await fetch(target, {
413
- method: carried.method ?? "GET",
414
- headers: carried.headers ?? {},
415
- body: carried.body,
416
- redirect: "manual",
417
- signal
418
- });
419
+ const at = await addressesToReach(target);
420
+ const response = await send(target, carried, signal, at);
419
421
  if (!REDIRECTS.includes(response.status)) return await answerOf(response);
420
422
  const location = response.headers.get("location");
421
423
  if (location === null) {
@@ -429,7 +431,15 @@ async function ask(url, sent = {}) {
429
431
  }
430
432
  throw new Error(`${url} redirected more than ${HOPS_FOLLOWED} times`);
431
433
  }
432
- async function resolvesNothingPrivate(url) {
434
+ async function addressesToReach(url) {
435
+ if (!publicHttps(url)) throw new Error(`${url} is not a public https URL`);
436
+ const found = await resolved(url);
437
+ if (found.length === 0 || !found.every((one) => publicAddress(one.address))) {
438
+ throw new Error(`${url} resolves to an address we do not reach`);
439
+ }
440
+ return found;
441
+ }
442
+ async function resolved(url) {
433
443
  const { lookup } = await import("dns/promises");
434
444
  const { setTimeout: sleep } = await import("timers/promises");
435
445
  const host = new URL(url).hostname.replace(/^\[|]$/g, "");
@@ -437,11 +447,7 @@ async function resolvesNothingPrivate(url) {
437
447
  lookup(host, { all: true, verbatim: true }).catch(() => []),
438
448
  sleep(LOOKUP_TIMEOUT_MS, null, { ref: false })
439
449
  ]);
440
- return found !== null && found.every((one) => publicAddress(one.address));
441
- }
442
- async function refuseUnlessPublic(url) {
443
- if (!publicHttps(url)) throw new Error(`${url} is not a public https URL`);
444
- if (!await resolvesNothingPrivate(url)) throw new Error(`${url} resolves to an address we do not reach`);
450
+ return found ?? [];
445
451
  }
446
452
  async function answerOf(response) {
447
453
  const { status, ok, headers } = response;
@@ -808,21 +814,21 @@ async function mintThroughGateway(config, address, amountMsat, nonce) {
808
814
  return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
809
815
  }
810
816
  async function mintBlind(config, address, amountMsat) {
811
- const resolved = await resolve([address], amountMsat);
812
- const minted = { ...resolved, amountMsat, lnAddress: resolved.address };
817
+ const resolved2 = await resolve([address], amountMsat);
818
+ const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
813
819
  const locked = config.sealed;
814
820
  try {
815
821
  await config.gateway.watchPayment({
816
- paymentHash: resolved.paymentHash,
817
- verifyUrl: resolved.verifyUrl,
818
- expiresAt: resolved.expiresAt,
822
+ paymentHash: resolved2.paymentHash,
823
+ verifyUrl: resolved2.verifyUrl,
824
+ expiresAt: resolved2.expiresAt,
819
825
  trigger: config.watchSecret,
820
826
  sealed: locked ? await seal(locked.secret, JSON.stringify(locked.data(minted))) : void 0
821
827
  });
822
828
  } catch (refused) {
823
829
  if (!alreadyWatched(refused)) throw refused;
824
830
  }
825
- return { bolt11: resolved.bolt11, verifyUrl: resolved.verifyUrl };
831
+ return { bolt11: resolved2.bolt11, verifyUrl: resolved2.verifyUrl };
826
832
  }
827
833
  function alreadyWatched(refused) {
828
834
  return refused instanceof ProblemError && isProblemType(refused, PAYMENT_ALREADY_WATCHED);
@@ -908,27 +914,26 @@ async function relayedVerifyUrl(endpoint, wallet, secret) {
908
914
  var LIGHTNING = "lightning";
909
915
  function blindLightningRail(config) {
910
916
  return async (order) => {
911
- const resolved = await invoiceFrom(config.lnAddresses, await config.amountMsat(order));
917
+ const resolved2 = await invoiceFrom(config.lnAddresses, await config.amountMsat(order));
912
918
  const relay = config.relayVerifyThrough;
913
919
  const watched = await config.gateway.watchPayment({
914
- paymentHash: resolved.paymentHash,
920
+ paymentHash: resolved2.paymentHash,
915
921
  verifyUrl: relay ? await relayedVerifyUrl(
916
922
  relay.endpoint,
917
- { url: resolved.verifyUrl, hash: resolved.paymentHash },
923
+ { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
918
924
  relay.secret
919
- ) : resolved.verifyUrl,
920
- expiresAt: resolved.expiresAt,
925
+ ) : resolved2.verifyUrl,
926
+ expiresAt: resolved2.expiresAt,
921
927
  trigger: config.trigger,
922
928
  sealed: await config.sealed?.(order),
923
- webhookUrl: config.webhookUrl,
924
- webhookSecret: config.webhookSecret
929
+ webhookUrl: config.webhookUrl
925
930
  });
926
931
  return {
927
932
  id: watched.id,
928
933
  rail: config.name ?? LIGHTNING,
929
- scan: resolved.bolt11,
930
- qr: encodeForQr(resolved.bolt11),
931
- expiresAt: resolved.expiresAt
934
+ scan: resolved2.bolt11,
935
+ qr: encodeForQr(resolved2.bolt11),
936
+ expiresAt: resolved2.expiresAt
932
937
  };
933
938
  };
934
939
  }
package/dist/server.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as ThunderBridge } from './rail-J8QoYbSr.cjs';
2
- export { m as BlindLightningRailConfig, n as blindLightningRail } from './rail-J8QoYbSr.cjs';
1
+ import { T as ThunderBridge } from './rail-CL9QkiHo.cjs';
2
+ export { m as BlindLightningRailConfig, n as blindLightningRail } from './rail-CL9QkiHo.cjs';
3
3
 
4
4
  interface TriggerConfig {
5
5
  /** The gateway that quotes the addresses and mints the invoice */
package/dist/server.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as ThunderBridge } from './rail-J8QoYbSr.js';
2
- export { m as BlindLightningRailConfig, n as blindLightningRail } from './rail-J8QoYbSr.js';
1
+ import { T as ThunderBridge } from './rail-CL9QkiHo.js';
2
+ export { m as BlindLightningRailConfig, n as blindLightningRail } from './rail-CL9QkiHo.js';
3
3
 
4
4
  interface TriggerConfig {
5
5
  /** The gateway that quotes the addresses and mints the invoice */
package/dist/server.js CHANGED
@@ -363,20 +363,22 @@ var REDIRECTS = [301, 302, 303, 307, 308];
363
363
  var KEEPS_THE_METHOD = [307, 308];
364
364
  var CREDENTIALS = ["authorization", "cookie", "proxy-authorization"];
365
365
  var BODY_LIMIT_BYTES = 262144;
366
+ var throughFetch = (url, sent, signal) => fetch(url, {
367
+ method: sent.method ?? "GET",
368
+ headers: sent.headers ?? {},
369
+ body: sent.body,
370
+ redirect: "manual",
371
+ signal
372
+ });
373
+ var send = throughFetch;
366
374
  async function ask(url, sent = {}) {
367
375
  const capped = AbortSignal.timeout(HTTP_TIMEOUT_MS);
368
376
  const signal = sent.deadline ? AbortSignal.any([sent.deadline, capped]) : capped;
369
377
  let target = url;
370
378
  let carried = sent;
371
379
  for (let hop = 0; hop <= HOPS_FOLLOWED; hop += 1) {
372
- await refuseUnlessPublic(target);
373
- const response = await fetch(target, {
374
- method: carried.method ?? "GET",
375
- headers: carried.headers ?? {},
376
- body: carried.body,
377
- redirect: "manual",
378
- signal
379
- });
380
+ const at = await addressesToReach(target);
381
+ const response = await send(target, carried, signal, at);
380
382
  if (!REDIRECTS.includes(response.status)) return await answerOf(response);
381
383
  const location = response.headers.get("location");
382
384
  if (location === null) {
@@ -390,7 +392,15 @@ async function ask(url, sent = {}) {
390
392
  }
391
393
  throw new Error(`${url} redirected more than ${HOPS_FOLLOWED} times`);
392
394
  }
393
- async function resolvesNothingPrivate(url) {
395
+ async function addressesToReach(url) {
396
+ if (!publicHttps(url)) throw new Error(`${url} is not a public https URL`);
397
+ const found = await resolved(url);
398
+ if (found.length === 0 || !found.every((one) => publicAddress(one.address))) {
399
+ throw new Error(`${url} resolves to an address we do not reach`);
400
+ }
401
+ return found;
402
+ }
403
+ async function resolved(url) {
394
404
  const { lookup } = await import("dns/promises");
395
405
  const { setTimeout: sleep } = await import("timers/promises");
396
406
  const host = new URL(url).hostname.replace(/^\[|]$/g, "");
@@ -398,11 +408,7 @@ async function resolvesNothingPrivate(url) {
398
408
  lookup(host, { all: true, verbatim: true }).catch(() => []),
399
409
  sleep(LOOKUP_TIMEOUT_MS, null, { ref: false })
400
410
  ]);
401
- return found !== null && found.every((one) => publicAddress(one.address));
402
- }
403
- async function refuseUnlessPublic(url) {
404
- if (!publicHttps(url)) throw new Error(`${url} is not a public https URL`);
405
- if (!await resolvesNothingPrivate(url)) throw new Error(`${url} resolves to an address we do not reach`);
411
+ return found ?? [];
406
412
  }
407
413
  async function answerOf(response) {
408
414
  const { status, ok, headers } = response;
@@ -769,21 +775,21 @@ async function mintThroughGateway(config, address, amountMsat, nonce) {
769
775
  return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
770
776
  }
771
777
  async function mintBlind(config, address, amountMsat) {
772
- const resolved = await resolve([address], amountMsat);
773
- const minted = { ...resolved, amountMsat, lnAddress: resolved.address };
778
+ const resolved2 = await resolve([address], amountMsat);
779
+ const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
774
780
  const locked = config.sealed;
775
781
  try {
776
782
  await config.gateway.watchPayment({
777
- paymentHash: resolved.paymentHash,
778
- verifyUrl: resolved.verifyUrl,
779
- expiresAt: resolved.expiresAt,
783
+ paymentHash: resolved2.paymentHash,
784
+ verifyUrl: resolved2.verifyUrl,
785
+ expiresAt: resolved2.expiresAt,
780
786
  trigger: config.watchSecret,
781
787
  sealed: locked ? await seal(locked.secret, JSON.stringify(locked.data(minted))) : void 0
782
788
  });
783
789
  } catch (refused) {
784
790
  if (!alreadyWatched(refused)) throw refused;
785
791
  }
786
- return { bolt11: resolved.bolt11, verifyUrl: resolved.verifyUrl };
792
+ return { bolt11: resolved2.bolt11, verifyUrl: resolved2.verifyUrl };
787
793
  }
788
794
  function alreadyWatched(refused) {
789
795
  return refused instanceof ProblemError && isProblemType(refused, PAYMENT_ALREADY_WATCHED);
@@ -869,27 +875,26 @@ async function relayedVerifyUrl(endpoint, wallet, secret) {
869
875
  var LIGHTNING = "lightning";
870
876
  function blindLightningRail(config) {
871
877
  return async (order) => {
872
- const resolved = await invoiceFrom(config.lnAddresses, await config.amountMsat(order));
878
+ const resolved2 = await invoiceFrom(config.lnAddresses, await config.amountMsat(order));
873
879
  const relay = config.relayVerifyThrough;
874
880
  const watched = await config.gateway.watchPayment({
875
- paymentHash: resolved.paymentHash,
881
+ paymentHash: resolved2.paymentHash,
876
882
  verifyUrl: relay ? await relayedVerifyUrl(
877
883
  relay.endpoint,
878
- { url: resolved.verifyUrl, hash: resolved.paymentHash },
884
+ { url: resolved2.verifyUrl, hash: resolved2.paymentHash },
879
885
  relay.secret
880
- ) : resolved.verifyUrl,
881
- expiresAt: resolved.expiresAt,
886
+ ) : resolved2.verifyUrl,
887
+ expiresAt: resolved2.expiresAt,
882
888
  trigger: config.trigger,
883
889
  sealed: await config.sealed?.(order),
884
- webhookUrl: config.webhookUrl,
885
- webhookSecret: config.webhookSecret
890
+ webhookUrl: config.webhookUrl
886
891
  });
887
892
  return {
888
893
  id: watched.id,
889
894
  rail: config.name ?? LIGHTNING,
890
- scan: resolved.bolt11,
891
- qr: encodeForQr(resolved.bolt11),
892
- expiresAt: resolved.expiresAt
895
+ scan: resolved2.bolt11,
896
+ qr: encodeForQr(resolved2.bolt11),
897
+ expiresAt: resolved2.expiresAt
893
898
  };
894
899
  };
895
900
  }
package/openapi.yaml CHANGED
@@ -2,7 +2,7 @@ openapi: 3.1.0
2
2
 
3
3
  info:
4
4
  title: thunder-bridge, the endpoint your own service serves
5
- version: 0.8.1
5
+ version: 1.0.0
6
6
  license:
7
7
  name: MIT
8
8
  identifier: MIT
@@ -207,6 +207,48 @@ paths:
207
207
  schema:
208
208
  $ref: "#/components/schemas/unsettled"
209
209
 
210
+ post:
211
+ tags: [bank-transfer]
212
+ operationId: answerVerifyChallenge
213
+ summary: Agree to be polled, before the gateway will watch anything pointed here
214
+ description: |
215
+ The gateway will not poll a URL a caller merely named. It POSTs a nonce here first and
216
+ watches nothing unless this endpoint hands the same nonce back, which is how a caller
217
+ shows the endpoint consented to the traffic rather than being volunteered for it. A
218
+ registration whose verify URL will not answer is refused with `verify-unconsented`.
219
+
220
+ Every verify endpoint the SDK builds answers this as its first line, before it parses
221
+ its own query, so `bankVerifyEndpoint`, `lightningVerifyEndpoint` and anything wrapping
222
+ `answerVerifyChallengeRequest` already do it.
223
+
224
+ There is nothing to sign. Echoing a nonce grants the asker nothing, so this holds no
225
+ secret and checks none.
226
+ requestBody:
227
+ required: true
228
+ content:
229
+ application/json:
230
+ schema:
231
+ type: object
232
+ properties:
233
+ type:
234
+ type: string
235
+ enum: [verify-challenge]
236
+ nonce:
237
+ type: string
238
+ pattern: "^[0-9a-f]{64}$"
239
+ required: [type, nonce]
240
+ responses:
241
+ "200":
242
+ description: The same nonce, and nothing else.
243
+ content:
244
+ application/json:
245
+ schema:
246
+ type: object
247
+ properties:
248
+ nonce:
249
+ type: string
250
+ required: [nonce]
251
+
210
252
  components:
211
253
  schemas:
212
254
  settlement:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thunder-bridge",
3
- "version": "0.8.9",
3
+ "version": "1.0.0",
4
4
  "description": "Trustless JavaScript client for the Thunder Bridge Lightning payment gateway. Proves the invoice came from your own wallet before the payer sees it.",
5
5
  "author": "i-am-fatik",
6
6
  "homepage": "https://agora.gripe/en/tools/thunder-bridge",