lnurlcash-kit 0.1.0-next.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 ADDED
@@ -0,0 +1,59 @@
1
+ # Changelog
2
+
3
+ Semantic versioning. While the LUD-25 draft is unmerged, `0.x` minor bumps
4
+ may carry breaking changes; pin an exact version.
5
+
6
+ ## 0.1.0 - 2026-08-20
7
+
8
+ First release. The protocol layer of
9
+ [lnurl-wallet](https://github.com/dni/lnurl-wallet) (MIT, dni), extracted as
10
+ a standalone library.
11
+
12
+ ### Changes made on extraction
13
+
14
+ **No globals.** The wallet's `offlineMode()` global became an `offline`
15
+ option, and `fetch`, `timeoutMs` and `randomSecret` joined it. Nothing reads
16
+ ambient state, so a caller can be certain what a call will and will not do.
17
+ `createClient(options)` binds one set for callers who would otherwise thread
18
+ the same object everywhere.
19
+
20
+ **No DOM or storage assumptions.** Only `fetch`, `URL` and `crypto` are
21
+ required, all substitutable.
22
+
23
+ ### Behavioural fixes
24
+
25
+ Both were found by the conformance vectors, and both exist in the source
26
+ this was extracted from.
27
+
28
+ **A reasonless service error is no longer reported as an unknown note.**
29
+ `{"status":"ERROR"}` with no `reason` had a friendly default substituted
30
+ before classification, and that default — "Unknown service error" — matched
31
+ the rule for "unknown note". A service that said nothing was therefore
32
+ reported as denying the note exists, and through `probeBurnedNote` that
33
+ reads as "the burn landed": a conclusion about somebody's money drawn from a
34
+ blank. The reason is now carried through exactly as sent, empty included.
35
+
36
+ **`grossUpForMintFee` returns the true minimum, and cannot be stalled.** It
37
+ estimated linearly then walked one msat at a time, bounded by a guard. At a
38
+ 99.9999% fee the walk is around a million steps, so the guard tripped and
39
+ the answer came back non-minimal — and the fee is chosen by the service, so
40
+ that input is reachable on purpose. It is now a binary search, which is
41
+ exact and bounded for every fee.
42
+
43
+ **The proportional fee term no longer overflows.** `gross * ppm / 1_000_000`
44
+ exceeds 64-bit unsigned at realistic amounts — 21M BTC is 2.1e15 msat, times
45
+ 999_999 ppm is about 2.1e21 — and exceeds a double's exact range too. It is
46
+ computed split. This changes nothing in TypeScript at ordinary amounts, and
47
+ matters a great deal to the ports.
48
+
49
+ ### Additions
50
+
51
+ **A mutation naming no note is refused** before it reaches the network,
52
+ rather than sent as a callback with no `k1` for a service to interpret
53
+ generously.
54
+
55
+ **`mint@localhost:8000` resolves.** A Lightning Address needs a dot to be a
56
+ domain, so a bare local host was rejected even though the resolution below
57
+ it already handled the port and the cleartext scheme such a host needs. The
58
+ strict `isLightningAddress` is unchanged; only the resolvers are more
59
+ generous, and only for hosts that are already treated as insecure.
package/LICENSE ADDED
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dni
4
+ Copyright (c) 2026 TheCryptoDonkey
5
+
6
+ This library's protocol layer was extracted from lnurl-wallet
7
+ (https://github.com/dni/lnurl-wallet), the LNURLcash reference wallet, which
8
+ is MIT licensed and authored by dni. Subsequent changes are TheCryptoDonkey's.
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # lnurlcash-kit
2
+
3
+ LNURLcash ([LUD-25 draft](https://github.com/lnurl/luds/pull/301)) bearer
4
+ notes for TypeScript: mint, rotate, split, merge, melt, and verify a note
5
+ offline.
6
+
7
+ ```bash
8
+ npm install lnurlcash-kit
9
+ ```
10
+
11
+ This is an early `0.x` release tracking a **draft** spec. Pin an exact
12
+ version.
13
+
14
+ ## What a bearer note is
15
+
16
+ An ordinary [LUD-03](https://github.com/lnurl/luds/blob/luds/03.md)
17
+ withdrawRequest link whose `k1` **is** the asset:
18
+
19
+ ```
20
+ lnurlw://mint.example/w?k1=<secret>&amount=<msat>
21
+ ```
22
+
23
+ Whoever knows the `k1` controls the sats behind it, like a banknote. The
24
+ `amount` alongside it is only a claim by whoever encoded the note; the
25
+ authoritative value is always `maxWithdrawable` from an informational GET.
26
+
27
+ No new endpoint and no new encoding, so a wallet that has never heard of
28
+ LNURLcash sees a normal withdraw link and can still cash it out. Every
29
+ mutating operation is a GET on the `callback` from that withdrawRequest:
30
+
31
+ | Request | Result |
32
+ | --- | --- |
33
+ | `callback?k1=X&pr=<bolt11>` | **melt**: X burned once `pr` settles |
34
+ | `callback?k1=X&h=<sha256(X')>` | **rotate**: X burned, a note keyed by `h` minted |
35
+ | `callback?k1=X&amount=<msat>&h=..&h2=..` | **split**: X burned, notes keyed by `h` and `h2` minted |
36
+ | `callback?k1=X&k1=Y&h=<sha256(Z)>` | **merge**: all burned, one note keyed by `h` minted |
37
+
38
+ ## Usage
39
+
40
+ ```ts
41
+ import {
42
+ resolveNoteInput,
43
+ fetchNoteInfo,
44
+ rotateNote,
45
+ splitNote,
46
+ meltNote,
47
+ verifyNoteSignature
48
+ } from 'lnurlcash-kit'
49
+
50
+ // accepts a bech32 LNURL, an lnurlw:// URL, or a plain https one
51
+ const url = resolveNoteInput(scanned)
52
+ if (!url) throw new Error('not a note')
53
+
54
+ // what is it actually worth? Only the service can say.
55
+ const info = await fetchNoteInfo(url)
56
+ console.log(info.maxWithdrawable, 'msat')
57
+
58
+ // that GET put the secret on the wire, so rotate it
59
+ const fresh = await rotateNote(info.callback, info.k1)
60
+
61
+ // and check the mint really issued it, without asking anyone
62
+ if (info.mintPubkey && fresh.signature) {
63
+ verifyNoteSignature(fresh.k1, info.maxWithdrawable, fresh.signature, info.mintPubkey)
64
+ }
65
+ ```
66
+
67
+ Every request function takes options last — `fetch`, `timeoutMs`, `offline`,
68
+ `randomSecret`. `createClient(options)` binds one set once:
69
+
70
+ ```ts
71
+ const client = createClient({timeoutMs: 10_000})
72
+ await client.rotateNote(callback, k1)
73
+ ```
74
+
75
+ ## The five things that will cost you money
76
+
77
+ Everything below is a bug class this library exists to close. If you write
78
+ your own client instead, write these first.
79
+
80
+ **1. Never let the service generate a replacement secret.** On rotate, split
81
+ and merge the *wallet* draws a fresh 32 bytes and discloses only
82
+ `sha256(secret)` as `h`. A service-issued replacement has, structurally,
83
+ been seen by that service — so a "rotate" that accepts one closes no
84
+ exposure at all. This library generates them and ignores any `k1` a
85
+ non-compliant service tries to hand back.
86
+
87
+ **2. A failed mutation is not a failure.** If a rotate times out, the
88
+ service may already have burned your input and minted the output. The fresh
89
+ secret in your process is then the only copy of that money in existence.
90
+ Every mutating call raises `AmbiguousMutationError` carrying `newSecrets` —
91
+ **persist them before doing anything else**, then use `probeBurnedNote` to
92
+ find out what happened:
93
+
94
+ ```ts
95
+ try {
96
+ const {k1} = await rotateNote(callback, oldK1)
97
+ } catch (err) {
98
+ if (err instanceof AmbiguousMutationError) {
99
+ await save(err.newSecrets) // first. always.
100
+ const fate = await probeBurnedNote(noteUrl)
101
+ // 'live' -> nothing landed, the saved secrets are worthless
102
+ // 'gone' -> the burn landed, the saved secrets ARE the note
103
+ // 'unknown' -> keep everything and try again later
104
+ }
105
+ }
106
+ ```
107
+
108
+ `RequestRefusedError` is the opposite and safe: nothing left the process.
109
+
110
+ **3. Your HTTP stack must not retry.** Every mutation is a GET, HTTP treats GET
111
+ as idempotent, and an LNURLcash mutation is not — the first attempt burns the
112
+ input. A retried mutation is answered "already spent", which reads as a
113
+ *definitive* rejection, so the fresh secret gets discarded along with the note
114
+ the service just minted. Node's `fetch` does not retry on its own, but a
115
+ browser will resend an idempotent request that failed on a stale pooled
116
+ connection, and any retry wrapper, service worker or proxy in front of this
117
+ will do the same. If you pass your own `fetch`, do not make it retry these.
118
+
119
+ This is not hypothetical: the same hazard broke the
120
+ [Kotlin](https://github.com/TheCryptoDonkey/lnurlcash-kotlin) and
121
+ [Go](https://github.com/TheCryptoDonkey/lnurlcash-go) siblings during
122
+ development, by two different mechanisms, and is now a named scenario in the
123
+ conformance vectors.
124
+
125
+ **4. A melt's `OK` means "in flight", not "spent".** The service pays
126
+ asynchronously and only burns the note once the payment settles, restoring
127
+ it if the payment fails. A failed melt is never reported back through the
128
+ callback — it is only observable as the note becoming spendable again. Other
129
+ operations on that `k1` raise `PendingNoteError` meanwhile; retry, never
130
+ read it as spent.
131
+
132
+ **5. Rotate the instant you claim a minted note.** The preimage that mints a
133
+ note is generated by the service, and if it serves
134
+ [LUD-21](https://github.com/lnurl/luds/blob/luds/21.md) `verify`, *anyone*
135
+ who saw the unpaid invoice can poll for it — the payment hash travels inside
136
+ the invoice. First rotater wins. A wallet that rotates on settlement wins by
137
+ construction; a human copying a preimage by hand does not.
138
+
139
+ ## Offline verification
140
+
141
+ A service may sign each note with its Lightning node identity key, so a
142
+ holder can confirm issuer and amount with nothing but the note:
143
+
144
+ ```
145
+ message = "LNURLcash:" || amount_msat || ":" || hex(sha256(k1))
146
+ digest = sha256(sha256("Lightning Signed Message:" || message))
147
+ sig = 65 bytes, r || s || recovery_id
148
+ ```
149
+
150
+ `verifyNoteSignature` recovers the pubkey and compares it to `mintPubkey`.
151
+ It accepts the recovery id at either end, because lnurl-mint once emitted
152
+ the reverse layout and other implementations may still; trying both is safe,
153
+ since the wrong ordering recovers an unrelated key that cannot match.
154
+
155
+ The signature commits to the note's *hash*, not its secret — so you can
156
+ prove a mint issued a note, to expose one that will not honour it, without
157
+ handing over what would let anyone spend it.
158
+
159
+ ## Scope
160
+
161
+ This library speaks the protocol. It does not store notes, hold keys, manage
162
+ a balance, pay invoices, or decide anything about your UI. Storage and key
163
+ management are yours, and they are where most of the remaining risk lives —
164
+ see [THREAT-MODEL.md](THREAT-MODEL.md).
165
+
166
+ Amounts are integers in **milli-satoshis**, everywhere, with no exceptions.
167
+
168
+ ## Provenance
169
+
170
+ The protocol layer was extracted from
171
+ [lnurl-wallet](https://github.com/dni/lnurl-wallet), the LNURLcash reference
172
+ wallet by dni, rather than reimplemented — that code has been exercised
173
+ against a real mint and an adversarial mock, and a fresh rewrite would have
174
+ thrown that away to no one's benefit.
175
+
176
+ The reference implementations, both dni's, both MIT:
177
+
178
+ - [lnurl-mint](https://github.com/dni/lnurl-mint) — the reference service
179
+ - [lnurl-wallet](https://github.com/dni/lnurl-wallet) — the reference wallet
180
+
181
+ Changes made on extraction are listed in [CHANGELOG.md](CHANGELOG.md); two
182
+ are behavioural fixes worth reading if you are porting from that code.
183
+
184
+ ## Conformance
185
+
186
+ Tested against [lnurlcash-conformance](https://github.com/TheCryptoDonkey/lnurlcash-conformance):
187
+ language-neutral vectors plus a mock mint that can be told to misbehave —
188
+ drop a connection mid-mutation, sign in the wrong byte order, lie about a
189
+ note's value, never settle a melt. If you are writing an LNURLcash
190
+ implementation in any language, run those vectors before you run real sats
191
+ through it.
192
+
193
+ ```bash
194
+ npm test
195
+ ```
196
+
197
+ ## License
198
+
199
+ MIT. See [LICENSE](LICENSE) for the attribution.
package/SECURITY.md ADDED
@@ -0,0 +1,39 @@
1
+ # Security policy
2
+
3
+ ## Supported versions
4
+
5
+ `0.x` tracks a draft spec ([LUD-25](https://github.com/lnurl/luds/pull/301)).
6
+ Only the latest `0.x` release is supported. Pin an exact version.
7
+
8
+ ## Reporting a vulnerability
9
+
10
+ Report privately through GitHub's advisory form:
11
+
12
+ <https://github.com/TheCryptoDonkey/lnurlcash-kit/security/advisories/new>
13
+
14
+ Please do not open a public issue for anything that could be used to take
15
+ somebody's notes.
16
+
17
+ Include what you can: affected version, a reproduction, and what an attacker
18
+ gets out of it. A rough report today beats a polished one next month.
19
+
20
+ Expect an acknowledgement within a few days. This is maintained by one
21
+ person, so timelines are best-effort rather than contractual.
22
+
23
+ ## Scope
24
+
25
+ **In scope**: anything in this library that could lose or leak a note —
26
+ secrets on the wire, a mutation misclassified as definitive, verification
27
+ that accepts a signature it should not, URL admission bypasses, a fee or
28
+ amount calculation that can be steered by a hostile mint.
29
+
30
+ **Out of scope**: how a calling application stores secrets, weak RNG
31
+ substituted through `randomSecret`, the behaviour of any particular mint, and
32
+ the LUD-25 draft itself — spec concerns belong on
33
+ [the PR](https://github.com/lnurl/luds/pull/301), where they help everyone
34
+ rather than just this library.
35
+
36
+ If a finding affects the protocol rather than this implementation, it is
37
+ worth telling the reference implementations too:
38
+ [lnurl-mint](https://github.com/dni/lnurl-mint) and
39
+ [lnurl-wallet](https://github.com/dni/lnurl-wallet).
@@ -0,0 +1,158 @@
1
+ # Threat model
2
+
3
+ What this library defends against, what it cannot, and what it hands to you.
4
+
5
+ ## What it is
6
+
7
+ A protocol client for LNURLcash bearer notes. It builds requests, classifies
8
+ responses, generates replacement note secrets, and verifies mint signatures
9
+ offline. It holds no state between calls.
10
+
11
+ ## Assets
12
+
13
+ **Note secrets (`k1`).** Bearer instruments. Whoever holds one can spend it,
14
+ with no further authentication, from anywhere. Compromise is theft, and it
15
+ is silent and irreversible: the money is gone before the previous holder has
16
+ any way to notice.
17
+
18
+ **Replacement secrets awaiting confirmation.** After a mutation whose outcome
19
+ is unknown, the secrets generated in-process may be the only copies of notes
20
+ a service has already minted. Losing them destroys the money exactly as
21
+ thoroughly as leaking them gives it away.
22
+
23
+ **Mint pubkeys.** Not secret, but a wrong one makes offline verification
24
+ meaningless.
25
+
26
+ ## Trust boundaries
27
+
28
+ | Party | Trusted for |
29
+ | --- | --- |
30
+ | the SERVICE (mint) | custody of the sats, and honest accounting. Nothing else. |
31
+ | the caller's storage | confidentiality and durability of secrets. This library provides neither. |
32
+ | the caller's RNG | unpredictability of replacement secrets. Substitutable, and load-bearing. |
33
+ | the network | nothing. |
34
+
35
+ A mint is trusted with custody by construction — it holds the funds. It is
36
+ *not* trusted to describe them accurately, which is why value comes from
37
+ `maxWithdrawable` rather than from a note URL's own `amount`, and why a
38
+ signed note can be checked against a key the mint published earlier.
39
+
40
+ ## What this library defends against
41
+
42
+ **A service that keeps a copy of your note.** Rotate, split and merge disclose
43
+ only `sha256(secret)`. The service registers the note under that hash and
44
+ never sees the secret. This is the difference between a bearer note and a
45
+ receipt, and it is why a service-generated replacement is refused even when
46
+ offered (`serverGeneratedSecrets` in the mock mint exercises exactly this).
47
+
48
+ **A mutation whose outcome is unknown.** Timeouts, dropped connections,
49
+ unreadable bodies and unconfirmed 200s are all raised as
50
+ `AmbiguousMutationError` carrying the fresh secrets, never as failure.
51
+ Requests that provably never left — offline mode, a refused URL, an
52
+ unparseable callback — are raised as `RequestRefusedError` instead, which is
53
+ safe to treat as "nothing happened".
54
+
55
+ **A note that answers its own questions.** Every URL fetched, whether scanned
56
+ by a user or supplied by a service in its own response, must be https, or
57
+ http to loopback or `.onion`. A `data:` URL carrying withdrawRequest JSON
58
+ would otherwise mint a self-contained fake note that verifies against
59
+ nothing. Redirects are followed by hand, each hop re-admitted against the
60
+ same rule, so an https endpoint cannot pass a request - and the k1 a
61
+ callback URL carries - off to cleartext or another scheme mid-chain. And
62
+ every response body is read with a size cap, so one hostile answer cannot
63
+ exhaust the caller's memory.
64
+
65
+ **A service that inflates a note.** With offline verification configured, the
66
+ signature commits to the amount. A service reporting more than it signed
67
+ fails verification, without the holder contacting anyone.
68
+
69
+ **A service that swaps your note.** The informational GET checks that the
70
+ echoed `k1` is the one queried. A different one means either a non-compliant
71
+ service or a note redeemed by somebody else.
72
+
73
+ **A secret leaking through a query string.** `sig` is stripped before the
74
+ informational GET, since the service already knows what it signed.
75
+
76
+ **A hostile fee advertisement.** Fees of 100% or more are refused at parse
77
+ time, and the gross-up search is a binary search rather than a walk — so a
78
+ service cannot stall a caller with an extreme fee.
79
+
80
+ **Integer overflow on realistic amounts.** The proportional fee term is
81
+ computed split, because 21M BTC in msat times a high ppm exceeds 64-bit
82
+ unsigned. Ports that multiply naively pass every small test and mangle large
83
+ ones; the conformance vectors include a case that catches it.
84
+
85
+ ## What it does not defend against
86
+
87
+ **Storage compromise.** This library never persists anything. If your
88
+ storage is readable — an unencrypted database, a synced folder, a debugger,
89
+ a crash dump — every note in it is spendable by whoever reads it. Encrypt at
90
+ rest, and treat backups as the same exposure.
91
+
92
+ **A weak RNG.** `randomSecret` is replaceable, which means it can be replaced
93
+ badly. A predictable secret is a note anyone can mint themselves. Use the
94
+ platform CSPRNG, or a hardware RNG, and nothing else.
95
+
96
+ **Secrets in logs.** A note URL carries its secret in a query string. A
97
+ request logger, an error reporter, a crash handler or an analytics SDK that
98
+ records URLs records bearer money. This library never logs; what wraps it
99
+ might.
100
+
101
+ **A malicious or compromised mint.** It holds the funds. It can refuse to
102
+ honour a note, vanish, or inflate its liabilities. Offline verification
103
+ proves what it *said*, which is useful for exposing it afterwards, and is
104
+ not custody.
105
+
106
+ **The mint-time preimage race.** A freshly minted note's secret is the
107
+ invoice preimage, so the service has necessarily seen it, and anyone who saw
108
+ the unpaid invoice can poll LUD-21 `verify` for it the moment it settles.
109
+ Rotating immediately wins that race; a slow manual flow does not. Do not
110
+ publish unpaid mint invoices.
111
+
112
+ **Traffic analysis.** Every operation reaches the mint directly. The mint
113
+ learns your IP, your timing, and which notes move together. Notes are bearer
114
+ instruments, not private ones — merging several notes tells the mint they
115
+ had one holder. Route over Tor if that matters.
116
+
117
+ **Anything about the sats themselves.** No custody, no channel management,
118
+ no payment routing.
119
+
120
+ ## The retry hazard
121
+
122
+ Every LNURLcash mutation is an HTTP GET, and HTTP treats GET as idempotent, so
123
+ a stack may resend one when a connection fails mid-flight. An LNURLcash
124
+ mutation is not idempotent: the first attempt burns the input.
125
+
126
+ A retried mutation is therefore answered "already spent", which classifies as a
127
+ definitive rejection — so a caller concludes nothing happened and discards the
128
+ fresh secret that was the only copy of the note the service just minted.
129
+
130
+ This library never retries, and the `fetch` it uses by default does not either
131
+ in Node. It cannot control what wraps it: a browser resending an idempotent
132
+ request on a stale pooled connection, a service worker, a retry interceptor, a
133
+ proxy or a mesh with automatic retries would all reintroduce it. A caller
134
+ passing its own `fetch` owns this.
135
+
136
+ The hazard is real rather than theoretical — it broke the Kotlin and Go
137
+ siblings during development, by two different mechanisms — and is a named
138
+ scenario in the conformance vectors.
139
+
140
+ ## Deliberate design choices
141
+
142
+ **Both signature recovery-id orderings are accepted.** The wire format is
143
+ `r || s || recovery_id`; lnurl-mint once emitted the reverse. Trying both is
144
+ not a weakening: recovering under the wrong ordering yields an unrelated
145
+ pubkey, which cannot match the expected one.
146
+
147
+ **Errors are typed by whether the request could have been processed**, not by
148
+ transport detail. That distinction is the whole safety model, and message
149
+ text is not a stable interface — never branch on it.
150
+
151
+ **No global state.** Offline mode, fetch, timeout and RNG are parameters. A
152
+ caller that wants certainty nothing reaches the network sets `offline` and
153
+ gets a refusal, rather than trusting that no code path happens to make a
154
+ request.
155
+
156
+ ## Reporting
157
+
158
+ See [SECURITY.md](SECURITY.md).
@@ -0,0 +1,193 @@
1
+ declare const isBech32Lnurl: (data: string) => boolean;
2
+ declare const toBech32Lnurl: (url: string) => string;
3
+ declare const fromBech32Lnurl: (data: string) => string | null;
4
+ declare const isAllowedServiceUrl: (value: string) => boolean;
5
+ declare const fromLud17: (url: string) => string;
6
+ declare const toLud17w: (url: string) => string;
7
+ declare const isLightningAddress: (value: string) => boolean;
8
+ declare const resolveMintInput: (value: string) => string | null;
9
+ declare const mintAddressUrl: (payUrl: string) => string | null;
10
+ declare const lightningAddressUsername: (payUrl: string) => string | null;
11
+ declare const resolveLnurlInput: (value: string) => string | null;
12
+ declare const serverOf: (url: string) => string;
13
+
14
+ declare const noteK1: (url: string) => string | null;
15
+ declare const requireNoteK1: (url: string) => string;
16
+ declare const noteDeclaredAmount: (url: string) => number | null;
17
+ declare const noteSignature: (url: string) => string | null;
18
+ declare const resolveNoteInput: (value: string) => string | null;
19
+ declare const isValidNoteInput: (value: string) => boolean;
20
+ declare const buildNoteUrl: (withdrawLink: string, k1: string, amountMsat?: number) => string;
21
+ declare const withNewK1: (url: string, k1: string, amountMsat: number, signature?: string) => string;
22
+ declare const withoutK1: (url: string, amountMsat: number, signature?: string) => string;
23
+
24
+ declare const hashK1: (k1: string) => string;
25
+ type RandomSecret = () => string;
26
+ declare const defaultRandomSecret: RandomSecret;
27
+ declare const isPreimage: (value: string) => boolean;
28
+
29
+ declare const noteSignatureMessage: (k1: string, amountMsat: number) => string;
30
+ declare const noteSignatureDigest: (k1: string, amountMsat: number) => Uint8Array;
31
+ declare const verifyNoteSignature: (k1: string, amountMsat: number, signatureHex: string, mintPubkeyHex: string) => boolean;
32
+
33
+ type MintFee = {
34
+ baseFeeMsat: number;
35
+ feePpm: number;
36
+ };
37
+ declare const parseMintFee: (metadata: string) => MintFee | null;
38
+ declare const applyMintFee: (grossMsat: number, fee: MintFee) => number;
39
+ declare const grossUpForMintFee: (netMsat: number, fee: MintFee) => number;
40
+ declare const formatFeePercent: (ppm: number) => string;
41
+ declare const describeMintFee: (fee: MintFee) => string;
42
+
43
+ declare const isBolt11Invoice: (value: string) => boolean;
44
+ declare const sameInvoice: (a: string, b: string) => boolean;
45
+ declare const decodeBolt11AmountMsat: (pr: string) => number | null;
46
+
47
+ declare class LnurlcashError extends Error {
48
+ constructor(message: string);
49
+ }
50
+ declare class RequestRefusedError extends LnurlcashError {
51
+ }
52
+ declare class ProtocolError extends LnurlcashError {
53
+ }
54
+ declare class ServiceRejectedError extends LnurlcashError {
55
+ readonly reason: string;
56
+ constructor(reason: string);
57
+ }
58
+ declare class PendingNoteError extends ServiceRejectedError {
59
+ constructor(reason?: string);
60
+ }
61
+ declare class NoteSpentError extends ServiceRejectedError {
62
+ constructor(reason: string);
63
+ }
64
+ declare class NoteUnknownError extends ServiceRejectedError {
65
+ constructor(reason: string);
66
+ }
67
+ declare class AmbiguousMintError extends LnurlcashError {
68
+ }
69
+ declare class AmbiguousMutationError extends AmbiguousMintError {
70
+ readonly newSecrets: string[];
71
+ constructor(message: string, newSecrets: string[]);
72
+ }
73
+ declare const classifyNoteError: (reason: string) => ServiceRejectedError;
74
+
75
+ type LnurlcashOptions = {
76
+ fetch?: typeof globalThis.fetch;
77
+ timeoutMs?: number;
78
+ offline?: boolean;
79
+ randomSecret?: RandomSecret;
80
+ };
81
+
82
+ type WithdrawRequestInfo = {
83
+ tag: 'withdrawRequest';
84
+ callback: string;
85
+ k1: string;
86
+ minWithdrawable: number;
87
+ maxWithdrawable: number;
88
+ defaultDescription?: string;
89
+ mintPubkey?: string;
90
+ };
91
+ declare const fetchNoteInfo: (url: string, options?: LnurlcashOptions) => Promise<WithdrawRequestInfo>;
92
+ declare const probeBurnedNote: (url: string, options?: LnurlcashOptions) => Promise<"live" | "gone" | "unknown">;
93
+ type MintAddressInfo = {
94
+ tag: 'withdrawRequest';
95
+ callback: string;
96
+ minWithdrawable: number;
97
+ maxWithdrawable: number;
98
+ defaultDescription?: string;
99
+ nodePubkey?: string;
100
+ payLink: string;
101
+ nodeAlias?: string;
102
+ nodeUri?: string;
103
+ nodeColor?: string;
104
+ nodeCapacityMsat?: number;
105
+ nodeNumChannels?: number;
106
+ nodeNumPeers?: number;
107
+ };
108
+ declare const fetchMintAddress: (url: string, options?: LnurlcashOptions) => Promise<MintAddressInfo>;
109
+ type WithdrawSuccessResponse = {
110
+ status: 'OK';
111
+ sig?: string;
112
+ sig2?: string;
113
+ pr?: string;
114
+ verify?: string;
115
+ };
116
+ type MeltResult = {
117
+ verify?: string;
118
+ pr?: string;
119
+ };
120
+ declare const meltNote: (callback: string, k1: string, pr: string, options?: LnurlcashOptions) => Promise<MeltResult>;
121
+ type HashedMutationResult = {
122
+ signature?: string;
123
+ };
124
+ declare const rotateNoteWithHash: (callback: string, k1: string, h: string, options?: LnurlcashOptions) => Promise<HashedMutationResult>;
125
+ type HashedSplitResult = {
126
+ signature?: string;
127
+ changeSignature?: string;
128
+ };
129
+ declare const splitNoteWithHash: (callback: string, k1s: string[], amountMsat: number, h: string, h2: string, options?: LnurlcashOptions) => Promise<HashedSplitResult>;
130
+ declare const mergeNotesWithHash: (callback: string, k1s: string[], h: string, options?: LnurlcashOptions) => Promise<HashedMutationResult>;
131
+ type RotateResult = {
132
+ k1: string;
133
+ signature?: string;
134
+ };
135
+ declare const rotateNote: (callback: string, k1: string, options?: LnurlcashOptions) => Promise<RotateResult>;
136
+ type SplitResult = {
137
+ k1: string;
138
+ signature?: string;
139
+ change: string;
140
+ changeSignature?: string;
141
+ };
142
+ declare const splitNote: (callback: string, k1s: string[], amountMsat: number, options?: LnurlcashOptions) => Promise<SplitResult>;
143
+ declare const mergeNotes: (callback: string, k1s: string[], options?: LnurlcashOptions) => Promise<RotateResult>;
144
+ type SettledNote = {
145
+ k1: string;
146
+ amountMsat: number;
147
+ signature?: string;
148
+ callback: string;
149
+ };
150
+ declare const settleNote: (baseUrl: string, k1: string, expectedAmountMsat: number, signature: string | undefined, options?: LnurlcashOptions) => Promise<SettledNote>;
151
+ type PayRequestInfo = {
152
+ tag: 'payRequest';
153
+ callback: string;
154
+ minSendable: number;
155
+ maxSendable: number;
156
+ metadata: string;
157
+ withdrawLink?: string;
158
+ mintPubkey?: string;
159
+ mintFee?: MintFee;
160
+ };
161
+ declare const fetchPayRequest: (url: string, options?: LnurlcashOptions) => Promise<PayRequestInfo>;
162
+ type InvoiceResult = {
163
+ pr: string;
164
+ verify?: string;
165
+ disposable: boolean;
166
+ };
167
+ declare const requestInvoice: (payCallback: string, amountMsat: number, options?: LnurlcashOptions) => Promise<InvoiceResult>;
168
+ type VerifyResult = {
169
+ settled: boolean;
170
+ preimage: string | null;
171
+ pr: string;
172
+ };
173
+ declare const fetchInvoiceVerification: (verifyUrl: string, options?: LnurlcashOptions) => Promise<VerifyResult>;
174
+
175
+ declare const createClient: (options?: LnurlcashOptions) => {
176
+ fetchNoteInfo: (url: string) => Promise<WithdrawRequestInfo>;
177
+ probeBurnedNote: (url: string) => Promise<"live" | "gone" | "unknown">;
178
+ fetchMintAddress: (url: string) => Promise<MintAddressInfo>;
179
+ meltNote: (callback: string, k1: string, pr: string) => Promise<MeltResult>;
180
+ rotateNote: (callback: string, k1: string) => Promise<RotateResult>;
181
+ rotateNoteWithHash: (callback: string, k1: string, h: string) => Promise<HashedMutationResult>;
182
+ splitNote: (callback: string, k1s: string[], amountMsat: number) => Promise<SplitResult>;
183
+ splitNoteWithHash: (callback: string, k1s: string[], amountMsat: number, h: string, h2: string) => Promise<HashedSplitResult>;
184
+ mergeNotes: (callback: string, k1s: string[]) => Promise<RotateResult>;
185
+ mergeNotesWithHash: (callback: string, k1s: string[], h: string) => Promise<HashedMutationResult>;
186
+ settleNote: (baseUrl: string, k1: string, expectedAmountMsat: number, signature?: string) => Promise<SettledNote>;
187
+ fetchPayRequest: (url: string) => Promise<PayRequestInfo>;
188
+ requestInvoice: (payCallback: string, amountMsat: number) => Promise<InvoiceResult>;
189
+ fetchInvoiceVerification: (verifyUrl: string) => Promise<VerifyResult>;
190
+ };
191
+ type LnurlcashClient = ReturnType<typeof createClient>;
192
+
193
+ export { AmbiguousMintError, AmbiguousMutationError, type HashedMutationResult, type HashedSplitResult, type InvoiceResult, type LnurlcashClient, LnurlcashError, type LnurlcashOptions, type MeltResult, type MintAddressInfo, type MintFee, NoteSpentError, NoteUnknownError, type PayRequestInfo, PendingNoteError, ProtocolError, type RandomSecret, RequestRefusedError, type RotateResult, ServiceRejectedError, type SettledNote, type SplitResult, type VerifyResult, type WithdrawRequestInfo, type WithdrawSuccessResponse, applyMintFee, buildNoteUrl, classifyNoteError, createClient, decodeBolt11AmountMsat, defaultRandomSecret, describeMintFee, fetchInvoiceVerification, fetchMintAddress, fetchNoteInfo, fetchPayRequest, formatFeePercent, fromBech32Lnurl, fromLud17, grossUpForMintFee, hashK1, isAllowedServiceUrl, isBech32Lnurl, isBolt11Invoice, isLightningAddress, isPreimage, isValidNoteInput, lightningAddressUsername, meltNote, mergeNotes, mergeNotesWithHash, mintAddressUrl, noteDeclaredAmount, noteK1, noteSignature, noteSignatureDigest, noteSignatureMessage, parseMintFee, probeBurnedNote, requestInvoice, requireNoteK1, resolveLnurlInput, resolveMintInput, resolveNoteInput, rotateNote, rotateNoteWithHash, sameInvoice, serverOf, settleNote, splitNote, splitNoteWithHash, toBech32Lnurl, toLud17w, verifyNoteSignature, withNewK1, withoutK1 };