lnurlcash-kit 0.1.1 → 0.2.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 +345 -0
- package/README.md +318 -0
- package/dist/index.d.ts +174 -68
- package/dist/index.js +515 -122
- package/llms.txt +67 -6
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { bech32 } from '@scure/base';
|
|
1
|
+
import { bech32, base64urlnopad } from '@scure/base';
|
|
2
|
+
import { hmac } from '@noble/hashes/hmac.js';
|
|
2
3
|
import { sha256 } from '@noble/hashes/sha2.js';
|
|
3
4
|
import { utf8ToBytes, bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
4
5
|
import { secp256k1 } from '@noble/curves/secp256k1.js';
|
|
@@ -122,6 +123,23 @@ var serverOf = (url) => {
|
|
|
122
123
|
var hashK1 = (k1) => bytesToHex(sha256(hexToBytes(k1)));
|
|
123
124
|
var defaultRandomSecret = () => bytesToHex(crypto.getRandomValues(new Uint8Array(32)));
|
|
124
125
|
var isPreimage = (value) => /^[0-9a-fA-F]{64}$/.test(value.trim());
|
|
126
|
+
var NOTE_DERIVATION_DOMAIN = utf8ToBytes("lnurlcash-note-v1");
|
|
127
|
+
var deriveNoteRoot = (seed) => hmac(sha256, NOTE_DERIVATION_DOMAIN, seed);
|
|
128
|
+
var requireIndex = (index) => {
|
|
129
|
+
if (!Number.isSafeInteger(index) || index < 0) {
|
|
130
|
+
throw new RangeError(
|
|
131
|
+
`A note index must be a non-negative integer, not ${index}.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return index;
|
|
135
|
+
};
|
|
136
|
+
var deriveNoteSecret = (root, host, index) => bytesToHex(hmac(sha256, root, utf8ToBytes(`${host}:${requireIndex(index)}`)));
|
|
137
|
+
var derivedSecretSource = (root, host, start = 0) => {
|
|
138
|
+
let next = requireIndex(start);
|
|
139
|
+
const source = (() => deriveNoteSecret(root, host, next++));
|
|
140
|
+
source.index = () => next;
|
|
141
|
+
return source;
|
|
142
|
+
};
|
|
125
143
|
|
|
126
144
|
// src/note.ts
|
|
127
145
|
var noteK1 = (url) => {
|
|
@@ -188,113 +206,6 @@ var withoutK1 = (url, amountMsat, signature) => {
|
|
|
188
206
|
else newUrl.searchParams.delete("sig");
|
|
189
207
|
return newUrl.toString();
|
|
190
208
|
};
|
|
191
|
-
var LIGHTNING_SIGNED_MESSAGE_PREFIX = utf8ToBytes("Lightning Signed Message:");
|
|
192
|
-
var noteSignatureMessage = (k1, amountMsat) => `LNURLcash:${amountMsat}:${hashK1(k1)}`;
|
|
193
|
-
var noteSignatureDigest = (k1, amountMsat) => sha256(
|
|
194
|
-
sha256(
|
|
195
|
-
new Uint8Array([
|
|
196
|
-
...LIGHTNING_SIGNED_MESSAGE_PREFIX,
|
|
197
|
-
...utf8ToBytes(noteSignatureMessage(k1, amountMsat))
|
|
198
|
-
])
|
|
199
|
-
)
|
|
200
|
-
);
|
|
201
|
-
var verifyNoteSignature = (k1, amountMsat, signatureHex, mintPubkeyHex) => {
|
|
202
|
-
let wireSig;
|
|
203
|
-
try {
|
|
204
|
-
wireSig = hexToBytes(signatureHex);
|
|
205
|
-
} catch {
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
if (wireSig.length !== 65) return false;
|
|
209
|
-
let digest;
|
|
210
|
-
try {
|
|
211
|
-
digest = noteSignatureDigest(k1, amountMsat);
|
|
212
|
-
} catch {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
const target = mintPubkeyHex.trim().toLowerCase();
|
|
216
|
-
const recoveryIdFirst = new Uint8Array([
|
|
217
|
-
wireSig[64],
|
|
218
|
-
...wireSig.subarray(0, 64)
|
|
219
|
-
]);
|
|
220
|
-
for (const candidate of [recoveryIdFirst, wireSig]) {
|
|
221
|
-
try {
|
|
222
|
-
const recovered = secp256k1.recoverPublicKey(candidate, digest, {
|
|
223
|
-
prehash: false
|
|
224
|
-
});
|
|
225
|
-
if (bytesToHex(recovered) === target) return true;
|
|
226
|
-
} catch {
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
return false;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
// src/fees.ts
|
|
233
|
-
var parseMintFee = (metadata) => {
|
|
234
|
-
let entries;
|
|
235
|
-
try {
|
|
236
|
-
entries = JSON.parse(metadata);
|
|
237
|
-
} catch {
|
|
238
|
-
return null;
|
|
239
|
-
}
|
|
240
|
-
if (!Array.isArray(entries)) return null;
|
|
241
|
-
for (const entry of entries) {
|
|
242
|
-
if (!Array.isArray(entry) || entry[0] !== "text/plain") continue;
|
|
243
|
-
const match = typeof entry[1] === "string" && entry[1].match(/^Mint fees:\s*(\d+)\s*,\s*(\d+)\s*$/);
|
|
244
|
-
if (!match) continue;
|
|
245
|
-
const baseFeeMsat = Number(match[1]);
|
|
246
|
-
const feePpm = Number(match[2]);
|
|
247
|
-
if (!Number.isSafeInteger(baseFeeMsat) || !Number.isSafeInteger(feePpm)) continue;
|
|
248
|
-
if (feePpm >= 1e6) continue;
|
|
249
|
-
if (baseFeeMsat === 0 && feePpm === 0) return null;
|
|
250
|
-
return { baseFeeMsat, feePpm };
|
|
251
|
-
}
|
|
252
|
-
return null;
|
|
253
|
-
};
|
|
254
|
-
var proportionalFee = (grossMsat, feePpm) => Math.floor(grossMsat / 1e6) * feePpm + Math.floor(grossMsat % 1e6 * feePpm / 1e6);
|
|
255
|
-
var applyMintFee = (grossMsat, fee) => Math.max(0, grossMsat - fee.baseFeeMsat - proportionalFee(grossMsat, fee.feePpm));
|
|
256
|
-
var grossUpForMintFee = (netMsat, fee) => {
|
|
257
|
-
if (netMsat <= 0) return 0;
|
|
258
|
-
let hi = netMsat + fee.baseFeeMsat;
|
|
259
|
-
while (applyMintFee(hi, fee) < netMsat) hi *= 2;
|
|
260
|
-
let lo = 0;
|
|
261
|
-
while (lo < hi) {
|
|
262
|
-
const mid = Math.floor((lo + hi) / 2);
|
|
263
|
-
if (applyMintFee(mid, fee) >= netMsat) hi = mid;
|
|
264
|
-
else lo = mid + 1;
|
|
265
|
-
}
|
|
266
|
-
return lo;
|
|
267
|
-
};
|
|
268
|
-
var formatFeePercent = (ppm) => (ppm / 1e4).toFixed(4).replace(/\.?0+$/, "");
|
|
269
|
-
var describeMintFee = (fee) => [
|
|
270
|
-
fee.baseFeeMsat > 0 ? `${Math.round(fee.baseFeeMsat / 1e3)} sat flat` : null,
|
|
271
|
-
fee.feePpm > 0 ? `${formatFeePercent(fee.feePpm)}% of the amount paid` : null
|
|
272
|
-
].filter(Boolean).join(" + ");
|
|
273
|
-
|
|
274
|
-
// src/bolt11.ts
|
|
275
|
-
var isBolt11Invoice = (value) => /^ln(bc|tb|bcrt|tbs|sb)[0-9]*[munp]?1[a-z0-9]+$/.test(
|
|
276
|
-
value.trim().toLowerCase()
|
|
277
|
-
);
|
|
278
|
-
var sameInvoice = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
279
|
-
var BOLT11_AMOUNT_MSAT_PER_UNIT = {
|
|
280
|
-
"": 1e11,
|
|
281
|
-
m: 1e8,
|
|
282
|
-
u: 1e5,
|
|
283
|
-
n: 100,
|
|
284
|
-
p: 0.1
|
|
285
|
-
};
|
|
286
|
-
var decodeBolt11AmountMsat = (pr) => {
|
|
287
|
-
const trimmed = pr.trim().toLowerCase();
|
|
288
|
-
const sep = trimmed.lastIndexOf("1");
|
|
289
|
-
if (sep < 2) return null;
|
|
290
|
-
const hrp = trimmed.slice(0, sep);
|
|
291
|
-
const match = hrp.match(/^ln(?:bc|tb|bcrt|tbs|sb)(\d+)?([munp])?$/);
|
|
292
|
-
if (!match) return null;
|
|
293
|
-
const [, digits, multiplier] = match;
|
|
294
|
-
if (!digits) return null;
|
|
295
|
-
const msat = Number(digits) * BOLT11_AMOUNT_MSAT_PER_UNIT[multiplier || ""];
|
|
296
|
-
return Number.isInteger(msat) ? msat : null;
|
|
297
|
-
};
|
|
298
209
|
|
|
299
210
|
// src/errors.ts
|
|
300
211
|
var LnurlcashError = class extends Error {
|
|
@@ -309,6 +220,11 @@ var ProtocolError = class extends LnurlcashError {
|
|
|
309
220
|
};
|
|
310
221
|
var ServiceRejectedError = class extends LnurlcashError {
|
|
311
222
|
reason;
|
|
223
|
+
// The WALLET-generated secrets a MUTATION disclosed the hashes of, when
|
|
224
|
+
// this refusal is one that could describe a mutation the SERVICE had
|
|
225
|
+
// already applied - see newSecretsOf. Absent on every other refusal, and
|
|
226
|
+
// absent on every non-mutating call.
|
|
227
|
+
newSecrets;
|
|
312
228
|
constructor(reason) {
|
|
313
229
|
super(reason || "The service rejected the request.");
|
|
314
230
|
this.reason = reason;
|
|
@@ -341,12 +257,190 @@ var AmbiguousMutationError = class extends AmbiguousMintError {
|
|
|
341
257
|
this.newSecrets = newSecrets;
|
|
342
258
|
}
|
|
343
259
|
};
|
|
260
|
+
var InsufficientValueError = class extends ServiceRejectedError {
|
|
261
|
+
amountMsat;
|
|
262
|
+
minMsat;
|
|
263
|
+
constructor(amountMsat, minMsat) {
|
|
264
|
+
super(`worth ${amountMsat} msat, needs ${minMsat} msat`);
|
|
265
|
+
this.amountMsat = amountMsat;
|
|
266
|
+
this.minMsat = minMsat;
|
|
267
|
+
this.message = `This note is worth ${amountMsat} msat, and ${minMsat} msat is required.`;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
var newSecretsOf = (err) => {
|
|
271
|
+
if (err instanceof AmbiguousMutationError) return err.newSecrets;
|
|
272
|
+
if (err instanceof ServiceRejectedError) return err.newSecrets ?? [];
|
|
273
|
+
return [];
|
|
274
|
+
};
|
|
344
275
|
var classifyNoteError = (reason) => {
|
|
276
|
+
if (reason === "pending") return new PendingNoteError(reason);
|
|
345
277
|
if (/spent/i.test(reason)) return new NoteSpentError(reason);
|
|
346
278
|
if (/unknown|not found/i.test(reason)) return new NoteUnknownError(reason);
|
|
347
279
|
return new ServiceRejectedError(reason);
|
|
348
280
|
};
|
|
349
281
|
|
|
282
|
+
// src/request.ts
|
|
283
|
+
var PAYMENT_REQUEST_PREFIX = "lnurlcashreq1";
|
|
284
|
+
var MAX_ENCODED_LENGTH = 4096;
|
|
285
|
+
var AMOUNT_RE = /^(?:0|[1-9][0-9]*)$/;
|
|
286
|
+
var ID_RE = /^[0-9a-f]{16}$/;
|
|
287
|
+
var NPUB_RE = /^npub1[023456789acdefghjklmnpqrstuvwxyz]{58}$/;
|
|
288
|
+
var isNpub = (value) => {
|
|
289
|
+
if (!NPUB_RE.test(value)) return false;
|
|
290
|
+
try {
|
|
291
|
+
const { prefix, words } = bech32.decode(value);
|
|
292
|
+
return prefix === "npub" && bech32.fromWords(words).length === 32;
|
|
293
|
+
} catch {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
var reject = (why) => {
|
|
298
|
+
throw new ProtocolError(`Not a valid payment request: ${why}`);
|
|
299
|
+
};
|
|
300
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
301
|
+
var canonicalise = (value) => {
|
|
302
|
+
if (Array.isArray(value)) return `[${value.map(canonicalise).join(",")}]`;
|
|
303
|
+
if (isPlainObject(value)) {
|
|
304
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0);
|
|
305
|
+
entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
306
|
+
const body = entries.map(([key, v]) => `${JSON.stringify(key)}:${canonicalise(v)}`).join(",");
|
|
307
|
+
return `{${body}}`;
|
|
308
|
+
}
|
|
309
|
+
if (typeof value === "number") {
|
|
310
|
+
if (!Number.isSafeInteger(value)) reject("a number that is not a safe integer");
|
|
311
|
+
return String(value);
|
|
312
|
+
}
|
|
313
|
+
if (typeof value === "string" || typeof value === "boolean" || value === null) {
|
|
314
|
+
return JSON.stringify(value);
|
|
315
|
+
}
|
|
316
|
+
return reject(`a value of type ${typeof value}`);
|
|
317
|
+
};
|
|
318
|
+
var validate = (value) => {
|
|
319
|
+
if (!isPlainObject(value)) reject("not an object");
|
|
320
|
+
const raw = value;
|
|
321
|
+
const known = /* @__PURE__ */ new Set([
|
|
322
|
+
"v",
|
|
323
|
+
"id",
|
|
324
|
+
"amount",
|
|
325
|
+
"currency",
|
|
326
|
+
"methodDetails",
|
|
327
|
+
"to",
|
|
328
|
+
"memo",
|
|
329
|
+
"expires"
|
|
330
|
+
]);
|
|
331
|
+
for (const key of Object.keys(raw)) {
|
|
332
|
+
if (!known.has(key)) reject(`unrecognised field "${key}"`);
|
|
333
|
+
}
|
|
334
|
+
if (raw.v !== 1) reject("unsupported version");
|
|
335
|
+
if (typeof raw.id !== "string" || !ID_RE.test(raw.id)) {
|
|
336
|
+
reject("id must be 16 lowercase hex characters");
|
|
337
|
+
}
|
|
338
|
+
if (typeof raw.amount !== "string" || !AMOUNT_RE.test(raw.amount)) {
|
|
339
|
+
reject("amount must be a whole number of sats, as a decimal string");
|
|
340
|
+
}
|
|
341
|
+
const sats = Number(raw.amount);
|
|
342
|
+
if (!Number.isSafeInteger(sats) || sats < 1) {
|
|
343
|
+
reject("amount must be at least 1 sat");
|
|
344
|
+
}
|
|
345
|
+
if (raw.currency !== "sat") reject('currency must be "sat"');
|
|
346
|
+
if (!isPlainObject(raw.methodDetails)) reject("methodDetails must be an object");
|
|
347
|
+
const details = raw.methodDetails;
|
|
348
|
+
for (const key of Object.keys(details)) {
|
|
349
|
+
if (key !== "mints" && key !== "mintPubkeys") {
|
|
350
|
+
reject(`unrecognised methodDetails field "${key}"`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
if (!Array.isArray(details.mints) || details.mints.length === 0 || !details.mints.every((mint) => typeof mint === "string" && mint.trim() !== "")) {
|
|
354
|
+
reject("methodDetails.mints must list at least one mint");
|
|
355
|
+
}
|
|
356
|
+
if (details.mintPubkeys !== void 0 && (!Array.isArray(details.mintPubkeys) || !details.mintPubkeys.every((key) => typeof key === "string"))) {
|
|
357
|
+
reject("methodDetails.mintPubkeys must be a list of strings");
|
|
358
|
+
}
|
|
359
|
+
if (raw.to !== void 0) {
|
|
360
|
+
if (typeof raw.to !== "string") reject("to must be a string");
|
|
361
|
+
const to = raw.to;
|
|
362
|
+
if (!isNpub(to) && !isLightningAddress(to)) {
|
|
363
|
+
reject("to must be an npub or a Lightning Address");
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (raw.memo !== void 0 && typeof raw.memo !== "string") {
|
|
367
|
+
reject("memo must be a string");
|
|
368
|
+
}
|
|
369
|
+
if (raw.expires !== void 0 && (typeof raw.expires !== "number" || !Number.isSafeInteger(raw.expires) || raw.expires < 0)) {
|
|
370
|
+
reject("expires must be a unix timestamp in whole seconds");
|
|
371
|
+
}
|
|
372
|
+
const request = {
|
|
373
|
+
v: 1,
|
|
374
|
+
id: raw.id,
|
|
375
|
+
amount: raw.amount,
|
|
376
|
+
currency: "sat",
|
|
377
|
+
methodDetails: {
|
|
378
|
+
mints: [...details.mints],
|
|
379
|
+
...details.mintPubkeys === void 0 ? {} : { mintPubkeys: [...details.mintPubkeys] }
|
|
380
|
+
},
|
|
381
|
+
...raw.to === void 0 ? {} : { to: raw.to },
|
|
382
|
+
...raw.memo === void 0 ? {} : { memo: raw.memo },
|
|
383
|
+
...raw.expires === void 0 ? {} : { expires: raw.expires }
|
|
384
|
+
};
|
|
385
|
+
return request;
|
|
386
|
+
};
|
|
387
|
+
var paymentRequestAmountMsat = (request) => Number(request.amount) * 1e3;
|
|
388
|
+
var SHORT_FORM_KEYS = /* @__PURE__ */ new Set(["a", "m", "u"]);
|
|
389
|
+
var fromShortForm = (value) => {
|
|
390
|
+
if (!isPlainObject(value)) return value;
|
|
391
|
+
const raw = value;
|
|
392
|
+
if (raw.v !== void 0 || raw.amount !== void 0) return value;
|
|
393
|
+
const keys = Object.keys(raw);
|
|
394
|
+
if (keys.length === 0 || !keys.every((key) => SHORT_FORM_KEYS.has(key))) {
|
|
395
|
+
return value;
|
|
396
|
+
}
|
|
397
|
+
if (typeof raw.a !== "number" || !Number.isSafeInteger(raw.a)) return value;
|
|
398
|
+
if (!Array.isArray(raw.m)) return value;
|
|
399
|
+
const canonical = canonicalise(raw);
|
|
400
|
+
return {
|
|
401
|
+
v: 1,
|
|
402
|
+
id: bytesToHex(sha256(utf8ToBytes(canonical))).slice(0, 16),
|
|
403
|
+
amount: String(raw.a),
|
|
404
|
+
currency: raw.u === void 0 ? "sat" : raw.u,
|
|
405
|
+
methodDetails: { mints: raw.m }
|
|
406
|
+
};
|
|
407
|
+
};
|
|
408
|
+
var encodePaymentRequest = (request) => PAYMENT_REQUEST_PREFIX + base64urlnopad.encode(new TextEncoder().encode(canonicalise(validate(request))));
|
|
409
|
+
var decodePaymentRequest = (value, { now = Math.floor(Date.now() / 1e3) } = {}) => {
|
|
410
|
+
const trimmed = value.trim();
|
|
411
|
+
if (trimmed.length > MAX_ENCODED_LENGTH) reject("far too long to be one");
|
|
412
|
+
if (trimmed.slice(0, PAYMENT_REQUEST_PREFIX.length).toLowerCase() !== PAYMENT_REQUEST_PREFIX) {
|
|
413
|
+
reject(`it does not start with ${PAYMENT_REQUEST_PREFIX}`);
|
|
414
|
+
}
|
|
415
|
+
let json;
|
|
416
|
+
try {
|
|
417
|
+
json = new TextDecoder().decode(
|
|
418
|
+
base64urlnopad.decode(trimmed.slice(PAYMENT_REQUEST_PREFIX.length))
|
|
419
|
+
);
|
|
420
|
+
} catch {
|
|
421
|
+
return reject("the body is not base64url");
|
|
422
|
+
}
|
|
423
|
+
let parsed;
|
|
424
|
+
try {
|
|
425
|
+
parsed = JSON.parse(json);
|
|
426
|
+
} catch {
|
|
427
|
+
return reject("the body is not JSON");
|
|
428
|
+
}
|
|
429
|
+
const request = validate(fromShortForm(parsed));
|
|
430
|
+
if (request.expires !== void 0 && now > 0 && request.expires <= now) {
|
|
431
|
+
reject("it expired");
|
|
432
|
+
}
|
|
433
|
+
return request;
|
|
434
|
+
};
|
|
435
|
+
var isPaymentRequest = (value) => {
|
|
436
|
+
try {
|
|
437
|
+
decodePaymentRequest(value, { now: 0 });
|
|
438
|
+
return true;
|
|
439
|
+
} catch {
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
|
|
350
444
|
// src/transport.ts
|
|
351
445
|
var resolveOptions = (options = {}) => ({
|
|
352
446
|
// Wrapped, never referenced bare: in a browser window.fetch is a method
|
|
@@ -455,6 +549,85 @@ var lnurlFetch = async (url, options) => {
|
|
|
455
549
|
return body;
|
|
456
550
|
};
|
|
457
551
|
|
|
552
|
+
// src/bolt11.ts
|
|
553
|
+
var isBolt11Invoice = (value) => /^ln(bc|tb|bcrt|tbs|sb)[0-9]*[munp]?1[a-z0-9]+$/.test(
|
|
554
|
+
value.trim().toLowerCase()
|
|
555
|
+
);
|
|
556
|
+
var sameInvoice = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
557
|
+
var BOLT11_AMOUNT_MSAT_PER_UNIT = {
|
|
558
|
+
"": 1e11,
|
|
559
|
+
m: 1e8,
|
|
560
|
+
u: 1e5,
|
|
561
|
+
n: 100,
|
|
562
|
+
p: 0.1
|
|
563
|
+
};
|
|
564
|
+
var decodeBolt11AmountMsat = (pr) => {
|
|
565
|
+
const trimmed = pr.trim().toLowerCase();
|
|
566
|
+
const sep = trimmed.lastIndexOf("1");
|
|
567
|
+
if (sep < 2) return null;
|
|
568
|
+
const hrp = trimmed.slice(0, sep);
|
|
569
|
+
const match = hrp.match(/^ln(?:bc|tb|bcrt|tbs|sb)(\d+)?([munp])?$/);
|
|
570
|
+
if (!match) return null;
|
|
571
|
+
const [, digits, multiplier] = match;
|
|
572
|
+
if (!digits) return null;
|
|
573
|
+
const msat = Number(digits) * BOLT11_AMOUNT_MSAT_PER_UNIT[multiplier || ""];
|
|
574
|
+
return Number.isInteger(msat) ? msat : null;
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// src/fees.ts
|
|
578
|
+
var parseMintFee = (metadata) => {
|
|
579
|
+
let entries;
|
|
580
|
+
try {
|
|
581
|
+
entries = JSON.parse(metadata);
|
|
582
|
+
} catch {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
if (!Array.isArray(entries)) return null;
|
|
586
|
+
for (const entry of entries) {
|
|
587
|
+
if (!Array.isArray(entry) || entry[0] !== "text/plain") continue;
|
|
588
|
+
const match = typeof entry[1] === "string" && entry[1].match(/^Mint fees:\s*(\d+)\s*,\s*(\d+)\s*$/);
|
|
589
|
+
if (!match) continue;
|
|
590
|
+
const baseFeeMsat = Number(match[1]);
|
|
591
|
+
const feePpm = Number(match[2]);
|
|
592
|
+
if (!Number.isSafeInteger(baseFeeMsat) || !Number.isSafeInteger(feePpm)) continue;
|
|
593
|
+
if (feePpm >= 1e6) continue;
|
|
594
|
+
if (baseFeeMsat === 0 && feePpm === 0) return null;
|
|
595
|
+
return { baseFeeMsat, feePpm };
|
|
596
|
+
}
|
|
597
|
+
return null;
|
|
598
|
+
};
|
|
599
|
+
var proportionalFee = (grossMsat, feePpm) => Math.floor(grossMsat / 1e6) * feePpm + Math.floor(grossMsat % 1e6 * feePpm / 1e6);
|
|
600
|
+
var applyMintFee = (grossMsat, fee) => Math.max(0, grossMsat - fee.baseFeeMsat - proportionalFee(grossMsat, fee.feePpm));
|
|
601
|
+
var mintFeeBand = (grossMsat, fee) => {
|
|
602
|
+
const exactFee = fee.baseFeeMsat + proportionalFee(grossMsat, fee.feePpm);
|
|
603
|
+
const satCeilinged = Math.ceil(exactFee / 1e3) * 1e3;
|
|
604
|
+
return {
|
|
605
|
+
minNetMsat: Math.max(0, grossMsat - satCeilinged),
|
|
606
|
+
maxNetMsat: Math.max(0, grossMsat - exactFee)
|
|
607
|
+
};
|
|
608
|
+
};
|
|
609
|
+
var withinMintFeeBand = (grossMsat, netMsat, fee) => {
|
|
610
|
+
const { minNetMsat, maxNetMsat } = mintFeeBand(grossMsat, fee);
|
|
611
|
+
return netMsat >= minNetMsat && netMsat <= maxNetMsat;
|
|
612
|
+
};
|
|
613
|
+
var grossUpForMintFee = (netMsat, fee) => {
|
|
614
|
+
if (netMsat <= 0) return 0;
|
|
615
|
+
let hi = netMsat + fee.baseFeeMsat;
|
|
616
|
+
while (applyMintFee(hi, fee) < netMsat) hi *= 2;
|
|
617
|
+
let lo = 0;
|
|
618
|
+
while (lo < hi) {
|
|
619
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
620
|
+
if (applyMintFee(mid, fee) >= netMsat) hi = mid;
|
|
621
|
+
else lo = mid + 1;
|
|
622
|
+
}
|
|
623
|
+
return lo;
|
|
624
|
+
};
|
|
625
|
+
var formatFeePercent = (ppm) => (ppm / 1e4).toFixed(4).replace(/\.?0+$/, "");
|
|
626
|
+
var describeMintFee = (fee) => [
|
|
627
|
+
fee.baseFeeMsat > 0 ? `${Math.round(fee.baseFeeMsat / 1e3)} sat flat` : null,
|
|
628
|
+
fee.feePpm > 0 ? `${formatFeePercent(fee.feePpm)}% of the amount paid` : null
|
|
629
|
+
].filter(Boolean).join(" + ");
|
|
630
|
+
|
|
458
631
|
// src/client.ts
|
|
459
632
|
var fetchNoteInfo = async (url, options = {}) => {
|
|
460
633
|
const opts = resolveOptions(options);
|
|
@@ -489,18 +662,63 @@ var probeBurnedNote = async (url, options = {}) => {
|
|
|
489
662
|
return "unknown";
|
|
490
663
|
}
|
|
491
664
|
};
|
|
665
|
+
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
666
|
+
var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
667
|
+
var asContact = (value) => {
|
|
668
|
+
if (!value || typeof value !== "object") return void 0;
|
|
669
|
+
const raw = value;
|
|
670
|
+
const contact = {
|
|
671
|
+
nostr: asString(raw.nostr),
|
|
672
|
+
email: asString(raw.email),
|
|
673
|
+
url: asString(raw.url)
|
|
674
|
+
};
|
|
675
|
+
const any = contact.nostr ?? contact.email ?? contact.url;
|
|
676
|
+
return any === void 0 ? void 0 : contact;
|
|
677
|
+
};
|
|
678
|
+
var asFees = (value) => {
|
|
679
|
+
if (!value || typeof value !== "object") return void 0;
|
|
680
|
+
const raw = value;
|
|
681
|
+
const baseFeeMsat = asNumber(raw.baseFeeMsat);
|
|
682
|
+
const feePpm = asNumber(raw.feePpm);
|
|
683
|
+
if (baseFeeMsat === void 0 && feePpm === void 0) return void 0;
|
|
684
|
+
return { baseFeeMsat: baseFeeMsat ?? 0, feePpm: feePpm ?? 0 };
|
|
685
|
+
};
|
|
686
|
+
var asBoolean = (value) => typeof value === "boolean" ? value : void 0;
|
|
687
|
+
var asPubkeyList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : void 0;
|
|
492
688
|
var fetchMintAddress = async (url, options = {}) => {
|
|
493
689
|
const body = await lnurlFetch(url, resolveOptions(options));
|
|
494
690
|
if (body?.tag !== "withdrawRequest" || typeof body.callback !== "string" || typeof body.payLink !== "string" || typeof body.maxWithdrawable !== "number") {
|
|
495
691
|
throw new ProtocolError("Not a mint address response (unexpected shape).");
|
|
496
692
|
}
|
|
497
|
-
const { mintPubkey, nodeCapacity, ...rest } = body;
|
|
498
693
|
return {
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
694
|
+
tag: "withdrawRequest",
|
|
695
|
+
callback: body.callback,
|
|
696
|
+
minWithdrawable: asNumber(body.minWithdrawable) ?? 0,
|
|
697
|
+
maxWithdrawable: body.maxWithdrawable,
|
|
698
|
+
defaultDescription: asString(body.defaultDescription),
|
|
699
|
+
mintPubkey: asString(body.mintPubkey),
|
|
700
|
+
// the same value under both names for one release, so nothing breaks
|
|
701
|
+
nodePubkey: asString(body.mintPubkey),
|
|
702
|
+
payLink: body.payLink,
|
|
703
|
+
nodeAlias: asString(body.nodeAlias),
|
|
704
|
+
nodeUri: asString(body.nodeUri),
|
|
705
|
+
nodeColor: asString(body.nodeColor),
|
|
706
|
+
// `nodeCapacity` is the wire name the reference mint, the mock and
|
|
707
|
+
// every implementation that copied them use. One live mint emits
|
|
708
|
+
// `nodeCapacityMsat` instead, so both are accepted and the bare name
|
|
709
|
+
// wins where a SERVICE sends both.
|
|
710
|
+
nodeCapacityMsat: asNumber(body.nodeCapacity) ?? asNumber(body.nodeCapacityMsat),
|
|
711
|
+
nodeNumChannels: asNumber(body.nodeNumChannels),
|
|
712
|
+
nodeNumPeers: asNumber(body.nodeNumPeers),
|
|
713
|
+
name: asString(body.name),
|
|
714
|
+
description: asString(body.description),
|
|
715
|
+
contact: asContact(body.contact),
|
|
716
|
+
tosUrl: asString(body.tosUrl),
|
|
717
|
+
motd: asString(body.motd),
|
|
718
|
+
fees: asFees(body.fees),
|
|
719
|
+
version: asString(body.version),
|
|
720
|
+
previousPubkeys: asPubkeyList(body.previousPubkeys),
|
|
721
|
+
mintToHash: asBoolean(body.mintToHash)
|
|
504
722
|
};
|
|
505
723
|
};
|
|
506
724
|
var callbackRequest = async (callback, params, options) => {
|
|
@@ -580,6 +798,12 @@ var mergeNotesWithHash = async (callback, k1s, h, options = {}) => {
|
|
|
580
798
|
);
|
|
581
799
|
return { signature: body.sig };
|
|
582
800
|
};
|
|
801
|
+
var keepingOutputs = (err, newSecrets) => {
|
|
802
|
+
if (err instanceof NoteSpentError || err instanceof NoteUnknownError) {
|
|
803
|
+
err.newSecrets = newSecrets;
|
|
804
|
+
}
|
|
805
|
+
return err;
|
|
806
|
+
};
|
|
583
807
|
var rotateNote = async (callback, k1, options = {}) => {
|
|
584
808
|
const opts = resolveOptions(options);
|
|
585
809
|
const newK1 = opts.randomSecret();
|
|
@@ -590,7 +814,7 @@ var rotateNote = async (callback, k1, options = {}) => {
|
|
|
590
814
|
if (err instanceof AmbiguousMintError) {
|
|
591
815
|
throw new AmbiguousMutationError(err.message, [newK1]);
|
|
592
816
|
}
|
|
593
|
-
throw err;
|
|
817
|
+
throw keepingOutputs(err, [newK1]);
|
|
594
818
|
}
|
|
595
819
|
};
|
|
596
820
|
var splitNote = async (callback, k1s, amountMsat, options = {}) => {
|
|
@@ -616,7 +840,7 @@ var splitNote = async (callback, k1s, amountMsat, options = {}) => {
|
|
|
616
840
|
if (err instanceof AmbiguousMintError) {
|
|
617
841
|
throw new AmbiguousMutationError(err.message, [newK1, changeK1]);
|
|
618
842
|
}
|
|
619
|
-
throw err;
|
|
843
|
+
throw keepingOutputs(err, [newK1, changeK1]);
|
|
620
844
|
}
|
|
621
845
|
};
|
|
622
846
|
var mergeNotes = async (callback, k1s, options = {}) => {
|
|
@@ -629,7 +853,7 @@ var mergeNotes = async (callback, k1s, options = {}) => {
|
|
|
629
853
|
if (err instanceof AmbiguousMintError) {
|
|
630
854
|
throw new AmbiguousMutationError(err.message, [newK1]);
|
|
631
855
|
}
|
|
632
|
-
throw err;
|
|
856
|
+
throw keepingOutputs(err, [newK1]);
|
|
633
857
|
}
|
|
634
858
|
};
|
|
635
859
|
var settleNote = async (baseUrl, k1, expectedAmountMsat, signature, options = {}) => {
|
|
@@ -660,11 +884,23 @@ var fetchPayRequest = async (url, options = {}) => {
|
|
|
660
884
|
throw new ProtocolError("Not a payRequest (unexpected response).");
|
|
661
885
|
}
|
|
662
886
|
const mintFee = typeof body.metadata === "string" ? parseMintFee(body.metadata) : null;
|
|
663
|
-
return {
|
|
887
|
+
return {
|
|
888
|
+
...body,
|
|
889
|
+
mintFee: mintFee ?? void 0,
|
|
890
|
+
mintToHash: asBoolean(body.mintToHash)
|
|
891
|
+
};
|
|
664
892
|
};
|
|
665
893
|
var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
666
894
|
const cbUrl = new URL(payCallback);
|
|
667
895
|
cbUrl.searchParams.set("amount", String(amountMsat));
|
|
896
|
+
if (options.h !== void 0) {
|
|
897
|
+
if (!isPreimage(options.h)) {
|
|
898
|
+
throw new RequestRefusedError(
|
|
899
|
+
"An output hash must be 32 bytes of hex - no invoice was requested."
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
cbUrl.searchParams.set("h", options.h.trim().toLowerCase());
|
|
903
|
+
}
|
|
668
904
|
const body = await lnurlFetch(cbUrl, resolveOptions(options));
|
|
669
905
|
if (typeof body?.pr !== "string") {
|
|
670
906
|
throw new ProtocolError("The service did not return an invoice.");
|
|
@@ -678,7 +914,8 @@ var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
|
678
914
|
return {
|
|
679
915
|
pr: body.pr,
|
|
680
916
|
verify: typeof body.verify === "string" ? body.verify : void 0,
|
|
681
|
-
disposable: body.disposable !== false
|
|
917
|
+
disposable: body.disposable !== false,
|
|
918
|
+
mintToHash: body.mintToHash === true
|
|
682
919
|
};
|
|
683
920
|
};
|
|
684
921
|
var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
@@ -692,6 +929,156 @@ var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
|
692
929
|
pr: body.pr
|
|
693
930
|
};
|
|
694
931
|
};
|
|
932
|
+
var claimMintedNote = async (withdrawLink, k1, options = {}) => {
|
|
933
|
+
const secret = k1.trim().toLowerCase();
|
|
934
|
+
if (!isPreimage(secret)) {
|
|
935
|
+
throw new RequestRefusedError(
|
|
936
|
+
"A note secret must be 32 bytes of hex - nothing was sent."
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
const blank = {
|
|
940
|
+
k1: secret,
|
|
941
|
+
amountMsat: null,
|
|
942
|
+
callback: null
|
|
943
|
+
};
|
|
944
|
+
try {
|
|
945
|
+
const info = await fetchNoteInfo(buildNoteUrl(withdrawLink, secret), options);
|
|
946
|
+
return {
|
|
947
|
+
state: "minted",
|
|
948
|
+
k1: secret,
|
|
949
|
+
amountMsat: info.maxWithdrawable,
|
|
950
|
+
callback: info.callback
|
|
951
|
+
};
|
|
952
|
+
} catch (err) {
|
|
953
|
+
if (err instanceof PendingNoteError) return { ...blank, state: "pending" };
|
|
954
|
+
if (err instanceof NoteSpentError) return { ...blank, state: "spent" };
|
|
955
|
+
if (err instanceof NoteUnknownError) return { ...blank, state: "unminted" };
|
|
956
|
+
throw err;
|
|
957
|
+
}
|
|
958
|
+
};
|
|
959
|
+
var LIGHTNING_SIGNED_MESSAGE_PREFIX = utf8ToBytes("Lightning Signed Message:");
|
|
960
|
+
var noteSignatureMessage = (k1, amountMsat) => `LNURLcash:${amountMsat}:${hashK1(k1)}`;
|
|
961
|
+
var noteSignatureDigest = (k1, amountMsat) => sha256(
|
|
962
|
+
sha256(
|
|
963
|
+
new Uint8Array([
|
|
964
|
+
...LIGHTNING_SIGNED_MESSAGE_PREFIX,
|
|
965
|
+
...utf8ToBytes(noteSignatureMessage(k1, amountMsat))
|
|
966
|
+
])
|
|
967
|
+
)
|
|
968
|
+
);
|
|
969
|
+
var NO_MATCH = { valid: false, pubkey: null };
|
|
970
|
+
var verifyNoteSignatureAgainst = (k1, amountMsat, signatureHex, mintPubkeys) => {
|
|
971
|
+
const targets = (Array.isArray(mintPubkeys) ? mintPubkeys : [mintPubkeys]).filter((key) => typeof key === "string").map((key) => key.trim().toLowerCase());
|
|
972
|
+
if (targets.length === 0) return NO_MATCH;
|
|
973
|
+
let wireSig;
|
|
974
|
+
try {
|
|
975
|
+
wireSig = hexToBytes(signatureHex);
|
|
976
|
+
} catch {
|
|
977
|
+
return NO_MATCH;
|
|
978
|
+
}
|
|
979
|
+
if (wireSig.length !== 65) return NO_MATCH;
|
|
980
|
+
let digest;
|
|
981
|
+
try {
|
|
982
|
+
digest = noteSignatureDigest(k1, amountMsat);
|
|
983
|
+
} catch {
|
|
984
|
+
return NO_MATCH;
|
|
985
|
+
}
|
|
986
|
+
const recoveryIdFirst = new Uint8Array([
|
|
987
|
+
wireSig[64],
|
|
988
|
+
...wireSig.subarray(0, 64)
|
|
989
|
+
]);
|
|
990
|
+
for (const candidate of [recoveryIdFirst, wireSig]) {
|
|
991
|
+
try {
|
|
992
|
+
const recovered = bytesToHex(
|
|
993
|
+
secp256k1.recoverPublicKey(candidate, digest, { prehash: false })
|
|
994
|
+
);
|
|
995
|
+
if (targets.includes(recovered)) return { valid: true, pubkey: recovered };
|
|
996
|
+
} catch {
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return NO_MATCH;
|
|
1000
|
+
};
|
|
1001
|
+
var verifyNoteSignature = (k1, amountMsat, signatureHex, mintPubkeys) => verifyNoteSignatureAgainst(k1, amountMsat, signatureHex, mintPubkeys).valid;
|
|
1002
|
+
|
|
1003
|
+
// src/settle.ts
|
|
1004
|
+
var normaliseHost = (value) => serverOf(value.trim().replace(/^@/, "")).toLowerCase();
|
|
1005
|
+
var settleNoteForValue = async (noteUrl, { mints, minMsat, requireSignature = false }, options = {}) => {
|
|
1006
|
+
const url = resolveNoteInput(noteUrl);
|
|
1007
|
+
if (!url) {
|
|
1008
|
+
throw new RequestRefusedError(
|
|
1009
|
+
"That is not a bearer note this library will fetch."
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
const host = serverOf(url).toLowerCase();
|
|
1013
|
+
const accepted = mints.map(normaliseHost);
|
|
1014
|
+
if (!accepted.includes(host)) {
|
|
1015
|
+
throw new ServiceRejectedError(
|
|
1016
|
+
accepted.length === 0 ? "This server accepts no mints." : `Notes from ${host} are not accepted here.`
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
const k1 = requireNoteK1(url);
|
|
1020
|
+
const info = await fetchNoteInfo(url, options);
|
|
1021
|
+
if (requireSignature) {
|
|
1022
|
+
const signature = noteSignature(url);
|
|
1023
|
+
if (!signature) {
|
|
1024
|
+
throw new ServiceRejectedError("This note carries no signature.");
|
|
1025
|
+
}
|
|
1026
|
+
if (!info.mintPubkey) {
|
|
1027
|
+
throw new ServiceRejectedError(
|
|
1028
|
+
"The mint published no signing key to check this note against."
|
|
1029
|
+
);
|
|
1030
|
+
}
|
|
1031
|
+
if (!verifyNoteSignature(k1, info.maxWithdrawable, signature, info.mintPubkey)) {
|
|
1032
|
+
throw new ServiceRejectedError("This note's signature does not verify.");
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
if (info.maxWithdrawable < minMsat) {
|
|
1036
|
+
throw new InsufficientValueError(info.maxWithdrawable, minMsat);
|
|
1037
|
+
}
|
|
1038
|
+
const rotated = await rotateNote(info.callback, k1, options);
|
|
1039
|
+
return {
|
|
1040
|
+
note: {
|
|
1041
|
+
k1: rotated.k1,
|
|
1042
|
+
amountMsat: info.maxWithdrawable,
|
|
1043
|
+
signature: rotated.signature,
|
|
1044
|
+
callback: info.callback
|
|
1045
|
+
},
|
|
1046
|
+
newUrl: withNewK1(url, rotated.k1, info.maxWithdrawable, rotated.signature)
|
|
1047
|
+
};
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
// src/restore.ts
|
|
1051
|
+
var restoreNotes = async (baseUrl, root, host, { gap = 20, start = 0 } = {}, options = {}) => {
|
|
1052
|
+
if (!Number.isSafeInteger(gap) || gap < 1) {
|
|
1053
|
+
throw new RangeError(`The gap limit must be a positive integer, not ${gap}.`);
|
|
1054
|
+
}
|
|
1055
|
+
const found = [];
|
|
1056
|
+
let lastUsed = null;
|
|
1057
|
+
let unknownRun = 0;
|
|
1058
|
+
for (let index = start; unknownRun < gap; index++) {
|
|
1059
|
+
const k1 = deriveNoteSecret(root, host, index);
|
|
1060
|
+
try {
|
|
1061
|
+
const info = await fetchNoteInfo(buildNoteUrl(baseUrl, k1), options);
|
|
1062
|
+
found.push({ index, k1, amountMsat: info.maxWithdrawable, state: "live" });
|
|
1063
|
+
lastUsed = index;
|
|
1064
|
+
unknownRun = 0;
|
|
1065
|
+
} catch (err) {
|
|
1066
|
+
if (err instanceof PendingNoteError) {
|
|
1067
|
+
found.push({ index, k1, amountMsat: null, state: "pending" });
|
|
1068
|
+
lastUsed = index;
|
|
1069
|
+
unknownRun = 0;
|
|
1070
|
+
} else if (err instanceof NoteSpentError) {
|
|
1071
|
+
lastUsed = index;
|
|
1072
|
+
unknownRun = 0;
|
|
1073
|
+
} else if (err instanceof NoteUnknownError) {
|
|
1074
|
+
unknownRun++;
|
|
1075
|
+
} else {
|
|
1076
|
+
throw err;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return { found, next: lastUsed === null ? start : lastUsed + 1 };
|
|
1081
|
+
};
|
|
695
1082
|
|
|
696
1083
|
// src/index.ts
|
|
697
1084
|
var createClient = (options = {}) => ({
|
|
@@ -707,8 +1094,14 @@ var createClient = (options = {}) => ({
|
|
|
707
1094
|
mergeNotesWithHash: (callback, k1s, h) => mergeNotesWithHash(callback, k1s, h, options),
|
|
708
1095
|
settleNote: (baseUrl, k1, expectedAmountMsat, signature) => settleNote(baseUrl, k1, expectedAmountMsat, signature, options),
|
|
709
1096
|
fetchPayRequest: (url) => fetchPayRequest(url, options),
|
|
710
|
-
|
|
711
|
-
|
|
1097
|
+
// `h` names the note the invoice will mint - see requestInvoice. The
|
|
1098
|
+
// bound transport options are merged under it, so a caller can still
|
|
1099
|
+
// reach for a one-off override.
|
|
1100
|
+
requestInvoice: (payCallback, amountMsat, h) => requestInvoice(payCallback, amountMsat, { ...options, h }),
|
|
1101
|
+
fetchInvoiceVerification: (verifyUrl) => fetchInvoiceVerification(verifyUrl, options),
|
|
1102
|
+
claimMintedNote: (withdrawLink, k1) => claimMintedNote(withdrawLink, k1, options),
|
|
1103
|
+
restoreNotes: (baseUrl, root, host, restoreOptions = {}) => restoreNotes(baseUrl, root, host, restoreOptions, options),
|
|
1104
|
+
settleNoteForValue: (noteUrl, terms) => settleNoteForValue(noteUrl, terms, options)
|
|
712
1105
|
});
|
|
713
1106
|
|
|
714
|
-
export { AmbiguousMintError, AmbiguousMutationError, LnurlcashError, NoteSpentError, NoteUnknownError, PendingNoteError, ProtocolError, RequestRefusedError, ServiceRejectedError, applyMintFee, buildNoteUrl, classifyNoteError, createClient, decodeBolt11AmountMsat, defaultRandomSecret, describeMintFee, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isLightningAddress, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureMessage, parseMintFee, probeBurnedNote, requestInvoice, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, verifyNoteSignature, withNewK1, withoutK1 };
|
|
1107
|
+
export { AmbiguousMintError, AmbiguousMutationError, InsufficientValueError, LnurlcashError, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, PendingNoteError, ProtocolError, RequestRefusedError, ServiceRejectedError, applyMintFee, buildNoteUrl, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodePaymentRequest, defaultRandomSecret, deriveNoteRoot, deriveNoteSecret, derivedSecretSource, describeMintFee, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, newSecretsOf, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureMessage, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, requestInvoice, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, verifyNoteSignature, verifyNoteSignatureAgainst, withNewK1, withinMintFeeBand, withoutK1 };
|