@qntx/spark 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,258 @@
1
+ import { n as SparkNetwork, t as LightningAddressStatement } from "./lightning-address-CRULmi9v.mjs";
2
+ import { t as Bolt11Invoice } from "./bolt11-DsNTTS_O.mjs";
3
+ //#region src/lnurl/auth.d.ts
4
+ type LnurlAuthAction = "register" | "login" | "link" | "auth";
5
+ type LnurlAuthRequest = {
6
+ readonly tag: "login";
7
+ /** The full service URL to GET in the second step, normalized. */
8
+ readonly url: string;
9
+ /** The 32-byte challenge, lowercase hex. */
10
+ readonly k1: string;
11
+ readonly action?: LnurlAuthAction | undefined;
12
+ /** The service's lowercase hostname — shown to the user on login. */
13
+ readonly domain: string;
14
+ };
15
+ /**
16
+ * Parses an LNURL-auth URL (the decoded form of a `lnurl1…` string or a bare https/onion URL).
17
+ * Requires `tag=login` and a 64-hex `k1` query parameter; `action` must be one of the LUD-04 enum
18
+ * values when present.
19
+ */
20
+ export declare function parseLnurlAuth(url: string): LnurlAuthRequest;
21
+ /**
22
+ * The signing shape the auth flow needs — a structural subset of `LnurlAuthSigner` (the lnurl layer
23
+ * must not import the signer implementation).
24
+ */
25
+ type LnurlAuthSigning = {
26
+ readonly sign: (request: {
27
+ readonly domain: string;
28
+ readonly k1: Uint8Array;
29
+ }) => Promise<{
30
+ readonly linkingKey: Uint8Array;
31
+ readonly signature: Uint8Array;
32
+ }>;
33
+ };
34
+ /**
35
+ * LUD-04 step 3: signs the `k1` challenge with the domain's linking key and GETs the service URL
36
+ * with `sig` and `key` appended. The k1 challenge and the signature never appear in error
37
+ * messages.
38
+ */
39
+ export declare function submitLnurlAuth(request: LnurlAuthRequest, options: {
40
+ readonly fetch: typeof globalThis.fetch;
41
+ readonly signer: LnurlAuthSigning;
42
+ }): Promise<void>;
43
+ //#endregion
44
+ //#region src/lnurl/codec.d.ts
45
+ /** LUD-01 §HTTPS or Onion: clearnet https, or http only for a `.onion` host. */
46
+ export declare function checkLnurlUrl(url: string): void;
47
+ /**
48
+ * Decodes a bech32 `lnurl1…` string into the normalized URL it wraps (LUD-01). Accepts upper- or
49
+ * lowercase (never mixed) and an optional `lightning:` scheme prefix.
50
+ */
51
+ export declare function decodeLnurl(text: string): string;
52
+ /** Bech32-encodes an https/onion URL into a lowercase `lnurl1…` string (LUD-01). */
53
+ export declare function encodeLnurl(url: string): string;
54
+ /**
55
+ * LUD-16 `<username>@<domain>` → the well-known pay endpoint. `@<domain>` resolves to the default
56
+ * identifier `_`. An `.onion` domain yields http, a clearnet domain https. Usernames are lowercased
57
+ * and must match `a-z0-9-_.` plus `+` for service tags; ports, userinfo and paths are rejected.
58
+ */
59
+ export declare function lightningAddressUrl(address: string): string;
60
+ //#endregion
61
+ //#region src/lnurl/http.d.ts
62
+ type JsonObject = Record<string, unknown>;
63
+ /**
64
+ * One GET against an LNURL endpoint with the injected fetch. HTTP failures and the protocol-level
65
+ * `{"status": "ERROR", "reason"}` envelope map onto the SSP client's error vocabulary; error
66
+ * messages never echo request URLs — withdraw callbacks carry the ephemeral `k1`, which authorizes
67
+ * a payout.
68
+ */
69
+ export declare function lnurlGetJson(url: string, fetch: typeof globalThis.fetch): Promise<JsonObject>;
70
+ /**
71
+ * One JSON request with a body (POST/DELETE) against an LNURL endpoint. Same error vocabulary as
72
+ * {@link lnurlGetJson}; a 2xx response with an empty body returns `undefined`, and request bodies
73
+ * never echo into error messages — the server's response text is sanitized before it is embedded.
74
+ */
75
+ export declare function lnurlSendJson(url: string, fetch: typeof globalThis.fetch, options: {
76
+ readonly method: string;
77
+ readonly body: JsonObject;
78
+ /** Whole-round-trip bound; defaults to {@link LNURL_REQUEST_TIMEOUT_MS}. */
79
+ readonly timeoutMs?: number;
80
+ }): Promise<JsonObject | undefined>;
81
+ //#endregion
82
+ //#region src/lnurl/success-action.d.ts
83
+ type LnurlSuccessAction = {
84
+ readonly tag: "message";
85
+ readonly message: string;
86
+ } | {
87
+ readonly tag: "url";
88
+ readonly description: string;
89
+ readonly url: string;
90
+ } | {
91
+ readonly tag: "aes";
92
+ readonly description: string;
93
+ readonly ciphertext: string;
94
+ readonly iv: string;
95
+ };
96
+ /**
97
+ * Parses a `successAction` object (LUD-09). `callbackHost` is the payRequest callback's hostname:
98
+ * `url` actions must stay on that domain. Unknown tags are rejected — a wallet that cannot show the
99
+ * action must not proceed (LUD-09).
100
+ */
101
+ export declare function parseSuccessAction(value: unknown, callbackHost: string): LnurlSuccessAction;
102
+ /**
103
+ * LUD-10: decrypts an `aes` successAction with the payment preimage — AES-256-CBC, the preimage is
104
+ * the key, `iv` is base64 of 16 bytes, `ciphertext` base64 of at most 4 KiB, PKCS#7 padding, UTF-8
105
+ * plaintext.
106
+ */
107
+ export declare function decryptSuccessAction(action: Extract<LnurlSuccessAction, {
108
+ readonly tag: "aes";
109
+ }>, preimage: Uint8Array): string;
110
+ //#endregion
111
+ //#region src/lnurl/pay.d.ts
112
+ type LnurlPayRequest = {
113
+ readonly tag: "payRequest";
114
+ /** Second-callback URL from the service. */
115
+ readonly callback: string;
116
+ readonly minSendableMsat: number;
117
+ readonly maxSendableMsat: number;
118
+ /** Raw metadata JSON string — hashed verbatim into the invoice's `h` field. */
119
+ readonly metadata: string;
120
+ /** The metadata's single `text/plain` entry (LUD-06, mandatory). */
121
+ readonly description: string;
122
+ /** Max comment characters the service accepts (LUD-12); 0 = unsupported. */
123
+ readonly commentAllowed: number;
124
+ /** NIP-57 zaps: the service accepts a signed `nostr` zap-request event. */
125
+ readonly allowsNostr: boolean;
126
+ /** 32-byte x-only nostr pubkey; present exactly when `allowsNostr`. */
127
+ readonly nostrPubkey?: Uint8Array | undefined;
128
+ };
129
+ export declare function parseLnurlPayRequest(body: JsonObject): LnurlPayRequest;
130
+ type LnurlInvoice = {
131
+ /** The service's BOLT-11 payment request string. */
132
+ readonly invoice: string;
133
+ readonly decoded: Bolt11Invoice;
134
+ readonly successAction?: LnurlSuccessAction | undefined;
135
+ };
136
+ /**
137
+ * LUD-06 second callback: requests the invoice for `amountMsat`, then verifies it against the
138
+ * request — exact amount, invoice network, and a `h` description-hash over the raw metadata (or
139
+ * over the zap-request event alone for a nostr zap, NIP-57).
140
+ */
141
+ export declare function requestInvoice(request: LnurlPayRequest, options: {
142
+ readonly fetch: typeof globalThis.fetch;
143
+ readonly network: SparkNetwork;
144
+ readonly amountMsat: number;
145
+ readonly comment?: string;
146
+ readonly zapRequest?: string;
147
+ }): Promise<LnurlInvoice>;
148
+ //#endregion
149
+ //#region src/lnurl/withdraw.d.ts
150
+ type LnurlWithdrawRequest = {
151
+ readonly tag: "withdrawRequest";
152
+ /** Second-callback URL accepting `k1` and `pr` query params. */
153
+ readonly callback: string;
154
+ /** Ephemeral service challenge echoed back verbatim on the callback. */
155
+ readonly k1: string;
156
+ readonly defaultDescription: string;
157
+ readonly minWithdrawableMsat: number;
158
+ readonly maxWithdrawableMsat: number;
159
+ };
160
+ export declare function parseLnurlWithdrawRequest(body: JsonObject): LnurlWithdrawRequest;
161
+ /**
162
+ * LUD-03 step 5: submits the wallet's BOLT-11 `invoice` for payment. The service pays
163
+ * asynchronously; a `{"status": "OK"}` body means it accepted — errors surface as
164
+ * `SparkRequestError` (`lnurl_error` / `http_*`).
165
+ */
166
+ export declare function submitWithdraw(request: LnurlWithdrawRequest, options: {
167
+ readonly fetch: typeof globalThis.fetch;
168
+ readonly invoice: string;
169
+ }): Promise<void>;
170
+ //#endregion
171
+ //#region src/lnurl/fetch.d.ts
172
+ type LnurlResolved = LnurlPayRequest | LnurlWithdrawRequest | LnurlAuthRequest;
173
+ /**
174
+ * Resolves an `lnurl1…` string, a lightning address (`user@domain`, LUD-16) or a bare https/onion
175
+ * URL into a typed LNURL service request. A `tag=login` query is a LUD-04 auth challenge carried
176
+ * inside the LNURL itself and returns without fetching; otherwise the endpoint is fetched and only
177
+ * the interactive `payRequest` (LUD-06) and `withdrawRequest` (LUD-03) tags are supported.
178
+ */
179
+ export declare function resolveLnurl(input: string, options: {
180
+ readonly fetch: typeof globalThis.fetch;
181
+ }): Promise<LnurlResolved>;
182
+ //#endregion
183
+ //#region src/lnurl/lightning-address.d.ts
184
+ /**
185
+ * The signing shape the registration helpers need — a structural subset of `SparkSigner` (the lnurl
186
+ * layer must not import the signer implementation).
187
+ */
188
+ type LightningAddressSigning = {
189
+ readonly identityPublicKey: () => Promise<Uint8Array>;
190
+ readonly signIdentity: (statement: LightningAddressStatement) => Promise<Uint8Array>;
191
+ };
192
+ type RegisterLightningAddressBody = {
193
+ readonly username: string;
194
+ readonly signature: string;
195
+ readonly timestamp: number;
196
+ readonly description: string;
197
+ };
198
+ type UnregisterLightningAddressBody = {
199
+ readonly username: string;
200
+ readonly signature: string;
201
+ readonly timestamp: number;
202
+ };
203
+ type LightningAddressRegistration = {
204
+ readonly lnurl: string;
205
+ readonly lightningAddress: string;
206
+ };
207
+ /**
208
+ * Builds the register request body the signed statement commits to — lowercased domain and
209
+ * username, fresh timestamp — and signs it. Split from {@link registerLightningAddress} so the same
210
+ * bytes can be handed to a server handler in tests without HTTP.
211
+ */
212
+ export declare function lightningAddressRegisterBody(options: {
213
+ readonly domain: string;
214
+ readonly username: string;
215
+ readonly description: string;
216
+ readonly signer: LightningAddressSigning;
217
+ readonly now?: (() => number) | undefined;
218
+ }): Promise<{
219
+ readonly pubkeyHex: string;
220
+ readonly body: RegisterLightningAddressBody;
221
+ }>;
222
+ /** The unregister counterpart of {@link lightningAddressRegisterBody}. */
223
+ export declare function lightningAddressUnregisterBody(options: {
224
+ readonly domain: string;
225
+ readonly username: string;
226
+ readonly signer: LightningAddressSigning;
227
+ readonly now?: (() => number) | undefined;
228
+ }): Promise<{
229
+ readonly pubkeyHex: string;
230
+ readonly body: UnregisterLightningAddressBody;
231
+ }>;
232
+ /**
233
+ * Claims `username` on the Lightning Address server at `serverUrl`: signs the register statement
234
+ * and POSTs it to `/lnurlpay/{pubkey}`. `serverUrl` must be https or an `.onion` http URL.
235
+ */
236
+ export declare function registerLightningAddress(options: {
237
+ readonly fetch: typeof globalThis.fetch;
238
+ readonly serverUrl: string;
239
+ readonly domain: string;
240
+ readonly username: string;
241
+ readonly description: string;
242
+ readonly signer: LightningAddressSigning;
243
+ readonly now?: (() => number) | undefined;
244
+ }): Promise<LightningAddressRegistration>;
245
+ /**
246
+ * Gives up `username` on the server: signs the unregister statement and DELETEs
247
+ * `/lnurlpay/{pubkey}`.
248
+ */
249
+ export declare function unregisterLightningAddress(options: {
250
+ readonly fetch: typeof globalThis.fetch;
251
+ readonly serverUrl: string;
252
+ readonly domain: string;
253
+ readonly username: string;
254
+ readonly signer: LightningAddressSigning;
255
+ readonly now?: (() => number) | undefined;
256
+ }): Promise<void>;
257
+ //#endregion
258
+ export type { JsonObject, LightningAddressRegistration, LightningAddressSigning, LnurlAuthAction, LnurlAuthRequest, LnurlAuthSigning, LnurlInvoice, LnurlPayRequest, LnurlResolved, LnurlSuccessAction, LnurlWithdrawRequest, RegisterLightningAddressBody, UnregisterLightningAddressBody };
package/dist/lnurl.mjs ADDED
@@ -0,0 +1 @@
1
+ export { f as checkLnurlUrl, p as decodeLnurl, u as decryptSuccessAction, m as encodeLnurl, t as lightningAddressRegisterBody, n as lightningAddressUnregisterBody, h as lightningAddressUrl, y as lnurlGetJson, b as lnurlSendJson, g as parseLnurlAuth, c as parseLnurlPayRequest, o as parseLnurlWithdrawRequest, d as parseSuccessAction, r as registerLightningAddress, l as requestInvoice, a as resolveLnurl, _ as submitLnurlAuth, s as submitWithdraw, i as unregisterLightningAddress } from "./lnurl-kcdXU75g.mjs";
@@ -0,0 +1,158 @@
1
+ import { n as SparkClient } from "./index-B_g3vHnT.mjs";
2
+ //#region src/server/store.d.ts
3
+ type LightningAddressUser = {
4
+ /** Lowercase LUD-16 local-part. */
5
+ readonly name: string;
6
+ /** The owner's compressed secp256k1 identity public key, lowercase hex. */
7
+ readonly pubkeyHex: string;
8
+ /** The registration description — verbatim into the payRequest `text/plain` metadata. */
9
+ readonly description: string;
10
+ };
11
+ type LightningAddressInvoice = {
12
+ /** Lowercase hex payment hash of the BOLT-11 invoice. */
13
+ readonly paymentHash: string;
14
+ /** The SSP lightning-receive request id — the settlement status is polled through it. */
15
+ readonly requestId: string;
16
+ /** The registered name the invoice was issued to. */
17
+ readonly name: string;
18
+ /** The payee's compressed identity public key, lowercase hex. */
19
+ readonly pubkeyHex: string;
20
+ /** The encoded BOLT-11 invoice handed to the payer. */
21
+ readonly invoice: string;
22
+ readonly expiresAtMs: number;
23
+ /** Last `settlePending` status check — the pending queue is served least-recently-checked first. */
24
+ readonly lastCheckedAtMs?: number;
25
+ /** The raw zap-request event JSON (NIP-57), when the payer sent one. */
26
+ readonly zapRequest?: string;
27
+ readonly comment?: string;
28
+ /** Lowercase hex settled preimage — present only once settled. */
29
+ readonly preimageHex?: string;
30
+ };
31
+ /**
32
+ * The persistence the server handlers need. A pubkey holds at most one name: registering a new name
33
+ * releases the old one.
34
+ */
35
+ type LightningAddressStore = {
36
+ readonly userByName: (name: string) => Promise<LightningAddressUser | undefined>;
37
+ readonly userByPublicKey: (pubkeyHex: string) => Promise<LightningAddressUser | undefined>;
38
+ /** Registered name count — one row per name, and each pubkey holds at most one. */
39
+ readonly countUsers: () => Promise<number>;
40
+ /**
41
+ * Registers `user.name` for `user.pubkeyHex`. Returns `false` when another pubkey already holds
42
+ * the name; otherwise removes the pubkey's previous name and stores the record.
43
+ */
44
+ readonly putUser: (user: LightningAddressUser) => Promise<boolean>;
45
+ /** Removes the name only when it is still held by `pubkeyHex`. */
46
+ readonly deleteUser: (pubkeyHex: string, name: string) => Promise<boolean>;
47
+ /**
48
+ * Claims a signed statement for replay protection: `false` when the hash was already claimed,
49
+ * else records it with `expiresAtMs` (after which the record may be dropped) and returns `true`.
50
+ */
51
+ readonly claimStatement: (hashHex: string, expiresAtMs: number) => Promise<boolean>;
52
+ readonly putInvoice: (invoice: LightningAddressInvoice) => Promise<void>;
53
+ readonly invoiceByPaymentHash: (hashHex: string) => Promise<LightningAddressInvoice | undefined>;
54
+ /**
55
+ * Unsettled invoices whose expiry has not passed at `nowMs` — at most `limit`, ordered
56
+ * least-recently-checked first so a bounded `settlePending` round rotates fairly.
57
+ */
58
+ readonly pendingInvoices: (nowMs: number, limit: number) => Promise<ReadonlyArray<LightningAddressInvoice>>;
59
+ /** Records that `hashHex` was status-checked at `nowMs`; feeds the {@link pendingInvoices} order. */
60
+ readonly markChecked: (hashHex: string, nowMs: number) => Promise<void>;
61
+ /** Unsettled, unexpired invoice count at `nowMs` — scoped to `pubkeyHex` when given. */
62
+ readonly countPendingInvoices: (nowMs: number, pubkeyHex?: string) => Promise<number>;
63
+ /**
64
+ * Deletes expired unsettled invoices and settled invoices whose expiry passed more than
65
+ * `settledRetentionMs` ago. Returns the number of rows removed.
66
+ */
67
+ readonly pruneInvoices: (nowMs: number, settledRetentionMs: number) => Promise<number>;
68
+ readonly settleInvoice: (hashHex: string, preimageHex: string) => Promise<LightningAddressInvoice | undefined>;
69
+ };
70
+ /**
71
+ * In-memory {@link LightningAddressStore} — a reference implementation and the test store. `now` is
72
+ * injectable so statement claims can prune expired entries deterministically.
73
+ */
74
+ export declare function createMemoryLightningAddressStore(now?: () => number): LightningAddressStore;
75
+ //#endregion
76
+ //#region src/server/index.d.ts
77
+ export type LightningAddressResult = {
78
+ readonly status: number;
79
+ readonly body: unknown;
80
+ };
81
+ /** NIP-57 zap support: the receipt key plus the app's own zap-request validation. */
82
+ export type LightningAddressZapConfig = {
83
+ /** The receipt signer's 32-byte x-only public key, lowercase hex — the `nostrPubkey` payers see. */
84
+ readonly receiptPublicKey: string;
85
+ /**
86
+ * Validates a raw zap-request event JSON for `amountMsat`; throwing rejects the invoice. The
87
+ * registered user record holds no nostr key, so the request's `p` tag (the nostr pubkey the
88
+ * receipt attributes the zap to) cannot be bound to the payee — same behavior as Breez.
89
+ */
90
+ readonly validateRequest: (json: string, amountMsat: number) => void;
91
+ };
92
+ /** The wallet the server creates invoices through — a `SparkClient` or its lightning slice. */
93
+ export type LightningAddressServerClient = Pick<SparkClient, "lightning">;
94
+ export type LightningAddressServer = {
95
+ /** `POST /lnurlpay/{pubkey}` — signed statement claims a name. */
96
+ readonly register: (pubkeyHex: string, body: unknown) => Promise<LightningAddressResult>;
97
+ /** `DELETE /lnurlpay/{pubkey}` — signed statement gives the held name up. */
98
+ readonly unregister: (pubkeyHex: string, body: unknown) => Promise<LightningAddressResult>;
99
+ /** `GET /.well-known/lnurlp/{name}` (or `/lnurlp/{name}`) — the LUD-06 payRequest. */
100
+ readonly payRequest: (name: string) => Promise<LightningAddressResult>;
101
+ /** `GET /lnurlp/{name}/invoice?amount=&comment=&nostr=` — the LUD-06 callback. */
102
+ readonly invoice: (name: string, params: {
103
+ readonly amount: unknown;
104
+ readonly comment?: string;
105
+ readonly nostr?: string;
106
+ }) => Promise<LightningAddressResult>;
107
+ /** `GET /lnurlp/verify/{paymentHash}` — LUD-21 payment status. */
108
+ readonly verify: (paymentHashHex: string) => Promise<LightningAddressResult>;
109
+ /**
110
+ * Polls `client.lightning.receiveStatus` for a bounded batch of pending invoices —
111
+ * least-recently-checked first — and settles the ones whose verified preimage arrived. Returns
112
+ * the records settled this round so the app can publish zap receipts or webhooks.
113
+ */
114
+ readonly settlePending: () => Promise<ReadonlyArray<LightningAddressInvoice>>;
115
+ };
116
+ /**
117
+ * Creates the Lightning Address handlers for one `domain`. The registration LNURL handed out is the
118
+ * LUD-01 bech32 encoding of `https://{domain}/lnurlp/{name}` (not the `lnurlp://` LUD-17 scheme) —
119
+ * every LUD-16 wallet resolves the bare address without scheme support.
120
+ */
121
+ export declare function createLightningAddressServer(options: {
122
+ readonly client: LightningAddressServerClient;
123
+ /** The served hostname — validated, lowercased, and signed into every registration message. */
124
+ readonly domain: string;
125
+ readonly store: LightningAddressStore;
126
+ readonly minSendableMsat: number;
127
+ readonly maxSendableMsat: number;
128
+ /** Longest payer comment accepted (LUD-12 `commentAllowed`). */
129
+ readonly commentAllowed: number;
130
+ /** BOLT-11 expiry requested for every issued invoice; default {@link DEFAULT_INVOICE_EXPIRY_SECS}. */
131
+ readonly invoiceExpirySecs?: number;
132
+ /**
133
+ * Pending (unsettled, unexpired) invoices one payee may hold — an unauthenticated callback is a
134
+ * write, so without a cap a name's quota is a free database row each. Default 100. The cap is a
135
+ * storage bound, not abuse protection: anyone can fill one name's quota with unpaid invoices for
136
+ * up to `invoiceExpirySecs`, temporarily blocking that name's payments — operators should add
137
+ * per-IP rate limiting at the reverse proxy.
138
+ */
139
+ readonly maxPendingInvoicesPerUser?: number;
140
+ /** Pending invoices the whole server holds; default 10_000. */
141
+ readonly maxPendingInvoices?: number;
142
+ /**
143
+ * Registered names the whole server holds; default 100_000. A pubkey re-registering a new name is
144
+ * never blocked by the cap. This is a storage bound, not abuse protection: registration needs a
145
+ * valid signature but pubkeys are free to generate — put per-IP rate limiting on the register
146
+ * route at the reverse proxy.
147
+ */
148
+ readonly maxUsers?: number;
149
+ /**
150
+ * Invoices status-checked per `settlePending` round, least-recently-checked first so a backlog
151
+ * rotates fairly instead of starving the tail. Default {@link DEFAULT_SETTLE_BATCH}.
152
+ */
153
+ readonly settleBatch?: number;
154
+ readonly zap?: LightningAddressZapConfig | undefined;
155
+ readonly now?: (() => number) | undefined;
156
+ }): LightningAddressServer;
157
+ //#endregion
158
+ export type { LightningAddressInvoice, LightningAddressStore, LightningAddressUser };