@usebutr/wallet-standard-shared 0.5.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/CHANGELOG.md ADDED
@@ -0,0 +1,227 @@
1
+ # @usebutr/wallet-standard-shared
2
+
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#203](https://github.com/pedroapfilho/usebutr/pull/203) [`1263f86`](https://github.com/pedroapfilho/usebutr/commit/1263f8666afd43b4f7dbf8df1763709c3edf09d9) Thanks [@pedroapfilho](https://github.com/pedroapfilho)! - Adapters now say what they can do by having the method: `capabilities` is gone, and an optional method (`sendTx`, `signMessage`, `getBalance`, `switchChain`, …) exists only when it works for that wallet. No adapter returns a placeholder balance or a receipt that stays pending anymore.
8
+
9
+ - `sendTx(tx, { account, chain })` replaces `sendTx` and `sendTxToChain`, and `signMessage`, `signTransaction` and `getBalance` take options objects. `chain` is a `ChainBase`. An adapter either routes the call to that chain, switches the wallet to it, or rejects; none ignores it.
10
+ - An `account` the wallet does not expose now rejects instead of signing with the first account. `chain.name` is the chain's name, never the wallet's.
11
+ - `getAccounts()` resolves the active account first. `getAccount` and `switchAccount` are removed.
12
+ - `getSigner()` resolves a tagged `WalletSigner` (`eip1193`, `wallet-standard`, `walletconnect`, `ledger-*`, …), so it can be narrowed with `switch (signer.kind)` instead of cast.
13
+ - `createWalletManager(config, { initialState })` owns discovery, hydration and persistence, and works without React. `config.sources` replaces the `discovery`, `connectors` and `createConnector` props. `fromAdapters` registers WalletConnect, Ledger and hand-built adapters, which now also appear in `useDiscoveredWallets()`.
14
+ - `WalletManagerProvider` takes `config` and `initialState`. `useSigner(wallet)` and `useBalance(wallet, { token, account })` take the wallet entry itself, and id-taking hooks read the active wallet only when the id is omitted (`null` reads none). The hooks are `useWalletManager`, `useWalletState`, `useConnect`, `useWallet`, `useSelectedWallet` (now typed per platform), `useAccounts`, `useConnectionStatus`, `useIsHydrated`, `useIsReconnecting`, `useDiscoveredWallets`, `useConnectedWallets`, the `…ByPlatform` groupings, `useBalance` and `useSigner`.
15
+ - `manager.connect()` and `useConnect().connectAsync()` resolve the `ConnectedWallet` and reject with a `ConnectionError`, now an `Error` subclass with a `kind` and a new `WalletNotFound` kind. `useConnect().connect()` never rejects: the failure lands in its `error`.
16
+ - `WalletPersistence` is `load()` / `save(state)`, and `createWalletStorage` replaces `new WalletStorage`. Storage keys are unchanged.
17
+ - Chain registries (`EVM_CHAINS`, `SVM_CHAINS`, …, `CHAINS_BY_PLATFORM`) move to `@usebutr/core`, and `BITCOIN_CHAINS` gains `testnet4`. `@usebutr/wallets` loads every platform's signer kinds, so an app that imports only it can narrow `signer.kind`.
18
+ - `@usebutr/react` requires React 19, which its `use()` call already needed.
19
+ - WalletConnect requests on Solana, Sui and Bitcoin now carry their chain, so they no longer fall through to the first namespace in a multi-namespace session. Solana takes `SVM_CHAINS` chains and maps them to the genesis-hash ids WalletConnect sessions use.
20
+ - Ledger's Solana `signTransaction` returns the full signed transaction, Sui signs the intent message, and Bitcoin `signMessage` returns a BIP-137 signature.
21
+ - `@usebutr/testing`'s fake adapter resolves a `signer` you pass instead of registering a test-only signer kind.
22
+ - `setAccount` selects an exposed account without changing any account's chain. Unknown accounts are ignored; chain changes come from the adapter.
23
+ - WalletConnect EVM events use the namespace in `session_event`, including empty account lists that end the connection. Local chain switches still update the manager.
24
+ - Failed persistence saves wait for every key write to finish before reporting failure, so delayed writes cannot overwrite a later disconnect.
25
+ - `@usebutr/svm/transaction` provides the shared legacy/v0 signing codec used by Ledger and WalletConnect, with consistent layout and signature validation.
26
+
27
+ See the migration guide at https://docs.usebutr.com/migration.
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies [[`1263f86`](https://github.com/pedroapfilho/usebutr/commit/1263f8666afd43b4f7dbf8df1763709c3edf09d9), [`5d1f234`](https://github.com/pedroapfilho/usebutr/commit/5d1f23403263f7a1f8a48be061727972fe749b5f)]:
32
+ - @usebutr/core@3.0.0
33
+
34
+ ## 1.0.0
35
+
36
+ ### Major Changes
37
+
38
+ - [#179](https://github.com/pedroapfilho/usebutr/pull/179) [`bc5b58c`](https://github.com/pedroapfilho/usebutr/commit/bc5b58cd87601be38dbb77a14e16eaaa36e8f012) Thanks [@pedroapfilho](https://github.com/pedroapfilho)! - Promote to 1.0.0 for the public launch. No API changes in this promotion; the bump marks the hook and adapter surface as stable under semver. Include package documentation, licensing, and npm metadata.
39
+
40
+ ### Patch Changes
41
+
42
+ - Updated dependencies [[`bc5b58c`](https://github.com/pedroapfilho/usebutr/commit/bc5b58cd87601be38dbb77a14e16eaaa36e8f012)]:
43
+ - @usebutr/core@2.0.1
44
+
45
+ ## 0.5.1
46
+
47
+ ### Patch Changes
48
+
49
+ - 302ae4a: Fix Wallet Standard connections failing with "Failed to get account"
50
+
51
+ Discovery mapped each wallet into a plain copy, which read `accounts` once, before connect, and pinned the empty list. Every `getAccount()` after a successful `standard:connect` then resolved to `null` and the connection was rejected. The wrapper now reads `accounts` and `chains` through to the wallet.
52
+
53
+ The same copy minted a new object per event, so `unregister` no longer matched the wallet seen at `register` and a removed extension never tore its pool entry down. Wrappers are now memoised per wallet. Feature objects are also passed through rather than spread, keeping their methods bound to the wallet.
54
+
55
+ Affects every Wallet Standard platform: `@usebutr/svm`, `@usebutr/sui`, `@usebutr/bitcoin` and `@usebutr/polkadot`.
56
+
57
+ ## 0.5.0
58
+
59
+ ### Minor Changes
60
+
61
+ - 9b1caa2: Correctness pass across the storage, hydration and connector layers. Several of
62
+ these change observable behaviour; the ones worth knowing about before upgrading:
63
+
64
+ **Breaking**
65
+
66
+ - `SuiWallet.signTransaction` now returns `{ bytes, signature }` instead of
67
+ `Uint8Array`. Sui's `executeTransactionBlock` needs both, and each connector
68
+ previously returned a different half, so the old value could not be broadcast
69
+ and a consumer could not tell which half they held. SVM and Bitcoin keep their
70
+ single-`Uint8Array` shape.
71
+ - `@usebutr/svm`'s `sendTx` returns the signature base58-encoded, matching the
72
+ WalletConnect namespace, the Ledger app, Solana explorers and
73
+ `getSignatureStatuses`. It previously returned base64.
74
+ - `@usebutr/sui`'s feature input types drop the `string` arm from
75
+ `transaction`; wallets only ever accepted the `toJSON()` form. Strings and BCS
76
+ bytes are now wrapped for you, so `sendTx` accepts strictly more than before.
77
+
78
+ **Behaviour fixes**
79
+
80
+ - `WalletStorage.setPool` and `removePoolEntry` could self-deadlock on a corrupt
81
+ pool payload, leaving `connectWallet` unresolved and every later pool write
82
+ jammed for the page's life. Both are fixed, and `readWalletSnapshot` now shares
83
+ one codec with `WalletStorage` so the server and client decodes cannot drift.
84
+ - A seeded (SSR) entry whose silent reconnect failed used to stay in the pool
85
+ backed by a placeholder connector forever, so `useSigner`/`useBalance`
86
+ reported an error and `useConnectionStatus` reported `"reconnecting"`
87
+ permanently. Those entries are now dropped.
88
+ - `useSigner` and `useBalance` stay `idle` for a wallet that is still
89
+ reconnecting instead of surfacing a placeholder rejection as `status: "error"`.
90
+ - `useConnectionStatus` lets a live `"connecting"`/`"error"` take precedence over
91
+ the derived `"reconnecting"`, so an in-flight attempt and a connection error
92
+ are no longer hidden. New `useIsReconnecting(connectorId?)` answers the
93
+ per-wallet question directly.
94
+ - A background restore no longer writes the connect-attempt status or steals
95
+ `activeConnectorId`, and a superseded `CONNECT_FAILED` no longer overwrites the
96
+ current attempt.
97
+ - An externally-disconnected connector is now torn down, not just dropped from
98
+ the pool, so a cached adapter is not reused while still holding a session.
99
+ - EVM: a malformed provider response is treated as absent rather than `""`, which
100
+ previously produced chain `eip155:0`, zero-length signatures, empty transaction
101
+ hashes and a confident `0 ETH`. `switchChain` gained the same-chain
102
+ short-circuit `sendTxToChain` already had.
103
+ - WalletConnect: accounts carry their own chain instead of all being stamped with
104
+ the active one, so a multi-chain session no longer returns a wrong-chain
105
+ address. One pairing now covers every configured namespace, the pairing URI
106
+ survives a reconnect, and concurrent connects share one pairing.
107
+ - Bitcoin: the sats-connect (Xverse) adapter honours `silent`, so a reload no
108
+ longer triggers an unsolicited approval prompt, and exposes the payment address
109
+ as its single account instead of also presenting the ordinals address as
110
+ spendable.
111
+ - Polkadot: the injected adapter actually delivers events to subscribers,
112
+ `switchChain` notifies like its Wallet Standard sibling, and chain resolution
113
+ prefers the mainnets rather than whatever the wallet listed first.
114
+ - Ledger: the device session is committed atomically, so a locked device no
115
+ longer leaves an open transport that reports itself connected, and concurrent
116
+ connects open one transport.
117
+ - SVM: `sendTxToChain` submits to the chain you asked for instead of the
118
+ adapter's current one, and rejects a chain the wallet does not advertise.
119
+ - `@usebutr/svm`'s `switchChain` capability now counts only `solana:` chains, so
120
+ a multi-namespace wallet no longer advertises a method that always throws.
121
+
122
+ **Testing**
123
+
124
+ - `createFakePersistence` is now the real `WalletStorage` over memory drivers
125
+ rather than a parallel implementation, so it inherits upsert semantics,
126
+ validation and JSON round-tripping. Two divergences are fixed as a result:
127
+ `setPool` upserts rather than replaces, and `clearAll` leaves the
128
+ user-disconnected flag alone.
129
+ - `createFakeConnectedWallet` rejects being given both an `adapter` and explicit
130
+ `addresses`/`accounts`, a combination whose halves could disagree.
131
+
132
+ ### Patch Changes
133
+
134
+ - Updated dependencies [9b1caa2]
135
+ - @usebutr/core@2.0.0
136
+
137
+ ## 0.4.1
138
+
139
+ ### Patch Changes
140
+
141
+ - Updated dependencies [8ecaf89]
142
+ - @usebutr/core@1.1.0
143
+
144
+ ## 0.4.0
145
+
146
+ ### Minor Changes
147
+
148
+ - 7887cf0: Export `buildWalletCapabilities`, the Wallet Standard capability mapping that `@usebutr/svm`, `@usebutr/sui`, `@usebutr/bitcoin` and `@usebutr/polkadot` each re-implemented. It takes a flat feature profile and owns the flags that are constant across every namespace, so only the feature-name mapping stays per-package.
149
+
150
+ ### Patch Changes
151
+
152
+ - Updated dependencies [7887cf0]
153
+ - Updated dependencies [7887cf0]
154
+ - Updated dependencies [f0a5116]
155
+ - Updated dependencies [f0a5116]
156
+ - @usebutr/core@1.0.0
157
+
158
+ ## 0.3.0
159
+
160
+ ### Minor Changes
161
+
162
+ - efe4550: Share the session plumbing behind the Wallet Standard and WalletConnect CAIP adapters. `@usebutr/wallet-standard-shared` now exports `createWalletStandardCore`, which the Bitcoin, Polkadot, Sui and SVM adapters build on; the WalletConnect Bitcoin, Sui and SVM namespaces share an equivalent CAIP core. Every package's public API is unchanged. The one visible difference is a dev-console warning: a failed SVM disconnect now logs `[butr] SVM Wallet Standard disconnect threw:` instead of `[butr] Wallet Standard disconnect threw:`.
163
+
164
+ ### Patch Changes
165
+
166
+ - Updated dependencies [4467a5e]
167
+ - @usebutr/core@0.5.0
168
+
169
+ ## 0.2.6
170
+
171
+ ### Patch Changes
172
+
173
+ - Updated dependencies [c1309ee]
174
+ - @usebutr/core@0.4.2
175
+
176
+ ## 0.2.5
177
+
178
+ ### Patch Changes
179
+
180
+ - 937dfae: Bump runtime dependency floors (`@wallet-standard/app` 1.1.1, `@ledgerhq/*` latest minors, `@walletconnect/universal-provider` 2.23.10) and modernize public type declarations from method signatures to property function types (oxlint `method-signature-style`). Type-level only — no runtime behavior change.
181
+ - Updated dependencies [937dfae]
182
+ - @usebutr/core@0.4.1
183
+
184
+ ## 0.2.4
185
+
186
+ ### Patch Changes
187
+
188
+ - a46eecd: Ship unminified ESM so downstream bundlers (Vite/esbuild dep pre-bundling) process the package correctly; fixes a ReferenceError in consumer dev servers. The consuming app minifies once at its own build.
189
+ - 3f26776: Warn (once) when `@wallet-standard/app` can't be loaded instead of silently disabling discovery. Wallet Standard discovery (used by the SVM, Sui, Bitcoin and Polkadot connectors) dynamically imports the optional peer dep `@wallet-standard/app`; when the import failed it was swallowed, so no wallets appeared with no hint why. It now logs a single actionable warning, forwarding the underlying error.
190
+ - Updated dependencies [b5322ae]
191
+ - Updated dependencies [d5f32c7]
192
+ - Updated dependencies [a46eecd]
193
+ - @usebutr/core@0.4.0
194
+
195
+ ## 0.2.3
196
+
197
+ ### Patch Changes
198
+
199
+ - Updated dependencies [886ee1d]
200
+ - @usebutr/core@0.3.0
201
+
202
+ ## 0.2.2
203
+
204
+ ### Patch Changes
205
+
206
+ - Updated dependencies [db5d7e9]
207
+ - @usebutr/core@0.2.2
208
+
209
+ ## 0.2.1
210
+
211
+ ### Patch Changes
212
+
213
+ - Updated dependencies [f846e77]
214
+ - @usebutr/core@0.2.1
215
+
216
+ ## 0.2.0
217
+
218
+ ### Minor Changes
219
+
220
+ - b77a477: Shared Wallet Standard protocol types (`WalletStandardWallet`, `WalletStandardWalletAccount`, `WalletStandardAppModule`, `WalletsApp`, `StandardConnectFeature`, `StandardDisconnectFeature`, `StandardEventsFeature`, `StandardEventsListener`) are imported from `@usebutr/wallet-standard-shared` directly — they are no longer re-exported from `@usebutr/svm`, `@usebutr/sui`, or `@usebutr/bitcoin`. Platform-specific feature shapes still ship from their own packages.
221
+
222
+ `@usebutr/svm` adapter ids are now platform-prefixed (`wallet-standard:svm-<slug>`), consistent with the `sui-` and `btc-` prefixes. `slugify` in `@usebutr/wallet-standard-shared` requires a non-empty platform prefix.
223
+
224
+ ### Patch Changes
225
+
226
+ - Updated dependencies [b77a477]
227
+ - @usebutr/core@0.2.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pedro Filho
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # @usebutr/wallet-standard-shared
2
+
3
+ Shared Wallet Standard discovery primitives used by @usebutr/svm, @usebutr/sui, @usebutr/bitcoin.
4
+
5
+ Part of [butr](https://www.usebutr.com), a multi-chain wallet discovery and
6
+ connection-state library. Your application owns the picker UI and chain client.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install @usebutr/wallet-standard-shared @wallet-standard/app zustand
12
+ ```
13
+
14
+ For adapter authors. The package supplies discovery, the session plumbing every Wallet Standard adapter shares, and `getFeature`. The default registry loader requires `@wallet-standard/app`; tests can inject a loader. Importing it registers the `wallet-standard` signer kind, so every adapter built on it resolves `getSigner()` to `{ kind: "wallet-standard", wallet }`.
15
+
16
+ ## Usage
17
+
18
+ ```ts
19
+ import type { SvmAdapter } from "@usebutr/core";
20
+ import { SVM_CHAINS_LIST } from "@usebutr/core";
21
+ import type { WalletStandardWallet } from "@usebutr/wallet-standard-shared";
22
+ import { createWalletStandardCore, slugify } from "@usebutr/wallet-standard-shared";
23
+
24
+ export const buildAdapter = (wallet: WalletStandardWallet): SvmAdapter | null => {
25
+ const core = createWalletStandardCore({
26
+ chains: SVM_CHAINS_LIST,
27
+ // Namespace the id so a multi-chain wallet has one adapter per platform.
28
+ id: slugify("svm", wallet.name),
29
+ label: "Solana",
30
+ namespace: "solana",
31
+ preferredChainIds: ["solana:mainnet"],
32
+ trackChainChanges: true,
33
+ wallet,
34
+ });
35
+ // `base` defines `disconnect`, `subscribe` and `switchChain` only when the
36
+ // wallet supports them. Add platform methods the same way: only when the
37
+ // wallet advertises the feature, routing `options.account` through
38
+ // `core.resolveAccount` and `options.chain` through `core.resolveChainId`.
39
+ return core === null ? null : { ...core.base, chainPlatform: "svm" };
40
+ };
41
+ ```
42
+
43
+ ## Documentation
44
+
45
+ - [Package reference](https://docs.usebutr.com/api/wallet-standard-shared)
46
+ - [Getting started](https://docs.usebutr.com/getting-started/quickstart)
47
+ - [Examples and source](https://github.com/pedroapfilho/usebutr)
48
+ - [Report an issue](https://github.com/pedroapfilho/usebutr/issues)
49
+
50
+ ## License
51
+
52
+ [MIT](./LICENSE), copyright 2026 Pedro Filho.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { Account, ChainBase, ConnectorEvent, WalletAdapter, WalletCapabilities, buildAccount } from "@usebutr/core";
1
+ import { Account, ChainBase, Connector, WalletAdapter, WalletSigner } from "@usebutr/core";
2
+ import "@wallet-standard/app";
2
3
  //#region src/types.d.ts
3
4
  /**
4
5
  * Declared inline so butr stays self-contained at the type level;
@@ -9,15 +10,21 @@ type WalletStandardWalletAccount = {
9
10
  address: string;
10
11
  chains: ReadonlyArray<string>;
11
12
  features: ReadonlyArray<string>;
12
- /** Public key bytes; Wallet Standard ships these as Uint8Array. */
13
- publicKey?: Uint8Array;
13
+ publicKey?: {
14
+ readonly length: number;
15
+ readonly [index: number]: number;
16
+ };
17
+ };
18
+ /** Reverse-DNS-keyed and open-ended by spec; `getFeature` is the one place
19
+ * that reads a member off it. */
20
+ type WalletStandardFeature = {
21
+ readonly [member: string]: unknown;
22
+ version?: string;
14
23
  };
15
24
  type WalletStandardWallet = {
16
25
  accounts: ReadonlyArray<WalletStandardWalletAccount>;
17
26
  chains: ReadonlyArray<string>;
18
- /** Map keyed by feature name. Values are feature-specific; narrow at
19
- * use sites. */
20
- features: Readonly<Record<string, unknown>>;
27
+ features: Readonly<Partial<Record<string, WalletStandardFeature>>>;
21
28
  icon: string;
22
29
  name: string;
23
30
  version: string;
@@ -29,15 +36,18 @@ type WalletsApp = {
29
36
  type WalletStandardAppModule = {
30
37
  getWallets: () => WalletsApp;
31
38
  };
39
+ type WalletStandardModuleLoader = () => Promise<WalletStandardAppModule>;
32
40
  type StandardConnectFeature = {
33
41
  connect: (input?: {
34
42
  silent?: boolean;
35
43
  }) => Promise<{
36
44
  accounts: ReadonlyArray<WalletStandardWalletAccount>;
37
45
  }>;
46
+ version?: string;
38
47
  };
39
48
  type StandardDisconnectFeature = {
40
49
  disconnect: () => Promise<void>;
50
+ version?: string;
41
51
  };
42
52
  type StandardEventsListener = (changes: {
43
53
  accounts?: ReadonlyArray<WalletStandardWalletAccount>;
@@ -46,6 +56,7 @@ type StandardEventsListener = (changes: {
46
56
  }) => void;
47
57
  type StandardEventsFeature = {
48
58
  on: (event: "change", listener: StandardEventsListener) => () => void;
59
+ version?: string;
49
60
  };
50
61
  //#endregion
51
62
  //#region src/primitives.d.ts
@@ -55,104 +66,63 @@ type StandardEventsFeature = {
55
66
  */
56
67
  declare const slugify: (platformPrefix: string, name: string) => string;
57
68
  /**
58
- * The spec types `features` as a reverse-DNS-keyed `Record<string,
59
- * unknown>`, so the caller declares the shape it expects. This accessor is
60
- * the single boundary where that dynamism is acknowledged.
61
- */
62
- declare const getFeature: <T>(wallet: WalletStandardWallet, name: string) => T | undefined;
63
- declare const pickFirstAddress: (accounts: ReadonlyArray<WalletStandardWalletAccount>) => string | null;
64
- /**
65
- * Per-call routing for `sendTx` / `signMessage`: callers pass an `account`
66
- * from `ConnectedWallet.accounts` to sign with a non-active address, but
67
- * the wallet's own `accounts[]` stays the source of truth.
69
+ * The spec keys `features` by reverse-DNS name with no fixed shape, so the
70
+ * caller names the feature type and the method that proves it:
71
+ * `getFeature<SolanaSignMessageFeature>(wallet, "solana:signMessage", "signMessage")`.
68
72
  */
69
- declare const pickAccountByAddress: (accounts: ReadonlyArray<WalletStandardWalletAccount>, address: string) => WalletStandardWalletAccount | undefined;
73
+ declare const getFeature: <Feature extends object>(wallet: WalletStandardWallet, name: string, method: keyof Feature & string) => (WalletStandardFeature & Feature) | undefined;
74
+ /** Wallet Standard account for a butr address. `undefined` for an address
75
+ * the wallet does not expose: never another account in its place. */
76
+ declare const findAccount: (accounts: ReadonlyArray<WalletStandardWalletAccount>, address: string) => WalletStandardWalletAccount | undefined;
70
77
  //#endregion
71
78
  //#region src/adapter-core.d.ts
72
79
  type WalletStandardCoreInput = {
73
- /** CAIP-2 prefix including its colon (`solana:`, `sui:`, `bip122:`). */
74
- chainPrefix: string;
80
+ /** The platform's chain registry, for chain names. A chain outside it is
81
+ * named by its CAIP-2 id. */
82
+ chains: ReadonlyArray<ChainBase>;
75
83
  /** Stable adapter id. Each platform package owns its own slug prefix. */
76
84
  id: string;
77
- /** How this adapter names itself in errors (`Bitcoin`, `SVM`, `Sui`). */
85
+ /** How the platform reads in errors: `Solana`, `Sui`, `Bitcoin`. */
78
86
  label: string;
79
- /** CAIP-2 namespace without its colon. */
87
+ /** CAIP-2 namespace without its colon: `solana`, `sui`, `bip122`. */
80
88
  namespace: string;
81
- /** How the chain family reads in errors (`Bitcoin`, `Solana`, `Sui`). */
82
- platform: string;
83
- /** Chain ids worth preferring over a bare prefix match. Matched against
84
- * the wallet's own ordering, so a wallet listing `mainnet-beta` before
89
+ /** Chain ids worth preferring over the first advertised one. Matched in
90
+ * the wallet's own order, so a wallet listing `mainnet-beta` before
85
91
  * `mainnet` keeps the chain it advertised first. */
86
92
  preferredChainIds: ReadonlyArray<string>;
87
- /** Called with a function that pushes a synthetic `disconnected` event
88
- * to all current subscribers. The discovery layer invokes it on Wallet
89
- * Standard `unregister`. */
93
+ /** Called with a function that pushes a synthetic `disconnected` event to
94
+ * all current subscribers. Discovery invokes it on `unregister`. */
90
95
  registerDisconnector?: (emit: () => void) => void;
91
96
  /** Re-point the active chain when the wallet reports a `chains` change.
92
- * Polkadot opts out: its wallets advertise a single relay chain, so a
93
- * re-point would only ever move the adapter off the chain it resolved. */
97
+ * Polkadot opts out: its wallets advertise a single relay chain. */
94
98
  trackChainChanges: boolean;
95
99
  wallet: WalletStandardWallet;
96
100
  };
97
- type WalletStandardCore = {
98
- /** Chains the wallet advertises in this namespace; feeds capability gating. */
99
- chainCount: number;
100
- connect: (opts?: {
101
- silent?: boolean;
102
- }) => Promise<void>;
103
- /** Active CAIP-2 chain id, for features that take a per-call `chain`. */
104
- currentChainId: () => string;
105
- disconnect: () => Promise<void>;
106
- getAccount: () => Promise<Account | null>;
107
- getAccounts: () => Promise<Array<Account>>;
108
- getSigner: () => Promise<unknown>;
109
- /** Whether the wallet advertises `standard:events`; feeds capability gating. */
110
- hasEvents: boolean;
111
- icon: string | undefined;
112
- id: string;
113
- name: string;
114
- /** Wallet Standard account a call should route through: the one matching
115
- * `account`, else the wallet's first exposed account. */
116
- resolveAccount: (account?: {
117
- walletAddress: string;
118
- }) => WalletStandardWalletAccount;
119
- subscribe: (listener: (event: ConnectorEvent) => void) => () => void;
120
- switchChain: (chain: ChainBase) => Promise<void>;
121
- /** butr chain for the active chain id, for responses that mint their own
122
- * `Account` (Sign-In-With-Solana) rather than going through `getAccounts`. */
123
- toChain: () => ChainBase;
101
+ /** The members every Wallet Standard adapter shares, ready to spread. */
102
+ type WalletStandardConnector = Pick<Connector, "connect" | "disconnect" | "getAccounts" | "icon" | "id" | "name" | "subscribe"> & {
103
+ getSigner: () => Promise<WalletSigner>;
104
+ switchChain?: (chain: ChainBase) => Promise<void>;
124
105
  };
125
- /**
126
- * Shared session plumbing for every Wallet Standard platform adapter.
127
- * `getSigner` hands back the raw Wallet Standard wallet so consumers can
128
- * wrap it in their own signing library.
129
- */
130
- declare const createWalletStandardCore: ({ chainPrefix, id, label, namespace, platform, preferredChainIds, registerDisconnector, trackChainChanges, wallet }: WalletStandardCoreInput) => WalletStandardCore | null;
131
- //#endregion
132
- //#region src/capabilities.d.ts
133
- type WalletCapabilityProfile = {
134
- /** Chains the wallet advertises in this namespace. */
135
- chainCount: number;
136
- /** Wallet advertises `standard:events`. */
137
- events: boolean;
138
- /** Wallet advertises a sign-and-broadcast feature:
139
- * `solana:signAndSendTransaction`, `sui:signAndExecuteTransaction`,
140
- * `bitcoin:sendTransfer`. */
141
- sendTransaction: boolean;
142
- /** Wallet advertises `solana:signIn`. No other namespace has an
143
- * equivalent, so every non-SVM caller passes `false`. */
144
- signIn: boolean;
145
- signMessage: boolean;
146
- /** Wallet advertises a sign-only feature: `solana:signTransaction`,
147
- * `sui:signTransaction`, `bitcoin:signPsbt`. */
148
- signTransaction: boolean;
106
+ type WalletStandardCore = {
107
+ /** Spread into the adapter. `disconnect`, `subscribe` and `switchChain` are
108
+ * present only when the wallet supports them. */
109
+ base: WalletStandardConnector;
110
+ /** The active chain, for results that mint their own `Account`. */
111
+ currentChain: () => ChainBase;
112
+ /** The Wallet Standard account a call signs with: the one matching
113
+ * `account`, else the active one. Throws for an account the wallet does
114
+ * not expose. */
115
+ resolveAccount: (account?: Account) => WalletStandardWalletAccount;
116
+ /** The chain id a call targets: `chain` when given, else the current one.
117
+ * Throws for a chain outside the namespace or not advertised. */
118
+ resolveChainId: (chain?: ChainBase) => string;
149
119
  };
150
120
  /**
151
- * Wallet Standard has no balance/receipt RPC, no EIP-2255 equivalent, and
152
- * no silent account switch, so those flags are false everywhere.
153
- * `switchChain` re-routes locally, so it needs a second advertised chain.
121
+ * `null` when the wallet advertises no chain in `namespace` or lacks
122
+ * `standard:connect`, so a multi-chain wallet yields one adapter per
123
+ * platform it actually speaks.
154
124
  */
155
- declare const buildWalletCapabilities: (profile: WalletCapabilityProfile) => WalletCapabilities;
125
+ declare const createWalletStandardCore: ({ chains, id, label, namespace, preferredChainIds, registerDisconnector, trackChainChanges, wallet }: WalletStandardCoreInput) => WalletStandardCore | null;
156
126
  //#endregion
157
127
  //#region src/discovery.d.ts
158
128
  /**
@@ -166,7 +136,18 @@ type WalletStandardAdapterBuilder = (wallet: WalletStandardWallet, registerDisco
166
136
  * disables discovery. Disconnectors are keyed by wallet identity rather
167
137
  * than adapter id because `register` / `unregister` emit the same object.
168
138
  */
169
- declare const discoverWalletStandard: (onAdapter: (adapter: WalletAdapter) => void, build: WalletStandardAdapterBuilder) => (() => void);
139
+ declare const discoverWalletStandard: (onAdapter: (adapter: WalletAdapter) => void, build: WalletStandardAdapterBuilder, loadModule?: WalletStandardModuleLoader) => (() => void);
140
+ //#endregion
141
+ //#region src/signer-augmentation.d.ts
142
+ /** Every Wallet Standard adapter, on any platform, hands back the raw wallet;
143
+ * reach its features with `getFeature`. */
144
+ declare module "@usebutr/core" {
145
+ interface WalletSignerRegistry {
146
+ "wallet-standard": {
147
+ wallet: WalletStandardWallet;
148
+ };
149
+ }
150
+ }
170
151
  //#endregion
171
- export { type StandardConnectFeature, type StandardDisconnectFeature, type StandardEventsFeature, type StandardEventsListener, type WalletCapabilityProfile, type WalletStandardAdapterBuilder, type WalletStandardAppModule, type WalletStandardCore, type WalletStandardWallet, type WalletStandardWalletAccount, type WalletsApp, buildAccount, buildWalletCapabilities, createWalletStandardCore, discoverWalletStandard, getFeature, pickAccountByAddress, pickFirstAddress, slugify };
152
+ export { type StandardConnectFeature, type StandardDisconnectFeature, type StandardEventsFeature, type StandardEventsListener, type WalletStandardAdapterBuilder, type WalletStandardAppModule, type WalletStandardCore, type WalletStandardCoreInput, type WalletStandardFeature, type WalletStandardModuleLoader, type WalletStandardWallet, type WalletStandardWalletAccount, type WalletsApp, createWalletStandardCore, discoverWalletStandard, findAccount, getFeature, slugify };
172
153
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/primitives.ts","../src/adapter-core.ts","../src/capabilities.ts","../src/discovery.ts"],"mappings":";;;;;;;KAMK;EACH;EACA,QAAQ;EACR,UAAU;;EAEV,YAAY;;KAGT;EACH,UAAU,cAAc;EACxB,QAAQ;;;EAGR,UAAU,SAAS;EACnB;EACA;EACA;;KAGG;EACH,WAAW,cAAc;EACzB,KACE,kCACA,cAAc,SAAS,cAAc;;KAIpC;EACH,kBAAkB;;KAGf;EACH,UAAU;IAAU;QAAuB;IACzC,UAAU,cAAc;;;KAIvB;EACH,kBAAkB;;KAGf,0BAA0B;EAC7B,WAAW,cAAc;EACzB,SAAS;EACT,WAAW;;KAGR;EACH,KAAK,iBAAiB,UAAU;;;;;;;;cChD5B,UAAW,wBAAwB;;;;;;cAcnC,aAAc,GAAG,QAAQ,sBAAsB,iBAAe;cAS9D,mBAAoB,UAAU,cAAc;;;;;;cAU5C,uBACJ,UAAU,cAAc,8BACxB,oBACC;;;KC9BE;;EAEH;;EAEA;;EAEA;;EAEA;;EAEA;;;;EAIA,mBAAmB;;;;EAInB,wBAAwB;;;;EAIxB;EACA,QAAQ;;KAGL;;EAEH;EACA,UAAU;IAAS;QAAuB;;EAE1C;EACA,kBAAkB;EAClB,kBAAkB,QAAQ;EAC1B,mBAAmB,QAAQ,MAAM;EACjC,iBAAiB;;EAEjB;EACA;EACA;EACA;;;EAGA,iBAAiB;IAAY;QAA4B;EACzD,YAAY,WAAW,OAAO;EAC9B,cAAc,OAAO,cAAc;;;EAGnC,eAAe;;;;;;;cAQX,6BAA4B,aAAA,IAAA,OAAA,WAAA,UAAA,mBAAA,sBAAA,mBAAA,UAU/B,4BAA0B;;;KC5ExB;;EAEH;;EAEA;;;;EAIA;;;EAGA;EACA;;;EAGA;;;;;;;cAQI,0BAA2B,SAAS,4BAA0B;;;;;;;;KCb/D,gCACH,QAAQ,sBACR,uBAAuB,8BACpB;;;;;;cAOC,yBACJ,YAAY,SAAS,wBACrB,OAAO"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/primitives.ts","../src/adapter-core.ts","../src/discovery.ts","../src/signer-augmentation.ts"],"mappings":";;;;;;;;KAMK;EACH;EACA,QAAQ;EACR,UAAU;EACV;aAAuB;cAA0B;;;;;KAM9C;YAAoC;EAA0B;;KAE9D;EACH,UAAU,cAAc;EACxB,QAAQ;EAER,UAAU,SAAS,QAAQ,eAAe;EAC1C;EACA;EACA;;KAGG;EACH,WAAW,cAAc;EACzB,KACE,kCACA,cAAc,SAAS,cAAc;;KAIpC;EACH,kBAAkB;;KAGf,mCAAmC,QAAQ;KAE3C;EACH,UAAU;IAAU;QAAuB;IACzC,UAAU,cAAc;;EAE1B;;KAGG;EACH,kBAAkB;EAClB;;KAGG,0BAA0B;EAC7B,WAAW,cAAc;EACzB,SAAS;EACT,WAAW;;KAGR;EACH,KAAK,iBAAiB,UAAU;EAChC;;;;;;;;cCpDI,UAAO,wBAA0B;;;;;;cAkBjC,aAAc,wBAAsB,QAChC,sBAAoB,cAChB,cACE,sBACZ,wBAAwB;;;cAOtB,cAAW,UACL,cAAc,8BAA4B,oBAEnD;;;KC9BE;;;EAGH,QAAQ,cAAc;;EAEtB;;EAEA;;EAEA;;;;EAIA,mBAAmB;;;EAGnB,wBAAwB;;;EAGxB;EACA,QAAQ;;;KAIL,0BAA0B,KAC7B;EAGA,iBAAiB,QAAQ;EACzB,eAAe,OAAO,cAAc;;KAGjC;;;EAGH,MAAM;;EAEN,oBAAoB;;;;EAIpB,iBAAiB,UAAU,YAAY;;;EAGvC,iBAAiB,QAAQ;;;;;;;cAQrB,6BAAwB,QAAA,IAAA,OAAA,WAAA,mBAAA,sBAAA,mBAAA,UAS3B,4BAA0B;;;;;;;;KCRxB,gCACH,QAAQ,sBACR,uBAAuB,8BACpB;;;;;;cAwBC,yBAAsB,YACd,SAAS,wBAAsB,OACpC,8BAA4B,aACvB;;;;;;YCzFF;IACR;MAAqB,QAAQ"}
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { buildAccount, buildAccount as buildAccount$1, logWarn, sanitizeIcon } from "@usebutr/core";
1
+ import { buildAccount, logWarn, resolveChain, sanitizeIcon } from "@usebutr/core";
2
2
 
3
3
  //#region src/primitives.ts
4
4
  /**
@@ -8,180 +8,166 @@ import { buildAccount, buildAccount as buildAccount$1, logWarn, sanitizeIcon } f
8
8
  const slugify = (platformPrefix, name) => {
9
9
  return `wallet-standard:${platformPrefix}-${name.trim().toLowerCase().replaceAll(new RegExp("[^a-z0-9]+", "gv"), "-")}`;
10
10
  };
11
+ const hasMethod = (feature, method) => typeof feature[method] === "function";
11
12
  /**
12
- * The spec types `features` as a reverse-DNS-keyed `Record<string,
13
- * unknown>`, so the caller declares the shape it expects. This accessor is
14
- * the single boundary where that dynamism is acknowledged.
13
+ * The spec keys `features` by reverse-DNS name with no fixed shape, so the
14
+ * caller names the feature type and the method that proves it:
15
+ * `getFeature<SolanaSignMessageFeature>(wallet, "solana:signMessage", "signMessage")`.
15
16
  */
16
- const getFeature = (wallet, name) => {
17
+ const getFeature = (wallet, name, method) => {
17
18
  const feature = wallet.features[name];
18
- if (typeof feature !== "object" || feature === null) return;
19
- return feature;
19
+ return feature !== void 0 && hasMethod(feature, method) ? feature : void 0;
20
20
  };
21
- const pickFirstAddress = (accounts) => {
22
- const first = accounts[0];
23
- return first === void 0 ? null : first.address;
24
- };
25
- /**
26
- * Per-call routing for `sendTx` / `signMessage`: callers pass an `account`
27
- * from `ConnectedWallet.accounts` to sign with a non-active address, but
28
- * the wallet's own `accounts[]` stays the source of truth.
29
- */
30
- const pickAccountByAddress = (accounts, address) => accounts.find((a) => a.address === address) ?? accounts[0];
21
+ /** Wallet Standard account for a butr address. `undefined` for an address
22
+ * the wallet does not expose: never another account in its place. */
23
+ const findAccount = (accounts, address) => accounts.find((a) => a.address === address);
31
24
 
32
25
  //#endregion
33
26
  //#region src/adapter-core.ts
34
27
  /**
35
- * Shared session plumbing for every Wallet Standard platform adapter.
36
- * `getSigner` hands back the raw Wallet Standard wallet so consumers can
37
- * wrap it in their own signing library.
28
+ * `null` when the wallet advertises no chain in `namespace` or lacks
29
+ * `standard:connect`, so a multi-chain wallet yields one adapter per
30
+ * platform it actually speaks.
38
31
  */
39
- const createWalletStandardCore = ({ chainPrefix, id, label, namespace, platform, preferredChainIds, registerDisconnector, trackChainChanges, wallet }) => {
40
- const pickChain = (chains) => chains.find((c) => preferredChainIds.includes(c)) ?? chains.find((c) => c.startsWith(chainPrefix)) ?? null;
32
+ const createWalletStandardCore = ({ chains, id, label, namespace, preferredChainIds, registerDisconnector, trackChainChanges, wallet }) => {
33
+ const prefix = `${namespace}:`;
34
+ const preferred = new Set(preferredChainIds);
35
+ const pickChain = (advertised) => advertised.find((c) => preferred.has(c)) ?? advertised.find((c) => c.startsWith(prefix));
41
36
  const initialChainId = pickChain(wallet.chains);
42
- if (initialChainId === null) return null;
43
- const connect = getFeature(wallet, "standard:connect");
44
- if (connect === void 0) return null;
45
- const disconnect = getFeature(wallet, "standard:disconnect");
46
- const events = getFeature(wallet, "standard:events");
37
+ const connect = getFeature(wallet, "standard:connect", "connect");
38
+ if (initialChainId === void 0 || connect === void 0) return null;
39
+ const disconnect = getFeature(wallet, "standard:disconnect", "disconnect");
40
+ const events = getFeature(wallet, "standard:events", "on");
47
41
  let currentChainId = initialChainId;
48
- const toChain = () => ({
49
- id: currentChainId,
50
- name: wallet.name,
51
- namespace,
52
- reference: currentChainId.slice(chainPrefix.length)
53
- });
42
+ const currentChain = () => resolveChain(currentChainId, chains);
43
+ const toAccounts = (accounts) => {
44
+ const chain = currentChain();
45
+ return accounts.map((a) => buildAccount(a.address, chain));
46
+ };
54
47
  const listeners = /* @__PURE__ */ new Set();
55
- const notifyAccountChanged = () => {
56
- if (wallet.accounts.length === 0) return;
57
- const chain = toChain();
58
- const built = wallet.accounts.map((a) => buildAccount$1(a.address, chain));
59
- const first = built[0];
60
- if (first === void 0) return;
61
- for (const listener of listeners) listener({
62
- account: first,
63
- accounts: built,
64
- type: "accountChanged"
65
- });
48
+ const emit = (event) => {
49
+ for (const listener of listeners) listener(event);
50
+ };
51
+ const toEvent = (accounts) => accounts.length === 0 ? { type: "disconnected" } : {
52
+ accounts: toAccounts(accounts),
53
+ type: "accountsChanged"
66
54
  };
67
55
  registerDisconnector?.(() => {
68
- for (const listener of listeners) listener({ type: "disconnected" });
56
+ emit({ type: "disconnected" });
69
57
  });
58
+ const resolveChainId = (chain) => {
59
+ if (chain === void 0) return currentChainId;
60
+ if (chain.namespace !== namespace) throw new Error(`${label} adapter received non-${label} chain "${chain.id}". Pass a chain with namespace "${namespace}".`);
61
+ if (!wallet.chains.includes(chain.id)) throw new Error(`Wallet ${wallet.name} does not advertise chain "${chain.id}". Available: ${wallet.chains.join(", ")}`);
62
+ return chain.id;
63
+ };
64
+ const subscribe = (listener) => {
65
+ listeners.add(listener);
66
+ const off = events?.on("change", (changes) => {
67
+ const next = trackChainChanges && changes.chains ? pickChain(changes.chains) : void 0;
68
+ if (next !== void 0) currentChainId = next;
69
+ if (changes.accounts !== void 0) listener(toEvent(changes.accounts));
70
+ else if (next !== void 0) listener(toEvent(wallet.accounts));
71
+ });
72
+ return () => {
73
+ listeners.delete(listener);
74
+ off?.();
75
+ };
76
+ };
77
+ const advertisedInNamespace = wallet.chains.filter((c) => c.startsWith(prefix));
70
78
  return {
71
- chainCount: wallet.chains.filter((c) => c.startsWith(chainPrefix)).length,
72
- async connect(opts) {
73
- await connect.connect(opts?.silent === true ? { silent: true } : void 0);
74
- },
75
- currentChainId: () => currentChainId,
76
- async disconnect() {
77
- if (disconnect !== void 0) try {
78
- await disconnect.disconnect();
79
- } catch (error) {
80
- logWarn(`[butr] ${label} Wallet Standard disconnect threw:`, error);
81
- }
82
- },
83
- getAccount: () => {
84
- const address = pickFirstAddress(wallet.accounts);
85
- return Promise.resolve(address === null ? null : buildAccount$1(address, toChain()));
86
- },
87
- getAccounts: () => {
88
- const chain = toChain();
89
- return Promise.resolve(wallet.accounts.map((a) => buildAccount$1(a.address, chain)));
79
+ base: {
80
+ async connect(options) {
81
+ await connect.connect(options?.silent === true ? { silent: true } : void 0);
82
+ },
83
+ getAccounts: () => Promise.resolve(toAccounts(wallet.accounts)),
84
+ getSigner: () => Promise.resolve({
85
+ kind: "wallet-standard",
86
+ wallet
87
+ }),
88
+ icon: sanitizeIcon(wallet.icon),
89
+ id,
90
+ name: wallet.name,
91
+ ...disconnect !== void 0 && { async disconnect() {
92
+ try {
93
+ await disconnect.disconnect();
94
+ } catch (error) {
95
+ logWarn(`[butr] ${label} Wallet Standard disconnect threw:`, error);
96
+ }
97
+ } },
98
+ ...(events !== void 0 || registerDisconnector !== void 0) && { subscribe },
99
+ ...advertisedInNamespace.length > 1 && { switchChain: async (chain) => {
100
+ currentChainId = resolveChainId(chain);
101
+ emit(toEvent(wallet.accounts));
102
+ await Promise.resolve();
103
+ } }
90
104
  },
91
- getSigner: () => Promise.resolve(wallet),
92
- hasEvents: Boolean(events),
93
- icon: sanitizeIcon(wallet.icon),
94
- id,
95
- name: wallet.name,
105
+ currentChain,
96
106
  resolveAccount: (account) => {
97
- const wsAccount = account ? pickAccountByAddress(wallet.accounts, account.walletAddress) : wallet.accounts[0];
98
- if (wsAccount === void 0) throw new Error("No connected account");
99
- return wsAccount;
100
- },
101
- subscribe(listener) {
102
- listeners.add(listener);
103
- let unsubWallet = null;
104
- if (events !== void 0) {
105
- const unsub = events.on("change", (changes) => {
106
- if (trackChainChanges && changes.chains !== void 0) {
107
- const next = pickChain(changes.chains);
108
- if (next !== null) currentChainId = next;
109
- }
110
- if (changes.accounts !== void 0) {
111
- if (changes.accounts.length === 0) {
112
- listener({ type: "disconnected" });
113
- return;
114
- }
115
- const chain = toChain();
116
- const built = changes.accounts.map((a) => buildAccount$1(a.address, chain));
117
- const first = built[0];
118
- if (first === void 0) return;
119
- listener({
120
- account: first,
121
- accounts: built,
122
- type: "accountChanged"
123
- });
124
- return;
125
- }
126
- if (trackChainChanges && changes.chains !== void 0) notifyAccountChanged();
127
- });
128
- unsubWallet = () => {
129
- unsub();
130
- };
131
- }
132
- return () => {
133
- listeners.delete(listener);
134
- unsubWallet?.();
135
- };
107
+ const match = account === void 0 ? wallet.accounts[0] : findAccount(wallet.accounts, account.walletAddress);
108
+ if (match === void 0) throw new Error(account === void 0 ? `Wallet ${wallet.name} has no connected account` : `Wallet ${wallet.name} does not expose account ${account.walletAddress}`);
109
+ return match;
136
110
  },
137
- switchChain: async (chain) => {
138
- if (chain.namespace !== namespace) throw new Error(`${label} adapter received non-${platform} chain "${chain.id}". Pass a chain with namespace "${namespace}".`);
139
- if (!wallet.chains.includes(chain.id)) throw new Error(`Wallet ${wallet.name} does not advertise chain "${chain.id}". Available: ${wallet.chains.join(", ")}`);
140
- currentChainId = chain.id;
141
- notifyAccountChanged();
142
- await Promise.resolve();
143
- },
144
- toChain
111
+ resolveChainId
145
112
  };
146
113
  };
147
114
 
148
115
  //#endregion
149
- //#region src/capabilities.ts
116
+ //#region src/discovery.ts
117
+ const isFeatureObject = (value) => typeof value === "object" && value !== null;
118
+ const toFeatureRecord = (features) => {
119
+ const record = {};
120
+ for (const [name, value] of Object.entries(features)) if (isFeatureObject(value)) record[name] = value;
121
+ return record;
122
+ };
123
+ const wrappers = /* @__PURE__ */ new WeakMap();
150
124
  /**
151
- * Wallet Standard has no balance/receipt RPC, no EIP-2255 equivalent, and
152
- * no silent account switch, so those flags are false everywhere.
153
- * `switchChain` re-routes locally, so it needs a second advertised chain.
125
+ * Reads through to the wallet: it re-points `accounts` on connect, so a copy
126
+ * pins the pre-connect empty list and every `getAccounts()` then resolves [].
127
+ * Memoised because `unregister` is matched to `register` by object identity.
154
128
  */
155
- const buildWalletCapabilities = (profile) => ({
156
- getBalance: false,
157
- getTransactionReceipt: false,
158
- requestAccounts: false,
159
- sendTransaction: profile.sendTransaction,
160
- signIn: profile.signIn,
161
- signMessage: profile.signMessage,
162
- signTransaction: profile.signTransaction,
163
- subscribe: profile.events,
164
- switchAccount: false,
165
- switchChain: profile.chainCount > 1
166
- });
167
-
168
- //#endregion
169
- //#region src/discovery.ts
129
+ const mapWallet = (wallet) => {
130
+ const cached = wrappers.get(wallet);
131
+ if (cached !== void 0) return cached;
132
+ const wrapper = {
133
+ get accounts() {
134
+ return wallet.accounts;
135
+ },
136
+ get chains() {
137
+ return wallet.chains;
138
+ },
139
+ features: toFeatureRecord(wallet.features),
140
+ icon: wallet.icon,
141
+ name: wallet.name,
142
+ version: wallet.version
143
+ };
144
+ wrappers.set(wallet, wrapper);
145
+ return wrapper;
146
+ };
170
147
  let warnedMissingApp = false;
148
+ const loadWalletStandardModule = async () => {
149
+ const imported = await import("@wallet-standard/app");
150
+ if (!("getWallets" in imported) || typeof imported.getWallets !== "function") throw new Error("@wallet-standard/app has no getWallets export");
151
+ const source = imported.getWallets();
152
+ return { getWallets: () => ({
153
+ get: () => source.get().map(mapWallet),
154
+ on: (event, listener) => source.on(event, (...wallets) => {
155
+ listener(...wallets.map(mapWallet));
156
+ })
157
+ }) };
158
+ };
171
159
  /**
172
160
  * `@wallet-standard/app` is an optional peer dep: a failed import quietly
173
161
  * disables discovery. Disconnectors are keyed by wallet identity rather
174
162
  * than adapter id because `register` / `unregister` emit the same object.
175
163
  */
176
- const discoverWalletStandard = (onAdapter, build) => {
164
+ const discoverWalletStandard = (onAdapter, build, loadModule = loadWalletStandardModule) => {
177
165
  let cancelled = false;
178
166
  let internalUnsub = null;
179
167
  (async () => {
180
168
  let mod;
181
169
  try {
182
- const imported = await import("@wallet-standard/app");
183
- if (typeof imported !== "object" || imported === null || !("getWallets" in imported) || typeof imported.getWallets !== "function") throw new Error("@wallet-standard/app has no getWallets export");
184
- mod = imported;
170
+ mod = await loadModule();
185
171
  } catch (error) {
186
172
  if (!warnedMissingApp) {
187
173
  warnedMissingApp = true;
@@ -227,5 +213,5 @@ const discoverWalletStandard = (onAdapter, build) => {
227
213
  };
228
214
 
229
215
  //#endregion
230
- export { buildAccount, buildWalletCapabilities, createWalletStandardCore, discoverWalletStandard, getFeature, pickAccountByAddress, pickFirstAddress, slugify };
216
+ export { createWalletStandardCore, discoverWalletStandard, findAccount, getFeature, slugify };
231
217
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["buildAccount"],"sources":["../src/primitives.ts","../src/adapter-core.ts","../src/capabilities.ts","../src/discovery.ts"],"sourcesContent":["import type { WalletStandardWallet, WalletStandardWalletAccount } from \"./types\";\n\n/**\n * The platform prefix scopes the id so one multi-chain wallet (Phantom\n * SVM, Sui, BTC) yields distinct adapters on the shared `getWallets()` bus.\n */\nconst slugify = (platformPrefix: string, name: string): string => {\n const slug = name\n .trim()\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/gv, \"-\");\n return `wallet-standard:${platformPrefix}-${slug}`;\n};\n\n/**\n * The spec types `features` as a reverse-DNS-keyed `Record<string,\n * unknown>`, so the caller declares the shape it expects. This accessor is\n * the single boundary where that dynamism is acknowledged.\n */\n// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- caller-supplied feature shape; not inferable from args\nconst getFeature = <T>(wallet: WalletStandardWallet, name: string): T | undefined => {\n const feature: unknown = wallet.features[name];\n if (typeof feature !== \"object\" || feature === null) {\n return undefined;\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- untyped Wallet Standard feature registry; caller declares the shape\n return feature as T;\n};\n\nconst pickFirstAddress = (accounts: ReadonlyArray<WalletStandardWalletAccount>): string | null => {\n const first = accounts[0];\n return first === undefined ? null : first.address;\n};\n\n/**\n * Per-call routing for `sendTx` / `signMessage`: callers pass an `account`\n * from `ConnectedWallet.accounts` to sign with a non-active address, but\n * the wallet's own `accounts[]` stays the source of truth.\n */\nconst pickAccountByAddress = (\n accounts: ReadonlyArray<WalletStandardWalletAccount>,\n address: string,\n): WalletStandardWalletAccount | undefined =>\n accounts.find((a) => a.address === address) ?? accounts[0];\n\nexport { buildAccount } from \"@usebutr/core\";\nexport { getFeature, pickAccountByAddress, pickFirstAddress, slugify };\n","import type { Account, ChainBase, ConnectorEvent } from \"@usebutr/core\";\nimport { buildAccount, logWarn, sanitizeIcon } from \"@usebutr/core\";\n\nimport { getFeature, pickAccountByAddress, pickFirstAddress } from \"./primitives\";\nimport type {\n StandardConnectFeature,\n StandardDisconnectFeature,\n StandardEventsFeature,\n WalletStandardWallet,\n WalletStandardWalletAccount,\n} from \"./types\";\n\ntype WalletStandardCoreInput = {\n /** CAIP-2 prefix including its colon (`solana:`, `sui:`, `bip122:`). */\n chainPrefix: string;\n /** Stable adapter id. Each platform package owns its own slug prefix. */\n id: string;\n /** How this adapter names itself in errors (`Bitcoin`, `SVM`, `Sui`). */\n label: string;\n /** CAIP-2 namespace without its colon. */\n namespace: string;\n /** How the chain family reads in errors (`Bitcoin`, `Solana`, `Sui`). */\n platform: string;\n /** Chain ids worth preferring over a bare prefix match. Matched against\n * the wallet's own ordering, so a wallet listing `mainnet-beta` before\n * `mainnet` keeps the chain it advertised first. */\n preferredChainIds: ReadonlyArray<string>;\n /** Called with a function that pushes a synthetic `disconnected` event\n * to all current subscribers. The discovery layer invokes it on Wallet\n * Standard `unregister`. */\n registerDisconnector?: (emit: () => void) => void;\n /** Re-point the active chain when the wallet reports a `chains` change.\n * Polkadot opts out: its wallets advertise a single relay chain, so a\n * re-point would only ever move the adapter off the chain it resolved. */\n trackChainChanges: boolean;\n wallet: WalletStandardWallet;\n};\n\ntype WalletStandardCore = {\n /** Chains the wallet advertises in this namespace; feeds capability gating. */\n chainCount: number;\n connect: (opts?: { silent?: boolean }) => Promise<void>;\n /** Active CAIP-2 chain id, for features that take a per-call `chain`. */\n currentChainId: () => string;\n disconnect: () => Promise<void>;\n getAccount: () => Promise<Account | null>;\n getAccounts: () => Promise<Array<Account>>;\n getSigner: () => Promise<unknown>;\n /** Whether the wallet advertises `standard:events`; feeds capability gating. */\n hasEvents: boolean;\n icon: string | undefined;\n id: string;\n name: string;\n /** Wallet Standard account a call should route through: the one matching\n * `account`, else the wallet's first exposed account. */\n resolveAccount: (account?: { walletAddress: string }) => WalletStandardWalletAccount;\n subscribe: (listener: (event: ConnectorEvent) => void) => () => void;\n switchChain: (chain: ChainBase) => Promise<void>;\n /** butr chain for the active chain id, for responses that mint their own\n * `Account` (Sign-In-With-Solana) rather than going through `getAccounts`. */\n toChain: () => ChainBase;\n};\n\n/**\n * Shared session plumbing for every Wallet Standard platform adapter.\n * `getSigner` hands back the raw Wallet Standard wallet so consumers can\n * wrap it in their own signing library.\n */\nconst createWalletStandardCore = ({\n chainPrefix,\n id,\n label,\n namespace,\n platform,\n preferredChainIds,\n registerDisconnector,\n trackChainChanges,\n wallet,\n}: WalletStandardCoreInput): WalletStandardCore | null => {\n const pickChain = (chains: ReadonlyArray<string>): string | null =>\n chains.find((c) => preferredChainIds.includes(c)) ??\n chains.find((c) => c.startsWith(chainPrefix)) ??\n null;\n\n const initialChainId = pickChain(wallet.chains);\n if (initialChainId === null) {\n return null;\n }\n const connect = getFeature<StandardConnectFeature>(wallet, \"standard:connect\");\n if (connect === undefined) {\n return null;\n }\n\n const disconnect = getFeature<StandardDisconnectFeature>(wallet, \"standard:disconnect\");\n const events = getFeature<StandardEventsFeature>(wallet, \"standard:events\");\n\n let currentChainId = initialChainId;\n const toChain = (): ChainBase => ({\n id: currentChainId,\n name: wallet.name,\n namespace,\n reference: currentChainId.slice(chainPrefix.length),\n });\n\n const listeners = new Set<(event: ConnectorEvent) => void>();\n const notifyAccountChanged = () => {\n if (wallet.accounts.length === 0) {\n return;\n }\n const chain = toChain();\n const built = wallet.accounts.map((a) => buildAccount(a.address, chain));\n const first = built[0];\n if (first === undefined) {\n return;\n }\n for (const listener of listeners) {\n listener({ account: first, accounts: built, type: \"accountChanged\" });\n }\n };\n\n registerDisconnector?.(() => {\n for (const listener of listeners) {\n listener({ type: \"disconnected\" });\n }\n });\n\n return {\n chainCount: wallet.chains.filter((c) => c.startsWith(chainPrefix)).length,\n\n async connect(opts) {\n await connect.connect(opts?.silent === true ? { silent: true } : undefined);\n },\n\n currentChainId: () => currentChainId,\n\n async disconnect() {\n if (disconnect !== undefined) {\n try {\n await disconnect.disconnect();\n } catch (error) {\n logWarn(`[butr] ${label} Wallet Standard disconnect threw:`, error);\n }\n }\n },\n\n getAccount: () => {\n const address = pickFirstAddress(wallet.accounts);\n return Promise.resolve(address === null ? null : buildAccount(address, toChain()));\n },\n\n getAccounts: () => {\n const chain = toChain();\n return Promise.resolve(wallet.accounts.map((a) => buildAccount(a.address, chain)));\n },\n\n getSigner: () => Promise.resolve(wallet),\n\n hasEvents: Boolean(events),\n\n icon: sanitizeIcon(wallet.icon),\n id,\n name: wallet.name,\n\n resolveAccount: (account) => {\n const wsAccount = account\n ? pickAccountByAddress(wallet.accounts, account.walletAddress)\n : wallet.accounts[0];\n if (wsAccount === undefined) {\n throw new Error(\"No connected account\");\n }\n return wsAccount;\n },\n\n subscribe(listener) {\n listeners.add(listener);\n let unsubWallet: (() => void) | null = null;\n if (events !== undefined) {\n const unsub = events.on(\"change\", (changes) => {\n if (trackChainChanges && changes.chains !== undefined) {\n const next = pickChain(changes.chains);\n if (next !== null) {\n currentChainId = next;\n }\n }\n\n if (changes.accounts !== undefined) {\n if (changes.accounts.length === 0) {\n listener({ type: \"disconnected\" });\n return;\n }\n const chain = toChain();\n const built = changes.accounts.map((a) => buildAccount(a.address, chain));\n const first = built[0];\n if (first === undefined) {\n return;\n }\n listener({ account: first, accounts: built, type: \"accountChanged\" });\n return;\n }\n\n if (trackChainChanges && changes.chains !== undefined) {\n notifyAccountChanged();\n }\n });\n unsubWallet = () => {\n unsub();\n };\n }\n return () => {\n listeners.delete(listener);\n unsubWallet?.();\n };\n },\n\n // Async so a rejected chain surfaces as a rejection. Declared\n // `Promise<void>`, this previously threw synchronously, so a caller\n // attaching `.catch()` instead of awaiting got an uncaught throw.\n switchChain: async (chain) => {\n if (chain.namespace !== namespace) {\n throw new Error(\n `${label} adapter received non-${platform} chain \"${chain.id}\". Pass a chain with namespace \"${namespace}\".`,\n );\n }\n if (!wallet.chains.includes(chain.id)) {\n throw new Error(\n `Wallet ${wallet.name} does not advertise chain \"${chain.id}\". Available: ${wallet.chains.join(\", \")}`,\n );\n }\n currentChainId = chain.id;\n notifyAccountChanged();\n await Promise.resolve();\n },\n\n toChain,\n };\n};\n\nexport type { WalletStandardCore };\nexport { createWalletStandardCore };\n","import type { WalletCapabilities } from \"@usebutr/core\";\n\ntype WalletCapabilityProfile = {\n /** Chains the wallet advertises in this namespace. */\n chainCount: number;\n /** Wallet advertises `standard:events`. */\n events: boolean;\n /** Wallet advertises a sign-and-broadcast feature:\n * `solana:signAndSendTransaction`, `sui:signAndExecuteTransaction`,\n * `bitcoin:sendTransfer`. */\n sendTransaction: boolean;\n /** Wallet advertises `solana:signIn`. No other namespace has an\n * equivalent, so every non-SVM caller passes `false`. */\n signIn: boolean;\n signMessage: boolean;\n /** Wallet advertises a sign-only feature: `solana:signTransaction`,\n * `sui:signTransaction`, `bitcoin:signPsbt`. */\n signTransaction: boolean;\n};\n\n/**\n * Wallet Standard has no balance/receipt RPC, no EIP-2255 equivalent, and\n * no silent account switch, so those flags are false everywhere.\n * `switchChain` re-routes locally, so it needs a second advertised chain.\n */\nconst buildWalletCapabilities = (profile: WalletCapabilityProfile): WalletCapabilities => ({\n getBalance: false,\n getTransactionReceipt: false,\n requestAccounts: false,\n sendTransaction: profile.sendTransaction,\n signIn: profile.signIn,\n signMessage: profile.signMessage,\n signTransaction: profile.signTransaction,\n subscribe: profile.events,\n switchAccount: false,\n switchChain: profile.chainCount > 1,\n});\n\nexport type { WalletCapabilityProfile };\nexport { buildWalletCapabilities };\n","import type { WalletAdapter } from \"@usebutr/core\";\nimport { logWarn } from \"@usebutr/core\";\n\nimport type { WalletStandardAppModule, WalletStandardWallet } from \"./types\";\n\nlet warnedMissingApp = false;\n\n/**\n * Returns `null` when the wallet doesn't advertise this platform, so a\n * multi-chain wallet yields one adapter per platform. `registerDisconnector`\n * wires the synthetic `disconnected` fired on Wallet Standard `unregister`.\n */\ntype WalletStandardAdapterBuilder = (\n wallet: WalletStandardWallet,\n registerDisconnector: (emit: () => void) => void,\n) => WalletAdapter | null;\n\n/**\n * `@wallet-standard/app` is an optional peer dep: a failed import quietly\n * disables discovery. Disconnectors are keyed by wallet identity rather\n * than adapter id because `register` / `unregister` emit the same object.\n */\nconst discoverWalletStandard = (\n onAdapter: (adapter: WalletAdapter) => void,\n build: WalletStandardAdapterBuilder,\n): (() => void) => {\n let cancelled = false;\n let internalUnsub: (() => void) | null = null;\n\n void (async () => {\n let mod: WalletStandardAppModule;\n try {\n const imported: unknown = await import(\"@wallet-standard/app\");\n if (\n typeof imported !== \"object\" ||\n imported === null ||\n !(\"getWallets\" in imported) ||\n typeof imported.getWallets !== \"function\"\n ) {\n throw new Error(\"@wallet-standard/app has no getWallets export\");\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- validated getWallets export above; the rest of the untyped module is trusted\n mod = imported as WalletStandardAppModule;\n } catch (error) {\n if (!warnedMissingApp) {\n warnedMissingApp = true;\n logWarn(\n \"[butr] Wallet Standard wallet discovery is disabled: `@wallet-standard/app` could not be loaded. Install it (`npm i @wallet-standard/app`) to detect Wallet Standard wallets.\",\n error,\n );\n }\n return;\n }\n if (cancelled) {\n return;\n }\n\n const seenIds = new Set<string>();\n const disconnectors = new Map<WalletStandardWallet, () => void>();\n\n const tryAdd = (wallet: WalletStandardWallet) => {\n const adapter = build(wallet, (emit) => {\n disconnectors.set(wallet, emit);\n });\n if (!adapter || seenIds.has(adapter.id)) {\n return;\n }\n seenIds.add(adapter.id);\n onAdapter(adapter);\n };\n\n const app = mod.getWallets();\n for (const wallet of app.get()) {\n tryAdd(wallet);\n }\n\n const offRegister = app.on(\"register\", (...wallets) => {\n for (const wallet of wallets) {\n tryAdd(wallet);\n }\n });\n const offUnregister = app.on(\"unregister\", (...wallets) => {\n for (const wallet of wallets) {\n const emit = disconnectors.get(wallet);\n if (emit) {\n emit();\n disconnectors.delete(wallet);\n }\n }\n });\n internalUnsub = () => {\n offRegister();\n offUnregister();\n };\n })();\n\n return () => {\n cancelled = true;\n internalUnsub?.();\n internalUnsub = null;\n };\n};\n\nexport type { WalletStandardAdapterBuilder };\nexport { discoverWalletStandard };\n"],"mappings":";;;;;;;AAMA,MAAM,WAAW,gBAAwB,SAAyB;CAKhE,OAAO,mBAAmB,eAAe,GAJ5B,KACV,KAAK,CAAC,CACN,YAAY,CAAC,CACb,WAAW,6BAAa,GAAG,GACiB;AACjD;;;;;;AAQA,MAAM,cAAiB,QAA8B,SAAgC;CACnF,MAAM,UAAmB,OAAO,SAAS;CACzC,IAAI,OAAO,YAAY,YAAY,YAAY,MAC7C;CAGF,OAAO;AACT;AAEA,MAAM,oBAAoB,aAAwE;CAChG,MAAM,QAAQ,SAAS;CACvB,OAAO,UAAU,SAAY,OAAO,MAAM;AAC5C;;;;;;AAOA,MAAM,wBACJ,UACA,YAEA,SAAS,MAAM,MAAM,EAAE,YAAY,OAAO,KAAK,SAAS;;;;;;;;;ACyB1D,MAAM,4BAA4B,EAChC,aACA,IACA,OACA,WACA,UACA,mBACA,sBACA,mBACA,aACwD;CACxD,MAAM,aAAa,WACjB,OAAO,MAAM,MAAM,kBAAkB,SAAS,CAAC,CAAC,KAChD,OAAO,MAAM,MAAM,EAAE,WAAW,WAAW,CAAC,KAC5C;CAEF,MAAM,iBAAiB,UAAU,OAAO,MAAM;CAC9C,IAAI,mBAAmB,MACrB,OAAO;CAET,MAAM,UAAU,WAAmC,QAAQ,kBAAkB;CAC7E,IAAI,YAAY,QACd,OAAO;CAGT,MAAM,aAAa,WAAsC,QAAQ,qBAAqB;CACtF,MAAM,SAAS,WAAkC,QAAQ,iBAAiB;CAE1E,IAAI,iBAAiB;CACrB,MAAM,iBAA4B;EAChC,IAAI;EACJ,MAAM,OAAO;EACb;EACA,WAAW,eAAe,MAAM,YAAY,MAAM;CACpD;CAEA,MAAM,4BAAY,IAAI,IAAqC;CAC3D,MAAM,6BAA6B;EACjC,IAAI,OAAO,SAAS,WAAW,GAC7B;EAEF,MAAM,QAAQ,QAAQ;EACtB,MAAM,QAAQ,OAAO,SAAS,KAAK,MAAMA,eAAa,EAAE,SAAS,KAAK,CAAC;EACvE,MAAM,QAAQ,MAAM;EACpB,IAAI,UAAU,QACZ;EAEF,KAAK,MAAM,YAAY,WACrB,SAAS;GAAE,SAAS;GAAO,UAAU;GAAO,MAAM;EAAiB,CAAC;CAExE;CAEA,6BAA6B;EAC3B,KAAK,MAAM,YAAY,WACrB,SAAS,EAAE,MAAM,eAAe,CAAC;CAErC,CAAC;CAED,OAAO;EACL,YAAY,OAAO,OAAO,QAAQ,MAAM,EAAE,WAAW,WAAW,CAAC,CAAC,CAAC;EAEnE,MAAM,QAAQ,MAAM;GAClB,MAAM,QAAQ,QAAQ,MAAM,WAAW,OAAO,EAAE,QAAQ,KAAK,IAAI,MAAS;EAC5E;EAEA,sBAAsB;EAEtB,MAAM,aAAa;GACjB,IAAI,eAAe,QACjB,IAAI;IACF,MAAM,WAAW,WAAW;GAC9B,SAAS,OAAO;IACd,QAAQ,UAAU,MAAM,qCAAqC,KAAK;GACpE;EAEJ;EAEA,kBAAkB;GAChB,MAAM,UAAU,iBAAiB,OAAO,QAAQ;GAChD,OAAO,QAAQ,QAAQ,YAAY,OAAO,OAAOA,eAAa,SAAS,QAAQ,CAAC,CAAC;EACnF;EAEA,mBAAmB;GACjB,MAAM,QAAQ,QAAQ;GACtB,OAAO,QAAQ,QAAQ,OAAO,SAAS,KAAK,MAAMA,eAAa,EAAE,SAAS,KAAK,CAAC,CAAC;EACnF;EAEA,iBAAiB,QAAQ,QAAQ,MAAM;EAEvC,WAAW,QAAQ,MAAM;EAEzB,MAAM,aAAa,OAAO,IAAI;EAC9B;EACA,MAAM,OAAO;EAEb,iBAAiB,YAAY;GAC3B,MAAM,YAAY,UACd,qBAAqB,OAAO,UAAU,QAAQ,aAAa,IAC3D,OAAO,SAAS;GACpB,IAAI,cAAc,QAChB,MAAM,IAAI,MAAM,sBAAsB;GAExC,OAAO;EACT;EAEA,UAAU,UAAU;GAClB,UAAU,IAAI,QAAQ;GACtB,IAAI,cAAmC;GACvC,IAAI,WAAW,QAAW;IACxB,MAAM,QAAQ,OAAO,GAAG,WAAW,YAAY;KAC7C,IAAI,qBAAqB,QAAQ,WAAW,QAAW;MACrD,MAAM,OAAO,UAAU,QAAQ,MAAM;MACrC,IAAI,SAAS,MACX,iBAAiB;KAErB;KAEA,IAAI,QAAQ,aAAa,QAAW;MAClC,IAAI,QAAQ,SAAS,WAAW,GAAG;OACjC,SAAS,EAAE,MAAM,eAAe,CAAC;OACjC;MACF;MACA,MAAM,QAAQ,QAAQ;MACtB,MAAM,QAAQ,QAAQ,SAAS,KAAK,MAAMA,eAAa,EAAE,SAAS,KAAK,CAAC;MACxE,MAAM,QAAQ,MAAM;MACpB,IAAI,UAAU,QACZ;MAEF,SAAS;OAAE,SAAS;OAAO,UAAU;OAAO,MAAM;MAAiB,CAAC;MACpE;KACF;KAEA,IAAI,qBAAqB,QAAQ,WAAW,QAC1C,qBAAqB;IAEzB,CAAC;IACD,oBAAoB;KAClB,MAAM;IACR;GACF;GACA,aAAa;IACX,UAAU,OAAO,QAAQ;IACzB,cAAc;GAChB;EACF;EAKA,aAAa,OAAO,UAAU;GAC5B,IAAI,MAAM,cAAc,WACtB,MAAM,IAAI,MACR,GAAG,MAAM,wBAAwB,SAAS,UAAU,MAAM,GAAG,kCAAkC,UAAU,GAC3G;GAEF,IAAI,CAAC,OAAO,OAAO,SAAS,MAAM,EAAE,GAClC,MAAM,IAAI,MACR,UAAU,OAAO,KAAK,6BAA6B,MAAM,GAAG,gBAAgB,OAAO,OAAO,KAAK,IAAI,GACrG;GAEF,iBAAiB,MAAM;GACvB,qBAAqB;GACrB,MAAM,QAAQ,QAAQ;EACxB;EAEA;CACF;AACF;;;;;;;;;AClNA,MAAM,2BAA2B,aAA0D;CACzF,YAAY;CACZ,uBAAuB;CACvB,iBAAiB;CACjB,iBAAiB,QAAQ;CACzB,QAAQ,QAAQ;CAChB,aAAa,QAAQ;CACrB,iBAAiB,QAAQ;CACzB,WAAW,QAAQ;CACnB,eAAe;CACf,aAAa,QAAQ,aAAa;AACpC;;;;AC/BA,IAAI,mBAAmB;;;;;;AAiBvB,MAAM,0BACJ,WACA,UACiB;CACjB,IAAI,YAAY;CAChB,IAAI,gBAAqC;CAEzC,CAAM,YAAY;EAChB,IAAI;EACJ,IAAI;GACF,MAAM,WAAoB,MAAM,OAAO;GACvC,IACE,OAAO,aAAa,YACpB,aAAa,QACb,EAAE,gBAAgB,aAClB,OAAO,SAAS,eAAe,YAE/B,MAAM,IAAI,MAAM,+CAA+C;GAGjE,MAAM;EACR,SAAS,OAAO;GACd,IAAI,CAAC,kBAAkB;IACrB,mBAAmB;IACnB,QACE,iLACA,KACF;GACF;GACA;EACF;EACA,IAAI,WACF;EAGF,MAAM,0BAAU,IAAI,IAAY;EAChC,MAAM,gCAAgB,IAAI,IAAsC;EAEhE,MAAM,UAAU,WAAiC;GAC/C,MAAM,UAAU,MAAM,SAAS,SAAS;IACtC,cAAc,IAAI,QAAQ,IAAI;GAChC,CAAC;GACD,IAAI,CAAC,WAAW,QAAQ,IAAI,QAAQ,EAAE,GACpC;GAEF,QAAQ,IAAI,QAAQ,EAAE;GACtB,UAAU,OAAO;EACnB;EAEA,MAAM,MAAM,IAAI,WAAW;EAC3B,KAAK,MAAM,UAAU,IAAI,IAAI,GAC3B,OAAO,MAAM;EAGf,MAAM,cAAc,IAAI,GAAG,aAAa,GAAG,YAAY;GACrD,KAAK,MAAM,UAAU,SACnB,OAAO,MAAM;EAEjB,CAAC;EACD,MAAM,gBAAgB,IAAI,GAAG,eAAe,GAAG,YAAY;GACzD,KAAK,MAAM,UAAU,SAAS;IAC5B,MAAM,OAAO,cAAc,IAAI,MAAM;IACrC,IAAI,MAAM;KACR,KAAK;KACL,cAAc,OAAO,MAAM;IAC7B;GACF;EACF,CAAC;EACD,sBAAsB;GACpB,YAAY;GACZ,cAAc;EAChB;CACF,EAAC,CAAE;CAEH,aAAa;EACX,YAAY;EACZ,gBAAgB;EAChB,gBAAgB;CAClB;AACF"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/primitives.ts","../src/adapter-core.ts","../src/discovery.ts"],"sourcesContent":["import type {\n WalletStandardFeature,\n WalletStandardWallet,\n WalletStandardWalletAccount,\n} from \"./types\";\n\n/**\n * The platform prefix scopes the id so one multi-chain wallet (Phantom\n * SVM, Sui, BTC) yields distinct adapters on the shared `getWallets()` bus.\n */\nconst slugify = (platformPrefix: string, name: string): string => {\n const slug = name\n .trim()\n .toLowerCase()\n .replaceAll(/[^a-z0-9]+/gv, \"-\");\n return `wallet-standard:${platformPrefix}-${slug}`;\n};\n\nconst hasMethod = <Feature extends object>(\n feature: WalletStandardFeature,\n method: keyof Feature & string,\n): feature is WalletStandardFeature & Feature => typeof feature[method] === \"function\";\n\n/**\n * The spec keys `features` by reverse-DNS name with no fixed shape, so the\n * caller names the feature type and the method that proves it:\n * `getFeature<SolanaSignMessageFeature>(wallet, \"solana:signMessage\", \"signMessage\")`.\n */\nconst getFeature = <Feature extends object>(\n wallet: WalletStandardWallet,\n name: string,\n method: keyof Feature & string,\n): (WalletStandardFeature & Feature) | undefined => {\n const feature = wallet.features[name];\n return feature !== undefined && hasMethod<Feature>(feature, method) ? feature : undefined;\n};\n\n/** Wallet Standard account for a butr address. `undefined` for an address\n * the wallet does not expose: never another account in its place. */\nconst findAccount = (\n accounts: ReadonlyArray<WalletStandardWalletAccount>,\n address: string,\n): WalletStandardWalletAccount | undefined => accounts.find((a) => a.address === address);\n\nexport { findAccount, getFeature, slugify };\n","import type { Account, ChainBase, Connector, ConnectorEvent, WalletSigner } from \"@usebutr/core\";\nimport { buildAccount, logWarn, resolveChain, sanitizeIcon } from \"@usebutr/core\";\n\nimport { findAccount, getFeature } from \"./primitives\";\nimport type {\n StandardConnectFeature,\n StandardDisconnectFeature,\n StandardEventsFeature,\n WalletStandardWallet,\n WalletStandardWalletAccount,\n} from \"./types\";\n\ntype WalletStandardCoreInput = {\n /** The platform's chain registry, for chain names. A chain outside it is\n * named by its CAIP-2 id. */\n chains: ReadonlyArray<ChainBase>;\n /** Stable adapter id. Each platform package owns its own slug prefix. */\n id: string;\n /** How the platform reads in errors: `Solana`, `Sui`, `Bitcoin`. */\n label: string;\n /** CAIP-2 namespace without its colon: `solana`, `sui`, `bip122`. */\n namespace: string;\n /** Chain ids worth preferring over the first advertised one. Matched in\n * the wallet's own order, so a wallet listing `mainnet-beta` before\n * `mainnet` keeps the chain it advertised first. */\n preferredChainIds: ReadonlyArray<string>;\n /** Called with a function that pushes a synthetic `disconnected` event to\n * all current subscribers. Discovery invokes it on `unregister`. */\n registerDisconnector?: (emit: () => void) => void;\n /** Re-point the active chain when the wallet reports a `chains` change.\n * Polkadot opts out: its wallets advertise a single relay chain. */\n trackChainChanges: boolean;\n wallet: WalletStandardWallet;\n};\n\n/** The members every Wallet Standard adapter shares, ready to spread. */\ntype WalletStandardConnector = Pick<\n Connector,\n \"connect\" | \"disconnect\" | \"getAccounts\" | \"icon\" | \"id\" | \"name\" | \"subscribe\"\n> & {\n getSigner: () => Promise<WalletSigner>;\n switchChain?: (chain: ChainBase) => Promise<void>;\n};\n\ntype WalletStandardCore = {\n /** Spread into the adapter. `disconnect`, `subscribe` and `switchChain` are\n * present only when the wallet supports them. */\n base: WalletStandardConnector;\n /** The active chain, for results that mint their own `Account`. */\n currentChain: () => ChainBase;\n /** The Wallet Standard account a call signs with: the one matching\n * `account`, else the active one. Throws for an account the wallet does\n * not expose. */\n resolveAccount: (account?: Account) => WalletStandardWalletAccount;\n /** The chain id a call targets: `chain` when given, else the current one.\n * Throws for a chain outside the namespace or not advertised. */\n resolveChainId: (chain?: ChainBase) => string;\n};\n\n/**\n * `null` when the wallet advertises no chain in `namespace` or lacks\n * `standard:connect`, so a multi-chain wallet yields one adapter per\n * platform it actually speaks.\n */\nconst createWalletStandardCore = ({\n chains,\n id,\n label,\n namespace,\n preferredChainIds,\n registerDisconnector,\n trackChainChanges,\n wallet,\n}: WalletStandardCoreInput): WalletStandardCore | null => {\n const prefix = `${namespace}:`;\n const preferred = new Set(preferredChainIds);\n const pickChain = (advertised: ReadonlyArray<string>): string | undefined =>\n advertised.find((c) => preferred.has(c)) ?? advertised.find((c) => c.startsWith(prefix));\n\n const initialChainId = pickChain(wallet.chains);\n const connect = getFeature<StandardConnectFeature>(wallet, \"standard:connect\", \"connect\");\n if (initialChainId === undefined || connect === undefined) {\n return null;\n }\n const disconnect = getFeature<StandardDisconnectFeature>(\n wallet,\n \"standard:disconnect\",\n \"disconnect\",\n );\n const events = getFeature<StandardEventsFeature>(wallet, \"standard:events\", \"on\");\n\n let currentChainId = initialChainId;\n const currentChain = () => resolveChain(currentChainId, chains);\n const toAccounts = (accounts: ReadonlyArray<WalletStandardWalletAccount>) => {\n const chain = currentChain();\n return accounts.map((a) => buildAccount(a.address, chain));\n };\n\n const listeners = new Set<(event: ConnectorEvent) => void>();\n const emit = (event: ConnectorEvent) => {\n for (const listener of listeners) {\n listener(event);\n }\n };\n const toEvent = (accounts: ReadonlyArray<WalletStandardWalletAccount>): ConnectorEvent =>\n accounts.length === 0\n ? { type: \"disconnected\" }\n : { accounts: toAccounts(accounts), type: \"accountsChanged\" };\n registerDisconnector?.(() => {\n emit({ type: \"disconnected\" });\n });\n\n const resolveChainId = (chain?: ChainBase): string => {\n if (chain === undefined) {\n return currentChainId;\n }\n if (chain.namespace !== namespace) {\n throw new Error(\n `${label} adapter received non-${label} chain \"${chain.id}\". Pass a chain with namespace \"${namespace}\".`,\n );\n }\n if (!wallet.chains.includes(chain.id)) {\n throw new Error(\n `Wallet ${wallet.name} does not advertise chain \"${chain.id}\". Available: ${wallet.chains.join(\", \")}`,\n );\n }\n return chain.id;\n };\n\n const subscribe = (listener: (event: ConnectorEvent) => void) => {\n listeners.add(listener);\n // Each subscriber owns a wallet listener, so it delivers to that\n // subscriber alone: broadcasting would repeat a change per subscriber.\n const off = events?.on(\"change\", (changes) => {\n const next = trackChainChanges && changes.chains ? pickChain(changes.chains) : undefined;\n if (next !== undefined) {\n currentChainId = next;\n }\n if (changes.accounts !== undefined) {\n listener(toEvent(changes.accounts));\n } else if (next !== undefined) {\n listener(toEvent(wallet.accounts));\n }\n });\n return () => {\n listeners.delete(listener);\n off?.();\n };\n };\n\n const advertisedInNamespace = wallet.chains.filter((c) => c.startsWith(prefix));\n\n const base: WalletStandardConnector = {\n async connect(options) {\n await connect.connect(options?.silent === true ? { silent: true } : undefined);\n },\n getAccounts: () => Promise.resolve(toAccounts(wallet.accounts)),\n getSigner: () => Promise.resolve({ kind: \"wallet-standard\", wallet }),\n icon: sanitizeIcon(wallet.icon),\n id,\n name: wallet.name,\n ...(disconnect !== undefined && {\n async disconnect() {\n try {\n await disconnect.disconnect();\n } catch (error) {\n logWarn(`[butr] ${label} Wallet Standard disconnect threw:`, error);\n }\n },\n }),\n // Discovery wires `unregister` through the same listeners, so a wallet\n // without `standard:events` can still report its removal.\n ...((events !== undefined || registerDisconnector !== undefined) && { subscribe }),\n // Wallet Standard has no switch-network call: this re-points butr's view\n // (and every later call's `chain` input), which only means something\n // when the wallet advertises more than one chain.\n ...(advertisedInNamespace.length > 1 && {\n switchChain: async (chain: ChainBase) => {\n currentChainId = resolveChainId(chain);\n emit(toEvent(wallet.accounts));\n await Promise.resolve();\n },\n }),\n };\n\n return {\n base,\n currentChain,\n resolveAccount: (account) => {\n const match =\n account === undefined\n ? wallet.accounts[0]\n : findAccount(wallet.accounts, account.walletAddress);\n if (match === undefined) {\n throw new Error(\n account === undefined\n ? `Wallet ${wallet.name} has no connected account`\n : `Wallet ${wallet.name} does not expose account ${account.walletAddress}`,\n );\n }\n return match;\n },\n resolveChainId,\n };\n};\n\nexport type { WalletStandardConnector, WalletStandardCore, WalletStandardCoreInput };\nexport { createWalletStandardCore };\n","import type { WalletAdapter } from \"@usebutr/core\";\nimport { logWarn } from \"@usebutr/core\";\nimport type { Wallets as ImportedWallets } from \"@wallet-standard/app\";\n\nimport type {\n WalletStandardAppModule,\n WalletStandardFeature,\n WalletStandardModuleLoader,\n WalletStandardWallet,\n} from \"./types\";\n\ntype ImportedWallet = ReturnType<ImportedWallets[\"get\"]>[number];\n\n// Any object satisfies the open-ended feature shape; `getFeature` checks the\n// member it needs before a caller relies on it.\nconst isFeatureObject = (value: unknown): value is WalletStandardFeature =>\n typeof value === \"object\" && value !== null;\n\nconst toFeatureRecord = (features: ImportedWallet[\"features\"]) => {\n const record: Partial<Record<string, WalletStandardFeature>> = {};\n for (const [name, value] of Object.entries(features)) {\n // Stored as-is rather than spread: a spread would detach its methods from\n // the wallet's own object, breaking any implementation that reads `this`.\n if (isFeatureObject(value)) {\n record[name] = value;\n }\n }\n return record;\n};\n\nconst wrappers = new WeakMap<ImportedWallet, WalletStandardWallet>();\n\n/**\n * Reads through to the wallet: it re-points `accounts` on connect, so a copy\n * pins the pre-connect empty list and every `getAccounts()` then resolves [].\n * Memoised because `unregister` is matched to `register` by object identity.\n */\nconst mapWallet = (wallet: ImportedWallet): WalletStandardWallet => {\n const cached = wrappers.get(wallet);\n if (cached !== undefined) {\n return cached;\n }\n const wrapper: WalletStandardWallet = {\n get accounts() {\n return wallet.accounts;\n },\n get chains() {\n return wallet.chains;\n },\n features: toFeatureRecord(wallet.features),\n icon: wallet.icon,\n name: wallet.name,\n version: wallet.version,\n };\n wrappers.set(wallet, wrapper);\n return wrapper;\n};\n\nlet warnedMissingApp = false;\n\n/**\n * Returns `null` when the wallet doesn't advertise this platform, so a\n * multi-chain wallet yields one adapter per platform. `registerDisconnector`\n * wires the synthetic `disconnected` fired on Wallet Standard `unregister`.\n */\ntype WalletStandardAdapterBuilder = (\n wallet: WalletStandardWallet,\n registerDisconnector: (emit: () => void) => void,\n) => WalletAdapter | null;\n\nconst loadWalletStandardModule = async (): Promise<WalletStandardAppModule> => {\n const imported = await import(\"@wallet-standard/app\");\n if (!(\"getWallets\" in imported) || typeof imported.getWallets !== \"function\") {\n throw new Error(\"@wallet-standard/app has no getWallets export\");\n }\n const source = imported.getWallets();\n return {\n getWallets: () => ({\n get: () => source.get().map(mapWallet),\n on: (event, listener) =>\n source.on(event, (...wallets) => {\n listener(...wallets.map(mapWallet));\n }),\n }),\n };\n};\n\n/**\n * `@wallet-standard/app` is an optional peer dep: a failed import quietly\n * disables discovery. Disconnectors are keyed by wallet identity rather\n * than adapter id because `register` / `unregister` emit the same object.\n */\nconst discoverWalletStandard = (\n onAdapter: (adapter: WalletAdapter) => void,\n build: WalletStandardAdapterBuilder,\n loadModule: WalletStandardModuleLoader = loadWalletStandardModule,\n): (() => void) => {\n // SAFETY: the effect cleanup sets this after the await; TypeScript cannot see writes from closures\n let cancelled = false as boolean;\n let internalUnsub: (() => void) | null = null;\n\n void (async () => {\n let mod: WalletStandardAppModule;\n try {\n mod = await loadModule();\n } catch (error) {\n if (!warnedMissingApp) {\n warnedMissingApp = true;\n logWarn(\n \"[butr] Wallet Standard wallet discovery is disabled: `@wallet-standard/app` could not be loaded. Install it (`npm i @wallet-standard/app`) to detect Wallet Standard wallets.\",\n error,\n );\n }\n return;\n }\n if (cancelled) {\n return;\n }\n\n const seenIds = new Set<string>();\n const disconnectors = new Map<WalletStandardWallet, () => void>();\n\n const tryAdd = (wallet: WalletStandardWallet) => {\n const adapter = build(wallet, (emit) => {\n disconnectors.set(wallet, emit);\n });\n if (!adapter || seenIds.has(adapter.id)) {\n return;\n }\n seenIds.add(adapter.id);\n onAdapter(adapter);\n };\n\n const app = mod.getWallets();\n for (const wallet of app.get()) {\n tryAdd(wallet);\n }\n\n const offRegister = app.on(\"register\", (...wallets) => {\n for (const wallet of wallets) {\n tryAdd(wallet);\n }\n });\n const offUnregister = app.on(\"unregister\", (...wallets) => {\n for (const wallet of wallets) {\n const emit = disconnectors.get(wallet);\n if (emit) {\n emit();\n disconnectors.delete(wallet);\n }\n }\n });\n internalUnsub = () => {\n offRegister();\n offUnregister();\n };\n })();\n\n return () => {\n cancelled = true;\n internalUnsub?.();\n internalUnsub = null;\n };\n};\n\nexport type { WalletStandardAdapterBuilder };\nexport { discoverWalletStandard, mapWallet };\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,21 @@
1
1
  {
2
2
  "name": "@usebutr/wallet-standard-shared",
3
- "version": "0.5.0",
3
+ "version": "2.0.0",
4
4
  "description": "Shared Wallet Standard discovery primitives used by @usebutr/svm, @usebutr/sui, @usebutr/bitcoin.",
5
+ "keywords": [
6
+ "butr",
7
+ "dapp",
8
+ "discovery",
9
+ "react",
10
+ "typescript",
11
+ "wallet",
12
+ "wallet-standard",
13
+ "web3"
14
+ ],
15
+ "homepage": "https://docs.usebutr.com/api/wallet-standard-shared",
16
+ "bugs": {
17
+ "url": "https://github.com/pedroapfilho/usebutr/issues"
18
+ },
5
19
  "license": "MIT",
6
20
  "author": "Pedro Filho <pedro@filho.me>",
7
21
  "repository": {
@@ -10,7 +24,8 @@
10
24
  "directory": "packages/wallet-standard-shared"
11
25
  },
12
26
  "files": [
13
- "dist"
27
+ "dist",
28
+ "CHANGELOG.md"
14
29
  ],
15
30
  "type": "module",
16
31
  "sideEffects": false,
@@ -21,13 +36,16 @@
21
36
  "default": "./dist/index.js"
22
37
  }
23
38
  },
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
24
42
  "dependencies": {
25
- "@usebutr/core": "2.0.0"
43
+ "@usebutr/core": "3.0.0"
26
44
  },
27
45
  "devDependencies": {
28
46
  "@wallet-standard/app": "^1.1.1",
29
- "typescript": "^6.0.3",
30
- "vitest": "^4.1.10",
47
+ "typescript": "^7.0.2",
48
+ "vitest": "^5.0.0",
31
49
  "@repo/config-vitest": "0.0.0",
32
50
  "@repo/typescript-config": "0.0.0"
33
51
  },