@usebutr/core 1.0.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,30 +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`. */
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. */
229
157
  icon?: string;
230
158
  /** Stable key: "metamask", "phantom", etc. Pool entries are keyed by this. */
231
159
  id: string;
232
160
  /** Human name: "MetaMask", "Phantom", etc. UI-facing only. */
233
161
  name: string;
234
- /** Optional. Ask the wallet to open its account-selection UI so the
235
- * user can expose additional accounts to this app. Implemented on
236
- * EIP-6963 wallets via `wallet_requestPermissions`; Wallet Standard
237
- * wallets generally leave this unset because the user enables more
238
- * accounts directly in the extension. Resolution doesn't include the
239
- * new accounts; call `getAccounts()` (or use butr's
240
- * `useRequestAccounts` hook, which refreshes the pool entry for
241
- * 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. */
242
166
  requestAccounts?: () => Promise<void>;
243
- /** Optional. Subscribe to wallet-side events (account swap, network swap,
244
- * external disconnect). butr's runtime calls this after a successful
245
- * `connect()`, and uses the returned function to unsubscribe on
246
- * disconnect / reset. Bridges native wallet events into the reducer so
247
- * consumers don't have to wire `accountsChanged` / `chainChanged`
248
- * 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. */
249
170
  subscribe?: (listener: (event: ConnectorEvent) => void) => () => void;
250
171
  };
251
172
  type ConnectorMeta = {
@@ -254,7 +175,10 @@ type ConnectorMeta = {
254
175
  * this at render time to gate the "Connect" button. */
255
176
  availability?: () => WalletAvailability;
256
177
  chainPlatform: ChainPlatform;
257
- /** Optional image URL or data URI for wallet selection UIs. */
178
+ /** Optional image URL or data URI for wallet selection UIs. Unlike
179
+ * `Connector.icon`, this one is consumer-supplied and butr does not
180
+ * sanitize it; run it through `sanitizeIcon` if the value came from
181
+ * wallet metadata rather than your own assets. */
258
182
  icon?: string;
259
183
  id: string;
260
184
  name: string;
@@ -282,12 +206,9 @@ type WalletBase = {
282
206
  getTransactionReceipt: (tx: string) => Promise<{
283
207
  status: "Success" | "Error" | "Pending";
284
208
  }>;
285
- /** Submit a transaction on the wallet's currently-active chain.
286
- * Pass an `account` from `ConnectedWallet.accounts` to route the
287
- * transaction through a specific exposed address instead of the
288
- * wallet's currently-active one. EVM wallets honour this via
289
- * `tx.from`; Wallet Standard wallets via the feature's `account`
290
- * 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. */
291
212
  sendTx: (tx: unknown, account?: Account) => Promise<string>;
292
213
  /** Submit a transaction targeting a specific chain. The optional
293
214
  * callback fires after the connector has switched chain (consumers
@@ -295,15 +216,9 @@ type WalletBase = {
295
216
  * specific exposed address (see `sendTx`). */
296
217
  sendTxToChain: (tx: unknown, targetChainId: string, account?: Account, cb?: () => void) => Promise<string>;
297
218
  /**
298
- * Sign a message and return both the signature and the bytes the wallet
299
- * actually signed. Solana Wallet Standard wallets may prefix or re-encode
300
- * the message internally; verifiers must check the signature against
301
- * `signedMessage`, not the input bytes. EVM wallets echo the input.
302
- *
303
- * Pass an `account` to sign with a specific exposed address. EIP-1193
304
- * routes it through `personal_sign`'s address param; Wallet Standard
305
- * uses the feature's `account` input. Both support per-call signing
306
- * 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.
307
222
  */
308
223
  signMessage: (msg: Uint8Array, account?: Account) => Promise<{
309
224
  signature: Uint8Array;
@@ -316,19 +231,15 @@ type WalletBase = {
316
231
  switchChain: (chain: ChainBase) => Promise<void>;
317
232
  };
318
233
  /**
319
- * EVM wallet surface. No `signIn` (Sign-In-With-Ethereum is an app-level
320
- * concern in this library, not a protocol method). No `signTransaction`:
321
- * EVM wallets sign-and-send via `eth_sendTransaction`; sign-only EVM
322
- * 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`.
323
237
  */
324
238
  type EvmWallet = WalletBase;
325
239
  /**
326
- * Solana wallet surface. Adds:
327
- * - `signIn`: Sign-In-With-Solana (`solana:signIn`). Optional;
328
- * `capabilities.signIn` gates availability at runtime.
329
- * - `signTransaction`: sign-only path for wallets that advertise
330
- * `solana:signTransaction` but not `solana:signAndSendTransaction`.
331
- * 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.
332
243
  */
