@usebutr/core 2.0.1 → 3.0.1

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,3 +1,4 @@
1
+ import { StoreApi } from "zustand/vanilla";
1
2
  //#region src/types/chain.d.ts
2
3
  /**
3
4
  * CAIP-2 shaped. butr never reads past these four fields, so consumers
@@ -6,13 +7,19 @@
6
7
  type ChainBase = {
7
8
  /** CAIP-2 identifier, e.g. "eip155:1", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" */
8
9
  id: string;
9
- /** Human-readable name, e.g. "Ethereum", "Solana" */
10
+ /** Human-readable chain name, e.g. "Ethereum". Never the wallet's name. */
10
11
  name: string;
11
12
  /** CAIP-2 namespace, e.g. "eip155", "solana" */
12
13
  namespace: string;
13
14
  /** CAIP-2 reference, e.g. "1", "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" */
14
15
  reference: string;
15
16
  };
17
+ /**
18
+ * Wallets report bare CAIP-2 ids, so the display name comes from a chain
19
+ * registry. A chain outside `known` keeps its id as its name rather than
20
+ * borrowing the wallet's.
21
+ */
22
+ declare const resolveChain: (id: string, known?: ReadonlyArray<ChainBase>) => ChainBase;
16
23
  //#endregion
17
24
  //#region src/types/account.d.ts
