thunder-bridge 0.8.8 → 0.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +151 -132
- package/dist/index.d.cts +13 -1
- package/dist/index.d.ts +13 -1
- package/dist/index.js +149 -132
- package/dist/server.cjs +70 -29
- package/dist/server.js +70 -29
- package/package.json +1 -1
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
|
+
answerVerifyChallenge: () => answerVerifyChallenge,
|
|
34
|
+
answerVerifyChallengeRequest: () => answerVerifyChallengeRequest,
|
|
33
35
|
answerWebhookChallenge: () => answerWebhookChallenge,
|
|
34
36
|
answerWebhookChallengeRequest: () => answerWebhookChallengeRequest,
|
|
35
37
|
bankRail: () => bankRail,
|
|
@@ -1287,6 +1289,151 @@ function minorScaleOf(currency) {
|
|
|
1287
1289
|
return 10 ** minorUnitsOf(currency);
|
|
1288
1290
|
}
|
|
1289
1291
|
|
|
1292
|
+
// ../core/ed25519.ts
|
|
1293
|
+
var ALGORITHM = { name: "Ed25519" };
|
|
1294
|
+
var SIGNATURE_BYTES = 64;
|
|
1295
|
+
var PUBLIC_KEY_BYTES = 32;
|
|
1296
|
+
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
1297
|
+
async function verifyHex(publicKeyHex, signatureHex, payload) {
|
|
1298
|
+
if (!isHex(publicKeyHex) || publicKeyHex.length !== PUBLIC_KEY_BYTES * 2) return false;
|
|
1299
|
+
if (!isHex(signatureHex) || signatureHex.length !== SIGNATURE_BYTES * 2) return false;
|
|
1300
|
+
try {
|
|
1301
|
+
const key = await crypto.subtle.importKey(
|
|
1302
|
+
"raw",
|
|
1303
|
+
asBuffer2(hexToBytes(publicKeyHex)),
|
|
1304
|
+
ALGORITHM,
|
|
1305
|
+
false,
|
|
1306
|
+
["verify"]
|
|
1307
|
+
);
|
|
1308
|
+
return await crypto.subtle.verify(
|
|
1309
|
+
ALGORITHM,
|
|
1310
|
+
key,
|
|
1311
|
+
asBuffer2(hexToBytes(signatureHex)),
|
|
1312
|
+
asBuffer2(payload)
|
|
1313
|
+
);
|
|
1314
|
+
} catch {
|
|
1315
|
+
return false;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
function asBuffer2(bytes) {
|
|
1319
|
+
return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// src/webhook.ts
|
|
1323
|
+
var SIGNATURE_HEADER = "x-signature";
|
|
1324
|
+
var TIMESTAMP_HEADER = "x-timestamp";
|
|
1325
|
+
var SHARED_SECRET_PREFIX = "sha256=";
|
|
1326
|
+
var GATEWAY_KEY_PREFIX = "ed25519=";
|
|
1327
|
+
var DEFAULT_TOLERANCE_SECS = 300;
|
|
1328
|
+
var CHALLENGE = "webhook-challenge";
|
|
1329
|
+
var VERIFY_CHALLENGE = "verify-challenge";
|
|
1330
|
+
async function verifyWebhookSignature(body, signature, credential, timestamp, options = {}) {
|
|
1331
|
+
if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) return false;
|
|
1332
|
+
if (typeof credential !== "string") {
|
|
1333
|
+
if (!signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1334
|
+
return verifyHex(
|
|
1335
|
+
credential.publicKey.toLowerCase(),
|
|
1336
|
+
signature.slice(GATEWAY_KEY_PREFIX.length).toLowerCase(),
|
|
1337
|
+
signed(timestamp, body)
|
|
1338
|
+
);
|
|
1339
|
+
}
|
|
1340
|
+
if (signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1341
|
+
const received = signature.startsWith(SHARED_SECRET_PREFIX) ? signature.slice(SHARED_SECRET_PREFIX.length) : signature;
|
|
1342
|
+
return equalInConstantTime(await sign(credential, timestamp, body), received.toLowerCase());
|
|
1343
|
+
}
|
|
1344
|
+
async function parseWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1345
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1346
|
+
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1347
|
+
try {
|
|
1348
|
+
return paymentFromWire(JSON.parse(text2));
|
|
1349
|
+
} catch {
|
|
1350
|
+
return null;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
async function parseWebhookRequest(request, credential, options = {}) {
|
|
1354
|
+
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1355
|
+
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1356
|
+
if (signature === null || timestamp === null) return null;
|
|
1357
|
+
return parseWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1358
|
+
}
|
|
1359
|
+
async function parseWatchedWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1360
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1361
|
+
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1362
|
+
try {
|
|
1363
|
+
return triggerEventFromWire(JSON.parse(text2));
|
|
1364
|
+
} catch {
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
async function parseWatchedWebhookRequest(request, credential, options = {}) {
|
|
1369
|
+
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1370
|
+
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1371
|
+
if (signature === null || timestamp === null) return null;
|
|
1372
|
+
return parseWatchedWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1373
|
+
}
|
|
1374
|
+
async function answerWebhookChallenge(body, signature, credential, timestamp, options = {}) {
|
|
1375
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1376
|
+
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1377
|
+
const nonce = challenged(text2);
|
|
1378
|
+
if (nonce === null) return null;
|
|
1379
|
+
if (typeof credential !== "string") return JSON.stringify({ nonce });
|
|
1380
|
+
return JSON.stringify({
|
|
1381
|
+
nonce,
|
|
1382
|
+
signature: `${SHARED_SECRET_PREFIX}${await hmacHex(credential, nonce)}`
|
|
1383
|
+
});
|
|
1384
|
+
}
|
|
1385
|
+
async function answerWebhookChallengeRequest(request, credential, options = {}) {
|
|
1386
|
+
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1387
|
+
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1388
|
+
if (signature === null || timestamp === null) return null;
|
|
1389
|
+
const answer = await answerWebhookChallenge(
|
|
1390
|
+
await request.clone().text(),
|
|
1391
|
+
signature,
|
|
1392
|
+
credential,
|
|
1393
|
+
timestamp,
|
|
1394
|
+
options
|
|
1395
|
+
);
|
|
1396
|
+
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
1397
|
+
}
|
|
1398
|
+
function challenged(text2) {
|
|
1399
|
+
return nonceOf(text2, CHALLENGE);
|
|
1400
|
+
}
|
|
1401
|
+
function nonceOf(text2, type) {
|
|
1402
|
+
try {
|
|
1403
|
+
const said = JSON.parse(text2);
|
|
1404
|
+
if (said["type"] !== type || typeof said["nonce"] !== "string") return null;
|
|
1405
|
+
return said["nonce"];
|
|
1406
|
+
} catch {
|
|
1407
|
+
return null;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
function answerVerifyChallenge(body) {
|
|
1411
|
+
const nonce = nonceOf(body, VERIFY_CHALLENGE);
|
|
1412
|
+
return nonce === null ? null : JSON.stringify({ nonce });
|
|
1413
|
+
}
|
|
1414
|
+
async function answerVerifyChallengeRequest(request) {
|
|
1415
|
+
if (request.method !== "POST") return null;
|
|
1416
|
+
const answer = answerVerifyChallenge(await request.clone().text());
|
|
1417
|
+
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
1418
|
+
}
|
|
1419
|
+
function recent(timestamp, toleranceSecs) {
|
|
1420
|
+
const sent = Number(timestamp);
|
|
1421
|
+
if (!Number.isFinite(sent)) return false;
|
|
1422
|
+
return Math.abs(Math.floor(Date.now() / 1e3) - sent) <= toleranceSecs;
|
|
1423
|
+
}
|
|
1424
|
+
async function sign(secret, timestamp, body) {
|
|
1425
|
+
return hmacHex(secret, signed(timestamp, body));
|
|
1426
|
+
}
|
|
1427
|
+
function signed(timestamp, body) {
|
|
1428
|
+
const encoder = new TextEncoder();
|
|
1429
|
+
const stamp = encoder.encode(`${timestamp}.`);
|
|
1430
|
+
const rest = typeof body === "string" ? encoder.encode(body) : new Uint8Array(body);
|
|
1431
|
+
const payload = new Uint8Array(stamp.length + rest.length);
|
|
1432
|
+
payload.set(stamp);
|
|
1433
|
+
payload.set(rest, stamp.length);
|
|
1434
|
+
return payload;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1290
1437
|
// src/bank.ts
|
|
1291
1438
|
var DEFAULT_CURRENCY = "CZK";
|
|
1292
1439
|
var DEFAULT_LOOK_BACK_SECS = 7 * 24 * 60 * 60;
|
|
@@ -1321,6 +1468,8 @@ function bankVerifyEndpoint(config) {
|
|
|
1321
1468
|
"cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
|
|
1322
1469
|
};
|
|
1323
1470
|
return async (request) => {
|
|
1471
|
+
const consented = await answerVerifyChallengeRequest(request);
|
|
1472
|
+
if (consented !== null) return consented;
|
|
1324
1473
|
const asked = readQuery(new URL(request.url));
|
|
1325
1474
|
if (asked === null) return Response.json({ settled: false }, { status: 400 });
|
|
1326
1475
|
const subject = subjectOf(asked.reference, asked.amountMinor, asked.currency);
|
|
@@ -1791,138 +1940,6 @@ function refusals2(answers) {
|
|
|
1791
1940
|
(answer) => answer.reason instanceof Error ? answer.reason.message : String(answer.reason)
|
|
1792
1941
|
).join("; ");
|
|
1793
1942
|
}
|
|
1794
|
-
|
|
1795
|
-
// ../core/ed25519.ts
|
|
1796
|
-
var ALGORITHM = { name: "Ed25519" };
|
|
1797
|
-
var SIGNATURE_BYTES = 64;
|
|
1798
|
-
var PUBLIC_KEY_BYTES = 32;
|
|
1799
|
-
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
1800
|
-
async function verifyHex(publicKeyHex, signatureHex, payload) {
|
|
1801
|
-
if (!isHex(publicKeyHex) || publicKeyHex.length !== PUBLIC_KEY_BYTES * 2) return false;
|
|
1802
|
-
if (!isHex(signatureHex) || signatureHex.length !== SIGNATURE_BYTES * 2) return false;
|
|
1803
|
-
try {
|
|
1804
|
-
const key = await crypto.subtle.importKey(
|
|
1805
|
-
"raw",
|
|
1806
|
-
asBuffer2(hexToBytes(publicKeyHex)),
|
|
1807
|
-
ALGORITHM,
|
|
1808
|
-
false,
|
|
1809
|
-
["verify"]
|
|
1810
|
-
);
|
|
1811
|
-
return await crypto.subtle.verify(
|
|
1812
|
-
ALGORITHM,
|
|
1813
|
-
key,
|
|
1814
|
-
asBuffer2(hexToBytes(signatureHex)),
|
|
1815
|
-
asBuffer2(payload)
|
|
1816
|
-
);
|
|
1817
|
-
} catch {
|
|
1818
|
-
return false;
|
|
1819
|
-
}
|
|
1820
|
-
}
|
|
1821
|
-
function asBuffer2(bytes) {
|
|
1822
|
-
return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
|
|
1823
|
-
}
|
|
1824
|
-
|
|
1825
|
-
// src/webhook.ts
|
|
1826
|
-
var SIGNATURE_HEADER = "x-signature";
|
|
1827
|
-
var TIMESTAMP_HEADER = "x-timestamp";
|
|
1828
|
-
var SHARED_SECRET_PREFIX = "sha256=";
|
|
1829
|
-
var GATEWAY_KEY_PREFIX = "ed25519=";
|
|
1830
|
-
var DEFAULT_TOLERANCE_SECS = 300;
|
|
1831
|
-
var CHALLENGE = "webhook-challenge";
|
|
1832
|
-
async function verifyWebhookSignature(body, signature, credential, timestamp, options = {}) {
|
|
1833
|
-
if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) return false;
|
|
1834
|
-
if (typeof credential !== "string") {
|
|
1835
|
-
if (!signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1836
|
-
return verifyHex(
|
|
1837
|
-
credential.publicKey.toLowerCase(),
|
|
1838
|
-
signature.slice(GATEWAY_KEY_PREFIX.length).toLowerCase(),
|
|
1839
|
-
signed(timestamp, body)
|
|
1840
|
-
);
|
|
1841
|
-
}
|
|
1842
|
-
if (signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1843
|
-
const received = signature.startsWith(SHARED_SECRET_PREFIX) ? signature.slice(SHARED_SECRET_PREFIX.length) : signature;
|
|
1844
|
-
return equalInConstantTime(await sign(credential, timestamp, body), received.toLowerCase());
|
|
1845
|
-
}
|
|
1846
|
-
async function parseWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1847
|
-
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1848
|
-
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1849
|
-
try {
|
|
1850
|
-
return paymentFromWire(JSON.parse(text2));
|
|
1851
|
-
} catch {
|
|
1852
|
-
return null;
|
|
1853
|
-
}
|
|
1854
|
-
}
|
|
1855
|
-
async function parseWebhookRequest(request, credential, options = {}) {
|
|
1856
|
-
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1857
|
-
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1858
|
-
if (signature === null || timestamp === null) return null;
|
|
1859
|
-
return parseWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1860
|
-
}
|
|
1861
|
-
async function parseWatchedWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1862
|
-
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1863
|
-
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1864
|
-
try {
|
|
1865
|
-
return triggerEventFromWire(JSON.parse(text2));
|
|
1866
|
-
} catch {
|
|
1867
|
-
return null;
|
|
1868
|
-
}
|
|
1869
|
-
}
|
|
1870
|
-
async function parseWatchedWebhookRequest(request, credential, options = {}) {
|
|
1871
|
-
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1872
|
-
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1873
|
-
if (signature === null || timestamp === null) return null;
|
|
1874
|
-
return parseWatchedWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1875
|
-
}
|
|
1876
|
-
async function answerWebhookChallenge(body, signature, credential, timestamp, options = {}) {
|
|
1877
|
-
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1878
|
-
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1879
|
-
const nonce = challenged(text2);
|
|
1880
|
-
if (nonce === null) return null;
|
|
1881
|
-
if (typeof credential !== "string") return JSON.stringify({ nonce });
|
|
1882
|
-
return JSON.stringify({
|
|
1883
|
-
nonce,
|
|
1884
|
-
signature: `${SHARED_SECRET_PREFIX}${await hmacHex(credential, nonce)}`
|
|
1885
|
-
});
|
|
1886
|
-
}
|
|
1887
|
-
async function answerWebhookChallengeRequest(request, credential, options = {}) {
|
|
1888
|
-
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1889
|
-
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1890
|
-
if (signature === null || timestamp === null) return null;
|
|
1891
|
-
const answer = await answerWebhookChallenge(
|
|
1892
|
-
await request.clone().text(),
|
|
1893
|
-
signature,
|
|
1894
|
-
credential,
|
|
1895
|
-
timestamp,
|
|
1896
|
-
options
|
|
1897
|
-
);
|
|
1898
|
-
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
1899
|
-
}
|
|
1900
|
-
function challenged(text2) {
|
|
1901
|
-
try {
|
|
1902
|
-
const said = JSON.parse(text2);
|
|
1903
|
-
if (said["type"] !== CHALLENGE || typeof said["nonce"] !== "string") return null;
|
|
1904
|
-
return said["nonce"];
|
|
1905
|
-
} catch {
|
|
1906
|
-
return null;
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
function recent(timestamp, toleranceSecs) {
|
|
1910
|
-
const sent = Number(timestamp);
|
|
1911
|
-
if (!Number.isFinite(sent)) return false;
|
|
1912
|
-
return Math.abs(Math.floor(Date.now() / 1e3) - sent) <= toleranceSecs;
|
|
1913
|
-
}
|
|
1914
|
-
async function sign(secret, timestamp, body) {
|
|
1915
|
-
return hmacHex(secret, signed(timestamp, body));
|
|
1916
|
-
}
|
|
1917
|
-
function signed(timestamp, body) {
|
|
1918
|
-
const encoder = new TextEncoder();
|
|
1919
|
-
const stamp = encoder.encode(`${timestamp}.`);
|
|
1920
|
-
const rest = typeof body === "string" ? encoder.encode(body) : new Uint8Array(body);
|
|
1921
|
-
const payload = new Uint8Array(stamp.length + rest.length);
|
|
1922
|
-
payload.set(stamp);
|
|
1923
|
-
payload.set(rest, stamp.length);
|
|
1924
|
-
return payload;
|
|
1925
|
-
}
|
|
1926
1943
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1927
1944
|
0 && (module.exports = {
|
|
1928
1945
|
GatewayCheatError,
|
|
@@ -1935,6 +1952,8 @@ function signed(timestamp, body) {
|
|
|
1935
1952
|
REQUEST_IN_FLIGHT,
|
|
1936
1953
|
ThunderBridge,
|
|
1937
1954
|
UnverifiedRecipientError,
|
|
1955
|
+
answerVerifyChallenge,
|
|
1956
|
+
answerVerifyChallengeRequest,
|
|
1938
1957
|
answerWebhookChallenge,
|
|
1939
1958
|
answerWebhookChallengeRequest,
|
|
1940
1959
|
bankRail,
|
package/dist/index.d.cts
CHANGED
|
@@ -381,6 +381,18 @@ declare function answerWebhookChallenge(body: string | Uint8Array, signature: st
|
|
|
381
381
|
* the same handler can go on to `parseWebhookRequest` when this was no challenge
|
|
382
382
|
*/
|
|
383
383
|
declare function answerWebhookChallengeRequest(request: Request, credential: WebhookCredential, options?: WebhookOptions): Promise<Response | null>;
|
|
384
|
+
/**
|
|
385
|
+
* Answer the challenge the gateway sends a verify URL before it will poll it,
|
|
386
|
+
* which is how a caller shows the endpoint agreed to the traffic rather than
|
|
387
|
+
* merely being named. Returns null for anything that is not a challenge, so a
|
|
388
|
+
* verify endpoint hands the request on to its own reading of a payment.
|
|
389
|
+
*
|
|
390
|
+
* The nonce is echoed to whoever asked, which grants them nothing, so there is
|
|
391
|
+
* no signature to check here and no secret to hold
|
|
392
|
+
*/
|
|
393
|
+
declare function answerVerifyChallenge(body: string): string | null;
|
|
394
|
+
/** {@link answerVerifyChallenge} against a `Request`, leaving its body unread */
|
|
395
|
+
declare function answerVerifyChallengeRequest(request: Request): Promise<Response | null>;
|
|
384
396
|
|
|
385
397
|
/**
|
|
386
398
|
* The way a gateway was caught out, every code is a check that held against the
|
|
@@ -458,4 +470,4 @@ declare class NoWalletAvailableError extends ProblemError {
|
|
|
458
470
|
}, wallets: WalletFailure[]);
|
|
459
471
|
}
|
|
460
472
|
|
|
461
|
-
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 };
|
|
473
|
+
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, answerVerifyChallenge, answerVerifyChallengeRequest, 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
|
@@ -381,6 +381,18 @@ declare function answerWebhookChallenge(body: string | Uint8Array, signature: st
|
|
|
381
381
|
* the same handler can go on to `parseWebhookRequest` when this was no challenge
|
|
382
382
|
*/
|
|
383
383
|
declare function answerWebhookChallengeRequest(request: Request, credential: WebhookCredential, options?: WebhookOptions): Promise<Response | null>;
|
|
384
|
+
/**
|
|
385
|
+
* Answer the challenge the gateway sends a verify URL before it will poll it,
|
|
386
|
+
* which is how a caller shows the endpoint agreed to the traffic rather than
|
|
387
|
+
* merely being named. Returns null for anything that is not a challenge, so a
|
|
388
|
+
* verify endpoint hands the request on to its own reading of a payment.
|
|
389
|
+
*
|
|
390
|
+
* The nonce is echoed to whoever asked, which grants them nothing, so there is
|
|
391
|
+
* no signature to check here and no secret to hold
|
|
392
|
+
*/
|
|
393
|
+
declare function answerVerifyChallenge(body: string): string | null;
|
|
394
|
+
/** {@link answerVerifyChallenge} against a `Request`, leaving its body unread */
|
|
395
|
+
declare function answerVerifyChallengeRequest(request: Request): Promise<Response | null>;
|
|
384
396
|
|
|
385
397
|
/**
|
|
386
398
|
* The way a gateway was caught out, every code is a check that held against the
|
|
@@ -458,4 +470,4 @@ declare class NoWalletAvailableError extends ProblemError {
|
|
|
458
470
|
}, wallets: WalletFailure[]);
|
|
459
471
|
}
|
|
460
472
|
|
|
461
|
-
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 };
|
|
473
|
+
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, answerVerifyChallenge, answerVerifyChallengeRequest, 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
|
@@ -1215,6 +1215,151 @@ function minorScaleOf(currency) {
|
|
|
1215
1215
|
return 10 ** minorUnitsOf(currency);
|
|
1216
1216
|
}
|
|
1217
1217
|
|
|
1218
|
+
// ../core/ed25519.ts
|
|
1219
|
+
var ALGORITHM = { name: "Ed25519" };
|
|
1220
|
+
var SIGNATURE_BYTES = 64;
|
|
1221
|
+
var PUBLIC_KEY_BYTES = 32;
|
|
1222
|
+
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
1223
|
+
async function verifyHex(publicKeyHex, signatureHex, payload) {
|
|
1224
|
+
if (!isHex(publicKeyHex) || publicKeyHex.length !== PUBLIC_KEY_BYTES * 2) return false;
|
|
1225
|
+
if (!isHex(signatureHex) || signatureHex.length !== SIGNATURE_BYTES * 2) return false;
|
|
1226
|
+
try {
|
|
1227
|
+
const key = await crypto.subtle.importKey(
|
|
1228
|
+
"raw",
|
|
1229
|
+
asBuffer2(hexToBytes(publicKeyHex)),
|
|
1230
|
+
ALGORITHM,
|
|
1231
|
+
false,
|
|
1232
|
+
["verify"]
|
|
1233
|
+
);
|
|
1234
|
+
return await crypto.subtle.verify(
|
|
1235
|
+
ALGORITHM,
|
|
1236
|
+
key,
|
|
1237
|
+
asBuffer2(hexToBytes(signatureHex)),
|
|
1238
|
+
asBuffer2(payload)
|
|
1239
|
+
);
|
|
1240
|
+
} catch {
|
|
1241
|
+
return false;
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function asBuffer2(bytes) {
|
|
1245
|
+
return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/webhook.ts
|
|
1249
|
+
var SIGNATURE_HEADER = "x-signature";
|
|
1250
|
+
var TIMESTAMP_HEADER = "x-timestamp";
|
|
1251
|
+
var SHARED_SECRET_PREFIX = "sha256=";
|
|
1252
|
+
var GATEWAY_KEY_PREFIX = "ed25519=";
|
|
1253
|
+
var DEFAULT_TOLERANCE_SECS = 300;
|
|
1254
|
+
var CHALLENGE = "webhook-challenge";
|
|
1255
|
+
var VERIFY_CHALLENGE = "verify-challenge";
|
|
1256
|
+
async function verifyWebhookSignature(body, signature, credential, timestamp, options = {}) {
|
|
1257
|
+
if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) return false;
|
|
1258
|
+
if (typeof credential !== "string") {
|
|
1259
|
+
if (!signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1260
|
+
return verifyHex(
|
|
1261
|
+
credential.publicKey.toLowerCase(),
|
|
1262
|
+
signature.slice(GATEWAY_KEY_PREFIX.length).toLowerCase(),
|
|
1263
|
+
signed(timestamp, body)
|
|
1264
|
+
);
|
|
1265
|
+
}
|
|
1266
|
+
if (signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1267
|
+
const received = signature.startsWith(SHARED_SECRET_PREFIX) ? signature.slice(SHARED_SECRET_PREFIX.length) : signature;
|
|
1268
|
+
return equalInConstantTime(await sign(credential, timestamp, body), received.toLowerCase());
|
|
1269
|
+
}
|
|
1270
|
+
async function parseWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1271
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1272
|
+
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1273
|
+
try {
|
|
1274
|
+
return paymentFromWire(JSON.parse(text2));
|
|
1275
|
+
} catch {
|
|
1276
|
+
return null;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
async function parseWebhookRequest(request, credential, options = {}) {
|
|
1280
|
+
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1281
|
+
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1282
|
+
if (signature === null || timestamp === null) return null;
|
|
1283
|
+
return parseWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1284
|
+
}
|
|
1285
|
+
async function parseWatchedWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1286
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1287
|
+
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1288
|
+
try {
|
|
1289
|
+
return triggerEventFromWire(JSON.parse(text2));
|
|
1290
|
+
} catch {
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
async function parseWatchedWebhookRequest(request, credential, options = {}) {
|
|
1295
|
+
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1296
|
+
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1297
|
+
if (signature === null || timestamp === null) return null;
|
|
1298
|
+
return parseWatchedWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1299
|
+
}
|
|
1300
|
+
async function answerWebhookChallenge(body, signature, credential, timestamp, options = {}) {
|
|
1301
|
+
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1302
|
+
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1303
|
+
const nonce = challenged(text2);
|
|
1304
|
+
if (nonce === null) return null;
|
|
1305
|
+
if (typeof credential !== "string") return JSON.stringify({ nonce });
|
|
1306
|
+
return JSON.stringify({
|
|
1307
|
+
nonce,
|
|
1308
|
+
signature: `${SHARED_SECRET_PREFIX}${await hmacHex(credential, nonce)}`
|
|
1309
|
+
});
|
|
1310
|
+
}
|
|
1311
|
+
async function answerWebhookChallengeRequest(request, credential, options = {}) {
|
|
1312
|
+
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1313
|
+
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1314
|
+
if (signature === null || timestamp === null) return null;
|
|
1315
|
+
const answer = await answerWebhookChallenge(
|
|
1316
|
+
await request.clone().text(),
|
|
1317
|
+
signature,
|
|
1318
|
+
credential,
|
|
1319
|
+
timestamp,
|
|
1320
|
+
options
|
|
1321
|
+
);
|
|
1322
|
+
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
1323
|
+
}
|
|
1324
|
+
function challenged(text2) {
|
|
1325
|
+
return nonceOf(text2, CHALLENGE);
|
|
1326
|
+
}
|
|
1327
|
+
function nonceOf(text2, type) {
|
|
1328
|
+
try {
|
|
1329
|
+
const said = JSON.parse(text2);
|
|
1330
|
+
if (said["type"] !== type || typeof said["nonce"] !== "string") return null;
|
|
1331
|
+
return said["nonce"];
|
|
1332
|
+
} catch {
|
|
1333
|
+
return null;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
function answerVerifyChallenge(body) {
|
|
1337
|
+
const nonce = nonceOf(body, VERIFY_CHALLENGE);
|
|
1338
|
+
return nonce === null ? null : JSON.stringify({ nonce });
|
|
1339
|
+
}
|
|
1340
|
+
async function answerVerifyChallengeRequest(request) {
|
|
1341
|
+
if (request.method !== "POST") return null;
|
|
1342
|
+
const answer = answerVerifyChallenge(await request.clone().text());
|
|
1343
|
+
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
1344
|
+
}
|
|
1345
|
+
function recent(timestamp, toleranceSecs) {
|
|
1346
|
+
const sent = Number(timestamp);
|
|
1347
|
+
if (!Number.isFinite(sent)) return false;
|
|
1348
|
+
return Math.abs(Math.floor(Date.now() / 1e3) - sent) <= toleranceSecs;
|
|
1349
|
+
}
|
|
1350
|
+
async function sign(secret, timestamp, body) {
|
|
1351
|
+
return hmacHex(secret, signed(timestamp, body));
|
|
1352
|
+
}
|
|
1353
|
+
function signed(timestamp, body) {
|
|
1354
|
+
const encoder = new TextEncoder();
|
|
1355
|
+
const stamp = encoder.encode(`${timestamp}.`);
|
|
1356
|
+
const rest = typeof body === "string" ? encoder.encode(body) : new Uint8Array(body);
|
|
1357
|
+
const payload = new Uint8Array(stamp.length + rest.length);
|
|
1358
|
+
payload.set(stamp);
|
|
1359
|
+
payload.set(rest, stamp.length);
|
|
1360
|
+
return payload;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1218
1363
|
// src/bank.ts
|
|
1219
1364
|
var DEFAULT_CURRENCY = "CZK";
|
|
1220
1365
|
var DEFAULT_LOOK_BACK_SECS = 7 * 24 * 60 * 60;
|
|
@@ -1249,6 +1394,8 @@ function bankVerifyEndpoint(config) {
|
|
|
1249
1394
|
"cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
|
|
1250
1395
|
};
|
|
1251
1396
|
return async (request) => {
|
|
1397
|
+
const consented = await answerVerifyChallengeRequest(request);
|
|
1398
|
+
if (consented !== null) return consented;
|
|
1252
1399
|
const asked = readQuery(new URL(request.url));
|
|
1253
1400
|
if (asked === null) return Response.json({ settled: false }, { status: 400 });
|
|
1254
1401
|
const subject = subjectOf(asked.reference, asked.amountMinor, asked.currency);
|
|
@@ -1719,138 +1866,6 @@ function refusals2(answers) {
|
|
|
1719
1866
|
(answer) => answer.reason instanceof Error ? answer.reason.message : String(answer.reason)
|
|
1720
1867
|
).join("; ");
|
|
1721
1868
|
}
|
|
1722
|
-
|
|
1723
|
-
// ../core/ed25519.ts
|
|
1724
|
-
var ALGORITHM = { name: "Ed25519" };
|
|
1725
|
-
var SIGNATURE_BYTES = 64;
|
|
1726
|
-
var PUBLIC_KEY_BYTES = 32;
|
|
1727
|
-
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
1728
|
-
async function verifyHex(publicKeyHex, signatureHex, payload) {
|
|
1729
|
-
if (!isHex(publicKeyHex) || publicKeyHex.length !== PUBLIC_KEY_BYTES * 2) return false;
|
|
1730
|
-
if (!isHex(signatureHex) || signatureHex.length !== SIGNATURE_BYTES * 2) return false;
|
|
1731
|
-
try {
|
|
1732
|
-
const key = await crypto.subtle.importKey(
|
|
1733
|
-
"raw",
|
|
1734
|
-
asBuffer2(hexToBytes(publicKeyHex)),
|
|
1735
|
-
ALGORITHM,
|
|
1736
|
-
false,
|
|
1737
|
-
["verify"]
|
|
1738
|
-
);
|
|
1739
|
-
return await crypto.subtle.verify(
|
|
1740
|
-
ALGORITHM,
|
|
1741
|
-
key,
|
|
1742
|
-
asBuffer2(hexToBytes(signatureHex)),
|
|
1743
|
-
asBuffer2(payload)
|
|
1744
|
-
);
|
|
1745
|
-
} catch {
|
|
1746
|
-
return false;
|
|
1747
|
-
}
|
|
1748
|
-
}
|
|
1749
|
-
function asBuffer2(bytes) {
|
|
1750
|
-
return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
|
|
1751
|
-
}
|
|
1752
|
-
|
|
1753
|
-
// src/webhook.ts
|
|
1754
|
-
var SIGNATURE_HEADER = "x-signature";
|
|
1755
|
-
var TIMESTAMP_HEADER = "x-timestamp";
|
|
1756
|
-
var SHARED_SECRET_PREFIX = "sha256=";
|
|
1757
|
-
var GATEWAY_KEY_PREFIX = "ed25519=";
|
|
1758
|
-
var DEFAULT_TOLERANCE_SECS = 300;
|
|
1759
|
-
var CHALLENGE = "webhook-challenge";
|
|
1760
|
-
async function verifyWebhookSignature(body, signature, credential, timestamp, options = {}) {
|
|
1761
|
-
if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) return false;
|
|
1762
|
-
if (typeof credential !== "string") {
|
|
1763
|
-
if (!signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1764
|
-
return verifyHex(
|
|
1765
|
-
credential.publicKey.toLowerCase(),
|
|
1766
|
-
signature.slice(GATEWAY_KEY_PREFIX.length).toLowerCase(),
|
|
1767
|
-
signed(timestamp, body)
|
|
1768
|
-
);
|
|
1769
|
-
}
|
|
1770
|
-
if (signature.startsWith(GATEWAY_KEY_PREFIX)) return false;
|
|
1771
|
-
const received = signature.startsWith(SHARED_SECRET_PREFIX) ? signature.slice(SHARED_SECRET_PREFIX.length) : signature;
|
|
1772
|
-
return equalInConstantTime(await sign(credential, timestamp, body), received.toLowerCase());
|
|
1773
|
-
}
|
|
1774
|
-
async function parseWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1775
|
-
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1776
|
-
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1777
|
-
try {
|
|
1778
|
-
return paymentFromWire(JSON.parse(text2));
|
|
1779
|
-
} catch {
|
|
1780
|
-
return null;
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
async function parseWebhookRequest(request, credential, options = {}) {
|
|
1784
|
-
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1785
|
-
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1786
|
-
if (signature === null || timestamp === null) return null;
|
|
1787
|
-
return parseWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1788
|
-
}
|
|
1789
|
-
async function parseWatchedWebhook(body, signature, credential, timestamp, options = {}) {
|
|
1790
|
-
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1791
|
-
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1792
|
-
try {
|
|
1793
|
-
return triggerEventFromWire(JSON.parse(text2));
|
|
1794
|
-
} catch {
|
|
1795
|
-
return null;
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
async function parseWatchedWebhookRequest(request, credential, options = {}) {
|
|
1799
|
-
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1800
|
-
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1801
|
-
if (signature === null || timestamp === null) return null;
|
|
1802
|
-
return parseWatchedWebhook(await request.text(), signature, credential, timestamp, options);
|
|
1803
|
-
}
|
|
1804
|
-
async function answerWebhookChallenge(body, signature, credential, timestamp, options = {}) {
|
|
1805
|
-
if (!await verifyWebhookSignature(body, signature, credential, timestamp, options)) return null;
|
|
1806
|
-
const text2 = typeof body === "string" ? body : new TextDecoder().decode(body);
|
|
1807
|
-
const nonce = challenged(text2);
|
|
1808
|
-
if (nonce === null) return null;
|
|
1809
|
-
if (typeof credential !== "string") return JSON.stringify({ nonce });
|
|
1810
|
-
return JSON.stringify({
|
|
1811
|
-
nonce,
|
|
1812
|
-
signature: `${SHARED_SECRET_PREFIX}${await hmacHex(credential, nonce)}`
|
|
1813
|
-
});
|
|
1814
|
-
}
|
|
1815
|
-
async function answerWebhookChallengeRequest(request, credential, options = {}) {
|
|
1816
|
-
const signature = request.headers.get(SIGNATURE_HEADER);
|
|
1817
|
-
const timestamp = request.headers.get(TIMESTAMP_HEADER);
|
|
1818
|
-
if (signature === null || timestamp === null) return null;
|
|
1819
|
-
const answer = await answerWebhookChallenge(
|
|
1820
|
-
await request.clone().text(),
|
|
1821
|
-
signature,
|
|
1822
|
-
credential,
|
|
1823
|
-
timestamp,
|
|
1824
|
-
options
|
|
1825
|
-
);
|
|
1826
|
-
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
1827
|
-
}
|
|
1828
|
-
function challenged(text2) {
|
|
1829
|
-
try {
|
|
1830
|
-
const said = JSON.parse(text2);
|
|
1831
|
-
if (said["type"] !== CHALLENGE || typeof said["nonce"] !== "string") return null;
|
|
1832
|
-
return said["nonce"];
|
|
1833
|
-
} catch {
|
|
1834
|
-
return null;
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
function recent(timestamp, toleranceSecs) {
|
|
1838
|
-
const sent = Number(timestamp);
|
|
1839
|
-
if (!Number.isFinite(sent)) return false;
|
|
1840
|
-
return Math.abs(Math.floor(Date.now() / 1e3) - sent) <= toleranceSecs;
|
|
1841
|
-
}
|
|
1842
|
-
async function sign(secret, timestamp, body) {
|
|
1843
|
-
return hmacHex(secret, signed(timestamp, body));
|
|
1844
|
-
}
|
|
1845
|
-
function signed(timestamp, body) {
|
|
1846
|
-
const encoder = new TextEncoder();
|
|
1847
|
-
const stamp = encoder.encode(`${timestamp}.`);
|
|
1848
|
-
const rest = typeof body === "string" ? encoder.encode(body) : new Uint8Array(body);
|
|
1849
|
-
const payload = new Uint8Array(stamp.length + rest.length);
|
|
1850
|
-
payload.set(stamp);
|
|
1851
|
-
payload.set(rest, stamp.length);
|
|
1852
|
-
return payload;
|
|
1853
|
-
}
|
|
1854
1869
|
export {
|
|
1855
1870
|
GatewayCheatError,
|
|
1856
1871
|
IDEMPOTENCY_KEY_REUSED,
|
|
@@ -1862,6 +1877,8 @@ export {
|
|
|
1862
1877
|
REQUEST_IN_FLIGHT,
|
|
1863
1878
|
ThunderBridge,
|
|
1864
1879
|
UnverifiedRecipientError,
|
|
1880
|
+
answerVerifyChallenge,
|
|
1881
|
+
answerVerifyChallengeRequest,
|
|
1865
1882
|
answerWebhookChallenge,
|
|
1866
1883
|
answerWebhookChallengeRequest,
|
|
1867
1884
|
bankRail,
|
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
|
|
412
|
-
const response = await
|
|
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
|
|
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
|
|
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;
|
|
@@ -494,6 +500,7 @@ var NoWalletAvailable = class extends Error {
|
|
|
494
500
|
// ../core/lnurl.ts
|
|
495
501
|
var VERIFY_WITHOUT_PREIMAGE = ["zeuspay.com", "zeusnuts.com", "ecash.love"];
|
|
496
502
|
var RESOLVE_TIMEOUT_MS = 3e4;
|
|
503
|
+
var NAMES_ONE_ACCOUNT = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
|
|
497
504
|
var MIN_PACE_SECS = 1;
|
|
498
505
|
var MAX_PACE_SECS = 3600;
|
|
499
506
|
var MIN_PER_SECOND = 0.01;
|
|
@@ -622,11 +629,19 @@ function withAmount(callback, amountMsat) {
|
|
|
622
629
|
}
|
|
623
630
|
function splitAddress(address) {
|
|
624
631
|
const [user, domain, ...rest] = address.split("@");
|
|
625
|
-
if (!user || !domain || rest.length > 0 || !publicHttps(`https://${domain}/`)) {
|
|
632
|
+
if (!user || !domain || rest.length > 0 || !NAMES_ONE_ACCOUNT.test(user) || !isNothingButAHost(domain) || !publicHttps(`https://${domain}/`)) {
|
|
626
633
|
throw new WalletRefused("address-unusable", `${address} is not a usable lightning address`);
|
|
627
634
|
}
|
|
628
635
|
return [user, domain];
|
|
629
636
|
}
|
|
637
|
+
function isNothingButAHost(domain) {
|
|
638
|
+
try {
|
|
639
|
+
const parsed = new URL(`https://${domain}/`);
|
|
640
|
+
return parsed.host === domain.toLowerCase() && parsed.pathname === "/" && parsed.search === "" && parsed.hash === "";
|
|
641
|
+
} catch {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
630
645
|
async function fetchJson(url, deadline) {
|
|
631
646
|
return (await answeredJson(url, deadline)).said;
|
|
632
647
|
}
|
|
@@ -799,21 +814,21 @@ async function mintThroughGateway(config, address, amountMsat, nonce) {
|
|
|
799
814
|
return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
|
|
800
815
|
}
|
|
801
816
|
async function mintBlind(config, address, amountMsat) {
|
|
802
|
-
const
|
|
803
|
-
const minted = { ...
|
|
817
|
+
const resolved2 = await resolve([address], amountMsat);
|
|
818
|
+
const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
|
|
804
819
|
const locked = config.sealed;
|
|
805
820
|
try {
|
|
806
821
|
await config.gateway.watchPayment({
|
|
807
|
-
paymentHash:
|
|
808
|
-
verifyUrl:
|
|
809
|
-
expiresAt:
|
|
822
|
+
paymentHash: resolved2.paymentHash,
|
|
823
|
+
verifyUrl: resolved2.verifyUrl,
|
|
824
|
+
expiresAt: resolved2.expiresAt,
|
|
810
825
|
trigger: config.watchSecret,
|
|
811
826
|
sealed: locked ? await seal(locked.secret, JSON.stringify(locked.data(minted))) : void 0
|
|
812
827
|
});
|
|
813
828
|
} catch (refused) {
|
|
814
829
|
if (!alreadyWatched(refused)) throw refused;
|
|
815
830
|
}
|
|
816
|
-
return { bolt11:
|
|
831
|
+
return { bolt11: resolved2.bolt11, verifyUrl: resolved2.verifyUrl };
|
|
817
832
|
}
|
|
818
833
|
function alreadyWatched(refused) {
|
|
819
834
|
return refused instanceof ProblemError && isProblemType(refused, PAYMENT_ALREADY_WATCHED);
|
|
@@ -831,6 +846,30 @@ function refuse(reason) {
|
|
|
831
846
|
return Response.json({ status: "ERROR", reason });
|
|
832
847
|
}
|
|
833
848
|
|
|
849
|
+
// ../core/ed25519.ts
|
|
850
|
+
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
851
|
+
|
|
852
|
+
// src/webhook.ts
|
|
853
|
+
var VERIFY_CHALLENGE = "verify-challenge";
|
|
854
|
+
function nonceOf(text2, type) {
|
|
855
|
+
try {
|
|
856
|
+
const said = JSON.parse(text2);
|
|
857
|
+
if (said["type"] !== type || typeof said["nonce"] !== "string") return null;
|
|
858
|
+
return said["nonce"];
|
|
859
|
+
} catch {
|
|
860
|
+
return null;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
function answerVerifyChallenge(body) {
|
|
864
|
+
const nonce = nonceOf(body, VERIFY_CHALLENGE);
|
|
865
|
+
return nonce === null ? null : JSON.stringify({ nonce });
|
|
866
|
+
}
|
|
867
|
+
async function answerVerifyChallengeRequest(request) {
|
|
868
|
+
if (request.method !== "POST") return null;
|
|
869
|
+
const answer = answerVerifyChallenge(await request.clone().text());
|
|
870
|
+
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
871
|
+
}
|
|
872
|
+
|
|
834
873
|
// src/qr.ts
|
|
835
874
|
var import_uqr = require("uqr");
|
|
836
875
|
function encodeForQr(destination) {
|
|
@@ -850,6 +889,8 @@ function lightningVerifyEndpoint(config) {
|
|
|
850
889
|
"cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
|
|
851
890
|
};
|
|
852
891
|
return async (request) => {
|
|
892
|
+
const consented = await answerVerifyChallengeRequest(request);
|
|
893
|
+
if (consented !== null) return consented;
|
|
853
894
|
const sealed = new URL(request.url).searchParams.get(WALLET);
|
|
854
895
|
if (sealed === null) return Response.json({ settled: false }, { status: 400 });
|
|
855
896
|
const opened = await unseal(config.secret, sealed);
|
|
@@ -873,16 +914,16 @@ async function relayedVerifyUrl(endpoint, wallet, secret) {
|
|
|
873
914
|
var LIGHTNING = "lightning";
|
|
874
915
|
function blindLightningRail(config) {
|
|
875
916
|
return async (order) => {
|
|
876
|
-
const
|
|
917
|
+
const resolved2 = await invoiceFrom(config.lnAddresses, await config.amountMsat(order));
|
|
877
918
|
const relay = config.relayVerifyThrough;
|
|
878
919
|
const watched = await config.gateway.watchPayment({
|
|
879
|
-
paymentHash:
|
|
920
|
+
paymentHash: resolved2.paymentHash,
|
|
880
921
|
verifyUrl: relay ? await relayedVerifyUrl(
|
|
881
922
|
relay.endpoint,
|
|
882
|
-
{ url:
|
|
923
|
+
{ url: resolved2.verifyUrl, hash: resolved2.paymentHash },
|
|
883
924
|
relay.secret
|
|
884
|
-
) :
|
|
885
|
-
expiresAt:
|
|
925
|
+
) : resolved2.verifyUrl,
|
|
926
|
+
expiresAt: resolved2.expiresAt,
|
|
886
927
|
trigger: config.trigger,
|
|
887
928
|
sealed: await config.sealed?.(order),
|
|
888
929
|
webhookUrl: config.webhookUrl,
|
|
@@ -891,9 +932,9 @@ function blindLightningRail(config) {
|
|
|
891
932
|
return {
|
|
892
933
|
id: watched.id,
|
|
893
934
|
rail: config.name ?? LIGHTNING,
|
|
894
|
-
scan:
|
|
895
|
-
qr: encodeForQr(
|
|
896
|
-
expiresAt:
|
|
935
|
+
scan: resolved2.bolt11,
|
|
936
|
+
qr: encodeForQr(resolved2.bolt11),
|
|
937
|
+
expiresAt: resolved2.expiresAt
|
|
897
938
|
};
|
|
898
939
|
};
|
|
899
940
|
}
|
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
|
|
373
|
-
const response = await
|
|
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
|
|
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
|
|
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;
|
|
@@ -455,6 +461,7 @@ var NoWalletAvailable = class extends Error {
|
|
|
455
461
|
// ../core/lnurl.ts
|
|
456
462
|
var VERIFY_WITHOUT_PREIMAGE = ["zeuspay.com", "zeusnuts.com", "ecash.love"];
|
|
457
463
|
var RESOLVE_TIMEOUT_MS = 3e4;
|
|
464
|
+
var NAMES_ONE_ACCOUNT = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
|
|
458
465
|
var MIN_PACE_SECS = 1;
|
|
459
466
|
var MAX_PACE_SECS = 3600;
|
|
460
467
|
var MIN_PER_SECOND = 0.01;
|
|
@@ -583,11 +590,19 @@ function withAmount(callback, amountMsat) {
|
|
|
583
590
|
}
|
|
584
591
|
function splitAddress(address) {
|
|
585
592
|
const [user, domain, ...rest] = address.split("@");
|
|
586
|
-
if (!user || !domain || rest.length > 0 || !publicHttps(`https://${domain}/`)) {
|
|
593
|
+
if (!user || !domain || rest.length > 0 || !NAMES_ONE_ACCOUNT.test(user) || !isNothingButAHost(domain) || !publicHttps(`https://${domain}/`)) {
|
|
587
594
|
throw new WalletRefused("address-unusable", `${address} is not a usable lightning address`);
|
|
588
595
|
}
|
|
589
596
|
return [user, domain];
|
|
590
597
|
}
|
|
598
|
+
function isNothingButAHost(domain) {
|
|
599
|
+
try {
|
|
600
|
+
const parsed = new URL(`https://${domain}/`);
|
|
601
|
+
return parsed.host === domain.toLowerCase() && parsed.pathname === "/" && parsed.search === "" && parsed.hash === "";
|
|
602
|
+
} catch {
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
591
606
|
async function fetchJson(url, deadline) {
|
|
592
607
|
return (await answeredJson(url, deadline)).said;
|
|
593
608
|
}
|
|
@@ -760,21 +775,21 @@ async function mintThroughGateway(config, address, amountMsat, nonce) {
|
|
|
760
775
|
return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
|
|
761
776
|
}
|
|
762
777
|
async function mintBlind(config, address, amountMsat) {
|
|
763
|
-
const
|
|
764
|
-
const minted = { ...
|
|
778
|
+
const resolved2 = await resolve([address], amountMsat);
|
|
779
|
+
const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
|
|
765
780
|
const locked = config.sealed;
|
|
766
781
|
try {
|
|
767
782
|
await config.gateway.watchPayment({
|
|
768
|
-
paymentHash:
|
|
769
|
-
verifyUrl:
|
|
770
|
-
expiresAt:
|
|
783
|
+
paymentHash: resolved2.paymentHash,
|
|
784
|
+
verifyUrl: resolved2.verifyUrl,
|
|
785
|
+
expiresAt: resolved2.expiresAt,
|
|
771
786
|
trigger: config.watchSecret,
|
|
772
787
|
sealed: locked ? await seal(locked.secret, JSON.stringify(locked.data(minted))) : void 0
|
|
773
788
|
});
|
|
774
789
|
} catch (refused) {
|
|
775
790
|
if (!alreadyWatched(refused)) throw refused;
|
|
776
791
|
}
|
|
777
|
-
return { bolt11:
|
|
792
|
+
return { bolt11: resolved2.bolt11, verifyUrl: resolved2.verifyUrl };
|
|
778
793
|
}
|
|
779
794
|
function alreadyWatched(refused) {
|
|
780
795
|
return refused instanceof ProblemError && isProblemType(refused, PAYMENT_ALREADY_WATCHED);
|
|
@@ -792,6 +807,30 @@ function refuse(reason) {
|
|
|
792
807
|
return Response.json({ status: "ERROR", reason });
|
|
793
808
|
}
|
|
794
809
|
|
|
810
|
+
// ../core/ed25519.ts
|
|
811
|
+
var PKCS8_HEADER = hexToBytes("302e020100300506032b657004220420");
|
|
812
|
+
|
|
813
|
+
// src/webhook.ts
|
|
814
|
+
var VERIFY_CHALLENGE = "verify-challenge";
|
|
815
|
+
function nonceOf(text2, type) {
|
|
816
|
+
try {
|
|
817
|
+
const said = JSON.parse(text2);
|
|
818
|
+
if (said["type"] !== type || typeof said["nonce"] !== "string") return null;
|
|
819
|
+
return said["nonce"];
|
|
820
|
+
} catch {
|
|
821
|
+
return null;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
function answerVerifyChallenge(body) {
|
|
825
|
+
const nonce = nonceOf(body, VERIFY_CHALLENGE);
|
|
826
|
+
return nonce === null ? null : JSON.stringify({ nonce });
|
|
827
|
+
}
|
|
828
|
+
async function answerVerifyChallengeRequest(request) {
|
|
829
|
+
if (request.method !== "POST") return null;
|
|
830
|
+
const answer = answerVerifyChallenge(await request.clone().text());
|
|
831
|
+
return answer === null ? null : new Response(answer, { headers: { "content-type": "application/json" } });
|
|
832
|
+
}
|
|
833
|
+
|
|
795
834
|
// src/qr.ts
|
|
796
835
|
import { encode } from "uqr";
|
|
797
836
|
function encodeForQr(destination) {
|
|
@@ -811,6 +850,8 @@ function lightningVerifyEndpoint(config) {
|
|
|
811
850
|
"cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
|
|
812
851
|
};
|
|
813
852
|
return async (request) => {
|
|
853
|
+
const consented = await answerVerifyChallengeRequest(request);
|
|
854
|
+
if (consented !== null) return consented;
|
|
814
855
|
const sealed = new URL(request.url).searchParams.get(WALLET);
|
|
815
856
|
if (sealed === null) return Response.json({ settled: false }, { status: 400 });
|
|
816
857
|
const opened = await unseal(config.secret, sealed);
|
|
@@ -834,16 +875,16 @@ async function relayedVerifyUrl(endpoint, wallet, secret) {
|
|
|
834
875
|
var LIGHTNING = "lightning";
|
|
835
876
|
function blindLightningRail(config) {
|
|
836
877
|
return async (order) => {
|
|
837
|
-
const
|
|
878
|
+
const resolved2 = await invoiceFrom(config.lnAddresses, await config.amountMsat(order));
|
|
838
879
|
const relay = config.relayVerifyThrough;
|
|
839
880
|
const watched = await config.gateway.watchPayment({
|
|
840
|
-
paymentHash:
|
|
881
|
+
paymentHash: resolved2.paymentHash,
|
|
841
882
|
verifyUrl: relay ? await relayedVerifyUrl(
|
|
842
883
|
relay.endpoint,
|
|
843
|
-
{ url:
|
|
884
|
+
{ url: resolved2.verifyUrl, hash: resolved2.paymentHash },
|
|
844
885
|
relay.secret
|
|
845
|
-
) :
|
|
846
|
-
expiresAt:
|
|
886
|
+
) : resolved2.verifyUrl,
|
|
887
|
+
expiresAt: resolved2.expiresAt,
|
|
847
888
|
trigger: config.trigger,
|
|
848
889
|
sealed: await config.sealed?.(order),
|
|
849
890
|
webhookUrl: config.webhookUrl,
|
|
@@ -852,9 +893,9 @@ function blindLightningRail(config) {
|
|
|
852
893
|
return {
|
|
853
894
|
id: watched.id,
|
|
854
895
|
rail: config.name ?? LIGHTNING,
|
|
855
|
-
scan:
|
|
856
|
-
qr: encodeForQr(
|
|
857
|
-
expiresAt:
|
|
896
|
+
scan: resolved2.bolt11,
|
|
897
|
+
qr: encodeForQr(resolved2.bolt11),
|
|
898
|
+
expiresAt: resolved2.expiresAt
|
|
858
899
|
};
|
|
859
900
|
};
|
|
860
901
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thunder-bridge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.10",
|
|
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",
|