thunder-bridge 0.8.2 → 0.8.4

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/README.md CHANGED
@@ -106,7 +106,7 @@ them and this table does not repeat them.
106
106
 
107
107
  | Export | What it does |
108
108
  |---|---|
109
- | `lnurlPayEndpoint(config)` | a whole LNURL-pay endpoint as one Fetch handler, so a static QR points at your domain |
109
+ | `lnurlPayEndpoint(config)` | a whole LNURL-pay endpoint as one Fetch handler, so a static QR points at your domain. From `thunder-bridge/server` |
110
110
  | `seal(secret, plaintext)`, `unseal` | the blob the gateway stores and cannot read |
111
111
  | `toLnurl(url)` | bech32-encode an endpoint url |
112
112
  | `bankTransfer(params)` | register a Czech QR platba as a watched payment. Refuses a gateway that serves strangers |
@@ -127,7 +127,7 @@ whatever runtime you keep state in.
127
127
  |---|---|
128
128
  | `bankRail(config)` | a `Rail` selling for a bank transfer, reading back through any `Statement` |
129
129
  | `lightningRail(config)` | a `Rail` where the gateway mints the invoice, so it learns the address and the amount |
130
- | `blindLightningRail(config)` | a `Rail` that resolves the address itself and tells the gateway only a hash |
130
+ | `blindLightningRail(config)` | a `Rail` that resolves the address itself and tells the gateway only a hash. From `thunder-bridge/server` |
131
131
 
132
132
  `Rail` is `(order: Order) => Promise<Leg>`. Everything that differs between rails
133
133
  is bound once when the rail is built, so the only thing passed per sale is which
@@ -166,7 +166,8 @@ const tipJar = lnurlToSvg("https://agora.gripe/tip");
166
166
  ```
167
167
 
168
168
  **Webhooks** - [`src/webhook.ts`](src/webhook.ts): `parseWebhookRequest`,
169
- `parseWebhook`, `verifyWebhookSignature`. See [Webhooks](#webhooks).
169
+ `parseWebhook`, `parseWatchedWebhookRequest`, `parseWatchedWebhook`,
170
+ `verifyWebhookSignature`. See [Webhooks](#webhooks).
170
171
 
171
172
  **Errors** - [`src/errors.ts`](src/errors.ts): `ProblemError`,
172
173
  `NoWalletAvailableError`, `GatewayCheatError`, `UnverifiedRecipientError`,
@@ -225,6 +226,27 @@ such as `nas`, the trailing-dot `localhost.`, and anything whose last label is
225
226
 
226
227
  It vets the first hop only. See below.
227
228
 
229
+ ### Which transfer counts as paying
230
+
231
+ `bankVerifyEndpoint` calls a credit a settlement when the amount and the currency
232
+ match exactly and the reference appears anywhere in what the payer wrote,
233
+ case-insensitively. With `fioStatement` "what the payer wrote" is four Fio columns
234
+ joined: the variable symbol, the user identification, the message for the recipient
235
+ and the payer's own reference. So a bank that prefixes, appends, or moves the text
236
+ between those fields still settles.
237
+
238
+ Two shapes do not settle, and both leave the payment `pending` while the money is
239
+ already in the account:
240
+
241
+ - **A shortened reference.** The match asks whether the reference is inside what the
242
+ bank forwarded, not the other way round, so a bank that truncates it never matches.
243
+ - **A payer whose bank forwards nothing but a numeric variable symbol.** The
244
+ reference is alphanumeric and cannot travel in a numeric field, and the match does
245
+ not read `X-VS` as an alternative.
246
+
247
+ Neither has been seen with Fio, which forwards the message untouched. Check it
248
+ against the banks your payers actually use before you promise them a rail.
249
+
228
250
  ## What is still trusted
229
251
 
230
252
  - **The gateway chooses which of your addresses gets paid.** Nothing here can
@@ -315,9 +337,9 @@ try {
315
337
 
316
338
  ## Webhooks
317
339
 
318
- Pass `webhookUrl` and optionally `webhookSecret` when you create a payment. Once
319
- it reaches `paid` the gateway POSTs the same JSON the API returns, so the body is
320
- a `Payment`. Every delivery carries `x-timestamp`, and with a secret set
340
+ Pass `webhookUrl` and optionally `webhookSecret` when you create a payment, or on
341
+ any rail. Once it reaches `paid` the gateway POSTs the same JSON the API returns,
342
+ so the body is a `Payment`. Every delivery carries `x-timestamp`, and with a secret set
321
343
  `x-signature: sha256=<hmac>` over `<timestamp>.<body>` rather than the body alone,
322
344
  so a captured delivery cannot be replayed at you later. Six attempts on a widening
323
345
  backoff, then it parks. An invoice that expires fires nothing.
@@ -339,6 +361,29 @@ app.post("/hooks/paid", async (context) => {
339
361
  });
340
362
  ```
341
363
 
364
+ ### A watched payment sends a different body
365
+
366
+ `bankRail` and `blindLightningRail` register a payment the gateway was told almost
367
+ nothing about, so its webhook carries no address, no amount and no invoice. That is
368
+ not a `Payment`, and `parseWebhook` answers `null` for it, which looks exactly like a
369
+ bad signature. Use `parseWatchedWebhookRequest` there instead and you get a
370
+ `TriggerEvent`, the shape `getWatched` hands back.
371
+
372
+ ```ts
373
+ import { parseWatchedWebhookRequest } from "thunder-bridge";
374
+
375
+ app.post("/hooks/bank", async (context) => {
376
+ const settled = await parseWatchedWebhookRequest(context.req.raw, secret);
377
+ if (settled === null) return context.text("bad signature", 401);
378
+
379
+ await fulfil(settled.id, settled.preimage);
380
+ return context.text("ok");
381
+ });
382
+ ```
383
+
384
+ Give each rail its own path, as above, and neither endpoint has to guess which body
385
+ it was handed.
386
+
342
387
  `parseWebhookRequest` refuses anything more than five minutes out of date,
343
388
  adjustable with `toleranceSecs`. The signature proves the body came from someone
344
389
  holding your secret. It does not prove the payment happened, since the gateway