lnurlcash-kit 0.2.1 → 0.4.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 +78 -0
- package/README.md +41 -0
- package/dist/index.d.ts +42 -2
- package/dist/index.js +239 -55
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,84 @@
|
|
|
3
3
|
Semantic versioning. While the LUD-25 draft is unmerged, `0.x` minor bumps
|
|
4
4
|
may carry breaking changes; pin an exact version.
|
|
5
5
|
|
|
6
|
+
## 0.4.0 - 2026-08-26
|
|
7
|
+
|
|
8
|
+
**Breaking: `restoreNotes` asks by hash, and no longer discloses note
|
|
9
|
+
secrets by default.**
|
|
10
|
+
|
|
11
|
+
The walk queries every index up to `gap` past the last one in use, and those
|
|
12
|
+
are exactly the indices the wallet is about to mint into next. Asking by
|
|
13
|
+
secret therefore published the next `gap` secrets the wallet would ever use,
|
|
14
|
+
in cleartext query strings, and the returned `next` then pointed straight at
|
|
15
|
+
one of them. A five-note wallet put twenty-five live-or-future secrets on the
|
|
16
|
+
wire and resumed at an index it had just disclosed.
|
|
17
|
+
|
|
18
|
+
- The walk now uses the informational GET's `h` parameter (LUD-25, "Checking
|
|
19
|
+
a note without exposing it"), so nothing spendable leaves the wallet.
|
|
20
|
+
`fetchNoteInfoByHash` and `buildNoteInfoUrlByHash` expose it directly, and
|
|
21
|
+
`RestoredNote.callback` carries the callback so a caller need not ask again
|
|
22
|
+
with the raw secret.
|
|
23
|
+
- `RestoreOptions.allowSecretDisclosure` (default `false`) permits the old
|
|
24
|
+
form as an explicit fallback, never automatically. When it is used,
|
|
25
|
+
`RestoreResult.disclosesSecrets` is `true` and `next` skips every index the
|
|
26
|
+
walk touched, because a disclosed secret is spent whether or not a note was
|
|
27
|
+
ever minted under it.
|
|
28
|
+
- `h` support is OPTIONAL in LUD-25 with no capability flag, so a SERVICE
|
|
29
|
+
that cannot answer by hash is indistinguishable from one holding none of
|
|
30
|
+
the notes asked about. A walk that never gets a positive answer now throws
|
|
31
|
+
`HashLookupUnsupportedError` rather than reporting an empty wallet.
|
|
32
|
+
`RestoreOptions.probeK1` supplies a positive control, and
|
|
33
|
+
`RestoreResult.hashLookupsConfirmed` reports what was established.
|
|
34
|
+
|
|
35
|
+
**`requestInvoice` names the mint output with a LUD-12 `comment`.**
|
|
36
|
+
|
|
37
|
+
LUD-25 specifies the mint-time output hash as `comment = hex(h)`, and that is
|
|
38
|
+
what a conforming SERVICE reads. The kit sent only `h`, a parameter one
|
|
39
|
+
implementation adopted before the comment form was written, so a wallet
|
|
40
|
+
naming an output against a conforming mint was silently ignored and its note
|
|
41
|
+
was keyed by the payment preimage instead.
|
|
42
|
+
|
|
43
|
+
- `comment` is now sent alongside `h`. A SERVICE reading either gets the same
|
|
44
|
+
hash; one reading neither behaves exactly as before.
|
|
45
|
+
- This matters beyond conformance: an unnamed mint's `k1` **is** the payment
|
|
46
|
+
preimage `P`, so a SERVICE offering LUD-21 `verify` on that payment hands
|
|
47
|
+
the note to whoever holds the verify URL. Naming the output is what makes
|
|
48
|
+
`verify` safe to offer at all.
|
|
49
|
+
|
|
50
|
+
- `PayRequestInfo.commentAllowed` is surfaced and `namesMintOutput()` decides
|
|
51
|
+
the capability from either spelling: LUD-25 advertises it as
|
|
52
|
+
`commentAllowed >= 64`, one mint shipped `mintToHash` first. One rule in
|
|
53
|
+
one place, because both directions of a wrong answer cost a note - read it
|
|
54
|
+
as no and the note is the payment preimage, published on the mint's verify
|
|
55
|
+
URL; read it as yes and the wallet waits for a note minted elsewhere.
|
|
56
|
+
|
|
57
|
+
**Also breaking: an unrecognised refusal no longer aborts a restore, and no
|
|
58
|
+
longer counts toward the gap.**
|
|
59
|
+
|
|
60
|
+
`classifyNoteError` falls through to a bare `ServiceRejectedError` for any
|
|
61
|
+
reason string it has no pattern for, and the walk rethrew it, so a single
|
|
62
|
+
unfamiliar reason from a SERVICE ended the whole restore. Any new note state
|
|
63
|
+
- expiry being the obvious one - would have done it.
|
|
64
|
+
|
|
65
|
+
- Only `NoteUnknownError` advances the gap counter now. Every other refusal
|
|
66
|
+
means the SERVICE knows the index, so it resets the counter and is reported
|
|
67
|
+
in the new `RestoreResult.unresolved`. Advancing it was how a walk could
|
|
68
|
+
terminate early and silently abandon live notes beyond the run.
|
|
69
|
+
- Transport and protocol failures still throw: a SERVICE being down is not a
|
|
70
|
+
statement about an index.
|
|
71
|
+
|
|
72
|
+
## 0.3.0 - 2026-08-24
|
|
73
|
+
|
|
74
|
+
- Additive bound-mint receipt parsing and validation for sealed signers:
|
|
75
|
+
`InvoiceResult.mint`, `VerifyResult.mint`, `requireBoundMintQuote` and
|
|
76
|
+
`validateBoundMintReceipt`. The quote must commit the requested `h` and
|
|
77
|
+
exact net amount before payment; the settled LUD-21 response must match the
|
|
78
|
+
invoice and commitment and carry a valid ordinary LUD-25 note signature.
|
|
79
|
+
- `noteSignatureMessageForHash`, `noteSignatureDigestForHash`,
|
|
80
|
+
`verifyNoteSignatureHash` and `verifyNoteSignatureHashAgainst` expose the
|
|
81
|
+
existing signature construction when a signer deliberately retains `k1`.
|
|
82
|
+
Existing secret-based helpers delegate to them unchanged.
|
|
83
|
+
|
|
6
84
|
## 0.2.1 - 2026-08-22
|
|
7
85
|
|
|
8
86
|
- `WithdrawRequestInfo` carries `payLink`, the way home a SERVICE may
|
package/README.md
CHANGED
|
@@ -358,6 +358,47 @@ is yours from the moment it exists. The claim GET does show the secret to the
|
|
|
358
358
|
mint it is a claim on, which is a different thing from showing it to whoever
|
|
359
359
|
scanned the QR, and you can still rotate if you want the offline signature.
|
|
360
360
|
|
|
361
|
+
### A sealed signer: confirm without exporting the secret
|
|
362
|
+
|
|
363
|
+
A hardware vault cannot use the claim GET above without handing `k1` to its
|
|
364
|
+
companion. A receipt-capable mint can instead commit the quote to the requested
|
|
365
|
+
`h` and exact net amount, then place the ordinary LUD-25 note signature on its
|
|
366
|
+
settled LUD-21 response. The extension is optional; absence means use the
|
|
367
|
+
unchanged preimage-import-and-rotate flow before showing an invoice.
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import {
|
|
371
|
+
requestInvoice, fetchInvoiceVerification,
|
|
372
|
+
requireBoundMintQuote, validateBoundMintReceipt
|
|
373
|
+
} from 'lnurlcash-kit'
|
|
374
|
+
|
|
375
|
+
const staged = await vault.newSecret() // {id, h}; k1 stays in the vault
|
|
376
|
+
const expectedNetMsat = 21_000
|
|
377
|
+
const quote = await requestInvoice(pay.callback, 21_000, {h: staged.h})
|
|
378
|
+
|
|
379
|
+
// Do this before displaying or paying quote.pr.
|
|
380
|
+
requireBoundMintQuote(quote, staged.h, expectedNetMsat)
|
|
381
|
+
if (!quote.verify) throw new Error('No settlement receipt offered')
|
|
382
|
+
|
|
383
|
+
// After payment, poll quote.verify until settled.
|
|
384
|
+
const verification = await fetchInvoiceVerification(quote.verify)
|
|
385
|
+
const receipt = validateBoundMintReceipt(
|
|
386
|
+
quote,
|
|
387
|
+
verification,
|
|
388
|
+
staged.h,
|
|
389
|
+
expectedNetMsat,
|
|
390
|
+
pinnedMintPubkeys
|
|
391
|
+
)
|
|
392
|
+
await vault.confirm(staged.id, receipt.amountMsat, mintHost, receipt.signature)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
`quote.mint` is `{h, amountMsat}` in the typed API (`amount` on the wire).
|
|
396
|
+
The settled response must repeat that commitment and add `signature` (`sig`
|
|
397
|
+
on the wire). Validation matches the invoice, output and amount, refuses a
|
|
398
|
+
pre-settlement signature, and recovers the signer against the pinned current
|
|
399
|
+
or previous mint keys. The payment preimage remains proof of payment; it never
|
|
400
|
+
replaces the vault's staged secret.
|
|
401
|
+
|
|
361
402
|
## Asking to be paid
|
|
362
403
|
|
|
363
404
|
"Send me 500 sat" today means handing over a Lightning Address, which is a
|
package/dist/index.d.ts
CHANGED
|
@@ -31,6 +31,9 @@ declare const grossUpForMintFee: (netMsat: number, fee: MintFee) => number;
|
|
|
31
31
|
declare const formatFeePercent: (ppm: number) => string;
|
|
32
32
|
declare const describeMintFee: (fee: MintFee) => string;
|
|
33
33
|
|
|
34
|
+
type NoteInfoByHash = Omit<WithdrawRequestInfo, 'k1'> & {
|
|
35
|
+
k1?: string;
|
|
36
|
+
};
|
|
34
37
|
type WithdrawRequestInfo = {
|
|
35
38
|
tag: 'withdrawRequest';
|
|
36
39
|
callback: string;
|
|
@@ -42,6 +45,7 @@ type WithdrawRequestInfo = {
|
|
|
42
45
|
payLink?: string;
|
|
43
46
|
};
|
|
44
47
|
declare const fetchNoteInfo: (url: string, options?: LnurlcashOptions) => Promise<WithdrawRequestInfo>;
|
|
48
|
+
declare const fetchNoteInfoByHash: (withdrawLink: string, h: string, options?: LnurlcashOptions) => Promise<NoteInfoByHash>;
|
|
45
49
|
declare const probeBurnedNote: (url: string, options?: LnurlcashOptions) => Promise<"live" | "gone" | "unknown">;
|
|
46
50
|
type MintContact = {
|
|
47
51
|
nostr?: string;
|
|
@@ -126,13 +130,27 @@ type PayRequestInfo = {
|
|
|
126
130
|
mintPubkey?: string;
|
|
127
131
|
mintFee?: MintFee;
|
|
128
132
|
mintToHash?: boolean;
|
|
133
|
+
commentAllowed?: number;
|
|
129
134
|
};
|
|
130
135
|
declare const fetchPayRequest: (url: string, options?: LnurlcashOptions) => Promise<PayRequestInfo>;
|
|
136
|
+
declare const namesMintOutput: (info: {
|
|
137
|
+
mintToHash?: boolean;
|
|
138
|
+
commentAllowed?: number;
|
|
139
|
+
}) => boolean;
|
|
131
140
|
type InvoiceResult = {
|
|
132
141
|
pr: string;
|
|
133
142
|
verify?: string;
|
|
134
143
|
disposable: boolean;
|
|
135
144
|
mintToHash: boolean;
|
|
145
|
+
mint?: BoundMintCommitment;
|
|
146
|
+
};
|
|
147
|
+
type BoundMintCommitment = {
|
|
148
|
+
h: string;
|
|
149
|
+
amountMsat: number;
|
|
150
|
+
signature?: string;
|
|
151
|
+
};
|
|
152
|
+
type ValidatedBoundMintReceipt = Required<BoundMintCommitment> & {
|
|
153
|
+
pubkey: string;
|
|
136
154
|
};
|
|
137
155
|
type InvoiceRequestOptions = LnurlcashOptions & {
|
|
138
156
|
h?: string;
|
|
@@ -142,8 +160,11 @@ type VerifyResult = {
|
|
|
142
160
|
settled: boolean;
|
|
143
161
|
preimage: string | null;
|
|
144
162
|
pr: string;
|
|
163
|
+
mint?: BoundMintCommitment;
|
|
145
164
|
};
|
|
146
165
|
declare const fetchInvoiceVerification: (verifyUrl: string, options?: LnurlcashOptions) => Promise<VerifyResult>;
|
|
166
|
+
declare const requireBoundMintQuote: (invoice: InvoiceResult, expectedH: string, expectedAmountMsat: number) => BoundMintCommitment;
|
|
167
|
+
declare const validateBoundMintReceipt: (invoice: InvoiceResult, verification: VerifyResult, expectedH: string, expectedAmountMsat: number, mintPubkeys: string | string[]) => ValidatedBoundMintReceipt;
|
|
147
168
|
type MintClaim = {
|
|
148
169
|
state: 'minted' | 'unminted' | 'pending' | 'spent';
|
|
149
170
|
k1: string;
|
|
@@ -168,16 +189,27 @@ type RestoredNote = {
|
|
|
168
189
|
k1: string;
|
|
169
190
|
amountMsat: number | null;
|
|
170
191
|
state: 'live' | 'pending';
|
|
192
|
+
callback?: string;
|
|
193
|
+
};
|
|
194
|
+
type UnresolvedIndex = {
|
|
195
|
+
index: number;
|
|
196
|
+
k1: string;
|
|
197
|
+
reason: string;
|
|
171
198
|
};
|
|
172
199
|
type RestoreResult = {
|
|
173
200
|
found: RestoredNote[];
|
|
201
|
+
unresolved: UnresolvedIndex[];
|
|
174
202
|
next: number;
|
|
203
|
+
hashLookupsConfirmed: boolean;
|
|
204
|
+
disclosesSecrets: boolean;
|
|
175
205
|
};
|
|
176
206
|
type RestoreOptions = {
|
|
177
207
|
gap?: number;
|
|
178
208
|
start?: number;
|
|
209
|
+
probeK1?: string;
|
|
210
|
+
allowSecretDisclosure?: boolean;
|
|
179
211
|
};
|
|
180
|
-
declare const restoreNotes: (baseUrl: string, root: Uint8Array, host: string, { gap, start }?: RestoreOptions, options?: LnurlcashOptions) => Promise<RestoreResult>;
|
|
212
|
+
declare const restoreNotes: (baseUrl: string, root: Uint8Array, host: string, { gap, start, probeK1, allowSecretDisclosure }?: RestoreOptions, options?: LnurlcashOptions) => Promise<RestoreResult>;
|
|
181
213
|
|
|
182
214
|
declare const isBech32Lnurl: (data: string) => boolean;
|
|
183
215
|
declare const toBech32Lnurl: (url: string) => string;
|
|
@@ -198,6 +230,7 @@ declare const noteDeclaredAmount: (url: string) => number | null;
|
|
|
198
230
|
declare const noteSignature: (url: string) => string | null;
|
|
199
231
|
declare const resolveNoteInput: (value: string) => string | null;
|
|
200
232
|
declare const isValidNoteInput: (value: string) => boolean;
|
|
233
|
+
declare const buildNoteInfoUrlByHash: (withdrawLink: string, h: string) => string;
|
|
201
234
|
declare const buildNoteUrl: (withdrawLink: string, k1: string, amountMsat?: number) => string;
|
|
202
235
|
declare const withNewK1: (url: string, k1: string, amountMsat: number, signature?: string) => string;
|
|
203
236
|
declare const withoutK1: (url: string, amountMsat: number, signature?: string) => string;
|
|
@@ -226,7 +259,9 @@ declare const decodePaymentRequest: (value: string, { now }?: DecodeOptions) =>
|
|
|
226
259
|
declare const isPaymentRequest: (value: string) => boolean;
|
|
227
260
|
|
|
228
261
|
declare const noteSignatureMessage: (k1: string, amountMsat: number) => string;
|
|
262
|
+
declare const noteSignatureMessageForHash: (h: string, amountMsat: number) => string;
|
|
229
263
|
declare const noteSignatureDigest: (k1: string, amountMsat: number) => Uint8Array;
|
|
264
|
+
declare const noteSignatureDigestForHash: (h: string, amountMsat: number) => Uint8Array;
|
|
230
265
|
type SignatureCheck = {
|
|
231
266
|
valid: true;
|
|
232
267
|
pubkey: string;
|
|
@@ -235,7 +270,9 @@ type SignatureCheck = {
|
|
|
235
270
|
pubkey: null;
|
|
236
271
|
};
|
|
237
272
|
declare const verifyNoteSignatureAgainst: (k1: string, amountMsat: number, signatureHex: string, mintPubkeys: string | string[]) => SignatureCheck;
|
|
273
|
+
declare const verifyNoteSignatureHashAgainst: (h: string, amountMsat: number, signatureHex: string, mintPubkeys: string | string[]) => SignatureCheck;
|
|
238
274
|
declare const verifyNoteSignature: (k1: string, amountMsat: number, signatureHex: string, mintPubkeys: string | string[]) => boolean;
|
|
275
|
+
declare const verifyNoteSignatureHash: (h: string, amountMsat: number, signatureHex: string, mintPubkeys: string | string[]) => boolean;
|
|
239
276
|
|
|
240
277
|
declare const isBolt11Invoice: (value: string) => boolean;
|
|
241
278
|
declare const sameInvoice: (a: string, b: string) => boolean;
|
|
@@ -262,6 +299,8 @@ declare class NoteSpentError extends ServiceRejectedError {
|
|
|
262
299
|
declare class NoteUnknownError extends ServiceRejectedError {
|
|
263
300
|
constructor(reason: string);
|
|
264
301
|
}
|
|
302
|
+
declare class HashLookupUnsupportedError extends LnurlcashError {
|
|
303
|
+
}
|
|
265
304
|
declare class AmbiguousMintError extends LnurlcashError {
|
|
266
305
|
}
|
|
267
306
|
declare class AmbiguousMutationError extends AmbiguousMintError {
|
|
@@ -278,6 +317,7 @@ declare const classifyNoteError: (reason: string) => ServiceRejectedError;
|
|
|
278
317
|
|
|
279
318
|
declare const createClient: (options?: LnurlcashOptions) => {
|
|
280
319
|
fetchNoteInfo: (url: string) => Promise<WithdrawRequestInfo>;
|
|
320
|
+
fetchNoteInfoByHash: (withdrawLink: string, h: string) => Promise<NoteInfoByHash>;
|
|
281
321
|
probeBurnedNote: (url: string) => Promise<"live" | "gone" | "unknown">;
|
|
282
322
|
fetchMintAddress: (url: string) => Promise<MintAddressInfo>;
|
|
283
323
|
meltNote: (callback: string, k1: string, pr: string) => Promise<MeltResult>;
|
|
@@ -297,4 +337,4 @@ declare const createClient: (options?: LnurlcashOptions) => {
|
|
|
297
337
|
};
|
|
298
338
|
type LnurlcashClient = ReturnType<typeof createClient>;
|
|
299
339
|
|
|
300
|
-
export { AmbiguousMintError, AmbiguousMutationError, type DecodeOptions, type HashedMutationResult, type HashedSplitResult, InsufficientValueError, type InvoiceRequestOptions, type InvoiceResult, type LnurlcashClient, LnurlcashError, type LnurlcashOptions, type MeltResult, type MintAddressInfo, type MintClaim, type MintContact, type MintFee, type MintFeeBand, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, type PayRequestInfo, type PaymentRequest, type PaymentRequestMethodDetails, PendingNoteError, ProtocolError, type RandomSecret, RequestRefusedError, type RestoreOptions, type RestoreResult, type RestoredNote, type RotateResult, ServiceRejectedError, type SettleForValueOptions, type SettledForValue, type SettledNote, type SignatureCheck, type SplitResult, type VerifyResult, type WithdrawRequestInfo, type WithdrawSuccessResponse, 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 };
|
|
340
|
+
export { AmbiguousMintError, AmbiguousMutationError, type BoundMintCommitment, type DecodeOptions, HashLookupUnsupportedError, type HashedMutationResult, type HashedSplitResult, InsufficientValueError, type InvoiceRequestOptions, type InvoiceResult, type LnurlcashClient, LnurlcashError, type LnurlcashOptions, type MeltResult, type MintAddressInfo, type MintClaim, type MintContact, type MintFee, type MintFeeBand, type NoteInfoByHash, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, type PayRequestInfo, type PaymentRequest, type PaymentRequestMethodDetails, PendingNoteError, ProtocolError, type RandomSecret, RequestRefusedError, type RestoreOptions, type RestoreResult, type RestoredNote, type RotateResult, ServiceRejectedError, type SettleForValueOptions, type SettledForValue, type SettledNote, type SignatureCheck, type SplitResult, type UnresolvedIndex, type ValidatedBoundMintReceipt, type VerifyResult, type WithdrawRequestInfo, type WithdrawSuccessResponse, applyMintFee, buildNoteInfoUrlByHash, buildNoteUrl, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodePaymentRequest, defaultRandomSecret, deriveNoteRoot, deriveNoteSecret, derivedSecretSource, describeMintFee, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchNoteInfoByHash, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, namesMintOutput, newSecretsOf, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureDigestForHash, noteSignatureMessage, noteSignatureMessageForHash, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, requestInvoice, requireBoundMintQuote, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, validateBoundMintReceipt, verifyNoteSignature, verifyNoteSignatureAgainst, verifyNoteSignatureHash, verifyNoteSignatureHashAgainst, withNewK1, withinMintFeeBand, withoutK1 };
|
package/dist/index.js
CHANGED
|
@@ -182,6 +182,18 @@ var resolveNoteInput = (value) => {
|
|
|
182
182
|
return url;
|
|
183
183
|
};
|
|
184
184
|
var isValidNoteInput = (value) => resolveNoteInput(value) !== null;
|
|
185
|
+
var buildNoteInfoUrlByHash = (withdrawLink, h) => {
|
|
186
|
+
const hex = h.trim().toLowerCase();
|
|
187
|
+
if (!/^[0-9a-f]{64}$/.test(hex)) {
|
|
188
|
+
throw new Error("A note hash must be 32 bytes of hex.");
|
|
189
|
+
}
|
|
190
|
+
const url = new URL(fromLud17(withdrawLink.trim()));
|
|
191
|
+
url.searchParams.delete("k1");
|
|
192
|
+
url.searchParams.delete("amount");
|
|
193
|
+
url.searchParams.delete("sig");
|
|
194
|
+
url.searchParams.set("h", hex);
|
|
195
|
+
return url.toString();
|
|
196
|
+
};
|
|
185
197
|
var buildNoteUrl = (withdrawLink, k1, amountMsat) => {
|
|
186
198
|
const url = new URL(fromLud17(withdrawLink.trim()));
|
|
187
199
|
url.searchParams.set("k1", k1.trim().toLowerCase());
|
|
@@ -248,6 +260,8 @@ var NoteUnknownError = class extends ServiceRejectedError {
|
|
|
248
260
|
this.message = `The service doesn't recognise this note (service says: "${reason}").`;
|
|
249
261
|
}
|
|
250
262
|
};
|
|
263
|
+
var HashLookupUnsupportedError = class extends LnurlcashError {
|
|
264
|
+
};
|
|
251
265
|
var AmbiguousMintError = class extends LnurlcashError {
|
|
252
266
|
};
|
|
253
267
|
var AmbiguousMutationError = class extends AmbiguousMintError {
|
|
@@ -627,8 +641,69 @@ var describeMintFee = (fee) => [
|
|
|
627
641
|
fee.baseFeeMsat > 0 ? `${Math.round(fee.baseFeeMsat / 1e3)} sat flat` : null,
|
|
628
642
|
fee.feePpm > 0 ? `${formatFeePercent(fee.feePpm)}% of the amount paid` : null
|
|
629
643
|
].filter(Boolean).join(" + ");
|
|
644
|
+
var LIGHTNING_SIGNED_MESSAGE_PREFIX = utf8ToBytes("Lightning Signed Message:");
|
|
645
|
+
var noteSignatureMessage = (k1, amountMsat) => noteSignatureMessageForHash(hashK1(k1), amountMsat);
|
|
646
|
+
var noteSignatureMessageForHash = (h, amountMsat) => `LNURLcash:${amountMsat}:${h.trim().toLowerCase()}`;
|
|
647
|
+
var noteSignatureDigest = (k1, amountMsat) => noteSignatureDigestForHash(hashK1(k1), amountMsat);
|
|
648
|
+
var noteSignatureDigestForHash = (h, amountMsat) => sha256(
|
|
649
|
+
sha256(
|
|
650
|
+
new Uint8Array([
|
|
651
|
+
...LIGHTNING_SIGNED_MESSAGE_PREFIX,
|
|
652
|
+
...utf8ToBytes(noteSignatureMessageForHash(h, amountMsat))
|
|
653
|
+
])
|
|
654
|
+
)
|
|
655
|
+
);
|
|
656
|
+
var NO_MATCH = { valid: false, pubkey: null };
|
|
657
|
+
var verifyNoteSignatureAgainst = (k1, amountMsat, signatureHex, mintPubkeys) => {
|
|
658
|
+
let h;
|
|
659
|
+
try {
|
|
660
|
+
h = hashK1(k1);
|
|
661
|
+
} catch {
|
|
662
|
+
return NO_MATCH;
|
|
663
|
+
}
|
|
664
|
+
return verifyNoteSignatureHashAgainst(h, amountMsat, signatureHex, mintPubkeys);
|
|
665
|
+
};
|
|
666
|
+
var verifyNoteSignatureHashAgainst = (h, amountMsat, signatureHex, mintPubkeys) => {
|
|
667
|
+
const targets = (Array.isArray(mintPubkeys) ? mintPubkeys : [mintPubkeys]).filter((key) => typeof key === "string").map((key) => key.trim().toLowerCase());
|
|
668
|
+
if (targets.length === 0) return NO_MATCH;
|
|
669
|
+
let wireSig;
|
|
670
|
+
try {
|
|
671
|
+
wireSig = hexToBytes(signatureHex);
|
|
672
|
+
} catch {
|
|
673
|
+
return NO_MATCH;
|
|
674
|
+
}
|
|
675
|
+
if (wireSig.length !== 65) return NO_MATCH;
|
|
676
|
+
let digest;
|
|
677
|
+
try {
|
|
678
|
+
if (!/^[0-9a-fA-F]{64}$/.test(h.trim())) return NO_MATCH;
|
|
679
|
+
digest = noteSignatureDigestForHash(h, amountMsat);
|
|
680
|
+
} catch {
|
|
681
|
+
return NO_MATCH;
|
|
682
|
+
}
|
|
683
|
+
const recoveryIdFirst = new Uint8Array([
|
|
684
|
+
wireSig[64],
|
|
685
|
+
...wireSig.subarray(0, 64)
|
|
686
|
+
]);
|
|
687
|
+
for (const candidate of [recoveryIdFirst, wireSig]) {
|
|
688
|
+
try {
|
|
689
|
+
const recovered = bytesToHex(
|
|
690
|
+
secp256k1.recoverPublicKey(candidate, digest, { prehash: false })
|
|
691
|
+
);
|
|
692
|
+
if (targets.includes(recovered)) return { valid: true, pubkey: recovered };
|
|
693
|
+
} catch {
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return NO_MATCH;
|
|
697
|
+
};
|
|
698
|
+
var verifyNoteSignature = (k1, amountMsat, signatureHex, mintPubkeys) => verifyNoteSignatureAgainst(k1, amountMsat, signatureHex, mintPubkeys).valid;
|
|
699
|
+
var verifyNoteSignatureHash = (h, amountMsat, signatureHex, mintPubkeys) => verifyNoteSignatureHashAgainst(h, amountMsat, signatureHex, mintPubkeys).valid;
|
|
630
700
|
|
|
631
701
|
// src/client.ts
|
|
702
|
+
var assertWithdrawRequestShape = (body, { requireK1 }) => {
|
|
703
|
+
if (body?.tag !== "withdrawRequest" || typeof body.callback !== "string" || requireK1 && typeof body.k1 !== "string" || typeof body.maxWithdrawable !== "number" || !Number.isSafeInteger(body.maxWithdrawable) || body.maxWithdrawable < 0 || body.minWithdrawable !== void 0 && (typeof body.minWithdrawable !== "number" || !Number.isSafeInteger(body.minWithdrawable) || body.minWithdrawable < 0 || body.minWithdrawable > body.maxWithdrawable)) {
|
|
704
|
+
throw new ProtocolError("Not a withdrawRequest (unexpected response).");
|
|
705
|
+
}
|
|
706
|
+
};
|
|
632
707
|
var fetchNoteInfo = async (url, options = {}) => {
|
|
633
708
|
const opts = resolveOptions(options);
|
|
634
709
|
const reqUrl = new URL(url);
|
|
@@ -640,9 +715,7 @@ var fetchNoteInfo = async (url, options = {}) => {
|
|
|
640
715
|
if (err instanceof ServiceRejectedError) throw classifyNoteError(err.reason);
|
|
641
716
|
throw err;
|
|
642
717
|
}
|
|
643
|
-
|
|
644
|
-
throw new ProtocolError("Not a withdrawRequest (unexpected response).");
|
|
645
|
-
}
|
|
718
|
+
assertWithdrawRequestShape(body, { requireK1: true });
|
|
646
719
|
const queried = noteK1(url);
|
|
647
720
|
if (queried && body.k1.toLowerCase() !== queried) {
|
|
648
721
|
throw new ProtocolError(
|
|
@@ -655,6 +728,23 @@ var fetchNoteInfo = async (url, options = {}) => {
|
|
|
655
728
|
else info.payLink = payLink;
|
|
656
729
|
return info;
|
|
657
730
|
};
|
|
731
|
+
var fetchNoteInfoByHash = async (withdrawLink, h, options = {}) => {
|
|
732
|
+
const opts = resolveOptions(options);
|
|
733
|
+
const reqUrl = new URL(buildNoteInfoUrlByHash(withdrawLink, h));
|
|
734
|
+
let body;
|
|
735
|
+
try {
|
|
736
|
+
body = await lnurlFetch(reqUrl, opts);
|
|
737
|
+
} catch (err) {
|
|
738
|
+
if (err instanceof ServiceRejectedError) throw classifyNoteError(err.reason);
|
|
739
|
+
throw err;
|
|
740
|
+
}
|
|
741
|
+
assertWithdrawRequestShape(body, { requireK1: false });
|
|
742
|
+
const info = body;
|
|
743
|
+
const payLink = sameOriginPayLink(body.payLink, reqUrl);
|
|
744
|
+
if (payLink === void 0) delete info.payLink;
|
|
745
|
+
else info.payLink = payLink;
|
|
746
|
+
return info;
|
|
747
|
+
};
|
|
658
748
|
var sameOriginPayLink = (value, noteUrl) => {
|
|
659
749
|
if (typeof value !== "string" || value.length === 0) return void 0;
|
|
660
750
|
let candidate;
|
|
@@ -902,7 +992,21 @@ var fetchPayRequest = async (url, options = {}) => {
|
|
|
902
992
|
return {
|
|
903
993
|
...body,
|
|
904
994
|
mintFee: mintFee ?? void 0,
|
|
905
|
-
mintToHash: asBoolean(body.mintToHash)
|
|
995
|
+
mintToHash: asBoolean(body.mintToHash),
|
|
996
|
+
commentAllowed: asNumber(body.commentAllowed)
|
|
997
|
+
};
|
|
998
|
+
};
|
|
999
|
+
var namesMintOutput = (info) => info.mintToHash === true || typeof info.commentAllowed === "number" && info.commentAllowed >= 64;
|
|
1000
|
+
var asBoundMintCommitment = (value) => {
|
|
1001
|
+
if (!value || typeof value !== "object") return void 0;
|
|
1002
|
+
const raw = value;
|
|
1003
|
+
if (typeof raw.h !== "string" || !/^[0-9a-fA-F]{64}$/.test(raw.h) || typeof raw.amount !== "number" || !Number.isSafeInteger(raw.amount) || raw.amount <= 0 || raw.sig !== void 0 && typeof raw.sig !== "string") {
|
|
1004
|
+
return void 0;
|
|
1005
|
+
}
|
|
1006
|
+
return {
|
|
1007
|
+
h: raw.h.toLowerCase(),
|
|
1008
|
+
amountMsat: raw.amount,
|
|
1009
|
+
...typeof raw.sig === "string" ? { signature: raw.sig } : {}
|
|
906
1010
|
};
|
|
907
1011
|
};
|
|
908
1012
|
var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
@@ -914,7 +1018,9 @@ var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
|
914
1018
|
"An output hash must be 32 bytes of hex - no invoice was requested."
|
|
915
1019
|
);
|
|
916
1020
|
}
|
|
917
|
-
|
|
1021
|
+
const h = options.h.trim().toLowerCase();
|
|
1022
|
+
cbUrl.searchParams.set("comment", h);
|
|
1023
|
+
cbUrl.searchParams.set("h", h);
|
|
918
1024
|
}
|
|
919
1025
|
const body = await lnurlFetch(cbUrl, resolveOptions(options));
|
|
920
1026
|
if (typeof body?.pr !== "string") {
|
|
@@ -930,7 +1036,8 @@ var requestInvoice = async (payCallback, amountMsat, options = {}) => {
|
|
|
930
1036
|
pr: body.pr,
|
|
931
1037
|
verify: typeof body.verify === "string" ? body.verify : void 0,
|
|
932
1038
|
disposable: body.disposable !== false,
|
|
933
|
-
mintToHash: body.mintToHash === true
|
|
1039
|
+
mintToHash: body.mintToHash === true,
|
|
1040
|
+
mint: asBoundMintCommitment(body.mint)
|
|
934
1041
|
};
|
|
935
1042
|
};
|
|
936
1043
|
var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
@@ -941,9 +1048,56 @@ var fetchInvoiceVerification = async (verifyUrl, options = {}) => {
|
|
|
941
1048
|
return {
|
|
942
1049
|
settled: body.settled,
|
|
943
1050
|
preimage: typeof body.preimage === "string" ? body.preimage : null,
|
|
944
|
-
pr: body.pr
|
|
1051
|
+
pr: body.pr,
|
|
1052
|
+
mint: asBoundMintCommitment(body.mint)
|
|
945
1053
|
};
|
|
946
1054
|
};
|
|
1055
|
+
var requireBoundMintQuote = (invoice, expectedH, expectedAmountMsat) => {
|
|
1056
|
+
const h = expectedH.trim().toLowerCase();
|
|
1057
|
+
if (!isPreimage(h)) throw new ProtocolError("The expected mint output hash is malformed.");
|
|
1058
|
+
if (!Number.isSafeInteger(expectedAmountMsat) || expectedAmountMsat <= 0) {
|
|
1059
|
+
throw new ProtocolError("The expected mint amount must be positive integer millisatoshis.");
|
|
1060
|
+
}
|
|
1061
|
+
if (!invoice.mintToHash || !invoice.mint) {
|
|
1062
|
+
throw new ProtocolError("The service did not commit this quote to a bound mint output.");
|
|
1063
|
+
}
|
|
1064
|
+
if (invoice.mint.h !== h) {
|
|
1065
|
+
throw new ProtocolError("The service committed the quote to a different mint output.");
|
|
1066
|
+
}
|
|
1067
|
+
if (invoice.mint.amountMsat !== expectedAmountMsat) {
|
|
1068
|
+
throw new ProtocolError("The service committed the quote to a different mint amount.");
|
|
1069
|
+
}
|
|
1070
|
+
if (invoice.mint.signature !== void 0) {
|
|
1071
|
+
throw new ProtocolError("The service signed a mint output before the invoice settled.");
|
|
1072
|
+
}
|
|
1073
|
+
return invoice.mint;
|
|
1074
|
+
};
|
|
1075
|
+
var validateBoundMintReceipt = (invoice, verification, expectedH, expectedAmountMsat, mintPubkeys) => {
|
|
1076
|
+
const quote = requireBoundMintQuote(invoice, expectedH, expectedAmountMsat);
|
|
1077
|
+
if (!verification.settled) {
|
|
1078
|
+
throw new ProtocolError("The invoice has not settled.");
|
|
1079
|
+
}
|
|
1080
|
+
if (!sameInvoice(invoice.pr, verification.pr)) {
|
|
1081
|
+
throw new ProtocolError("The settlement receipt names a different invoice.");
|
|
1082
|
+
}
|
|
1083
|
+
const receipt = verification.mint;
|
|
1084
|
+
if (!receipt || receipt.h !== quote.h || receipt.amountMsat !== quote.amountMsat) {
|
|
1085
|
+
throw new ProtocolError("The settlement receipt does not match the mint quote.");
|
|
1086
|
+
}
|
|
1087
|
+
if (!receipt.signature) {
|
|
1088
|
+
throw new ProtocolError("The settled mint receipt has no note signature.");
|
|
1089
|
+
}
|
|
1090
|
+
const checked = verifyNoteSignatureHashAgainst(
|
|
1091
|
+
receipt.h,
|
|
1092
|
+
receipt.amountMsat,
|
|
1093
|
+
receipt.signature,
|
|
1094
|
+
mintPubkeys
|
|
1095
|
+
);
|
|
1096
|
+
if (!checked.valid) {
|
|
1097
|
+
throw new ProtocolError("The settled mint receipt has an invalid note signature.");
|
|
1098
|
+
}
|
|
1099
|
+
return { ...receipt, signature: receipt.signature, pubkey: checked.pubkey };
|
|
1100
|
+
};
|
|
947
1101
|
var claimMintedNote = async (withdrawLink, k1, options = {}) => {
|
|
948
1102
|
const secret = k1.trim().toLowerCase();
|
|
949
1103
|
if (!isPreimage(secret)) {
|
|
@@ -971,49 +1125,6 @@ var claimMintedNote = async (withdrawLink, k1, options = {}) => {
|
|
|
971
1125
|
throw err;
|
|
972
1126
|
}
|
|
973
1127
|
};
|
|
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
1128
|
|
|
1018
1129
|
// src/settle.ts
|
|
1019
1130
|
var normaliseHost = (value) => serverOf(value.trim().replace(/^@/, "")).toLowerCase();
|
|
@@ -1063,18 +1174,86 @@ var settleNoteForValue = async (noteUrl, { mints, minMsat, requireSignature = fa
|
|
|
1063
1174
|
};
|
|
1064
1175
|
|
|
1065
1176
|
// src/restore.ts
|
|
1066
|
-
var restoreNotes = async (baseUrl, root, host, { gap = 20, start = 0 } = {}, options = {}) => {
|
|
1177
|
+
var restoreNotes = async (baseUrl, root, host, { gap = 20, start = 0, probeK1, allowSecretDisclosure = false } = {}, options = {}) => {
|
|
1067
1178
|
if (!Number.isSafeInteger(gap) || gap < 1) {
|
|
1068
1179
|
throw new RangeError(`The gap limit must be a positive integer, not ${gap}.`);
|
|
1069
1180
|
}
|
|
1181
|
+
if (!Number.isSafeInteger(start) || start < 0) {
|
|
1182
|
+
throw new RangeError(`The start index must be a non-negative integer, not ${start}.`);
|
|
1183
|
+
}
|
|
1184
|
+
let hashLookupsConfirmed = false;
|
|
1185
|
+
if (probeK1) {
|
|
1186
|
+
try {
|
|
1187
|
+
await fetchNoteInfoByHash(baseUrl, hashK1(probeK1), options);
|
|
1188
|
+
hashLookupsConfirmed = true;
|
|
1189
|
+
} catch (err) {
|
|
1190
|
+
if (!(err instanceof ServiceRejectedError)) throw err;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
const byHash = await walk(
|
|
1194
|
+
start,
|
|
1195
|
+
gap,
|
|
1196
|
+
async (k1) => {
|
|
1197
|
+
const info = await fetchNoteInfoByHash(baseUrl, hashK1(k1), options);
|
|
1198
|
+
hashLookupsConfirmed = true;
|
|
1199
|
+
return info;
|
|
1200
|
+
},
|
|
1201
|
+
root,
|
|
1202
|
+
host
|
|
1203
|
+
);
|
|
1204
|
+
if (byHash.found.length > 0 || byHash.unresolved.length > 0) hashLookupsConfirmed = true;
|
|
1205
|
+
if (hashLookupsConfirmed) {
|
|
1206
|
+
return {
|
|
1207
|
+
found: byHash.found,
|
|
1208
|
+
unresolved: byHash.unresolved,
|
|
1209
|
+
next: byHash.lastUsed === null ? start : byHash.lastUsed + 1,
|
|
1210
|
+
hashLookupsConfirmed: true,
|
|
1211
|
+
disclosesSecrets: false
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
if (!allowSecretDisclosure) {
|
|
1215
|
+
throw new HashLookupUnsupportedError(
|
|
1216
|
+
"This service never answered a lookup by hash, so a restore cannot tell an empty wallet from a service that only accepts raw secrets. Pass a probeK1 for a note known to exist here, or allowSecretDisclosure to walk by secret instead."
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
const bySecret = await walk(
|
|
1220
|
+
start,
|
|
1221
|
+
gap,
|
|
1222
|
+
(k1) => fetchNoteInfo(buildNoteUrl(baseUrl, k1), options),
|
|
1223
|
+
root,
|
|
1224
|
+
host
|
|
1225
|
+
);
|
|
1226
|
+
const walkedThrough = bySecret.highestWalked === null ? start - 1 : bySecret.highestWalked;
|
|
1227
|
+
const used = bySecret.lastUsed === null ? start - 1 : bySecret.lastUsed;
|
|
1228
|
+
return {
|
|
1229
|
+
found: bySecret.found,
|
|
1230
|
+
unresolved: bySecret.unresolved,
|
|
1231
|
+
// Every index this walk touched is burned, whether or not a note was
|
|
1232
|
+
// ever minted under it: its secret is in a log somewhere now, so
|
|
1233
|
+
// minting into it later would be minting a note a stranger can spend.
|
|
1234
|
+
next: Math.max(used, walkedThrough) + 1,
|
|
1235
|
+
hashLookupsConfirmed: false,
|
|
1236
|
+
disclosesSecrets: true
|
|
1237
|
+
};
|
|
1238
|
+
};
|
|
1239
|
+
var walk = async (start, gap, lookup, root, host) => {
|
|
1070
1240
|
const found = [];
|
|
1241
|
+
const unresolved = [];
|
|
1071
1242
|
let lastUsed = null;
|
|
1243
|
+
let highestWalked = null;
|
|
1072
1244
|
let unknownRun = 0;
|
|
1073
1245
|
for (let index = start; unknownRun < gap; index++) {
|
|
1074
1246
|
const k1 = deriveNoteSecret(root, host, index);
|
|
1247
|
+
highestWalked = index;
|
|
1075
1248
|
try {
|
|
1076
|
-
const info = await
|
|
1077
|
-
found.push({
|
|
1249
|
+
const info = await lookup(k1);
|
|
1250
|
+
found.push({
|
|
1251
|
+
index,
|
|
1252
|
+
k1,
|
|
1253
|
+
amountMsat: info.maxWithdrawable,
|
|
1254
|
+
state: "live",
|
|
1255
|
+
callback: info.callback
|
|
1256
|
+
});
|
|
1078
1257
|
lastUsed = index;
|
|
1079
1258
|
unknownRun = 0;
|
|
1080
1259
|
} catch (err) {
|
|
@@ -1087,17 +1266,22 @@ var restoreNotes = async (baseUrl, root, host, { gap = 20, start = 0 } = {}, opt
|
|
|
1087
1266
|
unknownRun = 0;
|
|
1088
1267
|
} else if (err instanceof NoteUnknownError) {
|
|
1089
1268
|
unknownRun++;
|
|
1269
|
+
} else if (err instanceof ServiceRejectedError) {
|
|
1270
|
+
unresolved.push({ index, k1, reason: err.reason });
|
|
1271
|
+
lastUsed = index;
|
|
1272
|
+
unknownRun = 0;
|
|
1090
1273
|
} else {
|
|
1091
1274
|
throw err;
|
|
1092
1275
|
}
|
|
1093
1276
|
}
|
|
1094
1277
|
}
|
|
1095
|
-
return { found,
|
|
1278
|
+
return { found, unresolved, lastUsed, highestWalked };
|
|
1096
1279
|
};
|
|
1097
1280
|
|
|
1098
1281
|
// src/index.ts
|
|
1099
1282
|
var createClient = (options = {}) => ({
|
|
1100
1283
|
fetchNoteInfo: (url) => fetchNoteInfo(url, options),
|
|
1284
|
+
fetchNoteInfoByHash: (withdrawLink, h) => fetchNoteInfoByHash(withdrawLink, h, options),
|
|
1101
1285
|
probeBurnedNote: (url) => probeBurnedNote(url, options),
|
|
1102
1286
|
fetchMintAddress: (url) => fetchMintAddress(url, options),
|
|
1103
1287
|
meltNote: (callback, k1, pr) => meltNote(callback, k1, pr, options),
|
|
@@ -1119,4 +1303,4 @@ var createClient = (options = {}) => ({
|
|
|
1119
1303
|
settleNoteForValue: (noteUrl, terms) => settleNoteForValue(noteUrl, terms, options)
|
|
1120
1304
|
});
|
|
1121
1305
|
|
|
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 };
|
|
1306
|
+
export { AmbiguousMintError, AmbiguousMutationError, HashLookupUnsupportedError, InsufficientValueError, LnurlcashError, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, PendingNoteError, ProtocolError, RequestRefusedError, ServiceRejectedError, applyMintFee, buildNoteInfoUrlByHash, buildNoteUrl, claimMintedNote, classifyNoteError, createClient, decodeBolt11AmountMsat, decodePaymentRequest, defaultRandomSecret, deriveNoteRoot, deriveNoteSecret, derivedSecretSource, describeMintFee, encodePaymentRequest, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchNoteInfoByHash, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isLightningAddress, isPaymentRequest, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, mintFeeBand, namesMintOutput, newSecretsOf, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureDigestForHash, noteSignatureMessage, noteSignatureMessageForHash, parseMintFee, paymentRequestAmountMsat, probeBurnedNote, requestInvoice, requireBoundMintQuote, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, restoreNotes, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, settleNoteForValue, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, validateBoundMintReceipt, verifyNoteSignature, verifyNoteSignatureAgainst, verifyNoteSignatureHash, verifyNoteSignatureHashAgainst, withNewK1, withinMintFeeBand, withoutK1 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lnurlcash-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "LNURLcash (LUD-25) bearer note client for TypeScript - mint, rotate, split, merge, melt, and verify offline",
|
|
5
5
|
"author": "TheCryptoDonkey",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@types/node": "^26.2.0",
|
|
63
|
-
"lnurlcash-conformance": "^0.
|
|
63
|
+
"lnurlcash-conformance": "^0.3.0",
|
|
64
64
|
"tsup": "^8.5.0",
|
|
65
65
|
"typescript": "^5.7.0",
|
|
66
66
|
"vitest": "^3.0.0"
|