@usebutr/core 0.4.1 → 0.5.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.ts CHANGED
@@ -4,12 +4,16 @@
4
4
  * Follows the CAIP-2 chain identifier standard.
5
5
  *
6
6
  * Consumers extend this with app-specific fields (logos, block explorers, etc.)
7
- * via structural typing — butr never inspects beyond these 4 fields.
7
+ * via structural typing; butr never inspects beyond these 4 fields.
8
8
  */
9
9
  type ChainBase = {
10
- /** CAIP-2 identifier, e.g. "eip155:1", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" */id: string; /** Human-readable name, e.g. "Ethereum", "Solana" */
11
- name: string; /** CAIP-2 namespace, e.g. "eip155", "solana" */
12
- namespace: string; /** CAIP-2 reference, e.g. "1", "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" */
10
+ /** CAIP-2 identifier, e.g. "eip155:1", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" */
11
+ id: string;
12
+ /** Human-readable name, e.g. "Ethereum", "Solana" */
13
+ name: string;
14
+ /** CAIP-2 namespace, e.g. "eip155", "solana" */
15
+ namespace: string;
16
+ /** CAIP-2 reference, e.g. "1", "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" */
13
17
  reference: string;
14
18
  };
15
19
  //#endregion
@@ -36,7 +40,7 @@ type StoredPoolEntry = {
36
40
  icon?: string;
37
41
  /** Human-facing wallet name (e.g. "MetaMask") captured at persist
38
42
  * time. Required so the shadow adapter renders the same identity
39
- * the live adapter will — no "metamask" → "MetaMask" swap at the
43
+ * the live adapter will; no "metamask" → "MetaMask" swap at the
40
44
  * hydration boundary. */
41
45
  name: string;
42
46
  };
