@usebutr/core 1.1.0 → 2.0.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
@@ -1,10 +1,7 @@
1
1
  //#region src/types/chain.d.ts
2
2
  /**
3
- * Minimal chain shape that butr needs to function.
4
- * Follows the CAIP-2 chain identifier standard.
5
- *
6
- * Consumers extend this with app-specific fields (logos, block explorers, etc.)
7
- * via structural typing; butr never inspects beyond these 4 fields.
3
+ * CAIP-2 shaped. butr never reads past these four fields, so consumers
4
+ * can extend the type structurally with logos, explorers and the like.
8
5
  */
9
6
  type ChainBase = {
10
7
  /** CAIP-2 identifier, e.g. "eip155:1", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" */
@@ -24,11 +21,9 @@ type Account = {
24
21
  walletAddress: string;
25
22
  };
26
23
  /**
27
- * Build butr's `Account` shape from a wallet address and a resolved
28
- * `ChainBase`. The composite id (`<chain>:<address>`) is what the
29
- * reducer uses to compare accounts across refreshes, so every adapter
30
- * must build accounts through this helper (or keep the format
31
- * byte-identical).
24
+ * The reducer compares accounts by the composite `<chain>:<address>`
25
+ * id, so every adapter must build accounts here or reproduce that
26
+ * format byte for byte.
32
27
  */
33
28
  declare const buildAccount: (address: string, chain: ChainBase) => Account;
34
29
  type Balance = {
@@ -44,24 +39,9 @@ type Balance = {
44
39
  //#endregion
45
40
  //#region src/types/capabilities.d.ts
46
41
  /**
47
- * Capability flags describing what an adapter can actually do at
48
- * runtime. Populated by each adapter (the auto-built ones derive
49
- * these from the underlying protocol's feature advertisements;
50
- * hand-rolled adapters declare them explicitly). Consumers branch on
51
- * these to gate UI affordances:
52
- *
53
- * ```tsx
54
- * {wallet.connector.capabilities.requestAccounts ? (
55
- * <button onClick={() => requestAccounts(wallet.connector.id)}>
56
- * Request more accounts
57
- * </button>
58
- * ) : null}
59
- * ```
60
- *
61
- * Each flag means "can this work right now," not "is the method
62
- * defined": `signMessage: false` means calling `signMessage()` would
63
- * reject; `switchChain: false` means switching is a no-op regardless
64
- * of which chain is passed.
42
+ * Each flag means "can this work right now", not "is the method
43
+ * defined": `signMessage: false` means the call would reject,
44
+ * `switchChain: false` means switching is a no-op for every chain.
65
45
  */
66
46
  type WalletCapabilities = {
67
47
  /** `getBalance` returns a real on-chain value (vs. a 0n placeholder). */
@@ -100,84 +80,38 @@ type WalletCapabilities = {
100
80
  //#endregion
101
81
  //#region src/types/platform.d.ts
102
82
  /**
103
- * Canonical list of supported chain platforms. The single runtime source
104
- * of truth: `ChainPlatform` is derived from it, and storage validators
105
- * build their allowlists from it, so the type and the runtime checks can
106
- * never drift (a missing platform here was why Polkadot connections failed
107
- * to persist).
83
+ * Single runtime source of truth: `ChainPlatform` and the storage
84
+ * validators' allowlists both derive from it. Omitting a platform here
85
+ * silently stops its connections from persisting.
108
86
  */
109
87
  declare const CHAIN_PLATFORMS: readonly ["evm", "svm", "sui", "bitcoin", "polkadot"];
110
88
  type ChainPlatform = (typeof CHAIN_PLATFORMS)[number];
111
89
  //#endregion
112
90
  //#region src/types/chains-by-platform.d.ts
113
91
  /**
114
- * Map of every chain platform to the list of chains the consumer wants
115
- * to expose to its UI (chain switcher, picker, etc).
116
- *
117
- * The platform key set is fixed by `ChainPlatform`. The value is the
118
- * chain list; empty when the consumer doesn't want to support that
119
- * platform's chains in this view. This is the only type that callers
120
- * write down; the values come from per-platform packages
121
- * (`EVM_CHAINS_LIST`, `SVM_CHAINS_LIST`, etc).
92
+ * Values come from the per-platform packages (`EVM_CHAINS_LIST`,
93
+ * `SVM_CHAINS_LIST`, …); an empty list opts that platform out of the
94
+ * consumer's chain UI.
122
95
  */
123
96
  type ChainsByPlatform = Readonly<Record<ChainPlatform, ReadonlyArray<ChainBase>>>;
124
97
  /**
125
- * Build a fully-populated `ChainsByPlatform` from a partial. Platforms
126
- * the consumer doesn't specify default to an empty list.
127
- *
128
- * Use this in apps that target one or two chain platforms; importing
129
- * only those packages keeps unused chain registries out of the bundle.
130
- * Apps that want every chain reach for `CHAINS_BY_PLATFORM` from
131
- * `@usebutr/wallets` instead.
132
- *
133
- * @example
134
- * // EVM-only app: Solana/Sui/Bitcoin tables never enter the bundle
135
- * import { EVM_CHAINS_LIST } from "@usebutr/evm";
136
- * import { buildChainsByPlatform } from "@usebutr/core";
137
- *
138
- * const chains = buildChainsByPlatform({ evm: EVM_CHAINS_LIST });
139
- *
140
- * @example
141
- * // Multi-chain app: pull from each package the app actually uses
142
- * import { EVM_CHAINS_LIST } from "@usebutr/evm";
143
- * import { SVM_CHAINS_LIST } from "@usebutr/svm";
144
- *
145
- * const chains = buildChainsByPlatform({
146
- * evm: EVM_CHAINS_LIST,
147
- * svm: SVM_CHAINS_LIST,
148
- * });
98
+ * Naming only the platforms an app targets keeps the other packages'
99
+ * chain registries out of its bundle. Apps that want all of them import
100
+ * `CHAINS_BY_PLATFORM` from `@usebutr/wallets`.
149
101
  */
150
102
  declare const buildChainsByPlatform: (partial: Partial<ChainsByPlatform>) => ChainsByPlatform;
151
103
  //#endregion
152
104
  //#region src/types/connector.d.ts
153
105
  /**
154
- * Whether a wallet is currently usable from the user's environment.
155
- *
156
- * - `installed`: the wallet is available and `connect()` can be called.
157
- * - `loadable`: the wallet's SDK can be loaded on demand (e.g. WalletConnect
158
- * modal that pops a QR code without requiring a browser extension).
159
- * - `not-installed`: the wallet isn't reachable. Consumers typically render
160
- * a "download" affordance pointing at `meta.url`.
106
+ * Gate Connect on `installed` or `loadable`: a `loadable` wallet has no
107
+ * extension yet still connects (WalletConnect's QR modal). Only
108
+ * `not-installed` should degrade to a download link at `meta.url`.
161
109
  */
162
110
  type WalletAvailability = "installed" | "loadable" | "not-installed";
163
111
  /**
164
- * Events a connector can emit while connected. butr's runtime subscribes
165
- * via `Connector.subscribe?` after a successful `connect()` and dispatches
166
- * the equivalent reducer event:
167
- *
168
- * - `accountChanged` carries both the new active `account` AND the full
169
- * `accounts` array the wallet currently exposes. The runtime mirrors
170
- * that list verbatim into the pool entry. This handles two cases
171
- * uniformly:
172
- * - Multi-account wallets (MetaMask, Rabby, Brave): the user adds or
173
- * removes accounts from the dapp's permission set; the array grows
174
- * or shrinks to match.
175
- * - Single-account-exposure wallets (Phantom EVM/SVM, MetaMask Snap):
176
- * only the active account is ever in `accounts`; switching swaps it
177
- * in place rather than appending.
178
- * Also covers chain switches; the new chain lives inside `account.chain`.
179
- * - `disconnected` → `DISCONNECTED` (wallet has gone away externally:
180
- * user locked it, removed the extension, etc.).
112
+ * `accountChanged` must carry every account the wallet still exposes,
113
+ * not just the new active one: the runtime mirrors the array verbatim
114
+ * into the pool entry. Chain switches arrive as `account.chain`.
181
115
  */
182
116
  type ConnectorEvent = {
183
117
  account: Account;
@@ -200,16 +134,10 @@ type Connector<P extends ChainPlatform = ChainPlatform> = {
200
134
  * use one of the per-platform adapter types (`EvmAdapter`,
201
135
  * `SvmAdapter`, etc). */
202
136
  chainPlatform: P;
203
- /** Begin a connection request. Resolves when the wallet is connected,
204
- * rejects on user cancellation or other error.
205
- *
206
- * `opts.silent` requests a non-interactive reconnect to
207
- * already-authorized accounts; butr's mount-time hydration passes it
208
- * so a reload restores wallets without re-prompting (Wallet Standard
209
- * `standard:connect`'s `silent` input; the `eth_accounts` read on
210
- * EIP-1193). Adapters that can't reconnect without a prompt should
211
- * reject when `silent` is set rather than show UI; hydration treats
212
- * the rejection as a clean restore failure. */
137
+ /** `opts.silent` is hydration's non-interactive reconnect (Wallet
138
+ * Standard `standard:connect` silent input, `eth_accounts` on
139
+ * EIP-1193). An adapter that cannot honour it must reject instead of
140
+ * prompting; hydration reads that as a clean restore failure. */
213
141
  connect: (opts?: {
214
142
  silent?: boolean;
215
143
  }) => Promise<void>;
@@ -222,38 +150,23 @@ type Connector<P extends ChainPlatform = ChainPlatform> = {
222
150
  * show many accounts at once (MetaMask with multiple imports). If
223
151
  * omitted, butr defaults to `[await getAccount()]`. */
224
152
  getAccounts?: () => Promise<Array<Account>>;
225
- /** Optional. Wallet logo as a URL or data URI. Adapters built via butr's
226
- * auto-discovery (EIP-6963, Wallet Standard) populate this from the
227
- * wallet's announced metadata; hand-rolled adapters can leave it
228
- * unset and supply icons separately via `ConnectorMeta`.
229
- *
230
- * **Already sanitized on discovered adapters.** Discovery runs
231
- * `sanitizeIcon` at construction, so the value is either a trimmed,
232
- * non-empty string or `undefined`: never blank, never whitespace-led.
233
- * Render it directly, including into strict consumers like
234
- * `next/image`. No second `sanitizeIcon` call and no `icon !== ""`
235
- * guard is needed. Hand-rolled adapters set this field themselves and
236
- * own the guarantee. */
153
+ /** Discovery runs `sanitizeIcon` at construction, so on discovered
154
+ * adapters this is a trimmed non-empty string or `undefined`: render
155
+ * it straight into `next/image` with no re-sanitizing and no
156
+ * `icon !== ""` guard. Hand-rolled adapters own that guarantee. */
237
157
  icon?: string;
238
158
  /** Stable key: "metamask", "phantom", etc. Pool entries are keyed by this. */
239
159
  id: string;
240
160
  /** Human name: "MetaMask", "Phantom", etc. UI-facing only. */
241
161
  name: string;
242
- /** Optional. Ask the wallet to open its account-selection UI so the
243
- * user can expose additional accounts to this app. Implemented on
244
- * EIP-6963 wallets via `wallet_requestPermissions`; Wallet Standard
245
- * wallets generally leave this unset because the user enables more
246
- * accounts directly in the extension. Resolution doesn't include the
247
- * new accounts; call `getAccounts()` (or use butr's
248
- * `useRequestAccounts` hook, which refreshes the pool entry for
249
- * you). */
162
+ /** Opens the wallet's account-selection UI (`wallet_requestPermissions`
163
+ * on EIP-6963; usually unset on Wallet Standard). Resolution does not
164
+ * carry the new accounts: follow it with `getAccounts()`, or use the
165
+ * `useRequestAccounts` hook which refreshes the pool entry. */
250
166
  requestAccounts?: () => Promise<void>;
251
- /** Optional. Subscribe to wallet-side events (account swap, network swap,
252
- * external disconnect). butr's runtime calls this after a successful
253
- * `connect()`, and uses the returned function to unsubscribe on
254
- * disconnect / reset. Bridges native wallet events into the reducer so
255
- * consumers don't have to wire `accountsChanged` / `chainChanged`
256
- * themselves. */
167
+ /** Bridges native wallet events (`accountsChanged`, `chainChanged`, …)
168
+ * into the reducer. Only `ConnectorLifecycle` may call this, and it
169
+ * guarantees at most one live subscription per connector. */
257
170
  subscribe?: (listener: (event: ConnectorEvent) => void) => () => void;
258
171
  };
259
172
  type ConnectorMeta = {
@@ -293,12 +206,9 @@ type WalletBase = {
293
206
  getTransactionReceipt: (tx: string) => Promise<{
294
207
  status: "Success" | "Error" | "Pending";
295
208
  }>;
296
- /** Submit a transaction on the wallet's currently-active chain.
297
- * Pass an `account` from `ConnectedWallet.accounts` to route the
298
- * transaction through a specific exposed address instead of the
299
- * wallet's currently-active one. EVM wallets honour this via
300
- * `tx.from`; Wallet Standard wallets via the feature's `account`
301
- * input. Omit for "use whichever the wallet picks." */
209
+ /** `account` routes through a specific exposed address (EVM via
210
+ * `tx.from`, Wallet Standard via the feature's `account` input);
211
+ * omitting it lets the wallet pick. */
302
212
  sendTx: (tx: unknown, account?: Account) => Promise<string>;
303
213
  /** Submit a transaction targeting a specific chain. The optional
304
214
  * callback fires after the connector has switched chain (consumers
@@ -306,15 +216,9 @@ type WalletBase = {
306
216
  * specific exposed address (see `sendTx`). */
307
217
  sendTxToChain: (tx: unknown, targetChainId: string, account?: Account, cb?: () => void) => Promise<string>;
308
218
  /**
309
- * Sign a message and return both the signature and the bytes the wallet
310
- * actually signed. Solana Wallet Standard wallets may prefix or re-encode
311
- * the message internally; verifiers must check the signature against
312
- * `signedMessage`, not the input bytes. EVM wallets echo the input.
313
- *
314
- * Pass an `account` to sign with a specific exposed address. EIP-1193
315
- * routes it through `personal_sign`'s address param; Wallet Standard
316
- * uses the feature's `account` input. Both support per-call signing
317
- * without changing the wallet's active account.
219
+ * Verify against `signedMessage`, not the input: Solana Wallet
220
+ * Standard wallets may prefix or re-encode it. `account` signs with a
221
+ * specific address without changing the wallet's active one.
318
222
  */
319
223
  signMessage: (msg: Uint8Array, account?: Account) => Promise<{
320
224
  signature: Uint8Array;
@@ -327,19 +231,15 @@ type WalletBase = {
327
231
  switchChain: (chain: ChainBase) => Promise<void>;
328
232
  };
329
233
  /**
330
- * EVM wallet surface. No `signIn` (Sign-In-With-Ethereum is an app-level
331
- * concern in this library, not a protocol method). No `signTransaction`:
332
- * EVM wallets sign-and-send via `eth_sendTransaction`; sign-only EVM
333
- * flows aren't exposed through this surface.
234
+ * No `signIn` (SIWE is app-level here, not a protocol method) and no
235
+ * `signTransaction`: EVM wallets sign and send in one step through
236
+ * `eth_sendTransaction`.
334
237
  */
335
238
  type EvmWallet = WalletBase;
336
239
  /**
337
- * Solana wallet surface. Adds:
338
- * - `signIn`: Sign-In-With-Solana (`solana:signIn`). Optional;
339
- * `capabilities.signIn` gates availability at runtime.
340
- * - `signTransaction`: sign-only path for wallets that advertise
341
- * `solana:signTransaction` but not `solana:signAndSendTransaction`.
342
- * Optional; `capabilities.signTransaction` gates availability.
240
+ * Both additions are optional at runtime; gate them on
241
+ * `capabilities.signIn` / `capabilities.signTransaction`, which mirror
242
+ * what the wallet actually advertises.
343
243
  */
344
244
  type SvmWallet = WalletBase & {
345
245
  /** Sign In With Solana (SIWS, `solana:signIn`). Authenticates the user
@@ -363,24 +263,27 @@ type SvmWallet = WalletBase & {
363
263
  * consumer via `@mysten/sui`'s SuiClient.
364
264
  */
365
265
  type SuiWallet = WalletBase & {
366
- signTransaction?: (tx: unknown, account?: Account) => Promise<Uint8Array>;
266
+ /** Sign a Sui transaction WITHOUT executing it. Returns BOTH halves the
267
+ * chain requires: `SuiClient.executeTransactionBlock` needs
268
+ * `{ transactionBlock, signature }`, so a bare `Uint8Array` cannot express
269
+ * the result and a consumer holding one cannot tell which half they have. */
270
+ signTransaction?: (tx: unknown, account?: Account) => Promise<{
271
+ bytes: Uint8Array;
272
+ signature: Uint8Array;
273
+ }>;
367
274
  };
368
275
  /**
369
- * Bitcoin wallet surface. `signTransaction` here is `bitcoin:signPsbt`
370
- * (sign-only PSBT path). Consumers pass `psbt.toBuffer()` bytes; the
371
- * wallet returns the signed PSBT bytes for the consumer to finalise /
372
- * broadcast through their own Esplora / Electrum client.
276
+ * `signTransaction` is `bitcoin:signPsbt`: pass `psbt.toBuffer()` bytes
277
+ * and get signed PSBT bytes back, to finalise and broadcast through
278
+ * your own Esplora or Electrum client.
373
279
  */
374
280
  type BitcoinWallet = WalletBase & {
375
281
  signTransaction?: (tx: unknown, account?: Account) => Promise<Uint8Array>;
376
282
  };
377
283
  /**
378
- * Polkadot/Substrate wallet surface. No standalone `signTransaction`:
379
- * building an extrinsic needs chain metadata (an RPC round-trip butr
380
- * doesn't ship), so transaction signing happens through the
381
- * `getSigner()` handoff; the consumer builds and submits with the
382
- * wallet's signer (e.g. polkadot-api). Message signing works via the
383
- * injected `signer.signRaw`. Same shape as `EvmWallet`.
284
+ * No standalone `signTransaction`: building an extrinsic needs chain
285
+ * metadata over RPC, which butr does not ship, so transaction signing
286
+ * goes through the `getSigner()` handoff.
384
287
  */
385
288
  type PolkadotWallet = WalletBase;
386
289
  /** Per-platform full adapter shapes: `Connector` + the platform's
@@ -392,22 +295,9 @@ type SuiAdapter = Connector<"sui"> & SuiWallet;
392
295
  type BitcoinAdapter = Connector<"bitcoin"> & BitcoinWallet;
393
296
  type PolkadotAdapter = Connector<"polkadot"> & PolkadotWallet;
394
297
  /**
395
- * Full adapter interface; discriminated union by `chainPlatform`.
396
- *
397
- * Narrow on `wallet.connector.chainPlatform === "svm"` (etc.) to gain
398
- * access to platform-specific methods like `signIn` (SVM) or
399
- * `signTransaction` (SVM / Sui / Bitcoin). Calling those methods on a
400
- * non-narrowed `WalletAdapter` is a TypeScript error; that's the
401
- * point. The discriminant carries the type-level fact "this method
402
- * doesn't exist on EVM" so consumers can't accidentally branch on
403
- * `capabilities.signIn` and call a method that EVM adapters don't
404
- * implement.
405
- *
406
- * Runtime gating via `capabilities` still matters for the methods that
407
- * are OPTIONAL within a platform (a Solana wallet might or might not
408
- * advertise `solana:signTransaction`). Capabilities narrow "wallet
409
- * supports this feature"; the discriminated union narrows "this
410
- * platform has this concept at all".
298
+ * The union narrows "this platform has the concept at all"; the
299
+ * `capabilities` flags narrow "this wallet supports it right now".
300
+ * Both gates are needed, and neither substitutes for the other.
411
301
  */
412
302
  type WalletAdapter = EvmAdapter | SvmAdapter | SuiAdapter | BitcoinAdapter | PolkadotAdapter;
413
303
  type ConnectedWallet = {
@@ -422,33 +312,15 @@ type ConnectedWallet = {
422
312
  //#endregion
423
313
  //#region src/types/discoverer.d.ts
424
314
  /**
425
- * Self-describing discovery descriptor for a single chain platform.
426
- *
427
- * Each platform package (`@usebutr/evm`, `@usebutr/svm`, `@usebutr/sui`,
428
- * `@usebutr/bitcoin`) exports one of these. The aggregator package
429
- * (`@usebutr/wallets`) composes them into `autoDiscovery()` without
430
- * needing to know per-platform defaults; the descriptor owns them.
431
- *
432
- * Adding a new chain platform means writing a `PlatformDiscoverer`
433
- * inside the new package and adding one import to the aggregator's
434
- * registry, which is keyed by `ChainPlatform`. The aggregator's logic
435
- * doesn't need to grow.
436
- *
437
- * Two parts:
438
- * - `subscribe`: the primary discovery channel (EIP-6963 / Wallet
439
- * Standard / etc).
440
- * - `fallback`: optional. The legacy-injected channel that should
441
- * only emit if the primary channel hasn't produced an adapter for
442
- * the same browser session by the settle deadline. `@usebutr/evm`
443
- * has one (window.ethereum); `@usebutr/bitcoin` has one
444
- * (window.unisat / sats-connect / window.btc); SVM and Sui don't.
315
+ * Each platform package exports exactly one; `@usebutr/wallets` composes
316
+ * them via a registry keyed by `ChainPlatform`, so a new chain adds a
317
+ * descriptor and a registry entry, not aggregator logic.
445
318
  */
446
319
  type PlatformDiscoverer = {
447
320
  /**
448
- * Optional legacy-injected fallback. Subscribes only when consumers
449
- * haven't disabled it. The hook receives `hasAnyPrimaryAdapter` so
450
- * the fallback can defer to standards-based discovery when an adapter
451
- * for the same wallet has already announced through the primary path.
321
+ * Legacy-injected channel (window.ethereum, window.unisat, …). Must
322
+ * consult `hasAnyPrimaryAdapter` and stay quiet when standards-based
323
+ * discovery already announced the same wallet, or it double-lists.
452
324
  */
453
325
  fallback?: {
454
326
  subscribe: (onAdapter: (adapter: WalletAdapter) => void, opts: {
@@ -461,16 +333,9 @@ type PlatformDiscoverer = {
461
333
  //#endregion
462
334
  //#region src/types/errors.d.ts
463
335
  /**
464
- * Tagged union of normalised connection errors.
465
- *
466
- * butr maps thrown values from connectors (which vary across wallet SDKs:
467
- * MetaMask uses EIP-1193 codes, Phantom throws stringly-typed errors,
468
- * embedded SDKs throw their own classes) into a small set of UX-meaningful
469
- * variants. Consumers branch on `kind` instead of regexing message strings.
470
- *
471
- * `message` is always present and human-readable. `cause` preserves the
472
- * original thrown value so callers can inspect raw connector errors when
473
- * the variant is `Unknown`.
336
+ * Wallet SDKs disagree on error shape (EIP-1193 codes, bare strings,
337
+ * bespoke classes), so consumers branch on `kind` rather than regexing
338
+ * messages. `cause` keeps the original value for the `Unknown` case.
474
339
  */
475
340
  type ConnectionError = {
476
341
  kind: "UserRejected";
@@ -499,17 +364,9 @@ type ConnectionError = {
499
364
  };
500
365
  type ConnectionErrorKind = ConnectionError["kind"];
501
366
  /**
502
- * Normalise a thrown value into a `ConnectionError`.
503
- *
504
- * Recognises:
505
- * - butr's own `Error("Connection timeout")` (from the 90s connect timeout)
506
- * - butr's own `Error("Failed to get account")` (from the connect flow)
507
- * - EIP-1193 numeric `code` properties (`4001` → UserRejected,
508
- * `-32002` → RequestPending, `4100`/`4900`/`4901` → NotConnected:
509
- * unauthorized / disconnected from all-or-one chains)
510
- * - common message substrings: "user rejected" / "user denied",
511
- * "locked", "chain", etc.
512
- * - anything else → `Unknown` with `cause` set to the original value.
367
+ * EIP-1193 codes: `4001` rejected, `-32002` pending, `4100`/`4900`/`4901`
368
+ * unauthorized or disconnected. Message-substring matching is the last
369
+ * resort for SDKs that ship no codes at all.
513
370
  */
514
371
  declare const mapConnectionError: (raw: unknown) => ConnectionError;
515
372
  //#endregion
@@ -558,16 +415,9 @@ type WalletPersistence = {
558
415
  //#endregion
559
416
  //#region src/storage/snapshot.d.ts
560
417
  /**
561
- * Server-safe view of a butr-persisted session; everything you can
562
- * know about a user's connected wallets from the cookie payload alone,
563
- * without instantiating a `Connector`.
564
- *
565
- * Notably absent: the `Connector` instance. A wallet extension exists
566
- * only in the browser, so a server render can know *which* wallet was
567
- * connected and *what address* it held, but cannot dispatch
568
- * `signMessage`/`sendTransaction` on it. Splitting display from action
569
- * along this seam keeps the impossibility expressed in the types
570
- * rather than hidden inside a runtime check.
418
+ * Carries no `Connector` by design: a wallet extension exists only in
419
+ * the browser, so a server render can name the wallet and its address
420
+ * but can never dispatch on it.
571
421
  */
572
422
  type WalletSnapshot = {
573
423
  activeConnectorId: string | null;
@@ -587,48 +437,17 @@ type SnapshotOptions = {
587
437
  };
588
438
  declare const EMPTY_SNAPSHOT: WalletSnapshot;
589
439
  /**
590
- * Parse a cookie source into a server-safe `WalletSnapshot`.
591
- *
592
- * Pure, sync, no `document`, no React; runnable in any environment
593
- * (Server Component, route handler, edge middleware, even client
594
- * code). Pair with `createCookieStorageDriver({ initialCookies })`
595
- * and `<WalletManagerProvider initialSnapshot={…} />` to render a
596
- * connected shell server-side without a hydration flash.
597
- *
598
- * **Stale-snapshot semantics.** The snapshot reflects whatever the
599
- * browser most recently persisted. If the user has since uninstalled
600
- * the wallet, switched accounts, or disconnected in another tab, the
601
- * client-side hydration will reconcile reality and the live store
602
- * will diverge from the snapshot. Treat the snapshot as an
603
- * *optimistic* shell; accurate enough to avoid a paint flicker,
604
- * authoritative only after `useIsHydrated()` is true.
605
- *
606
- * **Inputs.** Accepts the three shapes Next.js / Express / Hono /
607
- * generic-Node cookie code naturally produces:
608
- * - A plain object: `{ "butr-pool": "{...}", … }`
609
- * - An array of `{ name, value }` (Next.js' `cookies().getAll()`)
610
- * - An iterable of `[name, value]` tuples
611
- *
612
- * Malformed entries are dropped with a `logWarn` (same policy as
613
- * `WalletStorage.getPool`); a cross-tab corruption shouldn't crash
614
- * the server render.
440
+ * Optimistic: only what the browser last persisted, so an uninstall or
441
+ * other-tab disconnect makes it stale. Authoritative once the entry
442
+ * leaves `reconnectingIds`; `isHydrated` is true from render one.
615
443
  */
616
444
  declare const readWalletSnapshot: (source: CookieSource, options?: SnapshotOptions) => WalletSnapshot;
617
445
  //#endregion
618
446
  //#region src/types/manager.d.ts
619
447
  /**
620
- * Outcome of butr's mount-time hydration pass. Passed to
621
- * `WalletManagerConfig.onHydrated`. Three buckets:
622
- *
623
- * - `restoredIds`: wallets that came back fully. Their pool entries
624
- * are live and consumers can use them immediately.
625
- * - `pendingIds`: wallets whose adapter wasn't registered yet
626
- * (auto-discovery's async warmup). The runtime retries each one
627
- * when discovery announces a matching id, so most of these will
628
- * restore within a few hundred ms of mount.
629
- * - `dropped`: wallets whose restore actually failed (connector
630
- * threw mid-flight). These have been removed from storage; consumer
631
- * UX can surface "Couldn't reconnect Phantom; connect again."
448
+ * `pendingIds` are not failures: discovery announces those adapters
449
+ * asynchronously and the runtime retries them, usually within a few
450
+ * hundred ms. Only `dropped` entries have been removed from storage.
632
451
  */
633
452
  type HydrationOutcome = {
634
453
  dropped: Array<{
@@ -644,65 +463,35 @@ type WalletManagerConfig = {
644
463
  /** Function to instantiate a connector by ID */
645
464
  createConnector: (id: string) => WalletAdapter | null;
646
465
  /**
647
- * Seed the store synchronously with persisted wallet state; typically
648
- * the return value of `readWalletSnapshot(cookies, { keyPrefix })`
649
- * called from a Server Component. When provided:
650
- * - `pool` is populated with `ConnectedWallet` entries whose
651
- * `connector` is a shadow adapter (see `createShadowAdapter`):
652
- * identity-only, all capabilities `false`, methods throw
653
- * `ShadowConnectorError` if called.
654
- * - `activeConnectorId` and `selection` are set from the snapshot.
655
- * - `isHydrated` flips `true` immediately on construction.
656
- * - Every seeded id appears in `reconnectingIds`; the background
657
- * silent-reconnect pass removes the id and replaces the pool
658
- * entry with a live adapter on success, or drops the entry on
659
- * failure.
660
- *
661
- * Pre-hydration UI renders from the snapshot's data (address,
662
- * accounts, chain, name, icon) without a flash. Action affordances
663
- * (sign, send) are naturally gated by the shadow's all-false
664
- * capabilities, or consumers can branch on `reconnectingIds`.
466
+ * Seeds the pool with shadow adapters and flips `isHydrated` true on
467
+ * construction, so identity renders without a flash but every seeded
468
+ * id sits in `reconnectingIds` until silent reconnect verifies it.
665
469
  */
666
470
  initialState?: WalletSnapshot;
667
471
  /** Called after a wallet is successfully connected */
668
472
  onConnect?: (wallet: ConnectedWallet) => void;
669
473
  /**
670
- * Called after a connection attempt fails (user rejected, wallet
671
- * locked, chain mismatch, timeout, …). Receives the normalised
672
- * `ConnectionError` plus the id of the connector that was being
673
- * connected. Useful for piping into observability tooling
674
- * (Sentry, OTel) without each consumer wiring `try/catch`s around
675
- * `connectWallet` themselves.
474
+ * Fires for every failed attempt (rejection, locked wallet, timeout,
475
+ * …), so observability can hook here instead of wrapping every
476
+ * `connectWallet` call.
676
477
  */
677
478
  onConnectError?: (error: ConnectionError, connectorId: string) => void;
678
479
  /** Called after a wallet is disconnected */
679
480
  onDisconnect?: (chainPlatform: ChainPlatform) => void;
680
- /**
681
- * Called once after butr's mount-time hydration finishes. Receives a
682
- * `HydrationOutcome` summarising which stored wallets were restored,
683
- * which are pending an adapter announcement, and which failed.
684
- * Useful for surfacing "Phantom couldn't be reconnected; try
685
- * again" UX or piping a metric to telemetry.
686
- */
481
+ /** Fires once, after the mount-time hydration pass. */
687
482
  onHydrated?: (outcome: HydrationOutcome) => void;
688
483
  /** Called after all wallets are reset (e.g., to clear auth tokens) */
689
484
  onReset?: () => void | Promise<void>;
690
485
  /**
691
- * Called when a connect attempt takes longer than
692
- * `slowConnectThresholdMs` (default 5_000) but hasn't yet resolved
693
- * or rejected. Fires at most once per connect attempt. Useful for
694
- * surfacing a "still trying, check your wallet" hint in the UI or
695
- * piping a slow-path metric to telemetry.
486
+ * Fires at most once per attempt, once it passes
487
+ * `slowConnectThresholdMs` without settling. The attempt keeps
488
+ * running; this is a hint, not a timeout.
696
489
  */
697
490
  onSlowConnect?: (connectorId: string) => void;
698
491
  /**
699
- * Called when a storage write fails. butr's persistence layer is
700
- * fire-and-forget by design (any individual write can fail without
701
- * breaking butr's reducer state), but the consumer might still want
702
- * to know; quota-exceeded errors, IndexedDB shutdown, cross-tab
703
- * conflicts, cookie size limits. `context` is a short string
704
- * describing which write failed (e.g. `"failed to persist pool"`).
705
- * The default behaviour when no callback is set is `console.warn`.
492
+ * Persistence is fire-and-forget: a failed write (quota, cookie size,
493
+ * cross-tab conflict) never breaks reducer state, it only surfaces
494
+ * here. Defaults to `console.warn`.
706
495
  */
707
496
  onStorageError?: (error: unknown, context: string) => void;
708
497
  /** Threshold for `onSlowConnect`, in milliseconds. Defaults to 5_000. */
@@ -715,55 +504,9 @@ type WalletManagerConfig = {
715
504
  //#endregion
716
505
  //#region src/types/signer.d.ts
717
506
  /**
718
- * Per-platform signer type registry.
719
- *
720
- * `Connector.getSigner()` returns `Promise<unknown>` because the actual
721
- * signer shape lives in the platform-specific packages: `@usebutr/evm`
722
- * returns an EIP-1193 provider, `@usebutr/svm` returns a Wallet
723
- * Standard wallet, `@usebutr/sui` returns the same Wallet Standard
724
- * wallet narrowed to Sui features, `@usebutr/bitcoin` returns either a
725
- * Wallet Standard wallet or an injected provider depending on which
726
- * adapter discovered it.
727
- *
728
- * Consumers cast the `unknown` to whichever signer their integration
729
- * library expects. This registry exists so the cast target is sourced
730
- * from one place; when a platform package renames its signer type,
731
- * consumer code keeps working through the registry without an explicit
732
- * patch.
733
- *
734
- * **How to extend.** Each platform package declares a module-augmentation
735
- * block that adds its key to this interface. For example,
736
- * `@usebutr/evm` ships:
737
- *
738
- * ```ts
739
- * declare module "@usebutr/core" {
740
- * interface SignerForPlatform {
741
- * evm: Eip1193Provider;
742
- * }
743
- * }
744
- * ```
745
- *
746
- * Consumers that import the EVM package get the typed entry for free;
747
- * the augmentation is transitive through TypeScript's structural
748
- * declaration merging.
749
- *
750
- * **Usage.**
751
- *
752
- * ```ts
753
- * import type { SignerForPlatform } from "@usebutr/core";
754
- *
755
- * const signer = (await wallet.connector.getSigner()) as SignerForPlatform["evm"];
756
- * ```
757
- *
758
- * The cast is still there: `getSigner` itself stays type-erased to
759
- * keep the cross-package boundary loose, but the cast target lives in
760
- * one canonical place.
761
- *
762
- * **Why not make `getSigner` generic.** Generic-on-platform `getSigner`
763
- * would require `Connector` to be a discriminated union by
764
- * `chainPlatform`, which is a public-API breaking change held for a
765
- * future major release. The registry is the small step you can take
766
- * today; the union is the larger step that makes the cast obsolete.
507
+ * Canonical cast target for `getSigner()`'s `unknown`, which stays
508
+ * type-erased to keep the cross-package boundary loose. Platform
509
+ * packages add their key by augmenting this interface.
767
510
  */
768
511
  interface SignerForPlatform {}
769
512
  /** Convenience alias for narrowing a single platform's signer type. */
@@ -771,34 +514,24 @@ type SignerOf<P extends keyof SignerForPlatform> = SignerForPlatform[P];
771
514
  //#endregion
772
515
  //#region src/wallet-source.d.ts
773
516
  /**
774
- * A discovery seam. Implementations call `onAdapter(adapter)` each time
775
- * they find a wallet and return an unsubscribe handle. `@usebutr/wallets`
776
- * composes EVM + SVM into a single `WalletSource`; third parties can
777
- * implement this type without depending on `@usebutr/wallets`.
517
+ * The discovery seam: implementable by third parties without depending
518
+ * on `@usebutr/wallets`, which itself just composes the per-platform
519
+ * sources into one.
778
520
  */
779
521
  type WalletSource = {
780
522
  subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void;
781
523
  };
782
524
  /**
783
- * Wrap a bare `subscribe` function (the exact shape of
784
- * `discoverEvmAdapters` / `discoverSvmAdapters`) into a `WalletSource`,
785
- * so an EVM-only app can do
786
- * `createWalletSource(discoverEvmAdapters)` without importing anything
787
- * protocol-bearing beyond `@usebutr/evm`.
525
+ * Takes the exact shape of `discoverEvmAdapters` and friends, so a
526
+ * single-platform app keeps `@usebutr/wallets` out of its bundle.
788
527
  */
789
528
  declare const createWalletSource: (subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void) => WalletSource;
790
529
  //#endregion
791
530
  //#region src/store/reducer.d.ts
792
531
  /**
793
- * Public connection-status enum. `state.connectionStatus` in the
794
- * reducer only takes the first four values ("idle" | "connecting" |
795
- * "success" | "error"); those track the user-initiated connect
796
- * attempt. The fifth value, `"reconnecting"`, is *derived* by
797
- * `useConnectionStatus` when the active wallet is still backed by a
798
- * shadow adapter (its id is in `state.reconnectingIds`). The
799
- * derivation lives in the React selector so the reducer can stay a
800
- * narrow state machine while the public API matches wagmi's
801
- * vocabulary.
532
+ * The reducer never writes `"reconnecting"`; `useConnectionStatus`
533
+ * derives it from `reconnectingIds` so the state machine stays narrow
534
+ * while the public vocabulary matches wagmi's.
802
535
  */
803
536
  type ConnectionStatus = "idle" | "connecting" | "success" | "error" | "reconnecting";
804
537
  type State = {
@@ -810,13 +543,9 @@ type State = {
810
543
  isUserDisconnected: boolean;
811
544
  pool: Map<string, ConnectedWallet>;
812
545
  /**
813
- * Connector IDs whose pool entry is currently backed by a shadow
814
- * adapter: created from `WalletManagerConfig.initialState` and
815
- * waiting for the live adapter to be announced (via discovery) and
816
- * the silent reconnect to succeed. The set shrinks as entries
817
- * upgrade (`HYDRATED` / `CONNECT_SUCCEEDED` clear matching ids) or
818
- * drop (`DISCONNECTED` / `RESET`). Empty for stores constructed
819
- * without `initialState`.
546
+ * Ids still backed by a shadow adapter from `initialState`. Empty
547
+ * without `initialState`; shrinks on `HYDRATED` / `CONNECT_SUCCEEDED`
548
+ * and on `DISCONNECTED` / `RESET`.
820
549
  */
821
550
  reconnectingIds: ReadonlySet<string>;
822
551
  selection: Map<ChainPlatform, string>;
@@ -824,21 +553,9 @@ type State = {
824
553
  //#endregion
825
554
  //#region src/store/shadow-adapter.d.ts
826
555
  /**
827
- * Error thrown when a method is called on a shadow adapter; the
828
- * placeholder `WalletAdapter` that the store seeds into the pool when
829
- * an `initialState` is provided (e.g. from a server-rendered cookie
830
- * snapshot). Shadow adapters carry the identity and account data of a
831
- * previously-connected wallet, but the live wallet extension hasn't
832
- * been verified yet; the silent reconnect happens asynchronously
833
- * after mount.
834
- *
835
- * UI code that gates affordances on `wallet.connector.capabilities.*`
836
- * never reaches a shadow method (capabilities are all `false`). Code
837
- * that calls through anyway hits this typed error, which is the
838
- * correct loud failure: the consumer ignored the capability gate.
839
- *
840
- * Consumers wanting to wait out the reconnecting window should branch
841
- * on whether `connectorId` is in `state.reconnectingIds`.
556
+ * Loud failure for consumers that called a wallet method without gating
557
+ * on `capabilities.*` (all `false` here) or `reconnectingIds`, before
558
+ * silent reconnect swapped in the live adapter.
842
559
  */
843
560
  declare class ShadowConnectorError extends Error {
844
561
  readonly code = "BUTR_RECONNECTING";
@@ -847,17 +564,9 @@ declare class ShadowConnectorError extends Error {
847
564
  constructor(method: string, connectorId: string);
848
565
  }
849
566
  /**
850
- * Type guard. Returns true when an adapter is a placeholder created
851
- * by `createShadowAdapter`. Useful for the hydration coordinator
852
- * (which needs to know which pool entries still need upgrading) and
853
- * for consumers writing wagmi-style "is this connection verified yet"
854
- * checks without subscribing to `reconnectingIds` directly.
855
- *
856
- * Detection is structural: a shadow has all capabilities set to false.
857
- * Live adapters always advertise at least one capability (every wallet
858
- * surface includes `getBalance`, `signMessage`, `switchChain` as
859
- * required methods, and adapter constructors set their flags
860
- * accordingly).
567
+ * Structural detection: relies on every live adapter advertising at
568
+ * least one capability, since `getBalance`, `signMessage` and
569
+ * `switchChain` are required on all wallet surfaces.
861
570
  */
862
571
  declare const isShadowAdapter: (adapter: WalletAdapter) => boolean;
863
572
  //#endregion
@@ -886,22 +595,13 @@ type CookieDriverOptions = {
886
595
  */
887
596
  domain?: string;
888
597
  /**
889
- * Snapshot of cookies for the SSR pass; typically the result of
890
- * Next.js' `cookies()` (from `next/headers`) or a parsed
891
- * `req.headers.cookie`. Used only when `document` is unavailable.
892
- *
893
- * When provided, `getItem` reads from this snapshot during the
894
- * server render so the store sees the same persisted state the
895
- * client will see after hydration. Writes remain no-ops on the
896
- * server: emitting `Set-Cookie` has to be done by the framework
897
- * layer that owns the response.
598
+ * Server-side cookie snapshot (Next.js' `cookies()`, a parsed
599
+ * `req.headers.cookie`, …) read only when `document` is unavailable,
600
+ * so the SSR pass sees the state the client will hydrate with.
898
601
  */
899
602
  initialCookies?: InitialCookies;
900
603
  /**
901
- * Lifetime in seconds. Defaults to 30 days. Pass `undefined`
902
- * (the default) for "until the session ends", but note that
903
- * butr persists wallet state across sessions, so a finite
904
- * max-age is usually what consumers want.
604
+ * Lifetime in seconds. Defaults to 30 days.
905
605
  */
906
606
  maxAgeSeconds?: number;
907
607
  /**
@@ -921,34 +621,16 @@ type CookieDriverOptions = {
921
621
  secure?: boolean;
922
622
  };
923
623
  /**
924
- * Cookie-backed storage driver. Reads/writes `document.cookie`;
925
- * server-readable, survives reloads, scoped per `domain`/`path`.
926
- *
927
- * **When to use this:** SSR apps that need to know who's connected
928
- * during the server render (so they can stream the connected-wallet
929
- * UI without a client-side hydration flicker). Pass `initialCookies`
930
- * from a server-side cookie source (e.g. `cookies()` in Next.js'
931
- * `next/headers`, or a parsed `req.headers.cookie`) and the same
932
- * driver will serve those values during the server render and switch
933
- * to `document.cookie` once it mounts in the browser.
934
- *
935
- * **Trade-offs vs `localStorage`:** cookies travel with every
936
- * request, so they cost bytes on the wire. Keep the storage key
937
- * prefix short, and prefer this driver for the `persistent` slot
938
- * only: the `session` slot can stay in `sessionStorage` (which
939
- * cookies can't natively model anyway).
940
- *
941
- * **Server-side writes are no-ops.** Emitting `Set-Cookie` requires
942
- * access to the framework's response object, which a storage driver
943
- * shouldn't reach into. The store doesn't mutate persisted state
944
- * during the SSR pass anyway; writes only fire after client mount,
945
- * once `document.cookie` is reachable.
624
+ * Server-side writes are no-ops: `Set-Cookie` needs the framework's
625
+ * response object. Cookies ride every request, so use this driver for
626
+ * the `persistent` slot only and leave `session` on `sessionStorage`.
946
627
  */
947
628
  declare const createCookieStorageDriver: (options?: CookieDriverOptions) => StorageDriver;
948
629
  //#endregion
949
630
  //#region src/storage/wallet-storage.d.ts
950
631
  type StorageConfig = {
951
- keyPrefix: string;
632
+ /** Defaults to `"butr"`, matching `readWalletSnapshot`. */
633
+ keyPrefix?: string;
952
634
  /** Survives app restart. Defaults to localStorage on web. */
953
635
  persistent?: StorageDriver;
954
636
  /** Cleared on session end. Defaults to sessionStorage on web. */
@@ -962,27 +644,22 @@ declare class WalletStorage implements WalletPersistence {
962
644
  private readonly persistent;
963
645
  private readonly session;
964
646
  /**
965
- * Serializes pool-key mutations so concurrent fire-and-forget
966
- * writes can't interleave their read-modify-write phases. Without
967
- * this, two simultaneous `setPool` calls both read the pre-write
968
- * state, each merge their own entries, and whichever finishes last
969
- * overwrites the other's additions. Reads (`getPool`) don't enter
970
- * the queue; they observe whatever's currently in the driver.
647
+ * Serializes pool read-modify-writes; without it concurrent `setPool`
648
+ * calls drop each other's entries. Not reentrant, so a queued mutation
649
+ * must read via `readPool`: `getPool` re-enters and deadlocks it.
971
650
  */
972
651
  private poolMutationQueue;
973
652
  constructor(config: StorageConfig);
974
653
  /** Chain `fn` after the in-flight pool mutation. */
975
654
  private serializePoolMutation;
655
+ /** Read and decode the pool without touching the mutation queue. Safe to
656
+ * call from inside a queued mutation, unlike `getPool`. */
657
+ private readPool;
976
658
  getPool(): Promise<StoredPoolRecord>;
977
659
  /**
978
- * Upsert the in-memory pool into storage. Additive: entries in
979
- * `pool` are written; entries already in storage that aren't in
980
- * `pool` are kept. The in-memory pool reflects "what's live right
981
- * now", not "the complete list of remembered connections"; a
982
- * silent reconnect that fails on reload leaves the entry out of
983
- * the pool but the saved entry stays so the next load can retry.
984
- * Use `removePoolEntry` for explicit eviction (the user clicked
985
- * Disconnect) and `clearAll` for a full wipe (reset).
660
+ * Additive on purpose: a failed silent reconnect drops the entry from
661
+ * the live pool, and it must survive to be retried next load.
662
+ * Eviction goes through `removePoolEntry` or `clearAll`.
986
663
  */
987
664
  setPool(pool: Map<string, ConnectedWallet>): Promise<void>;
988
665
  removePoolEntry(connectorId: string): Promise<void>;
@@ -993,12 +670,9 @@ declare class WalletStorage implements WalletPersistence {
993
670
  setActiveConnectorId(connectorId: string | null): Promise<void>;
994
671
  clearAll(): Promise<void>;
995
672
  /**
996
- * Disconnect-intent tracking.
997
- *
998
- * Lives in the session driver: survives component remounts (unlike refs)
999
- * but clears when the session ends (unlike the persistent driver).
1000
- * Prevents auto-connect from firing immediately after a manual disconnect,
1001
- * while still allowing auto-connect on fresh sessions.
673
+ * Kept in the session driver so it survives remounts (unlike a ref)
674
+ * yet clears at session end (unlike the persistent driver): a manual
675
+ * disconnect must suppress auto-connect now, not forever.
1002
676
  */
1003
677
  isUserDisconnected(): Promise<boolean>;
1004
678
  markUserDisconnected(value: boolean): Promise<void>;
@@ -1032,38 +706,17 @@ declare const createWalletStore: (config: WalletManagerConfig) => import("zustan
1032
706
  //#endregion
1033
707
  //#region src/group-by-platform.d.ts
1034
708
  /**
1035
- * Bucket a flat list into per-platform groups.
1036
- *
1037
- * A multi-chain wallet announces one adapter per platform it speaks, so
1038
- * discovery hands back a flat list where a single brand appears several
1039
- * times. Any app targeting more than one chain has to bucket that list
1040
- * before rendering, and hand-rolled versions drift: butr's own demos
1041
- * carried a copy that reimplemented `CHAIN_PLATFORMS` as a local
1042
- * ordering constant.
1043
- *
1044
- * Keys are inserted in `CHAIN_PLATFORMS` order and platforms with no
1045
- * members are omitted, so the result is directly iterable for rendering
1046
- * (`[...groups]`) as well as addressable for lookup (`groups.get("evm")`)
1047
- * without a further emptiness filter.
1048
- *
1049
- * `getPlatform` exists because the discriminant sits at a different depth
1050
- * depending on the input: a discovered `WalletAdapter` carries
1051
- * `chainPlatform` at the top level, a pool entry nests it under
1052
- * `connector`. React consumers should reach for
1053
- * `useDiscoveredWalletsByPlatform` / `useConnectedWalletsByPlatform`
1054
- * instead, which bind the accessor for them.
709
+ * A multi-chain wallet announces one adapter per platform, so brands
710
+ * repeat in the flat list. Keys follow `CHAIN_PLATFORMS` order and empty
711
+ * platforms are omitted, so `[...groups]` needs no emptiness filter.
1055
712
  */
1056
713
  declare const groupByPlatform: <T>(items: ReadonlyArray<T>, getPlatform: (item: T) => ChainPlatform) => Map<ChainPlatform, Array<T>>;
1057
714
  //#endregion
1058
715
  //#region src/sign-in/sign-in-flow.d.ts
1059
716
  /**
1060
- * What the wallet produced, encoded for transport. Both byte fields are
1061
- * base64 because that is what survives `JSON.stringify` intact; the raw
1062
- * `Uint8Array`s are kept alongside for callers verifying in-process.
1063
- *
1064
- * Verify against `signedMessage`, not `message`. Solana Wallet Standard
1065
- * wallets may prefix or re-encode what they sign, so the bytes that carry
1066
- * the signature are the wallet's, not yours.
717
+ * Verify against `signedMessage`, not `message`: Solana Wallet Standard
718
+ * wallets may prefix or re-encode what they sign. Base64 mirrors exist
719
+ * because `Uint8Array` does not survive `JSON.stringify`.
1067
720
  */
1068
721
  type SignInResult = {
1069
722
  account: Account;
@@ -1086,15 +739,9 @@ type SignInMessageContext = {
1086
739
  };
1087
740
  type SignInFlowOptions = {
1088
741
  /**
1089
- * Compose the message to sign. Defaults to a plain
1090
- * `<address> signs in. Nonce: <nonce>` line.
1091
- *
1092
- * butr deliberately does not ship a wire format here: a message a
1093
- * server must parse is an authentication spec, and SIWE / SIWS already
1094
- * fill that role. Pass the formatter your backend expects.
1095
- *
1096
- * Unused on the SIWS path, where the wallet composes the message from
1097
- * the input fields.
742
+ * butr ships no wire format: a server-parsed message is an auth spec,
743
+ * and SIWE / SIWS already fill that role. Pass what your backend
744
+ * expects. Unused on the SIWS path, where the wallet composes it.
1098
745
  */
1099
746
  buildMessage?: (ctx: SignInMessageContext) => string;
1100
747
  /** Fetch a single-use nonce from your backend. */
@@ -1119,30 +766,9 @@ declare class SignInUnsupportedError extends Error {
1119
766
  constructor(connectorId: string);
1120
767
  }
1121
768
  /**
1122
- * Build a reusable sign-in flow: nonce, capability gate, signature,
1123
- * encoding, verification.
1124
- *
1125
- * Every wallet-auth app writes this same sequence, and the parts that
1126
- * are easy to get wrong are the ones that aren't about the app: which
1127
- * capability flag gates the attempt, whether a Solana wallet should take
1128
- * the SIWS path instead, and which bytes to base64 for the server (the
1129
- * wallet's `signedMessage`, not the input). This owns those; you own the
1130
- * nonce endpoint, the message format, and the verifier.
1131
- *
1132
- * ```ts
1133
- * const { signIn } = createSignInFlow({
1134
- * getNonce: () => fetch("/api/nonce").then((r) => r.text()),
1135
- * verify: (result) =>
1136
- * fetch("/api/verify", { body: JSON.stringify(result), method: "POST" }).then(() => {}),
1137
- * });
1138
- *
1139
- * await signIn(wallet);
1140
- * ```
1141
- *
1142
- * Solana wallets advertising `solana:signIn` take the SIWS path
1143
- * automatically, so `result.signedMessage` holds the wallet-composed
1144
- * SIWS statement and `result.message` is absent. Set
1145
- * `preferSignMessage` to opt out.
769
+ * Solana wallets advertising `solana:signIn` take the SIWS path, so
770
+ * `result.signedMessage` holds the wallet-composed statement and
771
+ * `result.message` is absent. `preferSignMessage` opts out.
1146
772
  */
1147
773
  declare const createSignInFlow: (options: SignInFlowOptions) => {
1148
774
  signIn: (wallet: ConnectedWallet, account?: Account) => Promise<SignInResult>;
@@ -1150,21 +776,9 @@ declare const createSignInFlow: (options: SignInFlowOptions) => {
1150
776
  //#endregion
1151
777
  //#region src/wallet-equal.d.ts
1152
778
  /**
1153
- * Two wallet snapshots are equivalent for selector purposes iff they
1154
- * share the same adapter instance, active account address, and active
1155
- * account chain id. Used by `useStoreWithEqualityFn` consumers
1156
- * (active-wallet, selected-wallet, useWalletEntry) to suppress spurious
1157
- * re-renders when the underlying Map churns but the resolved entry
1158
- * hasn't changed.
1159
- *
1160
- * The adapter is compared by reference, not by `connector.id`: hydration
1161
- * replaces a shadow adapter with the live one under an unchanged id and
1162
- * address, so an id comparison reports "equal" and leaves consumers
1163
- * holding a placeholder whose every method throws ShadowConnectorError.
1164
- *
1165
- * Hoisted to its own module so the equivalence rule lives in one place;
1166
- * if we ever extend the snapshot (e.g. to consider `accounts.length`),
1167
- * every selector hook picks up the new rule for free.
779
+ * The adapter is compared by reference, not `connector.id`: hydration
780
+ * swaps a shadow adapter for the live one under an unchanged id, and an
781
+ * id check would strand consumers on the throwing placeholder.
1168
782
  */
1169
783
  declare const walletEqual: (a: ConnectedWallet | undefined, b: ConnectedWallet | undefined) => boolean;
1170
784
  //#endregion
@@ -1174,43 +788,17 @@ declare const logError: (...args: ReadonlyArray<unknown>) => void;
1174
788
  //#endregion
1175
789
  //#region src/sanitize-icon.d.ts
1176
790
  /**
1177
- * Normalize a wallet-announced icon string.
1178
- *
1179
- * Wallets announce their icon through external metadata; EIP-6963
1180
- * `providerInfo.icon`, Wallet Standard `wallet.icon`. That value is
1181
- * not under butr's control, and some wallets ship data-URI icons with
1182
- * surrounding whitespace (a newline left over from a pretty-printed
1183
- * manifest). Strict consumers reject it: Next.js's `<Image>` throws
1184
- * because `src` must not start with a control character.
1185
- *
1186
- * Trims surrounding whitespace and treats an all-whitespace (or empty)
1187
- * icon as absent, so consumers get either a usable string or
1188
- * `undefined`: never a blank or malformed one. `undefined` passes
1189
- * through untouched.
1190
- *
1191
- * **Discovery already applies this.** Every adapter butr discovers has
1192
- * had its icon sanitized at construction, so `Connector.icon` is safe to
1193
- * render as-is. Reach for this helper only when building adapter or
1194
- * `ConnectorMeta` metadata yourself from a source butr didn't produce.
791
+ * Some wallets announce data-URI icons wrapped in whitespace, which
792
+ * makes Next.js `<Image>` throw on a leading control character.
793
+ * Discovery already applies this; only hand-built metadata needs it.
1195
794
  */
1196
795
  declare const sanitizeIcon: (icon: string | undefined) => string | undefined;
1197
796
  //#endregion
1198
797
  //#region src/encoding/bytes.d.ts
1199
798
  /**
1200
- * Shared byte-encoding helpers for the connector packages.
1201
- *
1202
- * These functions sit directly on the signing/address path of every
1203
- * chain. They used to be hand-reimplemented in ~10 connector files; a
1204
- * single tested module removes the drift surface (a `padStart` omission
1205
- * or base64 variant mismatch corrupts signatures/addresses for one chain
1206
- * only, and the divergence is invisible because the copies look "the
1207
- * same").
1208
- *
1209
- * Hex prefixing diverges across chains, so the module exposes **explicit
1210
- * variants** rather than one function:
1211
- * - {@link bytesToHex} returns bare hex (Bitcoin, Ledger).
1212
- * - {@link bytesToHexPrefixed} returns `0x`-prefixed hex (EVM, Polkadot).
1213
- * - {@link hexToBytes} tolerantly strips an optional `0x` (all callers).
799
+ * Hex prefixing diverges by chain (bare for Bitcoin and Ledger, `0x` for
800
+ * EVM and Polkadot), so the variants stay explicit: these sit on the
801
+ * signing path, where a silent mismatch corrupts one chain's signatures.
1214
802
  */
1215
803
  /** Bare lowercase hex, no `0x` prefix (Bitcoin, Ledger). */
1216
804
  declare const bytesToHex: (bytes: Uint8Array) => string;