lnurlcash-kit 0.1.2 → 0.2.1
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 +342 -0
- package/README.md +314 -0
- package/dist/index.d.ts +169 -68
- package/dist/index.js +531 -135
- 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,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);
|
|
@@ -488,7 +649,22 @@ var fetchNoteInfo = async (url, options = {}) => {
|
|
|
488
649
|
"The service echoed back a different k1 than was queried - the note may have been redeemed elsewhere, or the service isn't spec-compliant."
|
|
489
650
|
);
|
|
490
651
|
}
|
|
491
|
-
|
|
652
|
+
const info = body;
|
|
653
|
+
const payLink = sameOriginPayLink(body.payLink, reqUrl);
|
|
654
|
+
if (payLink === void 0) delete info.payLink;
|
|
655
|
+
else info.payLink = payLink;
|
|
656
|
+
return info;
|
|
657
|
+
};
|
|
658
|
+
var sameOriginPayLink = (value, noteUrl) => {
|
|
659
|
+
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
660
|
+
let candidate;
|
|
661
|
+
try {
|
|
662
|
+
candidate = new URL(value, noteUrl);
|
|
663
|
+
} catch {
|
|
664
|
+
return void 0;
|
|
665
|
+
}
|
|
666
|
+
if (candidate.origin !== noteUrl.origin) return void 0;
|
|
667
|
+
return candidate.toString();
|
|
492
668
|
};
|
|
493
669
|
var probeBurnedNote = async (url, options = {}) => {
|
|
494
670
|
try {
|
|
@@ -501,18 +677,63 @@ var probeBurnedNote = async (url, options = {}) => {
|
|
|
501
677
|
return "unknown";
|
|
502
678
|
}
|
|
503
679
|
};
|
|
680
|
+
var asString = (value) => typeof value === "string" ? value : void 0;
|
|
681
|
+
var asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
682
|
+
var asContact = (value) => {
|
|
683
|
+
if (!value || typeof value !== "object") return void 0;
|
|
684
|
+
const raw = value;
|
|
685
|
+
const contact = {
|
|
686
|
+
nostr: asString(raw.nostr),
|
|
687
|
+
email: asString(raw.email),
|
|
688
|
+
url: asString(raw.url)
|
|
689
|
+
};
|
|
690
|
+
const any = contact.nostr ?? contact.email ?? contact.url;
|
|
691
|
+
return any === void 0 ? void 0 : contact;
|
|
692
|
+
};
|
|
693
|
+
var asFees = (value) => {
|
|
694
|
+
if (!value || typeof value !== "object") return void 0;
|
|
695
|
+
const raw = value;
|
|
696
|
+
const baseFeeMsat = asNumber(raw.baseFeeMsat);
|
|
697
|
+
const feePpm = asNumber(raw.feePpm);
|
|
698
|
+
if (baseFeeMsat === void 0 && feePpm === void 0) return void 0;
|
|
699
|
+
return { baseFeeMsat: baseFeeMsat ?? 0, feePpm: feePpm ?? 0 };
|
|
700
|
+
};
|
|
701
|
+
var asBoolean = (value) => typeof value === "boolean" ? value : void 0;
|
|
702
|
+
var asPubkeyList = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : void 0;
|
|
504
703
|
var fetchMintAddress = async (url, options = {}) => {
|
|
505
704
|
const body = await lnurlFetch(url, resolveOptions(options));
|
|
506
705
|
if (body?.tag !== "withdrawRequest" || typeof body.callback !== "string" || typeof body.payLink !== "string" || typeof body.maxWithdrawable !== "number") {
|
|
507
706
|
throw new ProtocolError("Not a mint address response (unexpected shape).");
|
|
508
707
|
}
|
|
509
|
-
const { mintPubkey, nodeCapacity, ...rest } = body;
|
|
510
708
|
return {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
709
|
+
tag: "withdrawRequest",
|
|
710
|
+
callback: body.callback,
|
|
711
|
+
minWithdrawable: asNumber(body.minWithdrawable) ?? 0,
|
|
712
|
+
maxWithdrawable: body.maxWithdrawable,
|
|
713
|
+
defaultDescription: asString(body.defaultDescription),
|
|
714
|
+
mintPubkey: asString(body.mintPubkey),
|
|
715
|
+
// the same value under both names for one release, so nothing breaks
|
|
716
|
+
nodePubkey: asString(body.mintPubkey),
|
|
717
|
+
payLink: body.payLink,
|
|
718
|
+
nodeAlias: asString(body.nodeAlias),
|
|
719
|
+
nodeUri: asString(body.nodeUri),
|
|
720
|
+
nodeColor: asString(body.nodeColor),
|
|
721
|
+
// `nodeCapacity` is the wire name the reference mint, the mock and
|
|
722
|
+
// every implementation that copied them use. One live mint emits
|
|
723
|
+
// `nodeCapacityMsat` instead, so both are accepted and the bare name
|
|
724
|
+
// wins where a SERVICE sends both.
|
|
725
|
+
nodeCapacityMsat: asNumber(body.nodeCapacity) ?? asNumber(body.nodeCapacityMsat),
|
|
726
|
+
nodeNumChannels: asNumber(body.nodeNumChannels),
|
|
727
|
+
nodeNumPeers: asNumber(body.nodeNumPeers),
|
|
728
|
+
name: asString(body.name),
|
|
729
|
+
description: asString(body.description),
|
|
730
|
+
contact: asContact(body.contact),
|
|
731
|
+
tosUrl: asString(body.tosUrl),
|
|
732
|
+
motd: asString(body.motd),
|
|
733
|
+
fees: asFees(body.fees),
|
|
734
|
+
version: asString(body.version),
|
|
735
|
+
previousPubkeys: asPubkeyList(body.previousPubkeys),
|
|
736
|
+
mintToHash: asBoolean(body.mintToHash)
|
|
516
737
|
};
|
|
517
738
|
};
|
|
518
739
|
var callbackRequest = async (callback, params, options) => {
|
|
@@ -592,6 +813,12 @@ var mergeNotesWithHash = async (callback, k1s, h, options = {}) => {
|
|
|
592
813
|
);
|
|
593
814
|
return { signature: body.sig };
|
|
594
815
|
};
|
|
816
|
+
var keepingOutputs = (err, newSecrets) => {
|
|
817
|
+
if (err instanceof NoteSpentError || err instanceof NoteUnknownError) {
|
|
818
|
+
err.newSecrets = newSecrets;
|
|
819
|
+
}
|
|
820
|
+
return err;
|
|
821
|
+
};
|
|
595
822
|
var rotateNote = async (callback, k1, options = {}) => {
|
|
596
823
|
const opts = resolveOptions(options);
|
|
597
824
|
const newK1 = opts.randomSecret();
|
|
@@ -602,7 +829,7 @@ var rotateNote = async (callback, k1, options = {}) => {
|
|
|
602
829
|
if (err instanceof AmbiguousMintError) {
|
|
603
830
|
throw new AmbiguousMutationError(err.message, [newK1]);
|
|
604
831
|
}
|
|
605
|
-
throw err;
|
|
832
|
+
throw keepingOutputs(err, [newK1]);
|
|
606
833
|
}
|
|
607
834
|
};
|
|
608
835
|
var splitNote = async (callback, k1s, amountMsat, options = {}) => {
|
|
@@ -628,7 +855,7 @@ var splitNote = async (callback, k1s, amountMsat, options = {}) => {
|
|
|
628
855
|
if (err instanceof AmbiguousMintError) {
|
|
629
856
|
throw new AmbiguousMutationError(err.message, [newK1, changeK1]);
|
|
630
857
|
}
|
|
631
|
-
throw err;
|
|
858
|
+
throw keepingOutputs(err, [newK1, changeK1]);
|
|
632
859
|
}
|
|
633
860
|
};
|
|
634
861
|
var mergeNotes = async (callback, k1s, options = {}) => {
|
|
@@ -641,7 +868,7 @@ var mergeNotes = async (callback, k1s, options = {}) => {
|
|
|
641
868
|
if (err instanceof AmbiguousMintError) {
|
|
642
869
|
throw new AmbiguousMutationError(err.message, [newK1]);
|
|
643
870
|
}
|
|
644
|
-
throw err;
|
|
871
|
+
throw keepingOutputs(err, [newK1]);
|
|
645
872
|
}
|
|
646
873
|
};
|
|
647
874
|
var settleNote = async (baseUrl, k1, expectedAmountMsat, signature, options = {}) => {
|
|
@@ -672,11 +899,23 @@ var fetchPayRequest = async (url, options = {}) => {
|
|
|
672
899
|
throw new ProtocolError("Not a payRequest (unexpected response).");
|
|
673
900
|
}
|
|
674
901
|
const mintFee = typeof body.metadata === "string" ? parseMintFee(body.metadata) : null;
|
|
675
|
-
return {
|
|
902
|
+
return {
|
|
903
|
+
...body,
|
|
904
|
+
mintFee: mintFee ?? void 0,
|
|
905
|
+
mintToHash: asBoolean(body.mintToHash)
|
|
906
|
+
};
|
|
676
907
|
};
|
|
677
908
|
var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
678
909
|
const cbUrl = new URL(payCallback);
|
|
679
910
|
cbUrl.searchParams.set("amount", String(amountMsat));
|
|
911
|
+
if (options.h !== void 0) {
|
|
912
|
+
if (!isPreimage(options.h)) {
|
|
913
|
+
throw new RequestRefusedError(
|
|
914
|
+
"An output hash must be 32 bytes of hex - no invoice was requested."
|
|
915
|
+
);
|
|
916
|
+
}
|
|
917
|
+
cbUrl.searchParams.set("h", options.h.trim().toLowerCase());
|
|
918
|
+
}
|
|
680
919
|
const body = await lnurlFetch(cbUrl, resolveOptions(options));
|
|
681
920
|
if (typeof body?.pr !== "string") {
|
|
682
921
|
throw new ProtocolError("The service did not return an invoice.");
|
|
@@ -690,7 +929,8 @@ var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
|
690
929
|
return {
|
|
691
930
|
pr: body.pr,
|
|
692
931
|
verify: typeof body.verify === "string" ? body.verify : void 0,
|
|
693
|
-
disposable: body.disposable !== false
|
|
932
|
+
disposable: body.disposable !== false,
|
|
933
|
+
mintToHash: body.mintToHash === true
|
|
694
934
|
};
|
|
695
935
|
};
|
|
696
936
|
var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
@@ -704,6 +944,156 @@ var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
|
704
944
|
pr: body.pr
|
|
705
945
|
};
|
|
706
946
|
};
|
|
947
|
+
var claimMintedNote = async (withdrawLink, k1, options = {}) => {
|
|
948
|
+
const secret = k1.trim().toLowerCase();
|
|
949
|
+
if (!isPreimage(secret)) {
|
|
950
|
+
throw new RequestRefusedError(
|
|
951
|
+
"A note secret must be 32 bytes of hex - nothing was sent."
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
const blank = {
|
|
955
|
+
k1: secret,
|
|
956
|
+
amountMsat: null,
|
|
957
|
+
callback: null
|
|
958
|
+
};
|
|
959
|
+
try {
|
|
960
|
+
const info = await fetchNoteInfo(buildNoteUrl(withdrawLink, secret), options);
|
|
961
|
+
return {
|
|
962
|
+
state: "minted",
|
|
963
|
+
k1: secret,
|
|
964
|
+
amountMsat: info.maxWithdrawable,
|
|
965
|
+
callback: info.callback
|
|
966
|
+
};
|
|
967
|
+
} catch (err) {
|
|
968
|
+
if (err instanceof PendingNoteError) return { ...blank, state: "pending" };
|
|
969
|
+
if (err instanceof NoteSpentError) return { ...blank, state: "spent" };
|
|
970
|
+
if (err instanceof NoteUnknownError) return { ...blank, state: "unminted" };
|
|
971
|
+
throw err;
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
var LIGHTNING_SIGNED_MESSAGE_PREFIX = utf8ToBytes("Lightning Signed Message:");
|
|
975
|
+
var noteSignatureMessage = (k1, amountMsat) => `LNURLcash:${amountMsat}:${hashK1(k1)}`;
|
|
976
|
+
var noteSignatureDigest = (k1, amountMsat) => sha256(
|
|
977
|
+
sha256(
|
|
978
|
+
new Uint8Array([
|
|
979
|
+
...LIGHTNING_SIGNED_MESSAGE_PREFIX,
|
|
980
|
+
...utf8ToBytes(noteSignatureMessage(k1, amountMsat))
|
|
981
|
+
])
|
|
982
|
+
)
|
|
983
|
+
);
|
|
984
|
+
var NO_MATCH = { valid: false, pubkey: null };
|
|
985
|
+
var verifyNoteSignatureAgainst = (k1, amountMsat, signatureHex, mintPubkeys) => {
|
|
986
|
+
const targets = (Array.isArray(mintPubkeys) ? mintPubkeys : [mintPubkeys]).filter((key) => typeof key === "string").map((key) => key.trim().toLowerCase());
|
|
987
|
+
if (targets.length === 0) return NO_MATCH;
|
|
988
|
+
let wireSig;
|
|
989
|
+
try {
|
|
990
|
+
wireSig = hexToBytes(signatureHex);
|
|
991
|
+
} catch {
|
|
992
|
+
return NO_MATCH;
|
|
993
|
+
}
|
|
994
|
+
if (wireSig.length !== 65) return NO_MATCH;
|
|
995
|
+
let digest;
|
|
996
|
+
try {
|
|
997
|
+
digest = noteSignatureDigest(k1, amountMsat);
|
|
998
|
+
} catch {
|
|
999
|
+
return NO_MATCH;
|
|
1000
|
+
}
|
|
1001
|
+
const recoveryIdFirst = new Uint8Array([
|
|
1002
|
+
wireSig[64],
|
|
1003
|
+
...wireSig.subarray(0, 64)
|
|
1004
|
+
]);
|
|
1005
|
+
for (const candidate of [recoveryIdFirst, wireSig]) {
|
|
1006
|
+
try {
|
|
1007
|
+
const recovered = bytesToHex(
|
|
1008
|
+
secp256k1.recoverPublicKey(candidate, digest, { prehash: false })
|
|
1009
|
+
);
|
|
1010
|
+
if (targets.includes(recovered)) return { valid: true, pubkey: recovered };
|
|
1011
|
+
} catch {
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
return NO_MATCH;
|
|
1015
|
+
};
|
|
1016
|
+
var verifyNoteSignature = (k1, amountMsat, signatureHex, mintPubkeys) => verifyNoteSignatureAgainst(k1, amountMsat, signatureHex, mintPubkeys).valid;
|
|
1017
|
+
|
|
1018
|
+
// src/settle.ts
|
|
1019
|
+
var normaliseHost = (value) => serverOf(value.trim().replace(/^@/, "")).toLowerCase();
|
|
1020
|
+
var settleNoteForValue = async (noteUrl, { mints, minMsat, requireSignature = false }, options = {}) => {
|
|
1021
|
+
const url = resolveNoteInput(noteUrl);
|
|
1022
|
+
if (!url) {
|
|
1023
|
+
throw new RequestRefusedError(
|
|
1024
|
+
"That is not a bearer note this library will fetch."
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
const host = serverOf(url).toLowerCase();
|
|
1028
|
+
const accepted = mints.map(normaliseHost);
|
|
1029
|
+
if (!accepted.includes(host)) {
|
|
1030
|
+
throw new ServiceRejectedError(
|
|
1031
|
+
accepted.length === 0 ? "This server accepts no mints." : `Notes from ${host} are not accepted here.`
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
const k1 = requireNoteK1(url);
|
|
1035
|
+
const info = await fetchNoteInfo(url, options);
|
|
1036
|
+
if (requireSignature) {
|
|
1037
|
+
const signature = noteSignature(url);
|
|
1038
|
+
if (!signature) {
|
|
1039
|
+
throw new ServiceRejectedError("This note carries no signature.");
|
|
1040
|
+
}
|
|
1041
|
+
if (!info.mintPubkey) {
|
|
1042
|
+
throw new ServiceRejectedError(
|
|
1043
|
+
"The mint published no signing key to check this note against."
|
|
1044
|
+
);
|
|
1045
|
+
}
|
|
1046
|
+
if (!verifyNoteSignature(k1, info.maxWithdrawable, signature, info.mintPubkey)) {
|
|
1047
|
+
throw new ServiceRejectedError("This note's signature does not verify.");
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
if (info.maxWithdrawable < minMsat) {
|
|
1051
|
+
throw new InsufficientValueError(info.maxWithdrawable, minMsat);
|
|
1052
|
+
}
|
|
1053
|
+
const rotated = await rotateNote(info.callback, k1, options);
|
|
1054
|
+
return {
|
|
1055
|
+
note: {
|
|
1056
|
+
k1: rotated.k1,
|
|
1057
|
+
amountMsat: info.maxWithdrawable,
|
|
1058
|
+
signature: rotated.signature,
|
|
1059
|
+
callback: info.callback
|
|
1060
|
+
},
|
|
1061
|
+
newUrl: withNewK1(url, rotated.k1, info.maxWithdrawable, rotated.signature)
|
|
1062
|
+
};
|
|
1063
|
+
};
|
|
1064
|
+
|
|
1065
|
+
// src/restore.ts
|
|
1066
|
+
var restoreNotes = async (baseUrl, root, host, { gap = 20, start = 0 } = {}, options = {}) => {
|
|
1067
|
+
if (!Number.isSafeInteger(gap) || gap < 1) {
|
|
1068
|
+
throw new RangeError(`The gap limit must be a positive integer, not ${gap}.`);
|
|
1069
|
+
}
|
|
1070
|
+
const found = [];
|
|
1071
|
+
let lastUsed = null;
|
|
1072
|
+
let unknownRun = 0;
|
|
1073
|
+
for (let index = start; unknownRun < gap; index++) {
|
|
1074
|
+
const k1 = deriveNoteSecret(root, host, index);
|
|
1075
|
+
try {
|
|
1076
|
+
const info = await fetchNoteInfo(buildNoteUrl(baseUrl, k1), options);
|
|
1077
|
+
found.push({ index, k1, amountMsat: info.maxWithdrawable, state: "live" });
|
|
1078
|
+
lastUsed = index;
|
|
1079
|
+
unknownRun = 0;
|
|
1080
|
+
} catch (err) {
|
|
1081
|
+
if (err instanceof PendingNoteError) {
|
|
1082
|
+
found.push({ index, k1, amountMsat: null, state: "pending" });
|
|
1083
|
+
lastUsed = index;
|
|
1084
|
+
unknownRun = 0;
|
|
1085
|
+
} else if (err instanceof NoteSpentError) {
|
|
1086
|
+
lastUsed = index;
|
|
1087
|
+
unknownRun = 0;
|
|
1088
|
+
} else if (err instanceof NoteUnknownError) {
|
|
1089
|
+
unknownRun++;
|
|
1090
|
+
} else {
|
|
1091
|
+
throw err;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
return { found, next: lastUsed === null ? start : lastUsed + 1 };
|
|
1096
|
+
};
|
|
707
1097
|
|
|
708
1098
|
// src/index.ts
|
|
709
1099
|
var createClient = (options = {}) => ({
|
|
@@ -719,8 +1109,14 @@ var createClient = (options = {}) => ({
|
|
|
719
1109
|
mergeNotesWithHash: (callback, k1s, h) => mergeNotesWithHash(callback, k1s, h, options),
|
|
720
1110
|
settleNote: (baseUrl, k1, expectedAmountMsat, signature) => settleNote(baseUrl, k1, expectedAmountMsat, signature, options),
|
|
721
1111
|
fetchPayRequest: (url) => fetchPayRequest(url, options),
|
|
722
|
-
|
|
723
|
-
|
|
1112
|
+
// `h` names the note the invoice will mint - see requestInvoice. The
|
|
1113
|
+
// bound transport options are merged under it, so a caller can still
|
|
1114
|
+
// reach for a one-off override.
|
|
1115
|
+
requestInvoice: (payCallback, amountMsat, h) => requestInvoice(payCallback, amountMsat, { ...options, h }),
|
|
1116
|
+
fetchInvoiceVerification: (verifyUrl) => fetchInvoiceVerification(verifyUrl, options),
|
|
1117
|
+
claimMintedNote: (withdrawLink, k1) => claimMintedNote(withdrawLink, k1, options),
|
|
1118
|
+
restoreNotes: (baseUrl, root, host, restoreOptions = {}) => restoreNotes(baseUrl, root, host, restoreOptions, options),
|
|
1119
|
+
settleNoteForValue: (noteUrl, terms) => settleNoteForValue(noteUrl, terms, options)
|
|
724
1120
|
});
|
|
725
1121
|
|
|
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 };
|
|
1122
|
+
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 };
|