@usebutr/core 0.4.2 → 1.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
@@ -17,172 +17,7 @@ type ChainBase = {
17
17
  reference: string;
18
18
  };
19
19
  //#endregion
20
- //#region src/storage/persistence.d.ts
21
- type MaybePromise<T> = T | Promise<T>;
22
- /** Low-level key/value driver. Sync on web (localStorage, MMKV),
23
- * async on React Native (AsyncStorage). */
24
- type StorageDriver = {
25
- getItem: (key: string) => MaybePromise<string | null>;
26
- removeItem: (key: string) => MaybePromise<void>;
27
- setItem: (key: string, value: string) => MaybePromise<void>;
28
- };
29
- type StoredPoolEntry = {
30
- account: Account;
31
- /** All known accounts on the wallet at last persist. Always contains
32
- * `account`. */
33
- accounts: Array<Account>;
34
- chainPlatform: ChainPlatform;
35
- connectorId: string;
36
- /** Wallet icon URL or data-URI captured at persist time. Lets the
37
- * shadow adapter render the same icon the live adapter will when
38
- * the store is seeded from a snapshot. Omitted when the adapter
39
- * itself has no icon. */
40
- icon?: string;
41
- /** Human-facing wallet name (e.g. "MetaMask") captured at persist
42
- * time. Required so the shadow adapter renders the same identity
43
- * the live adapter will; no "metamask" → "MetaMask" swap at the
44
- * hydration boundary. */
45
- name: string;
46
- };
47
- type StoredPoolRecord = Partial<Record<string, StoredPoolEntry>>;
48
- type StoredSelectionRecord = Partial<Record<ChainPlatform, string>>;
49
- type WalletPersistence = {
50
- clearAll: () => Promise<void>;
51
- clearPool: () => Promise<void>;
52
- getActiveConnectorId: () => Promise<string | null>;
53
- getPool: () => Promise<StoredPoolRecord>;
54
- getSelection: () => Promise<StoredSelectionRecord>;
55
- isUserDisconnected: () => Promise<boolean>;
56
- markUserDisconnected: (value: boolean) => Promise<void>;
57
- removePoolEntry: (connectorId: string) => Promise<void>;
58
- setActiveConnectorId: (connectorId: string | null) => Promise<void>;
59
- setPool: (pool: Map<string, ConnectedWallet>) => Promise<void>;
60
- setSelection: (selection: Map<ChainPlatform, string>) => Promise<void>;
61
- };
62
- //#endregion
63
- //#region src/storage/snapshot.d.ts
64
- /**
65
- * Server-safe view of a butr-persisted session; everything you can
66
- * know about a user's connected wallets from the cookie payload alone,
67
- * without instantiating a `Connector`.
68
- *
69
- * Notably absent: the `Connector` instance. A wallet extension exists
70
- * only in the browser, so a server render can know *which* wallet was
71
- * connected and *what address* it held, but cannot dispatch
72
- * `signMessage`/`sendTransaction` on it. Splitting display from action
73
- * along this seam keeps the impossibility expressed in the types
74
- * rather than hidden inside a runtime check.
75
- */
76
- type WalletSnapshot = {
77
- activeConnectorId: string | null;
78
- pool: StoredPoolRecord;
79
- selection: StoredSelectionRecord;
80
- };
81
- type CookieSource = Iterable<{
82
- name: string;
83
- value: string;
84
- }> | Iterable<[string, string]> | Readonly<Record<string, string>>;
85
- type SnapshotOptions = {
86
- /**
87
- * Same prefix passed to `WalletManagerProvider` / `WalletStorage`.
88
- * Defaults to `"butr"` to match the library default.
89
- */
90
- keyPrefix?: string;
91
- };
92
- declare const EMPTY_SNAPSHOT: WalletSnapshot;
93
- /**
94
- * Parse a cookie source into a server-safe `WalletSnapshot`.
95
- *
96
- * Pure, sync, no `document`, no React; runnable in any environment
97
- * (Server Component, route handler, edge middleware, even client
98
- * code). Pair with `createCookieStorageDriver({ initialCookies })`
99
- * and `<WalletManagerProvider initialSnapshot={…} />` to render a
100
- * connected shell server-side without a hydration flash.
101
- *
102
- * **Stale-snapshot semantics.** The snapshot reflects whatever the
103
- * browser most recently persisted. If the user has since uninstalled
104
- * the wallet, switched accounts, or disconnected in another tab, the
105
- * client-side hydration will reconcile reality and the live store
106
- * will diverge from the snapshot. Treat the snapshot as an
107
- * *optimistic* shell; accurate enough to avoid a paint flicker,
108
- * authoritative only after `useIsHydrated()` is true.
109
- *
110
- * **Inputs.** Accepts the three shapes Next.js / Express / Hono /
111
- * generic-Node cookie code naturally produces:
112
- * - A plain object: `{ "butr-pool": "{...}", … }`
113
- * - An array of `{ name, value }` (Next.js' `cookies().getAll()`)
114
- * - An iterable of `[name, value]` tuples
115
- *
116
- * Malformed entries are dropped with a `logWarn` (same policy as
117
- * `WalletStorage.getPool`); a cross-tab corruption shouldn't crash
118
- * the server render.
119
- */
120
- declare const readWalletSnapshot: (source: CookieSource, options?: SnapshotOptions) => WalletSnapshot;
121
- //#endregion
122
- //#region src/types/errors.d.ts
123
- /**
124
- * Tagged union of normalised connection errors.
125
- *
126
- * butr maps thrown values from connectors (which vary across wallet SDKs:
127
- * MetaMask uses EIP-1193 codes, Phantom throws stringly-typed errors,
128
- * embedded SDKs throw their own classes) into a small set of UX-meaningful
129
- * variants. Consumers branch on `kind` instead of regexing message strings.
130
- *
131
- * `message` is always present and human-readable. `cause` preserves the
132
- * original thrown value so callers can inspect raw connector errors when
133
- * the variant is `Unknown`.
134
- */
135
- type ConnectionError = {
136
- kind: "UserRejected";
137
- message: string;
138
- } | {
139
- kind: "RequestPending";
140
- message: string;
141
- } | {
142
- kind: "WalletLocked";
143
- message: string;
144
- } | {
145
- actualChain?: string;
146
- expectedChain?: string;
147
- kind: "ChainMismatch";
148
- message: string;
149
- } | {
150
- kind: "NotConnected";
151
- message: string;
152
- } | {
153
- kind: "Timeout";
154
- message: string;
155
- } | {
156
- cause?: unknown;
157
- kind: "Unknown";
158
- message: string;
159
- };
160
- type ConnectionErrorKind = ConnectionError["kind"];
161
- /**
162
- * Normalise a thrown value into a `ConnectionError`.
163
- *
164
- * Recognises:
165
- * - butr's own `Error("Connection timeout")` (from the 90s connect timeout)
166
- * - butr's own `Error("Failed to get account")` (from the connect flow)
167
- * - EIP-1193 numeric `code` properties (`4001` → UserRejected,
168
- * `-32002` → RequestPending, `4100`/`4900`/`4901` → NotConnected:
169
- * unauthorized / disconnected from all-or-one chains)
170
- * - common message substrings: "user rejected" / "user denied",
171
- * "locked", "chain", etc.
172
- * - anything else → `Unknown` with `cause` set to the original value.
173
- */
174
- declare const mapConnectionError: (raw: unknown) => ConnectionError;
175
- //#endregion
176
- //#region src/types/wallet.d.ts
177
- /**
178
- * Canonical list of supported chain platforms. The single runtime source
179
- * of truth: `ChainPlatform` is derived from it, and storage validators
180
- * build their allowlists from it, so the type and the runtime checks can
181
- * never drift (a missing platform here was why Polkadot connections failed
182
- * to persist).
183
- */
184
- declare const CHAIN_PLATFORMS: readonly ["evm", "svm", "sui", "bitcoin", "polkadot"];
185
- type ChainPlatform = (typeof CHAIN_PLATFORMS)[number];
20
+ //#region src/types/account.d.ts
186
21
  type Account = {
187
22
  chain: ChainBase;
188
23
  id: string;
@@ -206,42 +41,8 @@ type Balance = {
206
41
  /** Raw integer amount */
207
42
  value: bigint;
208
43
  };
209
- /**
210
- * Whether a wallet is currently usable from the user's environment.
211
- *
212
- * - `installed`: the wallet is available and `connect()` can be called.
213
- * - `loadable`: the wallet's SDK can be loaded on demand (e.g. WalletConnect
214
- * modal that pops a QR code without requiring a browser extension).
215
- * - `not-installed`: the wallet isn't reachable. Consumers typically render
216
- * a "download" affordance pointing at `meta.url`.
217
- */
218
- type WalletAvailability = "installed" | "loadable" | "not-installed";
219
- /**
220
- * Events a connector can emit while connected. butr's runtime subscribes
221
- * via `Connector.subscribe?` after a successful `connect()` and dispatches
222
- * the equivalent reducer event:
223
- *
224
- * - `accountChanged` carries both the new active `account` AND the full
225
- * `accounts` array the wallet currently exposes. The runtime mirrors
226
- * that list verbatim into the pool entry. This handles two cases
227
- * uniformly:
228
- * - Multi-account wallets (MetaMask, Rabby, Brave): the user adds or
229
- * removes accounts from the dapp's permission set; the array grows
230
- * or shrinks to match.
231
- * - Single-account-exposure wallets (Phantom EVM/SVM, MetaMask Snap):
232
- * only the active account is ever in `accounts`; switching swaps it
233
- * in place rather than appending.
234
- * Also covers chain switches; the new chain lives inside `account.chain`.
235
- * - `disconnected` → `DISCONNECTED` (wallet has gone away externally:
236
- * user locked it, removed the extension, etc.).
237
- */
238
- type ConnectorEvent = {
239
- account: Account;
240
- accounts: Array<Account>;
241
- type: "accountChanged";
242
- } | {
243
- type: "disconnected";
244
- };
44
+ //#endregion
45
+ //#region src/types/capabilities.d.ts
245
46
  /**
246
47
  * Capability flags describing what an adapter can actually do at
247
48
  * runtime. Populated by each adapter (the auto-built ones derive
@@ -296,6 +97,95 @@ type WalletCapabilities = {
296
97
  * per-call `chain` input) when more than one chain is advertised. */
297
98
  switchChain: boolean;
298
99
  };
