thunder-bridge 2.2.1 → 3.0.1
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 +95 -412
- package/dist/bank.cjs +3 -0
- package/dist/bank.d.cts +3 -3
- package/dist/bank.d.ts +3 -3
- package/dist/bank.js +8 -0
- package/dist/index.cjs +273 -117
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +278 -116
- package/dist/{bank-BgA45yOU.d.ts → nwc-BivXGSPY.d.ts} +70 -114
- package/dist/{bank-DvcV85sa.d.cts → nwc-h_-cGvMb.d.cts} +70 -114
- package/dist/nwc.cjs +10 -424
- package/dist/nwc.d.cts +2 -18
- package/dist/nwc.d.ts +2 -18
- package/dist/nwc.js +10 -422
- package/openapi.yaml +1 -1
- package/package.json +8 -2
package/dist/index.cjs
CHANGED
|
@@ -41,7 +41,6 @@ __export(src_exports, {
|
|
|
41
41
|
WrapRefusedError: () => WrapRefusedError,
|
|
42
42
|
agreesWithItself: () => agreesWithItself,
|
|
43
43
|
answerVerifyChallenge: () => answerVerifyChallenge,
|
|
44
|
-
carriesProof: () => carriesProof,
|
|
45
44
|
decodeInvoice: () => decodeInvoice,
|
|
46
45
|
fiat: () => fiat,
|
|
47
46
|
invoiceFrom: () => invoiceFrom,
|
|
@@ -185,9 +184,9 @@ function fillSchedule(schedule, reader, block) {
|
|
|
185
184
|
schedule[at] = reader.getUint32(block + at * 4, false);
|
|
186
185
|
}
|
|
187
186
|
for (let at = 16; at < SCHEDULE_WORDS; at++) {
|
|
188
|
-
const
|
|
187
|
+
const recent = schedule[at - 15];
|
|
189
188
|
const distant = schedule[at - 2];
|
|
190
|
-
const mixedRecent = rotate(
|
|
189
|
+
const mixedRecent = rotate(recent, 7) ^ rotate(recent, 18) ^ recent >>> 3;
|
|
191
190
|
const mixedDistant = rotate(distant, 17) ^ rotate(distant, 19) ^ distant >>> 10;
|
|
192
191
|
schedule[at] = schedule[at - 16] + mixedRecent + schedule[at - 7] + mixedDistant | 0;
|
|
193
192
|
}
|
|
@@ -442,7 +441,7 @@ async function sealFor(purpose, secret, plaintext, { stable = false } = {}) {
|
|
|
442
441
|
}
|
|
443
442
|
async function unsealFor(purpose, secret, sealed) {
|
|
444
443
|
if (!sealed.startsWith(`${PURPOSE_VERSION}.`)) {
|
|
445
|
-
return
|
|
444
|
+
return null;
|
|
446
445
|
}
|
|
447
446
|
return await opened(
|
|
448
447
|
await keyFor(secret, `${INFO}/${purpose}`),
|
|
@@ -985,13 +984,182 @@ function asBuffer2(bytes) {
|
|
|
985
984
|
return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
|
|
986
985
|
}
|
|
987
986
|
|
|
988
|
-
// ../core/
|
|
989
|
-
var
|
|
990
|
-
var
|
|
991
|
-
var
|
|
992
|
-
var
|
|
993
|
-
var PREFIX = "ed25519=";
|
|
987
|
+
// ../core/signature.ts
|
|
988
|
+
var import_structured_headers = require("structured-headers");
|
|
989
|
+
var TOLERANCE_SECS = 300;
|
|
990
|
+
var LABEL = "thunder-bridge";
|
|
991
|
+
var ALGORITHM2 = "ed25519";
|
|
994
992
|
var NONCE_BYTES = 16;
|
|
993
|
+
var SIGNATURE_BYTES2 = 64;
|
|
994
|
+
var PUBLIC_KEY = /^[0-9a-f]{64}$/i;
|
|
995
|
+
var CLIENT_SIGNS = {
|
|
996
|
+
tag: "thunder-bridge-client",
|
|
997
|
+
covers: ["@method", "@authority", "@path", "@query", "content-digest"]
|
|
998
|
+
};
|
|
999
|
+
var GATEWAY_SIGNS = {
|
|
1000
|
+
tag: "thunder-bridge-gateway",
|
|
1001
|
+
covers: ["@method", "@scheme", "@authority", "@path", "content-digest"]
|
|
1002
|
+
};
|
|
1003
|
+
function contentDigest(body) {
|
|
1004
|
+
return (0, import_structured_headers.serializeDictionary)({
|
|
1005
|
+
"sha-256": [Uint8Array.from(sha256(new TextEncoder().encode(body))), /* @__PURE__ */ new Map()]
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
function signatureBase(message, covered) {
|
|
1009
|
+
const lines = [];
|
|
1010
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1011
|
+
for (const [name, parameters] of covered[0]) {
|
|
1012
|
+
if (typeof name !== "string" || parameters.size > 0 || seen.has(name)) {
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
seen.add(name);
|
|
1016
|
+
const value = componentValue(message, name);
|
|
1017
|
+
if (value === null) {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
lines.push(`${(0, import_structured_headers.serializeString)(name)}: ${value}`);
|
|
1021
|
+
}
|
|
1022
|
+
lines.push(`"@signature-params": ${(0, import_structured_headers.serializeInnerList)(covered)}`);
|
|
1023
|
+
return lines.join("\n");
|
|
1024
|
+
}
|
|
1025
|
+
async function signedBy(key, profile, message, body, { created = unixNow(), nonce = randomNonce() } = {}) {
|
|
1026
|
+
const digest = contentDigest(body);
|
|
1027
|
+
const covered = [
|
|
1028
|
+
profile.covers.map((name) => [name, /* @__PURE__ */ new Map()]),
|
|
1029
|
+
/* @__PURE__ */ new Map([
|
|
1030
|
+
["created", created],
|
|
1031
|
+
["nonce", nonce],
|
|
1032
|
+
["keyid", key.publicKeyHex],
|
|
1033
|
+
["alg", ALGORITHM2],
|
|
1034
|
+
["tag", profile.tag]
|
|
1035
|
+
])
|
|
1036
|
+
];
|
|
1037
|
+
const base = signatureBase(
|
|
1038
|
+
{ ...message, headers: new Headers({ "content-digest": digest }) },
|
|
1039
|
+
covered
|
|
1040
|
+
);
|
|
1041
|
+
if (base === null) {
|
|
1042
|
+
throw new Error(`${message.url} cannot be signed as ${profile.tag}`);
|
|
1043
|
+
}
|
|
1044
|
+
const signature = Uint8Array.from(hexToBytes(await key.sign(new TextEncoder().encode(base))));
|
|
1045
|
+
return {
|
|
1046
|
+
"content-digest": digest,
|
|
1047
|
+
"signature-input": (0, import_structured_headers.serializeDictionary)(/* @__PURE__ */ new Map([[LABEL, covered]])),
|
|
1048
|
+
signature: (0, import_structured_headers.serializeDictionary)(/* @__PURE__ */ new Map([[LABEL, [signature, /* @__PURE__ */ new Map()]]]))
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
async function signerOf(message, body, profile, toleranceSecs = TOLERANCE_SECS) {
|
|
1052
|
+
const found = chosen(message.headers, profile.tag);
|
|
1053
|
+
if (found === null) {
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
1056
|
+
const [components, parameters] = found.covered;
|
|
1057
|
+
const named = components.map(([name]) => name);
|
|
1058
|
+
if (!profile.covers.every((name) => named.includes(name))) {
|
|
1059
|
+
return null;
|
|
1060
|
+
}
|
|
1061
|
+
const keyid = parameters.get("keyid");
|
|
1062
|
+
const created = parameters.get("created");
|
|
1063
|
+
const nonce = parameters.get("nonce");
|
|
1064
|
+
const alg = parameters.get("alg");
|
|
1065
|
+
if (typeof keyid !== "string" || !PUBLIC_KEY.test(keyid)) {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
if (typeof created !== "number" || !Number.isInteger(created)) {
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
if (Math.abs(unixNow() - created) > toleranceSecs) {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
if (alg !== void 0 && alg !== ALGORITHM2 || nonce !== void 0 && typeof nonce !== "string") {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
if (!digestMatches(message.headers.get("content-digest"), body)) {
|
|
1078
|
+
return null;
|
|
1079
|
+
}
|
|
1080
|
+
const base = signatureBase(message, found.covered);
|
|
1081
|
+
if (base === null) {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
const signer = keyid.toLowerCase();
|
|
1085
|
+
const proven = await verifyHex(
|
|
1086
|
+
signer,
|
|
1087
|
+
bytesToHex(found.signature),
|
|
1088
|
+
new TextEncoder().encode(base)
|
|
1089
|
+
);
|
|
1090
|
+
return proven ? { keyid: signer, created, nonce: nonce ?? null } : null;
|
|
1091
|
+
}
|
|
1092
|
+
function componentValue(message, name) {
|
|
1093
|
+
let url;
|
|
1094
|
+
try {
|
|
1095
|
+
url = new URL(message.url);
|
|
1096
|
+
} catch {
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
switch (name) {
|
|
1100
|
+
case "@method":
|
|
1101
|
+
return message.method;
|
|
1102
|
+
case "@authority":
|
|
1103
|
+
return (message.authority ?? url.host).toLowerCase();
|
|
1104
|
+
case "@scheme":
|
|
1105
|
+
return url.protocol.slice(0, -1).toLowerCase();
|
|
1106
|
+
case "@path":
|
|
1107
|
+
return url.pathname === "" ? "/" : url.pathname;
|
|
1108
|
+
case "@query":
|
|
1109
|
+
return url.search === "" ? "?" : url.search;
|
|
1110
|
+
default:
|
|
1111
|
+
return name.startsWith("@") ? null : message.headers.get(name)?.trim() ?? null;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
function chosen(headers, tag) {
|
|
1115
|
+
const inputs = dictionaryOf(headers.get("signature-input"));
|
|
1116
|
+
const signatures = dictionaryOf(headers.get("signature"));
|
|
1117
|
+
if (inputs === null || signatures === null) {
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
for (const [label, member] of inputs) {
|
|
1121
|
+
if (!isInnerList(member) || member[1].get("tag") !== tag) {
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
const value = signatures.get(label);
|
|
1125
|
+
if (value === void 0 || isInnerList(value) || !(value[0] instanceof ArrayBuffer)) {
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
const signature = new Uint8Array(value[0]);
|
|
1129
|
+
return signature.length === SIGNATURE_BYTES2 ? { covered: member, signature } : null;
|
|
1130
|
+
}
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
function digestMatches(field, body) {
|
|
1134
|
+
const digest = dictionaryOf(field)?.get("sha-256");
|
|
1135
|
+
if (digest === void 0 || isInnerList(digest) || !(digest[0] instanceof ArrayBuffer)) {
|
|
1136
|
+
return false;
|
|
1137
|
+
}
|
|
1138
|
+
const sent = new Uint8Array(digest[0]);
|
|
1139
|
+
const counted = sha256(new TextEncoder().encode(body));
|
|
1140
|
+
return sent.length === counted.length && sent.every((byte, at) => byte === counted[at]);
|
|
1141
|
+
}
|
|
1142
|
+
function dictionaryOf(field) {
|
|
1143
|
+
if (field === null) {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
try {
|
|
1147
|
+
return (0, import_structured_headers.parseDictionary)(field);
|
|
1148
|
+
} catch {
|
|
1149
|
+
return null;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function isInnerList(member) {
|
|
1153
|
+
return Array.isArray(member[0]);
|
|
1154
|
+
}
|
|
1155
|
+
function randomNonce() {
|
|
1156
|
+
return bytesToHex(crypto.getRandomValues(new Uint8Array(NONCE_BYTES)));
|
|
1157
|
+
}
|
|
1158
|
+
function unixNow() {
|
|
1159
|
+
return Math.floor(Date.now() / 1e3);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// ../core/caller.ts
|
|
995
1163
|
var SIGNING_LABEL = "request-signing";
|
|
996
1164
|
async function callerKey(secret) {
|
|
997
1165
|
return await signingKeyFromSeed(hexToBytes(await hmacHex(secret, SIGNING_LABEL)));
|
|
@@ -1000,20 +1168,11 @@ function paymentNamedBy(caller, paymentHash) {
|
|
|
1000
1168
|
return sha256Hex(`${caller}.payment-id.${paymentHash}`);
|
|
1001
1169
|
}
|
|
1002
1170
|
async function signedAs(key, method, path, body, host) {
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
[NONCE_HEADER]: nonce,
|
|
1009
|
-
[SIGNATURE_HEADER]: `${PREFIX}${await key.sign(spokenTo(host, timestamp, nonce, method, path, body))}`
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
function spokenTo(host, timestamp, nonce, method, path, body) {
|
|
1013
|
-
return new TextEncoder().encode(
|
|
1014
|
-
["v2", host.toLowerCase(), timestamp, nonce, method.toUpperCase(), path, sha256Hex(body)].join(
|
|
1015
|
-
"\n"
|
|
1016
|
-
)
|
|
1171
|
+
return await signedBy(
|
|
1172
|
+
key,
|
|
1173
|
+
CLIENT_SIGNS,
|
|
1174
|
+
{ method, url: `https://${host}${path}`, authority: host },
|
|
1175
|
+
body
|
|
1017
1176
|
);
|
|
1018
1177
|
}
|
|
1019
1178
|
|
|
@@ -1195,14 +1354,23 @@ async function addressesToReach(url) {
|
|
|
1195
1354
|
return found;
|
|
1196
1355
|
}
|
|
1197
1356
|
async function resolved(url) {
|
|
1198
|
-
const { isIP } = await import(
|
|
1357
|
+
const { isIP } = await import(
|
|
1358
|
+
/* webpackIgnore: true */
|
|
1359
|
+
"node:net"
|
|
1360
|
+
);
|
|
1199
1361
|
const host = new URL(url).hostname.replace(/^\[|]$/g, "");
|
|
1200
1362
|
const literal = isIP(host);
|
|
1201
1363
|
if (literal !== 0) {
|
|
1202
1364
|
return [{ address: host, family: literal }];
|
|
1203
1365
|
}
|
|
1204
|
-
const { Resolver } = await import(
|
|
1205
|
-
|
|
1366
|
+
const { Resolver } = await import(
|
|
1367
|
+
/* webpackIgnore: true */
|
|
1368
|
+
"node:dns/promises"
|
|
1369
|
+
);
|
|
1370
|
+
const { setTimeout: sleep } = await import(
|
|
1371
|
+
/* webpackIgnore: true */
|
|
1372
|
+
"node:timers/promises"
|
|
1373
|
+
);
|
|
1206
1374
|
const resolver = new Resolver({ timeout: LOOKUP_TIMEOUT_MS, tries: 1 });
|
|
1207
1375
|
const found = await Promise.race([
|
|
1208
1376
|
Promise.all([
|
|
@@ -1289,6 +1457,7 @@ var NoWalletAvailable = class extends Error {
|
|
|
1289
1457
|
|
|
1290
1458
|
// ../core/lnurl.ts
|
|
1291
1459
|
var VERIFY_WITHOUT_PREIMAGE = ["zeuspay.com", "zeusnuts.com", "ecash.love"];
|
|
1460
|
+
var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
|
|
1292
1461
|
var RESOLVE_TIMEOUT_MS = 3e4;
|
|
1293
1462
|
var NAMES_ONE_ACCOUNT = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
|
|
1294
1463
|
var MIN_PACE_SECS = 1;
|
|
@@ -1350,6 +1519,12 @@ async function probe(send, address, amountMsat, deadline) {
|
|
|
1350
1519
|
if (pay.tag !== "payRequest" || !pay.callback) {
|
|
1351
1520
|
throw new WalletRefused("unreachable", `${address} answered with no payRequest`);
|
|
1352
1521
|
}
|
|
1522
|
+
if (!namesTheAddress(pay.metadata, address)) {
|
|
1523
|
+
throw new WalletRefused(
|
|
1524
|
+
"cannot-prove-delivery",
|
|
1525
|
+
`${address} serves metadata that does not name it, so no invoice binds to it`
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1353
1528
|
if (amountMsat < pay.minSendable || amountMsat > pay.maxSendable) {
|
|
1354
1529
|
throw new WalletRefused(
|
|
1355
1530
|
"amount-not-accepted",
|
|
@@ -1442,6 +1617,19 @@ function ceilingAskedFor(headers) {
|
|
|
1442
1617
|
}
|
|
1443
1618
|
return Math.min(Math.max(perWindow / window, MIN_PER_SECOND), MAX_PER_SECOND);
|
|
1444
1619
|
}
|
|
1620
|
+
function namesTheAddress(metadata, address) {
|
|
1621
|
+
if (typeof metadata !== "string") {
|
|
1622
|
+
return false;
|
|
1623
|
+
}
|
|
1624
|
+
try {
|
|
1625
|
+
const entries = JSON.parse(metadata);
|
|
1626
|
+
return Array.isArray(entries) && entries.some(
|
|
1627
|
+
(entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && entry[1].toLowerCase() === address.toLowerCase()
|
|
1628
|
+
);
|
|
1629
|
+
} catch {
|
|
1630
|
+
return false;
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1445
1633
|
function cannotReleaseAPreimage(host) {
|
|
1446
1634
|
const lowered = host.toLowerCase();
|
|
1447
1635
|
return VERIFY_WITHOUT_PREIMAGE.some(
|
|
@@ -1490,8 +1678,14 @@ var pinnedToTheAddressWeVerified = async (url, sent, signal, at) => {
|
|
|
1490
1678
|
if (first === void 0) {
|
|
1491
1679
|
throw new Error(`${url} resolved to nothing worth connecting to`);
|
|
1492
1680
|
}
|
|
1493
|
-
const { request } = await import(
|
|
1494
|
-
|
|
1681
|
+
const { request } = await import(
|
|
1682
|
+
/* webpackIgnore: true */
|
|
1683
|
+
"node:https"
|
|
1684
|
+
);
|
|
1685
|
+
const { Readable } = await import(
|
|
1686
|
+
/* webpackIgnore: true */
|
|
1687
|
+
"node:stream"
|
|
1688
|
+
);
|
|
1495
1689
|
return new Promise((settle, fail) => {
|
|
1496
1690
|
if (signal.aborted) {
|
|
1497
1691
|
fail(signal.reason);
|
|
@@ -1556,18 +1750,9 @@ function headersOf(raw) {
|
|
|
1556
1750
|
return headers;
|
|
1557
1751
|
}
|
|
1558
1752
|
|
|
1559
|
-
// ../core/delivery.ts
|
|
1560
|
-
function deliverySigned(url, timestamp, body) {
|
|
1561
|
-
const endpoint = new URL(url);
|
|
1562
|
-
return new TextEncoder().encode(
|
|
1563
|
-
["v2", `${endpoint.origin}${endpoint.pathname}`, timestamp, body].join("\n")
|
|
1564
|
-
);
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
1753
|
// src/verify.ts
|
|
1568
1754
|
var HTTP_TIMEOUT_MS2 = 15e3;
|
|
1569
1755
|
var ISSUED_SKEW_SECS = 300;
|
|
1570
|
-
var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
|
|
1571
1756
|
var DEFAULT_WRAP_PROPORTION = 0.01;
|
|
1572
1757
|
var DEFAULT_WRAP_BASE_MSAT = 1e3;
|
|
1573
1758
|
async function proveOrigin(payment, asked, askedAt) {
|
|
@@ -1614,16 +1799,6 @@ async function proveOrigin(payment, asked, askedAt) {
|
|
|
1614
1799
|
throw cheat("invoice_settled");
|
|
1615
1800
|
}
|
|
1616
1801
|
}
|
|
1617
|
-
function namesTheAddress(metadata, address) {
|
|
1618
|
-
try {
|
|
1619
|
-
const entries = JSON.parse(metadata);
|
|
1620
|
-
return Array.isArray(entries) && entries.some(
|
|
1621
|
-
(entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && equalIgnoringCase(entry[1], address)
|
|
1622
|
-
);
|
|
1623
|
-
} catch {
|
|
1624
|
-
return false;
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
1802
|
async function proveSettlement(payment, asked) {
|
|
1628
1803
|
await proveOrigin(payment, asked);
|
|
1629
1804
|
const verified = await reachable(payment.verifyUrl, payment);
|
|
@@ -1647,7 +1822,6 @@ function agreesWithItself(report) {
|
|
|
1647
1822
|
}
|
|
1648
1823
|
return preimageMatchesHash(report.preimage, report.paymentHash);
|
|
1649
1824
|
}
|
|
1650
|
-
var carriesProof = agreesWithItself;
|
|
1651
1825
|
function wrapFeeCeiling(amountMsat, allowance) {
|
|
1652
1826
|
const proportional = amountMsat * (allowance?.proportion ?? DEFAULT_WRAP_PROPORTION);
|
|
1653
1827
|
return msat(Math.max(allowance?.baseMsat ?? DEFAULT_WRAP_BASE_MSAT, Math.floor(proportional)));
|
|
@@ -1929,10 +2103,6 @@ function isStatus(value) {
|
|
|
1929
2103
|
}
|
|
1930
2104
|
|
|
1931
2105
|
// src/webhook.ts
|
|
1932
|
-
var SIGNATURE_HEADER2 = "x-signature-v2";
|
|
1933
|
-
var TIMESTAMP_HEADER2 = "x-timestamp";
|
|
1934
|
-
var GATEWAY_KEY_PREFIX = "ed25519=";
|
|
1935
|
-
var DEFAULT_TOLERANCE_SECS = 300;
|
|
1936
2106
|
var CHALLENGE = "webhook-challenge";
|
|
1937
2107
|
var VERIFY_CHALLENGE = "verify-challenge";
|
|
1938
2108
|
async function readPayment(request, credential, options = {}) {
|
|
@@ -1956,24 +2126,14 @@ async function answerVerifyChallenge(request) {
|
|
|
1956
2126
|
return nonce === null ? null : answered2(nonce);
|
|
1957
2127
|
}
|
|
1958
2128
|
async function believable(request, credential, options) {
|
|
1959
|
-
const signature = request.headers.get(SIGNATURE_HEADER2);
|
|
1960
|
-
const timestamp = request.headers.get(TIMESTAMP_HEADER2);
|
|
1961
|
-
if (signature === null || timestamp === null) {
|
|
1962
|
-
return null;
|
|
1963
|
-
}
|
|
1964
|
-
if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) {
|
|
1965
|
-
return null;
|
|
1966
|
-
}
|
|
1967
|
-
if (!signature.startsWith(GATEWAY_KEY_PREFIX)) {
|
|
1968
|
-
return null;
|
|
1969
|
-
}
|
|
1970
2129
|
const body = await request.clone().text();
|
|
1971
|
-
const
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2130
|
+
const signer = await signerOf(
|
|
2131
|
+
{ method: request.method, url: options.url ?? request.url, headers: request.headers },
|
|
2132
|
+
body,
|
|
2133
|
+
GATEWAY_SIGNS,
|
|
2134
|
+
options.toleranceSecs
|
|
1975
2135
|
);
|
|
1976
|
-
return
|
|
2136
|
+
return signer !== null && signer.keyid === credential.publicKey.toLowerCase() ? body : null;
|
|
1977
2137
|
}
|
|
1978
2138
|
function proved(read) {
|
|
1979
2139
|
return read !== null && read.status === "paid" && !agreesWithItself(read) ? null : read;
|
|
@@ -2001,13 +2161,6 @@ function nonceOf2(text3, type) {
|
|
|
2001
2161
|
return null;
|
|
2002
2162
|
}
|
|
2003
2163
|
}
|
|
2004
|
-
function recent(timestamp, toleranceSecs) {
|
|
2005
|
-
const sent = Number(timestamp);
|
|
2006
|
-
if (!Number.isFinite(sent)) {
|
|
2007
|
-
return false;
|
|
2008
|
-
}
|
|
2009
|
-
return Math.abs(Math.floor(Date.now() / 1e3) - sent) <= toleranceSecs;
|
|
2010
|
-
}
|
|
2011
2164
|
|
|
2012
2165
|
// src/bank.ts
|
|
2013
2166
|
var DEFAULT_CURRENCY = "CZK";
|
|
@@ -2086,7 +2239,7 @@ async function answeredAt(params, subject) {
|
|
|
2086
2239
|
return polling.toString();
|
|
2087
2240
|
}
|
|
2088
2241
|
async function creditedPreimage(params) {
|
|
2089
|
-
const since =
|
|
2242
|
+
const since = unixNow2() - (params.lookBackSecs ?? DEFAULT_LOOK_BACK_SECS);
|
|
2090
2243
|
const landed = (await params.statement(since)).some((credit) => pays(credit, params.asked));
|
|
2091
2244
|
if (!landed) {
|
|
2092
2245
|
return null;
|
|
@@ -2184,7 +2337,7 @@ function refuseUnusable(params, iban, currency) {
|
|
|
2184
2337
|
function accountOf(iban) {
|
|
2185
2338
|
return iban.replace(/\s+/g, "").toUpperCase();
|
|
2186
2339
|
}
|
|
2187
|
-
function
|
|
2340
|
+
function unixNow2() {
|
|
2188
2341
|
return Math.floor(Date.now() / 1e3);
|
|
2189
2342
|
}
|
|
2190
2343
|
|
|
@@ -2620,6 +2773,19 @@ function nwcRail(gateway, config) {
|
|
|
2620
2773
|
// src/relay.ts
|
|
2621
2774
|
var DEFAULT_POLL_EVERY_SECS3 = 5;
|
|
2622
2775
|
var WALLET = "w";
|
|
2776
|
+
function verifiedThrough(path) {
|
|
2777
|
+
const through = path.verifyThrough ?? null;
|
|
2778
|
+
const mints = path.gatewayMints === true;
|
|
2779
|
+
if (through !== null && mints) {
|
|
2780
|
+
throw new Error("verifyThrough and gatewayMints are two different paths, name only one");
|
|
2781
|
+
}
|
|
2782
|
+
if (through === null && !mints) {
|
|
2783
|
+
throw new Error(
|
|
2784
|
+
"name verifyThrough, where your serve.lightningVerify is mounted, or gatewayMints: true to let the gateway mint and poll the wallet itself"
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2787
|
+
return through;
|
|
2788
|
+
}
|
|
2623
2789
|
function lightningVerifyEndpoint(config) {
|
|
2624
2790
|
const paced = {
|
|
2625
2791
|
"cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS3}`
|
|
@@ -2698,6 +2864,10 @@ function bankRail(gateway, config) {
|
|
|
2698
2864
|
};
|
|
2699
2865
|
}
|
|
2700
2866
|
function lightningRail(gateway, config) {
|
|
2867
|
+
const through = verifiedThrough(config);
|
|
2868
|
+
return through === null ? mintedByGateway(gateway, config) : resolvedHere(gateway, config, through);
|
|
2869
|
+
}
|
|
2870
|
+
function mintedByGateway(gateway, config) {
|
|
2701
2871
|
return async (order) => {
|
|
2702
2872
|
const payment = await gateway.mint(
|
|
2703
2873
|
{
|
|
@@ -2721,21 +2891,20 @@ function lightningRail(gateway, config) {
|
|
|
2721
2891
|
};
|
|
2722
2892
|
};
|
|
2723
2893
|
}
|
|
2724
|
-
function
|
|
2894
|
+
function resolvedHere(gateway, config, through) {
|
|
2725
2895
|
return async (order) => {
|
|
2726
2896
|
const resolved2 = await invoiceFrom(
|
|
2727
2897
|
config.paidTo,
|
|
2728
2898
|
await msatForOrder(order, config.amount, config.rate),
|
|
2729
2899
|
config.send
|
|
2730
2900
|
);
|
|
2731
|
-
const relay = config.relayThrough;
|
|
2732
2901
|
const watched = await gateway.watch({
|
|
2733
2902
|
paymentHash: resolved2.paymentHash,
|
|
2734
|
-
verifyUrl:
|
|
2735
|
-
|
|
2903
|
+
verifyUrl: await relayedVerifyUrl(
|
|
2904
|
+
through.endpoint,
|
|
2736
2905
|
{ url: resolved2.verifyUrl, hash: resolved2.paymentHash },
|
|
2737
|
-
|
|
2738
|
-
)
|
|
2906
|
+
through.secret
|
|
2907
|
+
),
|
|
2739
2908
|
expiresAt: resolved2.expiresAt,
|
|
2740
2909
|
trigger: config.trigger,
|
|
2741
2910
|
replay: config.replay,
|
|
@@ -2778,16 +2947,12 @@ var Rails = class {
|
|
|
2778
2947
|
this.gateway = gateway;
|
|
2779
2948
|
}
|
|
2780
2949
|
gateway;
|
|
2781
|
-
/** Lightning, with the gateway minting against a priority list of addresses */
|
|
2782
|
-
lightning(config) {
|
|
2783
|
-
return lightningRail(this.gateway, config);
|
|
2784
|
-
}
|
|
2785
2950
|
/**
|
|
2786
|
-
* Lightning
|
|
2787
|
-
*
|
|
2951
|
+
* Lightning against a priority list of addresses, verified through your
|
|
2952
|
+
* `serve.lightningVerify`, or minted by the gateway when you say `gatewayMints`
|
|
2788
2953
|
*/
|
|
2789
|
-
|
|
2790
|
-
return
|
|
2954
|
+
lightning(config) {
|
|
2955
|
+
return lightningRail(this.gateway, config);
|
|
2791
2956
|
}
|
|
2792
2957
|
/** A bank transfer, proved the way a Lightning payment is */
|
|
2793
2958
|
bank(config) {
|
|
@@ -2853,11 +3018,12 @@ function waitingOf(options) {
|
|
|
2853
3018
|
// src/trigger.ts
|
|
2854
3019
|
var NONCE_BYTES3 = 16;
|
|
2855
3020
|
function lnurlPayEndpoint(gateway, config) {
|
|
3021
|
+
const through = verifiedThrough(config);
|
|
2856
3022
|
return async (request) => {
|
|
2857
3023
|
const url = new URL(request.url);
|
|
2858
3024
|
const asked = url.searchParams.get("to");
|
|
2859
3025
|
try {
|
|
2860
|
-
return asked === null ? await offer(gateway, config, url) : await mint(gateway, config, url, asked);
|
|
3026
|
+
return asked === null ? await offer(gateway, config, through, url) : await mint(gateway, config, through, url, asked);
|
|
2861
3027
|
} catch (failure) {
|
|
2862
3028
|
return refuse(failure instanceof Error ? failure.message : "the trigger could not be served");
|
|
2863
3029
|
}
|
|
@@ -2875,10 +3041,10 @@ function watchTicketEndpoint(gateway, config) {
|
|
|
2875
3041
|
function publicWatchTicketEndpoint(gateway, config) {
|
|
2876
3042
|
return () => issue(gateway, config);
|
|
2877
3043
|
}
|
|
2878
|
-
async function offer(gateway, config, url) {
|
|
3044
|
+
async function offer(gateway, config, through, url) {
|
|
2879
3045
|
const { least, most } = await spread(config.amount);
|
|
2880
|
-
const winner = await quoted(gateway, config, least);
|
|
2881
|
-
const nonce =
|
|
3046
|
+
const winner = await quoted(gateway, config, through, least);
|
|
3047
|
+
const nonce = randomNonce2();
|
|
2882
3048
|
const callback = new URL(config.baseUrl ?? `${url.origin}${url.pathname}`);
|
|
2883
3049
|
callback.searchParams.set("to", winner.address);
|
|
2884
3050
|
callback.searchParams.set("least", String(least));
|
|
@@ -2893,8 +3059,8 @@ async function offer(gateway, config, url) {
|
|
|
2893
3059
|
maxSendable: most
|
|
2894
3060
|
});
|
|
2895
3061
|
}
|
|
2896
|
-
async function quoted(gateway, config, amountMsat) {
|
|
2897
|
-
if (
|
|
3062
|
+
async function quoted(gateway, config, through, amountMsat) {
|
|
3063
|
+
if (through !== null) {
|
|
2898
3064
|
const { won } = await quote(
|
|
2899
3065
|
config.send ?? pinnedToTheAddressWeVerified,
|
|
2900
3066
|
listed(config.paidTo),
|
|
@@ -2920,7 +3086,7 @@ async function spread(amount) {
|
|
|
2920
3086
|
}
|
|
2921
3087
|
return { least, most };
|
|
2922
3088
|
}
|
|
2923
|
-
async function mint(gateway, config, url, address) {
|
|
3089
|
+
async function mint(gateway, config, through, url, address) {
|
|
2924
3090
|
const least = Number(url.searchParams.get("least"));
|
|
2925
3091
|
const most = Number(url.searchParams.get("most"));
|
|
2926
3092
|
const nonce = url.searchParams.get("n") ?? "";
|
|
@@ -2939,7 +3105,7 @@ async function mint(gateway, config, url, address) {
|
|
|
2939
3105
|
least === most ? `this trigger costs exactly ${least} msat` : `this trigger takes between ${least} and ${most} msat`
|
|
2940
3106
|
);
|
|
2941
3107
|
}
|
|
2942
|
-
const minted =
|
|
3108
|
+
const minted = through === null ? await mintThroughGateway(gateway, config, address, amountMsat, nonce) : await mintHere(gateway, config, through, address, amountMsat);
|
|
2943
3109
|
return Response.json({
|
|
2944
3110
|
status: "OK",
|
|
2945
3111
|
pr: minted.bolt11,
|
|
@@ -2954,7 +3120,7 @@ async function mintThroughGateway(gateway, config, address, amountMsat, nonce) {
|
|
|
2954
3120
|
);
|
|
2955
3121
|
return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
|
|
2956
3122
|
}
|
|
2957
|
-
async function
|
|
3123
|
+
async function mintHere(gateway, config, through, address, amountMsat) {
|
|
2958
3124
|
const resolved2 = await resolve(
|
|
2959
3125
|
config.send ?? pinnedToTheAddressWeVerified,
|
|
2960
3126
|
[address],
|
|
@@ -2962,15 +3128,14 @@ async function mintBlind(gateway, config, address, amountMsat) {
|
|
|
2962
3128
|
);
|
|
2963
3129
|
const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
|
|
2964
3130
|
const locked = config.sealed;
|
|
2965
|
-
const relay = config.relayThrough;
|
|
2966
3131
|
try {
|
|
2967
3132
|
await gateway.watch({
|
|
2968
3133
|
paymentHash: resolved2.paymentHash,
|
|
2969
|
-
verifyUrl:
|
|
2970
|
-
|
|
3134
|
+
verifyUrl: await relayedVerifyUrl(
|
|
3135
|
+
through.endpoint,
|
|
2971
3136
|
{ url: resolved2.verifyUrl, hash: resolved2.paymentHash },
|
|
2972
|
-
|
|
2973
|
-
)
|
|
3137
|
+
through.secret
|
|
3138
|
+
),
|
|
2974
3139
|
expiresAt: resolved2.expiresAt,
|
|
2975
3140
|
trigger: config.watchSecret,
|
|
2976
3141
|
replay: config.replay,
|
|
@@ -2992,7 +3157,7 @@ function alreadyWatched(refused) {
|
|
|
2992
3157
|
function sign(secret, address, least, most, nonce) {
|
|
2993
3158
|
return hmacHex(secret, `lnurl-callback|${address}|${least}|${most}|${nonce}`);
|
|
2994
3159
|
}
|
|
2995
|
-
function
|
|
3160
|
+
function randomNonce2() {
|
|
2996
3161
|
const bytes = crypto.getRandomValues(new Uint8Array(NONCE_BYTES3));
|
|
2997
3162
|
let hex = "";
|
|
2998
3163
|
for (const byte of bytes) {
|
|
@@ -3046,14 +3211,6 @@ var Serve = class {
|
|
|
3046
3211
|
lightningVerify(config) {
|
|
3047
3212
|
return lightningVerifyEndpoint(config);
|
|
3048
3213
|
}
|
|
3049
|
-
/**
|
|
3050
|
-
* What `lightningVerify` was called before 2.2.0
|
|
3051
|
-
*
|
|
3052
|
-
* @deprecated Use `lightningVerify`, beside `bankVerify` and `nwcVerify`
|
|
3053
|
-
*/
|
|
3054
|
-
verify(config) {
|
|
3055
|
-
return this.lightningVerify(config);
|
|
3056
|
-
}
|
|
3057
3214
|
/** The verify endpoint a bank rail is polled at, answering off your own statement */
|
|
3058
3215
|
bankVerify(config) {
|
|
3059
3216
|
return bankVerifyEndpoint(config);
|
|
@@ -3075,7 +3232,7 @@ var Serve = class {
|
|
|
3075
3232
|
*/
|
|
3076
3233
|
webhook(handlers) {
|
|
3077
3234
|
const options = { toleranceSecs: handlers.toleranceSecs, url: handlers.url };
|
|
3078
|
-
const once = onceWhileReplayable(handlers.toleranceSecs ??
|
|
3235
|
+
const once = onceWhileReplayable(handlers.toleranceSecs ?? TOLERANCE_SECS);
|
|
3079
3236
|
return async (request) => {
|
|
3080
3237
|
const credential = await this.credential(handlers);
|
|
3081
3238
|
const challenge = await answerWebhookChallenge(request, credential, options);
|
|
@@ -3926,7 +4083,6 @@ var Gateways = class {
|
|
|
3926
4083
|
WrapRefusedError,
|
|
3927
4084
|
agreesWithItself,
|
|
3928
4085
|
answerVerifyChallenge,
|
|
3929
|
-
carriesProof,
|
|
3930
4086
|
decodeInvoice,
|
|
3931
4087
|
fiat,
|
|
3932
4088
|
invoiceFrom,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { R as Resolved } from './qr-CF-YeXU1.cjs';
|
|
2
|
-
import { T as ThunderBridge,
|
|
3
|
-
export { A as AttendOptions,
|
|
2
|
+
import { T as ThunderBridge, f as ThunderBridgeOptions, W as WaitOptions } from './nwc-h_-cGvMb.cjs';
|
|
3
|
+
export { A as AttendOptions, g as BankRailConfig, d as BankVerifyConfig, h as CreateOptions, F as FollowOptions, H as Handler, L as Leg, i as LightningRailConfig, j as LightningRailSettings, k as LightningVerifyConfig, M as Minted, N as NwcConnection, l as NwcRailConfig, m as NwcVerifyConfig, O as Order, P as PaymentRequest, n as PaymentRequestInit, o as PaymentRequestOptions, p as Provable, R as Rail, q as RailConfig, r as Rails, s as Range, t as Relayed, u as SelfConsistent, v as Send, w as Serve, x as TicketOptions, y as TriggerConfig, z as TriggerSettings, V as VerifyPath, D as VerifyThrough, E as WatchTicketConfig, G as WebhookCredential, I as WebhookHandlers, J as WebhookOptions, K as WrapAllowance, Q as agreesWithItself, U as answerVerifyChallenge, X as invoiceFrom, Y as proveOrigin, Z as proveSettlement, _ as proveWrapped, $ as relayedVerifyUrl, a0 as wrapFeeCeiling } from './nwc-h_-cGvMb.cjs';
|
|
4
4
|
import { H as Handover, P as Payment, e as Held } from './types-DYZ9EkmJ.cjs';
|
|
5
5
|
export { A as Amount, C as Charge, F as FiatOptions, f as MintedPayment, g as Msat, h as PaymentStatus, i as Priced, Q as Quote, S as Settlement, j as SocketTicket, W as WalletFailure, l as WalletReason, n as WatchedPayment, o as fiat, p as msat, s as sats } from './types-DYZ9EkmJ.cjs';
|
|
6
6
|
import { G as GatewayCheatError } from './errors-DJmsalYZ.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { R as Resolved } from './qr-CF-YeXU1.js';
|
|
2
|
-
import { T as ThunderBridge,
|
|
3
|
-
export { A as AttendOptions,
|
|
2
|
+
import { T as ThunderBridge, f as ThunderBridgeOptions, W as WaitOptions } from './nwc-BivXGSPY.js';
|
|
3
|
+
export { A as AttendOptions, g as BankRailConfig, d as BankVerifyConfig, h as CreateOptions, F as FollowOptions, H as Handler, L as Leg, i as LightningRailConfig, j as LightningRailSettings, k as LightningVerifyConfig, M as Minted, N as NwcConnection, l as NwcRailConfig, m as NwcVerifyConfig, O as Order, P as PaymentRequest, n as PaymentRequestInit, o as PaymentRequestOptions, p as Provable, R as Rail, q as RailConfig, r as Rails, s as Range, t as Relayed, u as SelfConsistent, v as Send, w as Serve, x as TicketOptions, y as TriggerConfig, z as TriggerSettings, V as VerifyPath, D as VerifyThrough, E as WatchTicketConfig, G as WebhookCredential, I as WebhookHandlers, J as WebhookOptions, K as WrapAllowance, Q as agreesWithItself, U as answerVerifyChallenge, X as invoiceFrom, Y as proveOrigin, Z as proveSettlement, _ as proveWrapped, $ as relayedVerifyUrl, a0 as wrapFeeCeiling } from './nwc-BivXGSPY.js';
|
|
4
4
|
import { H as Handover, P as Payment, e as Held } from './types-DYZ9EkmJ.js';
|
|
5
5
|
export { A as Amount, C as Charge, F as FiatOptions, f as MintedPayment, g as Msat, h as PaymentStatus, i as Priced, Q as Quote, S as Settlement, j as SocketTicket, W as WalletFailure, l as WalletReason, n as WatchedPayment, o as fiat, p as msat, s as sats } from './types-DYZ9EkmJ.js';
|
|
6
6
|
import { G as GatewayCheatError } from './errors-VX0R18oE.js';
|