lnurlcash-kit 0.1.2 → 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 +325 -0
- package/README.md +314 -0
- package/dist/index.d.ts +168 -68
- package/dist/index.js +515 -134
- package/llms.txt +67 -6
- package/package.json +1 -1
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,125 +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 mintFeeBand = (grossMsat, fee) => {
|
|
257
|
-
const exactFee = fee.baseFeeMsat + proportionalFee(grossMsat, fee.feePpm);
|
|
258
|
-
const satCeilinged = Math.ceil(exactFee / 1e3) * 1e3;
|
|
259
|
-
return {
|
|
260
|
-
minNetMsat: Math.max(0, grossMsat - satCeilinged),
|
|
261
|
-
maxNetMsat: Math.max(0, grossMsat - exactFee)
|
|
262
|
-
};
|
|
263
|
-
};
|
|
264
|
-
var withinMintFeeBand = (grossMsat, netMsat, fee) => {
|
|
265
|
-
const { minNetMsat, maxNetMsat } = mintFeeBand(grossMsat, fee);
|
|
266
|
-
return netMsat >= minNetMsat && netMsat <= maxNetMsat;
|
|
267
|
-
};
|
|
268
|
-
var grossUpForMintFee = (netMsat, fee) => {
|
|
269
|
-
if (netMsat <= 0) return 0;
|
|
270
|
-
let hi = netMsat + fee.baseFeeMsat;
|
|
271
|
-
while (applyMintFee(hi, fee) < netMsat) hi *= 2;
|
|
272
|
-
let lo = 0;
|
|
273
|
-
while (lo < hi) {
|
|
274
|
-
const mid = Math.floor((lo + hi) / 2);
|
|
275
|
-
if (applyMintFee(mid, fee) >= netMsat) hi = mid;
|
|
276
|
-
else lo = mid + 1;
|
|
277
|
-
}
|
|
278
|
-
return lo;
|
|
279
|
-
};
|
|
280
|
-
var formatFeePercent = (ppm) => (ppm / 1e4).toFixed(4).replace(/\.?0+$/, "");
|
|
281
|
-
var describeMintFee = (fee) => [
|
|
282
|
-
fee.baseFeeMsat > 0 ? `${Math.round(fee.baseFeeMsat / 1e3)} sat flat` : null,
|
|
283
|
-
fee.feePpm > 0 ? `${formatFeePercent(fee.feePpm)}% of the amount paid` : null
|
|
284
|
-
].filter(Boolean).join(" + ");
|
|
285
|
-
|
|
286
|
-
// src/bolt11.ts
|
|
287
|
-
var isBolt11Invoice = (value) => /^ln(bc|tb|bcrt|tbs|sb)[0-9]*[munp]?1[a-z0-9]+$/.test(
|
|
288
|
-
value.trim().toLowerCase()
|
|
289
|
-
);
|
|
290
|
-
var sameInvoice = (a, b) => a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
291
|
-
var BOLT11_AMOUNT_MSAT_PER_UNIT = {
|
|
292
|
-
"": 1e11,
|
|
293
|
-
m: 1e8,
|
|
294
|
-
u: 1e5,
|
|
295
|
-
n: 100,
|
|
296
|
-
p: 0.1
|
|
297
|
-
};
|
|
298
|
-
var decodeBolt11AmountMsat = (pr) => {
|
|
299
|
-
const trimmed = pr.trim().toLowerCase();
|
|
300
|
-
const sep = trimmed.lastIndexOf("1");
|
|
301
|
-
if (sep < 2) return null;
|
|
302
|
-
const hrp = trimmed.slice(0, sep);
|
|
303
|
-
const match = hrp.match(/^ln(?:bc|tb|bcrt|tbs|sb)(\d+)?([munp])?$/);
|
|
304
|
-
if (!match) return null;
|
|
305
|
-
const [, digits, multiplier] = match;
|
|
306
|
-
if (!digits) return null;
|
|
307
|
-
const msat = Number(digits) * BOLT11_AMOUNT_MSAT_PER_UNIT[multiplier || ""];
|
|
308
|
-
return Number.isInteger(msat) ? msat : null;
|
|
309
|
-
};
|
|
310
209
|
|
|
311
210
|
// src/errors.ts
|
|
312
211
|
var LnurlcashError = class extends Error {
|
|
@@ -321,6 +220,11 @@ var ProtocolError = class extends LnurlcashError {
|
|
|
321
220
|
};
|
|
322
221
|
var ServiceRejectedError = class extends LnurlcashError {
|
|
323
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;
|
|
324
228
|
constructor(reason) {
|
|
325
229
|
super(reason || "The service rejected the request.");
|
|
326
230
|
this.reason = reason;
|
|
@@ -353,12 +257,190 @@ var AmbiguousMutationError = class extends AmbiguousMintError {
|
|
|
353
257
|
this.newSecrets = newSecrets;
|
|
354
258
|
}
|
|
355
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
|
+
};
|
|
356
275
|
var classifyNoteError = (reason) => {
|
|
276
|
+
if (reason === "pending") return new PendingNoteError(reason);
|
|
357
277
|
if (/spent/i.test(reason)) return new NoteSpentError(reason);
|
|
358
278
|
if (/unknown|not found/i.test(reason)) return new NoteUnknownError(reason);
|
|
359
279
|
return new ServiceRejectedError(reason);
|
|
360
280
|
};
|
|
361
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
|
+
|
|
362
444
|
// src/transport.ts
|
|
363
445
|
var resolveOptions = (options = {}) => ({
|
|
364
446
|
// Wrapped, never referenced bare: in a browser window.fetch is a method
|
|
@@ -467,6 +549,85 @@ var lnurlFetch = async (url, options) => {
|
|
|
467
549
|
return body;
|
|
468
550
|
};
|
|
469
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
|
+
|
|
470
631
|
// src/client.ts
|
|
471
632
|
var fetchNoteInfo = async (url, options = {}) => {
|
|
472
633
|
const opts = resolveOptions(options);
|
|
@@ -501,18 +662,63 @@ var probeBurnedNote = async (url, options = {}) => {
|
|
|
501
662
|
return "unknown";
|
|
502
663
|
}
|
|
503
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;
|
|
504
688
|
var fetchMintAddress = async (url, options = {}) => {
|
|
505
689
|
const body = await lnurlFetch(url, resolveOptions(options));
|
|
506
690
|
if (body?.tag !== "withdrawRequest" || typeof body.callback !== "string" || typeof body.payLink !== "string" || typeof body.maxWithdrawable !== "number") {
|
|
507
691
|
throw new ProtocolError("Not a mint address response (unexpected shape).");
|
|
508
692
|
}
|
|
509
|
-
const { mintPubkey, nodeCapacity, ...rest } = body;
|
|
510
693
|
return {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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)
|
|
516
722
|
};
|
|
517
723
|
};
|
|
518
724
|
var callbackRequest = async (callback, params, options) => {
|
|
@@ -592,6 +798,12 @@ var mergeNotesWithHash = async (callback, k1s, h, options = {}) => {
|
|
|
592
798
|
);
|
|
593
799
|
return { signature: body.sig };
|
|
594
800
|
};
|
|
801
|
+
var keepingOutputs = (err, newSecrets) => {
|
|
802
|
+
if (err instanceof NoteSpentError || err instanceof NoteUnknownError) {
|
|
803
|
+
err.newSecrets = newSecrets;
|
|
804
|
+
}
|
|
805
|
+
return err;
|
|
806
|
+
};
|
|
595
807
|
var rotateNote = async (callback, k1, options = {}) => {
|
|
596
808
|
const opts = resolveOptions(options);
|
|
597
809
|
const newK1 = opts.randomSecret();
|
|
@@ -602,7 +814,7 @@ var rotateNote = async (callback, k1, options = {}) => {
|
|
|
602
814
|
if (err instanceof AmbiguousMintError) {
|
|
603
815
|
throw new AmbiguousMutationError(err.message, [newK1]);
|
|
604
816
|
}
|
|
605
|
-
throw err;
|
|
817
|
+
throw keepingOutputs(err, [newK1]);
|
|
606
818
|
}
|
|
607
819
|
};
|
|
608
820
|
var splitNote = async (callback, k1s, amountMsat, options = {}) => {
|
|
@@ -628,7 +840,7 @@ var splitNote = async (callback, k1s, amountMsat, options = {}) => {
|
|
|
628
840
|
if (err instanceof AmbiguousMintError) {
|
|
629
841
|
throw new AmbiguousMutationError(err.message, [newK1, changeK1]);
|
|
630
842
|
}
|
|
631
|
-
throw err;
|
|
843
|
+
throw keepingOutputs(err, [newK1, changeK1]);
|
|
632
844
|
}
|
|
633
845
|
};
|
|
634
846
|
var mergeNotes = async (callback, k1s, options = {}) => {
|
|
@@ -641,7 +853,7 @@ var mergeNotes = async (callback, k1s, options = {}) => {
|
|
|
641
853
|
if (err instanceof AmbiguousMintError) {
|
|
642
854
|
throw new AmbiguousMutationError(err.message, [newK1]);
|
|
643
855
|
}
|
|
644
|
-
throw err;
|
|
856
|
+
throw keepingOutputs(err, [newK1]);
|
|
645
857
|
}
|
|
646
858
|
};
|
|
647
859
|
var settleNote = async (baseUrl, k1, expectedAmountMsat, signature, options = {}) => {
|
|
@@ -672,11 +884,23 @@ var fetchPayRequest = async (url, options = {}) => {
|
|
|
672
884
|
throw new ProtocolError("Not a payRequest (unexpected response).");
|
|
673
885
|
}
|
|
674
886
|
const mintFee = typeof body.metadata === "string" ? parseMintFee(body.metadata) : null;
|
|
675
|
-
return {
|
|
887
|
+
return {
|
|
888
|
+
...body,
|
|
889
|
+
mintFee: mintFee ?? void 0,
|
|
890
|
+
mintToHash: asBoolean(body.mintToHash)
|
|
891
|
+
};
|
|
676
892
|
};
|
|
677
893
|
var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
678
894
|
const cbUrl = new URL(payCallback);
|
|
679
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
|
+
}
|
|
680
904
|
const body = await lnurlFetch(cbUrl, resolveOptions(options));
|
|
681
905
|
if (typeof body?.pr !== "string") {
|
|
682
906
|
throw new ProtocolError("The service did not return an invoice.");
|
|
@@ -690,7 +914,8 @@ var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
|
690
914
|
return {
|
|
691
915
|
pr: body.pr,
|
|
692
916
|
verify: typeof body.verify === "string" ? body.verify : void 0,
|
|
693
|
-
disposable: body.disposable !== false
|
|
917
|
+
disposable: body.disposable !== false,
|
|
918
|
+
mintToHash: body.mintToHash === true
|
|
694
919
|
};
|
|
695
920
|
};
|
|
696
921
|
var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
@@ -704,6 +929,156 @@ var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
|
704
929
|
pr: body.pr
|
|
705
930
|
};
|
|
706
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
|
+
};
|
|
707
1082
|
|
|
708
1083
|
// src/index.ts
|
|
709
1084
|
var createClient = (options = {}) => ({
|
|
@@ -719,8 +1094,14 @@ var createClient = (options = {}) => ({
|
|
|
719
1094
|
mergeNotesWithHash: (callback, k1s, h) => mergeNotesWithHash(callback, k1s, h, options),
|
|
720
1095
|
settleNote: (baseUrl, k1, expectedAmountMsat, signature) => settleNote(baseUrl, k1, expectedAmountMsat, signature, options),
|
|
721
1096
|
fetchPayRequest: (url) => fetchPayRequest(url, options),
|
|
722
|
-
|
|
723
|
-
|
|
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)
|
|
724
1105
|
});
|
|
725
1106
|
|
|
726
|
-
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, mintFeeBand, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureMessage, parseMintFee, probeBurnedNote, requestInvoice, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, verifyNoteSignature, withNewK1, withinMintFeeBand, 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 };
|