100
+ //#endregion
101
+ //#region src/types/platform.d.ts
102
+ /**
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).
108
+ */
109
+ declare const CHAIN_PLATFORMS: readonly ["evm", "svm", "sui", "bitcoin", "polkadot"];
110
+ type ChainPlatform = (typeof CHAIN_PLATFORMS)[number];
111
+ //#endregion
112
+ //#region src/types/chains-by-platform.d.ts
113
+ /**
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).
122
+ */
123
+ type ChainsByPlatform = Readonly<Record<ChainPlatform, ReadonlyArray<ChainBase>>>;
124
+ /**
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
+ * });
149
+ */
150
+ declare const buildChainsByPlatform: (partial: Partial<ChainsByPlatform>) => ChainsByPlatform;
151
+ //#endregion
152
+ //#region src/types/connector.d.ts
153
+ /**
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`.
161
+ */
162
+ type WalletAvailability = "installed" | "loadable" | "not-installed";
163
+ /**
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.).
181
+ */
182
+ type ConnectorEvent = {
183
+ account: Account;
184
+ accounts: Array<Account>;
185
+ type: "accountChanged";
186
+ } | {
187
+ type: "disconnected";
188
+ };
299
189
  /**
300
190
  * Orchestration interface: what `butr` actually calls during the
301
191
  * connect / disconnect / hydrate flow. This is the contract `butr`
@@ -358,6 +248,22 @@ type Connector<P extends ChainPlatform = ChainPlatform> = {
358
248
  * themselves. */
