@arkade-os/lnurl-client 0.0.0-stage → 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.
@@ -0,0 +1,796 @@
1
+ import { d as PollVerifyOptions, F as FetchImpl, e as VerifyStatus, P as PayRequest, R as RequestInvoiceOptions, I as InvoiceResult, V as VerifyExpectation, Q as QuotedAmount, f as DestinationResult, g as RegisterAddressRequest, h as RegisteredAddress, D as DomainCapabilities, A as AddressListEntry, i as RegisterArkadeIdentityRequest, H as HdReceiveAllocationPage, a as PaymentPage } from './addresses-CPAzIbJ_.js';
2
+ export { j as AmountObject, B as Bolt11Activity, k as Bolt11Result, l as Bolt11VerifyStatus, m as DestinationActivity, n as DestinationVerifyStatus, c as HdReceiveAllocation, b as HdReceiveConfig, o as PaymentActivity, p as PaymentOption, q as PaymentQuote, r as Unit, U as UnitAmount, s as domainCapabilities, t as listAddresses, u as listHdAllocations, v as listPayments, w as registerAddress, x as registerArkadeIdentity, y as revokeAddress, z as upgradeAddress } from './addresses-CPAzIbJ_.js';
3
+
4
+ /**
5
+ * The single error type for expected LNURL failures.
6
+ *
7
+ * It unifies the server's two wire shapes: the LUD-06 protocol shape
8
+ * (`HTTP 200` carrying `{ status: "ERROR", reason }`) and the management
9
+ * shape (a real status code carrying `{ error, code? }`). Either way the
10
+ * caller keeps a `reason`, plus the `httpStatus` and `code` when the wire
11
+ * carried them.
12
+ *
13
+ * `retryable` is true if and only if `httpStatus === 429`: rate limiting is
14
+ * the one failure worth retrying, everything else is terminal.
15
+ */
16
+ declare class LnurlError extends Error {
17
+ /** Human-readable failure reason, taken from the wire `reason`/`error` field. */
18
+ readonly reason: string;
19
+ /** HTTP status when the failure came with one; absent for LUD-06 bodies answered at HTTP 200. */
20
+ readonly httpStatus?: number;
21
+ /** Machine-readable server code, when the management surface sent one. */
22
+ readonly code?: string;
23
+ /** True if and only if `httpStatus === 429`; every other failure is terminal. */
24
+ readonly retryable: boolean;
25
+ /**
26
+ * Creates an `LnurlError`.
27
+ *
28
+ * @param reason - Human-readable failure reason, also used as the message.
29
+ * @param opts - Optional `httpStatus` and `code` captured from the response.
30
+ */
31
+ constructor(reason: string, opts?: {
32
+ httpStatus?: number;
33
+ code?: string;
34
+ });
35
+ }
36
+ /**
37
+ * Thrown when the request itself never completed: the injected `fetch`
38
+ * rejected, or the body could not be read as JSON. The original failure is
39
+ * preserved as `cause`. Protocol and rate-limit failures never surface here;
40
+ * they become `LnurlError`.
41
+ */
42
+ declare class LnurlTransportError extends Error {
43
+ /**
44
+ * Creates an `LnurlTransportError`.
45
+ *
46
+ * @param message - Short description of which transport step failed.
47
+ * @param opts - Optional `cause` carrying the original thrown value.
48
+ */
49
+ constructor(message: string, opts?: {
50
+ cause?: unknown;
51
+ });
52
+ }
53
+ /**
54
+ * Thrown when `pollVerify` outlasts its deadline without seeing a settled
55
+ * invoice. `lastSnapshot` carries the most recent verify state so the caller
56
+ * can resume, display, or log it instead of starting blind.
57
+ */
58
+ declare class LnurlTimeoutError extends Error {
59
+ /** The most recent verify snapshot seen before the deadline, when one was seen. */
60
+ readonly lastSnapshot?: unknown;
61
+ /**
62
+ * Creates an `LnurlTimeoutError`.
63
+ *
64
+ * @param message - Short description including the timeout that elapsed.
65
+ * @param opts - Optional `lastSnapshot` with the latest verify state.
66
+ */
67
+ constructor(message: string, opts?: {
68
+ lastSnapshot?: unknown;
69
+ });
70
+ }
71
+
72
+ /**
73
+ * Fetches the payRequest for a lightning address or bech32 LNURL.
74
+ *
75
+ * The returned payRequest carries its `source` (URL plus `address`/`session`
76
+ * surface) so `requestInvoice` can key its rail guards off it. Anything whose
77
+ * `tag` is not `payRequest` is rejected: this client only pays.
78
+ *
79
+ * @param input - A lightning address or bech32 LNURL.
80
+ * @param fetchImpl - The injected `fetch` implementation to call.
81
+ * @returns The payRequest with its fetch source attached.
82
+ */
83
+ declare function resolve(input: string, fetchImpl: FetchImpl): Promise<PayRequest>;
84
+ /**
85
+ * Asks the payRequest callback for an invoice or payment destination.
86
+ *
87
+ * `amountSat` is sats and is converted to millisats on the wire; it is
88
+ * range-checked locally against the payRequest bounds before any network
89
+ * call. With `unit` and `amount` instead, the receiver quotes the amount to
90
+ * millisats and the invoice must match that quote. `paymentOption` is rejected
91
+ * on a session payRequest that does not advertise options: a live session's
92
+ * callback would silently ignore it.
93
+ * `verify` on the result is optional: the server omits it when the BOLT11
94
+ * payment hash will not decode.
95
+ *
96
+ * @param payRequest - The payRequest from `resolve`, with its source attached.
97
+ * @param opts - `amountSat` plus optional comment, rail and unit selection.
98
+ * @param fetchImpl - The injected `fetch` implementation to call.
99
+ * @returns A BOLT11 invoice or a destination to pay on the selected rail.
100
+ */
101
+ declare function requestInvoice(payRequest: PayRequest, opts: RequestInvoiceOptions, fetchImpl: FetchImpl): Promise<InvoiceResult>;
102
+ /**
103
+ * Whether a verify answer is about `expected`: a BOLT11 status for the same payment
104
+ * hash, or a destination status on the same rail and, when both name one, the same
105
+ * destination. The preimage check alone only proves the answer's own invoice.
106
+ */
107
+ declare function verifyStatusIsFor(status: VerifyStatus, expected: VerifyExpectation): boolean;
108
+ /**
109
+ * Polls a verify URL until the payment settles, the deadline passes, or the
110
+ * caller aborts.
111
+ *
112
+ * `verify` is optional on a callback response (the server omits it when the
113
+ * BOLT11 payment hash will not decode), so there is no polling without a URL
114
+ * and an empty one is rejected. A `{ status: "ERROR" }` body at HTTP 200 is
115
+ * terminal, not a retry: unknown payment hashes report `Not found` that way.
116
+ * Every snapshot, settled or not, goes to `onUpdate`; an overrun throws
117
+ * `LnurlTimeoutError` with the last snapshot attached.
118
+ *
119
+ * @param verifyUrl - The verify URL from the invoice result.
120
+ * @param opts - Optional interval, timeout, per-poll callback and abort signal.
121
+ * @param fetchImpl - The injected `fetch` implementation to call.
122
+ * @returns The settled verify status.
123
+ */
124
+ declare function pollVerify(verifyUrl: string, opts: PollVerifyOptions | undefined, fetchImpl: FetchImpl): Promise<VerifyStatus>;
125
+
126
+ /** One entry of the batch endpoint's `results` map, keyed by the verify URL. */
127
+ type VerifyBatchResultItem = {
128
+ kind: "verify";
129
+ status: VerifyStatus;
130
+ } | {
131
+ kind: "error";
132
+ reason: string;
133
+ };
134
+ interface VerifyBatchResult {
135
+ /** One entry per presented verify URL, keyed by the exact string the caller presented. */
136
+ results: Record<string, VerifyBatchResultItem>;
137
+ }
138
+ interface BatchVerifyOptions {
139
+ /** Abort the whole call. */
140
+ signal?: AbortSignal;
141
+ }
142
+ /**
143
+ * One-shot LUD-XX verifyBatch snapshot: N invoice statuses on one GET.
144
+ *
145
+ * `verifyBatchUrl` is the endpoint advertised alongside the verify URLs in the
146
+ * callback response; callers group pending invoices by that URL and issue one
147
+ * request per group. The `verify` URLs themselves are opaque and passed whole.
148
+ * A per-item error (`{status:"ERROR", reason}` inside `results`) is data, not a
149
+ * transport failure, so it is represented rather than thrown; a malformed
150
+ * request carries the top-level error shape and still throws.
151
+ */
152
+ declare function batchVerify(verifyBatchUrl: string, verifyUrls: readonly string[], opts?: BatchVerifyOptions, fetchImpl?: FetchImpl): Promise<VerifyBatchResult>;
153
+ interface OpenVerifyBatchStreamOptions {
154
+ /** The endpoint from the callback response's `verifyBatch` field. */
155
+ verifyBatchUrl: string;
156
+ /** Verify URLs pending at open; the endpoint covers exactly this set at first. */
157
+ verifyUrls: readonly string[];
158
+ /** Abort from the caller's side, triggering a clean close. */
159
+ signal?: AbortSignal;
160
+ }
161
+ interface VerifyBatchStreamHandlers {
162
+ /** Every verify-answer frame: the initial snapshot burst and later settlements. */
163
+ onUpdate?: (url: string, status: VerifyStatus) => void;
164
+ /** One call per removed URL; nothing further arrives for it. */
165
+ onRemoved?: (url: string) => void;
166
+ /** A stream failure other than a close the user initiated. */
167
+ onError?: (err: Error) => void;
168
+ /** Stream ended, cleanly or in failure. */
169
+ onClose?: () => void;
170
+ }
171
+ interface VerifyBatchStream {
172
+ /** The server's session for this stream; undefined until the session frame lands. */
173
+ readonly sessionId: string | undefined;
174
+ /**
175
+ * Adds and removes tracked verify URLs while the connection stays open. Each
176
+ * added URL is answered with one snapshot frame; each removed one with one
177
+ * `removed` frame. Throws when the stream has already ended.
178
+ */
179
+ update(verifyUrlsToAdd: readonly string[], verifyUrlsToRemove?: readonly string[]): void;
180
+ /** Ends the stream locally; safe to call more than once. */
181
+ close(): void;
182
+ }
183
+ /**
184
+ * Opens an LUD-XX verifyBatch stream on one connection: the endpoint reads the
185
+ * presented set, emits the snapshot burst, then pushes settlements live.
186
+ *
187
+ * A service without streaming answers the same request with the one-shot JSON
188
+ * snapshot (per LUD-01 the header is ignored, only 406 reaches a rejection):
189
+ * that body is still the current settlement state, so it is delivered through
190
+ * `onUpdate` before `onClose` and the caller falls back to polling.
191
+ */
192
+ declare function openVerifyBatchStream(opts: OpenVerifyBatchStreamOptions, handlers: VerifyBatchStreamHandlers, fetchImpl?: FetchImpl): VerifyBatchStream;
193
+
194
+ /**
195
+ * Options for opening a receiver session. Without a `token` the session is
196
+ * ephemeral: its id is random and dies with the socket, so there is nothing
197
+ * to reconnect to. Pass a token-derived `token` for a resumable session.
198
+ */
199
+ interface OpenSessionOptions {
200
+ /** Reusable token a session id is derived from; enables reconnect. */
201
+ token?: string;
202
+ /** Abort signal tearing down the stream and any pending reconnect wait. */
203
+ signal?: AbortSignal;
204
+ /** `false` disables reconnect; otherwise `{ maxAttempts?, baseDelayMs? }` with exponential backoff. */
205
+ reconnect?: false | {
206
+ maxAttempts?: number;
207
+ baseDelayMs?: number;
208
+ };
209
+ }
210
+ /**
211
+ * The two answers to a payer's invoice request, each `POST`ing to
212
+ * `/lnurl/session/:id/invoice` with the session Bearer token: either the
213
+ * BOLT11 invoice the payer should pay, or a rejection reason.
214
+ */
215
+ interface InvoiceResponder {
216
+ /**
217
+ * Answers the invoice request with a BOLT11 invoice.
218
+ *
219
+ * @param pr - The BOLT11 invoice the payer should pay.
220
+ * @returns A promise settling when the server accepts the invoice.
221
+ */
222
+ answerInvoice(pr: string): Promise<void>;
223
+ /**
224
+ * Rejects the invoice request with a payer-visible reason.
225
+ *
226
+ * @param reason - Why the request is refused.
227
+ * @returns A promise settling when the server accepts the rejection.
228
+ */
229
+ rejectInvoice(reason: string): Promise<void>;
230
+ }
231
+ /**
232
+ * Callbacks driving a receiver session. Only `onInvoiceRequest` is required:
233
+ * without it there is no way to mint the invoice each payer waits for.
234
+ */
235
+ interface SessionHandlers {
236
+ /**
237
+ * Called per payer invoice request; answer via `respond`.
238
+ *
239
+ * @param req - The requested `amountMsat` plus the payer `comment`, when one was sent.
240
+ * @param respond - Answers or rejects this specific request.
241
+ * @returns Nothing, or a promise whose rejection routes to `onError`.
242
+ */
243
+ onInvoiceRequest(req: {
244
+ amountMsat: number;
245
+ comment?: string;
246
+ }, respond: InvoiceResponder): void | Promise<void>;
247
+ /** Called with the parsed body of each `invoice_settled` frame. */
248
+ onSettled?(data: Record<string, unknown>): void;
249
+ /** Called with transport, parse and request failures; never throws back. */
250
+ onError?(err: Error): void;
251
+ /** Called with the 1-based count before each reconnect attempt. */
252
+ onReconnect?(attempt: number): void;
253
+ }
254
+ /**
255
+ * A live receiver session: the LNURL to display plus settlement reporting.
256
+ * Invoice creation stays caller-side: the client never holds keys or funds,
257
+ * it only ferries the payer's request to the caller's invoice provider.
258
+ */
259
+ interface LnurlSession {
260
+ /** Server-assigned session id, also the ownership key for LUD-16 addresses. */
261
+ readonly sessionId: string;
262
+ /** The LNURL to encode (QR, bech32) for payers. */
263
+ readonly lnurl: string;
264
+ /** Token owning this session; sent as the Bearer credential. */
265
+ readonly token: string;
266
+ /** Whether `close()` has been called. */
267
+ readonly closed: boolean;
268
+ /**
269
+ * Reports local settlement of an invoice for this session.
270
+ *
271
+ * Only valid while the session is connected: the server authenticates it
272
+ * against its live in-memory session map, so a dropped session must
273
+ * reconnect first or this is a 401.
274
+ *
275
+ * @param preimage - The payment preimage proving settlement.
276
+ * @returns A promise settling when the server records it.
277
+ */
278
+ reportSettled(preimage: string): Promise<void>;
279
+ /**
280
+ * Closes the session: stops reconnects and cancels the stream reader so an
281
+ * idle stream does not hang.
282
+ */
283
+ close(): void;
284
+ }
285
+ /**
286
+ * Opens a receiver session as POST-SSE over `fetch`, not `EventSource`.
287
+ *
288
+ * `EventSource` is GET-only and carries neither a JSON body nor an auth
289
+ * header, while the endpoint requires a `POST` with an optional `{ token }`
290
+ * body. The promise resolves on the first `session_created` frame carrying
291
+ * `{ sessionId, lnurl, token }`, not on the HTTP 200. Reconnect only happens
292
+ * when a `token` was supplied, because an ephemeral session id is random and
293
+ * dies with the socket; a 409 (id held by a different token) never retries.
294
+ * A null response body fails loudly instead of hanging.
295
+ *
296
+ * @param baseUrl - Server root, e.g. `https://lnurl.example.com`.
297
+ * @param opts - Optional token, abort signal and reconnect policy.
298
+ * @param handlers - Invoice, settlement, error and reconnect callbacks.
299
+ * @param fetchImpl - The injected `fetch` implementation to call.
300
+ * @returns The opened session once `session_created` arrives.
301
+ */
302
+ declare function openSession(baseUrl: string, opts: OpenSessionOptions, handlers: SessionHandlers, fetchImpl: FetchImpl): Promise<LnurlSession>;
303
+
304
+ /**
305
+ * Derives the session token from a raw private key.
306
+ *
307
+ * Produces exactly the same token as {@link deriveSessionTokenWithSigner},
308
+ * because it performs the same deterministic ECDSA signature itself — so a
309
+ * wallet can move between the two without losing ownership of its addresses.
310
+ *
311
+ * Prefer the signer form where an `Identity` is available: this one requires
312
+ * the caller to hand over key material, which a library should not need.
313
+ *
314
+ * @param privateKeyHex - Private key as a hex string.
315
+ * @param domain - The LUD-16 domain this token is for.
316
+ * @returns The session token as a hex string, valid only at that domain.
317
+ */
318
+ declare function deriveSessionToken(privateKeyHex: string, domain: string): string;
319
+ /**
320
+ * Derives the session token from a signer, so the private key never leaves the
321
+ * wallet. `@arkade-os/ts-sdk`'s `Identity` satisfies this structurally:
322
+ *
323
+ * ```ts
324
+ * const token = await deriveSessionTokenWithSigner(
325
+ * (msg, type) => identity.signMessage(msg, type),
326
+ * 'example.com',
327
+ * )
328
+ * ```
329
+ *
330
+ * **ECDSA specifically, and the type is passed for you.** BIP-340 schnorr is
331
+ * randomised unless an explicit aux is supplied, which `Identity.signMessage`
332
+ * does not expose — and schnorr is its *default* signature type. A schnorr
333
+ * signature is also 64 bytes, exactly like a compact ECDSA one, so nothing
334
+ * about the returned bytes reveals the mistake. A non-deterministic signer
335
+ * would mint a fresh token on every call and silently orphan the address, so
336
+ * this signs twice and compares rather than trusting the caller to have wired
337
+ * it correctly.
338
+ *
339
+ * @param signMessage - Signs a digest; called with `"ecdsa"` by this function.
340
+ * @param domain - The LUD-16 domain this token is for.
341
+ * @returns The session token as a hex string, identical to the key-derived one.
342
+ */
343
+ declare function deriveSessionTokenWithSigner(signMessage: (message: Uint8Array, signatureType: "ecdsa") => Promise<Uint8Array>, domain: string): Promise<string>;
344
+ /**
345
+ * Derives the session id from a token: the first 32 hex chars of
346
+ * `sha256(hexDecode(token))`, hashing the token's bytes, not its hex string.
347
+ *
348
+ * Must agree with the server's derivation (`src/session-token.ts`): the id is
349
+ * both the live session key and the ownership key for registered LUD-16
350
+ * lightning addresses, so a mismatch would orphan addresses and sessions.
351
+ *
352
+ * @param tokenHex - Session token as a hex string.
353
+ * @returns The 32-hex-char session id.
354
+ */
355
+ declare function deriveSessionId(tokenHex: string): string;
356
+
357
+ type LnurlSurface = "address" | "session";
358
+ /**
359
+ * Reports whether the input looks like a lightning address (`user@domain`).
360
+ *
361
+ * This is a shape check only; it never touches the network, so a well-formed
362
+ * but unregistered address still returns true here and fails at `resolve`.
363
+ *
364
+ * @param input - The raw user input to test.
365
+ * @returns True when the input has the `user@domain` shape.
366
+ */
367
+ declare function isLnAddress(input: string): boolean;
368
+ /**
369
+ * Reports whether the input is a bech32 LNURL that decodes to an http(s) URL.
370
+ *
371
+ * Decoded with an explicit 1023 limit because real LNURLs exceed bech32's
372
+ * 90-char default; mixed case is rejected per BIP-173, while an all-uppercase
373
+ * LNURL (what QR codes carry) still decodes fine.
374
+ *
375
+ * @param input - The raw user input to test.
376
+ * @returns True when the input is a well-formed LNURL.
377
+ */
378
+ declare function isLnUrl(input: string): boolean;
379
+ /**
380
+ * Reports whether the input is payable input of either shape: a lightning
381
+ * address or a bech32 LNURL.
382
+ *
383
+ * @param input - The raw user input to test.
384
+ * @returns True for a lightning address or a well-formed LNURL.
385
+ */
386
+ declare function isValidLnUrl(input: string): boolean;
387
+ /**
388
+ * Maps user input to the payRequest URL plus the surface it belongs to.
389
+ *
390
+ * A lightning address resolves against `https://domain/.well-known/lnurlp/user`
391
+ * with a lowercased username; a bech32 LNURL carries its own URL. The surface
392
+ * is `address` for `/.well-known/lnurlp/` URLs and `session` otherwise, which
393
+ * is what the payer rail guards are keyed off.
394
+ *
395
+ * @param input - A lightning address or bech32 LNURL.
396
+ * @returns The payRequest `url` and its `surface`.
397
+ */
398
+ declare function toPayRequestUrl(input: string): {
399
+ url: string;
400
+ surface: LnurlSurface;
401
+ };
402
+
403
+ /**
404
+ * One synced payment row, keyed by server plus server-local identifier.
405
+ * Identifiers are unique per server, not globally, so the key scopes them.
406
+ */
407
+ interface StoredPayment {
408
+ /** `${baseUrl}|${identifier}` — identifiers are unique per server, not globally. */
409
+ key: string;
410
+ /** Server root the payment was synced from. */
411
+ baseUrl: string;
412
+ /** Domain serving the address, taken from the page source. */
413
+ domain: string;
414
+ /** Full `user@domain` address, taken from the page source; null when nameless. */
415
+ lightningAddress: string | null;
416
+ /** The username, or the session id while nameless, taken from the page source. */
417
+ handle: string;
418
+ /** paymentHash for bolt11, verifyId for destination. */
419
+ identifier: string;
420
+ /** Which rail the payment arrived on. */
421
+ kind: "bolt11" | "destination";
422
+ /** Whether the payment has settled. */
423
+ settled: boolean;
424
+ /** Agreed amount in millisats, when recorded. */
425
+ amountMsat: number | null;
426
+ /** Creation timestamp in milliseconds. */
427
+ createdAt: number;
428
+ /** Settlement timestamp in milliseconds, null while pending. */
429
+ settledAt: number | null;
430
+ /** RFQ id when a bolt11 payment was an offline swap, null otherwise. */
431
+ swapId: string | null;
432
+ /** Arkade txid once observed on a destination payment, null otherwise. */
433
+ paymentReference: string | null;
434
+ /** Arkade txid that credited the owner's own address, on any rail — the one
435
+ * their wallet can match against its own transaction history. */
436
+ payoutReference: string | null;
437
+ /** Preimage once a bolt11 payment settled; null while pending and on the
438
+ * destination rail, which has no invoice to prove. */
439
+ preimage: string | null;
440
+ /** The rail a destination payment settled on, e.g. `arkade`. Null on the
441
+ * bolt11 rail, where `kind` already says which rail it was. */
442
+ paymentOption: string | null;
443
+ /** Per-payment covenant script when the server derived one, else null. The
444
+ * attribution key for concurrent covenant payments, and what a consumer
445
+ * verifying the payment on-chain has to match against. */
446
+ covenantScript: string | null;
447
+ /** What the payer asked to pay in a LUD-XX unit; absent when they paid in sats. */
448
+ quote?: QuotedAmount;
449
+ }
450
+ /**
451
+ * The persistence the sync loop needs. Applications can wrap their own backends
452
+ * with createLnurlPaymentRepository, or use browserPaymentStore for a small demo.
453
+ * IndexedDB does not exist in Node or React Native. `upsert` is a
454
+ * primitive rather than read-then-write so a database-backed store can do it
455
+ * in one statement instead of loading every record on every sync.
456
+ */
457
+ interface PaymentSyncStore {
458
+ /** Insert or replace by `record.key`. MUST overwrite, never append. */
459
+ upsert(records: StoredPayment[]): Promise<void>;
460
+ /** Last good inclusive cursor for one address, undefined when never synced. */
461
+ readWatermark(baseUrl: string, lightningAddress: string): Promise<number | undefined>;
462
+ /** Persists the inclusive cursor after a page is upserted; it moves backwards. */
463
+ writeWatermark(baseUrl: string, lightningAddress: string, since: number): Promise<void>;
464
+ }
465
+ /**
466
+ * One address to sync: where it lives and whose payments to list.
467
+ */
468
+ interface PaymentSyncTarget {
469
+ /** Server root serving the address. */
470
+ baseUrl: string;
471
+ /** Token owning the address; sent as the Bearer credential. */
472
+ token: string;
473
+ /** The username, or the session id while nameless; the sync watermark is keyed off it. */
474
+ handle: string;
475
+ /** Domain part of the address. */
476
+ domain: string;
477
+ }
478
+ /**
479
+ * Syncs payment activity for every target into the consumer-supplied store.
480
+ *
481
+ * Each target paginates while a page comes back full
482
+ * (`payments.length === limit`); a short page ends that target. The watermark
483
+ * is written only after a page is successfully upserted, so a target that fails
484
+ * wholly keeps its old cursor. It holds the oldest row still waiting to settle,
485
+ * not the newest `createdAt`, because `settled` mutates after a row is created;
486
+ * it reaches one page back at most, so a row nothing will ever settle costs one
487
+ * extra page per sync rather than re-paging the history. Pagination itself still
488
+ * walks `nextSince` forward. `nextSince` is inclusive, so the boundary row is
489
+ * deliberately re-fetched and the store's key-overwrite absorbs it — the loop
490
+ * itself never dedupes. Only `LnurlError.retryable` (HTTP 429) is retried;
491
+ * anything else is terminal. A full page that leaves the cursor where it was
492
+ * fails that target instead of re-fetching itself forever. A failing target is
493
+ * collected into `failures` and the loop continues with the next one; this
494
+ * function never throws.
495
+ *
496
+ * `client` is a factory rather than a single client because a client is pinned
497
+ * to one `baseUrl`, while targets may live on different servers. Sharing one
498
+ * would send every target's bearer token to whichever server that client was
499
+ * built for:
500
+ *
501
+ * ```ts
502
+ * await syncPayments(targets, {
503
+ * client: (baseUrl) => createLnurlClient({ baseUrl }),
504
+ * store,
505
+ * })
506
+ * ```
507
+ *
508
+ * @param targets - Addresses to sync, each bound to its serving baseUrl.
509
+ * @param opts - Builds the client for one server, the store to write to, and an optional page limit.
510
+ * @returns `synced` counts every row upserted, including rows a target stored
511
+ * on earlier pages before failing on a later one — so it always
512
+ * matches what is in the store. Such a target also appears in
513
+ * `failures`, meaning a non-zero `synced` and a failure can coexist.
514
+ */
515
+ declare function syncPayments(targets: PaymentSyncTarget[], opts: {
516
+ client: (baseUrl: string) => Pick<LnurlClient, "listPayments">;
517
+ store: PaymentSyncStore;
518
+ limit?: number;
519
+ }): Promise<{
520
+ synced: number;
521
+ failures: {
522
+ baseUrl: string;
523
+ error: unknown;
524
+ }[];
525
+ }>;
526
+
527
+ /** A CAIP-19 asset id, split. Its CAIP-2 prefix is the chain. */
528
+ interface TokenAsset {
529
+ id: string;
530
+ chainId: string;
531
+ namespace: string;
532
+ chainReference: string;
533
+ assetNamespace: string;
534
+ reference: string;
535
+ }
536
+ /** A `units[]` entry a token option is quoted in. */
537
+ interface TokenUnit {
538
+ code: string;
539
+ decimals: number;
540
+ name?: string;
541
+ }
542
+ /** A payment option for a token on a non-Bitcoin chain, as `tokenOptions` accepts one. */
543
+ interface TokenOption {
544
+ id: string;
545
+ asset: TokenAsset;
546
+ unit: TokenUnit;
547
+ /** False only when the payRequest says the option is down right now. */
548
+ available: boolean;
549
+ verifiable?: boolean;
550
+ minSendable?: number;
551
+ maxSendable?: number;
552
+ /** The third party the payer hands the token to, e.g. "FixedFloat". */
553
+ provider?: string;
554
+ }
555
+ /** True when `address` is a recipient on chains of the given CAIP-2 namespace. */
556
+ declare function isTokenAddress(namespace: string, address: string): boolean;
557
+ /**
558
+ * The payRequest's token options, parsed exactly as the BTCPay LNURLVerify plugin parses
559
+ * them (TokenOption.cs), so a wallet and a checkout agree on what is on offer.
560
+ *
561
+ * An option is dropped when its `asset` is not a CAIP-19 id of a namespace a payment can
562
+ * be built for, when its `type` is not that namespace, when its `unit` names no `units[]`
563
+ * entry, and, with every option sharing it, when its `id` is not unique.
564
+ */
565
+ declare function tokenOptions(payRequest: Pick<PayRequest, "paymentOptions" | "units">): TokenOption[];
566
+
567
+ /** A `PaymentSyncStore` over `localStorage`. Synchronous, a few megabytes, and it
568
+ * rewrites every record per upsert — back a real wallet with IndexedDB instead. */
569
+ declare function browserPaymentStore(prefix?: string): PaymentSyncStore;
570
+ /** Everything synced for one address, newest first. A nameless receiver has no
571
+ * lightning address to match on, so name it by `{ domain, handle }` instead. */
572
+ declare function storedPayments(address: string | {
573
+ domain: string;
574
+ handle: string;
575
+ }, prefix?: string): StoredPayment[];
576
+ declare function forgetStoredPayments(prefix?: string): void;
577
+
578
+ interface ReceiverConfirmation {
579
+ /** Defaults to verifyUrl; distinct sends can share a verify endpoint. */
580
+ key?: string;
581
+ verifyUrl: string;
582
+ verifyBatch?: string;
583
+ expect?: VerifyExpectation;
584
+ onSettled: (status: VerifyStatus) => void;
585
+ onError: (err: Error) => void;
586
+ /** Original send time in milliseconds, so resuming preserves its deadline. */
587
+ startedAt?: number;
588
+ timeoutMs?: number;
589
+ }
590
+ interface PendingConfirmations {
591
+ add(confirmation: ReceiverConfirmation): void;
592
+ forget(): void;
593
+ }
594
+ declare const CONFIRMATION_TIMEOUT_MS = 180000;
595
+ declare function createPendingConfirmations(client?: Pick<LnurlClient, "pollVerify" | "batchVerify">): PendingConfirmations;
596
+
597
+ interface LnurlPaymentStore {
598
+ read(): Promise<StoredPayment[]>;
599
+ /** Insert or replace by key, leaving records absent from the batch untouched. */
600
+ write(records: StoredPayment[]): Promise<void>;
601
+ clear(): Promise<void>;
602
+ }
603
+ interface LnurlPaymentWatermarkStore {
604
+ read(baseUrl: string, lightningAddress: string): Promise<number | undefined>;
605
+ write(baseUrl: string, lightningAddress: string, since: number): Promise<void>;
606
+ clear(): Promise<void>;
607
+ }
608
+ interface LnurlPaymentRepository extends PaymentSyncStore {
609
+ all(): Promise<StoredPayment[]>;
610
+ /** Explicit purge; use clearLnurlPaymentsForReset to close and drain writers first. */
611
+ clear(): Promise<void>;
612
+ closeWrites(): void;
613
+ reopenWrites(): void;
614
+ writesClosed(): boolean;
615
+ writesSettled(): Promise<void>;
616
+ }
617
+ declare function createLnurlPaymentRepository(payments: LnurlPaymentStore, watermarks: LnurlPaymentWatermarkStore): LnurlPaymentRepository;
618
+ declare function clearLnurlPaymentsForReset(repository: LnurlPaymentRepository): Promise<void>;
619
+
620
+ /** `amount` in a unit's smallest denomination, as a decimal: ("500", 2) reads "5.00". */
621
+ declare function formatUnitAmount(amount: string | number, decimals: number): string;
622
+ /** A typed decimal in a unit's smallest denomination: ("5.5", 2) is 550. Undefined for
623
+ * anything that is not a plain positive decimal, or has more places than the unit. */
624
+ declare function parseUnitAmount(text: string, decimals: number): number | undefined;
625
+
626
+ interface TokenQuote {
627
+ optionId: string;
628
+ destination: string;
629
+ baseUnits: string;
630
+ amount: string;
631
+ value: string;
632
+ /** Quote deadline in Unix milliseconds. */
633
+ expiresAt: number;
634
+ paymentDestinationTag?: string;
635
+ }
636
+ /** Validates the token answer and keeps only an instruction that pays its exact quote. */
637
+ declare function payableTokenUri(token: TokenOption, answer: DestinationResult): string;
638
+ /** Normalizes an expiring token quote without applying a wallet's minimum lifetime or price policy. */
639
+ declare function normalizeTokenQuote(token: TokenOption, answer: DestinationResult, nowMs?: number): TokenQuote;
640
+
641
+ /**
642
+ * Options for `createLnurlClient`. Everything is optional: payer-only
643
+ * callers omit `baseUrl` entirely and inject nothing when the global `fetch`
644
+ * will do.
645
+ */
646
+ interface LnurlClientOptions {
647
+ /**
648
+ * Server root, needed ONLY for receiver/management calls (`openSession`,
649
+ * `registerAddress`, `listAddresses`, `revokeAddress`,
650
+ * `registerArkadeIdentity`). The payer surface is host-agnostic — an
651
+ * address resolves against its own domain and a bech32 LNURL against its
652
+ * decoded URL — so `createLnurlClient()` with no argument is valid for
653
+ * payer-only use. Those methods throw `LnurlError` when it is missing.
654
+ */
655
+ baseUrl?: string;
656
+ /**
657
+ * `fetch` implementation to call. Defaults to resolving `globalThis.fetch`
658
+ * per call, not once at construction, so a client built at module scope
659
+ * still sees a fetch installed later (mock, polyfill, service worker).
660
+ */
661
+ fetchImpl?: FetchImpl;
662
+ }
663
+ /**
664
+ * The single facade both roles use: host-agnostic payer calls plus
665
+ * `baseUrl`-bound receiver/management calls sharing one `fetch`.
666
+ */
667
+ interface LnurlClient {
668
+ /**
669
+ * Fetches the payRequest for a lightning address or bech32 LNURL.
670
+ *
671
+ * @param input - A lightning address or bech32 LNURL.
672
+ * @returns The payRequest with its fetch source attached.
673
+ */
674
+ resolve(input: string): Promise<PayRequest>;
675
+ /**
676
+ * Asks the payRequest callback for an invoice or payment destination.
677
+ *
678
+ * @param payRequest - The payRequest from `resolve`.
679
+ * @param opts - `amountSat` plus optional comment, rail and unit selection.
680
+ * @returns A BOLT11 invoice or a destination to pay on the selected rail.
681
+ */
682
+ requestInvoice(payRequest: PayRequest, opts: RequestInvoiceOptions): Promise<InvoiceResult>;
683
+ /**
684
+ * Polls a verify URL until the payment settles, the deadline passes, or the
685
+ * caller aborts.
686
+ *
687
+ * @param verifyUrl - The verify URL from the invoice result.
688
+ * @param opts - Optional interval, timeout, per-poll callback and abort signal.
689
+ * @returns The settled verify status.
690
+ */
691
+ pollVerify(verifyUrl: string, opts?: PollVerifyOptions): Promise<VerifyStatus>;
692
+ /**
693
+ * One-shot LUD-XX verifyBatch: N invoice statuses on one GET.
694
+ *
695
+ * @param verifyBatchUrl - The endpoint advertised next to the verify URLs.
696
+ * @param verifyUrls - The verify URLs currently pending, grouped by endpoint.
697
+ * @param opts - Optional abort signal and `fetch` override.
698
+ */
699
+ batchVerify(verifyBatchUrl: string, verifyUrls: readonly string[], opts?: BatchVerifyOptions & {
700
+ fetchImpl?: FetchImpl;
701
+ }): Promise<VerifyBatchResult>;
702
+ /**
703
+ * Opens a receiver session as POST-SSE; resolves on `session_created`.
704
+ * Requires `baseUrl`.
705
+ *
706
+ * @param opts - Optional token, abort signal and reconnect policy.
707
+ * @param handlers - Invoice, settlement, error and reconnect callbacks.
708
+ * @returns The opened session once `session_created` arrives.
709
+ */
710
+ openSession(opts: OpenSessionOptions, handlers: SessionHandlers): Promise<LnurlSession>;
711
+ /**
712
+ * LUD-XX verifyBatch stream: snapshot burst first, then live settlements, and
713
+ * in-place tracked-set updates without reopening the connection.
714
+ *
715
+ * @param opts - Endpoint plus the initially pending verify URLs.
716
+ * @param handlers - Frame, removal and close callbacks.
717
+ */
718
+ openVerifyBatchStream(opts: OpenVerifyBatchStreamOptions & {
719
+ fetchImpl?: FetchImpl;
720
+ }, handlers: VerifyBatchStreamHandlers): VerifyBatchStream;
721
+ /**
722
+ * Registers a LUD-16 lightning address. Requires `baseUrl`.
723
+ *
724
+ * @param req - Token, optional username/claimCode/domain, and optional API key.
725
+ * @returns The registered address and how to reach it.
726
+ */
727
+ registerAddress(req: RegisterAddressRequest): Promise<RegisteredAddress>;
728
+ upgradeAddress(req: {
729
+ token: string;
730
+ handle: string;
731
+ username?: string;
732
+ claimCode?: string;
733
+ domain?: string;
734
+ }): Promise<RegisteredAddress>;
735
+ domainCapabilities(opts?: {
736
+ domain?: string;
737
+ }): Promise<DomainCapabilities>;
738
+ /**
739
+ * Lists the LUD-16 addresses owned by a token. Requires `baseUrl`.
740
+ *
741
+ * @param token - Token whose addresses to list.
742
+ * @returns The addresses owned by the token.
743
+ */
744
+ listAddresses(token: string): Promise<AddressListEntry[]>;
745
+ /**
746
+ * Revokes one address owned by a token. Requires `baseUrl`.
747
+ *
748
+ * @param token - Token owning the address.
749
+ * @param handle - Handle of the address to revoke.
750
+ * @param opts - Optional domain scoping the revocation.
751
+ * @returns A promise settling when the server revokes the address.
752
+ */
753
+ revokeAddress(token: string, handle: string, opts?: {
754
+ domain?: string;
755
+ }): Promise<void>;
756
+ /**
757
+ * Binds an Arkade identity to a registered address. Requires `baseUrl`.
758
+ *
759
+ * @param req - Token, handle, Arkade address, claim key, optional boarding address and domain.
760
+ * @returns A promise settling when the server records the identity.
761
+ */
762
+ registerArkadeIdentity(req: RegisterArkadeIdentityRequest): Promise<void>;
763
+ listHdAllocations(token: string, handle: string, opts?: {
764
+ domain?: string;
765
+ after?: number;
766
+ limit?: number;
767
+ }): Promise<HdReceiveAllocationPage>;
768
+ /**
769
+ * Lists the payments made to one address owned by a token. Requires `baseUrl`.
770
+ *
771
+ * @param token - Token owning the address.
772
+ * @param handle - Handle of the address whose payments to list.
773
+ * @param opts - Optional domain, inclusive since cursor and page limit.
774
+ * @returns The payment page with rail-discriminated activity entries.
775
+ */
776
+ listPayments(token: string, handle: string, opts?: {
777
+ domain?: string;
778
+ since?: number;
779
+ limit?: number;
780
+ }): Promise<PaymentPage>;
781
+ }
782
+ /**
783
+ * Creates the client both roles share.
784
+ *
785
+ * `baseUrl` is receiver-side state only: the payer surface resolves against
786
+ * the address domain or the decoded LNURL, so payer-only callers pass nothing
787
+ * and only the receiver/management methods demand it. The default `fetch`
788
+ * resolves `globalThis.fetch` per call, not once at construction, so a client
789
+ * built at module scope still sees a fetch installed later.
790
+ *
791
+ * @param opts - Optional `baseUrl` and `fetchImpl`.
792
+ * @returns A client sharing one resolved `fetch` across both roles.
793
+ */
794
+ declare function createLnurlClient(opts?: LnurlClientOptions): LnurlClient;
795
+
796
+ export { AddressListEntry, type BatchVerifyOptions, CONFIRMATION_TIMEOUT_MS, DestinationResult, DomainCapabilities, FetchImpl, HdReceiveAllocationPage, type InvoiceResponder, InvoiceResult, type LnurlClient, type LnurlClientOptions, LnurlError, type LnurlPaymentRepository, type LnurlPaymentStore, type LnurlPaymentWatermarkStore, type LnurlSession, type LnurlSurface, LnurlTimeoutError, LnurlTransportError, type OpenSessionOptions, type OpenVerifyBatchStreamOptions, PayRequest, PaymentPage, type PaymentSyncStore, type PaymentSyncTarget, type PendingConfirmations, PollVerifyOptions, QuotedAmount, type ReceiverConfirmation, RegisterAddressRequest, RegisterArkadeIdentityRequest, RegisteredAddress, RequestInvoiceOptions, type SessionHandlers, type StoredPayment, type TokenAsset, type TokenOption, type TokenQuote, type TokenUnit, type VerifyBatchResult, type VerifyBatchResultItem, type VerifyBatchStream, type VerifyBatchStreamHandlers, VerifyExpectation, VerifyStatus, batchVerify, browserPaymentStore, clearLnurlPaymentsForReset, createLnurlClient, createLnurlPaymentRepository, createPendingConfirmations, deriveSessionId, deriveSessionToken, deriveSessionTokenWithSigner, forgetStoredPayments, formatUnitAmount, isLnAddress, isLnUrl, isTokenAddress, isValidLnUrl, normalizeTokenQuote, openSession, openVerifyBatchStream, parseUnitAmount, payableTokenUri, pollVerify, requestInvoice, resolve, storedPayments, syncPayments, toPayRequestUrl, tokenOptions, verifyStatusIsFor };