@qntx/wallet-engine 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,19 +1,19 @@
1
+ import { r as NetworkInfo } from "./registry-LcFSPrE1.mjs";
1
2
  import { StoreApi } from "zustand/vanilla";
2
- import { CooperativeExitResult, LightningInvoice, LightningPayment, SparkClient, SparkExitChain, SparkNetwork, SparkPayment, SparkStore, SparkTransfer } from "@qntx/spark";
3
- import { NostrKeyRef, WalletSigner } from "@qntx/wallet/nostr";
4
- import { SparkSigner } from "@qntx/wallet/spark";
5
- import { PublicClient } from "viem";
6
- import { chains } from "@qntx/wallet/chains";
3
+ import { SignRequest } from "@qntx/wallet-chains";
4
+ import { BackupHeader, BackupInfo, InitialFactor, UnlockFactor, Wallet, WalletInfo } from "@qntx/wallet";
7
5
  import "@scure/btc-signer";
6
+ import { CooperativeExitResult, LightningInvoice, LightningPayment, SparkClient, SparkExitChain, SparkNetwork, SparkPayment, SparkStore, SparkTransfer } from "@qntx/spark";
8
7
  import { decodeEvm } from "@qntx/wallet-chains/evm";
9
- import { BackupHeader, BackupInfo, InitialFactor, UnlockFactor, Wallet, WalletInfo } from "@qntx/wallet";
10
- import { Event, Filter, NostrSigner } from "@qntx/nostr";
11
8
  import { LnurlPayRequest, LnurlWithdrawRequest } from "@qntx/spark/lnurl";
9
+ import { Event, Filter, NostrSigner } from "@qntx/nostr";
10
+ import { chains } from "@qntx/wallet/chains";
11
+ import { WalletSyncSigner } from "@qntx/wallet/nostr";
12
+ import { SparkSigner } from "@qntx/wallet/spark";
13
+ import { PublicClient } from "viem";
12
14
  import "@solana/kit";
13
- import { SparkAccountRef, SparkApproveIntent } from "@qntx/wallet/protocol";
14
15
  import "@scure/btc-signer/psbt.js";