359
249
  subscribe?: (listener: (event: ConnectorEvent) => void) => () => void;
360
250
  };
251
+ type ConnectorMeta = {
252
+ /** Optional. Sync probe that reports whether the wallet is currently
253
+ * available. Defaults to `"installed"` when omitted. Consumers call
254
+ * this at render time to gate the "Connect" button. */
255
+ availability?: () => WalletAvailability;
256
+ chainPlatform: ChainPlatform;
257
+ /** Optional image URL or data URI for wallet selection UIs. */
258
+ icon?: string;
259
+ id: string;
260
+ name: string;
261
+ /** Optional. Where to send users who don't have this wallet (download
262
+ * page, app store link, etc.). */
263
+ url?: string;
264
+ };
265
+ //#endregion
266
+ //#region src/types/wallet.d.ts
361
267
  /**
362
268
  * Methods every connected wallet supports regardless of chain. The
363
269
  * per-platform `Wallet` types extend this with their platform-specific
@@ -458,67 +364,247 @@ type BitcoinWallet = WalletBase & {
458
364
  signTransaction?: (tx: unknown, account?: Account) => Promise<Uint8Array>;
459
365
  };
460
366
  /**
461
- * Polkadot/Substrate wallet surface. No standalone `signTransaction`:
462
- * building an extrinsic needs chain metadata (an RPC round-trip butr
463
- * doesn't ship), so transaction signing happens through the
464
- * `getSigner()` handoff; the consumer builds and submits with the
465
- * wallet's signer (e.g. polkadot-api). Message signing works via the
466
- * injected `signer.signRaw`. Same shape as `EvmWallet`.
467
- */
468
- type PolkadotWallet = WalletBase;
469
- /** Per-platform full adapter shapes: `Connector` + the platform's
470
- * `Wallet` surface. These are the discriminated-union variants of
471
- * `WalletAdapter`. */
472
- type EvmAdapter = Connector<"evm"> & EvmWallet;
473
- type SvmAdapter = Connector<"svm"> & SvmWallet;
474
- type SuiAdapter = Connector<"sui"> & SuiWallet;
475
- type BitcoinAdapter = Connector<"bitcoin"> & BitcoinWallet;
476
- type PolkadotAdapter = Connector<"polkadot"> & PolkadotWallet;
477
- /**
478
- * Full adapter interface; discriminated union by `chainPlatform`.
479
- *
480
- * Narrow on `wallet.connector.chainPlatform === "svm"` (etc.) to gain
481
- * access to platform-specific methods like `signIn` (SVM) or
482
- * `signTransaction` (SVM / Sui / Bitcoin). Calling those methods on a
483
- * non-narrowed `WalletAdapter` is a TypeScript error; that's the
484
- * point. The discriminant carries the type-level fact "this method
485
- * doesn't exist on EVM" so consumers can't accidentally branch on
486
- * `capabilities.signIn` and call a method that EVM adapters don't
487
- * implement.
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`.
373
+ */
374
+ type PolkadotWallet = WalletBase;
375
+ /** Per-platform full adapter shapes: `Connector` + the platform's
376
+ * `Wallet` surface. These are the discriminated-union variants of
377
+ * `WalletAdapter`. */
378
+ type EvmAdapter = Connector<"evm"> & EvmWallet;
379
+ type SvmAdapter = Connector<"svm"> & SvmWallet;
380
+ type SuiAdapter = Connector<"sui"> & SuiWallet;
381
+ type BitcoinAdapter = Connector<"bitcoin"> & BitcoinWallet;
382
+ type PolkadotAdapter = Connector<"polkadot"> & PolkadotWallet;
383
+ /**
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".
400
+ */
401
+ type WalletAdapter = EvmAdapter | SvmAdapter | SuiAdapter | BitcoinAdapter | PolkadotAdapter;
402
+ type ConnectedWallet = {
403
+ /** Currently-active account on this wallet. */
404
+ account: Account;
405
+ /** All accounts known on this wallet at the time of connect/refresh.
406
+ * Always contains at least `account`. Populated from `getAccounts()`
407
+ * if the connector implements it; otherwise `[account]`. */
408
+ accounts: Array<Account>;
409
+ connector: WalletAdapter;
410
+ };
411
+ //#endregion
412
+ //#region src/types/discoverer.d.ts
413
+ /**
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.
434
+ */
435
+ type PlatformDiscoverer = {
436
+ /**
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.
441
+ */
442
+ fallback?: {
443
+ subscribe: (onAdapter: (adapter: WalletAdapter) => void, opts: {
444
+ hasAnyPrimaryAdapter: () => boolean;
445
+ }) => () => void;
446
+ };
447
+ /** Primary discovery subscription. */
448
+ subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void;
449
+ };
450
+ //#endregion
451
+ //#region src/types/errors.d.ts
452
+ /**
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`.
463
+ */
464
+ type ConnectionError = {
465
+ kind: "UserRejected";
466
+ message: string;
467
+ } | {
468
+ kind: "RequestPending";
469
+ message: string;
470
+ } | {
471
+ kind: "WalletLocked";
472
+ message: string;
473
+ } | {
474
+ actualChain?: string;
475
+ expectedChain?: string;
476
+ kind: "ChainMismatch";
477
+ message: string;
478
+ } | {
479
+ kind: "NotConnected";
480
+ message: string;
481
+ } | {
482
+ kind: "Timeout";
483
+ message: string;
484
+ } | {
485
+ cause?: unknown;
486
+ kind: "Unknown";
487
+ message: string;
488
+ };
489
+ type ConnectionErrorKind = ConnectionError["kind"];
490
+ /**
491
+ * Normalise a thrown value into a `ConnectionError`.
488
492
  *
489
- * Runtime gating via `capabilities` still matters for the methods that
490
- * are OPTIONAL within a platform (a Solana wallet might or might not
491
- * advertise `solana:signTransaction`). Capabilities narrow "wallet
492
- * supports this feature"; the discriminated union narrows "this
493
- * platform has this concept at all".
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.
494
502
  */