333
244
  type SvmWallet = WalletBase & {
334
245
  /** Sign In With Solana (SIWS, `solana:signIn`). Authenticates the user
@@ -352,24 +263,27 @@ type SvmWallet = WalletBase & {
352
263
  * consumer via `@mysten/sui`'s SuiClient.
353
264
  */
354
265
  type SuiWallet = WalletBase & {
355
- 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
+ }>;
356
274
  };
357
275
  /**
358
- * Bitcoin wallet surface. `signTransaction` here is `bitcoin:signPsbt`
359
- * (sign-only PSBT path). Consumers pass `psbt.toBuffer()` bytes; the
360
- * wallet returns the signed PSBT bytes for the consumer to finalise /
361
- * 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.
362
279
  */
363
280
  type BitcoinWallet = WalletBase & {
364
281
  signTransaction?: (tx: unknown, account?: Account) => Promise<Uint8Array>;
365
282
  };
366
283
  /**
367
- * Polkadot/Substrate wallet surface. No standalone `signTransaction`:
368
- * building an extrinsic needs chain metadata (an RPC round-trip butr
369
- * doesn't ship), so transaction signing happens through the
370
- * `getSigner()` handoff; the consumer builds and submits with the
371
- * wallet's signer (e.g. polkadot-api). Message signing works via the
372
- * 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.
373
287
  */
374
288
  type PolkadotWallet = WalletBase;
375
289
  /** Per-platform full adapter shapes: `Connector` + the platform's
@@ -381,22 +295,9 @@ type SuiAdapter = Connector<"sui"> & SuiWallet;
381
295
  type BitcoinAdapter = Connector<"bitcoin"> & BitcoinWallet;
382
296
  type PolkadotAdapter = Connector<"polkadot"> & PolkadotWallet;
383
297
  /**
384
- * Full adapter interface; discriminated union by `chainPlatform`.
385
- *
386
- * Narrow on `wallet.connector.chainPlatform === "svm"` (etc.) to gain
387
- * access to platform-specific methods like `signIn` (SVM) or
388
- * `signTransaction` (SVM / Sui / Bitcoin). Calling those methods on a
389
- * non-narrowed `WalletAdapter` is a TypeScript error; that's the
390
- * point. The discriminant carries the type-level fact "this method
391
- * doesn't exist on EVM" so consumers can't accidentally branch on
392
- * `capabilities.signIn` and call a method that EVM adapters don't
393
- * implement.
394
- *
395
- * Runtime gating via `capabilities` still matters for the methods that
396
- * are OPTIONAL within a platform (a Solana wallet might or might not
397
- * advertise `solana:signTransaction`). Capabilities narrow "wallet
398
- * supports this feature"; the discriminated union narrows "this
399
- * 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.
400
301
  */
401
302
  type WalletAdapter = EvmAdapter | SvmAdapter | SuiAdapter | BitcoinAdapter | PolkadotAdapter;