@@ -58,7 +62,7 @@ type WalletPersistence = {
58
62
  //#endregion
59
63
  //#region src/storage/snapshot.d.ts
60
64
  /**
61
- * Server-safe view of a butr-persisted session — everything you can
65
+ * Server-safe view of a butr-persisted session; everything you can
62
66
  * know about a user's connected wallets from the cookie payload alone,
63
67
  * without instantiating a `Connector`.
64
68
  *
@@ -89,7 +93,7 @@ declare const EMPTY_SNAPSHOT: WalletSnapshot;
89
93
  /**
90
94
  * Parse a cookie source into a server-safe `WalletSnapshot`.
91
95
  *
92
- * Pure, sync, no `document`, no React — runnable in any environment
96
+ * Pure, sync, no `document`, no React; runnable in any environment
93
97
  * (Server Component, route handler, edge middleware, even client
94
98
  * code). Pair with `createCookieStorageDriver({ initialCookies })`
95
99
  * and `<WalletManagerProvider initialSnapshot={…} />` to render a
@@ -100,7 +104,7 @@ declare const EMPTY_SNAPSHOT: WalletSnapshot;
100
104
  * the wallet, switched accounts, or disconnected in another tab, the
101
105
  * client-side hydration will reconcile reality and the live store
102
106
  * will diverge from the snapshot. Treat the snapshot as an
103
- * *optimistic* shell — accurate enough to avoid a paint flicker,
107
+ * *optimistic* shell; accurate enough to avoid a paint flicker,
104
108
  * authoritative only after `useIsHydrated()` is true.
105
109
  *
106
110
  * **Inputs.** Accepts the three shapes Next.js / Express / Hono /
@@ -110,7 +114,7 @@ declare const EMPTY_SNAPSHOT: WalletSnapshot;
110
114
  * - An iterable of `[name, value]` tuples
111
115
  *
112
116
  * Malformed entries are dropped with a `logWarn` (same policy as
113
- * `WalletStorage.getPool`) — a cross-tab corruption shouldn't crash
117
+ * `WalletStorage.getPool`); a cross-tab corruption shouldn't crash
114
118
  * the server render.
115
119
  */
116
120
  declare const readWalletSnapshot: (source: CookieSource, options?: SnapshotOptions) => WalletSnapshot;
@@ -161,7 +165,7 @@ type ConnectionErrorKind = ConnectionError["kind"];
161
165
  * - butr's own `Error("Connection timeout")` (from the 90s connect timeout)
162
166
  * - butr's own `Error("Failed to get account")` (from the connect flow)
163
167
  * - EIP-1193 numeric `code` properties (`4001` → UserRejected,
164
- * `-32002` → RequestPending, `4100`/`4900`/`4901` → NotConnected —
168
+ * `-32002` → RequestPending, `4100`/`4900`/`4901` → NotConnected:
165
169
  * unauthorized / disconnected from all-or-one chains)
166
170
  * - common message substrings: "user rejected" / "user denied",
167
171
  * "locked", "chain", etc.
@@ -184,19 +188,31 @@ type Account = {
184
188
  id: string;
185
189
  walletAddress: string;
186
190
  };
191
+ /**
192
+ * Build butr's `Account` shape from a wallet address and a resolved
193
+ * `ChainBase`. The composite id (`<chain>:<address>`) is what the
194
+ * reducer uses to compare accounts across refreshes, so every adapter
195
+ * must build accounts through this helper (or keep the format
196
+ * byte-identical).
197
+ */
198
+ declare const buildAccount: (address: string, chain: ChainBase) => Account;
187
199
  type Balance = {
188
- /** Token decimals (e.g. 9 for SOL, 18 for ETH) */decimals: number; /** Human-readable string, trimmed of trailing zeros */
189
- formatted: string; /** Token symbol (e.g. "SOL", "ETH") */
190
- symbol: string; /** Raw integer amount */
200
+ /** Token decimals (e.g. 9 for SOL, 18 for ETH) */
201
+ decimals: number;
202
+ /** Human-readable string, trimmed of trailing zeros */
203
+ formatted: string;
204
+ /** Token symbol (e.g. "SOL", "ETH") */
205
+ symbol: string;
206
+ /** Raw integer amount */
191
207
  value: bigint;
192
208
  };
193
209
  /**
194
210
  * Whether a wallet is currently usable from the user's environment.
195
211
  *
196
- * - `installed` — the wallet is available and `connect()` can be called.
197
- * - `loadable` — the wallet's SDK can be loaded on demand (e.g. WalletConnect
212
+ * - `installed`: the wallet is available and `connect()` can be called.
213
+ * - `loadable`: the wallet's SDK can be loaded on demand (e.g. WalletConnect
198
214
  * modal that pops a QR code without requiring a browser extension).
199
- * - `not-installed` — the wallet isn't reachable. Consumers typically render
215
+ * - `not-installed`: the wallet isn't reachable. Consumers typically render
200
216
  * a "download" affordance pointing at `meta.url`.
201
217
  */
202
218
  type WalletAvailability = "installed" | "loadable" | "not-installed";
@@ -215,7 +231,7 @@ type WalletAvailability = "installed" | "loadable" | "not-installed";
215
231
  * - Single-account-exposure wallets (Phantom EVM/SVM, MetaMask Snap):
216
232
  * only the active account is ever in `accounts`; switching swaps it
217
233
  * in place rather than appending.
218
- * Also covers chain switches — the new chain lives inside `account.chain`.
234
+ * Also covers chain switches; the new chain lives inside `account.chain`.
219
235
  * - `disconnected` → `DISCONNECTED` (wallet has gone away externally:
220
236
  * user locked it, removed the extension, etc.).
221
237
  */
@@ -247,15 +263,17 @@ type ConnectorEvent = {
247
263
  * of which chain is passed.
248
264
  */
249
265
  type WalletCapabilities = {
250
- /** `getBalance` returns a real on-chain value (vs. a 0n placeholder). */getBalance: boolean; /** `getTransactionReceipt` returns a real RPC response. */
266
+ /** `getBalance` returns a real on-chain value (vs. a 0n placeholder). */
267
+ getBalance: boolean;
268
+ /** `getTransactionReceipt` returns a real RPC response. */
251
269
  getTransactionReceipt: boolean;
252
- /** Calling `requestAccounts` will actually do something — either
270
+ /** Calling `requestAccounts` will actually do something: either
253
271
  * prompt the user (EIP-2255) or refresh the exposed list. */
254
272
  requestAccounts: boolean;
255
273
  /** `sendTx` / `sendTxToChain` will work. False for SVM wallets that
256
274
  * don't advertise `solana:signAndSendTransaction`. */
257
275
  sendTransaction: boolean;
258
- /** `signIn` works — Sign In With Solana (`solana:signIn`). True only
276
+ /** `signIn` works: Sign In With Solana (`solana:signIn`). True only
259
277
  * for SVM wallets advertising the feature. */
260
278
  signIn: boolean;
261
279
  /** `signMessage` will work. False for SVM wallets that don't
@@ -266,10 +284,11 @@ type WalletCapabilities = {
266
284
  * wallets advertising `solana:signTransaction`; butr ships no RPC so
267
285
  * it can't broadcast the result itself. EVM/hardware adapters leave
268
286
  * this `false` (no `signTransaction` method). */
269
- signTransaction: boolean; /** Wallet emits account/chain change events that butr can bridge. */
287
+ signTransaction: boolean;
288
+ /** Wallet emits account/chain change events that butr can bridge. */
270
289
  subscribe: boolean;
271
- /** `switchAccount` is real. Almost always `false` for auto adapters
272
- * — neither protocol exposes silent account switch. Hand-rolled
290
+ /** `switchAccount` is real. Almost always `false` for auto adapters;
291
+ * neither protocol exposes silent account switch. Hand-rolled
273
292
  * adapters with custom transports may set it `true`. */
274
293
  switchAccount: boolean;
275
294
  /** `switchChain` routes subsequent calls through the new chain. EVM:
@@ -278,12 +297,12 @@ type WalletCapabilities = {
278
297
  switchChain: boolean;
279
298
  };
280
299
  /**
281
- * Orchestration interface — what `butr` actually calls during the
300
+ * Orchestration interface: what `butr` actually calls during the
282
301
  * connect / disconnect / hydrate flow. This is the contract `butr`
283
302
  * cares about; everything else on `WalletAdapter` is consumer-facing.
284
303
  */
285
304
  type Connector<P extends ChainPlatform = ChainPlatform> = {
286
- /** Runtime capability flags — see `WalletCapabilities`. Read these
305
+ /** Runtime capability flags; see `WalletCapabilities`. Read these
287
306
  * to gate UI affordances rather than probing for method existence. */
288
307
  capabilities: WalletCapabilities;
289
308
  /** Discriminant: which chain platform this adapter speaks. Generic
@@ -295,7 +314,7 @@ type Connector<P extends ChainPlatform = ChainPlatform> = {
295
314
  * rejects on user cancellation or other error.
296
315
  *
297
316
  * `opts.silent` requests a non-interactive reconnect to
298
- * already-authorized accounts — butr's mount-time hydration passes it
317
+ * already-authorized accounts; butr's mount-time hydration passes it
299
318
  * so a reload restores wallets without re-prompting (Wallet Standard
300
319
  * `standard:connect`'s `silent` input; the `eth_accounts` read on
301
320
  * EIP-1193). Adapters that can't reconnect without a prompt should
@@ -303,7 +322,8 @@ type Connector<P extends ChainPlatform = ChainPlatform> = {
303
322
  * the rejection as a clean restore failure. */
304
323
  connect: (opts?: {
305
324
  silent?: boolean;
306
- }) => Promise<void>; /** Optional teardown. butr calls this on disconnect, error recovery, and reset. */
325
+ }) => Promise<void>;
326
+ /** Optional teardown. butr calls this on disconnect, error recovery, and reset. */
307
327
  disconnect?: () => Promise<void>;
308
328
  /** Read the currently-active account. butr uses this to populate the pool
309
329
  * after a successful `connect()` and during hydration. */
@@ -316,15 +336,17 @@ type Connector<P extends ChainPlatform = ChainPlatform> = {
316
336
  * auto-discovery (EIP-6963, Wallet Standard) populate this from the
317
337
  * wallet's announced metadata; hand-rolled adapters can leave it
318
338
  * unset and supply icons separately via `ConnectorMeta`. */
319
- icon?: string; /** Stable key: "metamask", "phantom", etc. Pool entries are keyed by this. */
320
- id: string; /** Human name: "MetaMask", "Phantom", etc. UI-facing only. */
339
+ icon?: string;
340
+ /** Stable key: "metamask", "phantom", etc. Pool entries are keyed by this. */
341
+ id: string;
342
+ /** Human name: "MetaMask", "Phantom", etc. UI-facing only. */
321
343
  name: string;
322
344
  /** Optional. Ask the wallet to open its account-selection UI so the
323
345
  * user can expose additional accounts to this app. Implemented on
324
346
  * EIP-6963 wallets via `wallet_requestPermissions`; Wallet Standard
325
347
  * wallets generally leave this unset because the user enables more
326
348
  * accounts directly in the extension. Resolution doesn't include the
327
- * new accounts — call `getAccounts()` (or use butr's
349
+ * new accounts; call `getAccounts()` (or use butr's
328
350
  * `useRequestAccounts` hook, which refreshes the pool entry for
329
351
  * you). */
330
352
  requestAccounts?: () => Promise<void>;
@@ -348,8 +370,9 @@ type WalletBase = {
348
370
  getBalance: (mint?: string) => Promise<Balance>;
349
371
  /** Returns a chain-specific signer. Consumers cast to the concrete
350
372
  * type via the `SignerForPlatform` registry (or directly to the
351
- * library shape they wrap — `WalletClient` on viem, etc.). */
352
- getSigner: () => Promise<unknown>; /** Look up the status of a previously-submitted transaction. */
373
+ * library shape they wrap: `WalletClient` on viem, etc.). */
374
+ getSigner: () => Promise<unknown>;
375
+ /** Look up the status of a previously-submitted transaction. */
353
376
  getTransactionReceipt: (tx: string) => Promise<{
354
377
  status: "Success" | "Error" | "Pending";
355
378
  }>;
@@ -382,21 +405,22 @@ type WalletBase = {
382
405
  }>;
383
406
  /** Switch to a different account on the same wallet (some wallets
384
407
  * expose multiple accounts simultaneously). */
385
- switchAccount?: (address: string) => Promise<void>; /** Switch the wallet's active chain. */
408
+ switchAccount?: (address: string) => Promise<void>;
409
+ /** Switch the wallet's active chain. */
386
410
  switchChain: (chain: ChainBase) => Promise<void>;
387
411
  };
388
412
  /**
389
413
  * EVM wallet surface. No `signIn` (Sign-In-With-Ethereum is an app-level
390
- * concern in this library, not a protocol method). No `signTransaction`
391
- * — EVM wallets sign-and-send via `eth_sendTransaction`; sign-only EVM
414
+ * concern in this library, not a protocol method). No `signTransaction`:
415
+ * EVM wallets sign-and-send via `eth_sendTransaction`; sign-only EVM
392
416
  * flows aren't exposed through this surface.
393
417
  */
394
418
  type EvmWallet = WalletBase;
395
419
  /**
396
420
  * Solana wallet surface. Adds:
397
- * - `signIn` — Sign-In-With-Solana (`solana:signIn`). Optional;
421
+ * - `signIn`: Sign-In-With-Solana (`solana:signIn`). Optional;
398
422
  * `capabilities.signIn` gates availability at runtime.
399
- * - `signTransaction` — sign-only path for wallets that advertise
423
+ * - `signTransaction`: sign-only path for wallets that advertise
400
424
  * `solana:signTransaction` but not `solana:signAndSendTransaction`.
401
425
  * Optional; `capabilities.signTransaction` gates availability.
402
426
  */
@@ -404,7 +428,7 @@ type SvmWallet = WalletBase & {
404
428
  /** Sign In With Solana (SIWS, `solana:signIn`). Authenticates the user
405
429
  * and returns the connected account plus the signed statement so the
406
430
  * consumer can verify server-side. `input` is the SIWS message fields
407
- * (domain, statement, nonce, …) — pass `{}` or omit for wallet
431
+ * (domain, statement, nonce, …); pass `{}` or omit for wallet
408
432
  * defaults. */
409
433
  signIn?: (input?: Record<string, unknown>) => Promise<{
410
434
  account: Account;
@@ -437,12 +461,12 @@ type BitcoinWallet = WalletBase & {
437
461
  * Polkadot/Substrate wallet surface. No standalone `signTransaction`:
438
462
  * building an extrinsic needs chain metadata (an RPC round-trip butr
439
463
  * doesn't ship), so transaction signing happens through the
440
- * `getSigner()` handoff — the consumer builds and submits with the
464
+ * `getSigner()` handoff; the consumer builds and submits with the
441
465
  * wallet's signer (e.g. polkadot-api). Message signing works via the
442
466
  * injected `signer.signRaw`. Same shape as `EvmWallet`.
443
467
  */
444
468
  type PolkadotWallet = WalletBase;
445
- /** Per-platform full adapter shapes — `Connector` + the platform's
469
+ /** Per-platform full adapter shapes: `Connector` + the platform's
446
470
  * `Wallet` surface. These are the discriminated-union variants of
447
471
  * `WalletAdapter`. */
448
472
  type EvmAdapter = Connector<"evm"> & EvmWallet;
@@ -451,12 +475,12 @@ type SuiAdapter = Connector<"sui"> & SuiWallet;
451
475
  type BitcoinAdapter = Connector<"bitcoin"> & BitcoinWallet;
452
476
  type PolkadotAdapter = Connector<"polkadot"> & PolkadotWallet;
453
477
  /**
454
- * Full adapter interface — discriminated union by `chainPlatform`.
478
+ * Full adapter interface; discriminated union by `chainPlatform`.
455
479
  *
456
480
  * Narrow on `wallet.connector.chainPlatform === "svm"` (etc.) to gain
457
481
  * access to platform-specific methods like `signIn` (SVM) or
458
482
  * `signTransaction` (SVM / Sui / Bitcoin). Calling those methods on a
459
- * non-narrowed `WalletAdapter` is a TypeScript error — that's the
483
+ * non-narrowed `WalletAdapter` is a TypeScript error; that's the
460
484
  * point. The discriminant carries the type-level fact "this method
461
485
  * doesn't exist on EVM" so consumers can't accidentally branch on
462
486
  * `capabilities.signIn` and call a method that EVM adapters don't
@@ -473,7 +497,8 @@ type WalletAdapter = EvmAdapter | SvmAdapter | SuiAdapter | BitcoinAdapter | Pol
473
497
  * `WalletBase`. Retained as an alias so existing names resolve. */
474
498
  type Wallet = WalletBase;
475
499
  type ConnectedWallet = {
476
- /** Currently-active account on this wallet. */account: Account;
500
+ /** Currently-active account on this wallet. */
501
+ account: Account;
477
502
  /** All accounts known on this wallet at the time of connect/refresh.
478
503
  * Always contains at least `account`. Populated from `getAccounts()`
479
504
  * if the connector implements it; otherwise `[account]`. */
@@ -485,7 +510,8 @@ type ConnectorMeta = {
485
510
  * available. Defaults to `"installed"` when omitted. Consumers call
486
511
  * this at render time to gate the "Connect" button. */
487
512
  availability?: () => WalletAvailability;
488
- chainPlatform: ChainPlatform; /** Optional image URL or data URI for wallet selection UIs. */
513
+ chainPlatform: ChainPlatform;
514
+ /** Optional image URL or data URI for wallet selection UIs. */
489
515
  icon?: string;
490
516
  id: string;
491
517
  name: string;
@@ -497,15 +523,15 @@ type ConnectorMeta = {
497
523
  * Outcome of butr's mount-time hydration pass. Passed to
498
524
  * `WalletManagerConfig.onHydrated`. Three buckets:
499
525
  *
500
- * - `restoredIds` — wallets that came back fully. Their pool entries
526
+ * - `restoredIds`: wallets that came back fully. Their pool entries
501
527
  * are live and consumers can use them immediately.
502
- * - `pendingIds` — wallets whose adapter wasn't registered yet
528
+ * - `pendingIds`: wallets whose adapter wasn't registered yet
503
529
  * (auto-discovery's async warmup). The runtime retries each one
504
530
  * when discovery announces a matching id, so most of these will
505
531
  * restore within a few hundred ms of mount.
506
- * - `dropped` — wallets whose restore actually failed (connector
532
+ * - `dropped`: wallets whose restore actually failed (connector
507
533
  * threw mid-flight). These have been removed from storage; consumer
508
- * UX can surface "Couldn't reconnect Phantom — connect again."
534
+ * UX can surface "Couldn't reconnect Phantom; connect again."
509
535
  */
510
536
  type HydrationOutcome = {
511
537
  dropped: Array<{
@@ -516,10 +542,12 @@ type HydrationOutcome = {
516
542
  restoredIds: Array<string>;
517
543
  };
518
544
  type WalletManagerConfig = {
519
- /** Available connector metadata */connectors: Array<ConnectorMeta>; /** Function to instantiate a connector by ID */
545
+ /** Available connector metadata */
546
+ connectors: Array<ConnectorMeta>;
547
+ /** Function to instantiate a connector by ID */
520
548
  createConnector: (id: string) => WalletAdapter | null;
521
549
  /**
522
- * Seed the store synchronously with persisted wallet state — typically
550
+ * Seed the store synchronously with persisted wallet state; typically
523
551
  * the return value of `readWalletSnapshot(cookies, { keyPrefix })`
524
552
  * called from a Server Component. When provided:
525
553
  * - `pool` is populated with `ConnectedWallet` entries whose
@@ -536,9 +564,10 @@ type WalletManagerConfig = {
536
564
  * Pre-hydration UI renders from the snapshot's data (address,
537
565
  * accounts, chain, name, icon) without a flash. Action affordances
538
566
  * (sign, send) are naturally gated by the shadow's all-false
539
- * capabilities — or consumers can branch on `reconnectingIds`.
567
+ * capabilities, or consumers can branch on `reconnectingIds`.
540
568
  */
541
- initialState?: WalletSnapshot; /** Called after a wallet is successfully connected */
569
+ initialState?: WalletSnapshot;
570
+ /** Called after a wallet is successfully connected */
542
571
  onConnect?: (wallet: ConnectedWallet) => void;
543
572
  /**
544
573
  * Called after a connection attempt fails (user rejected, wallet
@@ -548,22 +577,24 @@ type WalletManagerConfig = {
548
577
  * (Sentry, OTel) without each consumer wiring `try/catch`s around
549
578
  * `connectWallet` themselves.
550
579
  */
551
- onConnectError?: (error: ConnectionError, connectorId: string) => void; /** Called after a wallet is disconnected */
580
+ onConnectError?: (error: ConnectionError, connectorId: string) => void;
581
+ /** Called after a wallet is disconnected */
552
582
  onDisconnect?: (chainPlatform: ChainPlatform) => void;
553
583
  /**
554
584
  * Called once after butr's mount-time hydration finishes. Receives a
555
585
  * `HydrationOutcome` summarising which stored wallets were restored,
556
586
  * which are pending an adapter announcement, and which failed.
557
- * Useful for surfacing "Phantom couldn't be reconnected — try
587
+ * Useful for surfacing "Phantom couldn't be reconnected; try
558
588
  * again" UX or piping a metric to telemetry.
559
589
  */
560
- onHydrated?: (outcome: HydrationOutcome) => void; /** Called after all wallets are reset (e.g., to clear auth tokens) */
590
+ onHydrated?: (outcome: HydrationOutcome) => void;
591
+ /** Called after all wallets are reset (e.g., to clear auth tokens) */
561
592
  onReset?: () => void | Promise<void>;
562
593
  /**
563
594
  * Called when a connect attempt takes longer than
564
595
  * `slowConnectThresholdMs` (default 5_000) but hasn't yet resolved
565
596
  * or rejected. Fires at most once per connect attempt. Useful for
566
- * surfacing a "still trying — check your wallet" hint in the UI or
597
+ * surfacing a "still trying, check your wallet" hint in the UI or
567
598
  * piping a slow-path metric to telemetry.
568
599
  */
569
600
  onSlowConnect?: (connectorId: string) => void;
@@ -571,14 +602,17 @@ type WalletManagerConfig = {
571
602
  * Called when a storage write fails. butr's persistence layer is
572
603
  * fire-and-forget by design (any individual write can fail without
573
604
  * breaking butr's reducer state), but the consumer might still want
574
- * to know — quota-exceeded errors, IndexedDB shutdown, cross-tab
605
+ * to know; quota-exceeded errors, IndexedDB shutdown, cross-tab
575
606
  * conflicts, cookie size limits. `context` is a short string
576
607
  * describing which write failed (e.g. `"failed to persist pool"`).
577
608
  * The default behaviour when no callback is set is `console.warn`.
578
609
  */
579
- onStorageError?: (error: unknown, context: string) => void; /** Threshold for `onSlowConnect`, in milliseconds. Defaults to 5_000. */
580
- slowConnectThresholdMs?: number; /** Optional custom persistence implementation (e.g., cookie-backed) */
581
- storage?: WalletPersistence; /** Storage key prefix for localStorage */
610
+ onStorageError?: (error: unknown, context: string) => void;
611
+ /** Threshold for `onSlowConnect`, in milliseconds. Defaults to 5_000. */
612
+ slowConnectThresholdMs?: number;
613
+ /** Optional custom persistence implementation (e.g., cookie-backed) */
614
+ storage?: WalletPersistence;
615
+ /** Storage key prefix for localStorage */
582
616
  storageKeyPrefix?: string;
583
617
  };
584
618
  //#endregion
@@ -588,7 +622,7 @@ type WalletManagerConfig = {
588
622
  * to expose to its UI (chain switcher, picker, etc).
589
623
  *
590
624
  * The platform key set is fixed by `ChainPlatform`. The value is the
591
- * chain list — empty when the consumer doesn't want to support that
625
+ * chain list; empty when the consumer doesn't want to support that
592
626
  * platform's chains in this view. This is the only type that callers
593
627
  * write down; the values come from per-platform packages
594
628
  * (`EVM_CHAINS_LIST`, `SVM_CHAINS_LIST`, etc).
@@ -598,20 +632,20 @@ type ChainsByPlatform = Readonly<Record<ChainPlatform, ReadonlyArray<ChainBase>>
598
632
  * Build a fully-populated `ChainsByPlatform` from a partial. Platforms
599
633
  * the consumer doesn't specify default to an empty list.
600
634
  *
601
- * Use this in apps that target one or two chain platforms — importing
635
+ * Use this in apps that target one or two chain platforms; importing
602
636
  * only those packages keeps unused chain registries out of the bundle.
603
637
  * Apps that want every chain reach for `CHAINS_BY_PLATFORM` from
604
638
  * `@usebutr/wallets` instead.
605
639
  *
606
640
  * @example
607
- * // EVM-only app — Solana/Sui/Bitcoin tables never enter the bundle
641
+ * // EVM-only app: Solana/Sui/Bitcoin tables never enter the bundle
608
642
  * import { EVM_CHAINS_LIST } from "@usebutr/evm";
609
643
  * import { buildChainsByPlatform } from "@usebutr/core";
610
644
  *
611
645
  * const chains = buildChainsByPlatform({ evm: EVM_CHAINS_LIST });
612
646
  *
613
647
  * @example
614
- * // Multi-chain app — pull from each package the app actually uses
648
+ * // Multi-chain app: pull from each package the app actually uses
615
649
  * import { EVM_CHAINS_LIST } from "@usebutr/evm";
616
650
  * import { SVM_CHAINS_LIST } from "@usebutr/svm";
617
651
  *
@@ -629,16 +663,16 @@ declare const buildChainsByPlatform: (partial: Partial<ChainsByPlatform>) => Cha
629
663
  * Each platform package (`@usebutr/evm`, `@usebutr/svm`, `@usebutr/sui`,
630
664
  * `@usebutr/bitcoin`) exports one of these. The aggregator package
631
665
  * (`@usebutr/wallets`) composes them into `autoDiscovery()` without
632
- * needing to know per-platform defaults — the descriptor owns them.
666
+ * needing to know per-platform defaults; the descriptor owns them.
633
667
  *
634
668
  * Adding a new chain platform means writing a `PlatformDiscoverer`
635
669
  * inside the new package and adding one import to the aggregator's
636
670
  * registry. The aggregator's logic doesn't need to grow.
637
671
  *
638
672
  * Two parts:
639
- * - `subscribe` — the primary discovery channel (EIP-6963 / Wallet
673
+ * - `subscribe`: the primary discovery channel (EIP-6963 / Wallet
640
674
  * Standard / etc).
641
- * - `fallback` — optional. The legacy-injected channel that should
675
+ * - `fallback`: optional. The legacy-injected channel that should
642
676
  * only emit if the primary channel hasn't produced an adapter for
643
677
  * the same browser session by the settle deadline. `@usebutr/evm`
644
678
  * has one (window.ethereum); `@usebutr/bitcoin` has one
@@ -655,8 +689,10 @@ type PlatformDiscoverer = {
655
689
  subscribe: (onAdapter: (adapter: WalletAdapter) => void, opts: {
656
690
  hasAnyPrimaryAdapter: () => boolean;
657
691
  }) => () => void;
658
- }; /** Stable platform identifier. Used by the aggregator for keying. */
659
- platform: ChainPlatform; /** Primary discovery subscription. */
692
+ };
693
+ /** Stable platform identifier. Used by the aggregator for keying. */
694
+ platform: ChainPlatform;
695
+ /** Primary discovery subscription. */
660
696
  subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void;
661
697
  };
662
698
  //#endregion
@@ -665,7 +701,7 @@ type PlatformDiscoverer = {
665
701
  * Per-platform signer type registry.
666
702
  *
667
703
  * `Connector.getSigner()` returns `Promise<unknown>` because the actual
668
- * signer shape lives in the platform-specific packages — `@usebutr/evm`
704
+ * signer shape lives in the platform-specific packages: `@usebutr/evm`
669
705
  * returns an EIP-1193 provider, `@usebutr/svm` returns a Wallet
670
706
  * Standard wallet, `@usebutr/sui` returns the same Wallet Standard
671
707
  * wallet narrowed to Sui features, `@usebutr/bitcoin` returns either a
@@ -674,7 +710,7 @@ type PlatformDiscoverer = {
674
710
  *
675
711
  * Consumers cast the `unknown` to whichever signer their integration
676
712
  * library expects. This registry exists so the cast target is sourced
677
- * from one place — when a platform package renames its signer type,
713
+ * from one place; when a platform package renames its signer type,
678
714
  * consumer code keeps working through the registry without an explicit
679
715
  * patch.
680
716
  *
@@ -702,8 +738,8 @@ type PlatformDiscoverer = {
702
738
  * const signer = (await wallet.connector.getSigner()) as SignerForPlatform["evm"];
703
739
  * ```
704
740
  *
705
- * The cast is still there — `getSigner` itself stays type-erased to
706
- * keep the cross-package boundary loose — but the cast target lives in
741
+ * The cast is still there: `getSigner` itself stays type-erased to
742
+ * keep the cross-package boundary loose, but the cast target lives in
707
743
  * one canonical place.
708
744
  *
709
745
  * **Why not make `getSigner` generic.** Generic-on-platform `getSigner`
@@ -739,7 +775,7 @@ declare const createWalletSource: (subscribe: (onAdapter: (adapter: WalletAdapte
739
775
  /**
740
776
  * Public connection-status enum. `state.connectionStatus` in the
741
777
  * reducer only takes the first four values ("idle" | "connecting" |
742
- * "success" | "error") — those track the user-initiated connect
778
+ * "success" | "error"); those track the user-initiated connect
743
779
  * attempt. The fifth value, `"reconnecting"`, is *derived* by
744
780
  * `useConnectionStatus` when the active wallet is still backed by a
745
781
  * shadow adapter (its id is in `state.reconnectingIds`). The
@@ -758,7 +794,7 @@ type State = {
758
794
  pool: Map<string, ConnectedWallet>;
759
795
  /**
760
796
  * Connector IDs whose pool entry is currently backed by a shadow
761
- * adapter — created from `WalletManagerConfig.initialState` and
797
+ * adapter: created from `WalletManagerConfig.initialState` and
762
798
  * waiting for the live adapter to be announced (via discovery) and
763
799
  * the silent reconnect to succeed. The set shrinks as entries
764
800
  * upgrade (`HYDRATED` / `CONNECT_SUCCEEDED` clear matching ids) or
@@ -768,14 +804,51 @@ type State = {
768
804
  reconnectingIds: ReadonlySet<string>;
769
805
  selection: Map<ChainPlatform, string>;
770
806
  };
807
+ //#endregion
808
+ //#region src/store/shadow-adapter.d.ts
809
+ /**
810
+ * Error thrown when a method is called on a shadow adapter; the
811
+ * placeholder `WalletAdapter` that the store seeds into the pool when
812
+ * an `initialState` is provided (e.g. from a server-rendered cookie
813
+ * snapshot). Shadow adapters carry the identity and account data of a
814
+ * previously-connected wallet, but the live wallet extension hasn't
815
+ * been verified yet; the silent reconnect happens asynchronously
816
+ * after mount.
817
+ *
818
+ * UI code that gates affordances on `wallet.connector.capabilities.*`
819
+ * never reaches a shadow method (capabilities are all `false`). Code
820
+ * that calls through anyway hits this typed error, which is the
821
+ * correct loud failure: the consumer ignored the capability gate.
822
+ *
823
+ * Consumers wanting to wait out the reconnecting window should branch
824
+ * on whether `connectorId` is in `state.reconnectingIds`.
825
+ */
826
+ declare class ShadowConnectorError extends Error {
827
+ readonly code = "BUTR_RECONNECTING";
828
+ readonly connectorId: string;
829
+ readonly method: string;
830
+ constructor(method: string, connectorId: string);
831
+ }
771
832
  /**
772
- * Lifecycle events: hydration, full reset, and the explicit
773
- * user-disconnected flag the runtime sets to suppress eager reconnect.
833
+ * Type guard. Returns true when an adapter is a placeholder created
834
+ * by `createShadowAdapter`. Useful for the hydration coordinator
835
+ * (which needs to know which pool entries still need upgrading) and
836
+ * for consumers writing wagmi-style "is this connection verified yet"
837
+ * checks without subscribing to `reconnectingIds` directly.
838
+ *
839
+ * Detection is structural: a shadow has all capabilities set to false.
840
+ * Live adapters always advertise at least one capability (every wallet
841
+ * surface includes `getBalance`, `signMessage`, `switchChain` as
842
+ * required methods, and adapter constructors set their flags
843
+ * accordingly).
774
844
  */
845
+ declare const isShadowAdapter: (adapter: WalletAdapter) => boolean;
775
846
  //#endregion
776
847
  //#region src/storage/browser-storage-driver.d.ts
777
848
  type BrowserStorageDrivers = {
778
- /** Survives app restart (localStorage on web, AsyncStorage/MMKV on RN). */persistent: StorageDriver; /** Cleared when the app session ends (sessionStorage on web, in-memory on RN). */
849
+ /** Survives app restart (localStorage on web, AsyncStorage/MMKV on RN). */
850
+ persistent: StorageDriver;
851
+ /** Cleared when the app session ends (sessionStorage on web, in-memory on RN). */
779
852
  session: StorageDriver;
780
853
  };
781
854
  declare const createMemoryStorageDriver: () => StorageDriver;
@@ -796,20 +869,20 @@ type CookieDriverOptions = {
796
869
  */
797
870
  domain?: string;
798
871
  /**
799
- * Snapshot of cookies for the SSR pass — typically the result of
872
+ * Snapshot of cookies for the SSR pass; typically the result of
800
873
  * Next.js' `cookies()` (from `next/headers`) or a parsed
801
874
  * `req.headers.cookie`. Used only when `document` is unavailable.
802
875
  *
803
876
  * When provided, `getItem` reads from this snapshot during the
804
877
  * server render so the store sees the same persisted state the
805
878
  * client will see after hydration. Writes remain no-ops on the
806
- * server — emitting `Set-Cookie` has to be done by the framework
879
+ * server: emitting `Set-Cookie` has to be done by the framework
807
880
  * layer that owns the response.
808
881
  */
809
882
  initialCookies?: InitialCookies;
810
883
  /**
811
884
  * Lifetime in seconds. Defaults to 30 days. Pass `undefined`
812
- * (the default) for "until the session ends" — but note that
885
+ * (the default) for "until the session ends", but note that
813
886
  * butr persists wallet state across sessions, so a finite
814
887
  * max-age is usually what consumers want.
815
888
  */
@@ -819,7 +892,7 @@ type CookieDriverOptions = {
819
892
  */
820
893
  path?: string;
821
894
  /**
822
- * `SameSite` attribute. Defaults to `"lax"` — restrictive enough
895
+ * `SameSite` attribute. Defaults to `"lax"`; restrictive enough
823
896
  * to avoid CSRF on cross-origin POSTs, permissive enough for
824
897
  * top-level navigations.
825
898
  */
@@ -831,7 +904,7 @@ type CookieDriverOptions = {
831
904
  secure?: boolean;
832
905
  };
833
906
  /**
834
- * Cookie-backed storage driver. Reads/writes `document.cookie` —
907
+ * Cookie-backed storage driver. Reads/writes `document.cookie`;
835
908
  * server-readable, survives reloads, scoped per `domain`/`path`.
836
909
  *
837
910
  * **When to use this:** SSR apps that need to know who's connected
@@ -845,37 +918,39 @@ type CookieDriverOptions = {
845
918
  * **Trade-offs vs `localStorage`:** cookies travel with every
846
919
  * request, so they cost bytes on the wire. Keep the storage key
847
920
  * prefix short, and prefer this driver for the `persistent` slot
848
- * only — the `session` slot can stay in `sessionStorage` (which
921
+ * only: the `session` slot can stay in `sessionStorage` (which
849
922
  * cookies can't natively model anyway).
850
923
  *
851
924
  * **Server-side writes are no-ops.** Emitting `Set-Cookie` requires
852
925
  * access to the framework's response object, which a storage driver
853
926
  * shouldn't reach into. The store doesn't mutate persisted state
854
- * during the SSR pass anyway — writes only fire after client mount,
927
+ * during the SSR pass anyway; writes only fire after client mount,
855
928
  * once `document.cookie` is reachable.
856
929
  */
857
930
  declare const createCookieStorageDriver: (options?: CookieDriverOptions) => StorageDriver;
858
931
  //#endregion
859
932
  //#region src/storage/wallet-storage.d.ts
860
933
  type StorageConfig = {
861
- keyPrefix: string; /** Survives app restart. Defaults to localStorage on web. */
862
- persistent?: StorageDriver; /** Cleared on session end. Defaults to sessionStorage on web. */
934
+ keyPrefix: string;
935
+ /** Survives app restart. Defaults to localStorage on web. */
936
+ persistent?: StorageDriver;
937
+ /** Cleared on session end. Defaults to sessionStorage on web. */
863
938
  session?: StorageDriver;
864
939
  };
865
940
  declare class WalletStorage implements WalletPersistence {
866
- private poolKey;
867
- private selectionKey;
868
- private activeKey;
869
- private userDisconnectedKey;
870
- private persistent;
871
- private session;
941
+ private readonly poolKey;
942
+ private readonly selectionKey;
943
+ private readonly activeKey;
944
+ private readonly userDisconnectedKey;
945
+ private readonly persistent;
946
+ private readonly session;
872
947
  /**
873
948
  * Serializes pool-key mutations so concurrent fire-and-forget
874
949
  * writes can't interleave their read-modify-write phases. Without
875
950
  * this, two simultaneous `setPool` calls both read the pre-write
876
951
  * state, each merge their own entries, and whichever finishes last
877
952
  * overwrites the other's additions. Reads (`getPool`) don't enter
878
- * the queue — they observe whatever's currently in the driver.
953
+ * the queue; they observe whatever's currently in the driver.
879
954
  */
880
955
  private poolMutationQueue;
881
956
  constructor(config: StorageConfig);
@@ -886,7 +961,7 @@ declare class WalletStorage implements WalletPersistence {
886
961
  * Upsert the in-memory pool into storage. Additive: entries in
887
962
  * `pool` are written; entries already in storage that aren't in
888
963
  * `pool` are kept. The in-memory pool reflects "what's live right
889
- * now", not "the complete list of remembered connections" — a
964
+ * now", not "the complete list of remembered connections"; a
890
965
  * silent reconnect that fails on reload leaves the entry out of
891
966
  * the pool but the saved entry stays so the next load can retry.
892
967
  * Use `removePoolEntry` for explicit eviction (the user clicked
@@ -941,13 +1016,19 @@ declare const createWalletStore: (config: WalletManagerConfig) => import("zustan
941
1016
  //#region src/wallet-equal.d.ts
942
1017
  /**
943
1018
  * Two wallet snapshots are equivalent for selector purposes iff they
944
- * share connectorId, active account address, and active account chain
945
- * id. Used by `useStoreWithEqualityFn` consumers (active-wallet,
946
- * selected-wallet, useWalletEntry) to suppress spurious re-renders
947
- * when the underlying Map churns but the resolved entry hasn't changed.
1019
+ * share the same adapter instance, active account address, and active
1020
+ * account chain id. Used by `useStoreWithEqualityFn` consumers
1021
+ * (active-wallet, selected-wallet, useWalletEntry) to suppress spurious
1022
+ * re-renders when the underlying Map churns but the resolved entry
1023
+ * hasn't changed.
1024
+ *
1025
+ * The adapter is compared by reference, not by `connector.id`: hydration
1026
+ * replaces a shadow adapter with the live one under an unchanged id and
1027
+ * address, so an id comparison reports "equal" and leaves consumers
1028
+ * holding a placeholder whose every method throws ShadowConnectorError.
948
1029
  *
949
- * Hoisted to its own module so the equivalence rule lives in one place
950
- * — if we ever extend the snapshot (e.g. to consider `accounts.length`),
1030
+ * Hoisted to its own module so the equivalence rule lives in one place;
1031
+ * if we ever extend the snapshot (e.g. to consider `accounts.length`),
951
1032
  * every selector hook picks up the new rule for free.
952
1033
  */
953
1034
  declare const walletEqual: (a: ConnectedWallet | undefined, b: ConnectedWallet | undefined) => boolean;
@@ -960,7 +1041,7 @@ declare const logError: (...args: ReadonlyArray<unknown>) => void;
960
1041
  /**
961
1042
  * Normalize a wallet-announced icon string.
962
1043
  *
963
- * Wallets announce their icon through external metadata — EIP-6963
1044
+ * Wallets announce their icon through external metadata; EIP-6963
964
1045
  * `providerInfo.icon`, Wallet Standard `wallet.icon`. That value is
965
1046
  * not under butr's control, and some wallets ship data-URI icons with
966
1047
  * surrounding whitespace (a newline left over from a pretty-printed
@@ -969,7 +1050,7 @@ declare const logError: (...args: ReadonlyArray<unknown>) => void;
969
1050
  *
970
1051
  * Trims surrounding whitespace and treats an all-whitespace (or empty)
971
1052
  * icon as absent, so consumers get either a usable string or
972
- * `undefined` — never a blank or malformed one. `undefined` passes
1053
+ * `undefined`: never a blank or malformed one. `undefined` passes
973
1054
  * through untouched.
974
1055
  */
975
1056
  declare const sanitizeIcon: (icon: string | undefined) => string | undefined;
@@ -991,7 +1072,7 @@ declare const sanitizeIcon: (icon: string | undefined) => string | undefined;
991
1072
  * - {@link bytesToHexPrefixed} returns `0x`-prefixed hex (EVM, Polkadot).
992
1073
  * - {@link hexToBytes} tolerantly strips an optional `0x` (all callers).
993
1074
  */
994
- /** Bare lowercase hex — no `0x` prefix (Bitcoin, Ledger). */
1075
+ /** Bare lowercase hex, no `0x` prefix (Bitcoin, Ledger). */
995
1076
  declare const bytesToHex: (bytes: Uint8Array) => string;
996
1077
  /** `0x`-prefixed lowercase hex (EVM, Polkadot). */
997
1078
  declare const bytesToHexPrefixed: (bytes: Uint8Array) => string;
@@ -1009,5 +1090,5 @@ declare const base64ToBytes: (b64: string) => Uint8Array;
1009
1090
  /** Cross-platform `Uint8Array` → base64. */
1010
1091
  declare const bytesToBase64: (bytes: Uint8Array) => string;
1011
1092
  //#endregion
1012
- export { type Account, type Balance, type BitcoinAdapter, type BitcoinWallet, type BrowserStorageDrivers, CHAIN_PLATFORMS, type ChainBase, type ChainPlatform, type ChainsByPlatform, type ConnectedWallet, type ConnectionError, type ConnectionErrorKind, type ConnectionStatus, type Connector, type ConnectorEvent, type ConnectorMeta, type CookieDriverOptions, type CookieSource, EMPTY_SNAPSHOT, type EvmAdapter, type EvmWallet, type HydrationOutcome, type InitialCookies, type MaybePromise, type PlatformDiscoverer, type PolkadotAdapter, type PolkadotWallet, type SignerForPlatform, type SignerOf, type SnapshotOptions, type StorageDriver, type StoredPoolEntry, type StoredPoolRecord, type StoredSelectionRecord, type SuiAdapter, type SuiWallet, type SvmAdapter, type SvmWallet, type Wallet, type WalletAdapter, type WalletAvailability, type WalletBase, type WalletCapabilities, type WalletManagerConfig, type WalletPersistence, type WalletSnapshot, type WalletSource, WalletStorage, type WalletStore, type WalletStoreState, base64ToBytes, buildChainsByPlatform, bytesToBase64, bytesToHex, bytesToHexPrefixed, createBrowserStorageDriver, createCookieStorageDriver, createMemoryStorageDriver, createWalletSource, createWalletStore, hexToBytes, logError, logWarn, mapConnectionError, readWalletSnapshot, sanitizeIcon, walletEqual };
1093
+ export { type Account, type Balance, type BitcoinAdapter, type BitcoinWallet, type BrowserStorageDrivers, CHAIN_PLATFORMS, type ChainBase, type ChainPlatform, type ChainsByPlatform, type ConnectedWallet, type ConnectionError, type ConnectionErrorKind, type ConnectionStatus, type Connector, type ConnectorEvent, type ConnectorMeta, type CookieDriverOptions, type CookieSource, EMPTY_SNAPSHOT, type EvmAdapter, type EvmWallet, type HydrationOutcome, type InitialCookies, type MaybePromise, type PlatformDiscoverer, type PolkadotAdapter, type PolkadotWallet, ShadowConnectorError, type SignerForPlatform, type SignerOf, type SnapshotOptions, type StorageDriver, type StoredPoolEntry, type StoredPoolRecord, type StoredSelectionRecord, type SuiAdapter, type SuiWallet, type SvmAdapter, type SvmWallet, type Wallet, type WalletAdapter, type WalletAvailability, type WalletBase, type WalletCapabilities, type WalletManagerConfig, type WalletPersistence, type WalletSnapshot, type WalletSource, WalletStorage, type WalletStore, type WalletStoreState, base64ToBytes, buildAccount, buildChainsByPlatform, bytesToBase64, bytesToHex, bytesToHexPrefixed, createBrowserStorageDriver, createCookieStorageDriver, createMemoryStorageDriver, createWalletSource, createWalletStore, hexToBytes, isShadowAdapter, logError, logWarn, mapConnectionError, readWalletSnapshot, sanitizeIcon, walletEqual };
1013
1094
  //# sourceMappingURL=index.d.ts.map