15
- import "@qntx/wallet-chains";
16
- //#region src/controller.d.ts
16
+ import { SparkAccountRef, SparkApproveIntent } from "@qntx/wallet/protocol";
17
17
  type ControllerOptions<S> = {
18
18
  readonly name: string;
19
19
  readonly defaultState: () => S;
@@ -32,8 +32,6 @@ export declare abstract class Controller<S extends Record<string, unknown>> {
32
32
  abstract init(): Promise<void>;
33
33
  abstract destroy(): Promise<void>;
34
34
  }
35
- //#endregion
36
- //#region src/events.d.ts
37
35
  /** Log port: listener failures are reported here and never reach the emitter or peers. */
38
36
  type EngineLogger = {
39
37
  readonly warn: (message: string, error?: unknown) => void;
@@ -44,16 +42,12 @@ type EventBus<E> = {
44
42
  on: <K extends keyof E>(type: K, listener: (payload: E[K]) => void) => () => void;
45
43
  };
46
44
  export declare function createEventBus<E>(logger: EngineLogger): EventBus<E>;
47
- //#endregion
48
- //#region src/errors.d.ts
49
45
  type EngineErrorCode = "closed" | "integrity" | "invalid-input";
50
46
  /** Typed engine errors: store codes are `closed` and `integrity` (decrypt or bind failure). */
51
47
  export declare class EngineStoreError extends Error {
52
48
  readonly code: EngineErrorCode;
53
49
  constructor(code: EngineErrorCode, message: string);
54
50
  }
55
- //#endregion
56
- //#region src/store/port.d.ts
57
51
  /**
58
52
  * Engine storage port: named collections of byte values. `collection` is synchronous and cheap —
59
53
  * the same collection object backs every call, so guards (closed) live on the store.
@@ -69,42 +63,247 @@ type EngineStore = {
69
63
  readonly collection: (name: string) => EngineCollection;
70
64
  readonly close: () => Promise<void>;
71
65
  };
72
- //#endregion
73
- //#region src/store/memory.d.ts
74
66
  /** In-memory EngineStore: the Node/test implementation — same contract, no durability. */
75
67
  export declare function createMemoryEngineStore(backing?: {
76
68
  readonly collections?: Map<string, Map<string, Uint8Array>>;
77
69
  }): EngineStore;
78
- //#endregion
79
- //#region src/chains/bitcoin.d.ts
70
+ type ConnectionOrigin = {
71
+ readonly kind: "browser";
72
+ readonly origin: string;
73
+ };
74
+ type ConnectionScope = {
75
+ /** CAIP-10 */
76
+ readonly accounts: ReadonlyArray<string>;
77
+ readonly methods: ReadonlyArray<string>;
78
+ readonly notifications: ReadonlyArray<string>;
79
+ };
80
+ type ConnectionSession = {
81
+ readonly id: string;
82
+ readonly origin: ConnectionOrigin;
83
+ /** Keyed by CAIP-2. */
84
+ readonly scopes: Readonly<Record<string, ConnectionScope>>;
85
+ /** The session's current EVM network; only dApp sessions have one. */
86
+ readonly currentNetwork: string;
87
+ readonly createdAt: number;
88
+ readonly lastUsedAt: number;
89
+ };
90
+ /** `connect` asks for accounts; `network` asks to add a network the session does not cover. */
91
+ type PendingConnection = {
92
+ readonly id: string;
93
+ readonly origin: ConnectionOrigin;
94
+ readonly kind: "connect" | "network";
95
+ readonly network?: string;
96
+ readonly createdAt: number;
97
+ };
98
+ type AccountGroup$1 = {
99
+ readonly walletId: string;
100
+ readonly groupIndex: number;
101
+ };
102
+ type ConnectionState = {
103
+ readonly sessions: Readonly<Record<string, ConnectionSession>>;
104
+ readonly pending: Readonly<Record<string, PendingConnection>>;
105
+ };
106
+ type ConnectionControllerOptions = {
107
+ readonly openStore: () => Promise<EngineStore>;
108
+ readonly random: (n: number) => Uint8Array;
109
+ readonly now: () => number;
110
+ readonly logger: EngineLogger;
111
+ /** Development builds also accept `http://localhost`. */
112
+ readonly allowLocalhost: boolean;
113
+ /** The group the app currently shows; sessions are bound to it. */
114
+ readonly selectedGroup: () => AccountGroup$1 | undefined;
115
+ /** The group's EVM address on `network`. */
116
+ readonly evmAddress: (group: AccountGroup$1, network: string) => Promise<string>;
117
+ readonly enabledNetworks: () => ReadonlyArray<string>;
118
+ /** Voids a page's unfinished requests (revoked, or its network changed). */
119
+ readonly invalidate: (origin: string) => void;
120
+ };
121
+ export declare class ConnectionController extends Controller<ConnectionState> {
122
+ private readonly options;
123
+ private engineStore;
124
+ private readonly waiters;
125
+ private group;
126
+ constructor(options: ConnectionControllerOptions);
127
+ init(): Promise<void>;
128
+ destroy(): Promise<void>;
129
+ /** Locking is not consent: every pending connection is rejected. */
130
+ lock(): void;
131
+ /** Throws `4100` unless `origin` is `https://host[:port]` (or localhost in development). */
132
+ checkOrigin(origin: string): ConnectionOrigin;
133
+ session(origin: string): ConnectionSession | undefined;
134
+ pending(): ReadonlyArray<PendingConnection>;
135
+ /** The group sessions are bound to: the host's last `setActiveGroup`, else the app's selection. */
136
+ activeGroup(): AccountGroup$1 | undefined;
137
+ /** Whether `network` is an EVM network the wallet has enabled. */
138
+ isEnabled(network: string): boolean;
139
+ /** The origin's authorized addresses on its current network; empty when not connected. */
140
+ accounts(origin: string): ReadonlyArray<string>;
141
+ /** Throws `4100` unless the origin's session covers `method` on `network`. */
142
+ authorize(origin: string, network: string, method: string): ConnectionSession;
143
+ /** Resolves with the session once the host approves; rejects with `4001` when it refuses. */
144
+ request(origin: string, kind: PendingConnection["kind"], network?: string): Promise<ConnectionSession>;
145
+ /**
146
+ * The host's approval, by id: the accounts granted (EVM addresses of the active group) and the
147
+ * network.
148
+ */
149
+ approveConnection(id: string, grant: {
150
+ readonly accounts: ReadonlyArray<string>;
151
+ readonly network: string;
152
+ }): Promise<ConnectionSession>;
153
+ rejectConnection(id: string): void;
154
+ /** Ends the origin's session; its pending connection and unfinished requests are void. */
155
+ revoke(origin: string): Promise<void>;
156
+ /** Switches within the session's scope; the origin's unfinished requests are void. */
157
+ setCurrentNetwork(origin: string, network: string): Promise<void>;
158
+ touch(origin: string): Promise<void>;
159
+ /**
160
+ * The host switched account group: every session's accounts become the new group's addresses. A
161
+ * session that cannot get one (no address derivable) is left with an empty list and has to be
162
+ * authorized again.
163
+ */
164
+ setActiveGroup(walletId: string, groupIndex: number): Promise<void>;
165
+ private dropPending;
166
+ private persist;
167
+ private requireStore;
168
+ }
80
169
  type Esplora = {
81
170
  readonly get: (path: string) => Promise<unknown>;
82
171
  readonly post: (path: string, body: string) => Promise<unknown>;
83
172
  };
84
- //#endregion
85
- //#region src/meta/doc.d.ts
173
+ type SolanaRpc = {
174
+ readonly call: (method: string, params: unknown[]) => Promise<unknown>;
175
+ };
86
176
  /**
87
- * Wallet metadata document — the plaintext exchanged over kind-30078 events between this wallet's
88
- * own devices. One document per wallet; entry-level last-writer-wins on `updatedAt`.
177
+ * A host-endpoint-only client for cross-checking pre-signing reads. `not-needed`: the gateway is
178
+ * not this network's first endpoint, so the primary reads already come from the host.
179
+ * `unavailable`: a cross-check is needed but the network has no host endpoint.
89
180
  */
90
- type MetaDocWallet = {
91
- readonly name?: string;
181
+ type Independent<T> = {
182
+ readonly kind: "not-needed";
183
+ } | {
184
+ readonly kind: "unavailable";
185
+ } | {
186
+ readonly kind: "available";
187
+ readonly client: T;
188
+ };
189
+ type NetworkControllerState = {
190
+ readonly customRpc: Record<string, ReadonlyArray<string>>;
191
+ };
192
+ type Options$2 = {
193
+ readonly fetch: typeof globalThis.fetch;
194
+ readonly rpc: Readonly<Record<string, ReadonlyArray<string>>>;
195
+ readonly openStore: () => Promise<EngineStore>;
196
+ readonly logger: EngineLogger;
197
+ readonly gateway?: {
198
+ readonly url: string;
199
+ readonly installId: string;
200
+ } | undefined;
201
+ };
202
+ declare class NetworkController extends Controller<NetworkControllerState> {
203
+ private readonly options;
204
+ private engineStore?;
205
+ private readonly clients;
206
+ private readonly gatewayBase;
207
+ private readonly gatewayOrigin;
208
+ /** Networks the gateway reported at unlock; empty while locked or when the read failed. */
209
+ private gatewayServed;
210
+ constructor(options: Options$2);
211
+ init(): Promise<void>;
212
+ private loadCustomRpc;
213
+ /** Reads the gateway's served networks; any failure leaves the gateway unused until next unlock. */
214
+ private loadGatewayNetworks;
215
+ /** `fetch` that adds the install id to requests bound for the gateway's origin only. */
216
+ private readonly gatewayFetch;
217
+ destroy(): Promise<void>;
218
+ lock(): void;
219
+ /**
220
+ * Ordered endpoints: a network with custom endpoints uses custom → host and never the gateway;
221
+ * otherwise a gateway-served network uses gateway → host; else host only.
222
+ */
223
+ endpoints(network: string): ReadonlyArray<string>;
224
+ info(network: string): NetworkInfo;
225
+ client(network: string): PublicClient;
226
+ /** True when the gateway heads this network's endpoint list (no custom endpoints, gateway-served). */
227
+ private gatewayFirst;
228
+ private independent;
229
+ /** Host-endpoint EVM client for cross-checking reads; never sends the install id. */
230
+ independentClient(network: string): Independent<PublicClient>;
231
+ independentSolanaRpc(network: string): Independent<SolanaRpc>;
232
+ independentEsplora(network: string): Independent<Esplora>;
233
+ /** Networks with at least one configured endpoint or served by the gateway, by family. */
234
+ enabled(family: "evm" | "solana" | "bitcoin"): ReadonlyArray<string>;
235
+ /** Solana JSON-RPC over the ordered endpoint list — kit's transport is single-endpoint. */
236
+ solanaRpc(network: string): SolanaRpc;
237
+ /** Bounded-concurrency + 429-aware ordered-failover Esplora client. */
238
+ esplora(network: string): Esplora;
239
+ /** Validate a custom endpoint's reported chain id, then persist it first in the list. */
240
+ addRpc(network: string, url: string): Promise<void>;
241
+ }
242
+ type AddressFamily = "evm" | "solana" | "bitcoin" | "spark";
243
+ type AddressBookEntry = {
244
+ readonly id: string;
245
+ readonly name: string;
246
+ readonly family: AddressFamily;
247
+ readonly address: string;
248
+ readonly note?: string;
249
+ readonly createdAt: number;
92
250
  readonly updatedAt: number;
93
251
  };
94
- type MetaDocGroup = {
95
- readonly index: number;
96
- readonly name?: string;
97
- readonly hidden: boolean;
98
- readonly boundPubkey?: string;
252
+ type AddressBookTombstone = {
253
+ readonly id: string;
254
+ readonly deleted: true;
99
255
  readonly updatedAt: number;
100
256
  };
101
- type MetaDoc = {
102
- readonly v: 1;
103
- readonly wallet: MetaDocWallet;
104
- readonly groups: ReadonlyArray<MetaDocGroup>;
257
+ type AddressBookRecord = AddressBookEntry | AddressBookTombstone;
258
+ type AddressBookState = {
259
+ /** Live entries only; tombstones stay internal. */
260
+ readonly entries: Record<string, AddressBookEntry>;
261
+ };
262
+ type AddressBookOptions = {
263
+ readonly openStore: () => Promise<EngineStore>;
264
+ readonly random: (n: number) => Uint8Array;
265
+ readonly now: () => number;
266
+ readonly logger: EngineLogger;
105
267
  };
106
- //#endregion
107
- //#region src/spark/notes.d.ts
268
+ export declare class AddressBookController extends Controller<AddressBookState> {
269
+ private readonly options;
270
+ private engineStore;
271
+ /** Every record, tombstones included, by id. */
272
+ private records;
273
+ constructor(options: AddressBookOptions);
274
+ init(): Promise<void>;
275
+ destroy(): Promise<void>;
276
+ lock(): void;
277
+ /** Entries and tombstones, for the metadata document. */
278
+ syncEntries(): ReadonlyArray<AddressBookRecord>;
279
+ /**
280
+ * Merges records from another device (last writer wins, tombstones win over older entries).
281
+ * Returns whether anything local changed and whether the merged set holds something `remote`
282
+ * lacks, i.e. whether the remote copy needs a newer publish.
283
+ */
284
+ merge(remote: ReadonlyArray<AddressBookRecord>): Promise<{
285
+ readonly changed: boolean;
286
+ readonly localNewer: boolean;
287
+ }>;
288
+ add(params: {
289
+ readonly name: string;
290
+ readonly family: AddressFamily;
291
+ readonly address: string;
292
+ readonly note?: string;
293
+ }): Promise<AddressBookEntry>;
294
+ rename(id: string, name: string): Promise<AddressBookEntry>;
295
+ /** Sets or clears (empty or `undefined`) the note. */
296
+ setNote(id: string, note: string | undefined): Promise<AddressBookEntry>;
297
+ /** Removes the entry, leaving a tombstone so other devices drop it too. */
298
+ remove(id: string): Promise<void>;
299
+ private checkedNote;
300
+ private requireEntry;
301
+ private persist;
302
+ private requireStore;
303
+ }
304
+ type EvmFeeTier = "slow" | "normal" | "fast";
305
+ type SolanaFeeTier = "slow" | "normal" | "fast";
306
+ type RequestCheckCode = "decode-partial" | "nonce-mismatch" | "fee-mismatch" | "reads-unverified" | "fee-above-network" | "fee-exceeds-amount";
108
307
  /**
109
308
  * Local, engine-side metadata pinned to a history entry by transfer id. Written after the payment
110
309
  * it annotates has already succeeded — a missing or malformed note must never affect the payment
@@ -124,15 +323,11 @@ type PaymentNote = {
124
323
  type SparkActivity = SparkPayment & {
125
324
  readonly note?: PaymentNote;
126
325
  };
127
- //#endregion
128
- //#region src/spark/store.d.ts
129
326
  /**
130
327
  * SparkStore over one EngineCollection: chains keyed by leafId, plus an index record listing them
131
328
  * (`exitChains` is a full listing — the port has no secondary index).
132
329
  */
133
330
  export declare function sparkStore(col: EngineCollection): SparkStore;
134
- //#endregion
135
- //#region src/spark/controller.d.ts
136
331
  type SparkAccountState = {
137
332
  readonly status: "locked" | "connecting" | "ready" | "error";
138
333
  readonly balanceSats?: number;
@@ -260,6 +455,10 @@ export declare class SparkController extends Controller<SparkControllerState> {
260
455
  amountSats: number;
261
456
  memo?: string;
262
457
  }): Promise<LightningInvoice>;
458
+ /** A fresh single-use L1 deposit address (with the SO's proof of possession). */
459
+ newDepositAddress(ref: SparkAccountRef): Promise<string>;
460
+ /** Claims a confirmed L1 deposit paying one of the account's unused deposit addresses. */
461
+ claimDeposit(ref: SparkAccountRef, transaction: Uint8Array): Promise<void>;
263
462
  receiveAddress(ref: SparkAccountRef): Promise<string | undefined>;
264
463
  /**
265
464
  * Cooperative exit to an L1 address. The client exits `amountSats` of leaves and the payout is
@@ -274,198 +473,707 @@ export declare class SparkController extends Controller<SparkControllerState> {
274
473
  verify: UnlockFactor;
275
474
  }): Promise<CooperativeExitResult>;
276
475
  }
277
- //#endregion
278
- //#region src/accounts/controller.d.ts
279
- type AccountGroup = {
280
- readonly walletId: string;
281
- readonly index: number;
282
- /** `discoverGroups` result — family key → used; a family error lives under `errors`. */
283
- readonly used?: Readonly<Record<string, boolean>>;
284
- readonly errors?: Readonly<Record<string, string>>;
285
- readonly name?: string;
286
- readonly hidden: boolean;
287
- /** A social identity (64 lowercase hex) bound to this group; one identity binds one group. */
288
- readonly boundPubkey?: string;
289
- /** Last local modification time (ms); reconcile-created entries start at 0. */
290
- readonly updatedAt: number;
291
- };
292
- type AccountTreeState = {
293
- readonly status: "locked" | "ready";
294
- readonly wallets: ReadonlyArray<{
295
- readonly walletId: string;
296
- readonly name?: string;
297
- readonly updatedAt: number;
298
- }>;
299
- readonly groups: Readonly<Record<string, ReadonlyArray<AccountGroup>>>;
300
- readonly selected: {
301
- readonly walletId: string;
302
- readonly groupIndex: number;
303
- } | null;
304
- readonly totals: Readonly<Record<string, number>>;
305
- };
306
- type DiscoveryNetworks = {
307
- readonly client: (network: string) => {
308
- readonly getTransactionCount: (p: {
309
- readonly address: `0x${string}`;
310
- readonly blockTag: "latest";
311
- }) => Promise<number>;
312
- readonly getBalance: (p: {
313
- readonly address: `0x${string}`;
314
- }) => Promise<bigint>;
315
- };
316
- readonly solanaRpc: (network: string) => {
317
- readonly call: (method: string, params: unknown[]) => Promise<unknown>;
318
- };
319
- readonly esplora: (network: string) => Esplora;
320
- readonly enabled: (family: "evm" | "solana" | "bitcoin") => ReadonlyArray<string>;
476
+ /**
477
+ * What the host shows for a Spark, Lightning or exit request; `family` tells it apart from a chain
478
+ * intent.
479
+ */
480
+ type SparkDisplay = {
481
+ readonly family: "spark";
482
+ readonly network: SparkNetwork;
483
+ readonly kind: "transfer" | "lightning" | "exit";
484
+ readonly amountSats: number;
485
+ readonly maxFeeSats?: number;
486
+ /** Spark address, Lightning payee public key (hex) or bitcoin address. */
487
+ readonly receiver: string;
488
+ /** Invoice description or the LNURL metadata text. */
489
+ readonly description?: string;
321
490
  };
322
- type AccountTreeControllerOptions = {
323
- readonly wallet: Wallet;
324
- readonly spark: SparkController;
325
- readonly sparkNetwork: SparkNetwork;
326
- readonly networks?: DiscoveryNetworks;
327
- readonly chains?: {
328
- readonly account: (params: {
329
- readonly walletId: string;
330
- readonly groupIndex: number;
331
- readonly template: string;
332
- readonly network: string;
333
- }) => Promise<{
334
- readonly address?: string;
335
- readonly xpub?: string;
336
- }>;
337
- };
338
- readonly logger: EngineLogger;
339
- readonly now: () => number;
491
+ type SparkResult = {
492
+ readonly kind: "spark";
493
+ readonly transfer: SparkTransfer;
494
+ } | {
495
+ readonly kind: "lightning";
496
+ readonly payment: LightningPayment;
497
+ } | {
498
+ readonly kind: "withdraw";
499
+ readonly exit: CooperativeExitResult;
340
500
  };
341
- export declare class AccountTreeController extends Controller<AccountTreeState> {
342
- private readonly options;
343
- private col;
344
- private sparkUnsubscribe;
345
- private totalsTimer;
346
- private persistChain;
347
- constructor(options: AccountTreeControllerOptions);
348
- setStore(store: EngineStore | undefined): void;
349
- /** Load persisted state, reconcile with the snapshot, start driving Spark. */
350
- init(): Promise<void>;
351
- destroy(): Promise<void>;
352
- /** Locked: stop every Spark client, flush pending persists, then drop to the empty state. */
353
- lock(): Promise<void>;
354
- /** Reconcile groups/names/selected with `wallet.getSnapshot().wallets`; persists on change. */
355
- reconcile(): void;
356
- addGroup(walletId: string): number;
357
- addGroups(walletId: string, indices: ReadonlyArray<number>): void;
358
- renameWallet(walletId: string, name: string): void;
359
- renameGroup(walletId: string, index: number, name: string): void;
360
- setHidden(walletId: string, index: number, hidden: boolean): void;
361
- select(walletId: string, index: number): void;
362
- /**
363
- * Binds a social identity to one group. The identity is unique across the whole tree — every
364
- * group in every wallet carrying it loses the binding (each entry re-stamped).
365
- */
366
- bind(walletId: string, index: number, pubkey: string): void;
367
- unbind(walletId: string, index: number): void;
368
- /**
369
- * Which group currently holds a pubkey — merges can leave the same identity on groups in two
370
- * wallets; the newest `updatedAt` wins, ties resolve by snapshot wallet order then lower index.
371
- */
372
- bindingOf(pubkey: string): {
373
- walletId: string;
374
- groupIndex: number;
375
- } | undefined;
376
- /** The wallet's metadata document — every group, hidden included. */
377
- exportMeta(walletId: string): MetaDoc;
378
- /**
379
- * Entry-level last-writer-wins merge: a newer remote `updatedAt` replaces the local entry, a tie
380
- * keeps local; remote-only groups are added. `selected`/hidden rules are re-validated — if the
381
- * merge hides the selected group it is re-selected, and a merge may never leave every group
382
- * hidden (the selected one stays visible and is re-stamped so the fix propagates).
383
- */
384
- mergeMeta(walletId: string, doc: MetaDoc): {
385
- changed: boolean;
386
- localNewer: boolean;
387
- };
388
- /** Sequential inspect from 0 upward; index 0 always included; stop after 2 consecutive unused. */
389
- discoverSpark(walletId: string): Promise<number[]>;
390
- /**
391
- * Per-group chain-family discovery — same stop rule as `discoverSpark`: group 0 always, stop
392
- * after two consecutive fully-unused groups. A family error is reported on the group and does NOT
393
- * count as unused (the group stays in `found` — it can't be proven empty).
394
- */
395
- discoverGroups(walletId: string): Promise<number[]>;
396
- /** Refresh `totals` for every other visible group; per-group failures are logged and skipped. */
397
- refreshTotals(): Promise<void>;
398
- private onSparkState;
399
- private setTotal;
400
- private driveSpark;
401
- private visibleGroups;
402
- private validSelection;
403
- private selectFallback;
404
- private mutateGroups;
405
- private mustWallet;
406
- private mustGroup;
407
- private checkedName;
408
- private load;
409
- /** Queues a store write behind the previous one — the last queued write always lands last. */
410
- private persist;
411
- private scheduleTotalsPersist;
412
- private flushTotals;
413
- }
414
- //#endregion
415
- //#region src/networks/registry.d.ts
416
501
  /**
417
- * Built-in EVM network registry: the 8 mainnets from chains.mdx plus their main testnets. Each
418
- * chain id was checked against the chain's official documentation (source URL per entry).
419
- * Signing-time chainId always comes from this table, never from an RPC endpoint.
502
+ * A validated, fixed Spark payment: everything that does not need the user's verification has
503
+ * happened (including any LNURL invoice fetch), so approval only re-checks expiry and executes.
420
504
  */
421
- type NetworkInfo = {
422
- readonly id: string;
423
- readonly chainId: number;
424
- readonly name: string;
425
- readonly symbol: string;
426
- readonly multicall3?: string;
427
- /** CAIP-2 family — `solana:<ref>` networks are validated against genesis-hash, not chainId. */
428
- readonly family: "evm" | "solana" | "bitcoin";
429
- readonly genesisHash?: string;
430
- readonly decimals: number;
431
- readonly testnet: boolean;
505
+ type PreparedSparkAction = {
506
+ readonly display: SparkDisplay;
507
+ readonly checks: ReadonlyArray<RequestCheckCode>;
508
+ /** Why the action can no longer be paid at `now` (an expiring invoice), if so. */
509
+ readonly staleReason: (now: number) => string | undefined;
510
+ /** Runs the controller's approve-first payment call. */
511
+ readonly execute: (spark: SparkController, verify: UnlockFactor) => Promise<SparkResult>;
432
512
  };
433
- //#endregion
434
- //#region src/networks/controller.d.ts
435
- type SolanaRpc = {
436
- readonly call: (method: string, params: unknown[]) => Promise<unknown>;
513
+ type PaymentOption = {
514
+ readonly kind: "spark";
515
+ readonly address: string;
516
+ } | {
517
+ readonly kind: "spark-invoice";
518
+ readonly invoice: string;
519
+ readonly amountSats?: number;
520
+ readonly memo?: string;
521
+ readonly expiresAt?: number;
522
+ } | {
523
+ readonly kind: "lightning";
524
+ readonly invoice: string;
525
+ readonly amountSats?: number;
526
+ readonly paymentHash: string;
527
+ readonly description?: string;
528
+ readonly expiresAt: number;
529
+ } | {
530
+ readonly kind: "lnurl-pay";
531
+ readonly request: LnurlPayRequest;
532
+ readonly address?: string;
533
+ } | {
534
+ readonly kind: "lnurl-withdraw";
535
+ readonly request: LnurlWithdrawRequest;
536
+ } | {
537
+ readonly kind: "bitcoin";
538
+ readonly address: string;
437
539
  };
438
- type NetworkControllerState = {
439
- readonly customRpc: Record<string, ReadonlyArray<string>>;
540
+ type PaymentRequest = {
541
+ readonly options: ReadonlyArray<PaymentOption>;
542
+ readonly amountSats?: number;
543
+ readonly label?: string;
544
+ readonly message?: string;
440
545
  };
441
- type Options$2 = {
546
+ type PaymentParse = {
547
+ readonly kind: "request";
548
+ readonly request: PaymentRequest;
549
+ } | {
550
+ readonly kind: "lnurl";
551
+ readonly url: string;
552
+ readonly address?: string;
553
+ } | {
554
+ readonly kind: "unsupported";
555
+ readonly format: "bolt12" | "silent-payment" | "lnurl-auth" | "spark-token-invoice" | "nostr-wallet-connect";
556
+ } | {
557
+ readonly kind: "invalid";
558
+ readonly reason: "network" | "expired" | "amount-mismatch" | "malformed";
559
+ } | {
560
+ readonly kind: "unrecognized";
561
+ };
562
+ type ParseOptions = {
563
+ readonly network: SparkNetwork;
564
+ readonly now: number;
565
+ };
566
+ /** `input` → PaymentParse. Never throws: every failure is a structured result. */
567
+ export declare function parsePayment(input: string, options: ParseOptions): PaymentParse;
568
+ type PayParams = {
569
+ readonly account: SparkAccountRef;
570
+ readonly option: PaymentOption;
571
+ /** Caller-supplied amount — required by open invoices, the `spark` option, and LNURL flows. */
572
+ readonly amountSats?: number;
573
+ readonly comment?: string;
574
+ readonly maxFeeSats?: number;
575
+ readonly speed?: "FAST" | "MEDIUM" | "SLOW";
576
+ };
577
+ type WithdrawLnurlParams = {
578
+ readonly account: SparkAccountRef;
579
+ readonly option: Extract<PaymentOption, {
580
+ kind: "lnurl-withdraw";
581
+ }>;
582
+ readonly amountSats: number;
583
+ };
584
+ /** A profile zap names the recipient pubkey; an event zap carries the event it zaps. */
585
+ type ZapRecipient = {
586
+ readonly pubkey: string;
587
+ readonly lightning: string;
588
+ } | {
589
+ readonly event: Event;
590
+ readonly lightning: string;
591
+ };
592
+ type PrepareZapParams = {
593
+ /** Any `NostrSigner` — wallet key, external key, NIP-07/46 — signs the 9734. */
594
+ readonly signer: NostrSigner;
595
+ readonly recipient: ZapRecipient;
596
+ readonly amountSats: number;
597
+ readonly comment?: string | undefined;
598
+ readonly relays: ReadonlyArray<string>;
599
+ readonly network: SparkNetwork;
600
+ };
601
+ type PrepareZapContext = {
442
602
  readonly fetch: typeof globalThis.fetch;
443
- readonly rpc: Readonly<Record<string, ReadonlyArray<string>>>;
603
+ readonly now: () => number;
604
+ };
605
+ /** Everything a payer needs: the signed 9734, the invoice, and the note fields. */
606
+ type PreparedZap = {
607
+ readonly zapRequest: Event;
608
+ readonly invoice: string;
609
+ /** Hex payment hash decoded from the invoice. */
610
+ readonly paymentHash: string;
611
+ /** Millisecond expiry decoded from the invoice. */
612
+ readonly expiresAt: number;
613
+ readonly amountSats: number;
614
+ /** The LNURL service's 9735 signing pubkey. */
615
+ readonly receiptPubkey: string;
616
+ readonly recipientPubkey: string;
617
+ readonly eventId?: string;
618
+ readonly comment?: string;
619
+ /** The lud16 or LNURL as given. */
620
+ readonly lightning: string;
621
+ };
622
+ /** Everything up to payment: resolve LNURL, sign the 9734, request the invoice. */
623
+ export declare function prepareZap(params: PrepareZapParams, context: PrepareZapContext): Promise<PreparedZap>;
624
+ type ChainApis = ReturnType<typeof chains>;
625
+ type SignParams = Parameters<ChainApis["sign"]>[0];
626
+ type AccountRef = SignParams["request"]["account"];
627
+ type DisplayIntent = ReturnType<typeof decodeEvm>;
628
+ type TxState = "draft" | "signed" | "submitted" | "pending" | "confirmed" | "failed" | "rejected" | "dropped" | "replaced";
629
+ /** A bitcoin transaction that funds the wallet's own Spark account; it is claimed once confirmed. */
630
+ type SparkDepositTag = {
631
+ readonly walletId: string;
632
+ readonly groupIndex: number;
633
+ readonly network: SparkNetwork;
634
+ readonly claimed?: boolean;
635
+ };
636
+ type TrackedTransaction = {
637
+ readonly id: string;
638
+ readonly family: "evm" | "solana" | "bitcoin";
639
+ readonly network: string;
640
+ readonly account: string;
641
+ readonly hash: string | undefined;
642
+ readonly state: TxState;
643
+ readonly intent: DisplayIntent | undefined;
644
+ readonly nonce: number;
645
+ /** Solana only — the blockhash expiry that turns an unconfirmed tx `dropped`. */
646
+ readonly lastValidBlockHeight?: number;
647
+ /** EVM replacement — the earlier transaction this one bumps or cancels. */
648
+ readonly replaces?: string;
649
+ /** CPFP — the unconfirmed parent this child pushes. */
650
+ readonly parentId?: string;
651
+ /** Set on a Spark deposit; `claimed` flips once the deposit is credited on Spark. */
652
+ readonly sparkDeposit?: SparkDepositTag;
653
+ /** Bitcoin build record persisted at send for RBF/CPFP rebuilds. */
654
+ readonly btc?: {
655
+ readonly inputs: ReadonlyArray<{
656
+ readonly txid: string;
657
+ readonly vout: number;
658
+ readonly value: number;
659
+ readonly address: string;
660
+ }>;
661
+ readonly outputs: ReadonlyArray<{
662
+ readonly address: string;
663
+ readonly value: number;
664
+ readonly own: boolean;
665
+ }>;
666
+ readonly vsize: number;
667
+ readonly fee: number;
668
+ readonly feeRate: number;
669
+ };
670
+ readonly submittedAt: number;
671
+ readonly error: string | undefined;
672
+ };
673
+ type SendParams = {
674
+ readonly account: AccountRef;
675
+ readonly network: string;
676
+ readonly to: string;
677
+ readonly amount: bigint;
678
+ /** ERC-20 contract — when set, `to`/`amount` are the transfer recipient/units and value is 0. */
679
+ readonly token?: string;
680
+ readonly fee: EvmFeeTier | SolanaFeeTier;
681
+ /** SPL mint decimals — required when `token` is a Solana mint. */
682
+ readonly decimals?: number;
683
+ readonly verify: SignParams["verify"];
684
+ };
685
+ type PrepareParams = Omit<SendParams, "verify">;
686
+ type ChainRequestMethod = "send" | "speed-up" | "cancel" | "cpfp";
687
+ /** What a request asks for; replacements and CPFP name the original transaction. */
688
+ type ChainRequestInput = ({
689
+ readonly method: "send";
690
+ } & PrepareParams) | {
691
+ readonly method: "speed-up" | "cancel";
692
+ readonly txId: string;
693
+ } | {
694
+ readonly method: "cpfp";
695
+ readonly txId: string;
696
+ readonly feeRate: number;
697
+ };
698
+ /**
699
+ * A fully built, estimated and cross-checked send that has not been signed. The unsigned content
700
+ * lives in the closures and cannot change after `prepare` returns.
701
+ */
702
+ type PreparedRequest = {
703
+ /** Id of the tracked transaction `submit` creates. */
704
+ readonly txId: string;
705
+ readonly account: AccountRef;
706
+ readonly network: string;
707
+ /** The transaction being replaced or accelerated; absent for a plain send. */
708
+ readonly target?: string;
709
+ readonly intent: DisplayIntent;
710
+ readonly checks: ReadonlyArray<RequestCheckCode>;
711
+ /** Why the build can no longer be signed (nonce taken, blockhash expired), if so. */
712
+ readonly staleReason: () => string | undefined | Promise<string | undefined>;
713
+ /** Signs, finalizes, broadcasts and hands the transaction to tracking. */
714
+ readonly submit: (verify: SendParams["verify"]) => Promise<TrackedTransaction>;
715
+ /** Frees what `prepare` reserved; safe to call more than once. */
716
+ readonly release: () => void;
717
+ };
718
+ type SignatureResult = {
719
+ readonly kind: "evm.personal-message" | "evm.typed-data";
720
+ readonly signature: Uint8Array;
721
+ };
722
+ /** A prepared message signature: nothing is broadcast or tracked, `submit` returns the signature. */
723
+ type PreparedMessage = Omit<PreparedRequest, "submit" | "txId"> & {
724
+ readonly submit: (verify: SendParams["verify"]) => Promise<SignatureResult>;
725
+ };
726
+ type Options$1 = {
727
+ readonly wallet: Wallet;
728
+ readonly networks: NetworkController;
444
729
  readonly openStore: () => Promise<EngineStore>;
730
+ readonly random: (n: number) => Uint8Array;
731
+ readonly setTimer: (ms: number, fn: () => unknown) => () => void;
732
+ readonly now: () => number;
445
733
  readonly logger: EngineLogger;
734
+ readonly pollMs?: number;
735
+ /** Test seam — defaults to the wallet's real chains proxy. */
736
+ readonly chains?: ReturnType<typeof chains>;
446
737
  };
447
- declare class NetworkController extends Controller<NetworkControllerState> {
738
+ type TransactionControllerState = {
739
+ readonly txs: Record<string, TrackedTransaction>;
740
+ };
741
+ declare class TransactionController extends Controller<TransactionControllerState> {
448
742
  private readonly options;
449
743
  private engineStore?;
450
- private readonly clients;
451
- constructor(options: Options$2);
744
+ private readonly timers;
745
+ private evmRoute;
746
+ private bitcoinRoute;
747
+ private locked;
748
+ constructor(options: Options$1);
452
749
  init(): Promise<void>;
453
750
  destroy(): Promise<void>;
454
751
  lock(): void;
455
- endpoints(network: string): ReadonlyArray<string>;
456
- info(network: string): NetworkInfo;
457
- client(network: string): PublicClient;
458
- /** Validate a custom endpoint's reported chain id, then persist it first in the list. */
459
- /** Solana JSON-RPC over the ordered endpoint list — kit's transport is single-endpoint. */
460
- /** Networks with at least one configured endpoint, filtered by family. */
461
- enabled(family: "evm" | "solana" | "bitcoin"): ReadonlyArray<string>;
462
- solanaRpc(network: string): SolanaRpc;
463
- /** Bounded-concurrency + 429-aware ordered-failover Esplora client. */
464
- esplora(network: string): Esplora;
465
- addRpc(network: string, url: string): Promise<void>;
752
+ private persist;
753
+ private transition;
754
+ private routeCtx;
755
+ /**
756
+ * Internal entry for the request pipeline: builds, estimates and cross-checks a send without
757
+ * signing it. Bitcoin outputs it selects stay locked under `requestId` until the returned
758
+ * `release` runs or the broadcast moves them to the transaction.
759
+ */
760
+ prepareSend(params: PrepareParams, requestId: string, sparkDeposit?: SparkDepositTag): Promise<PreparedRequest>;
761
+ /** The group's next unused `btc.bip84` receive address on a bitcoin `network`. */
762
+ nextReceiveAddress(walletId: string, groupIndex: number, network: string): Promise<string>;
763
+ /** Records that a Spark deposit was credited; persisted with the transaction. */
764
+ markDepositClaimed(id: string): Promise<void>;
765
+ /**
766
+ * Internal entry for a dApp's EVM signature request: the request is normalized first, a
767
+ * transaction is rebuilt with fresh nonce and fees (the dApp's own are discarded), and a message
768
+ * is decoded and signed as is.
769
+ */
770
+ prepareDapp(account: AccountRef, request: SignRequest): Promise<PreparedRequest | PreparedMessage>;
771
+ /**
772
+ * Internal entry for replacements and CPFP: builds, cross-checks and reserves what the
773
+ * replacement needs without signing it. The original must be `submitted` or `pending`.
774
+ */
775
+ prepareReplacement(input: Exclude<ChainRequestInput, {
776
+ method: "send";
777
+ }>, requestId: string): Promise<PreparedRequest>;
778
+ private track;
779
+ }
780
+ /**
781
+ * Recipient risk, pure: a recipient the wallet has never paid or saved is a `warning`; one that
782
+ * looks like a known address (same first and last four characters, different address) is a `danger`
783
+ * — the shape of an address-poisoning lookalike. Both can apply to one recipient.
784
+ */
785
+ type RiskFinding = {
786
+ readonly code: "first-recipient" | "address-poisoning";
787
+ readonly level: "warning" | "danger";
788
+ };
789
+ /** A web page; `origin` is `scheme://host[:port]`, computed by the host from the top-level URL. */
790
+ type BrowserOrigin = {
791
+ readonly kind: "browser";
792
+ readonly origin: string;
793
+ };
794
+ type RequestOrigin = {
795
+ readonly kind: "internal";
796
+ } | BrowserOrigin;
797
+ type RequestMethod = ChainRequestMethod | "pay" | "zap" | "evm-sign" | "spark-deposit" | "spark-exit";
798
+ type SignLogOutcome = "submitted" | "rejected" | "failed" | "expired" | "invalidated";
799
+ type SignLogEntry = {
800
+ readonly id: string;
801
+ readonly at: number;
802
+ readonly origin: RequestOrigin;
803
+ readonly method: RequestMethod;
804
+ readonly account: AccountRef | SparkAccountRef;
805
+ readonly network: string;
806
+ readonly summary: string;
807
+ readonly verification: "verified" | "unverifiable";
808
+ readonly checks: ReadonlyArray<RequestCheckCode>;
809
+ readonly risk: ReadonlyArray<RiskFinding>;
810
+ readonly outcome: SignLogOutcome;
811
+ readonly error?: string;
812
+ };
813
+ /** A Spark, Lightning (including LNURL-pay and lightning addresses) or cooperative-exit payment. */
814
+ type PayInput = {
815
+ readonly method: "pay";
816
+ } & PayParams;
817
+ /** A prepared zap; its invoice was fetched by `engine.zaps.prepare` and is paid as-is. */
818
+ type ZapInput = {
819
+ readonly method: "zap";
820
+ readonly account: SparkAccountRef;
821
+ readonly zap: PreparedZap;
822
+ readonly maxFeeSats: number;
823
+ };
824
+ /**
825
+ * A dApp's EVM request: a transaction (rebuilt here with fresh nonce and fees), a personal message
826
+ * or EIP-712 typed data.
827
+ */
828
+ type EvmSignInput = {
829
+ readonly method: "evm-sign";
830
+ readonly origin: BrowserOrigin;
831
+ readonly account: AccountRef;
832
+ readonly network: string;
833
+ readonly request: SignRequest;
834
+ };
835
+ /**
836
+ * Funds the wallet's own Spark account from a bitcoin account of the same wallet; the transaction
837
+ * is an ordinary bitcoin send to a fresh Spark deposit address and is claimed once confirmed.
838
+ */
839
+ type SparkDepositInput = {
840
+ readonly method: "spark-deposit";
841
+ readonly account: SparkAccountRef;
842
+ readonly from: AccountRef;
843
+ readonly amountSats: number;
844
+ readonly fee: PrepareParams["fee"];
845
+ };
846
+ /** A cooperative exit to the group's own next unused `btc.bip84` receive address. */
847
+ type SparkExitInput = {
848
+ readonly method: "spark-exit";
849
+ readonly account: SparkAccountRef;
850
+ readonly amountSats: number;
851
+ readonly maxFeeSats: number;
852
+ readonly speed?: "FAST" | "MEDIUM" | "SLOW";
853
+ };
854
+ type RequestInput = ChainRequestInput | PayInput | ZapInput | EvmSignInput | SparkDepositInput | SparkExitInput;
855
+ type RequestStage = "awaiting-approval" | "signing" | "submitted" | "rejected" | "failed" | "expired" | "invalidated";
856
+ type PendingRequest = {
857
+ readonly id: string;
858
+ readonly origin: RequestOrigin;
859
+ readonly method: RequestMethod;
860
+ readonly account: AccountRef | SparkAccountRef;
861
+ readonly network: string;
862
+ readonly stage: RequestStage;
863
+ /** Decoded locally from the exact content that will be signed; `family` tells the shapes apart. */
864
+ readonly display: DisplayIntent | SparkDisplay;
865
+ /** `unverifiable` as soon as any check is recorded; the host must ask for explicit confirmation. */
866
+ readonly verification: "verified" | "unverifiable";
867
+ readonly checks: ReadonlyArray<RequestCheckCode>;
868
+ /**
869
+ * Recipient risk; independent of `verification`. A `danger` needs the user's explicit
870
+ * acknowledgement.
871
+ */
872
+ readonly risk: ReadonlyArray<RiskFinding>;
873
+ readonly createdAt: number;
874
+ readonly expiresAt: number;
875
+ /** The transaction being replaced or accelerated; absent for a plain send. */
876
+ readonly target?: string;
877
+ /** The tracked transaction once submitted. */
878
+ readonly txId?: string;
879
+ readonly error?: string;
880
+ };
881
+ type RequestControllerState = {
882
+ readonly requests: Record<string, PendingRequest>;
883
+ };
884
+ type RequestControllerOptions = {
885
+ readonly transactions: TransactionController;
886
+ /** The full controller: its payment methods are reachable only through this pipeline. */
887
+ readonly spark: SparkController;
888
+ readonly addressBook: AddressBookController;
889
+ readonly openStore: () => Promise<EngineStore>;
890
+ readonly fetch: typeof globalThis.fetch;
891
+ readonly random: (n: number) => Uint8Array;
892
+ readonly now: () => number;
893
+ readonly setTimer: (ms: number, fn: () => unknown) => () => void;
894
+ readonly logger: EngineLogger;
895
+ };
896
+ /** Requests expire this long after creation. */
897
+ export declare const REQUEST_TTL_MS: number;
898
+ export declare class RequestController extends Controller<RequestControllerState> {
899
+ private readonly options;
900
+ private readonly entries;
901
+ private readonly log;
902
+ private readonly settlers;
903
+ /** Original transaction id → the unfinished request replacing or accelerating it. */
904
+ private readonly targets;
905
+ private locked;
906
+ /** Bumped on every unlock and lock so work that outlives a session never touches the next one. */
907
+ private session;
908
+ constructor(options: RequestControllerOptions);
909
+ init(): Promise<void>;
910
+ destroy(): Promise<void>;
911
+ /** Locking is not consent: every unfinished request becomes `invalidated` and frees its resources. */
912
+ lock(): void;
913
+ /** A page navigated away, was revoked or changed network: its unfinished requests are void. */
914
+ invalidateOrigin(origin: string): void;
915
+ /** The signature record, newest first. */
916
+ signLog(): Promise<ReadonlyArray<SignLogEntry>>;
917
+ clearSignLog(): Promise<void>;
918
+ prepare(input: RequestInput): Promise<PendingRequest>;
919
+ /**
920
+ * Resolves with what `approve` returned, or rejects with `RequestEndedError` when the request
921
+ * ends any other way — how a dApp's pending call learns the outcome.
922
+ */
923
+ outcome(id: string): Promise<TrackedTransaction | SparkResult | SignatureResult>;
924
+ private openSettler;
925
+ private settle;
926
+ /** The bitcoin network `account`'s Spark network settles on; any other pairing is refused. */
927
+ private l1Network;
928
+ private sparkBuilt;
929
+ private build;
930
+ /**
931
+ * Signs and submits a request. Anything but a live, unexpired `send` request with this id throws
932
+ * before any signing; a stale build (nonce taken, blockhash expired) fails without signing.
933
+ */
934
+ approve(id: string, method: ChainRequestMethod | "spark-deposit", verify: SendParams["verify"]): Promise<TrackedTransaction>;
935
+ approve(id: string, method: "pay" | "zap" | "spark-exit", verify: SendParams["verify"]): Promise<SparkResult>;
936
+ approve(id: string, method: "evm-sign", verify: SendParams["verify"]): Promise<TrackedTransaction | SignatureResult>;
937
+ /**
938
+ * A cancelled or refused verification is `rejected`; anything after it is `failed`. Chain
939
+ * requests record which step failed on the tracked transaction; for Spark, the approval round is
940
+ * the only source of `WalletError`.
941
+ */
942
+ private failureStage;
943
+ reject(id: string): void;
944
+ private requireRequest;
945
+ private put;
946
+ private unclaim;
947
+ /** Ends a request in a terminal stage and frees what it reserved. */
948
+ private finish;
949
+ /** Appends the final stage to the signature record; a failed write is a warning, not an error. */
950
+ private record;
951
+ private appendLog;
952
+ /**
953
+ * Addresses the wallet already knows for a family: contacts, plus recipients of tracked chain
954
+ * sends (Spark payments are not tracked).
955
+ */
956
+ private knownAddresses;
957
+ private chainRisk;
958
+ /** Only a Spark transfer has a recipient to check; Lightning and exits are not assessed. */
959
+ private sparkRisk;
960
+ }
961
+ type Eip1193Request = {
962
+ readonly method: string;
963
+ readonly params?: unknown;
964
+ };
965
+ type Eip1193Event = {
966
+ readonly name: "connect";
967
+ readonly origin: string;
968
+ readonly chainId: string;
969
+ } | {
970
+ readonly name: "accountsChanged";
971
+ readonly origin: string;
972
+ readonly accounts: ReadonlyArray<string>;
973
+ } | {
974
+ readonly name: "chainChanged";
975
+ readonly origin: string;
976
+ readonly chainId: string;
977
+ } | {
978
+ readonly name: "disconnect";
979
+ readonly origin: string;
980
+ };
981
+ type Eip1193HandlerOptions = {
982
+ readonly connections: ConnectionController;
983
+ readonly requests: RequestController;
984
+ readonly networks: NetworkController;
985
+ /** The network an unconnected page sees. */
986
+ readonly defaultNetwork: () => string;
987
+ };
988
+ export declare class Eip1193Handler {
989
+ private readonly options;
990
+ private readonly listeners;
991
+ private unsubscribe;
992
+ constructor(options: Eip1193HandlerOptions);
993
+ /** Events for the host to forward to pages; derived from changes to the sessions. */
994
+ subscribe(listener: (event: Eip1193Event) => void): () => void;
995
+ handle(origin: string, request: Eip1193Request): Promise<unknown>;
996
+ private requestAccounts;
997
+ private permissions;
998
+ private switchChain;
999
+ private authorizedAccount;
1000
+ private personalSign;
1001
+ private typedData;
1002
+ private sendTransaction;
1003
+ private signingSession;
1004
+ private sign;
1005
+ private read;
1006
+ }
1007
+ /** EIP-1193 / EIP-1474 / EIP-3326 error: `code` is what the page's promise rejects with. */
1008
+ export declare class ProviderError extends Error {
1009
+ readonly code: number;
1010
+ constructor(code: number, message: string);
1011
+ }
1012
+ type AddressBookErrorCategory = "invalid-input" | "duplicate" | "not-found" | "locked" | "limit";
1013
+ export declare class AddressBookError extends Error {
1014
+ readonly category: AddressBookErrorCategory;
1015
+ constructor(category: AddressBookErrorCategory, message: string);
1016
+ }
1017
+ /**
1018
+ * Wallet metadata document — the plaintext exchanged over kind-30078 events between this wallet's
1019
+ * own devices. One document per wallet; entry-level last-writer-wins on `updatedAt`.
1020
+ */
1021
+ type MetaDocWallet = {
1022
+ readonly name?: string;
1023
+ readonly updatedAt: number;
1024
+ };
1025
+ type MetaDocGroup = {
1026
+ readonly index: number;
1027
+ readonly name?: string;
1028
+ readonly hidden: boolean;
1029
+ readonly boundPubkey?: string;
1030
+ readonly updatedAt: number;
1031
+ };
1032
+ /** The per-wallet part of the document: this wallet's name and account groups. */
1033
+ type MetaDocTree = {
1034
+ readonly wallet: MetaDocWallet;
1035
+ readonly groups: ReadonlyArray<MetaDocGroup>;
1036
+ };
1037
+ /** Every wallet's document also carries the whole address book, which is shared across wallets. */
1038
+ type MetaDoc = MetaDocTree & {
1039
+ readonly v: 2;
1040
+ readonly contacts: ReadonlyArray<AddressBookRecord>;
1041
+ };
1042
+ type AccountGroup = {
1043
+ readonly walletId: string;
1044
+ readonly index: number;
1045
+ /** `discoverGroups` result — family key → used; a family error lives under `errors`. */
1046
+ readonly used?: Readonly<Record<string, boolean>>;
1047
+ readonly errors?: Readonly<Record<string, string>>;
1048
+ readonly name?: string;
1049
+ readonly hidden: boolean;
1050
+ /** A social identity (64 lowercase hex) bound to this group; one identity binds one group. */
1051
+ readonly boundPubkey?: string;
1052
+ /** Last local modification time (ms); reconcile-created entries start at 0. */
1053
+ readonly updatedAt: number;
1054
+ };
1055
+ type AccountTreeState = {
1056
+ readonly status: "locked" | "ready";
1057
+ readonly wallets: ReadonlyArray<{
1058
+ readonly walletId: string;
1059
+ readonly name?: string;
1060
+ readonly updatedAt: number;
1061
+ }>;
1062
+ readonly groups: Readonly<Record<string, ReadonlyArray<AccountGroup>>>;
1063
+ readonly selected: {
1064
+ readonly walletId: string;
1065
+ readonly groupIndex: number;
1066
+ } | null;
1067
+ readonly totals: Readonly<Record<string, number>>;
1068
+ };
1069
+ type DiscoveryNetworks = {
1070
+ readonly client: (network: string) => {
1071
+ readonly getTransactionCount: (p: {
1072
+ readonly address: `0x${string}`;
1073
+ readonly blockTag: "latest";
1074
+ }) => Promise<number>;
1075
+ readonly getBalance: (p: {
1076
+ readonly address: `0x${string}`;
1077
+ }) => Promise<bigint>;
1078
+ };
1079
+ readonly solanaRpc: (network: string) => {
1080
+ readonly call: (method: string, params: unknown[]) => Promise<unknown>;
1081
+ };
1082
+ readonly esplora: (network: string) => Esplora;
1083
+ readonly enabled: (family: "evm" | "solana" | "bitcoin") => ReadonlyArray<string>;
1084
+ };
1085
+ type AccountTreeControllerOptions = {
1086
+ readonly wallet: Wallet;
1087
+ readonly spark: SparkController;
1088
+ readonly sparkNetwork: SparkNetwork;
1089
+ readonly networks?: DiscoveryNetworks;
1090
+ readonly chains?: {
1091
+ readonly account: (params: {
1092
+ readonly walletId: string;
1093
+ readonly groupIndex: number;
1094
+ readonly template: string;
1095
+ readonly network: string;
1096
+ }) => Promise<{
1097
+ readonly address?: string;
1098
+ readonly xpub?: string;
1099
+ }>;
1100
+ };
1101
+ readonly logger: EngineLogger;
1102
+ readonly now: () => number;
1103
+ };
1104
+ export declare class AccountTreeController extends Controller<AccountTreeState> {
1105
+ private readonly options;
1106
+ private col;
1107
+ private sparkUnsubscribe;
1108
+ private totalsTimer;
1109
+ private persistChain;
1110
+ constructor(options: AccountTreeControllerOptions);
1111
+ setStore(store: EngineStore | undefined): void;
1112
+ /** Load persisted state, reconcile with the snapshot, start driving Spark. */
1113
+ init(): Promise<void>;
1114
+ destroy(): Promise<void>;
1115
+ /** Locked: stop every Spark client, flush pending persists, then drop to the empty state. */
1116
+ lock(): Promise<void>;
1117
+ /** Reconcile groups/names/selected with `wallet.getSnapshot().wallets`; persists on change. */
1118
+ reconcile(): void;
1119
+ addGroup(walletId: string): number;
1120
+ addGroups(walletId: string, indices: ReadonlyArray<number>): void;
1121
+ renameWallet(walletId: string, name: string): void;
1122
+ renameGroup(walletId: string, index: number, name: string): void;
1123
+ setHidden(walletId: string, index: number, hidden: boolean): void;
1124
+ select(walletId: string, index: number): void;
1125
+ /**
1126
+ * Binds a social identity to one group. The identity is unique across the whole tree — every
1127
+ * group in every wallet carrying it loses the binding (each entry re-stamped).
1128
+ */
1129
+ bind(walletId: string, index: number, pubkey: string): void;
1130
+ unbind(walletId: string, index: number): void;
1131
+ /**
1132
+ * Which group currently holds a pubkey — merges can leave the same identity on groups in two
1133
+ * wallets; the newest `updatedAt` wins, ties resolve by snapshot wallet order then lower index.
1134
+ */
1135
+ bindingOf(pubkey: string): {
1136
+ walletId: string;
1137
+ groupIndex: number;
1138
+ } | undefined;
1139
+ /** The wallet's metadata document — every group, hidden included. */
1140
+ exportMeta(walletId: string): MetaDocTree;
1141
+ /**
1142
+ * Entry-level last-writer-wins merge: a newer remote `updatedAt` replaces the local entry, a tie
1143
+ * keeps local; remote-only groups are added. `selected`/hidden rules are re-validated — if the
1144
+ * merge hides the selected group it is re-selected, and a merge may never leave every group
1145
+ * hidden (the selected one stays visible and is re-stamped so the fix propagates).
1146
+ */
1147
+ mergeMeta(walletId: string, doc: MetaDocTree): {
1148
+ changed: boolean;
1149
+ localNewer: boolean;
1150
+ };
1151
+ /** Sequential inspect from 0 upward; index 0 always included; stop after 2 consecutive unused. */
1152
+ discoverSpark(walletId: string): Promise<number[]>;
1153
+ /**
1154
+ * Per-group chain-family discovery — same stop rule as `discoverSpark`: group 0 always, stop
1155
+ * after two consecutive fully-unused groups. A family error is reported on the group and does NOT
1156
+ * count as unused (the group stays in `found` — it can't be proven empty).
1157
+ */
1158
+ discoverGroups(walletId: string): Promise<number[]>;
1159
+ /** Refresh `totals` for every other visible group; per-group failures are logged and skipped. */
1160
+ refreshTotals(): Promise<void>;
1161
+ private onSparkState;
1162
+ private setTotal;
1163
+ private driveSpark;
1164
+ private visibleGroups;
1165
+ private validSelection;
1166
+ private selectFallback;
1167
+ private mutateGroups;
1168
+ private mustWallet;
1169
+ private mustGroup;
1170
+ private checkedName;
1171
+ private load;
1172
+ /** Queues a store write behind the previous one — the last queued write always lands last. */
1173
+ private persist;
1174
+ private scheduleTotalsPersist;
1175
+ private flushTotals;
466
1176
  }
467
- //#endregion
468
- //#region src/assets/controller.d.ts
469
1177
  type TokenBalance = {
470
1178
  readonly address: string;
471
1179
  readonly symbol: string;
@@ -484,7 +1192,7 @@ type AccountBalances = {
484
1192
  type AssetsControllerState = {
485
1193
  readonly balances: Record<string, AccountBalances>;
486
1194
  };
487
- type Options$1 = {
1195
+ type Options = {
488
1196
  readonly networks: NetworkController;
489
1197
  readonly openStore: () => Promise<EngineStore>;
490
1198
  readonly setTimer: (ms: number, fn: () => unknown) => () => void;
@@ -497,7 +1205,7 @@ declare class AssetsController extends Controller<AssetsControllerState> {
497
1205
  private visible;
498
1206
  private cancelPoll;
499
1207
  private readonly customTokens;
500
- constructor(options: Options$1);
1208
+ constructor(options: Options);
501
1209
  init(): Promise<void>;
502
1210
  lock(): void;
503
1211
  destroy(): Promise<void>;
@@ -510,26 +1218,19 @@ declare class AssetsController extends Controller<AssetsControllerState> {
510
1218
  private readTokens;
511
1219
  private balanceOfBatch;
512
1220
  private balanceOf;
513
- private callString;
514
- private callNumber;
1221
+ private callToken;
515
1222
  /** Reads symbol/decimals on-chain, marks unverified, persists; throws when the calls fail. */
516
1223
  addToken(network: string, address: string): Promise<void>;
517
1224
  private persistToken;
518
- private rpc;
519
- private rpcHex;
520
1225
  /** BTC balance = confirmed spendable + own unconfirmed change; frozen 546/330 shown separately. */
521
1226
  refreshBitcoin(xpub: string, network: string): Promise<void>;
522
1227
  }
523
- //#endregion
524
- //#region src/backup/errors.d.ts
525
1228
  /** BackupController error categories — never carry file contents, digests or secrets. */
526
1229
  type BackupErrorCategory = "cloud" | "conflict" | "unavailable";
527
1230
  export declare class BackupControllerError extends Error {
528
1231
  readonly category: BackupErrorCategory;
529
1232
  constructor(category: BackupErrorCategory, message: string);
530
1233
  }
531
- //#endregion
532
- //#region src/backup/port.d.ts
533
1234
  /**
534
1235
  * Cloud-file storage port (design/backup.mdx "存储"): the cloud is an opaque key-value namespace —
535
1236
  * names are file names (`qntx-wallet-backup-<id>.json`), contents are envelope texts. The kernel
@@ -547,8 +1248,6 @@ type CloudStore = {
547
1248
  /** Deleting a missing file succeeds. */
548
1249
  readonly delete: (name: string) => Promise<void>;
549
1250
  };
550
- //#endregion
551
- //#region src/backup/controller.d.ts
552
1251
  type BackupControllerState = {
553
1252
  readonly status: "locked" | "unavailable" | "off" | "syncing" | "synced" | "conflict" | "error";
554
1253
  /** The retained backup key's public summary from the wallet snapshot. */
@@ -556,7 +1255,6 @@ type BackupControllerState = {
556
1255
  readonly uploadedAt: number | undefined;
557
1256
  readonly unprotected: {
558
1257
  readonly walletIds: ReadonlyArray<string>;
559
- readonly nostrKeyIds: ReadonlyArray<string>;
560
1258
  };
561
1259
  readonly error: BackupErrorCategory | undefined;
562
1260
  };
@@ -618,16 +1316,12 @@ export declare class BackupController extends Controller<BackupControllerState>
618
1316
  private reconcile;
619
1317
  private pass;
620
1318
  }
621
- //#endregion
622
- //#region src/meta/errors.d.ts
623
1319
  /** Meta-sync error categories — messages never carry event or document content. */
624
1320
  type MetaSyncErrorCategory = "network" | "internal" | "unsupported" | "corrupt";
625
1321
  export declare class MetaSyncError extends Error {
626
1322
  readonly category: MetaSyncErrorCategory;
627
1323
  constructor(category: MetaSyncErrorCategory, message: string, cause?: unknown);
628
1324
  }
629
- //#endregion
630
- //#region src/meta/controller.d.ts
631
1325
  type MetaSyncRelayPort = {
632
1326
  readonly fetch: (filter: Filter) => Promise<ReadonlyArray<Event>>;
633
1327
  readonly publish: (event: Event) => Promise<void>;
@@ -638,18 +1332,30 @@ type MetaSyncConfig = {
638
1332
  readonly relays: MetaSyncRelayPort;
639
1333
  };
640
1334
  type MetaSyncWalletStatus = {
641
- readonly status: "unavailable" | "syncing" | "synced" | "error";
1335
+ readonly status: "syncing" | "synced" | "error";
642
1336
  readonly error?: MetaSyncErrorCategory;
643
1337
  };
644
1338
  type MetaSyncState = {
645
1339
  readonly wallets: Readonly<Record<string, MetaSyncWalletStatus>>;
646
1340
  };
1341
+ /** What the sync needs from the address book: its records, a merge, and a change subscription. */
1342
+ type AddressBookSync = {
1343
+ readonly syncEntries: () => ReadonlyArray<AddressBookRecord>;
1344
+ readonly merge: (remote: ReadonlyArray<AddressBookRecord>) => Promise<{
1345
+ readonly changed: boolean;
1346
+ readonly localNewer: boolean;
1347
+ }>;
1348
+ readonly store: {
1349
+ readonly subscribe: (listener: () => void) => () => void;
1350
+ };
1351
+ };
647
1352
  type MetaSyncControllerOptions = {
648
1353
  readonly wallet: Wallet;
649
1354
  readonly accounts: AccountTreeController;
650
- /** `nostr(wallet)` — the signer is requested per wallet for its group-0 derived key. */
1355
+ readonly addressBook: AddressBookSync;
1356
+ /** `nostr(wallet).syncSigner` — each wallet signs metadata with its dedicated sync key. */
651
1357
  readonly nostr: {
652
- readonly signer: (ref: NostrKeyRef) => WalletSigner;
1358
+ readonly syncSigner: (walletId: string) => WalletSyncSigner;
653
1359
  };
654
1360
  readonly metaSync: MetaSyncConfig | undefined;
655
1361
  readonly logger: EngineLogger;
@@ -657,9 +1363,10 @@ type MetaSyncControllerOptions = {
657
1363
  };
658
1364
  /**
659
1365
  * Wallet-metadata sync over kind-30078 addressable events: one NIP-44-encrypted document per
660
- * wallet, authored by its group-0 derived key. Pull always precedes publish in an unlock session —
661
- * replaceable events would otherwise let a stale local tree overwrite a newer remote document.
662
- * Debounced publishes are per wallet; a generation counter cancels in-flight write-backs on lock.
1366
+ * wallet, authored by its dedicated sync key (WD-070). Pull always precedes publish in an unlock
1367
+ * session — replaceable events would otherwise let a stale local tree overwrite a newer remote
1368
+ * document. Debounced publishes are per wallet; a generation counter cancels in-flight write-backs
1369
+ * on lock.
663
1370
  */
664
1371
  export declare class MetaSyncController extends Controller<MetaSyncState> {
665
1372
  private readonly options;
@@ -670,6 +1377,7 @@ export declare class MetaSyncController extends Controller<MetaSyncState> {
670
1377
  private readonly timers;
671
1378
  private readonly inflight;
672
1379
  private accountsUnsubscribe;
1380
+ private addressBookUnsubscribe;
673
1381
  constructor(options: MetaSyncControllerOptions);
674
1382
  /** Unlocked: pull every wallet whose group-0 identity exists, then watch the tree. */
675
1383
  init(): Promise<void>;
@@ -681,10 +1389,10 @@ export declare class MetaSyncController extends Controller<MetaSyncState> {
681
1389
  /** Manual re-pull of every wallet that can sync. */
682
1390
  sync(): Promise<void>;
683
1391
  private pullAll;
684
- private pubkeyFor;
685
- private signerFor;
686
1392
  private setStatus;
687
1393
  private pullOne;
1394
+ /** The wallet's tree plus the shared address book — every wallet's document carries both. */
1395
+ private exportDoc;
688
1396
  private filterFor;
689
1397
  private acceptableEvent;
690
1398
  /** Snapshot the wallet's exported doc — the debounce publishes only on a real change. */
@@ -692,245 +1400,6 @@ export declare class MetaSyncController extends Controller<MetaSyncState> {
692
1400
  private onTreeChange;
693
1401
  private publishNow;
694
1402
  }
695
- //#endregion
696
- //#region src/payments/parse.d.ts
697
- type PaymentOption = {
698
- readonly kind: "spark";
699
- readonly address: string;
700
- } | {
701
- readonly kind: "spark-invoice";
702
- readonly invoice: string;
703
- readonly amountSats?: number;
704
- readonly memo?: string;
705
- readonly expiresAt?: number;
706
- } | {
707
- readonly kind: "lightning";
708
- readonly invoice: string;
709
- readonly amountSats?: number;
710
- readonly paymentHash: string;
711
- readonly description?: string;
712
- readonly expiresAt: number;
713
- } | {
714
- readonly kind: "lnurl-pay";
715
- readonly request: LnurlPayRequest;
716
- readonly address?: string;
717
- } | {
718
- readonly kind: "lnurl-withdraw";
719
- readonly request: LnurlWithdrawRequest;
720
- } | {
721
- readonly kind: "bitcoin";
722
- readonly address: string;
723
- };
724
- type PaymentRequest = {
725
- readonly options: ReadonlyArray<PaymentOption>;
726
- readonly amountSats?: number;
727
- readonly label?: string;
728
- readonly message?: string;
729
- };
730
- type PaymentParse = {
731
- readonly kind: "request";
732
- readonly request: PaymentRequest;
733
- } | {
734
- readonly kind: "lnurl";
735
- readonly url: string;
736
- readonly address?: string;
737
- } | {
738
- readonly kind: "unsupported";
739
- readonly format: "bolt12" | "silent-payment" | "lnurl-auth" | "spark-token-invoice" | "nostr-wallet-connect";
740
- } | {
741
- readonly kind: "invalid";
742
- readonly reason: "network" | "expired" | "amount-mismatch" | "malformed";
743
- } | {
744
- readonly kind: "unrecognized";
745
- };
746
- type ParseOptions = {
747
- readonly network: SparkNetwork;
748
- readonly now: number;
749
- };
750
- /** `input` → PaymentParse. Never throws: every failure is a structured result. */
751
- export declare function parsePayment(input: string, options: ParseOptions): PaymentParse;
752
- //#endregion
753
- //#region src/payments/pay.d.ts
754
- type PayParams = {
755
- readonly account: SparkAccountRef;
756
- readonly option: PaymentOption;
757
- /** Caller-supplied amount — required by open invoices, the `spark` option, and LNURL flows. */
758
- readonly amountSats?: number;
759
- readonly comment?: string;
760
- readonly maxFeeSats?: number;
761
- readonly speed?: "FAST" | "MEDIUM" | "SLOW";
762
- readonly verify: UnlockFactor;
763
- };
764
- type PayResult = {
765
- readonly kind: "spark";
766
- readonly transfer: SparkTransfer;
767
- } | {
768
- readonly kind: "lightning";
769
- readonly payment: LightningPayment;
770
- } | {
771
- readonly kind: "withdraw";
772
- readonly exit: CooperativeExitResult;
773
- } | {
774
- readonly kind: "lnurl-withdraw";
775
- readonly invoice: LightningInvoice;
776
- };
777
- //#endregion
778
- //#region src/payments/zap.d.ts
779
- /** A profile zap names the recipient pubkey; an event zap carries the event it zaps. */
780
- type ZapRecipient = {
781
- readonly pubkey: string;
782
- readonly lightning: string;
783
- } | {
784
- readonly event: Event;
785
- readonly lightning: string;
786
- };
787
- type PrepareZapParams = {
788
- /** Any `NostrSigner` — wallet key, external key, NIP-07/46 — signs the 9734. */
789
- readonly signer: NostrSigner;
790
- readonly recipient: ZapRecipient;
791
- readonly amountSats: number;
792
- readonly comment?: string | undefined;
793
- readonly relays: ReadonlyArray<string>;
794
- readonly network: SparkNetwork;
795
- };
796
- type PrepareZapContext = {
797
- readonly fetch: typeof globalThis.fetch;
798
- readonly now: () => number;
799
- };
800
- /** Everything a payer needs: the signed 9734, the invoice, and the note fields. */
801
- type PreparedZap = {
802
- readonly zapRequest: Event;
803
- readonly invoice: string;
804
- /** Hex payment hash decoded from the invoice. */
805
- readonly paymentHash: string;
806
- /** Millisecond expiry decoded from the invoice. */
807
- readonly expiresAt: number;
808
- readonly amountSats: number;
809
- /** The LNURL service's 9735 signing pubkey. */
810
- readonly receiptPubkey: string;
811
- readonly recipientPubkey: string;
812
- readonly eventId?: string;
813
- readonly comment?: string;
814
- /** The lud16 or LNURL as given. */
815
- readonly lightning: string;
816
- };
817
- type PayZapOptions = {
818
- readonly maxFeeSats: number;
819
- readonly verify: UnlockFactor;
820
- readonly now: () => number;
821
- };
822
- /** Everything up to payment: resolve LNURL, sign the 9734, request the invoice. */
823
- export declare function prepareZap(params: PrepareZapParams, context: PrepareZapContext): Promise<PreparedZap>;
824
- /**
825
- * Pays a prepared zap through the controller's approve-first path. A nearly-expired invoice is
826
- * rejected before approval — the user may have sat on the confirm screen since `prepareZap`.
827
- */
828
- export declare function payZap(spark: SparkController, account: SparkAccountRef, prepared: PreparedZap, options: PayZapOptions): Promise<LightningPayment>;
829
- //#endregion
830
- //#region src/chains/evm.d.ts
831
- type EvmFeeTier = "slow" | "normal" | "fast";
832
- //#endregion
833
- //#region src/chains/solana.d.ts
834
- type SolanaFeeTier = "slow" | "normal" | "fast";
835
- //#endregion
836
- //#region src/transactions/route.d.ts
837
- type SignParams = Parameters<ReturnType<typeof chains>["sign"]>[0];
838
- type AccountRef = SignParams["request"]["account"];
839
- type DisplayIntent = ReturnType<typeof decodeEvm>;
840
- type TxState = "draft" | "signed" | "submitted" | "pending" | "confirmed" | "failed" | "rejected" | "dropped" | "replaced";
841
- type TrackedTransaction = {
842
- readonly id: string;
843
- readonly family: "evm" | "solana" | "bitcoin";
844
- readonly network: string;
845
- readonly account: string;
846
- readonly hash: string | undefined;
847
- readonly state: TxState;
848
- readonly intent: DisplayIntent | undefined;
849
- readonly nonce: number;
850
- /** Solana only — the blockhash expiry that turns an unconfirmed tx `dropped`. */
851
- readonly lastValidBlockHeight?: number;
852
- /** EVM replacement — the earlier transaction this one bumps or cancels. */
853
- readonly replaces?: string;
854
- /** CPFP — the unconfirmed parent this child pushes. */
855
- readonly parentId?: string;
856
- /** Bitcoin build record persisted at send for RBF/CPFP rebuilds. */
857
- readonly btc?: {
858
- readonly inputs: ReadonlyArray<{
859
- readonly txid: string;
860
- readonly vout: number;
861
- readonly value: number;
862
- readonly address: string;
863
- }>;
864
- readonly outputs: ReadonlyArray<{
865
- readonly address: string;
866
- readonly value: number;
867
- readonly own: boolean;
868
- }>;
869
- readonly vsize: number;
870
- readonly fee: number;
871
- readonly feeRate: number;
872
- };
873
- readonly submittedAt: number;
874
- readonly error: string | undefined;
875
- };
876
- type SendParams = {
877
- readonly account: AccountRef;
878
- readonly network: string;
879
- readonly to: string;
880
- readonly amount: bigint;
881
- /** ERC-20 contract — when set, `to`/`amount` are the transfer recipient/units and value is 0. */
882
- readonly token?: string;
883
- readonly fee: EvmFeeTier | SolanaFeeTier;
884
- /** SPL mint decimals — required when `token` is a Solana mint. */
885
- readonly decimals?: number;
886
- readonly verify: SignParams["verify"];
887
- };
888
- type Options = {
889
- readonly wallet: Wallet;
890
- readonly networks: NetworkController;
891
- readonly openStore: () => Promise<EngineStore>;
892
- readonly random: (n: number) => Uint8Array;
893
- readonly setTimer: (ms: number, fn: () => unknown) => () => void;
894
- readonly now: () => number;
895
- readonly logger: EngineLogger;
896
- readonly pollMs?: number;
897
- /** Test seam — defaults to the wallet's real chains proxy. */
898
- readonly chains?: ReturnType<typeof chains>;
899
- };
900
- //#endregion
901
- //#region src/transactions/controller.d.ts
902
- type TransactionControllerState = {
903
- readonly txs: Record<string, TrackedTransaction>;
904
- };
905
- declare class TransactionController extends Controller<TransactionControllerState> {
906
- private readonly options;
907
- private engineStore?;
908
- private readonly timers;
909
- private evmRoute;
910
- private readonly bitcoinRoute;
911
- private locked;
912
- constructor(options: Options);
913
- init(): Promise<void>;
914
- destroy(): Promise<void>;
915
- lock(): void;
916
- private persist;
917
- private transition;
918
- private routeCtx;
919
- send(params: SendParams): Promise<TrackedTransaction>;
920
- /**
921
- * Speed up or cancel a pending/submitted EVM transaction at the same nonce — a fresh signature is
922
- * required and a rejection leaves the original untouched.
923
- */
924
- replace(id: string, mode: "speed-up" | "cancel", verify: SendParams["verify"]): Promise<TrackedTransaction>;
925
- /**
926
- * CPFP — spend an unconfirmed tx's own outputs into a higher-fee child so the package mines.
927
- * Bitcoin only; the child is linked via `parentId`.
928
- */
929
- bumpChild(id: string, targetRate: number, verify: SendParams["verify"]): Promise<TrackedTransaction>;
930
- private track;
931
- }
932
- //#endregion
933
- //#region src/engine.d.ts
934
1403
  type EngineOptions = {
935
1404
  readonly wallet: Wallet;
936
1405
  /** Every Spark account the engine touches lives on this network. */
@@ -942,11 +1411,24 @@ type EngineOptions = {
942
1411
  readonly openStore: () => Promise<EngineStore>;
943
1412
  /** Cloud file transport for the backup controller; absent → `unavailable`. */
944
1413
  readonly cloud?: CloudStore;
945
- /** RPC endpoints per network (CAIP-2), ordered by preference for the viem fallback transport. */
1414
+ /**
1415
+ * Host endpoints per network (CAIP-2), ordered by preference, for all three chain families: EVM
1416
+ * and Solana JSON-RPC URLs, and for Bitcoin the Esplora base URL.
1417
+ */
946
1418
  readonly rpc?: Readonly<Record<string, ReadonlyArray<string>>>;
1419
+ /**
1420
+ * The wallet gateway: tried first for the networks it serves (read at each unlock) unless a
1421
+ * network has custom endpoints. The install id is sent only to the gateway's origin.
1422
+ */
1423
+ readonly gateway?: {
1424
+ readonly url: string;
1425
+ readonly installId: string;
1426
+ };
947
1427
  /** Injected timer for transaction polling; defaults to setTimeout. */
1428
+ /** Development builds: also accept `http://localhost` as a dApp origin. */
1429
+ readonly allowLocalhostOrigins?: boolean;
948
1430
  readonly setTimer?: (ms: number, fn: () => void) => () => void;
949
- /** Kind-30078 wallet-metadata sync; absent → every wallet reports `unavailable`. */
1431
+ /** Kind-30078 wallet-metadata sync; absent → no wallet metadata is synced. */
950
1432
  readonly metaSync?: MetaSyncConfig;
951
1433
  /** Test seam — defaults to the wallet's real spark proxy. */
952
1434
  readonly sparkApis?: SparkControllerOptions["spark"];
@@ -955,52 +1437,50 @@ type WalletEngine = {
955
1437
  readonly networks: NetworkController;
956
1438
  readonly assets: AssetsController;
957
1439
  readonly transactions: TransactionController;
958
- readonly spark: SparkController;
1440
+ readonly requests: RequestController;
1441
+ readonly addressBook: AddressBookController;
1442
+ readonly connections: ConnectionController;
1443
+ readonly eip1193: Eip1193Handler;
1444
+ /** Read and receive surface; every payment goes through `requests`. */
1445
+ readonly spark: Omit<SparkController, "send" | "payInvoice" | "paySparkInvoice" | "withdraw">;
959
1446
  readonly accounts: AccountTreeController;
960
1447
  readonly metaSync: MetaSyncController;
961
1448
  readonly backup: BackupController;
962
1449
  readonly zaps: {
963
1450
  readonly prepare: (params: PrepareZapParams) => Promise<PreparedZap>;
964
- readonly pay: (account: SparkAccountRef, prepared: PreparedZap, options: Omit<PayZapOptions, "now">) => Promise<LightningPayment>;
965
1451
  };
966
1452
  readonly payments: {
967
1453
  readonly parse: (input: string, network: SparkNetwork) => PaymentParse;
968
1454
  readonly resolve: (input: string, network: SparkNetwork) => Promise<PaymentParse>;
969
- readonly pay: (params: PayParams) => Promise<PayResult>;
1455
+ /** Receives funds from an LNURL-withdraw service; needs no verification factor. */
1456
+ readonly withdrawLnurl: (params: WithdrawLnurlParams) => Promise<{
1457
+ readonly kind: "lnurl-withdraw";
1458
+ readonly invoice: LightningInvoice;
1459
+ }>;
970
1460
  };
971
1461
  readonly unsubscribe: () => void;
972
1462
  };
973
1463
  export declare function createWalletEngine(options: EngineOptions): WalletEngine;
974
- //#endregion
975
- //#region src/backup/memory.d.ts
976
1464
  /** In-memory CloudStore for tests and the playground; swap the Map for cross-engine sharing. */
977
1465
  export declare function createMemoryCloudStore(backing?: Map<string, string>): CloudStore;
978
- //#endregion
979
- //#region src/spark/errors.d.ts
980
1466
  /** SparkController error categories — never carry request bytes or secrets. */
981
1467
  type SparkErrorCategory = "network" | "approval-required" | "insufficient-funds" | "rejected" | "internal";
982
1468
  export declare class SparkControllerError extends Error {
983
1469
  readonly category: SparkErrorCategory;
984
1470
  constructor(category: SparkErrorCategory, message: string, cause?: unknown);
985
1471
  }
986
- //#endregion
987
- //#region src/accounts/errors.d.ts
988
1472
  /** Account-tree error categories — messages never carry secrets. */
989
1473
  type AccountTreeErrorCategory = "invalid-input" | "rejected" | "not-found" | "locked";
990
1474
  export declare class AccountTreeError extends Error {
991
1475
  readonly category: AccountTreeErrorCategory;
992
1476
  constructor(category: AccountTreeErrorCategory, message: string, cause?: unknown);
993
1477
  }
994
- //#endregion
995
- //#region src/payments/errors.d.ts
996
1478
  /** Payment layer error categories — messages never carry remote or secret content. */
997
1479
  type PaymentErrorCategory = "invalid-input" | "network" | "rejected";
998
1480
  export declare class PaymentError extends Error {
999
1481
  readonly category: PaymentErrorCategory;
1000
1482
  constructor(category: PaymentErrorCategory, message: string, cause?: unknown);
1001
1483
  }
1002
- //#endregion
1003
- //#region src/payments/resolve.d.ts
1004
1484
  type ResolveOptions = ParseOptions & {
1005
1485
  readonly fetch: typeof globalThis.fetch;
1006
1486
  };
@@ -1010,10 +1490,7 @@ type ResolveOptions = ParseOptions & {
1010
1490
  * failures throw `PaymentError("network")` — remote error bodies never enter the message.
1011
1491
  */
1012
1492
  export declare function resolvePayment(input: string, options: ResolveOptions): Promise<PaymentParse>;
1013
- //#endregion
1014
- //#region src/spark/exit-chain-codec.d.ts
1015
1493
  export declare function encodeExitChain(chain: SparkExitChain): Uint8Array;
1016
1494
  export declare function decodeExitChain(bytes: Uint8Array): SparkExitChain;
1017
- //#endregion
1018
- export type { AccountGroup, AccountTreeState, BackupControllerState, BackupErrorCategory, CloudStore, ControllerOptions, EngineCollection, EngineErrorCode, EngineLogger, EngineOptions, EngineStore, EventBus, MetaDoc, MetaDocGroup, MetaDocWallet, MetaSyncConfig, MetaSyncRelayPort, MetaSyncState, MetaSyncWalletStatus, PayParams, PayResult, PayZapOptions, PaymentNote, PaymentOption, PaymentParse, PaymentRequest, PrepareZapContext, PrepareZapParams, PreparedZap, SparkAccountState, SparkActivity, SparkClientFactory, SparkControllerOptions, SparkControllerState, SparkErrorCategory, WalletEngine, ZapRecipient };
1495
+ export type { AccountGroup, AccountTreeState, AddressBookEntry, AddressBookErrorCategory, AddressBookState, AddressFamily, BackupControllerState, BackupErrorCategory, BrowserOrigin, CloudStore, AccountGroup$1 as ConnectionAccountGroup, ConnectionOrigin, ConnectionScope, ConnectionSession, ConnectionState, ControllerOptions, Eip1193Event, Eip1193Request, EngineCollection, EngineErrorCode, EngineLogger, EngineOptions, EngineStore, EventBus, EvmSignInput, MetaDoc, MetaDocGroup, MetaDocTree, MetaDocWallet, MetaSyncConfig, MetaSyncRelayPort, MetaSyncState, MetaSyncWalletStatus, PayInput, PayParams, PaymentNote, PaymentOption, PaymentParse, PaymentRequest, PendingConnection, PendingRequest, PrepareZapContext, PrepareZapParams, PreparedSparkAction, PreparedZap, RequestCheckCode, RequestControllerState, RequestInput, RequestMethod, RequestOrigin, RequestStage, RiskFinding, SignLogEntry, SignLogOutcome, SignatureResult, SparkAccountState, SparkActivity, SparkClientFactory, SparkControllerOptions, SparkControllerState, SparkDepositInput, SparkDisplay, SparkErrorCategory, SparkExitInput, SparkResult, WalletEngine, WithdrawLnurlParams, ZapRecipient };
1019
1496
  //# sourceMappingURL=index.d.mts.map