18
25
  type Account = {
@@ -37,47 +44,6 @@ type Balance = {
37
44
  value: bigint;
38
45
  };
39
46
  //#endregion
40
- //#region src/types/capabilities.d.ts
41
- /**
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.
45
- */
46
- type WalletCapabilities = {
47
- /** `getBalance` returns a real on-chain value (vs. a 0n placeholder). */
48
- getBalance: boolean;
49
- /** `getTransactionReceipt` returns a real RPC response. */
50
- getTransactionReceipt: boolean;
51
- /** Calling `requestAccounts` will actually do something: either
52
- * prompt the user (EIP-2255) or refresh the exposed list. */
53
- requestAccounts: boolean;
54
- /** `sendTx` / `sendTxToChain` will work. False for SVM wallets that
55
- * don't advertise `solana:signAndSendTransaction`. */
56
- sendTransaction: boolean;
57
- /** `signIn` works: Sign In With Solana (`solana:signIn`). True only
58
- * for SVM wallets advertising the feature. */
59
- signIn: boolean;
60
- /** `signMessage` will work. False for SVM wallets that don't
61
- * advertise `solana:signMessage`. */
62
- signMessage: boolean;
63
- /** `signTransaction` returns a signed-but-unbroadcast transaction the
64
- * consumer submits with their own RPC client. True only for SVM
65
- * wallets advertising `solana:signTransaction`; butr ships no RPC so
66
- * it can't broadcast the result itself. EVM/hardware adapters leave
67
- * this `false` (no `signTransaction` method). */
68
- signTransaction: boolean;
69
- /** Wallet emits account/chain change events that butr can bridge. */
70
- subscribe: boolean;
71
- /** `switchAccount` is real. Almost always `false` for auto adapters;
72
- * neither protocol exposes silent account switch. Hand-rolled
73
- * adapters with custom transports may set it `true`. */
74
- switchAccount: boolean;
75
- /** `switchChain` routes subsequent calls through the new chain. EVM:
76
- * true via `wallet_switchEthereumChain`. SVM: true (local state +
77
- * per-call `chain` input) when more than one chain is advertised. */
78
- switchChain: boolean;
79
- };
80
- //#endregion
81
47
  //#region src/types/platform.d.ts
82
48
  /**
83
49
  * Single runtime source of truth: `ChainPlatform` and the storage
@@ -86,246 +52,267 @@ type WalletCapabilities = {
86
52
  */
87
53
  declare const CHAIN_PLATFORMS: readonly ["evm", "svm", "sui", "bitcoin", "polkadot"];
88
54
  type ChainPlatform = (typeof CHAIN_PLATFORMS)[number];
55
+ declare const isChainPlatform: (value: string) => value is ChainPlatform;
89
56
  //#endregion
90
57
  //#region src/types/chains-by-platform.d.ts
91
- /**
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.
95
- */
58
+ /** An empty list opts that platform out of the consumer's chain UI. */
96
59
  type ChainsByPlatform = Readonly<Record<ChainPlatform, ReadonlyArray<ChainBase>>>;
97
- /**
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`.
101
- */
102
- declare const buildChainsByPlatform: (partial: Partial<ChainsByPlatform>) => {
103
- bitcoin: readonly ChainBase[];
104
- evm: readonly ChainBase[];
105
- polkadot: readonly ChainBase[];
106
- sui: readonly ChainBase[];
107
- svm: readonly ChainBase[];
108
- };
109
60
  //#endregion
110
61
  //#region src/types/connector.d.ts
111
62
  /**
112
- * Gate Connect on `installed` or `loadable`: a `loadable` wallet has no
113
- * extension yet still connects (WalletConnect's QR modal). Only
114
- * `not-installed` should degrade to a download link at `meta.url`.
115
- */
116
- type WalletAvailability = "installed" | "loadable" | "not-installed";
117
- /**
118
- * `accountChanged` must carry every account the wallet still exposes,
119
- * not just the new active one: the runtime mirrors the array verbatim
120
- * into the pool entry. Chain switches arrive as `account.chain`.
63
+ * `accounts` carries every account the wallet still exposes, active first;
64
+ * the runtime mirrors it verbatim into the pool entry. Chain switches
65
+ * arrive as a changed `account.chain`. An empty list is `disconnected`.
121
66
  */
122
67
  type ConnectorEvent = {
123
- account: Account;
124
- accounts: Array<Account>;
125
- type: "accountChanged";
68
+ accounts: ReadonlyArray<Account>;
69
+ type: "accountsChanged";
126
70
  } | {
127
71
  type: "disconnected";
128
72
  };
129
73
  /**
130
- * Orchestration interface: what `butr` actually calls during the
131
- * connect / disconnect / hydrate flow. This is the contract `butr`
132
- * cares about; everything else on `WalletAdapter` is consumer-facing.
74
+ * What butr itself calls during connect, disconnect and hydration. Optional
75
+ * members are defined only when they work for this wallet, so presence is
76
+ * the capability check.
133
77
  */
134
78
  type Connector<P extends ChainPlatform = ChainPlatform> = {
135
- /** Runtime capability flags; see `WalletCapabilities`. Read these
136
- * to gate UI affordances rather than probing for method existence. */
137
- capabilities: WalletCapabilities;
138
- /** Discriminant: which chain platform this adapter speaks. Generic
139
- * parameter `P` narrows this to a specific platform when consumers
140
- * use one of the per-platform adapter types (`EvmAdapter`,
141
- * `SvmAdapter`, etc). */
79
+ /** Discriminant: which chain platform this adapter speaks. */
142
80
  chainPlatform: P;
143
- /** `opts.silent` is hydration's non-interactive reconnect (Wallet
81
+ /** `options.silent` is hydration's non-interactive reconnect (Wallet
144
82
  * Standard `standard:connect` silent input, `eth_accounts` on
145
83
  * EIP-1193). An adapter that cannot honour it must reject instead of
146
84
  * prompting; hydration reads that as a clean restore failure. */
147
- connect: (opts?: {
85
+ connect: (options?: {
148
86
  silent?: boolean;
149
87
  }) => Promise<void>;
150
- /** Optional teardown. butr calls this on disconnect, error recovery, and reset. */
88
+ /** Teardown. butr calls it on disconnect, failed connects and reset. */
151
89
  disconnect?: () => Promise<void>;
152
- /** Read the currently-active account. butr uses this to populate the pool
153
- * after a successful `connect()` and during hydration. */
154
- getAccount: () => Promise<Account | null>;
155
- /** Optional. List every account the wallet exposes. Some browser wallets
156
- * show many accounts at once (MetaMask with multiple imports). If
157
- * omitted, butr defaults to `[await getAccount()]`. */
158
- getAccounts?: () => Promise<Array<Account>>;
90
+ /** Every account the wallet exposes, active account first. Empty when
91
+ * the wallet is not connected. */
92
+ getAccounts: () => Promise<ReadonlyArray<Account>>;
159
93
  /** Discovery runs `sanitizeIcon` at construction, so on discovered
160
- * adapters this is a trimmed non-empty string or `undefined`: render
161
- * it straight into `next/image` with no re-sanitizing and no
162
- * `icon !== ""` guard. Hand-rolled adapters own that guarantee. */
94
+ * adapters this is a trimmed non-empty string or `undefined`. Hand-rolled
95
+ * adapters own that guarantee. */
163
96
  icon?: string;
164
- /** Stable key: "metamask", "phantom", etc. Pool entries are keyed by this. */
97
+ /** Stable key: "io.metamask", "wallet-standard:svm-phantom", etc. Pool
98
+ * entries are keyed by this. */
165
99
  id: string;
166
100
  /** Human name: "MetaMask", "Phantom", etc. UI-facing only. */
167
101
  name: string;
168
102
  /** Opens the wallet's account-selection UI (`wallet_requestPermissions`
169
- * on EIP-6963; usually unset on Wallet Standard). Resolution does not
170
- * carry the new accounts: follow it with `getAccounts()`, or use the
171
- * `useRequestAccounts` hook which refreshes the pool entry. */
103
+ * on EIP-1193). Resolution does not carry the new accounts; the
104
+ * manager's `requestAccounts` action refreshes the pool entry. */
172
105
  requestAccounts?: () => Promise<void>;
173
- /** Bridges native wallet events (`accountsChanged`, `chainChanged`, …)
174
- * into the reducer. Only `ConnectorLifecycle` may call this, and it
175
- * guarantees at most one live subscription per connector. */
106
+ /** Bridges native wallet events into the reducer. Only the manager's
107
+ * connector lifecycle calls this, at most once per live connector. */
176
108
  subscribe?: (listener: (event: ConnectorEvent) => void) => () => void;
177
109
  };
178
- type ConnectorMeta = {
179
- /** Optional. Sync probe that reports whether the wallet is currently
180
- * available. Defaults to `"installed"` when omitted. Consumers call
181
- * this at render time to gate the "Connect" button. */
182
- availability?: () => WalletAvailability;
110
+ //#endregion
111
+ //#region src/storage/persistence.d.ts
112
+ type MaybePromise<T> = T | Promise<T>;
113
+ /** Low-level key/value driver. Sync on web (localStorage, MMKV),
114
+ * async on React Native (AsyncStorage). */
115
+ type StorageDriver = {
116
+ getItem: (key: string) => MaybePromise<string | null>;
117
+ removeItem: (key: string) => MaybePromise<void>;
118
+ setItem: (key: string, value: string) => MaybePromise<void>;
119
+ };
120
+ /** Everything needed to render a connection before its adapter exists:
121
+ * the shadow adapter shows this identity until silent reconnect lands. */
122
+ type StoredPoolEntry = {
123
+ account: Account;
124
+ accounts: ReadonlyArray<Account>;
183
125
  chainPlatform: ChainPlatform;
184
- /** Optional image URL or data URI for wallet selection UIs. Unlike
185
- * `Connector.icon`, this one is consumer-supplied and butr does not
186
- * sanitize it; run it through `sanitizeIcon` if the value came from
187
- * wallet metadata rather than your own assets. */
126
+ connectorId: string;
188
127
  icon?: string;
189
- id: string;
190
128
  name: string;
191
- /** Optional. Where to send users who don't have this wallet (download
192
- * page, app store link, etc.). */
193
- url?: string;
194
129
  };
130
+ type StoredPoolRecord = Partial<Record<string, StoredPoolEntry>>;
131
+ type StoredSelectionRecord = Partial<Record<ChainPlatform, string>>;
132
+ /**
133
+ * Carries no `Connector` by design: a wallet extension exists only in the
134
+ * browser, so a server render can name the wallet and its address but can
135
+ * never dispatch on it.
136
+ */
137
+ type WalletSnapshot = {
138
+ activeConnectorId: string | null;
139
+ pool: StoredPoolRecord;
140
+ selection: StoredSelectionRecord;
141
+ };
142
+ type PersistedWalletState = WalletSnapshot & {
143
+ /** Session-scoped: a manual disconnect suppresses auto-connect for this
144
+ * session, not forever. */
145
+ isUserDisconnected: boolean;
146
+ };
147
+ /**
148
+ * `save` receives the whole derived state after every change, so an
149
+ * implementation never merges or diffs. A `load` rejection is reported
150
+ * through `onStorageError` and treated as empty storage.
151
+ */
152
+ type WalletPersistence = {
153
+ load: () => Promise<PersistedWalletState>;
154
+ save: (state: PersistedWalletState) => Promise<void>;
155
+ };
156
+ //#endregion
157
+ //#region src/types/signer.d.ts
158
+ /**
159
+ * Filled by each transport package through module augmentation, e.g.
160
+ * `interface WalletSignerRegistry { eip1193: { provider: Eip1193Provider } }`.
161
+ * Keyed by transport, not platform: EVM through a Ledger is not EIP-1193.
162
+ */
163
+ interface WalletSignerRegistry {}
164
+ /** Narrow with `switch (signer.kind)`; each branch is fully typed. */
165
+ type WalletSigner = { [K in keyof WalletSignerRegistry]: {
166
+ kind: K;
167
+ } & WalletSignerRegistry[K]; }[keyof WalletSignerRegistry];
168
+ type WalletSignerKind = WalletSigner["kind"];
169
+ type WalletSignerOf<K extends WalletSignerKind> = Extract<WalletSigner, {
170
+ kind: K;
171
+ }>;
195
172
  //#endregion
196
173
  //#region src/types/wallet.d.ts
197
174
  type SignInInput = {
198
175
  readonly [key: string]: SignInValue;
199
176
  };
200
177
  type SignInValue = boolean | number | string | null | ReadonlyArray<SignInValue> | SignInInput;
201
- type TransactionObject = {
202
- readonly [key: string]: TransactionValue | undefined;
203
- };
204
- type TransactionMethod = (...args: ReadonlyArray<never>) => Promise<string>;
205
- type TransactionValue = bigint | boolean | number | string | null | Uint8Array | ReadonlyArray<TransactionValue> | TransactionObject | TransactionMethod;
206
- type TransactionInput = TransactionObject | string | Uint8Array;
207
- type WalletSigner = object;
208
- /**
209
- * Methods every connected wallet supports regardless of chain. The
210
- * per-platform `Wallet` types extend this with their platform-specific
211
- * methods (`signIn` for SVM, `signTransaction` for sign-only paths).
212
- */
213
- type WalletBase = {
214
- /** Read a token balance. `mint` is optional; the connector decides
215
- * what "no mint" means for its chain (native ETH on EVM, native SOL
216
- * on Solana, etc.). */
217
- getBalance: (mint?: string) => Promise<Balance>;
218
- /** Returns a chain-specific signer. Consumers cast to the concrete
219
- * type via the `SignerForPlatform` registry (or directly to the
220
- * library shape they wrap: `WalletClient` on viem, etc.). */
178
+ type SignedMessage = {
179
+ signature: Uint8Array;
180
+ signedMessage: Uint8Array;
181
+ };
182
+ type TransactionReceipt = {
183
+ status: "Error" | "Pending" | "Success";
184
+ };
185
+ /** Acts as `account`, which must be one the wallet exposes; an unknown one
186
+ * rejects rather than signing with another. Omitted, the wallet uses its own
187
+ * active account, so pass `wallet.account` to honour `setAccount`. */
188
+ type AccountOptions = {
189
+ account?: Account;
190
+ };
191
+ /**
192
+ * `chain` targets this one transaction. Wallet Standard routes it per call;
193
+ * an EVM wallet has one global network, so it switches first; a transport
194
+ * that can do neither rejects when `chain` is not its current chain.
195
+ */
196
+ type TransactionOptions = AccountOptions & {
197
+ chain?: ChainBase;
198
+ };
199
+ /** `token` is chain-specific: an ERC-20 contract address on EVM. Omit it for
200
+ * the native asset. */
201
+ type BalanceOptions = AccountOptions & {
202
+ token?: string;
203
+ };
204
+ /**
205
+ * Presence is the capability check: an adapter defines an optional method
206
+ * only when calling it can succeed for this wallet, and never ships a
207
+ * placeholder (a zero balance, a receipt that stays pending).
208
+ */
209
+ type WalletBase<Tx> = {
210
+ getBalance?: (options?: BalanceOptions) => Promise<Balance>;
211
+ /** Narrow with `switch (signer.kind)`; see `WalletSignerRegistry`. */
221
212
  getSigner: () => Promise<WalletSigner>;
222
- /** Look up the status of a previously-submitted transaction. */
223
- getTransactionReceipt: (tx: string) => Promise<{
224
- status: "Success" | "Error" | "Pending";
225
- }>;
226
- /** `account` routes through a specific exposed address (EVM via
227
- * `tx.from`, Wallet Standard via the feature's `account` input);
228
- * omitting it lets the wallet pick. */
229
- sendTx: (tx: TransactionInput, account?: Account) => Promise<string>;
230
- /** Submit a transaction targeting a specific chain. The optional
231
- * callback fires after the connector has switched chain (consumers
232
- * use this to re-enable UI). Pass an `account` to route through a
233
- * specific exposed address (see `sendTx`). */
234
- sendTxToChain: (tx: TransactionInput, targetChainId: string, account?: Account, cb?: () => void) => Promise<string>;
213
+ getTransactionReceipt?: (hash: string) => Promise<TransactionReceipt>;
214
+ /** Signs and broadcasts; resolves the transaction hash or signature. */
215
+ sendTx?: (tx: Tx, options?: TransactionOptions) => Promise<string>;
235
216
  /**
236
- * Verify against `signedMessage`, not the input: Solana Wallet
237
- * Standard wallets may prefix or re-encode it. `account` signs with a
238
- * specific address without changing the wallet's active one.
217
+ * Verify against `signedMessage`, not the input: Solana Wallet Standard
218
+ * wallets may prefix or re-encode it.
239
219
  */
240
- signMessage: (msg: Uint8Array, account?: Account) => Promise<{
241
- signature: Uint8Array;
242
- signedMessage: Uint8Array;
243
- }>;
244
- /** Switch to a different account on the same wallet (some wallets
245
- * expose multiple accounts simultaneously). */
246
- switchAccount?: (address: string) => Promise<void>;
247
- /** Switch the wallet's active chain. */
248
- switchChain: (chain: ChainBase) => Promise<void>;
249
- };
250
- /**
251
- * No `signIn` (SIWE is app-level here, not a protocol method) and no
252
- * `signTransaction`: EVM wallets sign and send in one step through
253
- * `eth_sendTransaction`.
254
- */
255
- type EvmWallet = WalletBase;
256
- /**
257
- * Both additions are optional at runtime; gate them on
258
- * `capabilities.signIn` / `capabilities.signTransaction`, which mirror
259
- * what the wallet actually advertises.
260
- */
261
- type SvmWallet = WalletBase & {
262
- /** Sign In With Solana (SIWS, `solana:signIn`). Authenticates the user
263
- * and returns the connected account plus the signed statement so the
264
- * consumer can verify server-side. `input` is the SIWS message fields
265
- * (domain, statement, nonce, …); pass `{}` or omit for wallet
266
- * defaults. */
267
- signIn?: (input?: SignInInput) => Promise<{
268
- account: Account;
269
- signature: Uint8Array;
270
- signedMessage: Uint8Array;
271
- }>;
272
- /** Sign a Solana transaction WITHOUT broadcasting it. butr ships no
273
- * RPC, so the consumer broadcasts the returned bytes via
274
- * `@solana/kit` / `@solana/web3.js` / etc. */
275
- signTransaction?: (tx: TransactionInput, account?: Account) => Promise<Uint8Array>;
276
- };
277
- /**
278
- * Sui wallet surface. Adds optional `signTransaction` for the
279
- * `sui:signTransaction` (sign-only) feature; broadcast is on the
280
- * consumer via `@mysten/sui`'s SuiClient.
281
- */
282
- type SuiWallet = WalletBase & {
283
- /** Sign a Sui transaction WITHOUT executing it. Returns BOTH halves the
284
- * chain requires: `SuiClient.executeTransactionBlock` needs
285
- * `{ transactionBlock, signature }`, so a bare `Uint8Array` cannot express
286
- * the result and a consumer holding one cannot tell which half they have. */
287
- signTransaction?: (tx: TransactionInput, account?: Account) => Promise<{
220
+ signMessage?: (message: Uint8Array, options?: AccountOptions) => Promise<SignedMessage>;
221
+ /** Moves the wallet (or butr's view of it) to `chain`. Rejects for a
222
+ * chain outside this adapter's namespace or not advertised by the wallet. */
223
+ switchChain?: (chain: ChainBase) => Promise<void>;
224
+ };
225
+ type EvmTransactionValue = bigint | boolean | number | string | null | ReadonlyArray<EvmTransactionValue> | {
226
+ readonly [key: string]: EvmTransactionValue | undefined;
227
+ };
228
+ /** An `eth_sendTransaction` request. `bigint` quantities are encoded as hex. */
229
+ type EvmTransactionRequest = Readonly<Record<string, EvmTransactionValue | undefined>>;
230
+ /** A `@mysten/sui` `Transaction` (anything with `toJSON()`), its JSON string,
231
+ * or BCS bytes. */
232
+ type SuiTransactionInput = string | Uint8Array | {
233
+ toJSON: () => Promise<string>;
234
+ };
235
+ /** `amount` in satoshis. */
236
+ type BitcoinTransfer = {
237
+ amount: bigint;
238
+ recipient: string;
239
+ };
240
+ type SignInOutput = {
241
+ account: Account;
242
+ signature: Uint8Array;
243
+ signedMessage: Uint8Array;
244
+ };
245
+ /** No `signIn` (SIWE is app-level) and no `signTransaction`: EVM wallets sign
246
+ * and send in one step through `eth_sendTransaction`. */
247
+ type EvmWallet = WalletBase<EvmTransactionRequest>;
248
+ type SvmWallet = WalletBase<Uint8Array> & {
249
+ /** Sign In With Solana (`solana:signIn`). `input` holds the SIWS message
250
+ * fields (domain, statement, nonce, …). */
251
+ signIn?: (input?: SignInInput) => Promise<SignInOutput>;
252
+ /** Signs a serialized transaction without broadcasting it and resolves the
253
+ * full signed transaction, ready for your own RPC client. */
254
+ signTransaction?: (tx: Uint8Array, options?: TransactionOptions) => Promise<Uint8Array>;
255
+ };
256
+ type SuiWallet = WalletBase<SuiTransactionInput> & {
257
+ /** Signs without executing. `SuiClient.executeTransactionBlock` needs both
258
+ * halves, so the result carries the transaction bytes and the signature. */
259
+ signTransaction?: (tx: SuiTransactionInput, options?: TransactionOptions) => Promise<{
288
260
  bytes: Uint8Array;
289
261
  signature: Uint8Array;
290
262
  }>;
291
263
  };
292
- /**
293
- * `signTransaction` is `bitcoin:signPsbt`: pass `psbt.toBuffer()` bytes
294
- * and get signed PSBT bytes back, to finalise and broadcast through
295
- * your own Esplora or Electrum client.
296
- */
297
- type BitcoinWallet = WalletBase & {
298
- signTransaction?: (tx: TransactionInput, account?: Account) => Promise<Uint8Array>;
264
+ type BitcoinWallet = WalletBase<BitcoinTransfer> & {
265
+ /** `bitcoin:signPsbt`: PSBT bytes in (`psbt.toBuffer()`), signed PSBT bytes
266
+ * out, to finalise and broadcast through your own Esplora or Electrum
267
+ * client. */
268
+ signTransaction?: (psbt: Uint8Array, options?: TransactionOptions) => Promise<Uint8Array>;
299
269
  };
300
- /**
301
- * No standalone `signTransaction`: building an extrinsic needs chain
302
- * metadata over RPC, which butr does not ship, so transaction signing
303
- * goes through the `getSigner()` handoff.
304
- */
305
- type PolkadotWallet = WalletBase;
306
- /** Per-platform full adapter shapes: `Connector` + the platform's
307
- * `Wallet` surface. These are the discriminated-union variants of
308
- * `WalletAdapter`. */
270
+ /** Building an extrinsic needs chain metadata over RPC, which butr does not
271
+ * ship, so transactions go through the `getSigner()` handoff. */
272
+ type PolkadotWallet = Omit<WalletBase<never>, "sendTx">;
309
273
  type EvmAdapter = Connector<"evm"> & EvmWallet;
310
274
  type SvmAdapter = Connector<"svm"> & SvmWallet;
311
275
  type SuiAdapter = Connector<"sui"> & SuiWallet;
312
276
  type BitcoinAdapter = Connector<"bitcoin"> & BitcoinWallet;
313
277
  type PolkadotAdapter = Connector<"polkadot"> & PolkadotWallet;
314
- /**
315
- * The union narrows "this platform has the concept at all"; the
316
- * `capabilities` flags narrow "this wallet supports it right now".
317
- * Both gates are needed, and neither substitutes for the other.
318
- */
278
+ /** Narrow on `chainPlatform` to reach a platform's own methods and
279
+ * transaction type. */
319
280
  type WalletAdapter = EvmAdapter | SvmAdapter | SuiAdapter | BitcoinAdapter | PolkadotAdapter;
320
- type ConnectedWallet = {
321
- /** Currently-active account on this wallet. */
281
+ type AdapterByPlatform = {
282
+ bitcoin: BitcoinAdapter;
283
+ evm: EvmAdapter;
284
+ polkadot: PolkadotAdapter;
285
+ sui: SuiAdapter;
286
+ svm: SvmAdapter;
287
+ };
288
+ type WalletAdapterFor<P extends ChainPlatform> = Extract<WalletAdapter, {
289
+ chainPlatform: P;
290
+ }>;
291
+ type ConnectedWallet<P extends ChainPlatform = ChainPlatform> = {
292
+ /** The active account; always one of `accounts`. */
322
293
  account: Account;
323
- /** All accounts known on this wallet at the time of connect/refresh.
324
- * Always contains at least `account`. Populated from `getAccounts()`
325
- * if the connector implements it; otherwise `[account]`. */
326
- accounts: Array<Account>;
327
- connector: WalletAdapter;
328
- };
294
+ /** Every account the wallet exposed at the last connect or refresh. */
295
+ accounts: ReadonlyArray<Account>;
296
+ /** An indexed access rather than `WalletAdapterFor`, so TypeScript measures
297
+ * `P` covariant and `ConnectedWallet<"evm">` stays a `ConnectedWallet`. */
298
+ connector: AdapterByPlatform[P];
299
+ };
300
+ /** Narrows a pool entry to one platform, e.g. before calling its `sendTx`. */
301
+ declare const isPlatformWallet: <P extends ChainPlatform>(wallet: ConnectedWallet, platform: P) => wallet is ConnectedWallet<P>;
302
+ //#endregion
303
+ //#region src/wallet-source.d.ts
304
+ /**
305
+ * The discovery seam: a function that announces adapters and returns its
306
+ * unsubscribe. Every `discover*Adapters` export already has this shape, so
307
+ * it goes into `sources` as-is.
308
+ */
309
+ type WalletSource = (onAdapter: (adapter: WalletAdapter) => void) => () => void;
310
+ /**
311
+ * For adapters that are built rather than discovered: WalletConnect, Ledger,
312
+ * hand-rolled ones. Takes one adapter or several, or a promise of either; a
313
+ * rejected promise is logged and contributes nothing.
314
+ */
315
+ declare const fromAdapters: (adapters: MaybePromise<WalletAdapter | Iterable<WalletAdapter>>) => WalletSource;
329
316
  //#endregion
330
317
  //#region src/types/discoverer.d.ts
331
318
  /**
@@ -340,256 +327,306 @@ type PlatformDiscoverer = {
340
327
  * discovery already announced the same wallet, or it double-lists.
341
328
  */
342
329
  fallback?: {
343
- subscribe: (onAdapter: (adapter: WalletAdapter) => void, opts: {
330
+ subscribe: (onAdapter: (adapter: WalletAdapter) => void, options: {
344
331
  hasAnyPrimaryAdapter: () => boolean;
345
332
  }) => () => void;
346
333
  };
347
334
  /** Primary discovery subscription. */
348
- subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void;
335
+ subscribe: WalletSource;
349
336
  };
350
337
  //#endregion
351
338
  //#region src/types/errors.d.ts
352
339
  /**
353
340
  * Wallet SDKs disagree on error shape (EIP-1193 codes, bare strings,
354
341
  * bespoke classes), so consumers branch on `kind` rather than regexing
355
- * messages. `cause` keeps the original value for the `Unknown` case.
356
- */
357
- type ConnectionError = {
358
- kind: "UserRejected";
359
- message: string;
360
- } | {
361
- kind: "RequestPending";
362
- message: string;
363
- } | {
364
- kind: "WalletLocked";
365
- message: string;
366
- } | {
367
- actualChain?: string;
368
- expectedChain?: string;
369
- kind: "ChainMismatch";
370
- message: string;
371
- } | {
372
- kind: "NotConnected";
373
- message: string;
374
- } | {
375
- kind: "Timeout";
376
- message: string;
377
- } | {
378
- cause?: ErrorCause;
379
- kind: "Unknown";
380
- message: string;
381
- };
382
- type ConnectionErrorKind = ConnectionError["kind"];
383
- type ErrorCause = Error | string;
384
- type CodedError = Error & {
385
- code: number | string;
386
- };
387
- /**
388
- * EIP-1193 codes: `4001` rejected, `-32002` pending, `4100`/`4900`/`4901`
389
- * unauthorized or disconnected. Message-substring matching is the last
390
- * resort for SDKs that ship no codes at all.
342
+ * messages. `cause` keeps the original value.
391
343
  */
392
- declare const mapConnectionError: (raw: ErrorCause) => ConnectionError;
393
- //#endregion
394
- //#region src/storage/persistence.d.ts
395
- type MaybePromise<T> = T | Promise<T>;
396
- /** Low-level key/value driver. Sync on web (localStorage, MMKV),
397
- * async on React Native (AsyncStorage). */
398
- type StorageDriver = {
399
- getItem: (key: string) => MaybePromise<string | null>;
400
- removeItem: (key: string) => MaybePromise<void>;
401
- setItem: (key: string, value: string) => MaybePromise<void>;
402
- };
403
- type StoredPoolEntry = {
404
- account: Account;
405
- /** All known accounts on the wallet at last persist. Always contains
406
- * `account`. */
407
- accounts: Array<Account>;
408
- chainPlatform: ChainPlatform;
409
- connectorId: string;
410
- /** Wallet icon URL or data-URI captured at persist time. Lets the
411
- * shadow adapter render the same icon the live adapter will when
412
- * the store is seeded from a snapshot. Omitted when the adapter
413
- * itself has no icon. */
414
- icon?: string;
415
- /** Human-facing wallet name (e.g. "MetaMask") captured at persist
416
- * time. Required so the shadow adapter renders the same identity
417
- * the live adapter will; no "metamask" → "MetaMask" swap at the
418
- * hydration boundary. */
419
- name: string;
420
- };
421
- type StoredPoolRecord = Partial<Record<string, StoredPoolEntry>>;
422
- type StoredSelectionRecord = Partial<Record<ChainPlatform, string>>;
423
- type WalletPersistence = {
424
- clearAll: () => Promise<void>;
425
- clearPool: () => Promise<void>;
426
- getActiveConnectorId: () => Promise<string | null>;
427
- getPool: () => Promise<StoredPoolRecord>;
428
- getSelection: () => Promise<StoredSelectionRecord>;
429
- isUserDisconnected: () => Promise<boolean>;
430
- markUserDisconnected: (value: boolean) => Promise<void>;
431
- removePoolEntry: (connectorId: string) => Promise<void>;
432
- setActiveConnectorId: (connectorId: string | null) => Promise<void>;
433
- setPool: (pool: Map<string, ConnectedWallet>) => Promise<void>;
434
- setSelection: (selection: Map<ChainPlatform, string>) => Promise<void>;
435
- };
436
- //#endregion
437
- //#region src/storage/snapshot.d.ts
438
- /**
439
- * Carries no `Connector` by design: a wallet extension exists only in
440
- * the browser, so a server render can name the wallet and its address
441
- * but can never dispatch on it.
442
- */
443
- type WalletSnapshot = {
444
- activeConnectorId: string | null;
445
- pool: StoredPoolRecord;
446
- selection: StoredSelectionRecord;
447
- };
448
- type CookieSource = Iterable<{
449
- name: string;
450
- value: string;
451
- }> | Iterable<[string, string]> | Readonly<Record<string, string>>;
452
- type SnapshotOptions = {
453
- /**
454
- * Same prefix passed to `WalletManagerProvider` / `WalletStorage`.
455
- * Defaults to `"butr"` to match the library default.
456
- */
457
- keyPrefix?: string;
458
- };
459
- declare const EMPTY_SNAPSHOT: WalletSnapshot;
460
- /**
461
- * Optimistic: only what the browser last persisted, so an uninstall or
462
- * other-tab disconnect makes it stale. Authoritative once the entry
463
- * leaves `reconnectingIds`; `isHydrated` is true from render one.
464
- */
465
- declare const readWalletSnapshot: (source: CookieSource, options?: SnapshotOptions) => WalletSnapshot;
344
+ type ConnectionErrorKind = "ChainMismatch" | "NotConnected" | "RequestPending" | "Timeout" | "Unknown" | "UserRejected" | "WalletLocked" | "WalletNotFound";
345
+ declare class ConnectionError extends Error {
346
+ readonly kind: ConnectionErrorKind;
347
+ constructor(kind: ConnectionErrorKind, message: string, options?: ErrorOptions);
348
+ }
349
+ /** The one boundary where a thrown value of unknown shape becomes typed. */
350
+ declare const toConnectionError: (cause: unknown) => ConnectionError;
466
351
  //#endregion
467
352
  //#region src/types/manager.d.ts
468
353
  /**
469
- * `pendingIds` are not failures: discovery announces those adapters
470
- * asynchronously and the runtime retries them, usually within a few
471
- * hundred ms. Only `dropped` entries have been removed from storage.
354
+ * `pendingIds` are not failures: their adapters have not been announced yet,
355
+ * and the manager restores them the moment they are. Only `dropped` entries
356
+ * failed their silent reconnect; they stay persisted for the next load.
472
357
  */
473
358
  type HydrationOutcome = {
474
- dropped: Array<{
359
+ dropped: ReadonlyArray<{
475
360
  connectorId: string;
476
- reason: ErrorCause;
361
+ reason: ConnectionError;
477
362
  }>;
478
- pendingIds: Array<string>;
479
- restoredIds: Array<string>;
363
+ pendingIds: ReadonlyArray<string>;
364
+ restoredIds: ReadonlyArray<string>;
480
365
  };
366
+ /**
367
+ * Read once, when the manager is created. Define it at module scope; the
368
+ * callbacks never see later values.
369
+ */
481
370
  type WalletManagerConfig = {
482
- /** Available connector metadata */
483
- connectors: Array<ConnectorMeta>;
484
- /** Function to instantiate a connector by ID */
485
- createConnector: (id: string) => WalletAdapter | null;
486
- /**
487
- * Seeds the pool with shadow adapters and flips `isHydrated` true on
488
- * construction, so identity renders without a flash but every seeded
489
- * id sits in `reconnectingIds` until silent reconnect verifies it.
490
- */
491
- initialState?: WalletSnapshot;
492
- /** Called after a wallet is successfully connected */
493
- onConnect?: (wallet: ConnectedWallet) => void;
494
- /**
495
- * Fires for every failed attempt (rejection, locked wallet, timeout,
496
- * …), so observability can hook here instead of wrapping every
497
- * `connectWallet` call.
498
- */
371
+ /** Fires whenever a wallet goes live: a user `connect` (`reconnected:
372
+ * false`) or a silent restore of a persisted connection (`true`). */
373
+ onConnect?: (wallet: ConnectedWallet, context: {
374
+ reconnected: boolean;
375
+ }) => void;
376
+ /** Fires for every failed attempt, so observability hooks here instead of
377
+ * wrapping every `connect` call. */
499
378
  onConnectError?: (error: ConnectionError, connectorId: string) => void;
500
- /** Called after a wallet is disconnected */
501
- onDisconnect?: (chainPlatform: ChainPlatform) => void;
502
- /** Fires once, after the mount-time hydration pass. */
379
+ /** `byUser` is false when the wallet itself ended the session (locked,
380
+ * extension removed, relay session expired). */
381
+ onDisconnect?: (wallet: ConnectedWallet, context: {
382
+ byUser: boolean;
383
+ }) => void;
384
+ /** Fires once, after the start-up hydration pass. */
503
385
  onHydrated?: (outcome: HydrationOutcome) => void;
504
- /** Called after all wallets are reset (e.g., to clear auth tokens) */
505
- onReset?: () => void | Promise<void>;
506
- /**
507
- * Fires at most once per attempt, once it passes
508
- * `slowConnectThresholdMs` without settling. The attempt keeps
509
- * running; this is a hint, not a timeout.
510
- */
386
+ /** Fires at most once per attempt, once it passes `slowConnectThresholdMs`
387
+ * without settling. A hint, not a timeout. */
511
388
  onSlowConnect?: (connectorId: string) => void;
512
- /**
513
- * Persistence is fire-and-forget: a failed write (quota, cookie size,
514
- * cross-tab conflict) never breaks reducer state, it only surfaces
515
- * here. Defaults to `console.warn`.
516
- */
517
- onStorageError?: (error: ErrorCause, context: string) => void;
389
+ /** Persistence is fire-and-forget: a failed write never breaks state, it
390
+ * only surfaces here. Defaults to `console.warn`. */
391
+ onStorageError?: (error: Error) => void;
518
392
  /** Threshold for `onSlowConnect`, in milliseconds. Defaults to 5_000. */
519
393
  slowConnectThresholdMs?: number;
520
- /** Optional custom persistence implementation (e.g., cookie-backed) */
394
+ /** Where adapters come from: `autoDiscovery()`, any `discover*Adapters`
395
+ * export, or `fromAdapters(…)` for WalletConnect, Ledger and hand-rolled
396
+ * adapters. The first adapter announced for an id wins. */
397
+ sources?: ReadonlyArray<WalletSource>;
398
+ /** Replaces the default localStorage + sessionStorage persistence. */
521
399
  storage?: WalletPersistence;
522
- /** Storage key prefix for localStorage */
400
+ /** Key prefix for the default persistence. Ignored when `storage` is set;
401
+ * pass the same value to `readWalletSnapshot`. */
523
402
  storageKeyPrefix?: string;
524
403
  };
525
404
  //#endregion
526
- //#region src/types/signer.d.ts
527
- /**
528
- * Canonical cast target for `getSigner()`'s `unknown`, which stays
529
- * type-erased to keep the cross-package boundary loose. Platform
530
- * packages add their key by augmenting this interface.
531
- */
532
- interface SignerForPlatform {}
533
- /** Convenience alias for narrowing a single platform's signer type. */
534
- type SignerOf<P extends keyof SignerForPlatform> = SignerForPlatform[P];
535
- //#endregion
536
- //#region src/wallet-source.d.ts
537
- /**
538
- * The discovery seam: implementable by third parties without depending
539
- * on `@usebutr/wallets`, which itself just composes the per-platform
540
- * sources into one.
541
- */
542
- type WalletSource = {
543
- subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void;
405
+ //#region src/chains.d.ts
406
+ /** Common EVM chains. `switchChain` accepts any `ChainBase`, so a chain
407
+ * missing here still works; it is just named by its CAIP-2 id. */
408
+ declare const EVM_CHAINS: {
409
+ readonly arbitrum: {
410
+ readonly id: "eip155:42161";
411
+ readonly name: "Arbitrum One";
412
+ readonly namespace: "eip155";
413
+ readonly reference: "42161";
414
+ };
415
+ readonly base: {
416
+ readonly id: "eip155:8453";
417
+ readonly name: "Base";
418
+ readonly namespace: "eip155";
419
+ readonly reference: "8453";
420
+ };
421
+ readonly bsc: {
422
+ readonly id: "eip155:56";
423
+ readonly name: "BNB Smart Chain";
424
+ readonly namespace: "eip155";
425
+ readonly reference: "56";
426
+ };
427
+ readonly ethereum: {
428
+ readonly id: "eip155:1";
429
+ readonly name: "Ethereum";
430
+ readonly namespace: "eip155";
431
+ readonly reference: "1";
432
+ };
433
+ readonly optimism: {
434
+ readonly id: "eip155:10";
435
+ readonly name: "Optimism";
436
+ readonly namespace: "eip155";
437
+ readonly reference: "10";
438
+ };
439
+ readonly polygon: {
440
+ readonly id: "eip155:137";
441
+ readonly name: "Polygon";
442
+ readonly namespace: "eip155";
443
+ readonly reference: "137";
444
+ };
445
+ readonly sepolia: {
446
+ readonly id: "eip155:11155111";
447
+ readonly name: "Sepolia";
448
+ readonly namespace: "eip155";
449
+ readonly reference: "11155111";
450
+ };
544
451
  };
545
- /**
546
- * Takes the exact shape of `discoverEvmAdapters` and friends, so a
547
- * single-platform app keeps `@usebutr/wallets` out of its bundle.
548
- */
549
- declare const createWalletSource: (subscribe: (onAdapter: (adapter: WalletAdapter) => void) => () => void) => WalletSource;
452
+ declare const SVM_CHAINS: {
453
+ readonly devnet: {
454
+ readonly id: "solana:devnet";
455
+ readonly name: "Solana Devnet";
456
+ readonly namespace: "solana";
457
+ readonly reference: "devnet";
458
+ };
459
+ readonly mainnet: {
460
+ readonly id: "solana:mainnet";
461
+ readonly name: "Solana Mainnet";
462
+ readonly namespace: "solana";
463
+ readonly reference: "mainnet";
464
+ };
465
+ readonly testnet: {
466
+ readonly id: "solana:testnet";
467
+ readonly name: "Solana Testnet";
468
+ readonly namespace: "solana";
469
+ readonly reference: "testnet";
470
+ };
471
+ };
472
+ declare const SUI_CHAINS: {
473
+ readonly devnet: {
474
+ readonly id: "sui:devnet";
475
+ readonly name: "Sui Devnet";
476
+ readonly namespace: "sui";
477
+ readonly reference: "devnet";
478
+ };
479
+ readonly localnet: {
480
+ readonly id: "sui:localnet";
481
+ readonly name: "Sui Localnet";
482
+ readonly namespace: "sui";
483
+ readonly reference: "localnet";
484
+ };
485
+ readonly mainnet: {
486
+ readonly id: "sui:mainnet";
487
+ readonly name: "Sui Mainnet";
488
+ readonly namespace: "sui";
489
+ readonly reference: "mainnet";
490
+ };
491
+ readonly testnet: {
492
+ readonly id: "sui:testnet";
493
+ readonly name: "Sui Testnet";
494
+ readonly namespace: "sui";
495
+ readonly reference: "testnet";
496
+ };
497
+ };
498
+ /** CAIP-2 Bitcoin references are the first 16 bytes of each network's
499
+ * genesis block hash, which is what wallets advertise in `wallet.chains`. */
500
+ declare const BITCOIN_CHAINS: {
501
+ readonly mainnet: {
502
+ readonly id: "bip122:000000000019d6689c085ae165831e93";
503
+ readonly name: "Bitcoin";
504
+ readonly namespace: "bip122";
505
+ readonly reference: "000000000019d6689c085ae165831e93";
506
+ };
507
+ readonly signet: {
508
+ readonly id: "bip122:00000008819873e925422c1ff0f99f7c";
509
+ readonly name: "Bitcoin Signet";
510
+ readonly namespace: "bip122";
511
+ readonly reference: "00000008819873e925422c1ff0f99f7c";
512
+ };
513
+ readonly testnet: {
514
+ readonly id: "bip122:000000000933ea01ad0ee984209779ba";
515
+ readonly name: "Bitcoin Testnet";
516
+ readonly namespace: "bip122";
517
+ readonly reference: "000000000933ea01ad0ee984209779ba";
518
+ };
519
+ readonly testnet4: {
520
+ readonly id: "bip122:00000000da84f2bafbbc53dee25a72ae";
521
+ readonly name: "Bitcoin Testnet4";
522
+ readonly namespace: "bip122";
523
+ readonly reference: "00000000da84f2bafbbc53dee25a72ae";
524
+ };
525
+ };
526
+ declare const POLKADOT_CHAINS: {
527
+ readonly kusama: {
528
+ readonly id: "polkadot:b0a8d493285c2df73290dfb7e61f870f";
529
+ readonly name: "Kusama";
530
+ readonly namespace: "polkadot";
531
+ readonly reference: "b0a8d493285c2df73290dfb7e61f870f";
532
+ };
533
+ readonly paseo: {
534
+ readonly id: "polkadot:77afd6190f1554ad45fd0d31aee62aac";
535
+ readonly name: "Paseo";
536
+ readonly namespace: "polkadot";
537
+ readonly reference: "77afd6190f1554ad45fd0d31aee62aac";
538
+ };
539
+ readonly polkadot: {
540
+ readonly id: "polkadot:91b171bb158e2d3848fa23a9f1c25182";
541
+ readonly name: "Polkadot";
542
+ readonly namespace: "polkadot";
543
+ readonly reference: "91b171bb158e2d3848fa23a9f1c25182";
544
+ };
545
+ readonly westend: {
546
+ readonly id: "polkadot:e143f23803ac50e8f6f8e62695d1ce9e";
547
+ readonly name: "Westend";
548
+ readonly namespace: "polkadot";
549
+ readonly reference: "e143f23803ac50e8f6f8e62695d1ce9e";
550
+ };
551
+ };
552
+ declare const EVM_CHAINS_LIST: ReadonlyArray<ChainBase>;
553
+ declare const SVM_CHAINS_LIST: ReadonlyArray<ChainBase>;
554
+ declare const SUI_CHAINS_LIST: ReadonlyArray<ChainBase>;
555
+ declare const BITCOIN_CHAINS_LIST: ReadonlyArray<ChainBase>;
556
+ declare const POLKADOT_CHAINS_LIST: ReadonlyArray<ChainBase>;
557
+ /** Every registry as a list, keyed by platform: the shape chain pickers want. */
558
+ declare const CHAINS_BY_PLATFORM: ChainsByPlatform;
550
559
  //#endregion
551
560
  //#region src/store/reducer.d.ts
552
- /**
553
- * The reducer never writes `"reconnecting"`; `useConnectionStatus`
554
- * derives it from `reconnectingIds` so the state machine stays narrow
555
- * while the public vocabulary matches wagmi's.
556
- */
557
- type ConnectionStatus = "idle" | "connecting" | "success" | "error" | "reconnecting";
558
- type State = {
561
+ /** Status of the latest `connect` attempt, not of the connection itself;
562
+ * `useConnectionStatus` derives the latter. */
563
+ type ConnectStatus = "idle" | "connecting" | "success" | "error";
564
+ type WalletState = {
559
565
  activeConnectorId: string | null;
566
+ /** Every adapter the sources announced, in announcement order. */
567
+ adapters: ReadonlyArray<WalletAdapter>;
560
568
  connectingConnectorId: string | null;
561
569
  connectionError: ConnectionError | null;
562
- connectionStatus: ConnectionStatus;
563
- isHydrated: boolean;
564
- isUserDisconnected: boolean;
565
- pool: Map<string, ConnectedWallet>;
570
+ connectionStatus: ConnectStatus;
566
571
  /**
567
- * Ids still backed by a shadow adapter from `initialState`. Empty
568
- * without `initialState`; shrinks on `HYDRATED` / `CONNECT_SUCCEEDED`
569
- * and on `DISCONNECTED` / `RESET`.
572
+ * Persisted connections that are not live: awaiting their adapter, failed
573
+ * their silent reconnect, or ended by the wallet rather than the user.
574
+ * They stay persisted so the next load retries them.
570
575
  */
576
+ dormant: ReadonlyMap<string, StoredPoolEntry>;
577
+ isHydrated: boolean;
578
+ isUserDisconnected: boolean;
579
+ pool: ReadonlyMap<string, ConnectedWallet>;
580
+ /** Pool ids still backed by a shadow adapter seeded from `initialState`. */
571
581
  reconnectingIds: ReadonlySet<string>;
572
- selection: Map<ChainPlatform, string>;
582
+ /** Which pool entry serves each platform present in the pool. */
583
+ selection: ReadonlyMap<ChainPlatform, string>;
573
584
  };
574
585
  //#endregion
575
586
  //#region src/store/shadow-adapter.d.ts
576
587
  /**
577
- * Loud failure for consumers that called a wallet method without gating
578
- * on `capabilities.*` (all `false` here) or `reconnectingIds`, before
579
- * silent reconnect swapped in the live adapter.
588
+ * Thrown by the three required methods of a shadow adapter. Every optional
589
+ * method is simply absent, so only code that skipped the `reconnectingIds`
590
+ * check and called `connect`, `getAccounts` or `getSigner` sees this.
580
591
  */
581
- declare class ShadowConnectorError extends Error implements CodedError {
582
- readonly code = "BUTR_RECONNECTING";
592
+ declare class ShadowConnectorError extends Error {
583
593
  readonly connectorId: string;
584
594
  readonly method: string;
585
595
  constructor(method: string, connectorId: string);
586
596
  }
597
+ //#endregion
598
+ //#region src/store/wallet-manager.d.ts
599
+ type WalletManager = Pick<StoreApi<WalletState>, "getInitialState" | "getState" | "subscribe"> & {
600
+ /** Clears `connectionError` and returns `connectionStatus` to idle. */
601
+ clearConnectionError: () => void;
602
+ /** Resolves the connected wallet; rejects with a `ConnectionError`. */
603
+ connect: (connectorId: string) => Promise<ConnectedWallet>;
604
+ disconnect: (connectorId: string) => void;
605
+ /** Disconnects every wallet and forgets every persisted connection. */
606
+ disconnectAll: () => void;
607
+ /** Opens the wallet's account picker, then refreshes the pool entry. A no-op
608
+ * for wallets without `requestAccounts`. */
609
+ requestAccounts: (connectorId: string) => Promise<void>;
610
+ /** Selects an exposed account without changing its chain or the account
611
+ * list. Unknown wallets and accounts are ignored. */
612
+ setAccount: (connectorId: string, account: Account) => void;
613
+ setActive: (connectorId: string) => void;
614
+ setSelection: (chainPlatform: ChainPlatform, connectorId: string) => void;
615
+ /**
616
+ * Subscribes the sources, hydrates persisted connections (once per
617
+ * manager) and bridges wallet events. The returned function undoes all of
618
+ * it except hydration, so a StrictMode remount is safe.
619
+ */
620
+ start: () => () => void;
621
+ };
587
622
  /**
588
- * Structural detection: relies on every live adapter advertising at
589
- * least one capability, since `getBalance`, `signMessage` and
590
- * `switchChain` are required on all wallet surfaces.
623
+ * Framework-free: React binds it through `WalletManagerProvider`, anything
624
+ * else calls `start()` itself. Creating one has no side effects, so it is
625
+ * safe during a server render.
591
626
  */
592
- declare const isShadowAdapter: (adapter: WalletAdapter) => boolean;
627
+ declare const createWalletManager: (config?: WalletManagerConfig, options?: {
628
+ initialState?: WalletSnapshot;
629
+ }) => WalletManager;
593
630
  //#endregion
594
631
  //#region src/storage/browser-storage-driver.d.ts
595
632
  type BrowserStorageDrivers = {
@@ -648,8 +685,28 @@ type CookieDriverOptions = {
648
685
  */
649
686
  declare const createCookieStorageDriver: (options?: CookieDriverOptions) => StorageDriver;
650
687
  //#endregion
688
+ //#region src/storage/snapshot.d.ts
689
+ type CookieSource = Iterable<{
690
+ name: string;
691
+ value: string;
692
+ }> | Iterable<[string, string]> | Readonly<Record<string, string>>;
693
+ type SnapshotOptions = {
694
+ /**
695
+ * Same prefix passed as `storageKeyPrefix` / to `createWalletStorage`.
696
+ * Defaults to `"butr"` to match the library default.
697
+ */
698
+ keyPrefix?: string;
699
+ };
700
+ declare const EMPTY_SNAPSHOT: WalletSnapshot;
701
+ /**
702
+ * Optimistic: only what the browser last persisted, so an uninstall or
703
+ * other-tab disconnect makes it stale. Authoritative once the entry
704
+ * leaves `reconnectingIds`; `isHydrated` is true from render one.
705
+ */
706
+ declare const readWalletSnapshot: (source: CookieSource, options?: SnapshotOptions) => WalletSnapshot;
707
+ //#endregion
651
708
  //#region src/storage/wallet-storage.d.ts
652
- type StorageConfig = {
709
+ type WalletStorageOptions = {
653
710
  /** Defaults to `"butr"`, matching `readWalletSnapshot`. */
654
711
  keyPrefix?: string;
655
712
  /** Survives app restart. Defaults to localStorage on web. */
@@ -657,73 +714,12 @@ type StorageConfig = {
657
714
  /** Cleared on session end. Defaults to sessionStorage on web. */
658
715
  session?: StorageDriver;
659
716
  };
660
- declare class WalletStorage implements WalletPersistence {
661
- private readonly poolKey;
662
- private readonly selectionKey;
663
- private readonly activeKey;
664
- private readonly userDisconnectedKey;
665
- private readonly persistent;
666
- private readonly session;
667
- /**
668
- * Serializes pool read-modify-writes; without it concurrent `setPool`
669
- * calls drop each other's entries. Not reentrant, so a queued mutation
670
- * must read via `readPool`: `getPool` re-enters and deadlocks it.
671
- */
672
- private poolMutationQueue;
673
- constructor(config: StorageConfig);
674
- /** Chain `fn` after the in-flight pool mutation. */
675
- private serializePoolMutation;
676
- /** Read and decode the pool without touching the mutation queue. Safe to
677
- * call from inside a queued mutation, unlike `getPool`. */
678
- private readPool;
679
- getPool(): Promise<StoredPoolRecord>;
680
- /**
681
- * Additive on purpose: a failed silent reconnect drops the entry from
682
- * the live pool, and it must survive to be retried next load.
683
- * Eviction goes through `removePoolEntry` or `clearAll`.
684
- */
685
- setPool(pool: Map<string, ConnectedWallet>): Promise<void>;
686
- removePoolEntry(connectorId: string): Promise<void>;
687
- clearPool(): Promise<void>;
688
- getSelection(): Promise<StoredSelectionRecord>;
689
- setSelection(selection: Map<ChainPlatform, string>): Promise<void>;
690
- getActiveConnectorId(): Promise<string | null>;
691
- setActiveConnectorId(connectorId: string | null): Promise<void>;
692
- clearAll(): Promise<void>;
693
- /**
694
- * Kept in the session driver so it survives remounts (unlike a ref)
695
- * yet clears at session end (unlike the persistent driver): a manual
696
- * disconnect must suppress auto-connect now, not forever.
697
- */
698
- isUserDisconnected(): Promise<boolean>;
699
- markUserDisconnected(value: boolean): Promise<void>;
700
- }
701
- //#endregion
702
- //#region src/store/wallet-store.d.ts
703
- type ExtractState<S> = S extends {
704
- getState: () => infer T;
705
- } ? T : never;
706
- type RuntimeMembers = {
707
- _config: WalletManagerConfig;
708
- _storage: WalletPersistence;
709
- connectWallet: (connectorId: string, onSuccess?: (wallet: ConnectedWallet) => void, onError?: (error: Error) => void) => Promise<void>;
710
- disconnectWallet: (connectorId: string) => void;
711
- getConnectorInstance: (id: string) => ReturnType<WalletManagerConfig["createConnector"]>;
712
- hydrateWallets: () => Promise<void>;
713
- refreshWallet: (connectorId: string) => void;
714
- requestAccounts: (connectorId: string) => Promise<void>;
715
- reset: () => void;
716
- resetConnectionStatus: () => void;
717
- setActiveConnector: (connectorId: string | null) => void;
718
- setConnectionError: (error: ConnectionError | null) => void;
719
- setSelection: (chainPlatform: ChainPlatform, connectorId: string | null) => void;
720
- setUserDisconnected: (value: boolean) => void;
721
- tryRestoreFromPending: (connectorId: string) => Promise<void>;
722
- updateWalletAccount: (connectorId: string, account: Account) => void;
723
- };
724
- type WalletStore = ReturnType<typeof createWalletStore>;
725
- type WalletStoreState = ExtractState<WalletStore>;
726
- declare const createWalletStore: (config: WalletManagerConfig) => import("zustand/vanilla").StoreApi<State & RuntimeMembers>;
717
+ /**
718
+ * Pool, selection and active id go to the persistent driver, the disconnect
719
+ * intent to the session driver. Unchanged keys are not rewritten, so a
720
+ * cookie-backed driver does not re-send every cookie on each save.
721
+ */
722
+ declare const createWalletStorage: (options?: WalletStorageOptions) => WalletPersistence;
727
723
  //#endregion
728
724
  //#region src/group-by-platform.d.ts
729
725
  /**
@@ -797,10 +793,12 @@ declare class SignInUnsupportedError extends Error {
797
793
  declare const createSignInFlow: (options: SignInFlowOptions) => SignInFlow;
798
794
  //#endregion
799
795
  //#region src/wallet-equal.d.ts
796
+ /** Account ids are canonical (`buildAccount`), so identity is the id. */
797
+ declare const accountsEqual: (a: ReadonlyArray<Account>, b: ReadonlyArray<Account>) => boolean;
800
798
  /**
801
799
  * The adapter is compared by reference, not `connector.id`: hydration
802
800
  * swaps a shadow adapter for the live one under an unchanged id, and an
803
- * id check would strand consumers on the throwing placeholder.
801
+ * id check would strand consumers on the placeholder.
804
802
  */
805
803
  declare const walletEqual: (a: ConnectedWallet | undefined, b: ConnectedWallet | undefined) => boolean;
806
804
  //#endregion
@@ -849,5 +847,5 @@ declare const bytesToBase58: (bytes: Uint8Array) => string;
849
847
  * rather than silently dropping them. */
850
848
  declare const base58ToBytes: (input: string) => Uint8Array;
851
849
  //#endregion
852
- 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 SignInInput, type SignInMessageContext, type SignInResult, SignInUnsupportedError, type SignInValue, type SignerForPlatform, type SignerOf, type SnapshotOptions, type StorageDriver, type StoredPoolEntry, type StoredPoolRecord, type StoredSelectionRecord, type SuiAdapter, type SuiWallet, type SvmAdapter, type SvmWallet, type TransactionInput, type TransactionMethod, type TransactionObject, type TransactionValue, type WalletAdapter, type WalletAvailability, type WalletBase, type WalletCapabilities, type WalletManagerConfig, type WalletPersistence, type WalletSigner, 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 };
850
+ export { type Account, type AccountOptions, BITCOIN_CHAINS, BITCOIN_CHAINS_LIST, type Balance, type BalanceOptions, type BitcoinAdapter, type BitcoinTransfer, type BitcoinWallet, type BrowserStorageDrivers, CHAINS_BY_PLATFORM, CHAIN_PLATFORMS, type ChainBase, type ChainPlatform, type ChainsByPlatform, type ConnectStatus, type ConnectedWallet, ConnectionError, type ConnectionErrorKind, type Connector, type ConnectorEvent, type CookieDriverOptions, type CookieSource, EMPTY_SNAPSHOT, EVM_CHAINS, EVM_CHAINS_LIST, type EvmAdapter, type EvmTransactionRequest, type EvmTransactionValue, type EvmWallet, type HydrationOutcome, type InitialCookies, type MaybePromise, POLKADOT_CHAINS, POLKADOT_CHAINS_LIST, type PersistedWalletState, type PlatformDiscoverer, type PolkadotAdapter, type PolkadotWallet, SUI_CHAINS, SUI_CHAINS_LIST, SVM_CHAINS, SVM_CHAINS_LIST, ShadowConnectorError, type SignInFlowOptions, type SignInInput, type SignInMessageContext, type SignInOutput, type SignInResult, SignInUnsupportedError, type SignInValue, type SignedMessage, type SnapshotOptions, type StorageDriver, type StoredPoolEntry, type StoredPoolRecord, type StoredSelectionRecord, type SuiAdapter, type SuiTransactionInput, type SuiWallet, type SvmAdapter, type SvmWallet, type TransactionOptions, type TransactionReceipt, type WalletAdapter, type WalletAdapterFor, type WalletBase, type WalletManager, type WalletManagerConfig, type WalletPersistence, type WalletSigner, type WalletSignerKind, type WalletSignerOf, type WalletSignerRegistry, type WalletSnapshot, type WalletSource, type WalletState, type WalletStorageOptions, accountsEqual, base58ToBytes, base64ToBytes, buildAccount, bytesToBase58, bytesToBase64, bytesToHex, bytesToHexPrefixed, createBrowserStorageDriver, createCookieStorageDriver, createMemoryStorageDriver, createSignInFlow, createWalletManager, createWalletStorage, fromAdapters, groupByPlatform, hexToBytes, isChainPlatform, isPlatformWallet, logError, logWarn, readWalletSnapshot, resolveChain, sanitizeIcon, toConnectionError, walletEqual };
853
851
  //# sourceMappingURL=index.d.ts.map