thunder-bridge 2.2.1 → 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 +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 +253 -112
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +258 -111
- 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.js
CHANGED
|
@@ -125,9 +125,9 @@ function fillSchedule(schedule, reader, block) {
|
|
|
125
125
|
schedule[at] = reader.getUint32(block + at * 4, false);
|
|
126
126
|
}
|
|
127
127
|
for (let at = 16; at < SCHEDULE_WORDS; at++) {
|
|
128
|
-
const
|
|
128
|
+
const recent = schedule[at - 15];
|
|
129
129
|
const distant = schedule[at - 2];
|
|
130
|
-
const mixedRecent = rotate(
|
|
130
|
+
const mixedRecent = rotate(recent, 7) ^ rotate(recent, 18) ^ recent >>> 3;
|
|
131
131
|
const mixedDistant = rotate(distant, 17) ^ rotate(distant, 19) ^ distant >>> 10;
|
|
132
132
|
schedule[at] = schedule[at - 16] + mixedRecent + schedule[at - 7] + mixedDistant | 0;
|
|
133
133
|
}
|
|
@@ -382,7 +382,7 @@ async function sealFor(purpose, secret, plaintext, { stable = false } = {}) {
|
|
|
382
382
|
}
|
|
383
383
|
async function unsealFor(purpose, secret, sealed) {
|
|
384
384
|
if (!sealed.startsWith(`${PURPOSE_VERSION}.`)) {
|
|
385
|
-
return
|
|
385
|
+
return null;
|
|
386
386
|
}
|
|
387
387
|
return await opened(
|
|
388
388
|
await keyFor(secret, `${INFO}/${purpose}`),
|
|
@@ -925,13 +925,187 @@ function asBuffer2(bytes) {
|
|
|
925
925
|
return bytes.buffer instanceof ArrayBuffer ? bytes : new Uint8Array(bytes);
|
|
926
926
|
}
|
|
927
927
|
|
|
928
|
-
// ../core/
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
928
|
+
// ../core/signature.ts
|
|
929
|
+
import {
|
|
930
|
+
parseDictionary,
|
|
931
|
+
serializeDictionary,
|
|
932
|
+
serializeInnerList,
|
|
933
|
+
serializeString
|
|
934
|
+
} from "structured-headers";
|
|
935
|
+
var TOLERANCE_SECS = 300;
|
|
936
|
+
var LABEL = "thunder-bridge";
|
|
937
|
+
var ALGORITHM2 = "ed25519";
|
|
934
938
|
var NONCE_BYTES = 16;
|
|
939
|
+
var SIGNATURE_BYTES2 = 64;
|
|
940
|
+
var PUBLIC_KEY = /^[0-9a-f]{64}$/i;
|
|
941
|
+
var CLIENT_SIGNS = {
|
|
942
|
+
tag: "thunder-bridge-client",
|
|
943
|
+
covers: ["@method", "@authority", "@path", "@query", "content-digest"]
|
|
944
|
+
};
|
|
945
|
+
var GATEWAY_SIGNS = {
|
|
946
|
+
tag: "thunder-bridge-gateway",
|
|
947
|
+
covers: ["@method", "@scheme", "@authority", "@path", "content-digest"]
|
|
948
|
+
};
|
|
949
|
+
function contentDigest(body) {
|
|
950
|
+
return serializeDictionary({
|
|
951
|
+
"sha-256": [Uint8Array.from(sha256(new TextEncoder().encode(body))), /* @__PURE__ */ new Map()]
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
function signatureBase(message, covered) {
|
|
955
|
+
const lines = [];
|
|
956
|
+
const seen = /* @__PURE__ */ new Set();
|
|
957
|
+
for (const [name, parameters] of covered[0]) {
|
|
958
|
+
if (typeof name !== "string" || parameters.size > 0 || seen.has(name)) {
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
seen.add(name);
|
|
962
|
+
const value = componentValue(message, name);
|
|
963
|
+
if (value === null) {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
lines.push(`${serializeString(name)}: ${value}`);
|
|
967
|
+
}
|
|
968
|
+
lines.push(`"@signature-params": ${serializeInnerList(covered)}`);
|
|
969
|
+
return lines.join("\n");
|
|
970
|
+
}
|
|
971
|
+
async function signedBy(key, profile, message, body, { created = unixNow(), nonce = randomNonce() } = {}) {
|
|
972
|
+
const digest = contentDigest(body);
|
|
973
|
+
const covered = [
|
|
974
|
+
profile.covers.map((name) => [name, /* @__PURE__ */ new Map()]),
|
|
975
|
+
/* @__PURE__ */ new Map([
|
|
976
|
+
["created", created],
|
|
977
|
+
["nonce", nonce],
|
|
978
|
+
["keyid", key.publicKeyHex],
|
|
979
|
+
["alg", ALGORITHM2],
|
|
980
|
+
["tag", profile.tag]
|
|
981
|
+
])
|
|
982
|
+
];
|
|
983
|
+
const base = signatureBase(
|
|
984
|
+
{ ...message, headers: new Headers({ "content-digest": digest }) },
|
|
985
|
+
covered
|
|
986
|
+
);
|
|
987
|
+
if (base === null) {
|
|
988
|
+
throw new Error(`${message.url} cannot be signed as ${profile.tag}`);
|
|
989
|
+
}
|
|
990
|
+
const signature = Uint8Array.from(hexToBytes(await key.sign(new TextEncoder().encode(base))));
|
|
991
|
+
return {
|
|
992
|
+
"content-digest": digest,
|
|
993
|
+
"signature-input": serializeDictionary(/* @__PURE__ */ new Map([[LABEL, covered]])),
|
|
994
|
+
signature: serializeDictionary(/* @__PURE__ */ new Map([[LABEL, [signature, /* @__PURE__ */ new Map()]]]))
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
async function signerOf(message, body, profile, toleranceSecs = TOLERANCE_SECS) {
|
|
998
|
+
const found = chosen(message.headers, profile.tag);
|
|
999
|
+
if (found === null) {
|
|
1000
|
+
return null;
|
|
1001
|
+
}
|
|
1002
|
+
const [components, parameters] = found.covered;
|
|
1003
|
+
const named = components.map(([name]) => name);
|
|
1004
|
+
if (!profile.covers.every((name) => named.includes(name))) {
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
const keyid = parameters.get("keyid");
|
|
1008
|
+
const created = parameters.get("created");
|
|
1009
|
+
const nonce = parameters.get("nonce");
|
|
1010
|
+
const alg = parameters.get("alg");
|
|
1011
|
+
if (typeof keyid !== "string" || !PUBLIC_KEY.test(keyid)) {
|
|
1012
|
+
return null;
|
|
1013
|
+
}
|
|
1014
|
+
if (typeof created !== "number" || !Number.isInteger(created)) {
|
|
1015
|
+
return null;
|
|
1016
|
+
}
|
|
1017
|
+
if (Math.abs(unixNow() - created) > toleranceSecs) {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
if (alg !== void 0 && alg !== ALGORITHM2 || nonce !== void 0 && typeof nonce !== "string") {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
if (!digestMatches(message.headers.get("content-digest"), body)) {
|
|
1024
|
+
return null;
|
|
1025
|
+
}
|
|
1026
|
+
const base = signatureBase(message, found.covered);
|
|
1027
|
+
if (base === null) {
|
|
1028
|
+
return null;
|
|
1029
|
+
}
|
|
1030
|
+
const signer = keyid.toLowerCase();
|
|
1031
|
+
const proven = await verifyHex(
|
|
1032
|
+
signer,
|
|
1033
|
+
bytesToHex(found.signature),
|
|
1034
|
+
new TextEncoder().encode(base)
|
|
1035
|
+
);
|
|
1036
|
+
return proven ? { keyid: signer, created, nonce: nonce ?? null } : null;
|
|
1037
|
+
}
|
|
1038
|
+
function componentValue(message, name) {
|
|
1039
|
+
let url;
|
|
1040
|
+
try {
|
|
1041
|
+
url = new URL(message.url);
|
|
1042
|
+
} catch {
|
|
1043
|
+
return null;
|
|
1044
|
+
}
|
|
1045
|
+
switch (name) {
|
|
1046
|
+
case "@method":
|
|
1047
|
+
return message.method;
|
|
1048
|
+
case "@authority":
|
|
1049
|
+
return (message.authority ?? url.host).toLowerCase();
|
|
1050
|
+
case "@scheme":
|
|
1051
|
+
return url.protocol.slice(0, -1).toLowerCase();
|
|
1052
|
+
case "@path":
|
|
1053
|
+
return url.pathname === "" ? "/" : url.pathname;
|
|
1054
|
+
case "@query":
|
|
1055
|
+
return url.search === "" ? "?" : url.search;
|
|
1056
|
+
default:
|
|
1057
|
+
return name.startsWith("@") ? null : message.headers.get(name)?.trim() ?? null;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
function chosen(headers, tag) {
|
|
1061
|
+
const inputs = dictionaryOf(headers.get("signature-input"));
|
|
1062
|
+
const signatures = dictionaryOf(headers.get("signature"));
|
|
1063
|
+
if (inputs === null || signatures === null) {
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
for (const [label, member] of inputs) {
|
|
1067
|
+
if (!isInnerList(member) || member[1].get("tag") !== tag) {
|
|
1068
|
+
continue;
|
|
1069
|
+
}
|
|
1070
|
+
const value = signatures.get(label);
|
|
1071
|
+
if (value === void 0 || isInnerList(value) || !(value[0] instanceof ArrayBuffer)) {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
const signature = new Uint8Array(value[0]);
|
|
1075
|
+
return signature.length === SIGNATURE_BYTES2 ? { covered: member, signature } : null;
|
|
1076
|
+
}
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
function digestMatches(field, body) {
|
|
1080
|
+
const digest = dictionaryOf(field)?.get("sha-256");
|
|
1081
|
+
if (digest === void 0 || isInnerList(digest) || !(digest[0] instanceof ArrayBuffer)) {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
const sent = new Uint8Array(digest[0]);
|
|
1085
|
+
const counted = sha256(new TextEncoder().encode(body));
|
|
1086
|
+
return sent.length === counted.length && sent.every((byte, at) => byte === counted[at]);
|
|
1087
|
+
}
|
|
1088
|
+
function dictionaryOf(field) {
|
|
1089
|
+
if (field === null) {
|
|
1090
|
+
return null;
|
|
1091
|
+
}
|
|
1092
|
+
try {
|
|
1093
|
+
return parseDictionary(field);
|
|
1094
|
+
} catch {
|
|
1095
|
+
return null;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
function isInnerList(member) {
|
|
1099
|
+
return Array.isArray(member[0]);
|
|
1100
|
+
}
|
|
1101
|
+
function randomNonce() {
|
|
1102
|
+
return bytesToHex(crypto.getRandomValues(new Uint8Array(NONCE_BYTES)));
|
|
1103
|
+
}
|
|
1104
|
+
function unixNow() {
|
|
1105
|
+
return Math.floor(Date.now() / 1e3);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// ../core/caller.ts
|
|
935
1109
|
var SIGNING_LABEL = "request-signing";
|
|
936
1110
|
async function callerKey(secret) {
|
|
937
1111
|
return await signingKeyFromSeed(hexToBytes(await hmacHex(secret, SIGNING_LABEL)));
|
|
@@ -940,20 +1114,11 @@ function paymentNamedBy(caller, paymentHash) {
|
|
|
940
1114
|
return sha256Hex(`${caller}.payment-id.${paymentHash}`);
|
|
941
1115
|
}
|
|
942
1116
|
async function signedAs(key, method, path, body, host) {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
[NONCE_HEADER]: nonce,
|
|
949
|
-
[SIGNATURE_HEADER]: `${PREFIX}${await key.sign(spokenTo(host, timestamp, nonce, method, path, body))}`
|
|
950
|
-
};
|
|
951
|
-
}
|
|
952
|
-
function spokenTo(host, timestamp, nonce, method, path, body) {
|
|
953
|
-
return new TextEncoder().encode(
|
|
954
|
-
["v2", host.toLowerCase(), timestamp, nonce, method.toUpperCase(), path, sha256Hex(body)].join(
|
|
955
|
-
"\n"
|
|
956
|
-
)
|
|
1117
|
+
return await signedBy(
|
|
1118
|
+
key,
|
|
1119
|
+
CLIENT_SIGNS,
|
|
1120
|
+
{ method, url: `https://${host}${path}`, authority: host },
|
|
1121
|
+
body
|
|
957
1122
|
);
|
|
958
1123
|
}
|
|
959
1124
|
|
|
@@ -1229,6 +1394,7 @@ var NoWalletAvailable = class extends Error {
|
|
|
1229
1394
|
|
|
1230
1395
|
// ../core/lnurl.ts
|
|
1231
1396
|
var VERIFY_WITHOUT_PREIMAGE = ["zeuspay.com", "zeusnuts.com", "ecash.love"];
|
|
1397
|
+
var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
|
|
1232
1398
|
var RESOLVE_TIMEOUT_MS = 3e4;
|
|
1233
1399
|
var NAMES_ONE_ACCOUNT = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)*$/;
|
|
1234
1400
|
var MIN_PACE_SECS = 1;
|
|
@@ -1290,6 +1456,12 @@ async function probe(send, address, amountMsat, deadline) {
|
|
|
1290
1456
|
if (pay.tag !== "payRequest" || !pay.callback) {
|
|
1291
1457
|
throw new WalletRefused("unreachable", `${address} answered with no payRequest`);
|
|
1292
1458
|
}
|
|
1459
|
+
if (!namesTheAddress(pay.metadata, address)) {
|
|
1460
|
+
throw new WalletRefused(
|
|
1461
|
+
"cannot-prove-delivery",
|
|
1462
|
+
`${address} serves metadata that does not name it, so no invoice binds to it`
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1293
1465
|
if (amountMsat < pay.minSendable || amountMsat > pay.maxSendable) {
|
|
1294
1466
|
throw new WalletRefused(
|
|
1295
1467
|
"amount-not-accepted",
|
|
@@ -1382,6 +1554,19 @@ function ceilingAskedFor(headers) {
|
|
|
1382
1554
|
}
|
|
1383
1555
|
return Math.min(Math.max(perWindow / window, MIN_PER_SECOND), MAX_PER_SECOND);
|
|
1384
1556
|
}
|
|
1557
|
+
function namesTheAddress(metadata, address) {
|
|
1558
|
+
if (typeof metadata !== "string") {
|
|
1559
|
+
return false;
|
|
1560
|
+
}
|
|
1561
|
+
try {
|
|
1562
|
+
const entries = JSON.parse(metadata);
|
|
1563
|
+
return Array.isArray(entries) && entries.some(
|
|
1564
|
+
(entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && entry[1].toLowerCase() === address.toLowerCase()
|
|
1565
|
+
);
|
|
1566
|
+
} catch {
|
|
1567
|
+
return false;
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1385
1570
|
function cannotReleaseAPreimage(host) {
|
|
1386
1571
|
const lowered = host.toLowerCase();
|
|
1387
1572
|
return VERIFY_WITHOUT_PREIMAGE.some(
|
|
@@ -1496,18 +1681,9 @@ function headersOf(raw) {
|
|
|
1496
1681
|
return headers;
|
|
1497
1682
|
}
|
|
1498
1683
|
|
|
1499
|
-
// ../core/delivery.ts
|
|
1500
|
-
function deliverySigned(url, timestamp, body) {
|
|
1501
|
-
const endpoint = new URL(url);
|
|
1502
|
-
return new TextEncoder().encode(
|
|
1503
|
-
["v2", `${endpoint.origin}${endpoint.pathname}`, timestamp, body].join("\n")
|
|
1504
|
-
);
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
1684
|
// src/verify.ts
|
|
1508
1685
|
var HTTP_TIMEOUT_MS2 = 15e3;
|
|
1509
1686
|
var ISSUED_SKEW_SECS = 300;
|
|
1510
|
-
var NAMES_AN_ADDRESS = ["text/identifier", "text/email"];
|
|
1511
1687
|
var DEFAULT_WRAP_PROPORTION = 0.01;
|
|
1512
1688
|
var DEFAULT_WRAP_BASE_MSAT = 1e3;
|
|
1513
1689
|
async function proveOrigin(payment, asked, askedAt) {
|
|
@@ -1554,16 +1730,6 @@ async function proveOrigin(payment, asked, askedAt) {
|
|
|
1554
1730
|
throw cheat("invoice_settled");
|
|
1555
1731
|
}
|
|
1556
1732
|
}
|
|
1557
|
-
function namesTheAddress(metadata, address) {
|
|
1558
|
-
try {
|
|
1559
|
-
const entries = JSON.parse(metadata);
|
|
1560
|
-
return Array.isArray(entries) && entries.some(
|
|
1561
|
-
(entry) => Array.isArray(entry) && NAMES_AN_ADDRESS.includes(entry[0]) && typeof entry[1] === "string" && equalIgnoringCase(entry[1], address)
|
|
1562
|
-
);
|
|
1563
|
-
} catch {
|
|
1564
|
-
return false;
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
1733
|
async function proveSettlement(payment, asked) {
|
|
1568
1734
|
await proveOrigin(payment, asked);
|
|
1569
1735
|
const verified = await reachable(payment.verifyUrl, payment);
|
|
@@ -1587,7 +1753,6 @@ function agreesWithItself(report) {
|
|
|
1587
1753
|
}
|
|
1588
1754
|
return preimageMatchesHash(report.preimage, report.paymentHash);
|
|
1589
1755
|
}
|
|
1590
|
-
var carriesProof = agreesWithItself;
|
|
1591
1756
|
function wrapFeeCeiling(amountMsat, allowance) {
|
|
1592
1757
|
const proportional = amountMsat * (allowance?.proportion ?? DEFAULT_WRAP_PROPORTION);
|
|
1593
1758
|
return msat(Math.max(allowance?.baseMsat ?? DEFAULT_WRAP_BASE_MSAT, Math.floor(proportional)));
|
|
@@ -1869,10 +2034,6 @@ function isStatus(value) {
|
|
|
1869
2034
|
}
|
|
1870
2035
|
|
|
1871
2036
|
// src/webhook.ts
|
|
1872
|
-
var SIGNATURE_HEADER2 = "x-signature-v2";
|
|
1873
|
-
var TIMESTAMP_HEADER2 = "x-timestamp";
|
|
1874
|
-
var GATEWAY_KEY_PREFIX = "ed25519=";
|
|
1875
|
-
var DEFAULT_TOLERANCE_SECS = 300;
|
|
1876
2037
|
var CHALLENGE = "webhook-challenge";
|
|
1877
2038
|
var VERIFY_CHALLENGE = "verify-challenge";
|
|
1878
2039
|
async function readPayment(request, credential, options = {}) {
|
|
@@ -1896,24 +2057,14 @@ async function answerVerifyChallenge(request) {
|
|
|
1896
2057
|
return nonce === null ? null : answered2(nonce);
|
|
1897
2058
|
}
|
|
1898
2059
|
async function believable(request, credential, options) {
|
|
1899
|
-
const signature = request.headers.get(SIGNATURE_HEADER2);
|
|
1900
|
-
const timestamp = request.headers.get(TIMESTAMP_HEADER2);
|
|
1901
|
-
if (signature === null || timestamp === null) {
|
|
1902
|
-
return null;
|
|
1903
|
-
}
|
|
1904
|
-
if (!recent(timestamp, options.toleranceSecs ?? DEFAULT_TOLERANCE_SECS)) {
|
|
1905
|
-
return null;
|
|
1906
|
-
}
|
|
1907
|
-
if (!signature.startsWith(GATEWAY_KEY_PREFIX)) {
|
|
1908
|
-
return null;
|
|
1909
|
-
}
|
|
1910
2060
|
const body = await request.clone().text();
|
|
1911
|
-
const
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
2061
|
+
const signer = await signerOf(
|
|
2062
|
+
{ method: request.method, url: options.url ?? request.url, headers: request.headers },
|
|
2063
|
+
body,
|
|
2064
|
+
GATEWAY_SIGNS,
|
|
2065
|
+
options.toleranceSecs
|
|
1915
2066
|
);
|
|
1916
|
-
return
|
|
2067
|
+
return signer !== null && signer.keyid === credential.publicKey.toLowerCase() ? body : null;
|
|
1917
2068
|
}
|
|
1918
2069
|
function proved(read) {
|
|
1919
2070
|
return read !== null && read.status === "paid" && !agreesWithItself(read) ? null : read;
|
|
@@ -1941,13 +2092,6 @@ function nonceOf2(text3, type) {
|
|
|
1941
2092
|
return null;
|
|
1942
2093
|
}
|
|
1943
2094
|
}
|
|
1944
|
-
function recent(timestamp, toleranceSecs) {
|
|
1945
|
-
const sent = Number(timestamp);
|
|
1946
|
-
if (!Number.isFinite(sent)) {
|
|
1947
|
-
return false;
|
|
1948
|
-
}
|
|
1949
|
-
return Math.abs(Math.floor(Date.now() / 1e3) - sent) <= toleranceSecs;
|
|
1950
|
-
}
|
|
1951
2095
|
|
|
1952
2096
|
// src/bank.ts
|
|
1953
2097
|
var DEFAULT_CURRENCY = "CZK";
|
|
@@ -2026,7 +2170,7 @@ async function answeredAt(params, subject) {
|
|
|
2026
2170
|
return polling.toString();
|
|
2027
2171
|
}
|
|
2028
2172
|
async function creditedPreimage(params) {
|
|
2029
|
-
const since =
|
|
2173
|
+
const since = unixNow2() - (params.lookBackSecs ?? DEFAULT_LOOK_BACK_SECS);
|
|
2030
2174
|
const landed = (await params.statement(since)).some((credit) => pays(credit, params.asked));
|
|
2031
2175
|
if (!landed) {
|
|
2032
2176
|
return null;
|
|
@@ -2124,7 +2268,7 @@ function refuseUnusable(params, iban, currency) {
|
|
|
2124
2268
|
function accountOf(iban) {
|
|
2125
2269
|
return iban.replace(/\s+/g, "").toUpperCase();
|
|
2126
2270
|
}
|
|
2127
|
-
function
|
|
2271
|
+
function unixNow2() {
|
|
2128
2272
|
return Math.floor(Date.now() / 1e3);
|
|
2129
2273
|
}
|
|
2130
2274
|
|
|
@@ -2560,6 +2704,19 @@ function nwcRail(gateway, config) {
|
|
|
2560
2704
|
// src/relay.ts
|
|
2561
2705
|
var DEFAULT_POLL_EVERY_SECS3 = 5;
|
|
2562
2706
|
var WALLET = "w";
|
|
2707
|
+
function verifiedThrough(path) {
|
|
2708
|
+
const through = path.verifyThrough ?? null;
|
|
2709
|
+
const mints = path.gatewayMints === true;
|
|
2710
|
+
if (through !== null && mints) {
|
|
2711
|
+
throw new Error("verifyThrough and gatewayMints are two different paths, name only one");
|
|
2712
|
+
}
|
|
2713
|
+
if (through === null && !mints) {
|
|
2714
|
+
throw new Error(
|
|
2715
|
+
"name verifyThrough, where your serve.lightningVerify is mounted, or gatewayMints: true to let the gateway mint and poll the wallet itself"
|
|
2716
|
+
);
|
|
2717
|
+
}
|
|
2718
|
+
return through;
|
|
2719
|
+
}
|
|
2563
2720
|
function lightningVerifyEndpoint(config) {
|
|
2564
2721
|
const paced = {
|
|
2565
2722
|
"cache-control": `max-age=${config.pollEverySecs ?? DEFAULT_POLL_EVERY_SECS3}`
|
|
@@ -2638,6 +2795,10 @@ function bankRail(gateway, config) {
|
|
|
2638
2795
|
};
|
|
2639
2796
|
}
|
|
2640
2797
|
function lightningRail(gateway, config) {
|
|
2798
|
+
const through = verifiedThrough(config);
|
|
2799
|
+
return through === null ? mintedByGateway(gateway, config) : resolvedHere(gateway, config, through);
|
|
2800
|
+
}
|
|
2801
|
+
function mintedByGateway(gateway, config) {
|
|
2641
2802
|
return async (order) => {
|
|
2642
2803
|
const payment = await gateway.mint(
|
|
2643
2804
|
{
|
|
@@ -2661,21 +2822,20 @@ function lightningRail(gateway, config) {
|
|
|
2661
2822
|
};
|
|
2662
2823
|
};
|
|
2663
2824
|
}
|
|
2664
|
-
function
|
|
2825
|
+
function resolvedHere(gateway, config, through) {
|
|
2665
2826
|
return async (order) => {
|
|
2666
2827
|
const resolved2 = await invoiceFrom(
|
|
2667
2828
|
config.paidTo,
|
|
2668
2829
|
await msatForOrder(order, config.amount, config.rate),
|
|
2669
2830
|
config.send
|
|
2670
2831
|
);
|
|
2671
|
-
const relay = config.relayThrough;
|
|
2672
2832
|
const watched = await gateway.watch({
|
|
2673
2833
|
paymentHash: resolved2.paymentHash,
|
|
2674
|
-
verifyUrl:
|
|
2675
|
-
|
|
2834
|
+
verifyUrl: await relayedVerifyUrl(
|
|
2835
|
+
through.endpoint,
|
|
2676
2836
|
{ url: resolved2.verifyUrl, hash: resolved2.paymentHash },
|
|
2677
|
-
|
|
2678
|
-
)
|
|
2837
|
+
through.secret
|
|
2838
|
+
),
|
|
2679
2839
|
expiresAt: resolved2.expiresAt,
|
|
2680
2840
|
trigger: config.trigger,
|
|
2681
2841
|
replay: config.replay,
|
|
@@ -2718,16 +2878,12 @@ var Rails = class {
|
|
|
2718
2878
|
this.gateway = gateway;
|
|
2719
2879
|
}
|
|
2720
2880
|
gateway;
|
|
2721
|
-
/** Lightning, with the gateway minting against a priority list of addresses */
|
|
2722
|
-
lightning(config) {
|
|
2723
|
-
return lightningRail(this.gateway, config);
|
|
2724
|
-
}
|
|
2725
2881
|
/**
|
|
2726
|
-
* Lightning
|
|
2727
|
-
*
|
|
2882
|
+
* Lightning against a priority list of addresses, verified through your
|
|
2883
|
+
* `serve.lightningVerify`, or minted by the gateway when you say `gatewayMints`
|
|
2728
2884
|
*/
|
|
2729
|
-
|
|
2730
|
-
return
|
|
2885
|
+
lightning(config) {
|
|
2886
|
+
return lightningRail(this.gateway, config);
|
|
2731
2887
|
}
|
|
2732
2888
|
/** A bank transfer, proved the way a Lightning payment is */
|
|
2733
2889
|
bank(config) {
|
|
@@ -2793,11 +2949,12 @@ function waitingOf(options) {
|
|
|
2793
2949
|
// src/trigger.ts
|
|
2794
2950
|
var NONCE_BYTES3 = 16;
|
|
2795
2951
|
function lnurlPayEndpoint(gateway, config) {
|
|
2952
|
+
const through = verifiedThrough(config);
|
|
2796
2953
|
return async (request) => {
|
|
2797
2954
|
const url = new URL(request.url);
|
|
2798
2955
|
const asked = url.searchParams.get("to");
|
|
2799
2956
|
try {
|
|
2800
|
-
return asked === null ? await offer(gateway, config, url) : await mint(gateway, config, url, asked);
|
|
2957
|
+
return asked === null ? await offer(gateway, config, through, url) : await mint(gateway, config, through, url, asked);
|
|
2801
2958
|
} catch (failure) {
|
|
2802
2959
|
return refuse(failure instanceof Error ? failure.message : "the trigger could not be served");
|
|
2803
2960
|
}
|
|
@@ -2815,10 +2972,10 @@ function watchTicketEndpoint(gateway, config) {
|
|
|
2815
2972
|
function publicWatchTicketEndpoint(gateway, config) {
|
|
2816
2973
|
return () => issue(gateway, config);
|
|
2817
2974
|
}
|
|
2818
|
-
async function offer(gateway, config, url) {
|
|
2975
|
+
async function offer(gateway, config, through, url) {
|
|
2819
2976
|
const { least, most } = await spread(config.amount);
|
|
2820
|
-
const winner = await quoted(gateway, config, least);
|
|
2821
|
-
const nonce =
|
|
2977
|
+
const winner = await quoted(gateway, config, through, least);
|
|
2978
|
+
const nonce = randomNonce2();
|
|
2822
2979
|
const callback = new URL(config.baseUrl ?? `${url.origin}${url.pathname}`);
|
|
2823
2980
|
callback.searchParams.set("to", winner.address);
|
|
2824
2981
|
callback.searchParams.set("least", String(least));
|
|
@@ -2833,8 +2990,8 @@ async function offer(gateway, config, url) {
|
|
|
2833
2990
|
maxSendable: most
|
|
2834
2991
|
});
|
|
2835
2992
|
}
|
|
2836
|
-
async function quoted(gateway, config, amountMsat) {
|
|
2837
|
-
if (
|
|
2993
|
+
async function quoted(gateway, config, through, amountMsat) {
|
|
2994
|
+
if (through !== null) {
|
|
2838
2995
|
const { won } = await quote(
|
|
2839
2996
|
config.send ?? pinnedToTheAddressWeVerified,
|
|
2840
2997
|
listed(config.paidTo),
|
|
@@ -2860,7 +3017,7 @@ async function spread(amount) {
|
|
|
2860
3017
|
}
|
|
2861
3018
|
return { least, most };
|
|
2862
3019
|
}
|
|
2863
|
-
async function mint(gateway, config, url, address) {
|
|
3020
|
+
async function mint(gateway, config, through, url, address) {
|
|
2864
3021
|
const least = Number(url.searchParams.get("least"));
|
|
2865
3022
|
const most = Number(url.searchParams.get("most"));
|
|
2866
3023
|
const nonce = url.searchParams.get("n") ?? "";
|
|
@@ -2879,7 +3036,7 @@ async function mint(gateway, config, url, address) {
|
|
|
2879
3036
|
least === most ? `this trigger costs exactly ${least} msat` : `this trigger takes between ${least} and ${most} msat`
|
|
2880
3037
|
);
|
|
2881
3038
|
}
|
|
2882
|
-
const minted =
|
|
3039
|
+
const minted = through === null ? await mintThroughGateway(gateway, config, address, amountMsat, nonce) : await mintHere(gateway, config, through, address, amountMsat);
|
|
2883
3040
|
return Response.json({
|
|
2884
3041
|
status: "OK",
|
|
2885
3042
|
pr: minted.bolt11,
|
|
@@ -2894,7 +3051,7 @@ async function mintThroughGateway(gateway, config, address, amountMsat, nonce) {
|
|
|
2894
3051
|
);
|
|
2895
3052
|
return { bolt11: payment.bolt11, verifyUrl: payment.verifyUrl };
|
|
2896
3053
|
}
|
|
2897
|
-
async function
|
|
3054
|
+
async function mintHere(gateway, config, through, address, amountMsat) {
|
|
2898
3055
|
const resolved2 = await resolve(
|
|
2899
3056
|
config.send ?? pinnedToTheAddressWeVerified,
|
|
2900
3057
|
[address],
|
|
@@ -2902,15 +3059,14 @@ async function mintBlind(gateway, config, address, amountMsat) {
|
|
|
2902
3059
|
);
|
|
2903
3060
|
const minted = { ...resolved2, amountMsat, lnAddress: resolved2.address };
|
|
2904
3061
|
const locked = config.sealed;
|
|
2905
|
-
const relay = config.relayThrough;
|
|
2906
3062
|
try {
|
|
2907
3063
|
await gateway.watch({
|
|
2908
3064
|
paymentHash: resolved2.paymentHash,
|
|
2909
|
-
verifyUrl:
|
|
2910
|
-
|
|
3065
|
+
verifyUrl: await relayedVerifyUrl(
|
|
3066
|
+
through.endpoint,
|
|
2911
3067
|
{ url: resolved2.verifyUrl, hash: resolved2.paymentHash },
|
|
2912
|
-
|
|
2913
|
-
)
|
|
3068
|
+
through.secret
|
|
3069
|
+
),
|
|
2914
3070
|
expiresAt: resolved2.expiresAt,
|
|
2915
3071
|
trigger: config.watchSecret,
|
|
2916
3072
|
replay: config.replay,
|
|
@@ -2932,7 +3088,7 @@ function alreadyWatched(refused) {
|
|
|
2932
3088
|
function sign(secret, address, least, most, nonce) {
|
|
2933
3089
|
return hmacHex(secret, `lnurl-callback|${address}|${least}|${most}|${nonce}`);
|
|
2934
3090
|
}
|
|
2935
|
-
function
|
|
3091
|
+
function randomNonce2() {
|
|
2936
3092
|
const bytes = crypto.getRandomValues(new Uint8Array(NONCE_BYTES3));
|
|
2937
3093
|
let hex = "";
|
|
2938
3094
|
for (const byte of bytes) {
|
|
@@ -2986,14 +3142,6 @@ var Serve = class {
|
|
|
2986
3142
|
lightningVerify(config) {
|
|
2987
3143
|
return lightningVerifyEndpoint(config);
|
|
2988
3144
|
}
|
|
2989
|
-
/**
|
|
2990
|
-
* What `lightningVerify` was called before 2.2.0
|
|
2991
|
-
*
|
|
2992
|
-
* @deprecated Use `lightningVerify`, beside `bankVerify` and `nwcVerify`
|
|
2993
|
-
*/
|
|
2994
|
-
verify(config) {
|
|
2995
|
-
return this.lightningVerify(config);
|
|
2996
|
-
}
|
|
2997
3145
|
/** The verify endpoint a bank rail is polled at, answering off your own statement */
|
|
2998
3146
|
bankVerify(config) {
|
|
2999
3147
|
return bankVerifyEndpoint(config);
|
|
@@ -3015,7 +3163,7 @@ var Serve = class {
|
|
|
3015
3163
|
*/
|
|
3016
3164
|
webhook(handlers) {
|
|
3017
3165
|
const options = { toleranceSecs: handlers.toleranceSecs, url: handlers.url };
|
|
3018
|
-
const once = onceWhileReplayable(handlers.toleranceSecs ??
|
|
3166
|
+
const once = onceWhileReplayable(handlers.toleranceSecs ?? TOLERANCE_SECS);
|
|
3019
3167
|
return async (request) => {
|
|
3020
3168
|
const credential = await this.credential(handlers);
|
|
3021
3169
|
const challenge = await answerWebhookChallenge(request, credential, options);
|
|
@@ -3865,7 +4013,6 @@ export {
|
|
|
3865
4013
|
WrapRefusedError,
|
|
3866
4014
|
agreesWithItself,
|
|
3867
4015
|
answerVerifyChallenge,
|
|
3868
|
-
carriesProof,
|
|
3869
4016
|
decodeInvoice,
|
|
3870
4017
|
fiat,
|
|
3871
4018
|
invoiceFrom,
|