thunder-bridge 0.8.4 → 0.8.6
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 +56 -7
- package/dist/index.cjs +113 -12
- package/dist/index.d.cts +27 -8
- package/dist/index.d.ts +27 -8
- package/dist/index.js +111 -12
- package/dist/{rail-QZtUN1D-.d.cts → rail-CdKeyVGn.d.cts} +10 -1
- package/dist/{rail-QZtUN1D-.d.ts → rail-CdKeyVGn.d.ts} +10 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -167,7 +167,8 @@ const tipJar = lnurlToSvg("https://agora.gripe/tip");
|
|
|
167
167
|
|
|
168
168
|
**Webhooks** - [`src/webhook.ts`](src/webhook.ts): `parseWebhookRequest`,
|
|
169
169
|
`parseWebhook`, `parseWatchedWebhookRequest`, `parseWatchedWebhook`,
|
|
170
|
-
`verifyWebhookSignature
|
|
170
|
+
`verifyWebhookSignature`, `answerWebhookChallengeRequest`,
|
|
171
|
+
`answerWebhookChallenge`. See [Webhooks](#webhooks).
|
|
171
172
|
|
|
172
173
|
**Errors** - [`src/errors.ts`](src/errors.ts): `ProblemError`,
|
|
173
174
|
`NoWalletAvailableError`, `GatewayCheatError`, `UnverifiedRecipientError`,
|
|
@@ -339,17 +340,34 @@ try {
|
|
|
339
340
|
|
|
340
341
|
Pass `webhookUrl` and optionally `webhookSecret` when you create a payment, or on
|
|
341
342
|
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
|
|
343
|
-
`x-signature
|
|
344
|
-
|
|
345
|
-
|
|
343
|
+
so the body is a `Payment`. Every delivery carries `x-timestamp` and an
|
|
344
|
+
`x-signature` over `<timestamp>.<body>` rather than the body alone, so a captured
|
|
345
|
+
delivery cannot be replayed at you later. With a secret set it is
|
|
346
|
+
`sha256=<hmac>` keyed with that secret, and without one it is `ed25519=<signature>`
|
|
347
|
+
from the gateway's own key, which is the better default and is below. Retries widen
|
|
348
|
+
until the payment itself runs out, never sooner than an hour. An invoice that expires
|
|
349
|
+
fires nothing.
|
|
346
350
|
|
|
347
351
|
Delivery is at-least-once, so deduplicate on `id`.
|
|
348
352
|
|
|
353
|
+
Your handler answers one challenge before any of that. The gateway POSTs
|
|
354
|
+
`{"type":"webhook-challenge","nonce":"..."}` to the URL while the create is still open
|
|
355
|
+
and refuses the payment with a 424 unless the nonce comes back, so the endpoint has to
|
|
356
|
+
be deployed before you register it. `answerWebhookChallengeRequest` verifies that
|
|
357
|
+
challenge and hands you the response to return, or `null` when the delivery was a real
|
|
358
|
+
settlement, and it leaves the body unread either way.
|
|
359
|
+
|
|
349
360
|
```ts
|
|
350
|
-
import {
|
|
361
|
+
import {
|
|
362
|
+
answerWebhookChallengeRequest,
|
|
363
|
+
parseWebhookRequest,
|
|
364
|
+
proveSettlement,
|
|
365
|
+
} from "thunder-bridge";
|
|
351
366
|
|
|
352
367
|
app.post("/hooks/paid", async (context) => {
|
|
368
|
+
const challenge = await answerWebhookChallengeRequest(context.req.raw, secret);
|
|
369
|
+
if (challenge) return challenge;
|
|
370
|
+
|
|
353
371
|
const payment = await parseWebhookRequest(context.req.raw, secret);
|
|
354
372
|
if (payment === null) return context.text("bad signature", 401);
|
|
355
373
|
|
|
@@ -382,7 +400,38 @@ app.post("/hooks/bank", async (context) => {
|
|
|
382
400
|
```
|
|
383
401
|
|
|
384
402
|
Give each rail its own path, as above, and neither endpoint has to guess which body
|
|
385
|
-
it was handed.
|
|
403
|
+
it was handed. Both events also carry `kind`, `"minted"` or `"watched"`, so a single
|
|
404
|
+
path serving a trigger that both rails settle on can branch on the field instead of
|
|
405
|
+
on which fields are missing.
|
|
406
|
+
|
|
407
|
+
### Or hand the gateway no secret at all
|
|
408
|
+
|
|
409
|
+
A secret you give the gateway is kept in its ledger and replicated to its peers,
|
|
410
|
+
because any instance may be the one that delivers. Leave `webhookSecret` out and the
|
|
411
|
+
delivery is signed with the gateway's own key instead, `x-signature:
|
|
412
|
+
ed25519=<signature>` over the same `<timestamp>.<body>`. Fetch the public half once
|
|
413
|
+
and pass it as `{ publicKey }` wherever a secret would go.
|
|
414
|
+
|
|
415
|
+
```ts
|
|
416
|
+
const publicKey = await gateway.webhookKey();
|
|
417
|
+
|
|
418
|
+
app.post("/hooks/paid", async (context) => {
|
|
419
|
+
const challenge = await answerWebhookChallengeRequest(context.req.raw, { publicKey });
|
|
420
|
+
if (challenge) return challenge;
|
|
421
|
+
|
|
422
|
+
const payment = await parseWebhookRequest(context.req.raw, { publicKey });
|
|
423
|
+
if (payment === null) return context.text("bad signature", 401);
|
|
424
|
+
...
|
|
425
|
+
});
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Answering echoes the nonce and nothing else here, because there is no secret to sign it
|
|
429
|
+
with. Holding the URL the gateway challenged is the whole proof in that case.
|
|
430
|
+
|
|
431
|
+
The key is derived from the gateway's `CLUSTER_KEY`, so every instance in one cluster
|
|
432
|
+
signs alike and an operator rotating that key changes this one too. Neither
|
|
433
|
+
credential is ever accepted for the other's scheme, so a secret cannot check an
|
|
434
|
+
`ed25519=` delivery and a public key cannot check a `sha256=` one.
|
|
386
435
|
|
|
387
436
|
`parseWebhookRequest` refuses anything more than five minutes out of date,
|
|
388
437
|
adjustable with `toleranceSecs`. The signature proves the body came from someone
|
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,8 @@ __export(index_exports, {
|
|
|
30
30
|
REQUEST_IN_FLIGHT: () => REQUEST_IN_FLIGHT,
|
|
31
31
|
ThunderBridge: () => ThunderBridge,
|
|
32
32
|
UnverifiedRecipientError: () => UnverifiedRecipientError,
|
|
33
|
+
answerWebhookChallenge: () => answerWebhookChallenge,
|
|
34
|
+
answerWebhookChallengeRequest: () => answerWebhookChallengeRequest,
|
|
33
35
|
bankRail: () => bankRail,
|
|
34
36
|
bankTransfer: () => bankTransfer,
|
|
35
37
|
bankVerifyEndpoint: () => bankVerifyEndpoint,
|
|
@@ -632,6 +634,7 @@ function triggerEventFromWire(body) {
|
|
|
632
634
|
if (sealed !== null && typeof sealed !== "string") return null;
|
|
633
635
|
return {
|
|
634
636
|
id,
|
|
637
|
+
kind: kindOf(wire),
|
|
635
638
|
paymentHash,
|
|
636
639
|
verifyUrl,
|
|
637
640
|
status,
|
|
@@ -643,6 +646,11 @@ function triggerEventFromWire(body) {
|
|
|
643
646
|
amountMsat: wire["incoming_amount"] === void 0 ? null : msatFrom(wire["incoming_amount"], 1)
|
|
644
647
|
};
|
|
645
648
|
}
|
|
649
|
+
function kindOf(wire) {
|
|
650
|
+
const said = wire["kind"];
|
|
651
|
+
if (said === "minted" || said === "watched") return said;
|
|
652
|
+
return text(wire["ln_address"]) === null ? "watched" : "minted";
|
|
653
|
+
}
|
|
646
654
|
function toAmount(msat) {
|
|
647
655
|
return { value: String(msat), asset_code: ASSET_CODE, asset_scale: ASSET_SCALE };
|
|
648
656
|
}
|
|
@@ -780,6 +788,23 @@ var ThunderBridge = class {
|
|
|
780
788
|
return quote;
|
|
781
789
|
}
|
|
782
790
|
/** Read a payment back, null when the gateway has never heard of it */
|
|
791
|
+
/**
|
|
792
|
+
* The key this gateway signs webhooks with when you registered none of your own,
|
|
793
|
+
* ready to hand to `parseWebhookRequest` as `{ publicKey }`. Fetch it once and
|
|
794
|
+
* keep it: it is the same for every instance in the cluster
|
|
795
|
+
*/
|
|
796
|
+
async webhookKey() {
|
|
797
|
+
const response = await fetch(`${this.baseUrl}/webhook-key`);
|
|
798
|
+
if (!response.ok) throw await problemFrom(response);
|
|
799
|
+
const body = await response.json().catch(() => null);
|
|
800
|
+
if (body?.algorithm !== "ed25519" || typeof body.public_key !== "string") {
|
|
801
|
+
throw new ProblemError({
|
|
802
|
+
status: response.status,
|
|
803
|
+
title: "The gateway published no ed25519 webhook key"
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
return body.public_key;
|
|
807
|
+
}
|
|
783
808
|
async getPayment(id) {
|
|
784
809
|
const response = await fetch(`${this.baseUrl}/incoming-payments/${encodeURIComponent(id)}`, {
|
|
785
810
|
headers: this.reading()
|
|
@@ -1763,18 +1788,59 @@ function refusals2(answers) {
|
|
|
1763
1788
|
).join("; ");
|
|
1764
1789
|
}
|
|
1765
1790
|
|
|
1791
|
+
// ../core/ed25519.ts
|
|
1792
|
+
var ALGORITHM = { name: "Ed25519" };
|
|
1793
|
+
var SIGNATURE_BYTES = 64;
|
|
1794
|
+
var PUBLIC_KEY_BYTES = 32;
|
|
1795
|
+
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
1796
|
+
async function verifyHex(publicKeyHex, signatureHex, payload) {
|
|
1797
|
+
if (!isHex(publicKeyHex) || publicKeyHex.length !== PUBLIC_KEY_BYTES * 2) return false;
|
|
1798
|
+
if (!isHex(signatureHex) || signatureHex.length !== SIGNATURE_BYTES * 2) return false;
|
|
1799
|
+
try {
|
|
1800
|
+
const key = await crypto.subtle.importKey(
|
|
1801
|
+
"raw",
|
|
1802
|
+
asBuffer2(hexToBytes(publicKeyHex)),
|
|
1803
|
+
ALGORITHM,
|
|
1804
|
+
false,
|
|
1805
|
+
["verify"]
|
|
1806
|
+
);
|
|
1807
|
+
return await crypto.subtle.verify(
|
|
1808
|
+
ALGORITHM,
|
|
1809
|
+
key,
|
|
1810
|
+
asBuffer2(hexToBytes(signatureHex)),
|
|
1811
|
+
asBuffer2(payload)
|
|
1812
|
+
);
|
|
1813
|
+
} catch {
|
|
1814
|
+
return false;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
function asBuffer2(bytes) {
|
|
1818
|
+
return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1766
1821
|
// src/webhook.ts
|
|
1767
1822
|
var SIGNATURE_HEADER = "x-signature";
|
|
1768
1823
|
var TIMESTAMP_HEADER = "x-timestamp";
|
|
1769
|
-
var
|
|
1824
|
+
var SHARED_SECRET_PREFIX = "sha256=";
|
|
1825
|
+
var GATEWAY_KEY_PREFIX = "ed25519=";
|
|
1770
1826
|
var DEFAULT_TOLERANCE_SECS = 300;
|
|
1771
|
-
|
|
1827
|
+
var CHALLENGE = "webhook-challenge";
|
|
1828
|
+
async function verifyWebhookSignature(body, signature, credential, timestamp, options = {}) {
|
|
1772
1829
|
if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) return false;
|
|
1773
|
-
|
|
1774
|
-
|
|
1830
|
+
if (typeof credential !== "string") {
|
|
1831
|
+
if (!signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1832
|
+
return verifyHex(
|
|
1833
|
+
credential.publicKey.toLowerCase(),
|
|
1834
|
+
signature.slice(GATEWAY_KEY_PREFIX.length).toLowerCase(),
|
|
1835
|
+
signed(timestamp, body)
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
if (signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1839
|
+
const received = signature.startsWith(SHARED_SECRET_PREFIX) ? signature.slice(SHARED_SECRET_PREFIX.length) : signature;
|
|
1840
|
+
return equalInConstantTime(await sign(credential, timestamp, body), received.toLowerCase());
|
|
1775
1841
|
}
|
|
1776
|
-
async function parseWebhook(body, signature,
|
|
1777
|
-
if (!await verifyWebhookSignature(body, signature,
|
|
1842
|
+
async function parseWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1843
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1778
1844
|
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1779
1845
|
try {
|
|
1780
1846
|
return paymentFromWire(JSON.parse(text2));
|
|
@@ -1782,14 +1848,14 @@ async function parseWebhook(body, signature, secret, timestamp, options = {}) {
|
|
|
1782
1848
|
return null;
|
|
1783
1849
|
}
|
|
1784
1850
|
}
|
|
1785
|
-
async function parseWebhookRequest(request,
|
|
1851
|
+
async function parseWebhookRequest(request, credential, options = {}) {
|
|
1786
1852
|
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1787
1853
|
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1788
1854
|
if (signature === null || timestamp === null) return null;
|
|
1789
|
-
return parseWebhook(await request.text(), signature,
|
|
1855
|
+
return parseWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1790
1856
|
}
|
|
1791
|
-
async function parseWatchedWebhook(body, signature,
|
|
1792
|
-
if (!await verifyWebhookSignature(body, signature,
|
|
1857
|
+
async function parseWatchedWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1858
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1793
1859
|
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1794
1860
|
try {
|
|
1795
1861
|
return triggerEventFromWire(JSON.parse(text2));
|
|
@@ -1797,11 +1863,44 @@ async function parseWatchedWebhook(body, signature, secret, timestamp, options =
|
|
|
1797
1863
|
return null;
|
|
1798
1864
|
}
|
|
1799
1865
|
}
|
|
1800
|
-
async function parseWatchedWebhookRequest(request,
|
|
1866
|
+
async function parseWatchedWebhookRequest(request, credential, options = {}) {
|
|
1867
|
+
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1868
|
+
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1869
|
+
if (signature === null || timestamp === null) return null;
|
|
1870
|
+
return parseWatchedWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1871
|
+
}
|
|
1872
|
+
async function answerWebhookChallenge(body, signature, credential, timestamp, options = {}) {
|
|
1873
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1874
|
+
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1875
|
+
const nonce = challenged(text2);
|
|
1876
|
+
if (nonce === null) return null;
|
|
1877
|
+
if (typeof credential !== "string") return JSON.stringify({ nonce });
|
|
1878
|
+
return JSON.stringify({
|
|
1879
|
+
nonce,
|
|
1880
|
+
signature: `${SHARED_SECRET_PREFIX}${await hmacHex(credential, nonce)}`
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
async function answerWebhookChallengeRequest(request, credential, options = {}) {
|
|
1801
1884
|
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1802
1885
|
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1803
1886
|
if (signature === null || timestamp === null) return null;
|
|
1804
|
-
|
|
1887
|
+
const answer = await answerWebhookChallenge(
|
|
1888
|
+
await request.clone().text(),
|
|
1889
|
+
signature,
|
|
1890
|
+
credential,
|
|
1891
|
+
timestamp,
|
|
1892
|
+
options
|
|
1893
|
+
);
|
|
1894
|
+
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
1895
|
+
}
|
|
1896
|
+
function challenged(text2) {
|
|
1897
|
+
try {
|
|
1898
|
+
const said = JSON.parse(text2);
|
|
1899
|
+
if (said["type"] !== CHALLENGE || typeof said["nonce"] !== "string") return null;
|
|
1900
|
+
return said["nonce"];
|
|
1901
|
+
} catch {
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1805
1904
|
}
|
|
1806
1905
|
function recent(timestamp, toleranceSecs) {
|
|
1807
1906
|
const sent = Number(timestamp);
|
|
@@ -1832,6 +1931,8 @@ function signed(timestamp, body) {
|
|
|
1832
1931
|
REQUEST_IN_FLIGHT,
|
|
1833
1932
|
ThunderBridge,
|
|
1834
1933
|
UnverifiedRecipientError,
|
|
1934
|
+
answerWebhookChallenge,
|
|
1935
|
+
answerWebhookChallengeRequest,
|
|
1835
1936
|
bankRail,
|
|
1836
1937
|
bankTransfer,
|
|
1837
1938
|
bankVerifyEndpoint,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as ThunderBridge, P as Payment, C as CreatePaymentParams, a as TriggerEvent, W as WalletFailure } from './rail-
|
|
2
|
-
export { B as BankRailConfig, b as CreateOptions, c as CreateQuoteParams, F as FollowOptions, L as Leg, d as LightningRailConfig, O as Order, e as PaymentStatus, Q as Quote, R as Rail,
|
|
1
|
+
import { T as ThunderBridge, P as Payment, C as CreatePaymentParams, a as TriggerEvent, W as WalletFailure } from './rail-CdKeyVGn.cjs';
|
|
2
|
+
export { B as BankRailConfig, b as CreateOptions, c as CreateQuoteParams, F as FollowOptions, L as Leg, d as LightningRailConfig, O as Order, e as PaymentKind, f as PaymentStatus, Q as Quote, R as Rail, g as ThunderBridgeOptions, h as WaitOptions, i as WalletReason, j as WatchPaymentParams, k as bankRail, l as lightningRail } from './rail-CdKeyVGn.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Encrypt what the watcher needs and the gateway must not have. The gateway
|
|
@@ -338,23 +338,42 @@ declare function toLnurl(endpoint: string): string;
|
|
|
338
338
|
type WebhookOptions = {
|
|
339
339
|
toleranceSecs?: number;
|
|
340
340
|
};
|
|
341
|
+
/**
|
|
342
|
+
* What checks a delivery. A string is the `webhook.secret` you registered. Pass
|
|
343
|
+
* `{ publicKey }` instead, the hex from the gateway's `/webhook-key`, when you
|
|
344
|
+
* registered no secret and would rather it held nothing of yours
|
|
345
|
+
*/
|
|
346
|
+
type WebhookCredential = string | {
|
|
347
|
+
publicKey: string;
|
|
348
|
+
};
|
|
341
349
|
/** Verify the `X-Signature` header against the raw body and the `X-Timestamp` that came with it */
|
|
342
|
-
declare function verifyWebhookSignature(body: string | Uint8Array, signature: string,
|
|
350
|
+
declare function verifyWebhookSignature(body: string | Uint8Array, signature: string, credential: WebhookCredential, timestamp: string, options?: WebhookOptions): Promise<boolean>;
|
|
343
351
|
/** Verify and parse in one step, returns null on a bad signature or a body that is not a payment */
|
|
344
|
-
declare function parseWebhook(body: string | Uint8Array, signature: string,
|
|
352
|
+
declare function parseWebhook(body: string | Uint8Array, signature: string, credential: WebhookCredential, timestamp: string, options?: WebhookOptions): Promise<Payment | null>;
|
|
345
353
|
/**
|
|
346
354
|
* Verify and parse from a Fetch API `Request` as used by Hono, Next, SvelteKit,
|
|
347
355
|
* Cloudflare Workers and Deno, WebCrypto only so it runs anywhere fetch does
|
|
348
356
|
*/
|
|
349
|
-
declare function parseWebhookRequest(request: Request,
|
|
357
|
+
declare function parseWebhookRequest(request: Request, credential: WebhookCredential, options?: WebhookOptions): Promise<Payment | null>;
|
|
350
358
|
/**
|
|
351
359
|
* The same, for a payment the gateway only watched. A bank transfer and a blind
|
|
352
360
|
* Lightning leg carry no address, amount or invoice, so they arrive in the shape
|
|
353
361
|
* `followTrigger` and `getWatched` hand back rather than the minted one
|
|
354
362
|
*/
|
|
355
|
-
declare function parseWatchedWebhook(body: string | Uint8Array, signature: string,
|
|
363
|
+
declare function parseWatchedWebhook(body: string | Uint8Array, signature: string, credential: WebhookCredential, timestamp: string, options?: WebhookOptions): Promise<TriggerEvent | null>;
|
|
356
364
|
/** `parseWatchedWebhook` from a Fetch API `Request`, the way `parseWebhookRequest` is */
|
|
357
|
-
declare function parseWatchedWebhookRequest(request: Request,
|
|
365
|
+
declare function parseWatchedWebhookRequest(request: Request, credential: WebhookCredential, options?: WebhookOptions): Promise<TriggerEvent | null>;
|
|
366
|
+
/**
|
|
367
|
+
* Answer the one challenge the gateway sends before it will watch a payment your
|
|
368
|
+
* webhook is registered on. Returns the body to send back with a 200, or null when
|
|
369
|
+
* this delivery is not a challenge, so a handler tries this first and then parses
|
|
370
|
+
*/
|
|
371
|
+
declare function answerWebhookChallenge(body: string | Uint8Array, signature: string, credential: WebhookCredential, timestamp: string, options?: WebhookOptions): Promise<string | null>;
|
|
372
|
+
/**
|
|
373
|
+
* `answerWebhookChallenge` from a Fetch API `Request`, leaving the body unread so
|
|
374
|
+
* the same handler can go on to `parseWebhookRequest` when this was no challenge
|
|
375
|
+
*/
|
|
376
|
+
declare function answerWebhookChallengeRequest(request: Request, credential: WebhookCredential, options?: WebhookOptions): Promise<Response | null>;
|
|
358
377
|
|
|
359
378
|
/**
|
|
360
379
|
* The way a gateway was caught out, every code is a check that held against the
|
|
@@ -432,4 +451,4 @@ declare class NoWalletAvailableError extends ProblemError {
|
|
|
432
451
|
}, wallets: WalletFailure[]);
|
|
433
452
|
}
|
|
434
453
|
|
|
435
|
-
export { type BankTransfer, type BankTransferParams, type BankVerifyConfig, CreatePaymentParams, type Credit, type FioConfig, type GatewayCheatCode, GatewayCheatError, IDEMPOTENCY_KEY_REUSED, type IdempotencyConflict, IdempotencyConflictError, type Invoice, type MedianOptions, NO_WALLET_AVAILABLE, NoWalletAvailableError, PAYMENT_ALREADY_WATCHED, Payment, ProblemError, type QrOptions, REQUEST_IN_FLIGHT, type Statement, ThunderBridge, type Ticker, TriggerEvent, UnverifiedRecipientError, WalletFailure, type WebhookOptions, bankTransfer, bankVerifyEndpoint, bitstamp, coinbase, coinmate, decodeInvoice, fioStatement, invoiceToDataUrl, invoiceToSvg, isProblemType, isProvablyPaid, kraken, lnurlToDataUrl, lnurlToSvg, medianOf, minorScaleOf, minorUnitsOf, msatFor, parseWatchedWebhook, parseWatchedWebhookRequest, parseWebhook, parseWebhookRequest, preimageMatchesHash, proveOrigin, proveSettlement, qrToDataUrl, qrToSvg, seal, spdToDataUrl, spdToSvg, toLnurl, unseal, verifyWebhookSignature };
|
|
454
|
+
export { type BankTransfer, type BankTransferParams, type BankVerifyConfig, CreatePaymentParams, type Credit, type FioConfig, type GatewayCheatCode, GatewayCheatError, IDEMPOTENCY_KEY_REUSED, type IdempotencyConflict, IdempotencyConflictError, type Invoice, type MedianOptions, NO_WALLET_AVAILABLE, NoWalletAvailableError, PAYMENT_ALREADY_WATCHED, Payment, ProblemError, type QrOptions, REQUEST_IN_FLIGHT, type Statement, ThunderBridge, type Ticker, TriggerEvent, UnverifiedRecipientError, WalletFailure, type WebhookCredential, type WebhookOptions, answerWebhookChallenge, answerWebhookChallengeRequest, bankTransfer, bankVerifyEndpoint, bitstamp, coinbase, coinmate, decodeInvoice, fioStatement, invoiceToDataUrl, invoiceToSvg, isProblemType, isProvablyPaid, kraken, lnurlToDataUrl, lnurlToSvg, medianOf, minorScaleOf, minorUnitsOf, msatFor, parseWatchedWebhook, parseWatchedWebhookRequest, parseWebhook, parseWebhookRequest, preimageMatchesHash, proveOrigin, proveSettlement, qrToDataUrl, qrToSvg, seal, spdToDataUrl, spdToSvg, toLnurl, unseal, verifyWebhookSignature };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as ThunderBridge, P as Payment, C as CreatePaymentParams, a as TriggerEvent, W as WalletFailure } from './rail-
|
|
2
|
-
export { B as BankRailConfig, b as CreateOptions, c as CreateQuoteParams, F as FollowOptions, L as Leg, d as LightningRailConfig, O as Order, e as PaymentStatus, Q as Quote, R as Rail,
|
|
1
|
+
import { T as ThunderBridge, P as Payment, C as CreatePaymentParams, a as TriggerEvent, W as WalletFailure } from './rail-CdKeyVGn.js';
|
|
2
|
+
export { B as BankRailConfig, b as CreateOptions, c as CreateQuoteParams, F as FollowOptions, L as Leg, d as LightningRailConfig, O as Order, e as PaymentKind, f as PaymentStatus, Q as Quote, R as Rail, g as ThunderBridgeOptions, h as WaitOptions, i as WalletReason, j as WatchPaymentParams, k as bankRail, l as lightningRail } from './rail-CdKeyVGn.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Encrypt what the watcher needs and the gateway must not have. The gateway
|
|
@@ -338,23 +338,42 @@ declare function toLnurl(endpoint: string): string;
|
|
|
338
338
|
type WebhookOptions = {
|
|
339
339
|
toleranceSecs?: number;
|
|
340
340
|
};
|
|
341
|
+
/**
|
|
342
|
+
* What checks a delivery. A string is the `webhook.secret` you registered. Pass
|
|
343
|
+
* `{ publicKey }` instead, the hex from the gateway's `/webhook-key`, when you
|
|
344
|
+
* registered no secret and would rather it held nothing of yours
|
|
345
|
+
*/
|
|
346
|
+
type WebhookCredential = string | {
|
|
347
|
+
publicKey: string;
|
|
348
|
+
};
|
|
341
349
|
/** Verify the `X-Signature` header against the raw body and the `X-Timestamp` that came with it */
|
|
342
|
-
declare function verifyWebhookSignature(body: string | Uint8Array, signature: string,
|
|
350
|
+
declare function verifyWebhookSignature(body: string | Uint8Array, signature: string, credential: WebhookCredential, timestamp: string, options?: WebhookOptions): Promise<boolean>;
|
|
343
351
|
/** Verify and parse in one step, returns null on a bad signature or a body that is not a payment */
|
|
344
|
-
declare function parseWebhook(body: string | Uint8Array, signature: string,
|
|
352
|
+
declare function parseWebhook(body: string | Uint8Array, signature: string, credential: WebhookCredential, timestamp: string, options?: WebhookOptions): Promise<Payment | null>;
|
|
345
353
|
/**
|
|
346
354
|
* Verify and parse from a Fetch API `Request` as used by Hono, Next, SvelteKit,
|
|
347
355
|
* Cloudflare Workers and Deno, WebCrypto only so it runs anywhere fetch does
|
|
348
356
|
*/
|
|
349
|
-
declare function parseWebhookRequest(request: Request,
|
|
357
|
+
declare function parseWebhookRequest(request: Request, credential: WebhookCredential, options?: WebhookOptions): Promise<Payment | null>;
|
|
350
358
|
/**
|
|
351
359
|
* The same, for a payment the gateway only watched. A bank transfer and a blind
|
|
352
360
|
* Lightning leg carry no address, amount or invoice, so they arrive in the shape
|
|
353
361
|
* `followTrigger` and `getWatched` hand back rather than the minted one
|
|
354
362
|
*/
|
|
355
|
-
declare function parseWatchedWebhook(body: string | Uint8Array, signature: string,
|
|
363
|
+
declare function parseWatchedWebhook(body: string | Uint8Array, signature: string, credential: WebhookCredential, timestamp: string, options?: WebhookOptions): Promise<TriggerEvent | null>;
|
|
356
364
|
/** `parseWatchedWebhook` from a Fetch API `Request`, the way `parseWebhookRequest` is */
|
|
357
|
-
declare function parseWatchedWebhookRequest(request: Request,
|
|
365
|
+
declare function parseWatchedWebhookRequest(request: Request, credential: WebhookCredential, options?: WebhookOptions): Promise<TriggerEvent | null>;
|
|
366
|
+
/**
|
|
367
|
+
* Answer the one challenge the gateway sends before it will watch a payment your
|
|
368
|
+
* webhook is registered on. Returns the body to send back with a 200, or null when
|
|
369
|
+
* this delivery is not a challenge, so a handler tries this first and then parses
|
|
370
|
+
*/
|
|
371
|
+
declare function answerWebhookChallenge(body: string | Uint8Array, signature: string, credential: WebhookCredential, timestamp: string, options?: WebhookOptions): Promise<string | null>;
|
|
372
|
+
/**
|
|
373
|
+
* `answerWebhookChallenge` from a Fetch API `Request`, leaving the body unread so
|
|
374
|
+
* the same handler can go on to `parseWebhookRequest` when this was no challenge
|
|
375
|
+
*/
|
|
376
|
+
declare function answerWebhookChallengeRequest(request: Request, credential: WebhookCredential, options?: WebhookOptions): Promise<Response | null>;
|
|
358
377
|
|
|
359
378
|
/**
|
|
360
379
|
* The way a gateway was caught out, every code is a check that held against the
|
|
@@ -432,4 +451,4 @@ declare class NoWalletAvailableError extends ProblemError {
|
|
|
432
451
|
}, wallets: WalletFailure[]);
|
|
433
452
|
}
|
|
434
453
|
|
|
435
|
-
export { type BankTransfer, type BankTransferParams, type BankVerifyConfig, CreatePaymentParams, type Credit, type FioConfig, type GatewayCheatCode, GatewayCheatError, IDEMPOTENCY_KEY_REUSED, type IdempotencyConflict, IdempotencyConflictError, type Invoice, type MedianOptions, NO_WALLET_AVAILABLE, NoWalletAvailableError, PAYMENT_ALREADY_WATCHED, Payment, ProblemError, type QrOptions, REQUEST_IN_FLIGHT, type Statement, ThunderBridge, type Ticker, TriggerEvent, UnverifiedRecipientError, WalletFailure, type WebhookOptions, bankTransfer, bankVerifyEndpoint, bitstamp, coinbase, coinmate, decodeInvoice, fioStatement, invoiceToDataUrl, invoiceToSvg, isProblemType, isProvablyPaid, kraken, lnurlToDataUrl, lnurlToSvg, medianOf, minorScaleOf, minorUnitsOf, msatFor, parseWatchedWebhook, parseWatchedWebhookRequest, parseWebhook, parseWebhookRequest, preimageMatchesHash, proveOrigin, proveSettlement, qrToDataUrl, qrToSvg, seal, spdToDataUrl, spdToSvg, toLnurl, unseal, verifyWebhookSignature };
|
|
454
|
+
export { type BankTransfer, type BankTransferParams, type BankVerifyConfig, CreatePaymentParams, type Credit, type FioConfig, type GatewayCheatCode, GatewayCheatError, IDEMPOTENCY_KEY_REUSED, type IdempotencyConflict, IdempotencyConflictError, type Invoice, type MedianOptions, NO_WALLET_AVAILABLE, NoWalletAvailableError, PAYMENT_ALREADY_WATCHED, Payment, ProblemError, type QrOptions, REQUEST_IN_FLIGHT, type Statement, ThunderBridge, type Ticker, TriggerEvent, UnverifiedRecipientError, WalletFailure, type WebhookCredential, type WebhookOptions, answerWebhookChallenge, answerWebhookChallengeRequest, bankTransfer, bankVerifyEndpoint, bitstamp, coinbase, coinmate, decodeInvoice, fioStatement, invoiceToDataUrl, invoiceToSvg, isProblemType, isProvablyPaid, kraken, lnurlToDataUrl, lnurlToSvg, medianOf, minorScaleOf, minorUnitsOf, msatFor, parseWatchedWebhook, parseWatchedWebhookRequest, parseWebhook, parseWebhookRequest, preimageMatchesHash, proveOrigin, proveSettlement, qrToDataUrl, qrToSvg, seal, spdToDataUrl, spdToSvg, toLnurl, unseal, verifyWebhookSignature };
|
package/dist/index.js
CHANGED
|
@@ -562,6 +562,7 @@ function triggerEventFromWire(body) {
|
|
|
562
562
|
if (sealed !== null && typeof sealed !== "string") return null;
|
|
563
563
|
return {
|
|
564
564
|
id,
|
|
565
|
+
kind: kindOf(wire),
|
|
565
566
|
paymentHash,
|
|
566
567
|
verifyUrl,
|
|
567
568
|
status,
|
|
@@ -573,6 +574,11 @@ function triggerEventFromWire(body) {
|
|
|
573
574
|
amountMsat: wire["incoming_amount"] === void 0 ? null : msatFrom(wire["incoming_amount"], 1)
|
|
574
575
|
};
|
|
575
576
|
}
|
|
577
|
+
function kindOf(wire) {
|
|
578
|
+
const said = wire["kind"];
|
|
579
|
+
if (said === "minted" || said === "watched") return said;
|
|
580
|
+
return text(wire["ln_address"]) === null ? "watched" : "minted";
|
|
581
|
+
}
|
|
576
582
|
function toAmount(msat) {
|
|
577
583
|
return { value: String(msat), asset_code: ASSET_CODE, asset_scale: ASSET_SCALE };
|
|
578
584
|
}
|
|
@@ -710,6 +716,23 @@ var ThunderBridge = class {
|
|
|
710
716
|
return quote;
|
|
711
717
|
}
|
|
712
718
|
/** Read a payment back, null when the gateway has never heard of it */
|
|
719
|
+
/**
|
|
720
|
+
* The key this gateway signs webhooks with when you registered none of your own,
|
|
721
|
+
* ready to hand to `parseWebhookRequest` as `{ publicKey }`. Fetch it once and
|
|
722
|
+
* keep it: it is the same for every instance in the cluster
|
|
723
|
+
*/
|
|
724
|
+
async webhookKey() {
|
|
725
|
+
const response = await fetch(`${this.baseUrl}/webhook-key`);
|
|
726
|
+
if (!response.ok) throw await problemFrom(response);
|
|
727
|
+
const body = await response.json().catch(() => null);
|
|
728
|
+
if (body?.algorithm !== "ed25519" || typeof body.public_key !== "string") {
|
|
729
|
+
throw new ProblemError({
|
|
730
|
+
status: response.status,
|
|
731
|
+
title: "The gateway published no ed25519 webhook key"
|
|
732
|
+
});
|
|
733
|
+
}
|
|
734
|
+
return body.public_key;
|
|
735
|
+
}
|
|
713
736
|
async getPayment(id) {
|
|
714
737
|
const response = await fetch(`${this.baseUrl}/incoming-payments/${encodeURIComponent(id)}`, {
|
|
715
738
|
headers: this.reading()
|
|
@@ -1693,18 +1716,59 @@ function refusals2(answers) {
|
|
|
1693
1716
|
).join("; ");
|
|
1694
1717
|
}
|
|
1695
1718
|
|
|
1719
|
+
// ../core/ed25519.ts
|
|
1720
|
+
var ALGORITHM = { name: "Ed25519" };
|
|
1721
|
+
var SIGNATURE_BYTES = 64;
|
|
1722
|
+
var PUBLIC_KEY_BYTES = 32;
|
|
1723
|
+
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
1724
|
+
async function verifyHex(publicKeyHex, signatureHex, payload) {
|
|
1725
|
+
if (!isHex(publicKeyHex) || publicKeyHex.length !== PUBLIC_KEY_BYTES * 2) return false;
|
|
1726
|
+
if (!isHex(signatureHex) || signatureHex.length !== SIGNATURE_BYTES * 2) return false;
|
|
1727
|
+
try {
|
|
1728
|
+
const key = await crypto.subtle.importKey(
|
|
1729
|
+
"raw",
|
|
1730
|
+
asBuffer2(hexToBytes(publicKeyHex)),
|
|
1731
|
+
ALGORITHM,
|
|
1732
|
+
false,
|
|
1733
|
+
["verify"]
|
|
1734
|
+
);
|
|
1735
|
+
return await crypto.subtle.verify(
|
|
1736
|
+
ALGORITHM,
|
|
1737
|
+
key,
|
|
1738
|
+
asBuffer2(hexToBytes(signatureHex)),
|
|
1739
|
+
asBuffer2(payload)
|
|
1740
|
+
);
|
|
1741
|
+
} catch {
|
|
1742
|
+
return false;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
function asBuffer2(bytes) {
|
|
1746
|
+
return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1696
1749
|
// src/webhook.ts
|
|
1697
1750
|
var SIGNATURE_HEADER = "x-signature";
|
|
1698
1751
|
var TIMESTAMP_HEADER = "x-timestamp";
|
|
1699
|
-
var
|
|
1752
|
+
var SHARED_SECRET_PREFIX = "sha256=";
|
|
1753
|
+
var GATEWAY_KEY_PREFIX = "ed25519=";
|
|
1700
1754
|
var DEFAULT_TOLERANCE_SECS = 300;
|
|
1701
|
-
|
|
1755
|
+
var CHALLENGE = "webhook-challenge";
|
|
1756
|
+
async function verifyWebhookSignature(body, signature, credential, timestamp, options = {}) {
|
|
1702
1757
|
if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) return false;
|
|
1703
|
-
|
|
1704
|
-
|
|
1758
|
+
if (typeof credential !== "string") {
|
|
1759
|
+
if (!signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1760
|
+
return verifyHex(
|
|
1761
|
+
credential.publicKey.toLowerCase(),
|
|
1762
|
+
signature.slice(GATEWAY_KEY_PREFIX.length).toLowerCase(),
|
|
1763
|
+
signed(timestamp, body)
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
if (signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1767
|
+
const received = signature.startsWith(SHARED_SECRET_PREFIX) ? signature.slice(SHARED_SECRET_PREFIX.length) : signature;
|
|
1768
|
+
return equalInConstantTime(await sign(credential, timestamp, body), received.toLowerCase());
|
|
1705
1769
|
}
|
|
1706
|
-
async function parseWebhook(body, signature,
|
|
1707
|
-
if (!await verifyWebhookSignature(body, signature,
|
|
1770
|
+
async function parseWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1771
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1708
1772
|
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1709
1773
|
try {
|
|
1710
1774
|
return paymentFromWire(JSON.parse(text2));
|
|
@@ -1712,14 +1776,14 @@ async function parseWebhook(body, signature, secret, timestamp, options = {}) {
|
|
|
1712
1776
|
return null;
|
|
1713
1777
|
}
|
|
1714
1778
|
}
|
|
1715
|
-
async function parseWebhookRequest(request,
|
|
1779
|
+
async function parseWebhookRequest(request, credential, options = {}) {
|
|
1716
1780
|
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1717
1781
|
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1718
1782
|
if (signature === null || timestamp === null) return null;
|
|
1719
|
-
return parseWebhook(await request.text(), signature,
|
|
1783
|
+
return parseWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1720
1784
|
}
|
|
1721
|
-
async function parseWatchedWebhook(body, signature,
|
|
1722
|
-
if (!await verifyWebhookSignature(body, signature,
|
|
1785
|
+
async function parseWatchedWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1786
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1723
1787
|
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1724
1788
|
try {
|
|
1725
1789
|
return triggerEventFromWire(JSON.parse(text2));
|
|
@@ -1727,11 +1791,44 @@ async function parseWatchedWebhook(body, signature, secret, timestamp, options =
|
|
|
1727
1791
|
return null;
|
|
1728
1792
|
}
|
|
1729
1793
|
}
|
|
1730
|
-
async function parseWatchedWebhookRequest(request,
|
|
1794
|
+
async function parseWatchedWebhookRequest(request, credential, options = {}) {
|
|
1795
|
+
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1796
|
+
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1797
|
+
if (signature === null || timestamp === null) return null;
|
|
1798
|
+
return parseWatchedWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1799
|
+
}
|
|
1800
|
+
async function answerWebhookChallenge(body, signature, credential, timestamp, options = {}) {
|
|
1801
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1802
|
+
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1803
|
+
const nonce = challenged(text2);
|
|
1804
|
+
if (nonce === null) return null;
|
|
1805
|
+
if (typeof credential !== "string") return JSON.stringify({ nonce });
|
|
1806
|
+
return JSON.stringify({
|
|
1807
|
+
nonce,
|
|
1808
|
+
signature: `${SHARED_SECRET_PREFIX}${await hmacHex(credential, nonce)}`
|
|
1809
|
+
});
|
|
1810
|
+
}
|
|
1811
|
+
async function answerWebhookChallengeRequest(request, credential, options = {}) {
|
|
1731
1812
|
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1732
1813
|
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1733
1814
|
if (signature === null || timestamp === null) return null;
|
|
1734
|
-
|
|
1815
|
+
const answer = await answerWebhookChallenge(
|
|
1816
|
+
await request.clone().text(),
|
|
1817
|
+
signature,
|
|
1818
|
+
credential,
|
|
1819
|
+
timestamp,
|
|
1820
|
+
options
|
|
1821
|
+
);
|
|
1822
|
+
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
1823
|
+
}
|
|
1824
|
+
function challenged(text2) {
|
|
1825
|
+
try {
|
|
1826
|
+
const said = JSON.parse(text2);
|
|
1827
|
+
if (said["type"] !== CHALLENGE || typeof said["nonce"] !== "string") return null;
|
|
1828
|
+
return said["nonce"];
|
|
1829
|
+
} catch {
|
|
1830
|
+
return null;
|
|
1831
|
+
}
|
|
1735
1832
|
}
|
|
1736
1833
|
function recent(timestamp, toleranceSecs) {
|
|
1737
1834
|
const sent = Number(timestamp);
|
|
@@ -1761,6 +1858,8 @@ export {
|
|
|
1761
1858
|
REQUEST_IN_FLIGHT,
|
|
1762
1859
|
ThunderBridge,
|
|
1763
1860
|
UnverifiedRecipientError,
|
|
1861
|
+
answerWebhookChallenge,
|
|
1862
|
+
answerWebhookChallengeRequest,
|
|
1764
1863
|
bankRail,
|
|
1765
1864
|
bankTransfer,
|
|
1766
1865
|
bankVerifyEndpoint,
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Where a payment stands, `paid` is the only status that carries a preimage */
|
|
2
2
|
type PaymentStatus = "pending" | "paid" | "expired";
|
|
3
|
+
/** Whether the gateway resolved the address and got the invoice, or was handed one to watch */
|
|
4
|
+
type PaymentKind = "minted" | "watched";
|
|
3
5
|
/** A payment as the gateway reports it, every field is checkable against the recipient */
|
|
4
6
|
interface Payment {
|
|
5
7
|
id: string;
|
|
@@ -53,6 +55,7 @@ interface Quote {
|
|
|
53
55
|
*/
|
|
54
56
|
interface TriggerEvent {
|
|
55
57
|
id: string;
|
|
58
|
+
kind: PaymentKind;
|
|
56
59
|
paymentHash: string;
|
|
57
60
|
verifyUrl: string;
|
|
58
61
|
status: PaymentStatus;
|
|
@@ -189,6 +192,12 @@ declare class ThunderBridge {
|
|
|
189
192
|
*/
|
|
190
193
|
createQuote(params: CreateQuoteParams): Promise<Quote>;
|
|
191
194
|
/** Read a payment back, null when the gateway has never heard of it */
|
|
195
|
+
/**
|
|
196
|
+
* The key this gateway signs webhooks with when you registered none of your own,
|
|
197
|
+
* ready to hand to `parseWebhookRequest` as `{ publicKey }`. Fetch it once and
|
|
198
|
+
* keep it: it is the same for every instance in the cluster
|
|
199
|
+
*/
|
|
200
|
+
webhookKey(): Promise<string>;
|
|
192
201
|
getPayment(id: string): Promise<Payment | null>;
|
|
193
202
|
/**
|
|
194
203
|
* Read back a payment the gateway is only watching, null when it has never
|
|
@@ -378,4 +387,4 @@ declare function lightningRail(config: LightningRailConfig): Rail;
|
|
|
378
387
|
*/
|
|
379
388
|
declare function blindLightningRail(config: BlindLightningRailConfig): Rail;
|
|
380
389
|
|
|
381
|
-
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
|
|
390
|
+
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 };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Where a payment stands, `paid` is the only status that carries a preimage */
|
|
2
2
|
type PaymentStatus = "pending" | "paid" | "expired";
|
|
3
|
+
/** Whether the gateway resolved the address and got the invoice, or was handed one to watch */
|
|
4
|
+
type PaymentKind = "minted" | "watched";
|
|
3
5
|
/** A payment as the gateway reports it, every field is checkable against the recipient */
|
|
4
6
|
interface Payment {
|
|
5
7
|
id: string;
|
|
@@ -53,6 +55,7 @@ interface Quote {
|
|
|
53
55
|
*/
|
|
54
56
|
interface TriggerEvent {
|
|
55
57
|
id: string;
|
|
58
|
+
kind: PaymentKind;
|
|
56
59
|
paymentHash: string;
|
|
57
60
|
verifyUrl: string;
|
|
58
61
|
status: PaymentStatus;
|
|
@@ -189,6 +192,12 @@ declare class ThunderBridge {
|
|
|
189
192
|
*/
|
|
190
193
|
createQuote(params: CreateQuoteParams): Promise<Quote>;
|
|
191
194
|
/** Read a payment back, null when the gateway has never heard of it */
|
|
195
|
+
/**
|
|
196
|
+
* The key this gateway signs webhooks with when you registered none of your own,
|
|
197
|
+
* ready to hand to `parseWebhookRequest` as `{ publicKey }`. Fetch it once and
|
|
198
|
+
* keep it: it is the same for every instance in the cluster
|
|
199
|
+
*/
|
|
200
|
+
webhookKey(): Promise<string>;
|
|
192
201
|
getPayment(id: string): Promise<Payment | null>;
|
|
193
202
|
/**
|
|
194
203
|
* Read back a payment the gateway is only watching, null when it has never
|
|
@@ -378,4 +387,4 @@ declare function lightningRail(config: LightningRailConfig): Rail;
|
|
|
378
387
|
*/
|
|
379
388
|
declare function blindLightningRail(config: BlindLightningRailConfig): Rail;
|
|
380
389
|
|
|
381
|
-
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
|
|
390
|
+
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 };
|
package/dist/server.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as ThunderBridge } from './rail-
|
|
2
|
-
export {
|
|
1
|
+
import { T as ThunderBridge } from './rail-CdKeyVGn.cjs';
|
|
2
|
+
export { m as BlindLightningRailConfig, n as blindLightningRail } from './rail-CdKeyVGn.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-
|
|
2
|
-
export {
|
|
1
|
+
import { T as ThunderBridge } from './rail-CdKeyVGn.js';
|
|
2
|
+
export { m as BlindLightningRailConfig, n as blindLightningRail } from './rail-CdKeyVGn.js';
|
|
3
3
|
|
|
4
4
|
interface TriggerConfig {
|
|
5
5
|
/** The gateway that quotes the addresses and mints the invoice */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thunder-bridge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.6",
|
|
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",
|