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