thunder-bridge 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -412
- package/dist/bank.cjs +13 -3
- package/dist/bank.d.cts +9 -3
- package/dist/bank.d.ts +9 -3
- package/dist/bank.js +18 -3
- package/dist/index.cjs +254 -113
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +259 -112
- package/dist/{bank-B5MxT_6u.d.ts → nwc-BivXGSPY.d.ts} +80 -118
- package/dist/{bank-Bg6RjuO-.d.cts → nwc-h_-cGvMb.d.cts} +80 -118
- 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/bank.js
CHANGED
|
@@ -121,6 +121,14 @@ var INITIAL_STATE = new Uint32Array([
|
|
|
121
121
|
1541459225
|
|
122
122
|
]);
|
|
123
123
|
|
|
124
|
+
// ../core/signature.ts
|
|
125
|
+
import {
|
|
126
|
+
parseDictionary,
|
|
127
|
+
serializeDictionary,
|
|
128
|
+
serializeInnerList,
|
|
129
|
+
serializeString
|
|
130
|
+
} from "structured-headers";
|
|
131
|
+
|
|
124
132
|
// ../core/sealed.ts
|
|
125
133
|
var MIN_SECRET_CHARS = 32;
|
|
126
134
|
function refuseAWeakSecret(secret) {
|
|
@@ -223,7 +231,7 @@ function fioStatement(config) {
|
|
|
223
231
|
const paceMs = tokenWindowMs / usedAt.size;
|
|
224
232
|
let lastRead = Number.NEGATIVE_INFINITY;
|
|
225
233
|
let credits = [];
|
|
226
|
-
|
|
234
|
+
const read = async (sinceUnix) => {
|
|
227
235
|
const now = Date.now();
|
|
228
236
|
if (now - lastRead < paceMs) {
|
|
229
237
|
return credits;
|
|
@@ -249,10 +257,17 @@ function fioStatement(config) {
|
|
|
249
257
|
if (!answer.ok) {
|
|
250
258
|
throw new Error(`fio answered ${answer.status} reading the statement`);
|
|
251
259
|
}
|
|
252
|
-
const
|
|
253
|
-
|
|
260
|
+
const statement = await answer.json();
|
|
261
|
+
const reads = statement.accountStatement?.info?.iban;
|
|
262
|
+
if (config.iban !== void 0 && accountOf(reads ?? "") !== accountOf(config.iban)) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
`a fio token reads ${reads ?? "an account it does not name"}, not ${config.iban}`
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
credits = (statement.accountStatement?.transactionList?.transaction ?? []).filter(isCredit).map(asCredit);
|
|
254
268
|
return credits;
|
|
255
269
|
};
|
|
270
|
+
return Object.assign(read, { freshEverySecs: Math.ceil(paceMs / MILLIS) });
|
|
256
271
|
}
|
|
257
272
|
function longestUnused(usedAt) {
|
|
258
273
|
let token = "";
|
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
|
|
|
@@ -1289,6 +1448,7 @@ var NoWalletAvailable = class extends Error {
|
|
|
1289
1448
|
|
|
1290
1449
|
// ../core/lnurl.ts
|
|
1291
1450
|
var VERIFY_WITHOUT_PREIMAGE = ["zeuspay.com", "zeusnuts.com", "ecash.love"];
|
|
1451
|
+
var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
|
|
1292
1452
|
var RESOLVE_TIMEOUT_MS = 3e4;
|
|
1293
1453
|
var NAMES_ONE_ACCOUNT = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
|
|
1294
1454
|
var MIN_PACE_SECS = 1;
|
|
@@ -1350,6 +1510,12 @@ async function probe(send, address, amountMsat, deadline) {
|
|
|
1350
1510
|
if (pay.tag !== "payRequest" || !pay.callback) {
|
|
1351
1511
|
throw new WalletRefused("unreachable", `${address} answered with no payRequest`);
|
|
1352
1512
|
}
|
|
1513
|
+
if (!namesTheAddress(pay.metadata, address)) {
|
|
1514
|
+
throw new WalletRefused(
|
|
1515
|
+
"cannot-prove-delivery",
|
|
1516
|
+
`${address} serves metadata that does not name it, so no invoice binds to it`
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1353
1519
|
if (amountMsat < pay.minSendable || amountMsat > pay.maxSendable) {
|
|
1354
1520
|
throw new WalletRefused(
|
|
1355
1521
|
"amount-not-accepted",
|
|
@@ -1442,6 +1608,19 @@ function ceilingAskedFor(headers) {
|
|
|
1442
1608
|
}
|
|
1443
1609
|
return Math.min(Math.max(perWindow / window, MIN_PER_SECOND), MAX_PER_SECOND);
|
|
1444
1610
|
}
|
|
1611
|
+
function namesTheAddress(metadata, address) {
|
|
1612
|
+
if (typeof metadata !== "string") {
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
try {
|
|
1616
|
+
const entries = JSON.parse(metadata);
|
|
1617
|
+
return Array.isArray(entries) && entries.some(
|
|
1618
|
+
(entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && entry[1].toLowerCase() === address.toLowerCase()
|
|
1619
|
+
);
|
|
1620
|
+
} catch {
|
|
1621
|
+
return false;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1445
1624
|
function cannotReleaseAPreimage(host) {
|
|
1446
1625
|
const lowered = host.toLowerCase();
|
|
1447
1626
|
return VERIFY_WITHOUT_PREIMAGE.some(
|
|
@@ -1556,18 +1735,9 @@ function headersOf(raw) {
|
|
|
1556
1735
|
return headers;
|
|
1557
1736
|
}
|
|
1558
1737
|
|
|
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
1738
|
// src/verify.ts
|
|
1568
1739
|
var HTTP_TIMEOUT_MS2 = 15e3;
|
|
1569
1740
|
var ISSUED_SKEW_SECS = 300;
|
|
1570
|
-
var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
|
|
1571
1741
|
var DEFAULT_WRAP_PROPORTION = 0.01;
|
|
1572
1742
|
var DEFAULT_WRAP_BASE_MSAT = 1e3;
|
|
1573
1743
|
async function proveOrigin(payment, asked, askedAt) {
|
|
@@ -1614,16 +1784,6 @@ async function proveOrigin(payment, asked, askedAt) {
|
|
|
1614
1784
|
throw cheat("invoice_settled");
|
|
1615
1785
|
}
|
|
1616
1786
|
}
|
|
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
1787
|
async function proveSettlement(payment, asked) {
|
|
1628
1788
|
await proveOrigin(payment, asked);
|
|
1629
1789
|
const verified = await reachable(payment.verifyUrl, payment);
|
|
@@ -1647,7 +1807,6 @@ function agreesWithItself(report) {
|
|
|
1647
1807
|
}
|
|
1648
1808
|
return preimageMatchesHash(report.preimage, report.paymentHash);
|
|
1649
1809
|
}
|
|
1650
|
-
var carriesProof = agreesWithItself;
|
|
1651
1810
|
function wrapFeeCeiling(amountMsat, allowance) {
|
|
1652
1811
|
const proportional = amountMsat * (allowance?.proportion ?? DEFAULT_WRAP_PROPORTION);
|
|
1653
1812
|
return msat(Math.max(allowance?.baseMsat ?? DEFAULT_WRAP_BASE_MSAT, Math.floor(proportional)));
|
|
@@ -1929,10 +2088,6 @@ function isStatus(value) {
|
|
|
1929
2088
|
}
|
|
1930
2089
|
|
|
1931
2090
|
// 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
2091
|
var CHALLENGE = "webhook-challenge";
|
|
1937
2092
|
var VERIFY_CHALLENGE = "verify-challenge";
|
|
1938
2093
|
async function readPayment(request, credential, options = {}) {
|
|
@@ -1956,24 +2111,14 @@ async function answerVerifyChallenge(request) {
|
|
|
1956
2111
|
return nonce === null ? null : answered2(nonce);
|
|
1957
2112
|
}
|
|
1958
2113
|
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
2114
|
const body = await request.clone().text();
|
|
1971
|
-
const
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2115
|
+
const signer = await signerOf(
|
|
2116
|
+
{ method: request.method, url: options.url ?? request.url, headers: request.headers },
|
|
2117
|
+
body,
|
|
2118
|
+
GATEWAY_SIGNS,
|
|
2119
|
+
options.toleranceSecs
|
|
1975
2120
|
);
|
|
1976
|
-
return
|
|
2121
|
+
return signer !== null && signer.keyid === credential.publicKey.toLowerCase() ? body : null;
|
|
1977
2122
|
}
|
|
1978
2123
|
function proved(read) {
|
|
1979
2124
|
return read !== null && read.status === "paid" && !agreesWithItself(read) ? null : read;
|
|
@@ -2001,13 +2146,6 @@ function nonceOf2(text3, type) {
|
|
|
2001
2146
|
return null;
|
|
2002
2147
|
}
|
|
2003
2148
|
}
|
|
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
2149
|
|
|
2012
2150
|
// src/bank.ts
|
|
2013
2151
|
var DEFAULT_CURRENCY = "CZK";
|
|
@@ -2049,7 +2187,7 @@ function bankVerifyEndpoint(config) {
|
|
|
2049
2187
|
throw new Error(`${config.iban} is not an IBAN, so this endpoint answers for no account`);
|
|
2050
2188
|
}
|
|
2051
2189
|
const paced = {
|
|
2052
|
-
"cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
|
|
2190
|
+
"cache-control": `max-age=${config.pollEverySecs ?? config.statement.freshEverySecs ?? DEFAULT_POLL_EVERY_SECS}`
|
|
2053
2191
|
};
|
|
2054
2192
|
return async (request) => {
|
|
2055
2193
|
const consented = await answerVerifyChallenge(request);
|
|
@@ -2086,7 +2224,7 @@ async function answeredAt(params, subject) {
|
|
|
2086
2224
|
return polling.toString();
|
|
2087
2225
|
}
|
|
2088
2226
|
async function creditedPreimage(params) {
|
|
2089
|
-
const since =
|
|
2227
|
+
const since = unixNow2() - (params.lookBackSecs ?? DEFAULT_LOOK_BACK_SECS);
|
|
2090
2228
|
const landed = (await params.statement(since)).some((credit) => pays(credit, params.asked));
|
|
2091
2229
|
if (!landed) {
|
|
2092
2230
|
return null;
|
|
@@ -2184,7 +2322,7 @@ function refuseUnusable(params, iban, currency) {
|
|
|
2184
2322
|
function accountOf(iban) {
|
|
2185
2323
|
return iban.replace(/\s+/g, "").toUpperCase();
|
|
2186
2324
|
}
|
|
2187
|
-
function
|
|
2325
|
+
function unixNow2() {
|
|
2188
2326
|
return Math.floor(Date.now() / 1e3);
|
|
2189
2327
|
}
|
|
2190
2328
|
|
|
@@ -2620,6 +2758,19 @@ function nwcRail(gateway, config) {
|
|
|
2620
2758
|
// src/relay.ts
|
|
2621
2759
|
var DEFAULT_POLL_EVERY_SECS3 = 5;
|
|
2622
2760
|
var WALLET = "w";
|
|
2761
|
+
function verifiedThrough(path) {
|
|
2762
|
+
const through = path.verifyThrough ?? null;
|
|
2763
|
+
const mints = path.gatewayMints === true;
|
|
2764
|
+
if (through !== null && mints) {
|
|
2765
|
+
throw new Error("verifyThrough and gatewayMints are two different paths, name only one");
|
|
2766
|
+
}
|
|
2767
|
+
if (through === null && !mints) {
|
|
2768
|
+
throw new Error(
|
|
2769
|
+
"name verifyThrough, where your serve.lightningVerify is mounted, or gatewayMints: true to let the gateway mint and poll the wallet itself"
|
|
2770
|
+
);
|
|
2771
|
+
}
|
|
2772
|
+
return through;
|
|
2773
|
+
}
|
|
2623
2774
|
function lightningVerifyEndpoint(config) {
|
|
2624
2775
|
const paced = {
|
|
2625
2776
|
"cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS3}`
|
|
@@ -2698,6 +2849,10 @@ function bankRail(gateway, config) {
|
|
|
2698
2849
|
};
|
|
2699
2850
|
}
|
|
2700
2851
|
function lightningRail(gateway, config) {
|
|
2852
|
+
const through = verifiedThrough(config);
|
|
2853
|
+
return through === null ? mintedByGateway(gateway, config) : resolvedHere(gateway, config, through);
|
|
2854
|
+
}
|
|
2855
|
+
function mintedByGateway(gateway, config) {
|
|
2701
2856
|
return async (order) => {
|
|
2702
2857
|
const payment = await gateway.mint(
|
|
2703
2858
|
{
|
|
@@ -2721,21 +2876,20 @@ function lightningRail(gateway, config) {
|
|
|
2721
2876
|
};
|
|
2722
2877
|
};
|
|
2723
2878
|
}
|
|
2724
|
-
function
|
|
2879
|
+
function resolvedHere(gateway, config, through) {
|
|
2725
2880
|
return async (order) => {
|
|
2726
2881
|
const resolved2 = await invoiceFrom(
|
|
2727
2882
|
config.paidTo,
|
|
2728
2883
|
await msatForOrder(order, config.amount, config.rate),
|
|
2729
2884
|
config.send
|
|
2730
2885
|
);
|
|
2731
|
-
const relay = config.relayThrough;
|
|
2732
2886
|
const watched = await gateway.watch({
|
|
2733
2887
|
paymentHash: resolved2.paymentHash,
|
|
2734
|
-
verifyUrl:
|
|
2735
|
-
|
|
2888
|
+
verifyUrl: await relayedVerifyUrl(
|
|
2889
|
+
through.endpoint,
|
|
2736
2890
|
{ url: resolved2.verifyUrl, hash: resolved2.paymentHash },
|
|
2737
|
-
|
|
2738
|
-
)
|
|
2891
|
+
through.secret
|
|
2892
|
+
),
|
|
2739
2893
|
expiresAt: resolved2.expiresAt,
|
|
2740
2894
|
trigger: config.trigger,
|
|
2741
2895
|
replay: config.replay,
|
|
@@ -2778,16 +2932,12 @@ var Rails = class {
|
|
|
2778
2932
|
this.gateway = gateway;
|
|
2779
2933
|
}
|
|
2780
2934
|
gateway;
|
|
2781
|
-
/** Lightning, with the gateway minting against a priority list of addresses */
|
|
2782
|
-
lightning(config) {
|
|
2783
|
-
return lightningRail(this.gateway, config);
|
|
2784
|
-
}
|
|
2785
2935
|
/**
|
|
2786
|
-
* Lightning
|
|
2787
|
-
*
|
|
2936
|
+
* Lightning against a priority list of addresses, verified through your
|
|
2937
|
+
* `serve.lightningVerify`, or minted by the gateway when you say `gatewayMints`
|
|
2788
2938
|
*/
|
|
2789
|
-
|
|
2790
|
-
return
|
|
2939
|
+
lightning(config) {
|
|
2940
|
+
return lightningRail(this.gateway, config);
|
|
2791
2941
|
}
|
|
2792
2942
|
/** A bank transfer, proved the way a Lightning payment is */
|
|
2793
2943
|
bank(config) {
|
|
@@ -2853,11 +3003,12 @@ function waitingOf(options) {
|
|
|
2853
3003
|
// src/trigger.ts
|
|
2854
3004
|
var NONCE_BYTES3 = 16;
|
|
2855
3005
|
function lnurlPayEndpoint(gateway, config) {
|
|
3006
|
+
const through = verifiedThrough(config);
|
|
2856
3007
|
return async (request) => {
|
|
2857
3008
|
const url = new URL(request.url);
|
|
2858
3009
|
const asked = url.searchParams.get("to");
|
|
2859
3010
|
try {
|
|
2860
|
-
return asked === null ? await offer(gateway, config, url) : await mint(gateway, config, url, asked);
|
|
3011
|
+
return asked === null ? await offer(gateway, config, through, url) : await mint(gateway, config, through, url, asked);
|
|
2861
3012
|
} catch (failure) {
|
|
2862
3013
|
return refuse(failure instanceof Error ? failure.message : "the trigger could not be served");
|
|
2863
3014
|
}
|
|
@@ -2875,10 +3026,10 @@ function watchTicketEndpoint(gateway, config) {
|
|
|
2875
3026
|
function publicWatchTicketEndpoint(gateway, config) {
|
|
2876
3027
|
return () => issue(gateway, config);
|
|
2877
3028
|
}
|
|
2878
|
-
async function offer(gateway, config, url) {
|
|
3029
|
+
async function offer(gateway, config, through, url) {
|
|
2879
3030
|
const { least, most } = await spread(config.amount);
|
|
2880
|
-
const winner = await quoted(gateway, config, least);
|
|
2881
|
-
const nonce =
|
|
3031
|
+
const winner = await quoted(gateway, config, through, least);
|
|
3032
|
+
const nonce = randomNonce2();
|
|
2882
3033
|
const callback = new URL(config.baseUrl ?? `${url.origin}${url.pathname}`);
|
|
2883
3034
|
callback.searchParams.set("to", winner.address);
|
|
2884
3035
|
callback.searchParams.set("least", String(least));
|
|
@@ -2893,8 +3044,8 @@ async function offer(gateway, config, url) {
|
|
|
2893
3044
|
maxSendable: most
|
|
2894
3045
|
});
|
|
2895
3046
|
}
|
|
2896
|
-
async function quoted(gateway, config, amountMsat) {
|
|
2897
|
-
if (
|
|
3047
|
+
async function quoted(gateway, config, through, amountMsat) {
|
|
3048
|
+
if (through !== null) {
|
|
2898
3049
|
const { won } = await quote(
|
|
2899
3050
|
config.send ?? pinnedToTheAddressWeVerified,
|
|
2900
3051
|
listed(config.paidTo),
|
|
@@ -2920,7 +3071,7 @@ async function spread(amount) {
|
|
|
2920
3071
|
}
|
|
2921
3072
|
return { least, most };
|
|
2922
3073
|
}
|
|
2923
|
-
async function mint(gateway, config, url, address) {
|
|
3074
|
+
async function mint(gateway, config, through, url, address) {
|
|
2924
3075
|
const least = Number(url.searchParams.get("least"));
|
|
2925
3076
|
const most = Number(url.searchParams.get("most"));
|
|
2926
3077
|
const nonce = url.searchParams.get("n") ?? "";
|
|
@@ -2939,7 +3090,7 @@ async function mint(gateway, config, url, address) {
|
|
|
2939
3090
|
least === most ? `this trigger costs exactly ${least} msat` : `this trigger takes between ${least} and ${most} msat`
|
|
2940
3091
|
);
|
|
2941
3092
|
}
|
|
2942
|
-
const minted =
|
|
3093
|
+
const minted = through === null ? await mintThroughGateway(gateway, config, address, amountMsat, nonce) : await mintHere(gateway, config, through, address, amountMsat);
|
|
2943
3094
|
return Response.json({
|
|
2944
3095
|
status: "OK",
|
|
2945
3096
|
pr: minted.bolt11,
|
|
@@ -2954,7 +3105,7 @@ async function mintThroughGateway(gateway, config, address, amountMsat, nonce) {
|
|
|
2954
3105
|
);
|
|
2955
3106
|
return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
|
|
2956
3107
|
}
|
|
2957
|
-
async function
|
|
3108
|
+
async function mintHere(gateway, config, through, address, amountMsat) {
|
|
2958
3109
|
const resolved2 = await resolve(
|
|
2959
3110
|
config.send ?? pinnedToTheAddressWeVerified,
|
|
2960
3111
|
[address],
|
|
@@ -2962,15 +3113,14 @@ async function mintBlind(gateway, config, address, amountMsat) {
|
|
|
2962
3113
|
);
|
|
2963
3114
|
const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
|
|
2964
3115
|
const locked = config.sealed;
|
|
2965
|
-
const relay = config.relayThrough;
|
|
2966
3116
|
try {
|
|
2967
3117
|
await gateway.watch({
|
|
2968
3118
|
paymentHash: resolved2.paymentHash,
|
|
2969
|
-
verifyUrl:
|
|
2970
|
-
|
|
3119
|
+
verifyUrl: await relayedVerifyUrl(
|
|
3120
|
+
through.endpoint,
|
|
2971
3121
|
{ url: resolved2.verifyUrl, hash: resolved2.paymentHash },
|
|
2972
|
-
|
|
2973
|
-
)
|
|
3122
|
+
through.secret
|
|
3123
|
+
),
|
|
2974
3124
|
expiresAt: resolved2.expiresAt,
|
|
2975
3125
|
trigger: config.watchSecret,
|
|
2976
3126
|
replay: config.replay,
|
|
@@ -2992,7 +3142,7 @@ function alreadyWatched(refused) {
|
|
|
2992
3142
|
function sign(secret, address, least, most, nonce) {
|
|
2993
3143
|
return hmacHex(secret, `lnurl-callback|${address}|${least}|${most}|${nonce}`);
|
|
2994
3144
|
}
|
|
2995
|
-
function
|
|
3145
|
+
function randomNonce2() {
|
|
2996
3146
|
const bytes = crypto.getRandomValues(new Uint8Array(NONCE_BYTES3));
|
|
2997
3147
|
let hex = "";
|
|
2998
3148
|
for (const byte of bytes) {
|
|
@@ -3046,14 +3196,6 @@ var Serve = class {
|
|
|
3046
3196
|
lightningVerify(config) {
|
|
3047
3197
|
return lightningVerifyEndpoint(config);
|
|
3048
3198
|
}
|
|
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
3199
|
/** The verify endpoint a bank rail is polled at, answering off your own statement */
|
|
3058
3200
|
bankVerify(config) {
|
|
3059
3201
|
return bankVerifyEndpoint(config);
|
|
@@ -3075,7 +3217,7 @@ var Serve = class {
|
|
|
3075
3217
|
*/
|
|
3076
3218
|
webhook(handlers) {
|
|
3077
3219
|
const options = { toleranceSecs: handlers.toleranceSecs, url: handlers.url };
|
|
3078
|
-
const once = onceWhileReplayable(handlers.toleranceSecs ??
|
|
3220
|
+
const once = onceWhileReplayable(handlers.toleranceSecs ?? TOLERANCE_SECS);
|
|
3079
3221
|
return async (request) => {
|
|
3080
3222
|
const credential = await this.credential(handlers);
|
|
3081
3223
|
const challenge = await answerWebhookChallenge(request, credential, options);
|
|
@@ -3926,7 +4068,6 @@ var Gateways = class {
|
|
|
3926
4068
|
WrapRefusedError,
|
|
3927
4069
|
agreesWithItself,
|
|
3928
4070
|
answerVerifyChallenge,
|
|
3929
|
-
carriesProof,
|
|
3930
4071
|
decodeInvoice,
|
|
3931
4072
|
fiat,
|
|
3932
4073
|
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';
|