@lnurlcash/kit 0.14.0-rc.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/CHANGELOG.md +16 -0
- package/LICENSE +22 -0
- package/MIGRATION-0.14.md +63 -0
- package/README.md +90 -0
- package/RELEASING.md +64 -0
- package/SECURITY.md +14 -0
- package/THREAT-MODEL.md +44 -0
- package/dist/index.d.ts +275 -0
- package/dist/index.js +1231 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1231 @@
|
|
|
1
|
+
import { bech32, bech32m } from '@scure/base';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
3
|
+
import { utf8ToBytes, bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
4
|
+
import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js';
|
|
5
|
+
|
|
6
|
+
// errors.ts
|
|
7
|
+
var AmbiguousMintError = class extends Error {
|
|
8
|
+
};
|
|
9
|
+
var AmbiguousMutationError = class extends AmbiguousMintError {
|
|
10
|
+
newSecrets;
|
|
11
|
+
constructor(message, newSecrets) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.newSecrets = newSecrets;
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var ServiceError = class extends Error {
|
|
17
|
+
reason;
|
|
18
|
+
constructor(reason) {
|
|
19
|
+
super(reason || "The service rejected the request without saying why.");
|
|
20
|
+
this.reason = reason;
|
|
21
|
+
this.name = "ServiceError";
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var PendingNoteError = class extends Error {
|
|
25
|
+
constructor() {
|
|
26
|
+
super(
|
|
27
|
+
"This note has another operation in progress - try again in a moment."
|
|
28
|
+
);
|
|
29
|
+
this.name = "PendingNoteError";
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
var NoteSpentError = class extends Error {
|
|
33
|
+
constructor(reason) {
|
|
34
|
+
super(`This note has already been spent (service says: "${reason}").`);
|
|
35
|
+
this.name = "NoteSpentError";
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var NoteUnknownError = class extends Error {
|
|
39
|
+
constructor(reason) {
|
|
40
|
+
super(
|
|
41
|
+
`The service doesn't recognize this note (service says: "${reason}").`
|
|
42
|
+
);
|
|
43
|
+
this.name = "NoteUnknownError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var classifyNoteError = (err) => {
|
|
47
|
+
if (!(err instanceof ServiceError)) return err;
|
|
48
|
+
const reason = err.reason;
|
|
49
|
+
if (/spent/i.test(reason)) return new NoteSpentError(reason);
|
|
50
|
+
if (/unknown|not found/i.test(reason)) return new NoteUnknownError(reason);
|
|
51
|
+
return err;
|
|
52
|
+
};
|
|
53
|
+
var sameInvoice = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
54
|
+
var isPreimage = (value) => /^[0-9a-fA-F]{64}$/.test(value.trim());
|
|
55
|
+
var isBolt11Invoice = (value) => /^ln(bc|tb|bcrt|tbs|sb)[0-9]*[munp]?1[a-z0-9]+$/.test(
|
|
56
|
+
value.trim().toLowerCase()
|
|
57
|
+
);
|
|
58
|
+
var BOLT11_AMOUNT_MSAT_PER_UNIT = {
|
|
59
|
+
"": 1e11,
|
|
60
|
+
m: 1e8,
|
|
61
|
+
u: 1e5,
|
|
62
|
+
n: 100,
|
|
63
|
+
p: 0.1
|
|
64
|
+
};
|
|
65
|
+
var encodeBolt11AmountSuffix = (amountMsat) => {
|
|
66
|
+
if (!Number.isInteger(amountMsat) || amountMsat < 0) {
|
|
67
|
+
throw new Error("amount_msat must be a non-negative integer.");
|
|
68
|
+
}
|
|
69
|
+
for (const multiplier of ["", "m", "u", "n", "p"]) {
|
|
70
|
+
const digits = amountMsat / BOLT11_AMOUNT_MSAT_PER_UNIT[multiplier];
|
|
71
|
+
if (Number.isInteger(digits)) return `${digits}${multiplier}`;
|
|
72
|
+
}
|
|
73
|
+
throw new Error("amount_msat could not be encoded.");
|
|
74
|
+
};
|
|
75
|
+
var decodeBolt11AmountSuffix = (suffix) => {
|
|
76
|
+
const match = suffix.match(/^(\d+)([munp])?$/);
|
|
77
|
+
if (!match) return null;
|
|
78
|
+
const [, digits, multiplier] = match;
|
|
79
|
+
const msat = Number(digits) * BOLT11_AMOUNT_MSAT_PER_UNIT[multiplier ?? ""];
|
|
80
|
+
return Number.isSafeInteger(msat) ? msat : null;
|
|
81
|
+
};
|
|
82
|
+
var decodeBolt11AmountMsat = (pr) => {
|
|
83
|
+
const trimmed = pr.trim().toLowerCase();
|
|
84
|
+
const sep = trimmed.lastIndexOf("1");
|
|
85
|
+
if (sep < 2) return null;
|
|
86
|
+
const hrp = trimmed.slice(0, sep);
|
|
87
|
+
const match = hrp.match(/^ln(?:bc|tb|bcrt|tbs|sb)(\d+)?([munp])?$/);
|
|
88
|
+
if (!match) return null;
|
|
89
|
+
const [, digits, multiplier] = match;
|
|
90
|
+
if (!digits) return null;
|
|
91
|
+
const msat = Number(digits) * BOLT11_AMOUNT_MSAT_PER_UNIT[multiplier || ""];
|
|
92
|
+
return Number.isInteger(msat) ? msat : null;
|
|
93
|
+
};
|
|
94
|
+
var BOLT11_TAG_PAYMENT_HASH = 1;
|
|
95
|
+
var decodeBolt11PaymentHash = (pr) => {
|
|
96
|
+
const trimmed = pr.trim().toLowerCase();
|
|
97
|
+
try {
|
|
98
|
+
const decoded = bech32.decode(trimmed, 2048);
|
|
99
|
+
const words = decoded.words;
|
|
100
|
+
const fieldsEnd = words.length - 104;
|
|
101
|
+
let pos = 7;
|
|
102
|
+
while (pos + 3 <= fieldsEnd) {
|
|
103
|
+
const tag = words[pos];
|
|
104
|
+
const len = words[pos + 1] * 32 + words[pos + 2];
|
|
105
|
+
const start = pos + 3;
|
|
106
|
+
const end = start + len;
|
|
107
|
+
if (end > fieldsEnd) break;
|
|
108
|
+
if (tag === BOLT11_TAG_PAYMENT_HASH && len === 52) {
|
|
109
|
+
return bytesToHex(
|
|
110
|
+
bech32.fromWords(words.slice(start, end)).slice(0, 32)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
pos = end;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var verifyMeltPreimage = (pr, preimage) => {
|
|
121
|
+
if (!isPreimage(preimage)) return false;
|
|
122
|
+
const expected = decodeBolt11PaymentHash(pr);
|
|
123
|
+
if (!expected) return false;
|
|
124
|
+
return bytesToHex(sha256(hexToBytes(preimage))) === expected;
|
|
125
|
+
};
|
|
126
|
+
var encodeFixed = (hrp, bytes, length) => {
|
|
127
|
+
if (bytes.length !== length) {
|
|
128
|
+
throw new Error(`${hrp}1... payload must be exactly ${length} bytes`);
|
|
129
|
+
}
|
|
130
|
+
return bech32m.encode(hrp, bech32m.toWords(bytes), false);
|
|
131
|
+
};
|
|
132
|
+
var decodeFixed = (hrp, value, length) => {
|
|
133
|
+
const trimmed = value.trim().toLowerCase();
|
|
134
|
+
if (!trimmed.startsWith(`${hrp}1`)) return null;
|
|
135
|
+
try {
|
|
136
|
+
const decoded = bech32m.decode(trimmed, false);
|
|
137
|
+
if (decoded.prefix !== hrp) return null;
|
|
138
|
+
const bytes = bech32m.fromWords(decoded.words);
|
|
139
|
+
return bytes.length === length ? bytes : null;
|
|
140
|
+
} catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
var encodeCp1 = (pubkeyXOnly) => encodeFixed("cp", pubkeyXOnly, 32);
|
|
145
|
+
var decodeCp1 = (value) => decodeFixed("cp", value, 32);
|
|
146
|
+
var isCp1 = (value) => decodeCp1(value) !== null;
|
|
147
|
+
var encodeCk1 = (signature) => encodeFixed("ck", signature, 65);
|
|
148
|
+
var decodeCk1 = (value) => decodeFixed("ck", value, 65);
|
|
149
|
+
var isCk1 = (value) => decodeCk1(value) !== null;
|
|
150
|
+
var encodeCs1 = (signature) => encodeFixed("cs", signature, 65);
|
|
151
|
+
var decodeCs1 = (value) => decodeFixed("cs", value, 65);
|
|
152
|
+
var isCs1 = (value) => decodeCs1(value) !== null;
|
|
153
|
+
var encodeCs1WithAmount = (amountMsat, signature) => encodeFixed(`cs${encodeBolt11AmountSuffix(amountMsat)}`, signature, 65);
|
|
154
|
+
var decodeCs1WithAmount = (value) => {
|
|
155
|
+
const trimmed = value.trim().toLowerCase();
|
|
156
|
+
const sep = trimmed.lastIndexOf("1");
|
|
157
|
+
if (sep < 2 || !trimmed.slice(0, sep).startsWith("cs")) return null;
|
|
158
|
+
const amountMsat = decodeBolt11AmountSuffix(trimmed.slice(2, sep));
|
|
159
|
+
if (amountMsat === null) return null;
|
|
160
|
+
const signature = decodeFixed(trimmed.slice(0, sep), trimmed, 65);
|
|
161
|
+
return signature ? { amountMsat, signature } : null;
|
|
162
|
+
};
|
|
163
|
+
var isCs1WithAmount = (value) => decodeCs1WithAmount(value) !== null;
|
|
164
|
+
var decodeAnyCs1 = (value) => decodeCs1WithAmount(value)?.signature ?? decodeCs1(value);
|
|
165
|
+
var isAnyCs1 = (value) => isCs1WithAmount(value) || isCs1(value);
|
|
166
|
+
var encodeCx1 = (pubkeyXOnly, chainCode) => encodeFixed("cx", new Uint8Array([...pubkeyXOnly, ...chainCode]), 64);
|
|
167
|
+
var decodeCx1 = (value) => {
|
|
168
|
+
const bytes = decodeFixed("cx", value, 64);
|
|
169
|
+
if (!bytes) return null;
|
|
170
|
+
return { pubkeyXOnly: bytes.slice(0, 32), chainCode: bytes.slice(32) };
|
|
171
|
+
};
|
|
172
|
+
var isCx1 = (value) => decodeCx1(value) !== null;
|
|
173
|
+
var NOTE_DERIVE_TAG = "LNURLcash/derive";
|
|
174
|
+
var ser32BE = (index) => {
|
|
175
|
+
const bytes = new Uint8Array(4);
|
|
176
|
+
new DataView(bytes.buffer).setUint32(0, index, false);
|
|
177
|
+
return bytes;
|
|
178
|
+
};
|
|
179
|
+
var bytesToNumberBE = (bytes) => BigInt(`0x${bytesToHex(bytes)}`);
|
|
180
|
+
var numberToBytesBE = (n, length) => hexToBytes(n.toString(16).padStart(length * 2, "0"));
|
|
181
|
+
var CURVE_ORDER = schnorr.Point.CURVE().n;
|
|
182
|
+
var tweakScalar = (branchPubkeyXOnly, chainCode, index) => {
|
|
183
|
+
const t = schnorr.utils.taggedHash(
|
|
184
|
+
NOTE_DERIVE_TAG,
|
|
185
|
+
branchPubkeyXOnly,
|
|
186
|
+
chainCode,
|
|
187
|
+
ser32BE(index)
|
|
188
|
+
);
|
|
189
|
+
return bytesToNumberBE(t) % CURVE_ORDER;
|
|
190
|
+
};
|
|
191
|
+
var deriveNotePubkey = (branchPubkeyXOnly, chainCode, index) => {
|
|
192
|
+
const t = tweakScalar(branchPubkeyXOnly, chainCode, index);
|
|
193
|
+
const branchPoint = schnorr.utils.lift_x(bytesToNumberBE(branchPubkeyXOnly));
|
|
194
|
+
const notePoint = branchPoint.add(schnorr.Point.BASE.multiply(t));
|
|
195
|
+
return schnorr.utils.pointToBytes(notePoint);
|
|
196
|
+
};
|
|
197
|
+
var deriveNoteSecretKey = (branchPrivateKey, chainCode, index) => {
|
|
198
|
+
const branchPubkeyXOnly = schnorr.getPublicKey(branchPrivateKey);
|
|
199
|
+
const t = tweakScalar(branchPubkeyXOnly, chainCode, index);
|
|
200
|
+
const rawScalar = bytesToNumberBE(branchPrivateKey);
|
|
201
|
+
const branchFullPoint = schnorr.Point.BASE.multiply(rawScalar);
|
|
202
|
+
const evenYScalar = branchFullPoint.y % 2n === 0n ? rawScalar : (CURVE_ORDER - rawScalar) % CURVE_ORDER;
|
|
203
|
+
const noteScalar = (evenYScalar + t) % CURVE_ORDER;
|
|
204
|
+
return numberToBytesBE(noteScalar, 32);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// urls.ts
|
|
208
|
+
var isBech32Lnurl = (data) => data.trim().toUpperCase().startsWith("LNURL1");
|
|
209
|
+
var toBech32Lnurl = (url) => {
|
|
210
|
+
const bytes = new TextEncoder().encode(url);
|
|
211
|
+
return bech32.encode("lnurl", bech32.toWords(bytes), 2048).toUpperCase();
|
|
212
|
+
};
|
|
213
|
+
var fromBech32Lnurl = (data) => {
|
|
214
|
+
const safe = data.trim().toUpperCase();
|
|
215
|
+
if (!safe.startsWith("LNURL1")) return null;
|
|
216
|
+
try {
|
|
217
|
+
const decoded = bech32.decode(
|
|
218
|
+
`LNURL1${safe.slice(6)}`,
|
|
219
|
+
2048
|
|
220
|
+
);
|
|
221
|
+
return new TextDecoder().decode(bech32.fromWords(decoded.words));
|
|
222
|
+
} catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var INSECURE_HOSTS = ["127.0.0.1", "0.0.0.0", "localhost"];
|
|
227
|
+
var isInsecureHost = (host) => INSECURE_HOSTS.includes(host) || host.endsWith(".onion");
|
|
228
|
+
var defaultSchemeFor = (hostish) => isInsecureHost(hostish.split("/")[0].split(":")[0]) ? "http" : "https";
|
|
229
|
+
var isAllowedServiceUrl = (value) => {
|
|
230
|
+
let url;
|
|
231
|
+
try {
|
|
232
|
+
url = new URL(value);
|
|
233
|
+
} catch {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
if (url.protocol === "https:") return true;
|
|
237
|
+
if (url.protocol === "http:") return isInsecureHost(url.hostname);
|
|
238
|
+
return false;
|
|
239
|
+
};
|
|
240
|
+
var fromLud17 = (url) => {
|
|
241
|
+
const match = url.match(/^(?:lnurlw|lnurlp|lnurlc|keyauth):\/\/([^/]+)/i);
|
|
242
|
+
if (!match) return url;
|
|
243
|
+
return url.replace(/^[a-z]+:\/\//i, `${defaultSchemeFor(match[1])}://`);
|
|
244
|
+
};
|
|
245
|
+
var toLud17w = (url) => url.replace(/^https?:\/\//, "lnurlw://");
|
|
246
|
+
var isLightningAddress = (value) => {
|
|
247
|
+
const trimmed = value.trim();
|
|
248
|
+
const at = trimmed.indexOf("@");
|
|
249
|
+
if (at <= 0 || at === trimmed.length - 1) return false;
|
|
250
|
+
if (trimmed.indexOf("@", at + 1) !== -1) return false;
|
|
251
|
+
const domain = trimmed.slice(at + 1);
|
|
252
|
+
if (/\s/.test(trimmed)) return false;
|
|
253
|
+
if (/^[^\s@]+\.[^\s@]+$/.test(domain)) return true;
|
|
254
|
+
return isInsecureHost(domain.split(":")[0]);
|
|
255
|
+
};
|
|
256
|
+
var lnAddressToUrl = (address) => {
|
|
257
|
+
const [name, domain] = address.trim().split("@");
|
|
258
|
+
return `${defaultSchemeFor(domain)}://${domain}/.well-known/lnurlp/${name}`;
|
|
259
|
+
};
|
|
260
|
+
var isBareMintDomain = (value) => {
|
|
261
|
+
const trimmed = value.trim();
|
|
262
|
+
if (isLightningAddress(trimmed)) return false;
|
|
263
|
+
if (/^@?[^\s@/]+\.[^\s@/]+$/.test(trimmed)) return true;
|
|
264
|
+
return isInsecureHost(trimmed.replace(/^@/, "").split(":")[0]);
|
|
265
|
+
};
|
|
266
|
+
var bareMintDomainToUrl = (value) => lnAddressToUrl(`mint@${value.trim().replace(/^@/, "")}`);
|
|
267
|
+
var resolveMintInput = (value) => {
|
|
268
|
+
const trimmed = value.trim();
|
|
269
|
+
if (!trimmed) return null;
|
|
270
|
+
if (isBech32Lnurl(trimmed)) {
|
|
271
|
+
const url = fromBech32Lnurl(trimmed);
|
|
272
|
+
return url && isAllowedServiceUrl(url) ? url : null;
|
|
273
|
+
}
|
|
274
|
+
if (isLightningAddress(trimmed)) return lnAddressToUrl(trimmed);
|
|
275
|
+
if (isBareMintDomain(trimmed)) return bareMintDomainToUrl(trimmed);
|
|
276
|
+
return null;
|
|
277
|
+
};
|
|
278
|
+
var LNURLP_PATH_RE = /^(.*\/\.well-known\/)lnurlp\/([^/]+)$/;
|
|
279
|
+
var mintAddressUrl = (payUrl) => {
|
|
280
|
+
let parsed;
|
|
281
|
+
try {
|
|
282
|
+
parsed = new URL(payUrl);
|
|
283
|
+
} catch {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
const match = parsed.pathname.match(LNURLP_PATH_RE);
|
|
287
|
+
if (!match) return null;
|
|
288
|
+
return `${parsed.origin}${match[1]}lnurlw/${match[2]}`;
|
|
289
|
+
};
|
|
290
|
+
var lightningAddressUsername = (payUrl) => {
|
|
291
|
+
try {
|
|
292
|
+
return new URL(payUrl).pathname.match(LNURLP_PATH_RE)?.[2] ?? null;
|
|
293
|
+
} catch {
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
var resolveLnurlInput = (value) => {
|
|
298
|
+
const trimmed = value.trim().replace(/^lightning:/i, "");
|
|
299
|
+
if (!trimmed) return null;
|
|
300
|
+
if (isBech32Lnurl(trimmed)) {
|
|
301
|
+
const url = fromBech32Lnurl(trimmed);
|
|
302
|
+
return url && isAllowedServiceUrl(url) ? url : null;
|
|
303
|
+
}
|
|
304
|
+
if (/^(lnurlw|lnurlp|lnurlc|keyauth):\/\//i.test(trimmed)) {
|
|
305
|
+
const url = fromLud17(trimmed);
|
|
306
|
+
return isAllowedServiceUrl(url) ? url : null;
|
|
307
|
+
}
|
|
308
|
+
if (isLightningAddress(trimmed)) return lnAddressToUrl(trimmed);
|
|
309
|
+
if (/^https?:\/\//i.test(trimmed)) {
|
|
310
|
+
return isAllowedServiceUrl(trimmed) ? trimmed : null;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
};
|
|
314
|
+
var noteK1 = (url) => {
|
|
315
|
+
try {
|
|
316
|
+
return new URL(url).searchParams.get("k1")?.toLowerCase() ?? null;
|
|
317
|
+
} catch {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
var requireNoteK1 = (url) => {
|
|
322
|
+
const k1 = noteK1(url);
|
|
323
|
+
if (!k1) {
|
|
324
|
+
throw new Error(
|
|
325
|
+
'This note has no secret in the browser. If it is marked "on device", reconnect the vault before refreshing or spending it.'
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
return k1;
|
|
329
|
+
};
|
|
330
|
+
var noteDeclaredAmount = (url) => {
|
|
331
|
+
try {
|
|
332
|
+
const raw = new URL(url).searchParams.get("amount");
|
|
333
|
+
if (raw === null) return null;
|
|
334
|
+
const n = Number(raw);
|
|
335
|
+
return Number.isFinite(n) ? n : null;
|
|
336
|
+
} catch {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
var noteSignature = (url) => {
|
|
341
|
+
try {
|
|
342
|
+
return new URL(url).searchParams.get("sig");
|
|
343
|
+
} catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
var isValidK1 = (value) => isPreimage(value) || isCk1(value);
|
|
348
|
+
var resolveNoteInput = (value) => {
|
|
349
|
+
const url = resolveLnurlInput(value);
|
|
350
|
+
const k1 = url ? noteK1(url) : null;
|
|
351
|
+
if (!url || !k1 || !isValidK1(k1)) return null;
|
|
352
|
+
return url;
|
|
353
|
+
};
|
|
354
|
+
var isValidNoteInput = (value) => resolveNoteInput(value) !== null;
|
|
355
|
+
var buildNoteUrl = (withdrawLink, k1, amountMsat) => {
|
|
356
|
+
const url = new URL(fromLud17(withdrawLink.trim()));
|
|
357
|
+
url.searchParams.set("k1", k1.trim().toLowerCase());
|
|
358
|
+
if (amountMsat !== void 0) {
|
|
359
|
+
url.searchParams.set("amount", String(amountMsat));
|
|
360
|
+
}
|
|
361
|
+
return url.toString();
|
|
362
|
+
};
|
|
363
|
+
var withNewK1 = (url, k1, amountMsat, signature) => {
|
|
364
|
+
const newUrl = new URL(url);
|
|
365
|
+
newUrl.searchParams.set("k1", k1);
|
|
366
|
+
newUrl.searchParams.set("amount", String(amountMsat));
|
|
367
|
+
if (signature) newUrl.searchParams.set("sig", signature);
|
|
368
|
+
else newUrl.searchParams.delete("sig");
|
|
369
|
+
return newUrl.toString();
|
|
370
|
+
};
|
|
371
|
+
var withoutK1 = (url, amountMsat, signature) => {
|
|
372
|
+
const newUrl = new URL(url);
|
|
373
|
+
newUrl.searchParams.delete("k1");
|
|
374
|
+
newUrl.searchParams.set("amount", String(amountMsat));
|
|
375
|
+
if (signature) newUrl.searchParams.set("sig", signature);
|
|
376
|
+
else newUrl.searchParams.delete("sig");
|
|
377
|
+
return newUrl.toString();
|
|
378
|
+
};
|
|
379
|
+
var withoutSignature = (url) => {
|
|
380
|
+
const newUrl = new URL(url);
|
|
381
|
+
newUrl.searchParams.delete("sig");
|
|
382
|
+
return newUrl.toString();
|
|
383
|
+
};
|
|
384
|
+
var serverOf = (url) => {
|
|
385
|
+
try {
|
|
386
|
+
return new URL(url).host;
|
|
387
|
+
} catch {
|
|
388
|
+
return url;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
var serviceOriginOf = (value) => {
|
|
392
|
+
const expanded = fromLud17(value.trim());
|
|
393
|
+
try {
|
|
394
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(expanded) && !/^https?:\/\//i.test(expanded)) {
|
|
395
|
+
return "";
|
|
396
|
+
}
|
|
397
|
+
const candidate = /^https?:\/\//i.test(expanded) ? expanded : `${defaultSchemeFor(expanded)}://${expanded}`;
|
|
398
|
+
return isAllowedServiceUrl(candidate) ? new URL(candidate).origin : "";
|
|
399
|
+
} catch {
|
|
400
|
+
return "";
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
var noteEndpointOf = (url) => {
|
|
404
|
+
try {
|
|
405
|
+
const parsed = new URL(fromLud17(url.trim()));
|
|
406
|
+
const path = parsed.pathname === "/" ? "" : parsed.pathname.replace(/\/+$/, "");
|
|
407
|
+
return `${parsed.host}${path}`;
|
|
408
|
+
} catch {
|
|
409
|
+
return serverOf(url);
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
var MINT_PUBKEY_PATTERN = /^0[23][0-9a-f]{64}$/i;
|
|
413
|
+
var NOTE_SIGNATURE_PATTERN = /^[0-9a-f]{130}$/i;
|
|
414
|
+
var parseMintKey = (body) => {
|
|
415
|
+
if (typeof body?.mintPubkey !== "string" || !MINT_PUBKEY_PATTERN.test(body.mintPubkey)) {
|
|
416
|
+
throw new Error(
|
|
417
|
+
"SERVICE did not publish a valid persistent signing key (mintPubkey)."
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return { mintPubkey: body.mintPubkey.toLowerCase() };
|
|
421
|
+
};
|
|
422
|
+
var hashK1 = (k1) => bytesToHex(sha256(hexToBytes(k1)));
|
|
423
|
+
var LIGHTNING_SIGNED_MESSAGE_PREFIX = utf8ToBytes("Lightning Signed Message:");
|
|
424
|
+
var noteSignatureDigestForHash = (h, amountMsat) => {
|
|
425
|
+
if (!/^[0-9a-fA-F]{64}$/.test(h.trim())) {
|
|
426
|
+
throw new Error("A note hash must be 32 bytes of hex.");
|
|
427
|
+
}
|
|
428
|
+
const message = utf8ToBytes(
|
|
429
|
+
`LNURLcash:${amountMsat}:${h.trim().toLowerCase()}`
|
|
430
|
+
);
|
|
431
|
+
return sha256(
|
|
432
|
+
sha256(new Uint8Array([...LIGHTNING_SIGNED_MESSAGE_PREFIX, ...message]))
|
|
433
|
+
);
|
|
434
|
+
};
|
|
435
|
+
var noteSignatureDigest = (k1, amountMsat) => noteSignatureDigestForHash(hashK1(k1), amountMsat);
|
|
436
|
+
var normalizeSignatureHex = (signature) => {
|
|
437
|
+
if (NOTE_SIGNATURE_PATTERN.test(signature)) return signature.toLowerCase();
|
|
438
|
+
const decoded = decodeAnyCs1(signature);
|
|
439
|
+
return decoded ? bytesToHex(decoded) : null;
|
|
440
|
+
};
|
|
441
|
+
var verifyNoteSignatureDigest = (digest, signature, mintPubkeyHex) => {
|
|
442
|
+
const signatureHex = normalizeSignatureHex(signature);
|
|
443
|
+
if (!signatureHex) return false;
|
|
444
|
+
let wireSig;
|
|
445
|
+
try {
|
|
446
|
+
wireSig = hexToBytes(signatureHex);
|
|
447
|
+
} catch {
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
if (wireSig.length !== 65) return false;
|
|
451
|
+
const target = mintPubkeyHex.toLowerCase();
|
|
452
|
+
const trailing = new Uint8Array([wireSig[64], ...wireSig.subarray(0, 64)]);
|
|
453
|
+
const leading = wireSig;
|
|
454
|
+
for (const candidate of [trailing, leading]) {
|
|
455
|
+
try {
|
|
456
|
+
const recovered = secp256k1.recoverPublicKey(candidate, digest, {
|
|
457
|
+
prehash: false
|
|
458
|
+
});
|
|
459
|
+
if (bytesToHex(recovered) === target) return true;
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
return false;
|
|
464
|
+
};
|
|
465
|
+
var verifyNoteSignature = (k1, amountMsat, signatureHex, mintPubkeyHex) => {
|
|
466
|
+
if (isCk1(k1)) {
|
|
467
|
+
const signatureBytes = decodeCk1(k1);
|
|
468
|
+
const pubkey = signatureBytes ? recoverNoteOwnershipPubkey(signatureBytes) : null;
|
|
469
|
+
return pubkey ? verifyNoteSignatureHash(
|
|
470
|
+
bytesToHex(pubkey),
|
|
471
|
+
amountMsat,
|
|
472
|
+
signatureHex,
|
|
473
|
+
mintPubkeyHex
|
|
474
|
+
) : false;
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
return verifyNoteSignatureDigest(
|
|
478
|
+
noteSignatureDigest(k1, amountMsat),
|
|
479
|
+
signatureHex,
|
|
480
|
+
mintPubkeyHex
|
|
481
|
+
);
|
|
482
|
+
} catch {
|
|
483
|
+
return false;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
var verifyNoteSignatureHash = (h, amountMsat, signatureHex, mintPubkeyHex) => {
|
|
487
|
+
try {
|
|
488
|
+
return verifyNoteSignatureDigest(
|
|
489
|
+
noteSignatureDigestForHash(h, amountMsat),
|
|
490
|
+
signatureHex,
|
|
491
|
+
mintPubkeyHex
|
|
492
|
+
);
|
|
493
|
+
} catch {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
var NOTE_OWNERSHIP_MESSAGE = utf8ToBytes("LNURLcash");
|
|
498
|
+
var noteOwnershipDigest = () => sha256(
|
|
499
|
+
sha256(
|
|
500
|
+
new Uint8Array([
|
|
501
|
+
...LIGHTNING_SIGNED_MESSAGE_PREFIX,
|
|
502
|
+
...NOTE_OWNERSHIP_MESSAGE
|
|
503
|
+
])
|
|
504
|
+
)
|
|
505
|
+
);
|
|
506
|
+
var signNoteOwnership = (secretKey) => {
|
|
507
|
+
const libSig = secp256k1.sign(noteOwnershipDigest(), secretKey, {
|
|
508
|
+
format: "recovered",
|
|
509
|
+
prehash: false
|
|
510
|
+
});
|
|
511
|
+
return new Uint8Array([...libSig.subarray(1), libSig[0]]);
|
|
512
|
+
};
|
|
513
|
+
var recoverNoteOwnershipPubkey = (signature) => {
|
|
514
|
+
if (signature.length !== 65) return null;
|
|
515
|
+
try {
|
|
516
|
+
const recidLeading = new Uint8Array([
|
|
517
|
+
signature[64],
|
|
518
|
+
...signature.subarray(0, 64)
|
|
519
|
+
]);
|
|
520
|
+
const recovered = secp256k1.recoverPublicKey(
|
|
521
|
+
recidLeading,
|
|
522
|
+
noteOwnershipDigest(),
|
|
523
|
+
{ prehash: false }
|
|
524
|
+
);
|
|
525
|
+
return recovered.subarray(1);
|
|
526
|
+
} catch {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
var addressProofDigest = (action, username) => {
|
|
531
|
+
const message = utf8ToBytes(`LNURLcash:${action}:${username}`);
|
|
532
|
+
return sha256(
|
|
533
|
+
sha256(new Uint8Array([...LIGHTNING_SIGNED_MESSAGE_PREFIX, ...message]))
|
|
534
|
+
);
|
|
535
|
+
};
|
|
536
|
+
var signAddressProof = (branchIndexZeroSecretKey, action, username) => {
|
|
537
|
+
const libSig = secp256k1.sign(
|
|
538
|
+
addressProofDigest(action, username),
|
|
539
|
+
branchIndexZeroSecretKey,
|
|
540
|
+
{ format: "recovered", prehash: false }
|
|
541
|
+
);
|
|
542
|
+
return new Uint8Array([...libSig.subarray(1), libSig[0]]);
|
|
543
|
+
};
|
|
544
|
+
var cp1FromCk1 = (ck1) => {
|
|
545
|
+
const signature = decodeCk1(ck1);
|
|
546
|
+
const pubkey = signature ? recoverNoteOwnershipPubkey(signature) : null;
|
|
547
|
+
return pubkey ? encodeCp1(pubkey) : null;
|
|
548
|
+
};
|
|
549
|
+
var requireMutationSignature = (body, field) => {
|
|
550
|
+
const signature = body?.[field];
|
|
551
|
+
if (typeof signature === "string") {
|
|
552
|
+
if (NOTE_SIGNATURE_PATTERN.test(signature)) {
|
|
553
|
+
return signature.toLowerCase();
|
|
554
|
+
}
|
|
555
|
+
if (isAnyCs1(signature)) return signature.trim();
|
|
556
|
+
}
|
|
557
|
+
throw new AmbiguousMintError(
|
|
558
|
+
`SERVICE confirmed the mutation without a valid ${field} signature.`
|
|
559
|
+
);
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
// fees.ts
|
|
563
|
+
var MIN_COMMENT_LENGTH_FOR_SECRET = 64;
|
|
564
|
+
var canUseMintComment = (info) => typeof info.commentAllowed === "number" && info.commentAllowed >= MIN_COMMENT_LENGTH_FOR_SECRET;
|
|
565
|
+
var requireMintComment = (info) => {
|
|
566
|
+
if (!canUseMintComment(info)) {
|
|
567
|
+
throw new Error(
|
|
568
|
+
"This mint cannot create current LUD-25 notes because it does not advertise commentAllowed: 64."
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
var parseMintFee = (metadata) => {
|
|
573
|
+
let entries;
|
|
574
|
+
try {
|
|
575
|
+
entries = JSON.parse(metadata);
|
|
576
|
+
} catch {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
if (!Array.isArray(entries)) return null;
|
|
580
|
+
for (const entry of entries) {
|
|
581
|
+
if (!Array.isArray(entry) || entry[0] !== "text/plain") continue;
|
|
582
|
+
const match = typeof entry[1] === "string" && entry[1].match(/^Mint fees:\s*(\d+)\s*,\s*(\d+)\s*$/);
|
|
583
|
+
if (!match) continue;
|
|
584
|
+
const baseFeeMsat = Number(match[1]);
|
|
585
|
+
const feePpm = Number(match[2]);
|
|
586
|
+
if (!Number.isFinite(baseFeeMsat) || !Number.isFinite(feePpm)) continue;
|
|
587
|
+
if (feePpm >= 1e6) continue;
|
|
588
|
+
if (baseFeeMsat === 0 && feePpm === 0) return null;
|
|
589
|
+
return { baseFeeMsat, feePpm };
|
|
590
|
+
}
|
|
591
|
+
return null;
|
|
592
|
+
};
|
|
593
|
+
var applyMintFee = (grossMsat, fee) => {
|
|
594
|
+
const whole = Math.floor(grossMsat / 1e6);
|
|
595
|
+
const rest = grossMsat % 1e6;
|
|
596
|
+
const proportional = whole * fee.feePpm + Math.floor(rest * fee.feePpm / 1e6);
|
|
597
|
+
return Math.max(0, grossMsat - fee.baseFeeMsat - proportional);
|
|
598
|
+
};
|
|
599
|
+
var withinMintFeeBand = (grossMsat, netMsat, fee) => {
|
|
600
|
+
const exactNet = applyMintFee(grossMsat, fee);
|
|
601
|
+
const exactFee = grossMsat - exactNet;
|
|
602
|
+
const roundedNet = Math.max(0, grossMsat - Math.ceil(exactFee / 1e3) * 1e3);
|
|
603
|
+
return netMsat >= roundedNet && netMsat <= exactNet;
|
|
604
|
+
};
|
|
605
|
+
var grossUpForMintFee = (netMsat, fee) => {
|
|
606
|
+
if (netMsat <= 0) return 0;
|
|
607
|
+
let lo = netMsat;
|
|
608
|
+
let hi = netMsat + fee.baseFeeMsat;
|
|
609
|
+
let guard = 0;
|
|
610
|
+
while (applyMintFee(hi, fee) < netMsat && guard++ < 64) hi *= 2;
|
|
611
|
+
while (lo < hi) {
|
|
612
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
613
|
+
if (applyMintFee(mid, fee) < netMsat) lo = mid + 1;
|
|
614
|
+
else hi = mid;
|
|
615
|
+
}
|
|
616
|
+
return lo;
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
// net.ts
|
|
620
|
+
var networkGuard = () => {
|
|
621
|
+
};
|
|
622
|
+
var configureNetworkGuard = (guard) => {
|
|
623
|
+
networkGuard = guard;
|
|
624
|
+
};
|
|
625
|
+
var transport = (url, signal, method) => fetch(url, { signal, method });
|
|
626
|
+
var configureTransport = (next) => {
|
|
627
|
+
transport = next;
|
|
628
|
+
};
|
|
629
|
+
var lnurlFetch = async (url, method = "GET") => {
|
|
630
|
+
networkGuard();
|
|
631
|
+
if (!isAllowedServiceUrl(url.toString())) {
|
|
632
|
+
throw new Error(
|
|
633
|
+
"The service provided a URL this wallet will not fetch (not an allowed https/http address)."
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
let res;
|
|
637
|
+
try {
|
|
638
|
+
res = await transport(url.toString(), AbortSignal.timeout(3e4), method);
|
|
639
|
+
} catch (err) {
|
|
640
|
+
if (err.name === "TimeoutError") {
|
|
641
|
+
throw new AmbiguousMintError(
|
|
642
|
+
"The service took too long to respond - try again later."
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
throw new AmbiguousMintError(
|
|
646
|
+
"Failed to reach the service - it may be offline or not allow cross-origin requests."
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
const body = await res.json().catch(() => {
|
|
650
|
+
throw new AmbiguousMintError("Service returned an invalid response.");
|
|
651
|
+
});
|
|
652
|
+
if (body?.status === "ERROR") {
|
|
653
|
+
throw new ServiceError(typeof body.reason === "string" ? body.reason : "");
|
|
654
|
+
}
|
|
655
|
+
return body;
|
|
656
|
+
};
|
|
657
|
+
var defaultSecretProvider = () => bytesToHex(crypto.getRandomValues(new Uint8Array(32)));
|
|
658
|
+
var secretProvider = defaultSecretProvider;
|
|
659
|
+
var configureSecretProvider = (provider) => {
|
|
660
|
+
secretProvider = provider;
|
|
661
|
+
};
|
|
662
|
+
var generateSecret = (domain) => secretProvider(domain);
|
|
663
|
+
var pubkeySecretProvider = () => null;
|
|
664
|
+
var configurePubkeySecretProvider = (provider) => {
|
|
665
|
+
pubkeySecretProvider = provider;
|
|
666
|
+
};
|
|
667
|
+
var generatePubkeySecret = (domain) => pubkeySecretProvider(domain);
|
|
668
|
+
|
|
669
|
+
// request.ts
|
|
670
|
+
var parseOptionalSig = (value) => {
|
|
671
|
+
if (typeof value !== "string") return void 0;
|
|
672
|
+
if (NOTE_SIGNATURE_PATTERN.test(value)) return value.toLowerCase();
|
|
673
|
+
return isAnyCs1(value) ? value.trim() : void 0;
|
|
674
|
+
};
|
|
675
|
+
var parseNoteLookupBody = (body) => {
|
|
676
|
+
if (body?.tag !== "withdrawRequest" || typeof body.callback !== "string" || typeof body.maxWithdrawable !== "number" || !Number.isFinite(body.maxWithdrawable) || body.maxWithdrawable < 0 || body.minWithdrawable !== void 0 && (typeof body.minWithdrawable !== "number" || !Number.isFinite(body.minWithdrawable) || body.minWithdrawable < 0 || body.minWithdrawable > body.maxWithdrawable)) {
|
|
677
|
+
throw new Error("Not a withdrawRequest (unexpected response).");
|
|
678
|
+
}
|
|
679
|
+
const { sig: _rawSig, ...rest } = body;
|
|
680
|
+
const sig = parseOptionalSig(body.sig);
|
|
681
|
+
return {
|
|
682
|
+
...rest,
|
|
683
|
+
...parseMintKey(body),
|
|
684
|
+
...sig !== void 0 ? { sig } : {}
|
|
685
|
+
};
|
|
686
|
+
};
|
|
687
|
+
var requestNoteInfoByField = async (url, field, value) => {
|
|
688
|
+
const lookupUrl = new URL(url);
|
|
689
|
+
lookupUrl.searchParams.delete("k1");
|
|
690
|
+
lookupUrl.searchParams.delete("amount");
|
|
691
|
+
lookupUrl.searchParams.delete("sig");
|
|
692
|
+
lookupUrl.searchParams.set(field, value);
|
|
693
|
+
const body = await lnurlFetch(lookupUrl);
|
|
694
|
+
if (body.k1 !== void 0) {
|
|
695
|
+
throw new Error("SERVICE returned k1 in a hash-only lookup response.");
|
|
696
|
+
}
|
|
697
|
+
return parseNoteLookupBody(body);
|
|
698
|
+
};
|
|
699
|
+
var requestNoteInfoByHash = async (url, h) => {
|
|
700
|
+
if (!/^[0-9a-f]{64}$/i.test(h)) {
|
|
701
|
+
throw new Error("A note hash must be 32 bytes of hex.");
|
|
702
|
+
}
|
|
703
|
+
return requestNoteInfoByField(url, "h", h.toLowerCase());
|
|
704
|
+
};
|
|
705
|
+
var requestNoteInfoByPubkey = async (url, cp1Value) => {
|
|
706
|
+
if (!isCp1(cp1Value)) {
|
|
707
|
+
throw new Error("A note pubkey must be a valid cp1 value.");
|
|
708
|
+
}
|
|
709
|
+
return requestNoteInfoByField(url, "p", cp1Value);
|
|
710
|
+
};
|
|
711
|
+
var fetchNoteInfoByHash = async (url, h) => {
|
|
712
|
+
try {
|
|
713
|
+
return await requestNoteInfoByHash(url, h);
|
|
714
|
+
} catch (err) {
|
|
715
|
+
throw classifyNoteError(err);
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
var fetchNoteInfoByPubkey = async (url, cp1Value) => {
|
|
719
|
+
try {
|
|
720
|
+
return await requestNoteInfoByPubkey(url, cp1Value);
|
|
721
|
+
} catch (err) {
|
|
722
|
+
throw classifyNoteError(err);
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
var fetchNoteInfo = async (url) => {
|
|
726
|
+
const queried = requireNoteK1(url);
|
|
727
|
+
const rawUrl = new URL(url);
|
|
728
|
+
rawUrl.searchParams.delete("sig");
|
|
729
|
+
if (isCk1(queried)) {
|
|
730
|
+
const signatureBytes = decodeCk1(queried);
|
|
731
|
+
const pubkey = signatureBytes ? recoverNoteOwnershipPubkey(signatureBytes) : null;
|
|
732
|
+
if (!pubkey) {
|
|
733
|
+
throw new Error("This note's ck1 secret is malformed.");
|
|
734
|
+
}
|
|
735
|
+
const info = await fetchNoteInfoByPubkey(
|
|
736
|
+
rawUrl.toString(),
|
|
737
|
+
encodeCp1(pubkey)
|
|
738
|
+
);
|
|
739
|
+
return { ...info, k1: queried };
|
|
740
|
+
}
|
|
741
|
+
try {
|
|
742
|
+
const info = await requestNoteInfoByHash(rawUrl.toString(), hashK1(queried));
|
|
743
|
+
return { ...info, k1: queried };
|
|
744
|
+
} catch (err) {
|
|
745
|
+
const missingK1 = err instanceof ServiceError && /(?:missing|required|specify).{0,40}\bk1\b|\bk1\b.{0,40}(?:missing|required)/i.test(
|
|
746
|
+
err.reason
|
|
747
|
+
);
|
|
748
|
+
if (!missingK1) throw classifyNoteError(err);
|
|
749
|
+
}
|
|
750
|
+
let body;
|
|
751
|
+
try {
|
|
752
|
+
body = await lnurlFetch(rawUrl);
|
|
753
|
+
} catch (fallbackError) {
|
|
754
|
+
throw classifyNoteError(fallbackError);
|
|
755
|
+
}
|
|
756
|
+
if (typeof body.k1 !== "string" || body.k1.toLowerCase() !== queried) {
|
|
757
|
+
throw new Error(
|
|
758
|
+
"Service echoed back a different k1 than queried - the note may have been redeemed elsewhere, or the service isn't spec-compliant."
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
return {
|
|
762
|
+
...parseNoteLookupBody(body),
|
|
763
|
+
k1: queried
|
|
764
|
+
};
|
|
765
|
+
};
|
|
766
|
+
var probeBurnedNote = async (url) => {
|
|
767
|
+
try {
|
|
768
|
+
await fetchNoteInfo(url);
|
|
769
|
+
return "live";
|
|
770
|
+
} catch (err) {
|
|
771
|
+
if (err instanceof NoteSpentError || err instanceof NoteUnknownError) {
|
|
772
|
+
return "gone";
|
|
773
|
+
}
|
|
774
|
+
return "unknown";
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
var fetchMintAddress = async (url) => {
|
|
778
|
+
const body = await lnurlFetch(url);
|
|
779
|
+
if (body?.tag !== "withdrawRequest" || typeof body.callback !== "string" || typeof body.payLink !== "string" || typeof body.maxWithdrawable !== "number") {
|
|
780
|
+
throw new Error("Not a mint address response (unexpected shape).");
|
|
781
|
+
}
|
|
782
|
+
const { mintPubkey, nodeCapacity, outstandingNotesMsat, ...rest } = body;
|
|
783
|
+
const signingKey = parseMintKey({ mintPubkey });
|
|
784
|
+
const nodePubkey = typeof body.nodeUri === "string" && MINT_PUBKEY_PATTERN.test(body.nodeUri.split("@")[0] ?? "") ? body.nodeUri.split("@")[0].toLowerCase() : void 0;
|
|
785
|
+
return {
|
|
786
|
+
...rest,
|
|
787
|
+
...signingKey,
|
|
788
|
+
nodePubkey,
|
|
789
|
+
nodeCapacityMsat: typeof nodeCapacity === "number" ? nodeCapacity : void 0,
|
|
790
|
+
outstandingNotesMsat: typeof outstandingNotesMsat === "number" ? outstandingNotesMsat : void 0
|
|
791
|
+
};
|
|
792
|
+
};
|
|
793
|
+
var callbackRequest = async (callback, params) => {
|
|
794
|
+
const k1s = params.filter(([key]) => key === "k1").map(([, value]) => value);
|
|
795
|
+
if (k1s.length === 0 || k1s.some((k1) => k1.trim() === "")) {
|
|
796
|
+
throw new Error(
|
|
797
|
+
'This note has no secret in the browser. If it is marked "on device", reconnect the vault before refreshing or spending it.'
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
let cbUrl;
|
|
801
|
+
try {
|
|
802
|
+
cbUrl = new URL(callback);
|
|
803
|
+
} catch {
|
|
804
|
+
throw new Error("The service provided an invalid callback URL.");
|
|
805
|
+
}
|
|
806
|
+
for (const [key, value] of params) cbUrl.searchParams.append(key, value);
|
|
807
|
+
let body;
|
|
808
|
+
try {
|
|
809
|
+
body = await lnurlFetch(cbUrl);
|
|
810
|
+
} catch (err) {
|
|
811
|
+
if (err instanceof ServiceError && err.reason === "pending") {
|
|
812
|
+
throw new PendingNoteError();
|
|
813
|
+
}
|
|
814
|
+
if (err instanceof AmbiguousMintError) throw err;
|
|
815
|
+
throw classifyNoteError(err);
|
|
816
|
+
}
|
|
817
|
+
if (body?.status !== "OK") {
|
|
818
|
+
throw new AmbiguousMintError("Operation was not confirmed by the service.");
|
|
819
|
+
}
|
|
820
|
+
return body;
|
|
821
|
+
};
|
|
822
|
+
var meltNote = async (callback, k1, pr) => {
|
|
823
|
+
const body = await callbackRequest(callback, [
|
|
824
|
+
["k1", k1],
|
|
825
|
+
["pr", pr.trim()]
|
|
826
|
+
]);
|
|
827
|
+
return {
|
|
828
|
+
verify: body.verify,
|
|
829
|
+
pr: typeof body.pr === "string" ? body.pr : void 0
|
|
830
|
+
};
|
|
831
|
+
};
|
|
832
|
+
var OUTPUT_FIELD_NAMES = {
|
|
833
|
+
"1": { legacy: "h", current: "p1" },
|
|
834
|
+
"2": { legacy: "h2", current: "p2" }
|
|
835
|
+
};
|
|
836
|
+
var outputFieldName = (value, suffix) => isCp1(value) ? OUTPUT_FIELD_NAMES[suffix].current : OUTPUT_FIELD_NAMES[suffix].legacy;
|
|
837
|
+
var rotateNoteWithHash = async (callback, k1, h) => {
|
|
838
|
+
const body = await callbackRequest(callback, [
|
|
839
|
+
["k1", k1],
|
|
840
|
+
[outputFieldName(h, "1"), h]
|
|
841
|
+
]);
|
|
842
|
+
return { signature: requireMutationSignature(body, "sig") };
|
|
843
|
+
};
|
|
844
|
+
var splitNoteWithHash = async (callback, k1s, amountMsat, h, h2) => {
|
|
845
|
+
const body = await callbackRequest(callback, [
|
|
846
|
+
...k1s.map((k1) => ["k1", k1]),
|
|
847
|
+
["amount", String(amountMsat)],
|
|
848
|
+
[outputFieldName(h, "1"), h],
|
|
849
|
+
[outputFieldName(h2, "2"), h2]
|
|
850
|
+
]);
|
|
851
|
+
return {
|
|
852
|
+
signature: requireMutationSignature(body, "sig"),
|
|
853
|
+
changeSignature: requireMutationSignature(body, "sig2")
|
|
854
|
+
};
|
|
855
|
+
};
|
|
856
|
+
var mergeNotesWithHash = async (callback, k1s, h) => {
|
|
857
|
+
const body = await callbackRequest(callback, [
|
|
858
|
+
...k1s.map((k1) => ["k1", k1]),
|
|
859
|
+
[outputFieldName(h, "1"), h]
|
|
860
|
+
]);
|
|
861
|
+
return { signature: requireMutationSignature(body, "sig") };
|
|
862
|
+
};
|
|
863
|
+
var generateOutputSecret = (domain, preferPubkey) => {
|
|
864
|
+
if (preferPubkey) {
|
|
865
|
+
const pubkeySecret = generatePubkeySecret(domain);
|
|
866
|
+
if (pubkeySecret) return pubkeySecret;
|
|
867
|
+
}
|
|
868
|
+
return generateSecret(domain);
|
|
869
|
+
};
|
|
870
|
+
var disclosedValue = (secret) => {
|
|
871
|
+
if (isCk1(secret)) {
|
|
872
|
+
const cp1 = cp1FromCk1(secret);
|
|
873
|
+
if (!cp1) throw new Error("Generated an invalid pubkey secret.");
|
|
874
|
+
return cp1;
|
|
875
|
+
}
|
|
876
|
+
return hashK1(secret);
|
|
877
|
+
};
|
|
878
|
+
var rotateNote = async (callback, k1) => {
|
|
879
|
+
const newK1 = generateOutputSecret(serverOf(callback), isCk1(k1));
|
|
880
|
+
try {
|
|
881
|
+
const result = await rotateNoteWithHash(callback, k1, disclosedValue(newK1));
|
|
882
|
+
return { k1: newK1, signature: result.signature };
|
|
883
|
+
} catch (err) {
|
|
884
|
+
if (err instanceof AmbiguousMintError) {
|
|
885
|
+
throw new AmbiguousMutationError(err.message, [newK1]);
|
|
886
|
+
}
|
|
887
|
+
throw err;
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
var splitNote = async (callback, k1s, amountMsat) => {
|
|
891
|
+
const domain = serverOf(callback);
|
|
892
|
+
const preferPubkey = k1s.every(isCk1);
|
|
893
|
+
const newK1 = generateOutputSecret(domain, preferPubkey);
|
|
894
|
+
const changeK1 = generateOutputSecret(domain, preferPubkey);
|
|
895
|
+
try {
|
|
896
|
+
const result = await splitNoteWithHash(
|
|
897
|
+
callback,
|
|
898
|
+
k1s,
|
|
899
|
+
amountMsat,
|
|
900
|
+
disclosedValue(newK1),
|
|
901
|
+
disclosedValue(changeK1)
|
|
902
|
+
);
|
|
903
|
+
return {
|
|
904
|
+
k1: newK1,
|
|
905
|
+
signature: result.signature,
|
|
906
|
+
change: changeK1,
|
|
907
|
+
changeSignature: result.changeSignature
|
|
908
|
+
};
|
|
909
|
+
} catch (err) {
|
|
910
|
+
if (err instanceof AmbiguousMintError) {
|
|
911
|
+
throw new AmbiguousMutationError(err.message, [
|
|
912
|
+
newK1,
|
|
913
|
+
changeK1
|
|
914
|
+
]);
|
|
915
|
+
}
|
|
916
|
+
throw err;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
var mergeNotes = async (callback, k1s) => {
|
|
920
|
+
const newK1 = generateOutputSecret(serverOf(callback), k1s.every(isCk1));
|
|
921
|
+
try {
|
|
922
|
+
const result = await mergeNotesWithHash(
|
|
923
|
+
callback,
|
|
924
|
+
k1s,
|
|
925
|
+
disclosedValue(newK1)
|
|
926
|
+
);
|
|
927
|
+
return { k1: newK1, signature: result.signature };
|
|
928
|
+
} catch (err) {
|
|
929
|
+
if (err instanceof AmbiguousMintError) {
|
|
930
|
+
throw new AmbiguousMutationError(err.message, [newK1]);
|
|
931
|
+
}
|
|
932
|
+
throw err;
|
|
933
|
+
}
|
|
934
|
+
};
|
|
935
|
+
var settleNote = async (baseUrl, k1, expectedAmountMsat, signature) => {
|
|
936
|
+
const info = await fetchNoteInfo(
|
|
937
|
+
withNewK1(baseUrl, k1, expectedAmountMsat, signature)
|
|
938
|
+
);
|
|
939
|
+
return {
|
|
940
|
+
k1,
|
|
941
|
+
amountMsat: info.maxWithdrawable,
|
|
942
|
+
signature,
|
|
943
|
+
callback: info.callback
|
|
944
|
+
};
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
// internalTransfer.ts
|
|
948
|
+
var parseInternalTransferHint = (metadata) => {
|
|
949
|
+
let entries;
|
|
950
|
+
try {
|
|
951
|
+
entries = JSON.parse(metadata);
|
|
952
|
+
} catch {
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
if (!Array.isArray(entries)) return null;
|
|
956
|
+
for (const entry of entries) {
|
|
957
|
+
if (!Array.isArray(entry) || entry[0] !== "text/xpub") continue;
|
|
958
|
+
if (typeof entry[1] !== "string") continue;
|
|
959
|
+
const sep = entry[1].lastIndexOf(":");
|
|
960
|
+
if (sep < 0) continue;
|
|
961
|
+
const cx1 = decodeCx1(entry[1].slice(0, sep));
|
|
962
|
+
const startIndex = Number(entry[1].slice(sep + 1));
|
|
963
|
+
if (!cx1 || !Number.isInteger(startIndex) || startIndex < 0) continue;
|
|
964
|
+
return { cx1, startIndex };
|
|
965
|
+
}
|
|
966
|
+
return null;
|
|
967
|
+
};
|
|
968
|
+
var isIndexInUse = (err) => err instanceof ServiceError && /already in use/i.test(err.reason);
|
|
969
|
+
var MAX_INDEX_ATTEMPTS = 25;
|
|
970
|
+
var payInternalTransfer = async (callback, k1s, amountMsat, totalInputMsat, hint) => {
|
|
971
|
+
if (amountMsat > totalInputMsat) {
|
|
972
|
+
throw new Error("Cannot pay more than the selected notes are worth.");
|
|
973
|
+
}
|
|
974
|
+
const domain = serverOf(callback);
|
|
975
|
+
const preferPubkey = k1s.every(isCk1);
|
|
976
|
+
let index = hint.startIndex;
|
|
977
|
+
for (let attempt = 0; attempt < MAX_INDEX_ATTEMPTS; attempt++) {
|
|
978
|
+
const recipientOutput = encodeCp1(
|
|
979
|
+
deriveNotePubkey(hint.cx1.pubkeyXOnly, hint.cx1.chainCode, index)
|
|
980
|
+
);
|
|
981
|
+
if (amountMsat === totalInputMsat) {
|
|
982
|
+
try {
|
|
983
|
+
const { signature } = await mergeNotesWithHash(
|
|
984
|
+
callback,
|
|
985
|
+
k1s,
|
|
986
|
+
recipientOutput
|
|
987
|
+
);
|
|
988
|
+
return { kind: "merge", index, signature };
|
|
989
|
+
} catch (err) {
|
|
990
|
+
if (isIndexInUse(err)) {
|
|
991
|
+
index++;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
throw err;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
const changeK1 = generateOutputSecret(domain, preferPubkey);
|
|
998
|
+
try {
|
|
999
|
+
const result = await splitNoteWithHash(
|
|
1000
|
+
callback,
|
|
1001
|
+
k1s,
|
|
1002
|
+
amountMsat,
|
|
1003
|
+
recipientOutput,
|
|
1004
|
+
disclosedValue(changeK1)
|
|
1005
|
+
);
|
|
1006
|
+
return {
|
|
1007
|
+
kind: "split",
|
|
1008
|
+
index,
|
|
1009
|
+
signature: result.signature,
|
|
1010
|
+
change: changeK1,
|
|
1011
|
+
changeSignature: result.changeSignature
|
|
1012
|
+
};
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
if (isIndexInUse(err)) {
|
|
1015
|
+
index++;
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
if (err instanceof AmbiguousMintError) {
|
|
1019
|
+
throw new AmbiguousMutationError(err.message, [changeK1]);
|
|
1020
|
+
}
|
|
1021
|
+
throw err;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
throw new Error(
|
|
1025
|
+
`${domain} kept reporting every index tried as already in use - could not complete the internal transfer.`
|
|
1026
|
+
);
|
|
1027
|
+
};
|
|
1028
|
+
var fetchPayRequest = async (url) => {
|
|
1029
|
+
const body = await lnurlFetch(url);
|
|
1030
|
+
if (body?.tag !== "payRequest" || typeof body.callback !== "string") {
|
|
1031
|
+
throw new Error("Not a payRequest (unexpected response).");
|
|
1032
|
+
}
|
|
1033
|
+
const mintFee = typeof body.metadata === "string" ? parseMintFee(body.metadata) : null;
|
|
1034
|
+
const internalTransfer = typeof body.metadata === "string" ? parseInternalTransferHint(body.metadata) : null;
|
|
1035
|
+
return {
|
|
1036
|
+
...body,
|
|
1037
|
+
mintFee: mintFee ?? void 0,
|
|
1038
|
+
internalTransfer: internalTransfer ?? void 0,
|
|
1039
|
+
mintToHash: body.mintToHash === true,
|
|
1040
|
+
commentAllowed: typeof body.commentAllowed === "number" ? body.commentAllowed : void 0
|
|
1041
|
+
};
|
|
1042
|
+
};
|
|
1043
|
+
var normalizeNoteId = (value) => {
|
|
1044
|
+
const trimmed = value.trim().toLowerCase();
|
|
1045
|
+
if (/^[0-9a-f]{64}$/.test(trimmed)) return trimmed;
|
|
1046
|
+
const decoded = decodeCp1(trimmed);
|
|
1047
|
+
return decoded ? bytesToHex(decoded) : null;
|
|
1048
|
+
};
|
|
1049
|
+
var normalizeSignature = (value) => {
|
|
1050
|
+
if (/^[0-9a-f]{130}$/i.test(value)) return value.toLowerCase();
|
|
1051
|
+
const decoded = decodeAnyCs1(value);
|
|
1052
|
+
return decoded ? bytesToHex(decoded) : null;
|
|
1053
|
+
};
|
|
1054
|
+
var parseBoundMintCommitment = (value) => {
|
|
1055
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1056
|
+
const raw = value;
|
|
1057
|
+
if (typeof raw.h !== "string") return void 0;
|
|
1058
|
+
const h = normalizeNoteId(raw.h);
|
|
1059
|
+
if (h === null || typeof raw.amount !== "number" || !Number.isSafeInteger(raw.amount) || raw.amount <= 0 || raw.sig !== void 0 && typeof raw.sig !== "string") {
|
|
1060
|
+
return void 0;
|
|
1061
|
+
}
|
|
1062
|
+
let signature;
|
|
1063
|
+
if (typeof raw.sig === "string") {
|
|
1064
|
+
const normalized = normalizeSignature(raw.sig);
|
|
1065
|
+
if (normalized === null) return void 0;
|
|
1066
|
+
signature = normalized;
|
|
1067
|
+
}
|
|
1068
|
+
return {
|
|
1069
|
+
h,
|
|
1070
|
+
amountMsat: raw.amount,
|
|
1071
|
+
...signature !== void 0 ? { signature } : {}
|
|
1072
|
+
};
|
|
1073
|
+
};
|
|
1074
|
+
var requestInvoice = async (payCallback, amountMsat, outputHash) => {
|
|
1075
|
+
const cbUrl = new URL(payCallback);
|
|
1076
|
+
cbUrl.searchParams.set("amount", String(amountMsat));
|
|
1077
|
+
if (outputHash !== void 0) {
|
|
1078
|
+
const value = outputHash.trim().toLowerCase();
|
|
1079
|
+
if (isCp1(value)) {
|
|
1080
|
+
cbUrl.searchParams.set("comment", value);
|
|
1081
|
+
} else if (isPreimage(value)) {
|
|
1082
|
+
cbUrl.searchParams.set("comment", value);
|
|
1083
|
+
cbUrl.searchParams.set("h", value);
|
|
1084
|
+
} else {
|
|
1085
|
+
throw new Error(
|
|
1086
|
+
"An output hash must be 32 bytes of hex, or a cp1 pubkey - no invoice was requested."
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
const body = await lnurlFetch(cbUrl);
|
|
1091
|
+
if (typeof body?.pr !== "string") {
|
|
1092
|
+
throw new Error("Service did not return an invoice.");
|
|
1093
|
+
}
|
|
1094
|
+
const invoiceMsat = decodeBolt11AmountMsat(body.pr);
|
|
1095
|
+
if (invoiceMsat !== null && invoiceMsat !== amountMsat) {
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
`Service returned an invoice for ${invoiceMsat} msat, not the ${amountMsat} requested.`
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
return {
|
|
1101
|
+
pr: body.pr,
|
|
1102
|
+
verify: typeof body.verify === "string" ? body.verify : void 0,
|
|
1103
|
+
disposable: body.disposable !== false,
|
|
1104
|
+
mintToHash: body.mintToHash === true,
|
|
1105
|
+
mint: parseBoundMintCommitment(body.mint)
|
|
1106
|
+
};
|
|
1107
|
+
};
|
|
1108
|
+
var fetchInvoiceVerification = async (verifyUrl) => {
|
|
1109
|
+
const body = await lnurlFetch(verifyUrl);
|
|
1110
|
+
if (typeof body?.settled !== "boolean" || typeof body?.pr !== "string") {
|
|
1111
|
+
throw new Error("Service returned an unexpected verify response.");
|
|
1112
|
+
}
|
|
1113
|
+
return {
|
|
1114
|
+
settled: body.settled,
|
|
1115
|
+
preimage: typeof body.preimage === "string" ? body.preimage : null,
|
|
1116
|
+
pr: body.pr,
|
|
1117
|
+
mint: parseBoundMintCommitment(body.mint)
|
|
1118
|
+
};
|
|
1119
|
+
};
|
|
1120
|
+
var requireBoundMintQuote = (invoice, expectedH, grossMsat, fee) => {
|
|
1121
|
+
const h = normalizeNoteId(expectedH);
|
|
1122
|
+
if (h === null) throw new Error("The expected mint output is malformed.");
|
|
1123
|
+
const commitment = invoice.mint;
|
|
1124
|
+
if (!invoice.mintToHash || !invoice.verify || !commitment) {
|
|
1125
|
+
throw new Error(
|
|
1126
|
+
"The mint did not offer an authenticated device-bound receipt for this quote."
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1129
|
+
if (commitment.h !== h) {
|
|
1130
|
+
throw new Error("The mint committed the quote to a different output.");
|
|
1131
|
+
}
|
|
1132
|
+
const amountAccepted = fee ? withinMintFeeBand(grossMsat, commitment.amountMsat, fee) : commitment.amountMsat === grossMsat;
|
|
1133
|
+
if (!amountAccepted) {
|
|
1134
|
+
throw new Error(
|
|
1135
|
+
"The mint committed the quote to an unexpected note amount."
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
if (commitment.signature !== void 0) {
|
|
1139
|
+
throw new Error("The mint signed an output before its invoice settled.");
|
|
1140
|
+
}
|
|
1141
|
+
return commitment;
|
|
1142
|
+
};
|
|
1143
|
+
var validateBoundMintReceipt = (invoice, verification, expectedH, expectedAmountMsat, mintPubkey) => {
|
|
1144
|
+
if (!verification.settled) throw new Error("The invoice has not settled.");
|
|
1145
|
+
if (!sameInvoice(invoice.pr, verification.pr)) {
|
|
1146
|
+
throw new Error("The settlement receipt names a different invoice.");
|
|
1147
|
+
}
|
|
1148
|
+
const expectedNormalized = normalizeNoteId(expectedH);
|
|
1149
|
+
const receipt = verification.mint;
|
|
1150
|
+
if (!receipt || expectedNormalized === null || receipt.h !== expectedNormalized || receipt.amountMsat !== expectedAmountMsat) {
|
|
1151
|
+
throw new Error("The settlement receipt does not match the mint quote.");
|
|
1152
|
+
}
|
|
1153
|
+
if (!receipt.signature || !verifyNoteSignatureHash(
|
|
1154
|
+
receipt.h,
|
|
1155
|
+
receipt.amountMsat,
|
|
1156
|
+
receipt.signature,
|
|
1157
|
+
mintPubkey
|
|
1158
|
+
)) {
|
|
1159
|
+
throw new Error("The settled mint receipt has an invalid signature.");
|
|
1160
|
+
}
|
|
1161
|
+
return { ...receipt, signature: receipt.signature };
|
|
1162
|
+
};
|
|
1163
|
+
var addressProofUrl = (server, username, indexZeroSecretKey, action) => {
|
|
1164
|
+
const url = new URL(`/p/${encodeURIComponent(username)}`, server);
|
|
1165
|
+
url.searchParams.set(
|
|
1166
|
+
"sig",
|
|
1167
|
+
bytesToHex(signAddressProof(indexZeroSecretKey, action, username))
|
|
1168
|
+
);
|
|
1169
|
+
return url;
|
|
1170
|
+
};
|
|
1171
|
+
var registerUsername = async (server, username, cx1, indexZeroSecretKey) => {
|
|
1172
|
+
const url = addressProofUrl(server, username, indexZeroSecretKey, "register");
|
|
1173
|
+
url.searchParams.set("cx1", cx1);
|
|
1174
|
+
const body = await lnurlFetch(url, "POST");
|
|
1175
|
+
if (body?.status !== "OK") {
|
|
1176
|
+
throw new Error("SERVICE did not confirm the registration.");
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
var unregisterUsername = async (server, username, indexZeroSecretKey) => {
|
|
1180
|
+
const url = addressProofUrl(
|
|
1181
|
+
server,
|
|
1182
|
+
username,
|
|
1183
|
+
indexZeroSecretKey,
|
|
1184
|
+
"unregister"
|
|
1185
|
+
);
|
|
1186
|
+
const body = await lnurlFetch(url, "DELETE");
|
|
1187
|
+
if (body?.status !== "OK") {
|
|
1188
|
+
throw new Error("SERVICE did not confirm the unregistration.");
|
|
1189
|
+
}
|
|
1190
|
+
};
|
|
1191
|
+
var DEFAULT_GAP_LIMIT = 20;
|
|
1192
|
+
var DEFAULT_RATE_LIMIT_BACKOFF_MS = 2e3;
|
|
1193
|
+
var isRateLimited = (err) => err instanceof ServiceError && /rate.?limit/i.test(err.reason);
|
|
1194
|
+
var scanForAddressNotes = async (withdrawUrl, branch, opts = {}) => {
|
|
1195
|
+
const gapLimit = opts.gapLimit ?? DEFAULT_GAP_LIMIT;
|
|
1196
|
+
const backoffMs = opts.rateLimitBackoffMs ?? DEFAULT_RATE_LIMIT_BACKOFF_MS;
|
|
1197
|
+
const found = [];
|
|
1198
|
+
let consecutiveUnknown = 0;
|
|
1199
|
+
let index = opts.startIndex ?? 0;
|
|
1200
|
+
while (consecutiveUnknown < gapLimit) {
|
|
1201
|
+
const pubkey = deriveNotePubkey(branch.pubkeyXOnly, branch.chainCode, index);
|
|
1202
|
+
const cp1 = encodeCp1(pubkey);
|
|
1203
|
+
try {
|
|
1204
|
+
const info = await fetchNoteInfoByPubkey(withdrawUrl, cp1);
|
|
1205
|
+
const result = { index, cp1, info };
|
|
1206
|
+
found.push(result);
|
|
1207
|
+
opts.onFound?.(result);
|
|
1208
|
+
consecutiveUnknown = 0;
|
|
1209
|
+
index++;
|
|
1210
|
+
} catch (err) {
|
|
1211
|
+
if (isRateLimited(err)) {
|
|
1212
|
+
await new Promise((resolve) => setTimeout(resolve, backoffMs));
|
|
1213
|
+
continue;
|
|
1214
|
+
}
|
|
1215
|
+
if (err instanceof NoteUnknownError) {
|
|
1216
|
+
consecutiveUnknown++;
|
|
1217
|
+
index++;
|
|
1218
|
+
continue;
|
|
1219
|
+
}
|
|
1220
|
+
if (err instanceof NoteSpentError) {
|
|
1221
|
+
consecutiveUnknown = 0;
|
|
1222
|
+
index++;
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
throw classifyNoteError(err);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return found;
|
|
1229
|
+
};
|
|
1230
|
+
|
|
1231
|
+
export { AmbiguousMintError, AmbiguousMutationError, MINT_PUBKEY_PATTERN, MIN_COMMENT_LENGTH_FOR_SECRET, NOTE_SIGNATURE_PATTERN, NoteSpentError, NoteUnknownError, PendingNoteError, ServiceError, applyMintFee, buildNoteUrl, canUseMintComment, classifyNoteError, configureNetworkGuard, configurePubkeySecretProvider, configureSecretProvider, configureTransport, cp1FromCk1, decodeAnyCs1, decodeBolt11AmountMsat, decodeBolt11AmountSuffix, decodeBolt11PaymentHash, decodeCk1, decodeCp1, decodeCs1, decodeCs1WithAmount, decodeCx1, defaultSchemeFor, deriveNotePubkey, deriveNoteSecretKey, disclosedValue, encodeBolt11AmountSuffix, encodeCk1, encodeCp1, encodeCs1, encodeCs1WithAmount, encodeCx1, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchNoteInfoByHash, fetchNoteInfoByPubkey, fetchPayRequest, fromBech32Lnurl, fromLud17, generateOutputSecret, grossUpForMintFee, hashK1, isAllowedServiceUrl, isAnyCs1, isBech32Lnurl, isBolt11Invoice, isCk1, isCp1, isCs1, isCs1WithAmount, isCx1, isLightningAddress, isPreimage, isValidK1, isValidNoteInput, lightningAddressUsername, lnurlFetch, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, noteDeclaredAmount, noteEndpointOf, noteK1, noteSignature, parseInternalTransferHint, parseMintFee, parseMintKey, payInternalTransfer, probeBurnedNote, recoverNoteOwnershipPubkey, registerUsername, requestInvoice, requireBoundMintQuote, requireMintComment, requireMutationSignature, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, rotateNote, rotateNoteWithHash, sameInvoice, scanForAddressNotes, serverOf, serviceOriginOf, settleNote, signAddressProof, signNoteOwnership, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, unregisterUsername, validateBoundMintReceipt, verifyMeltPreimage, verifyNoteSignature, verifyNoteSignatureHash, withNewK1, withinMintFeeBand, withoutK1, withoutSignature };
|