402
303
  type ConnectedWallet = {
@@ -411,33 +312,15 @@ type ConnectedWallet = {
411
312
  //#endregion
412
313
  //#region src/types/discoverer.d.ts
413
314
  /**
414
- * Self-describing discovery descriptor for a single chain platform.
415
- *
416
- * Each platform package (`@usebutr/evm`, `@usebutr/svm`, `@usebutr/sui`,
417
- * `@usebutr/bitcoin`) exports one of these. The aggregator package
418
- * (`@usebutr/wallets`) composes them into `autoDiscovery()` without
419
- * needing to know per-platform defaults; the descriptor owns them.
420
- *
421
- * Adding a new chain platform means writing a `PlatformDiscoverer`
422
- * inside the new package and adding one import to the aggregator's
423
- * registry, which is keyed by `ChainPlatform`. The aggregator's logic
424
- * doesn't need to grow.
425
- *
426
- * Two parts:
427
- * - `subscribe`: the primary discovery channel (EIP-6963 / Wallet
428
- * Standard / etc).
429
- * - `fallback`: optional. The legacy-injected channel that should
430
- * only emit if the primary channel hasn't produced an adapter for
431
- * the same browser session by the settle deadline. `@usebutr/evm`
432
- * has one (window.ethereum); `@usebutr/bitcoin` has one
433
- * (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.
434
318
  */
435
319
  type PlatformDiscoverer = {
436
320
  /**
437
- * Optional legacy-injected fallback. Subscribes only when consumers
438
- * haven't disabled it. The hook receives `hasAnyPrimaryAdapter` so
439
- * the fallback can defer to standards-based discovery when an adapter
440
- * 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.
441
324
  */
442
325
  fallback?: {
443
326
  subscribe: (onAdapter: (adapter: WalletAdapter) => void, opts: {
@@ -450,16 +333,9 @@ type PlatformDiscoverer = {
450
333
  //#endregion
451
334
  //#region src/types/errors.d.ts
452
335
  /**
453
- * Tagged union of normalised connection errors.
454
- *
455
- * butr maps thrown values from connectors (which vary across wallet SDKs:
456
- * MetaMask uses EIP-1193 codes, Phantom throws stringly-typed errors,
457
- * embedded SDKs throw their own classes) into a small set of UX-meaningful
458
- * variants. Consumers branch on `kind` instead of regexing message strings.
459
- *
460
- * `message` is always present and human-readable. `cause` preserves the
461
- * original thrown value so callers can inspect raw connector errors when
462
- * 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.
463
339
  */
464
340
  type ConnectionError = {
465
341
  kind: "UserRejected";
@@ -488,17 +364,9 @@ type ConnectionError = {
488
364
  };
489
365
  type ConnectionErrorKind = ConnectionError["kind"];
490
366
  /**
491
- * Normalise a thrown value into a `ConnectionError`.
492
- *
493
- * Recognises:
494
- * - butr's own `Error("Connection timeout")` (from the 90s connect timeout)
495
- * - butr's own `Error("Failed to get account")` (from the connect flow)
496
- * - EIP-1193 numeric `code` properties (`4001` → UserRejected,
497
- * `-32002` → RequestPending, `4100`/`4900`/`4901` → NotConnected:
498
- * unauthorized / disconnected from all-or-one chains)
499
- * - common message substrings: "user rejected" / "user denied",
500
- * "locked", "chain", etc.
501
- * - 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.
502
370
  */
503
371
  declare const mapConnectionError: (raw: unknown) => ConnectionError;
504
372
  //#endregion
@@ -547,16 +415,9 @@ type WalletPersistence = {
547
415
  //#endregion
548
416
  //#region src/storage/snapshot.d.ts
549
417
  /**
550
- * Server-safe view of a butr-persisted session; everything you can
551
- * know about a user's connected wallets from the cookie payload alone,
552
- * without instantiating a `Connector`.
553
- *
554
- * Notably absent: the `Connector` instance. A wallet extension exists
555
- * only in the browser, so a server render can know *which* wallet was
556
- * connected and *what address* it held, but cannot dispatch
557
- * `signMessage`/`sendTransaction` on it. Splitting display from action
558
- * along this seam keeps the impossibility expressed in the types
559
- * 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.
560
421
  */
561
422
  type WalletSnapshot = {
562
423
  activeConnectorId: string | null;
@@ -576,48 +437,17 @@ type SnapshotOptions = {
576
437
  };
577
438
  declare const EMPTY_SNAPSHOT: WalletSnapshot;
578
439
  /**
579
- * Parse a cookie source into a server-safe `WalletSnapshot`.
580
- *
581
- * Pure, sync, no `document`, no React; runnable in any environment
582
- * (Server Component, route handler, edge middleware, even client
583
- * code). Pair with `createCookieStorageDriver({ initialCookies })`
584
- * and `<WalletManagerProvider initialSnapshot={…} />` to render a
585
- * connected shell server-side without a hydration flash.
586
- *
587
- * **Stale-snapshot semantics.** The snapshot reflects whatever the
588
- * browser most recently persisted. If the user has since uninstalled
589
- * the wallet, switched accounts, or disconnected in another tab, the
590
- * client-side hydration will reconcile reality and the live store
591
- * will diverge from the snapshot. Treat the snapshot as an
592
- * *optimistic* shell; accurate enough to avoid a paint flicker,
593
- * authoritative only after `useIsHydrated()` is true.
594
- *
595
- * **Inputs.** Accepts the three shapes Next.js / Express / Hono /
596
- * generic-Node cookie code naturally produces:
597
- * - A plain object: `{ "butr-pool": "{...}", … }`
598
- * - An array of `{ name, value }` (Next.js' `cookies().getAll()`)
599
- * - An iterable of `[name, value]` tuples
600
- *
601
- * Malformed entries are dropped with a `logWarn` (same policy as
602
- * `WalletStorage.getPool`); a cross-tab corruption shouldn't crash
603
- * 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.
604
443
  */
605
444
  declare const readWalletSnapshot: (source: CookieSource, options?: SnapshotOptions) => WalletSnapshot;
606
445
  //#endregion
607
446
  //#region src/types/manager.d.ts
608
447
  /**
609
- * Outcome of butr's mount-time hydration pass. Passed to
610
- * `WalletManagerConfig.onHydrated`. Three buckets:
611
- *
612
- * - `restoredIds`: wallets that came back fully. Their pool entries
613
- * are live and consumers can use them immediately.
614
- * - `pendingIds`: wallets whose adapter wasn't registered yet
615
- * (auto-discovery's async warmup). The runtime retries each one
616
- * when discovery announces a matching id, so most of these will
617
- * restore within a few hundred ms of mount.
618
- * - `dropped`: wallets whose restore actually failed (connector
619
- * threw mid-flight). These have been removed from storage; consumer
620
- * 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.
621
451
  */
622
452
  type HydrationOutcome = {
623
453
  dropped: Array<{
@@ -633,65 +463,35 @@ type WalletManagerConfig = {
633
463
  /** Function to instantiate a connector by ID */
634
464
  createConnector: (id: string) => WalletAdapter | null;
635
465
  /**
636
- * Seed the store synchronously with persisted wallet state; typically
637
- * the return value of `readWalletSnapshot(cookies, { keyPrefix })`
638
- * called from a Server Component. When provided:
639
- * - `pool` is populated with `ConnectedWallet` entries whose
640
- * `connector` is a shadow adapter (see `createShadowAdapter`):
641
- * identity-only, all capabilities `false`, methods throw
642
- * `ShadowConnectorError` if called.
643
- * - `activeConnectorId` and `selection` are set from the snapshot.
644
- * - `isHydrated` flips `true` immediately on construction.
645
- * - Every seeded id appears in `reconnectingIds`; the background
646
- * silent-reconnect pass removes the id and replaces the pool
647
- * entry with a live adapter on success, or drops the entry on
648
- * failure.
649
- *
650
- * Pre-hydration UI renders from the snapshot's data (address,
651
- * accounts, chain, name, icon) without a flash. Action affordances
652
- * (sign, send) are naturally gated by the shadow's all-false
653
- * 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.
654
469
  */
655
470
  initialState?: WalletSnapshot;
656
471
  /** Called after a wallet is successfully connected */
657
472
  onConnect?: (wallet: ConnectedWallet) => void;
658
473
  /**
659
- * Called after a connection attempt fails (user rejected, wallet
660
- * locked, chain mismatch, timeout, …). Receives the normalised
661
- * `ConnectionError` plus the id of the connector that was being
662
- * connected. Useful for piping into observability tooling
663
- * (Sentry, OTel) without each consumer wiring `try/catch`s around
664
- * `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.
665
477
  */
666
478
  onConnectError?: (error: ConnectionError, connectorId: string) => void;
667
479
  /** Called after a wallet is disconnected */
668
480
  onDisconnect?: (chainPlatform: ChainPlatform) => void;
669
- /**
670
- * Called once after butr's mount-time hydration finishes. Receives a
671
- * `HydrationOutcome` summarising which stored wallets were restored,
672
- * which are pending an adapter announcement, and which failed.
673
- * Useful for surfacing "Phantom couldn't be reconnected; try
674
- * again" UX or piping a metric to telemetry.
675
- */
481
+ /** Fires once, after the mount-time hydration pass. */
676
482
  onHydrated?: (outcome: HydrationOutcome) => void;
677
483
  /** Called after all wallets are reset (e.g., to clear auth tokens) */
678
484
  onReset?: () => void | Promise<void>;
679
485
  /**
680
- * Called when a connect attempt takes longer than
681
- * `slowConnectThresholdMs` (default 5_000) but hasn't yet resolved
682
- * or rejected. Fires at most once per connect attempt. Useful for
683
- * surfacing a "still trying, check your wallet" hint in the UI or
684
- * 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.
685
489
  */
686
490
  onSlowConnect?: (connectorId: string) => void;
687
491
  /**
688
- * Called when a storage write fails. butr's persistence layer is
689
- * fire-and-forget by design (any individual write can fail without
690
- * breaking butr's reducer state), but the consumer might still want
691
- * to know; quota-exceeded errors, IndexedDB shutdown, cross-tab
692
- * conflicts, cookie size limits. `context` is a short string
693
- * describing which write failed (e.g. `"failed to persist pool"`).
694
- * 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`.
695
495
  */
696
496
  onStorageError?: (error: unknown, context: string) => void;
697
497
  /** Threshold for `onSlowConnect`, in milliseconds. Defaults to 5_000. */
@@ -704,55 +504,9 @@ type WalletManagerConfig = {
704
504
  //#endregion
705
505
  //#region src/types/signer.d.ts
706
506
  /**
707
- * Per-platform signer type registry.
708
- *
709
- * `Connector.getSigner()` returns `Promise<unknown>` because the actual
710
- * signer shape lives in the platform-specific packages: `@usebutr/evm`
711
- * returns an EIP-1193 provider, `@usebutr/svm` returns a Wallet
712
- * Standard wallet, `@usebutr/sui` returns the same Wallet Standard
713
- * wallet narrowed to Sui features, `@usebutr/bitcoin` returns either a
714
- * Wallet Standard wallet or an injected provider depending on which
715
- * adapter discovered it.
716
- *
717
- * Consumers cast the `unknown` to whichever signer their integration
718
- * library expects. This registry exists so the cast target is sourced
719
- * from one place; when a platform package renames its signer type,
720
- * consumer code keeps working through the registry without an explicit
721
- * patch.
722
- *
723
- * **How to extend.** Each platform package declares a module-augmentation
724
- * block that adds its key to this interface. For example,
725
- * `@usebutr/evm` ships:
726
- *
727
- * ```ts
728
- * declare module "@usebutr/core" {
729
- * interface SignerForPlatform {
730
- * evm: Eip1193Provider;
731
- * }
732
- * }
733
- * ```
734
- *
735
- * Consumers that import the EVM package get the typed entry for free;
736
- * the augmentation is transitive through TypeScript's structural
737
- * declaration merging.
738
- *
739
- * **Usage.**
740
- *
741
- * ```ts
742
- * import type { SignerForPlatform } from "@usebutr/core";
743
- *
744
- * const signer = (await wallet.connector.getSigner()) as SignerForPlatform["evm"];
745
- * ```
746
- *
747
- * The cast is still there: `getSigner` itself stays type-erased to
748
- * keep the cross-package boundary loose, but the cast target lives in
749
- * one canonical place.
750
- *
751
- * **Why not make `getSigner` generic.** Generic-on-platform `getSigner`
752
- * would require `Connector` to be a discriminated union by
753
- * `chainPlatform`, which is a public-API breaking change held for a
754
- * future major release. The registry is the small step you can take
755
- * 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.
756
510
  */
757
511
  interface SignerForPlatform {}
758
512
  /** Convenience alias for narrowing a single platform's signer type. */
@@ -760,34 +514,24 @@ type SignerOf<P extends keyof SignerForPlatform> = SignerForPlatform[P];
760
514
  //#endregion
761
515
  //#region src/wallet-source.d.ts
762
516
  /**
763
- * A discovery seam. Implementations call `onAdapter(adapter)` each time
764
- * they find a wallet and return an unsubscribe handle. `@usebutr/wallets`
765
- * composes EVM + SVM into a single `WalletSource`; third parties can
766
- * 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.
767
520
  */
768
521
  type WalletSource = {
769
522
  subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void;
770
523
  };
771
524
  /**
772
- * Wrap a bare `subscribe` function (the exact shape of
773
- * `discoverEvmAdapters` / `discoverSvmAdapters`) into a `WalletSource`,
774
- * so an EVM-only app can do
775
- * `createWalletSource(discoverEvmAdapters)` without importing anything
776
- * 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.
777
527
  */
778
528
  declare const createWalletSource: (subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void) => WalletSource;
779
529
  //#endregion
780
530
  //#region src/store/reducer.d.ts
781
531
  /**
782
- * Public connection-status enum. `state.connectionStatus` in the
783
- * reducer only takes the first four values ("idle" | "connecting" |
784
- * "success" | "error"); those track the user-initiated connect
785
- * attempt. The fifth value, `"reconnecting"`, is *derived* by
786
- * `useConnectionStatus` when the active wallet is still backed by a
787
- * shadow adapter (its id is in `state.reconnectingIds`). The
788
- * derivation lives in the React selector so the reducer can stay a
789
- * narrow state machine while the public API matches wagmi's
790
- * 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.
791
535
  */
792
536
  type ConnectionStatus = "idle" | "connecting" | "success" | "error" | "reconnecting";
793
537
  type State = {
@@ -799,13 +543,9 @@ type State = {
799
543
  isUserDisconnected: boolean;
800
544
  pool: Map<string, ConnectedWallet>;
801
545
  /**
802
- * Connector IDs whose pool entry is currently backed by a shadow
803
- * adapter: created from `WalletManagerConfig.initialState` and
804
- * waiting for the live adapter to be announced (via discovery) and
805
- * the silent reconnect to succeed. The set shrinks as entries
806
- * upgrade (`HYDRATED` / `CONNECT_SUCCEEDED` clear matching ids) or
807
- * drop (`DISCONNECTED` / `RESET`). Empty for stores constructed
808
- * 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`.
809
549
  */
810
550
  reconnectingIds: ReadonlySet<string>;
811
551
  selection: Map<ChainPlatform, string>;
@@ -813,21 +553,9 @@ type State = {
813
553
  //#endregion
814
554
  //#region src/store/shadow-adapter.d.ts
815
555
  /**
816
- * Error thrown when a method is called on a shadow adapter; the
817
- * placeholder `WalletAdapter` that the store seeds into the pool when
818
- * an `initialState` is provided (e.g. from a server-rendered cookie
819
- * snapshot). Shadow adapters carry the identity and account data of a
820
- * previously-connected wallet, but the live wallet extension hasn't
821
- * been verified yet; the silent reconnect happens asynchronously
822
- * after mount.
823
- *
824
- * UI code that gates affordances on `wallet.connector.capabilities.*`
825
- * never reaches a shadow method (capabilities are all `false`). Code
826
- * that calls through anyway hits this typed error, which is the
827
- * correct loud failure: the consumer ignored the capability gate.
828
- *
829
- * Consumers wanting to wait out the reconnecting window should branch
830
- * 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.
831
559
  */
832
560
  declare class ShadowConnectorError extends Error {
833
561
  readonly code = "BUTR_RECONNECTING";
@@ -836,17 +564,9 @@ declare class ShadowConnectorError extends Error {
836
564
  constructor(method: string, connectorId: string);
837
565
  }
838
566
  /**
839
- * Type guard. Returns true when an adapter is a placeholder created
840
- * by `createShadowAdapter`. Useful for the hydration coordinator
841
- * (which needs to know which pool entries still need upgrading) and
842
- * for consumers writing wagmi-style "is this connection verified yet"
843
- * checks without subscribing to `reconnectingIds` directly.
844
- *
845
- * Detection is structural: a shadow has all capabilities set to false.
846
- * Live adapters always advertise at least one capability (every wallet
847
- * surface includes `getBalance`, `signMessage`, `switchChain` as
848
- * required methods, and adapter constructors set their flags
849
- * 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.
850
570
  */
851
571
  declare const isShadowAdapter: (adapter: WalletAdapter) => boolean;
852
572
  //#endregion
@@ -875,22 +595,13 @@ type CookieDriverOptions = {
875
595
  */
876
596
  domain?: string;
877
597
  /**
878
- * Snapshot of cookies for the SSR pass; typically the result of
879
- * Next.js' `cookies()` (from `next/headers`) or a parsed
880
- * `req.headers.cookie`. Used only when `document` is unavailable.
881
- *
882
- * When provided, `getItem` reads from this snapshot during the
883
- * server render so the store sees the same persisted state the
884
- * client will see after hydration. Writes remain no-ops on the
885
- * server: emitting `Set-Cookie` has to be done by the framework
886
- * 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.
887
601
  */
888
602
  initialCookies?: InitialCookies;
889
603
  /**
890
- * Lifetime in seconds. Defaults to 30 days. Pass `undefined`
891
- * (the default) for "until the session ends", but note that
892
- * butr persists wallet state across sessions, so a finite
893
- * max-age is usually what consumers want.
604
+ * Lifetime in seconds. Defaults to 30 days.
894
605
  */
895
606
  maxAgeSeconds?: number;
896
607
  /**
@@ -910,34 +621,16 @@ type CookieDriverOptions = {
910
621
  secure?: boolean;
911
622
  };
912
623
  /**
913
- * Cookie-backed storage driver. Reads/writes `document.cookie`;
914
- * server-readable, survives reloads, scoped per `domain`/`path`.
915
- *
916
- * **When to use this:** SSR apps that need to know who's connected
917
- * during the server render (so they can stream the connected-wallet
918
- * UI without a client-side hydration flicker). Pass `initialCookies`
919
- * from a server-side cookie source (e.g. `cookies()` in Next.js'
920
- * `next/headers`, or a parsed `req.headers.cookie`) and the same
921
- * driver will serve those values during the server render and switch
922
- * to `document.cookie` once it mounts in the browser.
923
- *
924
- * **Trade-offs vs `localStorage`:** cookies travel with every
925
- * request, so they cost bytes on the wire. Keep the storage key
926
- * prefix short, and prefer this driver for the `persistent` slot
927
- * only: the `session` slot can stay in `sessionStorage` (which
928
- * cookies can't natively model anyway).
929
- *
930
- * **Server-side writes are no-ops.** Emitting `Set-Cookie` requires
931
- * access to the framework's response object, which a storage driver
932
- * shouldn't reach into. The store doesn't mutate persisted state
933
- * during the SSR pass anyway; writes only fire after client mount,
934
- * 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`.
935
627
  */
936
628
  declare const createCookieStorageDriver: (options?: CookieDriverOptions) => StorageDriver;
937
629
  //#endregion
938
630
  //#region src/storage/wallet-storage.d.ts
939
631
  type StorageConfig = {
940
- keyPrefix: string;
632
+ /** Defaults to `"butr"`, matching `readWalletSnapshot`. */
633
+ keyPrefix?: string;
941
634
  /** Survives app restart. Defaults to localStorage on web. */
942
635
  persistent?: StorageDriver;
943
636
  /** Cleared on session end. Defaults to sessionStorage on web. */
@@ -951,27 +644,22 @@ declare class WalletStorage implements WalletPersistence {
951
644
  private readonly persistent;
952
645
  private readonly session;
953
646
  /**
954
- * Serializes pool-key mutations so concurrent fire-and-forget
955
- * writes can't interleave their read-modify-write phases. Without
956
- * this, two simultaneous `setPool` calls both read the pre-write
957
- * state, each merge their own entries, and whichever finishes last
958
- * overwrites the other's additions. Reads (`getPool`) don't enter
959
- * 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.
960
650
  */
961
651
  private poolMutationQueue;
962
652
  constructor(config: StorageConfig);
963
653
  /** Chain `fn` after the in-flight pool mutation. */
964
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;
965
658
  getPool(): Promise<StoredPoolRecord>;
966
659
  /**
967
- * Upsert the in-memory pool into storage. Additive: entries in
968
- * `pool` are written; entries already in storage that aren't in
969
- * `pool` are kept. The in-memory pool reflects "what's live right
970
- * now", not "the complete list of remembered connections"; a
971
- * silent reconnect that fails on reload leaves the entry out of
972
- * the pool but the saved entry stays so the next load can retry.
973
- * Use `removePoolEntry` for explicit eviction (the user clicked
974
- * 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`.
975
663
  */
976
664
  setPool(pool: Map<string, ConnectedWallet>): Promise<void>;
977
665
  removePoolEntry(connectorId: string): Promise<void>;
@@ -982,12 +670,9 @@ declare class WalletStorage implements WalletPersistence {
982
670
  setActiveConnectorId(connectorId: string | null): Promise<void>;
983
671
  clearAll(): Promise<void>;
984
672
  /**
985
- * Disconnect-intent tracking.
986
- *
987
- * Lives in the session driver: survives component remounts (unlike refs)
988
- * but clears when the session ends (unlike the persistent driver).
989
- * Prevents auto-connect from firing immediately after a manual disconnect,
990
- * 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.
991
676
  */
992
677
  isUserDisconnected(): Promise<boolean>;
993
678
  markUserDisconnected(value: boolean): Promise<void>;
@@ -1019,23 +704,81 @@ type WalletStore = ReturnType<typeof createWalletStore>;
1019
704
  type WalletStoreState = ExtractState<WalletStore>;
1020
705
  declare const createWalletStore: (config: WalletManagerConfig) => import("zustand/vanilla").StoreApi<State & RuntimeMembers>;
1021
706
  //#endregion
707
+ //#region src/group-by-platform.d.ts
708
+ /**
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.
712
+ */
713
+ declare const groupByPlatform: <T>(items: ReadonlyArray<T>, getPlatform: (item: T) => ChainPlatform) => Map<ChainPlatform, Array<T>>;
714
+ //#endregion
715
+ //#region src/sign-in/sign-in-flow.d.ts
716
+ /**
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`.
720
+ */
721
+ type SignInResult = {
722
+ account: Account;
723
+ /** The message handed to the wallet. Absent on the SIWS path, where the
724
+ * wallet composes the message itself. */
725
+ message?: string;
726
+ nonce: string;
727
+ signature: Uint8Array;
728
+ /** Base64 of `signature`. */
729
+ signatureBase64: string;
730
+ signedMessage: Uint8Array;
731
+ /** Base64 of `signedMessage`; verify against this. */
732
+ signedMessageBase64: string;
733
+ wallet: ConnectedWallet;
734
+ };
735
+ type SignInMessageContext = {
736
+ account: Account;
737
+ nonce: string;
738
+ wallet: ConnectedWallet;
739
+ };
740
+ type SignInFlowOptions = {
741
+ /**
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.
745
+ */
746
+ buildMessage?: (ctx: SignInMessageContext) => string;
747
+ /** Fetch a single-use nonce from your backend. */
748
+ getNonce: (ctx: {
749
+ account: Account;
750
+ wallet: ConnectedWallet;
751
+ }) => Promise<string>;
752
+ /**
753
+ * Skip the Sign In With Solana path even on wallets that advertise it,
754
+ * forcing every platform down the same `signMessage` route. Useful when
755
+ * one backend verifier has to handle every chain identically.
756
+ */
757
+ preferSignMessage?: boolean;
758
+ /** Hand the signed result to your backend. Throw to fail the flow. */
759
+ verify: (result: SignInResult) => Promise<void>;
760
+ };
761
+ /** Thrown before any wallet interaction when the wallet can't sign at
762
+ * all. Distinct from a rejection: nothing was asked of the user, so UI
763
+ * should say "this wallet can't sign in" rather than "you declined". */
764
+ declare class SignInUnsupportedError extends Error {
765
+ readonly connectorId: string;
766
+ constructor(connectorId: string);
767
+ }
768
+ /**
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.
772
+ */
773
+ declare const createSignInFlow: (options: SignInFlowOptions) => {
774
+ signIn: (wallet: ConnectedWallet, account?: Account) => Promise<SignInResult>;
775
+ };
776
+ //#endregion
1022
777
  //#region src/wallet-equal.d.ts
1023
778
  /**
1024
- * Two wallet snapshots are equivalent for selector purposes iff they
1025
- * share the same adapter instance, active account address, and active
1026
- * account chain id. Used by `useStoreWithEqualityFn` consumers
1027
- * (active-wallet, selected-wallet, useWalletEntry) to suppress spurious
1028
- * re-renders when the underlying Map churns but the resolved entry
1029
- * hasn't changed.
1030
- *
1031
- * The adapter is compared by reference, not by `connector.id`: hydration
1032
- * replaces a shadow adapter with the live one under an unchanged id and
1033
- * address, so an id comparison reports "equal" and leaves consumers
1034
- * holding a placeholder whose every method throws ShadowConnectorError.
1035
- *
1036
- * Hoisted to its own module so the equivalence rule lives in one place;
1037
- * if we ever extend the snapshot (e.g. to consider `accounts.length`),
1038
- * 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.
1039
782
  */
1040
783
  declare const walletEqual: (a: ConnectedWallet | undefined, b: ConnectedWallet | undefined) => boolean;
1041
784
  //#endregion
@@ -1045,38 +788,17 @@ declare const logError: (...args: ReadonlyArray<unknown>) => void;
1045
788
  //#endregion
1046
789
  //#region src/sanitize-icon.d.ts
1047
790
  /**
1048
- * Normalize a wallet-announced icon string.
1049
- *
1050
- * Wallets announce their icon through external metadata; EIP-6963
1051
- * `providerInfo.icon`, Wallet Standard `wallet.icon`. That value is
1052
- * not under butr's control, and some wallets ship data-URI icons with
1053
- * surrounding whitespace (a newline left over from a pretty-printed
1054
- * manifest). Strict consumers reject it: Next.js's `<Image>` throws
1055
- * because `src` must not start with a control character.
1056
- *
1057
- * Trims surrounding whitespace and treats an all-whitespace (or empty)
1058
- * icon as absent, so consumers get either a usable string or
1059
- * `undefined`: never a blank or malformed one. `undefined` passes
1060
- * through untouched.
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.
1061
794
  */
1062
795
  declare const sanitizeIcon: (icon: string | undefined) => string | undefined;
1063
796
  //#endregion
1064
797
  //#region src/encoding/bytes.d.ts
1065
798
  /**
1066
- * Shared byte-encoding helpers for the connector packages.
1067
- *
1068
- * These functions sit directly on the signing/address path of every
1069
- * chain. They used to be hand-reimplemented in ~10 connector files; a
1070
- * single tested module removes the drift surface (a `padStart` omission
1071
- * or base64 variant mismatch corrupts signatures/addresses for one chain
1072
- * only, and the divergence is invisible because the copies look "the
1073
- * same").
1074
- *
1075
- * Hex prefixing diverges across chains, so the module exposes **explicit
1076
- * variants** rather than one function:
1077
- * - {@link bytesToHex} returns bare hex (Bitcoin, Ledger).
1078
- * - {@link bytesToHexPrefixed} returns `0x`-prefixed hex (EVM, Polkadot).
1079
- * - {@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.
1080
802
  */
1081
803
  /** Bare lowercase hex, no `0x` prefix (Bitcoin, Ledger). */
1082
804
  declare const bytesToHex: (bytes: Uint8Array) => string;
@@ -1105,5 +827,5 @@ declare const bytesToBase58: (bytes: Uint8Array) => string;
1105
827
  * rather than silently dropping them. */
1106
828
  declare const base58ToBytes: (input: string) => Uint8Array;
1107
829
  //#endregion
1108
- 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 WalletAdapter, type WalletAvailability, type WalletBase, type WalletCapabilities, type WalletManagerConfig, type WalletPersistence, type WalletSnapshot, type WalletSource, WalletStorage, type WalletStore, type WalletStoreState, base58ToBytes, base64ToBytes, buildAccount, buildChainsByPlatform, bytesToBase58, bytesToBase64, bytesToHex, bytesToHexPrefixed, createBrowserStorageDriver, createCookieStorageDriver, createMemoryStorageDriver, createWalletSource, createWalletStore, hexToBytes, isShadowAdapter, logError, logWarn, mapConnectionError, readWalletSnapshot, sanitizeIcon, walletEqual };
830
+ 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 SignInFlowOptions, type SignInMessageContext, type SignInResult, SignInUnsupportedError, type SignerForPlatform, type SignerOf, type SnapshotOptions, type StorageDriver, type StoredPoolEntry, type StoredPoolRecord, type StoredSelectionRecord, type SuiAdapter, type SuiWallet, type SvmAdapter, type SvmWallet, type WalletAdapter, type WalletAvailability, type WalletBase, type WalletCapabilities, type WalletManagerConfig, type WalletPersistence, type WalletSnapshot, type WalletSource, WalletStorage, type WalletStore, type WalletStoreState, base58ToBytes, base64ToBytes, buildAccount, buildChainsByPlatform, bytesToBase58, bytesToBase64, bytesToHex, bytesToHexPrefixed, createBrowserStorageDriver, createCookieStorageDriver, createMemoryStorageDriver, createSignInFlow, createWalletSource, createWalletStore, groupByPlatform, hexToBytes, isShadowAdapter, logError, logWarn, mapConnectionError, readWalletSnapshot, sanitizeIcon, walletEqual };
1109
831
  //# sourceMappingURL=index.d.ts.map