lnurlcash-kit 0.5.0 → 0.7.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 +67 -0
- package/README.md +65 -52
- package/dist/index.d.ts +8 -2
- package/dist/index.js +87 -15
- package/llms.txt +30 -11
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.7.0 - 2026-09-04
|
|
6
|
+
|
|
7
|
+
**Offline verification is mandatory, and this library now insists on it.**
|
|
8
|
+
LUD-25 stopped treating a note signature as optional: a SERVICE MUST
|
|
9
|
+
publish `mintPubkey` and MUST sign every note a rotate, split or merge
|
|
10
|
+
mints. A wallet that quietly accepted unsigned notes was handing its holder
|
|
11
|
+
something nobody downstream could check, which is exactly the gap offline
|
|
12
|
+
verification exists to close.
|
|
13
|
+
|
|
14
|
+
- `fetchNoteInfo` and `fetchNoteInfoByHash` refuse a `withdrawRequest` that
|
|
15
|
+
publishes no `mintPubkey`, or one that is not a 33-byte compressed
|
|
16
|
+
secp256k1 key. `WithdrawRequestInfo.mintPubkey` is typed as present.
|
|
17
|
+
- `rotateNote`, `splitNote`, `mergeNotes` and their `*WithHash` forms throw
|
|
18
|
+
the new `UnverifiableNoteError` when the SERVICE confirms the mutation but
|
|
19
|
+
returns no `sig` (or no `sig2` on a split's change).
|
|
20
|
+
- **That error carries the secrets.** The mutation landed - `status` was OK -
|
|
21
|
+
so the note exists at the hash the wallet disclosed and its secret is the
|
|
22
|
+
only key to that value. `newSecretsOf()` reads them exactly as it reads
|
|
23
|
+
them off an ambiguous mutation. Enforcing conformance must never be the
|
|
24
|
+
thing that destroys the money.
|
|
25
|
+
- `requireSignatures: false` opts out, for a mint that predates the
|
|
26
|
+
requirement. One option, stated once, at the call site that needs it.
|
|
27
|
+
|
|
28
|
+
**A mutation whose answer was lost is now re-sent, and usually completes.**
|
|
29
|
+
LUD-25 gained a "Retrying a mutation" section: a SERVICE MUST answer a
|
|
30
|
+
byte-identical rotate, split or merge with the success it already returned,
|
|
31
|
+
signature and all, rather than with the already-spent refusal its burned
|
|
32
|
+
inputs would otherwise earn.
|
|
33
|
+
|
|
34
|
+
That closes the sharpest edge in the protocol. Every mutation is a GET,
|
|
35
|
+
HTTP treats GET as idempotent, and stacks retry one whose connection
|
|
36
|
+
dropped - browsers on a stale keep-alive, Go's `net/http` on a reused
|
|
37
|
+
connection, the JDK's `HttpClient` with no way to switch it off. The mint
|
|
38
|
+
saw the request twice, answered the second as already spent, and the wallet
|
|
39
|
+
was told a mutation had not happened while a note sat at the hash it had
|
|
40
|
+
disclosed. Now the second answer is the first one.
|
|
41
|
+
|
|
42
|
+
- `mutationRetries` defaults to 1. Set 0 for the previous behaviour.
|
|
43
|
+
- Only rotate, split and merge. A melt is **never** retried: it carries
|
|
44
|
+
`pr`, is paid out asynchronously, and the replay rule does not cover it.
|
|
45
|
+
- Only an ambiguous failure is retried. A definitive refusal is the
|
|
46
|
+
SERVICE's considered answer and asking again cannot improve it.
|
|
47
|
+
- The retry re-sends the identical request rather than rebuilding it. The
|
|
48
|
+
replay is matched on the k1 set, `h`, `h2` and `amount`, so a freshly
|
|
49
|
+
generated secret would make the second attempt a different mutation - and
|
|
50
|
+
a second real burn.
|
|
51
|
+
|
|
52
|
+
Against a SERVICE that has not implemented the replay rule, retrying leaves
|
|
53
|
+
a caller exactly where giving up would have: the same secrets on the same
|
|
54
|
+
error, and the same instruction to go and ask what the note at each hash is
|
|
55
|
+
worth.
|
|
56
|
+
|
|
57
|
+
Requires `lnurlcash-conformance` 0.6.0, whose vectors carry the same MUSTs.
|
|
58
|
+
|
|
59
|
+
## 0.6.0 - 2026-08-31
|
|
60
|
+
|
|
61
|
+
- `namesMintOutput()` now requires `commentAllowed >= 64`; the additive
|
|
62
|
+
`mintToHash` advertisement alone no longer authorizes minting.
|
|
63
|
+
- `fetchPayRequest()` rejects a minting payRequest that cannot carry the
|
|
64
|
+
mandatory 64-character commitment, so a caller cannot proceed into an
|
|
65
|
+
invoice flow that has no conforming output name.
|
|
66
|
+
- Mint requests carrying an output hash continue to send identical
|
|
67
|
+
`comment` and `h` fields. Documentation now treats the former as mandatory
|
|
68
|
+
LUD-25 and the latter as the Moneyer/ForgeSworn receipt extension.
|
|
69
|
+
|
|
3
70
|
Semantic versioning. While the LUD-25 draft is unmerged, `0.x` minor bumps
|
|
4
71
|
may carry breaking changes; pin an exact version.
|
|
5
72
|
|
package/README.md
CHANGED
|
@@ -58,14 +58,15 @@ console.log(info.maxWithdrawable, 'msat')
|
|
|
58
58
|
// that GET put the secret on the wire, so rotate it
|
|
59
59
|
const fresh = await rotateNote(info.callback, info.k1)
|
|
60
60
|
|
|
61
|
-
// and check the mint really issued it, without asking anyone
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
// and check the mint really issued it, without asking anyone. Both fields
|
|
62
|
+
// are guaranteed here: LUD-25 requires the mint to publish mintPubkey and
|
|
63
|
+
// to sign what it mints, and this library refuses a mint that does neither.
|
|
64
|
+
verifyNoteSignature(fresh.k1, info.maxWithdrawable, fresh.signature!, info.mintPubkey)
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Every request function takes options last — `fetch`, `timeoutMs`, `offline`,
|
|
68
|
-
`randomSecret
|
|
68
|
+
`randomSecret`, `requireSignatures`, `mutationRetries`.
|
|
69
|
+
`createClient(options)` binds one set once:
|
|
69
70
|
|
|
70
71
|
```ts
|
|
71
72
|
const client = createClient({timeoutMs: 10_000})
|
|
@@ -107,23 +108,39 @@ try {
|
|
|
107
108
|
|
|
108
109
|
`RequestRefusedError` is the opposite and safe: nothing left the process.
|
|
109
110
|
|
|
110
|
-
**3.
|
|
111
|
-
as idempotent, and an LNURLcash mutation is not —
|
|
112
|
-
input.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
This is not hypothetical: the same hazard broke the
|
|
111
|
+
**3. A retried mutation is now a replay, not a double spend.** Every mutation
|
|
112
|
+
is a GET, HTTP treats GET as idempotent, and an LNURLcash mutation is not —
|
|
113
|
+
the first attempt burns the input. For most of this draft's life that was the
|
|
114
|
+
sharpest edge in the protocol: a stack that resent a dropped GET got "already
|
|
115
|
+
spent" for the second attempt, which reads as a *definitive* rejection, so the
|
|
116
|
+
fresh secret got discarded along with the note the service had just minted.
|
|
117
|
+
Node's `fetch` does not retry on its own, but a browser resends an idempotent
|
|
118
|
+
request that failed on a stale pooled connection, and Go and the JDK do the
|
|
119
|
+
same by their own routes — the hazard broke the
|
|
120
120
|
[Kotlin](https://github.com/TheCryptoDonkey/lnurlcash-kotlin) and
|
|
121
121
|
[Go](https://github.com/TheCryptoDonkey/lnurlcash-go) siblings during
|
|
122
|
-
development, by two different mechanisms
|
|
123
|
-
conformance vectors.
|
|
122
|
+
development, by two different mechanisms.
|
|
124
123
|
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
LUD-25 closed it. A service MUST answer a byte-identical rotate, split or
|
|
125
|
+
merge with the success it already returned, signature and all. So this library
|
|
126
|
+
re-sends one whose answer was lost, and an unstoppable transport retry is now
|
|
127
|
+
simply invisible:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
// the connection dropped after the mint applied this. It completes anyway.
|
|
131
|
+
const fresh = await rotateNote(callback, oldK1)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`mutationRetries` sets how many times (default 1; `0` restores the old
|
|
135
|
+
give-up-at-once behaviour). Only rotate, split and merge are re-sent — never a
|
|
136
|
+
melt, which carries `pr`, is paid asynchronously and has no replay guarantee —
|
|
137
|
+
and only an ambiguous failure, never a refusal the service actually
|
|
138
|
+
considered. The re-sent request is byte-identical, because the replay is
|
|
139
|
+
matched on the k1 set, `h`, `h2` and `amount`.
|
|
140
|
+
|
|
141
|
+
A service that has not implemented the rule answers the second attempt as
|
|
142
|
+
already spent, exactly as before. So the old defence stays: a mutation refused
|
|
143
|
+
with the input already spent or unknown carries its outputs anyway:
|
|
127
144
|
|
|
128
145
|
```ts
|
|
129
146
|
try {
|
|
@@ -154,23 +171,16 @@ callback — it is only observable as the note becoming spendable again. Other
|
|
|
154
171
|
operations on that `k1` raise `PendingNoteError` meanwhile; retry, never
|
|
155
172
|
read it as spent.
|
|
156
173
|
|
|
157
|
-
**5.
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
the invoice. First rotater wins. A wallet that rotates on settlement wins by
|
|
162
|
-
construction; a human copying a preimage by hand does not.
|
|
163
|
-
|
|
164
|
-
That is a race, and the way to win a race is not to enter it. Where a mint
|
|
165
|
-
advertises `mintToHash`, name the note you are buying and the preimage is
|
|
166
|
-
not its secret at all: see
|
|
167
|
-
[Minting a note you named yourself](#minting-a-note-you-named-yourself).
|
|
168
|
-
The rule above stands for every mint that does not offer it.
|
|
174
|
+
**5. Persist the mint secret before requesting the invoice.** Current
|
|
175
|
+
LUD-25 requires `comment=hex(sha256(secret))`; there is no preimage-backed
|
|
176
|
+
creation fallback. If the payRequest cannot carry that 64-character comment,
|
|
177
|
+
do not mint. Existing notes still redeem through ordinary LUD-03.
|
|
169
178
|
|
|
170
179
|
## Offline verification
|
|
171
180
|
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
Mandatory, and enforced here. A service MUST publish `mintPubkey` and MUST
|
|
182
|
+
sign every note a rotate, split or merge mints, so a holder can confirm
|
|
183
|
+
issuer and amount with nothing but the note:
|
|
174
184
|
|
|
175
185
|
```
|
|
176
186
|
message = "LNURLcash:" || amount_msat || ":" || hex(sha256(k1))
|
|
@@ -178,6 +188,14 @@ digest = sha256(sha256("Lightning Signed Message:" || message))
|
|
|
178
188
|
sig = 65 bytes, r || s || recovery_id
|
|
179
189
|
```
|
|
180
190
|
|
|
191
|
+
A `withdrawRequest` publishing no `mintPubkey`, or one that is not a 33-byte
|
|
192
|
+
compressed secp256k1 key, is refused with a `ProtocolError`. A mutation the
|
|
193
|
+
service confirms but does not sign raises `UnverifiableNoteError` — which
|
|
194
|
+
**carries the fresh secrets**, because the mutation landed and the note it
|
|
195
|
+
minted is real; read them with `newSecretsOf` and persist them before
|
|
196
|
+
anything else. Pass `requireSignatures: false` to deal with a mint that
|
|
197
|
+
predates the requirement.
|
|
198
|
+
|
|
181
199
|
`verifyNoteSignature` recovers the pubkey and compares it to `mintPubkey`.
|
|
182
200
|
It accepts the recovery id at either end, because lnurl-mint once emitted
|
|
183
201
|
the reverse layout and other implementations may still; trying both is safe,
|
|
@@ -297,18 +315,19 @@ can poll LUD-21 `verify` with the payment hash inside it and take the
|
|
|
297
315
|
preimage the moment it settles, which is what a QR code on a desktop screen
|
|
298
316
|
hands out.
|
|
299
317
|
|
|
300
|
-
A mint
|
|
301
|
-
|
|
302
|
-
|
|
318
|
+
A current-draft mint binds the note to the hash supplied in the mandatory
|
|
319
|
+
LUD-12 comment. The kit repeats the same value as `h` for the additive
|
|
320
|
+
Moneyer/ForgeSworn receipt extension. You chose the secret, nobody else ever
|
|
321
|
+
had it, and the preimage is ordinary payment proof.
|
|
303
322
|
|
|
304
323
|
```ts
|
|
305
324
|
import {
|
|
306
325
|
fetchPayRequest, requestInvoice, claimMintedNote,
|
|
307
|
-
deriveNoteRoot, deriveNoteSecret, hashK1
|
|
326
|
+
deriveNoteRoot, deriveNoteSecret, hashK1, namesMintOutput
|
|
308
327
|
} from 'lnurlcash-kit'
|
|
309
328
|
|
|
310
329
|
const pay = await fetchPayRequest(payUrl) // a Lightning Address resolves here
|
|
311
|
-
if (!pay
|
|
330
|
+
if (!namesMintOutput(pay)) throw new Error('mint lacks commentAllowed: 64')
|
|
312
331
|
|
|
313
332
|
const root = deriveNoteRoot(seed)
|
|
314
333
|
const k1 = deriveNoteSecret(root, 'mint.example', nextIndex)
|
|
@@ -322,26 +341,20 @@ const claim = await claimMintedNote(pay.withdrawLink!, k1)
|
|
|
322
341
|
// 'minted' -> claim.amountMsat is what it is worth, claim.callback melts it
|
|
323
342
|
```
|
|
324
343
|
|
|
325
|
-
Ask before you buy. `
|
|
326
|
-
|
|
327
|
-
for one whose secret three other parties can learn. It turns up in three
|
|
328
|
-
places, saying three different things:
|
|
344
|
+
Ask before you buy. `commentAllowed >= 64` is the normative minting
|
|
345
|
+
capability. `mintToHash` describes only the additive `h` and receipt fields:
|
|
329
346
|
|
|
330
347
|
| Where | What it means |
|
|
331
348
|
| --- | --- |
|
|
332
|
-
| `PayRequestInfo.
|
|
349
|
+
| `PayRequestInfo.commentAllowed` | room for the mandatory hash comment; required for minting |
|
|
350
|
+
| `PayRequestInfo.mintToHash` | "I also accept the matching `h` extension." |
|
|
333
351
|
| `MintAddressInfo.mintToHash` | the same fact on the experimental discovery document |
|
|
334
352
|
| `InvoiceResult.mintToHash` | "I bound *this quote* to the hash you named" |
|
|
335
353
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
`
|
|
339
|
-
|
|
340
|
-
that says it in neither place ignores the `h`, keys the note by the preimage
|
|
341
|
-
as it always has, and the verify path is unchanged. Anything that is not
|
|
342
|
-
exactly `true` is a no, everywhere, and `false` on the invoice result is
|
|
343
|
-
silence rather than a refusal, so decide from the advertisement and claim by
|
|
344
|
-
probing.
|
|
354
|
+
Decide whether minting is possible from `commentAllowed` on the payRequest;
|
|
355
|
+
never substitute the mint-address extension field. Anything other than
|
|
356
|
+
boolean `true` is no for `mintToHash`, but that affects only extension receipt
|
|
357
|
+
handling. The note remains comment-bound either way.
|
|
345
358
|
|
|
346
359
|
**Persist the secret before you ask for the invoice.** Paying for a note and
|
|
347
360
|
then losing the secret is the one way this is worse than the preimage scheme,
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ type LnurlcashOptions = {
|
|
|
13
13
|
timeoutMs?: number;
|
|
14
14
|
offline?: boolean;
|
|
15
15
|
randomSecret?: RandomSecret;
|
|
16
|
+
requireSignatures?: boolean;
|
|
17
|
+
mutationRetries?: number;
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
type MintFee = {
|
|
@@ -41,7 +43,7 @@ type WithdrawRequestInfo = {
|
|
|
41
43
|
minWithdrawable: number;
|
|
42
44
|
maxWithdrawable: number;
|
|
43
45
|
defaultDescription?: string;
|
|
44
|
-
mintPubkey
|
|
46
|
+
mintPubkey: string;
|
|
45
47
|
payLink?: string;
|
|
46
48
|
};
|
|
47
49
|
declare const fetchNoteInfo: (url: string, options?: LnurlcashOptions) => Promise<WithdrawRequestInfo>;
|
|
@@ -308,6 +310,10 @@ declare class HashLookupUnsupportedError extends LnurlcashError {
|
|
|
308
310
|
}
|
|
309
311
|
declare class AmbiguousMintError extends LnurlcashError {
|
|
310
312
|
}
|
|
313
|
+
declare class UnverifiableNoteError extends LnurlcashError {
|
|
314
|
+
newSecrets: string[];
|
|
315
|
+
constructor(message: string, newSecrets?: string[]);
|
|
316
|
+
}
|
|
311
317
|
declare class AmbiguousMutationError extends AmbiguousMintError {
|
|
312
318
|
readonly newSecrets: string[];
|
|
313
319
|
constructor(message: string, newSecrets: string[]);
|
|
@@ -342,4 +348,4 @@ declare const createClient: (options?: LnurlcashOptions) => {
|
|
|
342
348
|
};
|
|
343
349
|
type LnurlcashClient = ReturnType<typeof createClient>;
|
|
344
350
|
|
|
345
|
-
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, mergeBatches, 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 };
|
|
351
|
+
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, UnverifiableNoteError, 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, mergeBatches, 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
|
@@ -264,6 +264,13 @@ var HashLookupUnsupportedError = class extends LnurlcashError {
|
|
|
264
264
|
};
|
|
265
265
|
var AmbiguousMintError = class extends LnurlcashError {
|
|
266
266
|
};
|
|
267
|
+
var UnverifiableNoteError = class extends LnurlcashError {
|
|
268
|
+
newSecrets;
|
|
269
|
+
constructor(message, newSecrets = []) {
|
|
270
|
+
super(message);
|
|
271
|
+
this.newSecrets = newSecrets;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
267
274
|
var AmbiguousMutationError = class extends AmbiguousMintError {
|
|
268
275
|
newSecrets;
|
|
269
276
|
constructor(message, newSecrets) {
|
|
@@ -283,6 +290,7 @@ var InsufficientValueError = class extends ServiceRejectedError {
|
|
|
283
290
|
};
|
|
284
291
|
var newSecretsOf = (err) => {
|
|
285
292
|
if (err instanceof AmbiguousMutationError) return err.newSecrets;
|
|
293
|
+
if (err instanceof UnverifiableNoteError) return err.newSecrets;
|
|
286
294
|
if (err instanceof ServiceRejectedError) return err.newSecrets ?? [];
|
|
287
295
|
return [];
|
|
288
296
|
};
|
|
@@ -465,8 +473,18 @@ var resolveOptions = (options = {}) => ({
|
|
|
465
473
|
fetch: options.fetch ?? ((...args) => globalThis.fetch(...args)),
|
|
466
474
|
timeoutMs: options.timeoutMs ?? 3e4,
|
|
467
475
|
offline: options.offline ?? false,
|
|
468
|
-
randomSecret: options.randomSecret ?? defaultRandomSecret
|
|
476
|
+
randomSecret: options.randomSecret ?? defaultRandomSecret,
|
|
477
|
+
requireSignatures: options.requireSignatures ?? true,
|
|
478
|
+
// A negative or non-finite count is read as none rather than thrown on:
|
|
479
|
+
// this is a resilience knob, and refusing the whole operation over it
|
|
480
|
+
// would be a worse answer than not retrying.
|
|
481
|
+
mutationRetries: normaliseRetries(options.mutationRetries)
|
|
469
482
|
});
|
|
483
|
+
var normaliseRetries = (value) => {
|
|
484
|
+
if (value === void 0) return 1;
|
|
485
|
+
if (!Number.isFinite(value) || value <= 0) return 0;
|
|
486
|
+
return Math.floor(value);
|
|
487
|
+
};
|
|
470
488
|
var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
|
|
471
489
|
var MAX_REDIRECTS = 5;
|
|
472
490
|
var MAX_BODY_BYTES = 1048576;
|
|
@@ -699,10 +717,17 @@ var verifyNoteSignature = (k1, amountMsat, signatureHex, mintPubkeys) => verifyN
|
|
|
699
717
|
var verifyNoteSignatureHash = (h, amountMsat, signatureHex, mintPubkeys) => verifyNoteSignatureHashAgainst(h, amountMsat, signatureHex, mintPubkeys).valid;
|
|
700
718
|
|
|
701
719
|
// src/client.ts
|
|
702
|
-
var
|
|
720
|
+
var COMPRESSED_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
721
|
+
var isCompressedPubkey = (value) => typeof value === "string" && COMPRESSED_PUBKEY.test(value.trim().toLowerCase());
|
|
722
|
+
var assertWithdrawRequestShape = (body, { requireK1, requireMintPubkey }) => {
|
|
703
723
|
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
724
|
throw new ProtocolError("Not a withdrawRequest (unexpected response).");
|
|
705
725
|
}
|
|
726
|
+
if (requireMintPubkey && !isCompressedPubkey(body.mintPubkey)) {
|
|
727
|
+
throw new ProtocolError(
|
|
728
|
+
body.mintPubkey === void 0 ? "This service publishes no mintPubkey, so its notes cannot be verified offline (LUD-25 requires one)." : "This service published a mintPubkey that is not a 33-byte compressed secp256k1 key."
|
|
729
|
+
);
|
|
730
|
+
}
|
|
706
731
|
};
|
|
707
732
|
var fetchNoteInfo = async (url, options = {}) => {
|
|
708
733
|
const opts = resolveOptions(options);
|
|
@@ -715,7 +740,10 @@ var fetchNoteInfo = async (url, options = {}) => {
|
|
|
715
740
|
if (err instanceof ServiceRejectedError) throw classifyNoteError(err.reason);
|
|
716
741
|
throw err;
|
|
717
742
|
}
|
|
718
|
-
assertWithdrawRequestShape(body, {
|
|
743
|
+
assertWithdrawRequestShape(body, {
|
|
744
|
+
requireK1: true,
|
|
745
|
+
requireMintPubkey: opts.requireSignatures
|
|
746
|
+
});
|
|
719
747
|
const queried = noteK1(url);
|
|
720
748
|
if (queried && body.k1.toLowerCase() !== queried) {
|
|
721
749
|
throw new ProtocolError(
|
|
@@ -738,7 +766,10 @@ var fetchNoteInfoByHash = async (withdrawLink, h, options = {}) => {
|
|
|
738
766
|
if (err instanceof ServiceRejectedError) throw classifyNoteError(err.reason);
|
|
739
767
|
throw err;
|
|
740
768
|
}
|
|
741
|
-
assertWithdrawRequestShape(body, {
|
|
769
|
+
assertWithdrawRequestShape(body, {
|
|
770
|
+
requireK1: false,
|
|
771
|
+
requireMintPubkey: opts.requireSignatures
|
|
772
|
+
});
|
|
742
773
|
const info = body;
|
|
743
774
|
const payLink = sameOriginPayLink(body.payLink, reqUrl);
|
|
744
775
|
if (payLink === void 0) delete info.payLink;
|
|
@@ -857,6 +888,28 @@ var callbackRequest = async (callback, params, options) => {
|
|
|
857
888
|
}
|
|
858
889
|
return body;
|
|
859
890
|
};
|
|
891
|
+
var replayableCallbackRequest = async (callback, params, options) => {
|
|
892
|
+
const { mutationRetries } = resolveOptions(options);
|
|
893
|
+
let lastError;
|
|
894
|
+
for (let attempt = 0; ; attempt++) {
|
|
895
|
+
try {
|
|
896
|
+
return await callbackRequest(callback, params, options);
|
|
897
|
+
} catch (err) {
|
|
898
|
+
if (!(err instanceof AmbiguousMintError) || attempt >= mutationRetries) {
|
|
899
|
+
throw err;
|
|
900
|
+
}
|
|
901
|
+
lastError = err;
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
throw lastError;
|
|
905
|
+
};
|
|
906
|
+
var requireSignature = (value, options, what) => {
|
|
907
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
908
|
+
if (!resolveOptions(options).requireSignatures) return void 0;
|
|
909
|
+
throw new UnverifiableNoteError(
|
|
910
|
+
`The service confirmed the ${what} but returned no signature, so the note it just minted cannot be verified offline. The note exists - keep the secret.`
|
|
911
|
+
);
|
|
912
|
+
};
|
|
860
913
|
var meltNote = async (callback, k1, pr, options = {}) => {
|
|
861
914
|
const body = await callbackRequest(
|
|
862
915
|
callback,
|
|
@@ -872,7 +925,7 @@ var meltNote = async (callback, k1, pr, options = {}) => {
|
|
|
872
925
|
};
|
|
873
926
|
};
|
|
874
927
|
var rotateNoteWithHash = async (callback, k1, h, options = {}) => {
|
|
875
|
-
const body = await
|
|
928
|
+
const body = await replayableCallbackRequest(
|
|
876
929
|
callback,
|
|
877
930
|
[
|
|
878
931
|
["k1", k1],
|
|
@@ -880,10 +933,10 @@ var rotateNoteWithHash = async (callback, k1, h, options = {}) => {
|
|
|
880
933
|
],
|
|
881
934
|
options
|
|
882
935
|
);
|
|
883
|
-
return { signature: body.sig };
|
|
936
|
+
return { signature: requireSignature(body.sig, options, "rotate") };
|
|
884
937
|
};
|
|
885
938
|
var splitNoteWithHash = async (callback, k1s, amountMsat, h, h2, options = {}) => {
|
|
886
|
-
const body = await
|
|
939
|
+
const body = await replayableCallbackRequest(
|
|
887
940
|
callback,
|
|
888
941
|
[
|
|
889
942
|
...k1s.map((k1) => ["k1", k1]),
|
|
@@ -893,20 +946,26 @@ var splitNoteWithHash = async (callback, k1s, amountMsat, h, h2, options = {}) =
|
|
|
893
946
|
],
|
|
894
947
|
options
|
|
895
948
|
);
|
|
896
|
-
return {
|
|
949
|
+
return {
|
|
950
|
+
signature: requireSignature(body.sig, options, "split"),
|
|
951
|
+
changeSignature: requireSignature(body.sig2, options, "split's change")
|
|
952
|
+
};
|
|
897
953
|
};
|
|
898
954
|
var mergeNotesWithHash = async (callback, k1s, h, options = {}) => {
|
|
899
|
-
const body = await
|
|
955
|
+
const body = await replayableCallbackRequest(
|
|
900
956
|
callback,
|
|
901
957
|
[...k1s.map((k1) => ["k1", k1]), ["h", h]],
|
|
902
958
|
options
|
|
903
959
|
);
|
|
904
|
-
return { signature: body.sig };
|
|
960
|
+
return { signature: requireSignature(body.sig, options, "merge") };
|
|
905
961
|
};
|
|
906
962
|
var keepingOutputs = (err, newSecrets) => {
|
|
907
963
|
if (err instanceof NoteSpentError || err instanceof NoteUnknownError) {
|
|
908
964
|
err.newSecrets = newSecrets;
|
|
909
965
|
}
|
|
966
|
+
if (err instanceof UnverifiableNoteError) {
|
|
967
|
+
err.newSecrets = newSecrets;
|
|
968
|
+
}
|
|
910
969
|
return err;
|
|
911
970
|
};
|
|
912
971
|
var rotateNote = async (callback, k1, options = {}) => {
|
|
@@ -1012,6 +1071,10 @@ var foldNotes = async (callback, batches, opts, options) => {
|
|
|
1012
1071
|
err.newSecrets = live;
|
|
1013
1072
|
throw err;
|
|
1014
1073
|
}
|
|
1074
|
+
if (err instanceof UnverifiableNoteError) {
|
|
1075
|
+
err.newSecrets = live;
|
|
1076
|
+
throw err;
|
|
1077
|
+
}
|
|
1015
1078
|
throw new AmbiguousMutationError(
|
|
1016
1079
|
err instanceof Error ? err.message : String(err),
|
|
1017
1080
|
live
|
|
@@ -1048,14 +1111,23 @@ var fetchPayRequest = async (url, options = {}) => {
|
|
|
1048
1111
|
throw new ProtocolError("Not a payRequest (unexpected response).");
|
|
1049
1112
|
}
|
|
1050
1113
|
const mintFee = typeof body.metadata === "string" ? parseMintFee(body.metadata) : null;
|
|
1114
|
+
const commentAllowed = asNumber(body.commentAllowed);
|
|
1115
|
+
if (body.withdrawLink !== void 0 && typeof body.withdrawLink !== "string") {
|
|
1116
|
+
throw new ProtocolError("A minting payRequest has an invalid withdrawLink.");
|
|
1117
|
+
}
|
|
1118
|
+
if (typeof body.withdrawLink === "string" && !(typeof commentAllowed === "number" && commentAllowed >= 64)) {
|
|
1119
|
+
throw new ProtocolError(
|
|
1120
|
+
"A minting payRequest must allow a 64-character output commitment."
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1051
1123
|
return {
|
|
1052
1124
|
...body,
|
|
1053
1125
|
mintFee: mintFee ?? void 0,
|
|
1054
1126
|
mintToHash: asBoolean(body.mintToHash),
|
|
1055
|
-
commentAllowed
|
|
1127
|
+
commentAllowed
|
|
1056
1128
|
};
|
|
1057
1129
|
};
|
|
1058
|
-
var namesMintOutput = (info) =>
|
|
1130
|
+
var namesMintOutput = (info) => typeof info.commentAllowed === "number" && info.commentAllowed >= 64;
|
|
1059
1131
|
var asBoundMintCommitment = (value) => {
|
|
1060
1132
|
if (!value || typeof value !== "object") return void 0;
|
|
1061
1133
|
const raw = value;
|
|
@@ -1187,7 +1259,7 @@ var claimMintedNote = async (withdrawLink, k1, options = {}) => {
|
|
|
1187
1259
|
|
|
1188
1260
|
// src/settle.ts
|
|
1189
1261
|
var normaliseHost = (value) => serverOf(value.trim().replace(/^@/, "")).toLowerCase();
|
|
1190
|
-
var settleNoteForValue = async (noteUrl, { mints, minMsat, requireSignature = false }, options = {}) => {
|
|
1262
|
+
var settleNoteForValue = async (noteUrl, { mints, minMsat, requireSignature: requireSignature2 = false }, options = {}) => {
|
|
1191
1263
|
const url = resolveNoteInput(noteUrl);
|
|
1192
1264
|
if (!url) {
|
|
1193
1265
|
throw new RequestRefusedError(
|
|
@@ -1203,7 +1275,7 @@ var settleNoteForValue = async (noteUrl, { mints, minMsat, requireSignature = fa
|
|
|
1203
1275
|
}
|
|
1204
1276
|
const k1 = requireNoteK1(url);
|
|
1205
1277
|
const info = await fetchNoteInfo(url, options);
|
|
1206
|
-
if (
|
|
1278
|
+
if (requireSignature2) {
|
|
1207
1279
|
const signature = noteSignature(url);
|
|
1208
1280
|
if (!signature) {
|
|
1209
1281
|
throw new ServiceRejectedError("This note carries no signature.");
|
|
@@ -1362,4 +1434,4 @@ var createClient = (options = {}) => ({
|
|
|
1362
1434
|
settleNoteForValue: (noteUrl, terms) => settleNoteForValue(noteUrl, terms, options)
|
|
1363
1435
|
});
|
|
1364
1436
|
|
|
1365
|
-
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, mergeBatches, 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 };
|
|
1437
|
+
export { AmbiguousMintError, AmbiguousMutationError, HashLookupUnsupportedError, InsufficientValueError, LnurlcashError, NoteSpentError, NoteUnknownError, PAYMENT_REQUEST_PREFIX, PendingNoteError, ProtocolError, RequestRefusedError, ServiceRejectedError, UnverifiableNoteError, 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, mergeBatches, 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/llms.txt
CHANGED
|
@@ -18,10 +18,10 @@ Reference wallet: https://github.com/dni/lnurl-wallet
|
|
|
18
18
|
## Core API
|
|
19
19
|
|
|
20
20
|
resolveNoteInput(text) -> url | null accepts bech32 LNURL, lnurlw://, https
|
|
21
|
-
fetchNoteInfo(url, opts?) -> {callback, k1, maxWithdrawable, mintPubkey
|
|
22
|
-
rotateNote(callback, k1, opts?) -> {k1, signature
|
|
23
|
-
splitNote(callback, k1s, amountMsat, opts?) -> {k1, change, signature
|
|
24
|
-
mergeNotes(callback, k1s, opts?) -> {k1, signature
|
|
21
|
+
fetchNoteInfo(url, opts?) -> {callback, k1, maxWithdrawable, mintPubkey}
|
|
22
|
+
rotateNote(callback, k1, opts?) -> {k1, signature}
|
|
23
|
+
splitNote(callback, k1s, amountMsat, opts?) -> {k1, change, signature, changeSignature}
|
|
24
|
+
mergeNotes(callback, k1s, opts?) -> {k1, signature}
|
|
25
25
|
meltNote(callback, k1, bolt11, opts?) -> {pr?, verify?}
|
|
26
26
|
settleNote(baseUrl, k1, expectedMsat, sig?, opts?) -> {k1, amountMsat, signature?, callback}
|
|
27
27
|
settleNoteForValue(noteUrl, {mints, minMsat, requireSignature?}, opts?)
|
|
@@ -53,7 +53,11 @@ paymentRequestAmountMsat(req) -> msat request amount is in SAT, this is the
|
|
|
53
53
|
parseMintFee(metadata) / applyMintFee(gross, fee) / grossUpForMintFee(net, fee)
|
|
54
54
|
createClient(opts) -> all of the above with opts bound
|
|
55
55
|
|
|
56
|
-
Options (always last): {fetch?, timeoutMs?, offline?, randomSecret
|
|
56
|
+
Options (always last): {fetch?, timeoutMs?, offline?, randomSecret?,
|
|
57
|
+
requireSignatures?, mutationRetries?}
|
|
58
|
+
requireSignatures defaults true: refuse a mint that publishes no
|
|
59
|
+
mintPubkey or returns an unsigned mutation. mutationRetries defaults 1:
|
|
60
|
+
re-send a rotate/split/merge whose answer was lost, never a melt.
|
|
57
61
|
|
|
58
62
|
## Rules an implementation MUST follow
|
|
59
63
|
|
|
@@ -64,12 +68,23 @@ Options (always last): {fetch?, timeoutMs?, offline?, randomSecret?}
|
|
|
64
68
|
3. On ANY error from a mutation call newSecretsOf(err) FIRST, persist what
|
|
65
69
|
it returns, then call probeBurnedNote to learn what happened. Treating a
|
|
66
70
|
failure as a failure destroys money the service may already have minted.
|
|
67
|
-
AmbiguousMutationError always carries secrets; so does
|
|
68
|
-
or NoteUnknownError from a mutation, because
|
|
69
|
-
GET looks like
|
|
71
|
+
AmbiguousMutationError always carries secrets; so does UnverifiableNoteError,
|
|
72
|
+
and so does a NoteSpentError or NoteUnknownError from a mutation, because
|
|
73
|
+
that is also what a retried GET looks like at a service that has not
|
|
74
|
+
implemented the replay rule.
|
|
70
75
|
4. RequestRefusedError means nothing was sent - safe to treat as no-op.
|
|
71
76
|
5. A melt's OK means IN FLIGHT, not spent. PendingNoteError means retry, not
|
|
72
77
|
spent.
|
|
78
|
+
5b. Offline verification is MANDATORY. A conforming service publishes
|
|
79
|
+
mintPubkey on every withdrawRequest and returns sig (and sig2 on a split)
|
|
80
|
+
from every rotate/split/merge. This library refuses a service that does
|
|
81
|
+
not; UnverifiableNoteError means the mutation LANDED unsigned, so keep its
|
|
82
|
+
secrets. Opt out per call with requireSignatures: false.
|
|
83
|
+
5c. A retried rotate/split/merge MUST be answered by the service as a replay
|
|
84
|
+
of the original success, so re-sending one whose answer was lost is safe
|
|
85
|
+
and usually completes it. Re-send the IDENTICAL request: the replay is
|
|
86
|
+
matched on the k1 set, h, h2 and amount, so a fresh secret makes it a
|
|
87
|
+
different mutation and a second real burn. Never re-send a melt.
|
|
73
88
|
6. Rotate immediately after claiming a minted note: the mint generated that
|
|
74
89
|
preimage, and LUD-21 verify exposes it to anyone who saw the invoice.
|
|
75
90
|
Does not apply to a note minted with `h` - see 13.
|
|
@@ -112,14 +127,18 @@ Options (always last): {fetch?, timeoutMs?, offline?, randomSecret?}
|
|
|
112
127
|
RequestRefusedError nothing sent, note untouched
|
|
113
128
|
ServiceRejectedError processed and refused (definitive)
|
|
114
129
|
PendingNoteError a melt is in flight on this k1 - retry
|
|
115
|
-
NoteSpentError authoritative: already burned - but from a MUTATION
|
|
116
|
-
|
|
130
|
+
NoteSpentError authoritative: already burned - but from a MUTATION at
|
|
131
|
+
a service that will not replay a retry, it may be a
|
|
132
|
+
retry whose first attempt landed; read
|
|
117
133
|
newSecretsOf(err) before believing it
|
|
118
134
|
NoteUnknownError service does not recognise it
|
|
119
135
|
AmbiguousMintError outcome UNKNOWN - assume nothing
|
|
120
136
|
AmbiguousMutationError carries .newSecrets - persist them
|
|
137
|
+
UnverifiableNoteError the mutation LANDED and came back unsigned. The note is
|
|
138
|
+
real; carries .newSecrets - persist them
|
|
121
139
|
newSecretsOf(err) -> string[] the secrets any error is carrying, or none
|
|
122
|
-
ProtocolError a non-mutating response did not match the spec
|
|
140
|
+
ProtocolError a non-mutating response did not match the spec, which
|
|
141
|
+
includes a withdrawRequest publishing no mintPubkey
|
|
123
142
|
|
|
124
143
|
## Conformance
|
|
125
144
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lnurlcash-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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.6.0",
|
|
64
64
|
"tsup": "^8.5.0",
|
|
65
65
|
"typescript": "^5.7.0",
|
|
66
66
|
"vitest": "^3.0.0"
|