495
- type WalletAdapter = EvmAdapter | SvmAdapter | SuiAdapter | BitcoinAdapter | PolkadotAdapter;
496
- /** Deprecated. Use one of the per-platform `*Wallet` types or
497
- * `WalletBase`. Retained as an alias so existing names resolve. */
498
- type Wallet = WalletBase;
499
- type ConnectedWallet = {
500
- /** Currently-active account on this wallet. */
503
+ declare const mapConnectionError: (raw: unknown) => ConnectionError;
504
+ //#endregion
505
+ //#region src/storage/persistence.d.ts
506
+ type MaybePromise<T> = T | Promise<T>;
507
+ /** Low-level key/value driver. Sync on web (localStorage, MMKV),
508
+ * async on React Native (AsyncStorage). */
509
+ type StorageDriver = {
510
+ getItem: (key: string) => MaybePromise<string | null>;
511
+ removeItem: (key: string) => MaybePromise<void>;
512
+ setItem: (key: string, value: string) => MaybePromise<void>;
513
+ };
514
+ type StoredPoolEntry = {
501
515
  account: Account;
502
- /** All accounts known on this wallet at the time of connect/refresh.
503
- * Always contains at least `account`. Populated from `getAccounts()`
504
- * if the connector implements it; otherwise `[account]`. */
516
+ /** All known accounts on the wallet at last persist. Always contains
517
+ * `account`. */
505
518
  accounts: Array<Account>;
506
- connector: WalletAdapter;
507
- };
508
- type ConnectorMeta = {
509
- /** Optional. Sync probe that reports whether the wallet is currently
510
- * available. Defaults to `"installed"` when omitted. Consumers call
511
- * this at render time to gate the "Connect" button. */
512
- availability?: () => WalletAvailability;
513
519
  chainPlatform: ChainPlatform;
514
- /** Optional image URL or data URI for wallet selection UIs. */
520
+ connectorId: string;
521
+ /** Wallet icon URL or data-URI captured at persist time. Lets the
522
+ * shadow adapter render the same icon the live adapter will when
523
+ * the store is seeded from a snapshot. Omitted when the adapter
524
+ * itself has no icon. */
515
525
  icon?: string;
516
- id: string;
526
+ /** Human-facing wallet name (e.g. "MetaMask") captured at persist
527
+ * time. Required so the shadow adapter renders the same identity
528
+ * the live adapter will; no "metamask" → "MetaMask" swap at the
529
+ * hydration boundary. */
517
530
  name: string;
518
- /** Optional. Where to send users who don't have this wallet (download
519
- * page, app store link, etc.). */
520
- url?: string;
521
531
  };
532
+ type StoredPoolRecord = Partial<Record<string, StoredPoolEntry>>;
533
+ type StoredSelectionRecord = Partial<Record<ChainPlatform, string>>;
534
+ type WalletPersistence = {
535
+ clearAll: () => Promise<void>;
536
+ clearPool: () => Promise<void>;
537
+ getActiveConnectorId: () => Promise<string | null>;
538
+ getPool: () => Promise<StoredPoolRecord>;
539
+ getSelection: () => Promise<StoredSelectionRecord>;
540
+ isUserDisconnected: () => Promise<boolean>;
541
+ markUserDisconnected: (value: boolean) => Promise<void>;
542
+ removePoolEntry: (connectorId: string) => Promise<void>;
543
+ setActiveConnectorId: (connectorId: string | null) => Promise<void>;
544
+ setPool: (pool: Map<string, ConnectedWallet>) => Promise<void>;
545
+ setSelection: (selection: Map<ChainPlatform, string>) => Promise<void>;
546
+ };
547
+ //#endregion
548
+ //#region src/storage/snapshot.d.ts
549
+ /**
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.
560
+ */
561
+ type WalletSnapshot = {
562
+ activeConnectorId: string | null;
563
+ pool: StoredPoolRecord;
564
+ selection: StoredSelectionRecord;
565
+ };
566
+ type CookieSource = Iterable<{
567
+ name: string;
568
+ value: string;
569
+ }> | Iterable<[string, string]> | Readonly<Record<string, string>>;
570
+ type SnapshotOptions = {
571
+ /**
572
+ * Same prefix passed to `WalletManagerProvider` / `WalletStorage`.
573
+ * Defaults to `"butr"` to match the library default.
574
+ */
575
+ keyPrefix?: string;
576
+ };
577
+ declare const EMPTY_SNAPSHOT: WalletSnapshot;
578
+ /**
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.
604
+ */
605
+ declare const readWalletSnapshot: (source: CookieSource, options?: SnapshotOptions) => WalletSnapshot;
606
+ //#endregion
607
+ //#region src/types/manager.d.ts
522
608
  /**
523
609
  * Outcome of butr's mount-time hydration pass. Passed to
524
610
  * `WalletManagerConfig.onHydrated`. Three buckets:
@@ -616,86 +702,6 @@ type WalletManagerConfig = {
616
702
  storageKeyPrefix?: string;
617
703
  };
618
704
  //#endregion
619
- //#region src/types/chains-by-platform.d.ts
620
- /**
621
- * Map of every chain platform to the list of chains the consumer wants
622
- * to expose to its UI (chain switcher, picker, etc).
623
- *
624
- * The platform key set is fixed by `ChainPlatform`. The value is the
625
- * chain list; empty when the consumer doesn't want to support that
626
- * platform's chains in this view. This is the only type that callers
627
- * write down; the values come from per-platform packages
628
- * (`EVM_CHAINS_LIST`, `SVM_CHAINS_LIST`, etc).
629
- */
630
- type ChainsByPlatform = Readonly<Record<ChainPlatform, ReadonlyArray<ChainBase>>>;
631
- /**
632
- * Build a fully-populated `ChainsByPlatform` from a partial. Platforms
633
- * the consumer doesn't specify default to an empty list.
634
- *
635
- * Use this in apps that target one or two chain platforms; importing
636
- * only those packages keeps unused chain registries out of the bundle.
637
- * Apps that want every chain reach for `CHAINS_BY_PLATFORM` from
638
- * `@usebutr/wallets` instead.
639
- *
640
- * @example
641
- * // EVM-only app: Solana/Sui/Bitcoin tables never enter the bundle
642
- * import { EVM_CHAINS_LIST } from "@usebutr/evm";
643
- * import { buildChainsByPlatform } from "@usebutr/core";
644
- *
645
- * const chains = buildChainsByPlatform({ evm: EVM_CHAINS_LIST });
646
- *
647
- * @example
648
- * // Multi-chain app: pull from each package the app actually uses
649
- * import { EVM_CHAINS_LIST } from "@usebutr/evm";
650
- * import { SVM_CHAINS_LIST } from "@usebutr/svm";
651
- *
652
- * const chains = buildChainsByPlatform({
653
- * evm: EVM_CHAINS_LIST,
654
- * svm: SVM_CHAINS_LIST,
655
- * });
656
- */
657
- declare const buildChainsByPlatform: (partial: Partial<ChainsByPlatform>) => ChainsByPlatform;
658
- //#endregion
659
- //#region src/types/discoverer.d.ts
660
- /**
661
- * Self-describing discovery descriptor for a single chain platform.
662
- *
663
- * Each platform package (`@usebutr/evm`, `@usebutr/svm`, `@usebutr/sui`,
664
- * `@usebutr/bitcoin`) exports one of these. The aggregator package
665
- * (`@usebutr/wallets`) composes them into `autoDiscovery()` without
666
- * needing to know per-platform defaults; the descriptor owns them.
667
- *
668
- * Adding a new chain platform means writing a `PlatformDiscoverer`
669
- * inside the new package and adding one import to the aggregator's
670
- * registry. The aggregator's logic doesn't need to grow.
671
- *
672
- * Two parts:
673
- * - `subscribe`: the primary discovery channel (EIP-6963 / Wallet
674
- * Standard / etc).
675
- * - `fallback`: optional. The legacy-injected channel that should
676
- * only emit if the primary channel hasn't produced an adapter for
677
- * the same browser session by the settle deadline. `@usebutr/evm`
678
- * has one (window.ethereum); `@usebutr/bitcoin` has one
679
- * (window.unisat / sats-connect / window.btc); SVM and Sui don't.
680
- */
681
- type PlatformDiscoverer = {
682
- /**
683
- * Optional legacy-injected fallback. Subscribes only when consumers
684
- * haven't disabled it. The hook receives `hasAnyPrimaryAdapter` so
685
- * the fallback can defer to standards-based discovery when an adapter
686
- * for the same wallet has already announced through the primary path.
687
- */
688
- fallback?: {
689
- subscribe: (onAdapter: (adapter: WalletAdapter) => void, opts: {
690
- hasAnyPrimaryAdapter: () => boolean;
691
- }) => () => void;
692
- };
693
- /** Stable platform identifier. Used by the aggregator for keying. */
694
- platform: ChainPlatform;
695
- /** Primary discovery subscription. */
696
- subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void;
697
- };
698
- //#endregion
699
705
  //#region src/types/signer.d.ts
700
706
  /**
701
707
  * Per-platform signer type registry.
@@ -805,6 +811,45 @@ type State = {
805
811
  selection: Map<ChainPlatform, string>;
806
812
  };
807
813
  //#endregion
814
+ //#region src/store/shadow-adapter.d.ts
815
+ /**
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`.
831
+ */
832
+ declare class ShadowConnectorError extends Error {
833
+ readonly code = "BUTR_RECONNECTING";
834
+ readonly connectorId: string;
835
+ readonly method: string;
836
+ constructor(method: string, connectorId: string);
837
+ }
838
+ /**
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).
850
+ */
851
+ declare const isShadowAdapter: (adapter: WalletAdapter) => boolean;
852
+ //#endregion
808
853
  //#region src/storage/browser-storage-driver.d.ts
809
854
  type BrowserStorageDrivers = {
810
855
  /** Survives app restart (localStorage on web, AsyncStorage/MMKV on RN). */
@@ -977,10 +1022,16 @@ declare const createWalletStore: (config: WalletManagerConfig) => import("zustan
977
1022
  //#region src/wallet-equal.d.ts
978
1023
  /**
979
1024
  * Two wallet snapshots are equivalent for selector purposes iff they
980
- * share connectorId, active account address, and active account chain
981
- * id. Used by `useStoreWithEqualityFn` consumers (active-wallet,
982
- * selected-wallet, useWalletEntry) to suppress spurious re-renders
983
- * when the underlying Map churns but the resolved entry hasn't changed.
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.
984
1035
  *
985
1036
  * Hoisted to its own module so the equivalence rule lives in one place;
986
1037
  * if we ever extend the snapshot (e.g. to consider `accounts.length`),
@@ -1044,6 +1095,15 @@ declare const hexToBytes: (hex: string) => Uint8Array;
1044
1095
  declare const base64ToBytes: (b64: string) => Uint8Array;
1045
1096
  /** Cross-platform `Uint8Array` → base64. */
1046
1097
  declare const bytesToBase64: (bytes: Uint8Array) => string;
1098
+ /**
1099
+ * `Uint8Array` → base58 (Solana addresses and signatures). Leading zero
1100
+ * bytes are significant in base58 and survive the BigInt round-trip only
1101
+ * because they're re-prefixed as `1`s afterwards.
1102
+ */
1103
+ declare const bytesToBase58: (bytes: Uint8Array) => string;
1104
+ /** Decode base58 into raw bytes. Throws on characters outside the alphabet
1105
+ * rather than silently dropping them. */
1106
+ declare const base58ToBytes: (input: string) => Uint8Array;
1047
1107
  //#endregion
1048
- export { type Account, type Balance, type BitcoinAdapter, type BitcoinWallet, type BrowserStorageDrivers, CHAIN_PLATFORMS, type ChainBase, type ChainPlatform, type ChainsByPlatform, type ConnectedWallet, type ConnectionError, type ConnectionErrorKind, type ConnectionStatus, type Connector, type ConnectorEvent, type ConnectorMeta, type CookieDriverOptions, type CookieSource, EMPTY_SNAPSHOT, type EvmAdapter, type EvmWallet, type HydrationOutcome, type InitialCookies, type MaybePromise, type PlatformDiscoverer, type PolkadotAdapter, type PolkadotWallet, type SignerForPlatform, type SignerOf, type SnapshotOptions, type StorageDriver, type StoredPoolEntry, type StoredPoolRecord, type StoredSelectionRecord, type SuiAdapter, type SuiWallet, type SvmAdapter, type SvmWallet, type Wallet, type WalletAdapter, type WalletAvailability, type WalletBase, type WalletCapabilities, type WalletManagerConfig, type WalletPersistence, type WalletSnapshot, type WalletSource, WalletStorage, type WalletStore, type WalletStoreState, base64ToBytes, buildAccount, buildChainsByPlatform, bytesToBase64, bytesToHex, bytesToHexPrefixed, createBrowserStorageDriver, createCookieStorageDriver, createMemoryStorageDriver, createWalletSource, createWalletStore, hexToBytes, logError, logWarn, mapConnectionError, readWalletSnapshot, sanitizeIcon, walletEqual };
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 };
1049
1109
  //# sourceMappingURL=index.d.ts.map