@usebutr/svm 2.0.0 → 3.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,278 @@
1
+ # @usebutr/svm
2
+
3
+ ## 3.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
+ - @usebutr/wallet-standard-shared@2.0.0
34
+
35
+ ## 2.0.2
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies [[`bc5b58c`](https://github.com/pedroapfilho/usebutr/commit/bc5b58cd87601be38dbb77a14e16eaaa36e8f012), [`bc5b58c`](https://github.com/pedroapfilho/usebutr/commit/bc5b58cd87601be38dbb77a14e16eaaa36e8f012)]:
40
+ - @usebutr/wallet-standard-shared@1.0.0
41
+ - @usebutr/core@2.0.1
42
+
43
+ ## 2.0.1
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies [302ae4a]
48
+ - @usebutr/wallet-standard-shared@0.5.1
49
+
50
+ ## 2.0.0
51
+
52
+ ### Major Changes
53
+
54
+ - 9b1caa2: Correctness pass across the storage, hydration and connector layers. Several of
55
+ these change observable behaviour; the ones worth knowing about before upgrading:
56
+
57
+ **Breaking**
58
+
59
+ - `SuiWallet.signTransaction` now returns `{ bytes, signature }` instead of
60
+ `Uint8Array`. Sui's `executeTransactionBlock` needs both, and each connector
61
+ previously returned a different half, so the old value could not be broadcast
62
+ and a consumer could not tell which half they held. SVM and Bitcoin keep their
63
+ single-`Uint8Array` shape.
64
+ - `@usebutr/svm`'s `sendTx` returns the signature base58-encoded, matching the
65
+ WalletConnect namespace, the Ledger app, Solana explorers and
66
+ `getSignatureStatuses`. It previously returned base64.
67
+ - `@usebutr/sui`'s feature input types drop the `string` arm from
68
+ `transaction`; wallets only ever accepted the `toJSON()` form. Strings and BCS
69
+ bytes are now wrapped for you, so `sendTx` accepts strictly more than before.
70
+
71
+ **Behaviour fixes**
72
+
73
+ - `WalletStorage.setPool` and `removePoolEntry` could self-deadlock on a corrupt
74
+ pool payload, leaving `connectWallet` unresolved and every later pool write
75
+ jammed for the page's life. Both are fixed, and `readWalletSnapshot` now shares
76
+ one codec with `WalletStorage` so the server and client decodes cannot drift.
77
+ - A seeded (SSR) entry whose silent reconnect failed used to stay in the pool
78
+ backed by a placeholder connector forever, so `useSigner`/`useBalance`
79
+ reported an error and `useConnectionStatus` reported `"reconnecting"`
80
+ permanently. Those entries are now dropped.
81
+ - `useSigner` and `useBalance` stay `idle` for a wallet that is still
82
+ reconnecting instead of surfacing a placeholder rejection as `status: "error"`.
83
+ - `useConnectionStatus` lets a live `"connecting"`/`"error"` take precedence over
84
+ the derived `"reconnecting"`, so an in-flight attempt and a connection error
85
+ are no longer hidden. New `useIsReconnecting(connectorId?)` answers the
86
+ per-wallet question directly.
87
+ - A background restore no longer writes the connect-attempt status or steals
88
+ `activeConnectorId`, and a superseded `CONNECT_FAILED` no longer overwrites the
89
+ current attempt.
90
+ - An externally-disconnected connector is now torn down, not just dropped from
91
+ the pool, so a cached adapter is not reused while still holding a session.
92
+ - EVM: a malformed provider response is treated as absent rather than `""`, which
93
+ previously produced chain `eip155:0`, zero-length signatures, empty transaction
94
+ hashes and a confident `0 ETH`. `switchChain` gained the same-chain
95
+ short-circuit `sendTxToChain` already had.
96
+ - WalletConnect: accounts carry their own chain instead of all being stamped with
97
+ the active one, so a multi-chain session no longer returns a wrong-chain
98
+ address. One pairing now covers every configured namespace, the pairing URI
99
+ survives a reconnect, and concurrent connects share one pairing.
100
+ - Bitcoin: the sats-connect (Xverse) adapter honours `silent`, so a reload no
101
+ longer triggers an unsolicited approval prompt, and exposes the payment address
102
+ as its single account instead of also presenting the ordinals address as
103
+ spendable.
104
+ - Polkadot: the injected adapter actually delivers events to subscribers,
105
+ `switchChain` notifies like its Wallet Standard sibling, and chain resolution
106
+ prefers the mainnets rather than whatever the wallet listed first.
107
+ - Ledger: the device session is committed atomically, so a locked device no
108
+ longer leaves an open transport that reports itself connected, and concurrent
109
+ connects open one transport.
110
+ - SVM: `sendTxToChain` submits to the chain you asked for instead of the
111
+ adapter's current one, and rejects a chain the wallet does not advertise.
112
+ - `@usebutr/svm`'s `switchChain` capability now counts only `solana:` chains, so
113
+ a multi-namespace wallet no longer advertises a method that always throws.
114
+
115
+ **Testing**
116
+
117
+ - `createFakePersistence` is now the real `WalletStorage` over memory drivers
118
+ rather than a parallel implementation, so it inherits upsert semantics,
119
+ validation and JSON round-tripping. Two divergences are fixed as a result:
120
+ `setPool` upserts rather than replaces, and `clearAll` leaves the
121
+ user-disconnected flag alone.
122
+ - `createFakeConnectedWallet` rejects being given both an `adapter` and explicit
123
+ `addresses`/`accounts`, a combination whose halves could disagree.
124
+
125
+ ### Patch Changes
126
+
127
+ - Updated dependencies [9b1caa2]
128
+ - @usebutr/wallet-standard-shared@0.5.0
129
+ - @usebutr/core@2.0.0
130
+
131
+ ## 1.0.1
132
+
133
+ ### Patch Changes
134
+
135
+ - 8ecaf89: Close five gaps that made multi-chain integration harder than it needed to be.
136
+
137
+ **Group wallets by platform without writing the loop yourself.**
138
+ `groupByPlatform(items, getPlatform)` in `@usebutr/core` buckets any list into
139
+ a `Map<ChainPlatform, T[]>` keyed in `CHAIN_PLATFORMS` order with empty
140
+ platforms omitted, and `@usebutr/react` adds
141
+ `useDiscoveredWalletsByPlatform()` / `useConnectedWalletsByPlatform()` on top.
142
+ A multi-chain wallet announces one adapter per platform, so every app hitting
143
+ more than one chain was writing this bucketing by hand.
144
+
145
+ **`autoDiscovery` takes an allowlist array, and says something when it's empty.**
146
+ `autoDiscovery(["evm", "svm"])` now works alongside the object form and reads
147
+ as the allowlist it is. An options value that enables no platforms logs a
148
+ warning instead of silently discovering nothing: a list built at runtime that
149
+ comes back empty was otherwise indistinguishable from "no wallets installed".
150
+ The bare `autoDiscovery()` everything-path is unchanged and stays silent.
151
+
152
+ **`createFakeConnectedWallet` in `@usebutr/testing`.** Builds the
153
+ `{ account, accounts, connector }` pool entry that UI tests actually render,
154
+ with accounts constructed through `buildAccount` so the `<chain>:<address>` id
155
+ format is never restated in a fixture. Defaults to the platform's mainnet chain
156
+ and a deterministic address; pass `adapter` to wrap a connector you already
157
+ built.
158
+
159
+ **The icon sanitization contract is now on the type.** `Connector.icon` is
160
+ already run through `sanitizeIcon` at discovery, so it is a trimmed non-empty
161
+ string or `undefined`, safe to hand to `next/image` with no second call and no
162
+ `icon !== ""` guard. `ConnectorMeta.icon` documents the opposite: it is
163
+ consumer-supplied and not sanitized.
164
+
165
+ **`createSignInFlow` in `@usebutr/core`.** Wraps the nonce, capability gate,
166
+ signature, base64 encoding, and verification handshake that every wallet-auth
167
+ app writes identically. Solana wallets advertising `solana:signIn` take the
168
+ SIWS path automatically. It deliberately does not define a message format: pass
169
+ `buildMessage` to match your backend. Also re-exports the SVM SIWS types
170
+ (`SolanaSignInFeature`, `SolanaSignInInput`, `SolanaSignInOutput`) from
171
+ `@usebutr/svm`, which were defined but not exported.
172
+
173
+ - Updated dependencies [8ecaf89]
174
+ - @usebutr/core@1.1.0
175
+ - @usebutr/wallet-standard-shared@0.4.1
176
+
177
+ ## 1.0.0
178
+
179
+ ### Major Changes
180
+
181
+ - f0a5116: **Breaking:** the `platform` field is gone from the `PlatformDiscoverer` type in `@usebutr/core`, and from the `evmDiscoverer`, `svmDiscoverer`, `suiDiscoverer`, `bitcoinDiscoverer` and `polkadotDiscoverer` objects that implement it. Nothing read it: the aggregator keys discoverers by `ChainPlatform` in its own registry, so the field only restated the key.
182
+
183
+ Migration: read the platform from the `KNOWN_DISCOVERERS` key in `@usebutr/wallets` (`Object.entries(KNOWN_DISCOVERERS)`), or from `adapter.chainPlatform` on a discovered adapter. Custom `PlatformDiscoverer` implementations must drop the `platform` property; keeping it is now an excess-property error.
184
+
185
+ - f0a5116: **Breaking:** `slugify` is no longer exported from `@usebutr/svm`, `@usebutr/sui` or `@usebutr/bitcoin`. Each package exported a one-argument wrapper that only bound a platform prefix onto the canonical two-argument helper, so three names shadowed one implementation.
186
+
187
+ Migration: import `slugify` from `@usebutr/wallet-standard-shared` and pass the platform prefix as the first argument. `slugify(name)` from `@usebutr/svm` becomes `slugify("svm", name)`; the `@usebutr/sui` prefix is `"sui"` and the `@usebutr/bitcoin` prefix is `"btc"`. Adapter ids are byte-for-byte the same.
188
+
189
+ ### Patch Changes
190
+
191
+ - 7887cf0: Resolve capabilities through `buildWalletCapabilities` from `@usebutr/wallet-standard-shared` instead of a per-package copy of the same object literal. Resolver names, input types and returned flags are unchanged. The injected Polkadot profile now derives its chain count from `POLKADOT_CHAINS_LIST` rather than asserting `switchChain: true` directly; the result is the same.
192
+ - 7887cf0: Move each package's `discover*Adapters` function next to the adapter builder it wraps and drop the single-function `wallet-standard.ts` module. The functions are still exported from the package root under the same names; only an internal file boundary went away.
193
+ - Updated dependencies [7887cf0]
194
+ - Updated dependencies [7887cf0]
195
+ - Updated dependencies [f0a5116]
196
+ - Updated dependencies [f0a5116]
197
+ - Updated dependencies [7887cf0]
198
+ - @usebutr/core@1.0.0
199
+ - @usebutr/wallet-standard-shared@0.4.0
200
+
201
+ ## 0.2.7
202
+
203
+ ### Patch Changes
204
+
205
+ - 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:`.
206
+ - Updated dependencies [efe4550]
207
+ - Updated dependencies [4467a5e]
208
+ - @usebutr/wallet-standard-shared@0.3.0
209
+ - @usebutr/core@0.5.0
210
+
211
+ ## 0.2.6
212
+
213
+ ### Patch Changes
214
+
215
+ - Updated dependencies [c1309ee]
216
+ - @usebutr/core@0.4.2
217
+ - @usebutr/wallet-standard-shared@0.2.6
218
+
219
+ ## 0.2.5
220
+
221
+ ### Patch Changes
222
+
223
+ - 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.
224
+ - Updated dependencies [937dfae]
225
+ - @usebutr/core@0.4.1
226
+ - @usebutr/wallet-standard-shared@0.2.5
227
+
228
+ ## 0.2.4
229
+
230
+ ### Patch Changes
231
+
232
+ - 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.
233
+ - Updated dependencies [b5322ae]
234
+ - Updated dependencies [d5f32c7]
235
+ - Updated dependencies [a46eecd]
236
+ - Updated dependencies [3f26776]
237
+ - @usebutr/core@0.4.0
238
+ - @usebutr/wallet-standard-shared@0.2.4
239
+
240
+ ## 0.2.3
241
+
242
+ ### Patch Changes
243
+
244
+ - Updated dependencies [886ee1d]
245
+ - @usebutr/core@0.3.0
246
+ - @usebutr/wallet-standard-shared@0.2.3
247
+
248
+ ## 0.2.2
249
+
250
+ ### Patch Changes
251
+
252
+ - Updated dependencies [db5d7e9]
253
+ - @usebutr/core@0.2.2
254
+ - @usebutr/wallet-standard-shared@0.2.2
255
+
256
+ ## 0.2.1
257
+
258
+ ### Patch Changes
259
+
260
+ - f846e77: Wallet-announced icons are trimmed of surrounding whitespace on ingestion. Some wallets ship data-URI icons with a leading newline, which strict consumers reject — Next.js's `<Image>` throws because `src` must not start with a control character. `@usebutr/core` exports a `sanitizeIcon` helper; the EIP-6963 and Wallet Standard adapters apply it, and an all-whitespace icon now resolves to `undefined` rather than a blank string.
261
+ - Updated dependencies [f846e77]
262
+ - @usebutr/core@0.2.1
263
+ - @usebutr/wallet-standard-shared@0.2.1
264
+
265
+ ## 0.2.0
266
+
267
+ ### Minor Changes
268
+
269
+ - 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.
270
+
271
+ `@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.
272
+
273
+ ### Patch Changes
274
+
275
+ - Updated dependencies [b77a477]
276
+ - Updated dependencies [b77a477]
277
+ - @usebutr/core@0.2.0
278
+ - @usebutr/wallet-standard-shared@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,66 @@
1
+ # @usebutr/svm
2
+
3
+ Wallet Standard adapter for butr (Solana / SVM).
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/react @usebutr/core @usebutr/svm zustand @wallet-standard/app
12
+ ```
13
+
14
+ React 19 is a peer dependency of the provider. Discovery does not render a connect modal; add your picker as the provider child. `@wallet-standard/app` is required for Wallet Standard discovery.
15
+
16
+ ## Usage
17
+
18
+ ```tsx
19
+ import type { ReactNode } from "react";
20
+ import type { WalletManagerConfig } from "@usebutr/core";
21
+ import { discoverSvmAdapters } from "@usebutr/svm";
22
+ import { WalletManagerProvider } from "@usebutr/react";
23
+
24
+ // Every `discover*Adapters` export is a wallet source as-is.
25
+ const config: WalletManagerConfig = { sources: [discoverSvmAdapters] };
26
+
27
+ export const WalletProvider = ({ children }: { children: ReactNode }) => (
28
+ <WalletManagerProvider config={config}>{children}</WalletManagerProvider>
29
+ );
30
+ ```
31
+
32
+ Each adapter defines `sendTx` (`solana:signAndSendTransaction`, resolves the base58 signature), `signMessage`, `signTransaction` (resolves the signed transaction bytes) and `signIn` (Sign In With Solana) only when the wallet advertises the matching feature, and `switchChain` only when it advertises more than one Solana cluster. Wallet Standard carries the chain per call, so `options.chain` routes one transaction without moving the wallet. Balances and receipts need your own RPC client. Check a method's presence before calling it:
33
+
34
+ ```ts
35
+ import type { ConnectedWallet } from "@usebutr/core";
36
+ import { SVM_CHAINS } from "@usebutr/core";
37
+
38
+ export const send = async (wallet: ConnectedWallet<"svm">, serializedTx: Uint8Array) => {
39
+ if (!wallet.connector.sendTx) {
40
+ throw new Error(`${wallet.connector.name} cannot send Solana transactions`);
41
+ }
42
+ return wallet.connector.sendTx(serializedTx, {
43
+ account: wallet.account,
44
+ chain: SVM_CHAINS.devnet,
45
+ });
46
+ };
47
+ ```
48
+
49
+ An `account` the wallet does not expose, or a chain it does not advertise, rejects. `getSigner()` resolves `{ kind: "wallet-standard", wallet }`: reach any other feature with `getFeature` from `@usebutr/wallet-standard-shared` and the feature types this package exports. Chain registries (`SVM_CHAINS`, `SVM_CHAINS_LIST`) come from `@usebutr/core`.
50
+
51
+ `@usebutr/svm/transaction` exports `prepareSolanaTransaction(tx, signer)` for
52
+ transports returning a bare signature. It exposes the serialized `message`
53
+ and `withSignature(bytes)`, which fills the signer's slot in a copy of the
54
+ transaction. Ledger and WalletConnect share this legacy/v0 signing codec;
55
+ the entry point loads no discovery code.
56
+
57
+ ## Documentation
58
+
59
+ - [Package reference](https://docs.usebutr.com/api/svm)
60
+ - [Getting started](https://docs.usebutr.com/getting-started/quickstart)
61
+ - [Examples and source](https://github.com/pedroapfilho/usebutr)
62
+ - [Report an issue](https://github.com/pedroapfilho/usebutr/issues)
63
+
64
+ ## License
65
+
66
+ [MIT](./LICENSE), copyright 2026 Pedro Filho.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ChainBase, PlatformDiscoverer, WalletAdapter, WalletCapabilities } from "@usebutr/core";
2
- import { WalletStandardWallet, WalletStandardWalletAccount } from "@usebutr/wallet-standard-shared";
1
+ import { PlatformDiscoverer, SignInInput, SvmAdapter, WalletAdapter } from "@usebutr/core";
2
+ import { WalletStandardModuleLoader, WalletStandardWallet, WalletStandardWalletAccount } from "@usebutr/wallet-standard-shared";
3
3
  //#region src/wallet-standard-types.d.ts
4
4
  type SolanaSignMessageInput = {
5
5
  account: WalletStandardWalletAccount;
@@ -11,6 +11,7 @@ type SolanaSignMessageOutput = {
11
11
  };
12
12
  type SolanaSignMessageFeature = {
13
13
  signMessage: (...inputs: ReadonlyArray<SolanaSignMessageInput>) => Promise<ReadonlyArray<SolanaSignMessageOutput>>;
14
+ version?: string;
14
15
  };
15
16
  type SolanaSignAndSendTransactionInput = {
16
17
  account: WalletStandardWalletAccount;
@@ -22,9 +23,21 @@ type SolanaSignAndSendTransactionOutput = {
22
23
  };
23
24
  type SolanaSignAndSendTransactionFeature = {
24
25
  signAndSendTransaction: (...inputs: ReadonlyArray<SolanaSignAndSendTransactionInput>) => Promise<ReadonlyArray<SolanaSignAndSendTransactionOutput>>;
26
+ version?: string;
25
27
  };
26
- /** SIWS message fields, all optional; the wallet fills defaults. */
27
- type SolanaSignInInput = Record<string, unknown>;
28
+ type SolanaSignTransactionInput = {
29
+ account: WalletStandardWalletAccount;
30
+ chain?: string;
31
+ transaction: Uint8Array;
32
+ };
33
+ type SolanaSignTransactionOutput = {
34
+ signedTransaction: Uint8Array;
35
+ };
36
+ type SolanaSignTransactionFeature = {
37
+ signTransaction: (...inputs: ReadonlyArray<SolanaSignTransactionInput>) => Promise<ReadonlyArray<SolanaSignTransactionOutput>>;
38
+ version?: string;
39
+ };
40
+ type SolanaSignInInput = SignInInput;
28
41
  type SolanaSignInOutput = {
29
42
  account: WalletStandardWalletAccount;
30
43
  signature: Uint8Array;
@@ -32,66 +45,25 @@ type SolanaSignInOutput = {
32
45
  };
33
46
  type SolanaSignInFeature = {
34
47
  signIn: (input?: SolanaSignInInput) => Promise<ReadonlyArray<SolanaSignInOutput>>;
48
+ version?: string;
35
49
  };
36
50
  //#endregion
37
51
  //#region src/wallet-standard-adapter.d.ts
38
52
  /**
39
- * Wallet Standard has no switch-cluster RPC: `switchChain` re-points butr's
40
- * view only, and `signAndSendTransaction` takes the chain per call.
41
- * `requestAccounts` re-runs `standard:connect`; some wallets answer silently.
53
+ * Wallet Standard carries the chain per call, so `sendTx` and
54
+ * `signTransaction` route `options.chain` without moving the wallet.
55
+ * Balances and receipts need an RPC client, which butr does not ship.
42
56
  */
43
57
  declare const buildSvmAdapter: (wallet: WalletStandardWallet,
44
- /** Optional. Called with a function that pushes a synthetic
45
- * `disconnected` event to all current subscribers. The discovery
46
- * layer invokes it on Wallet Standard `unregister` so a connected
47
- * pool entry tears down when its extension is removed. */
48
- registerDisconnector?: (emit: () => void) => void) => WalletAdapter | null;
58
+ /** Discovery passes this so a Wallet Standard `unregister` tears down the
59
+ * connected pool entry. */
60
+ registerDisconnector?: (emit: () => void) => void) => SvmAdapter | null;
49
61
  /**
50
62
  * Requires the optional `@wallet-standard/app` peer dep; without it SVM
51
63
  * discovery silently does nothing. The returned unsubscribe is safe to call
52
64
  * before the dynamic import has resolved.
53
65
  */
54
- declare const discoverSvmAdapters: (onAdapter: (adapter: WalletAdapter) => void) => (() => void);
55
- //#endregion
56
- //#region src/chains.d.ts
57
- declare const SVM_CHAINS: {
58
- readonly devnet: {
59
- readonly id: "solana:devnet";
60
- readonly name: "Solana Devnet";
61
- readonly namespace: "solana";
62
- readonly reference: "devnet";
63
- };
64
- readonly mainnet: {
65
- readonly id: "solana:mainnet";
66
- readonly name: "Solana Mainnet";
67
- readonly namespace: "solana";
68
- readonly reference: "mainnet";
69
- };
70
- readonly testnet: {
71
- readonly id: "solana:testnet";
72
- readonly name: "Solana Testnet";
73
- readonly namespace: "solana";
74
- readonly reference: "testnet";
75
- };
76
- };
77
- declare const SVM_CHAINS_LIST: ReadonlyArray<ChainBase>;
78
- //#endregion
79
- //#region src/capabilities.d.ts
80
- type WalletStandardCapabilityInput = {
81
- chainCount: number;
82
- features: {
83
- events: boolean;
84
- signAndSendTransaction: boolean;
85
- signIn: boolean;
86
- signMessage: boolean;
87
- signTransaction: boolean;
88
- };
89
- };
90
- /**
91
- * Solana Wallet Standard feature → butr capability mapping. The flags that
92
- * hold for every namespace are documented on `buildWalletCapabilities`.
93
- */
94
- declare const resolveWalletStandardCapabilities: (input: WalletStandardCapabilityInput) => WalletCapabilities;
66
+ declare const discoverSvmAdapters: (onAdapter: (adapter: WalletAdapter) => void, loadModule?: WalletStandardModuleLoader) => (() => void);
95
67
  //#endregion
96
68
  //#region src/discoverer.d.ts
97
69
  /**
@@ -100,17 +72,5 @@ declare const resolveWalletStandardCapabilities: (input: WalletStandardCapabilit
100
72
  */
101
73
  declare const svmDiscoverer: PlatformDiscoverer;
102
74
  //#endregion
103
- //#region src/signer-augmentation.d.ts
104
- /**
105
- * Augment `@usebutr/core`'s `SignerForPlatform` registry with the SVM
106
- * entry. SVM's `getSigner()` returns the Wallet Standard wallet
107
- * object, which consumers narrow further via its `features` map.
108
- */
109
- declare module "@usebutr/core" {
110
- interface SignerForPlatform {
111
- svm: WalletStandardWallet;
112
- }
113
- }
114
- //#endregion
115
- export { SVM_CHAINS, SVM_CHAINS_LIST, type SolanaSignAndSendTransactionFeature, type SolanaSignAndSendTransactionInput, type SolanaSignAndSendTransactionOutput, type SolanaSignInFeature, type SolanaSignInInput, type SolanaSignInOutput, type SolanaSignMessageFeature, type SolanaSignMessageInput, type SolanaSignMessageOutput, type WalletStandardCapabilityInput, buildSvmAdapter, discoverSvmAdapters, resolveWalletStandardCapabilities, svmDiscoverer };
75
+ export { type SolanaSignAndSendTransactionFeature, type SolanaSignAndSendTransactionInput, type SolanaSignAndSendTransactionOutput, type SolanaSignInFeature, type SolanaSignInInput, type SolanaSignInOutput, type SolanaSignMessageFeature, type SolanaSignMessageInput, type SolanaSignMessageOutput, type SolanaSignTransactionFeature, type SolanaSignTransactionInput, type SolanaSignTransactionOutput, buildSvmAdapter, discoverSvmAdapters, svmDiscoverer };
116
76
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/wallet-standard-types.ts","../src/wallet-standard-adapter.ts","../src/chains.ts","../src/capabilities.ts","../src/discoverer.ts","../src/signer-augmentation.ts"],"mappings":";;;KAOK;EACH,SAAS;EACT,SAAS;;KAGN;EACH,WAAW;EACX,eAAe;;KAGZ;EACH,iBACK,QAAQ,cAAc,4BACtB,QAAQ,cAAc;;KAGxB;EACH,SAAS;EACT;EACA,aAAa;;KAGV;EACH,WAAW;;KAGR;EACH,4BACK,QAAQ,cAAc,uCACtB,QAAQ,cAAc;;;KAoBxB,oBAAoB;KAEpB;EACH,SAAS;EACT,WAAW;EACX,eAAe;;KAGZ;EACH,SAAS,QAAQ,sBAAsB,QAAQ,cAAc;;;;;;;;;cCtCzD,kBACJ,QAAQ,sBAKR;;;;;AAAA,wBAAwB,8BACvB;;;;;;cAsKG,sBAAuB,YAAY,SAAS;;;cCtM5C;;;;;;;;;;;;;;;;;;;;cAgBA,iBAAiB,cAAc;;;KCfhC;EACH;EACA;IACE;IACA;IACA;IACA;IACA;;;;;;;cAQE,oCACJ,OAAO,kCACN;;;;;;;cCZG,eAAe;;;;;;;;;YCCT;IACR,KAAK"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/wallet-standard-types.ts","../src/wallet-standard-adapter.ts","../src/discoverer.ts"],"mappings":";;;KAQK;EACH,SAAS;EACT,SAAS;;KAGN;EACH,WAAW;EACX,eAAe;;KAGZ;EACH,iBACK,QAAQ,cAAc,4BACtB,QAAQ,cAAc;EAC3B;;KAGG;EACH,SAAS;EACT;EACA,aAAa;;KAGV;EACH,WAAW;;KAGR;EACH,4BACK,QAAQ,cAAc,uCACtB,QAAQ,cAAc;EAC3B;;KAGG;EACH,SAAS;EACT;EACA,aAAa;;KAGV;EACH,mBAAmB;;KAGhB;EACH,qBACK,QAAQ,cAAc,gCACtB,QAAQ,cAAc;EAC3B;;KAGG,oBAAoB;KAEpB;EACH,SAAS;EACT,WAAW;EACX,eAAe;;KAGZ;EACH,SAAS,QAAQ,sBAAsB,QAAQ,cAAc;EAC7D;;;;;;;;;cClCI,kBAAe,QACX,sBAGe;;;AAAA,wBAAC,8BACvB;;;;;;cA2FG,sBAAmB,YACX,SAAS,wBAAsB,aAC9B;;;;;;;cC7HT,eAAe"}
package/dist/index.js CHANGED
@@ -1,126 +1,67 @@
1
- import { buildAccount, bytesToBase58 } from "@usebutr/core";
2
- import { buildWalletCapabilities, createWalletStandardCore, discoverWalletStandard, getFeature, slugify } from "@usebutr/wallet-standard-shared";
1
+ import { SVM_CHAINS_LIST, buildAccount, bytesToBase58 } from "@usebutr/core";
2
+ import { createWalletStandardCore, discoverWalletStandard, getFeature, slugify } from "@usebutr/wallet-standard-shared";
3
3
 
4
- //#region src/capabilities.ts
5
- /**
6
- * Solana Wallet Standard feature → butr capability mapping. The flags that
7
- * hold for every namespace are documented on `buildWalletCapabilities`.
8
- */
9
- const resolveWalletStandardCapabilities = (input) => buildWalletCapabilities({
10
- chainCount: input.chainCount,
11
- events: input.features.events,
12
- sendTransaction: input.features.signAndSendTransaction,
13
- signIn: input.features.signIn,
14
- signMessage: input.features.signMessage,
15
- signTransaction: input.features.signTransaction
16
- });
17
-
18
- //#endregion
19
4
  //#region src/wallet-standard-adapter.ts
20
- const SOLANA_PREFIX = "solana:";
21
- const SOLANA_DECIMALS = 9;
22
- const SOLANA_MAINNETS = ["solana:mainnet", "solana:mainnet-beta"];
5
+ /** Solana features take and return arrays so one call can batch; butr sends
6
+ * one input, so an empty result is a wallet bug, not an empty success. */
7
+ const firstOutput = (outputs, method) => {
8
+ const [output] = outputs;
9
+ if (output === void 0) throw new Error(`${method} returned no outputs`);
10
+ return output;
11
+ };
23
12
  /**
24
- * Wallet Standard has no switch-cluster RPC: `switchChain` re-points butr's
25
- * view only, and `signAndSendTransaction` takes the chain per call.
26
- * `requestAccounts` re-runs `standard:connect`; some wallets answer silently.
13
+ * Wallet Standard carries the chain per call, so `sendTx` and
14
+ * `signTransaction` route `options.chain` without moving the wallet.
15
+ * Balances and receipts need an RPC client, which butr does not ship.
27
16
  */
28
17
  const buildSvmAdapter = (wallet, registerDisconnector) => {
29
18
  const core = createWalletStandardCore({
30
- chainPrefix: SOLANA_PREFIX,
19
+ chains: SVM_CHAINS_LIST,
31
20
  id: slugify("svm", wallet.name),
32
- label: "SVM",
21
+ label: "Solana",
33
22
  namespace: "solana",
34
- platform: "Solana",
35
- preferredChainIds: SOLANA_MAINNETS,
23
+ preferredChainIds: ["solana:mainnet", "solana:mainnet-beta"],
36
24
  registerDisconnector,
37
25
  trackChainChanges: true,
38
26
  wallet
39
27
  });
40
28
  if (core === null) return null;
41
- const signMessage = getFeature(wallet, "solana:signMessage");
42
- const signAndSendTx = getFeature(wallet, "solana:signAndSendTransaction");
43
- const signTx = getFeature(wallet, "solana:signTransaction");
44
- const signIn = getFeature(wallet, "solana:signIn");
45
- /**
46
- * Resolve a caller-supplied target into a chain the wallet advertises.
47
- * Accepts both the full CAIP-2 id (`solana:devnet`) and a bare reference
48
- * (`devnet`), since consumers reasonably reach for either.
49
- */
50
- const resolveTargetChain = (targetChainId) => {
51
- const candidate = targetChainId.startsWith(SOLANA_PREFIX) ? targetChainId : `${SOLANA_PREFIX}${targetChainId}`;
52
- if (!wallet.chains.includes(candidate)) throw new Error(`Wallet ${wallet.name} does not advertise chain "${candidate}". Available: ${wallet.chains.join(", ")}`);
53
- return candidate;
54
- };
55
- const signAndSend = async (tx, account, chain) => {
56
- if (signAndSendTx === void 0) throw new Error(`Wallet ${wallet.name} does not advertise solana:signAndSendTransaction`);
57
- const wsAccount = core.resolveAccount(account);
58
- if (!(tx instanceof Uint8Array)) throw new TypeError("SVM sendTx expects a serialized transaction (Uint8Array)");
59
- const [output] = await signAndSendTx.signAndSendTransaction({
60
- account: wsAccount,
61
- chain: chain ?? core.currentChainId(),
62
- transaction: tx
63
- });
64
- if (output === void 0) throw new Error("signAndSendTransaction returned no outputs");
65
- return bytesToBase58(output.signature);
66
- };
29
+ const signAndSend = getFeature(wallet, "solana:signAndSendTransaction", "signAndSendTransaction");
30
+ const signMessage = getFeature(wallet, "solana:signMessage", "signMessage");
31
+ const signTransaction = getFeature(wallet, "solana:signTransaction", "signTransaction");
32
+ const signIn = getFeature(wallet, "solana:signIn", "signIn");
67
33
  return {
68
- ...core,
69
- capabilities: resolveWalletStandardCapabilities({
70
- chainCount: core.chainCount,
71
- features: {
72
- events: core.hasEvents,
73
- signAndSendTransaction: Boolean(signAndSendTx),
74
- signIn: Boolean(signIn),
75
- signMessage: Boolean(signMessage),
76
- signTransaction: Boolean(signTx)
77
- }
78
- }),
34
+ ...core.base,
79
35
  chainPlatform: "svm",
80
- getBalance: () => Promise.resolve({
81
- decimals: SOLANA_DECIMALS,
82
- formatted: "0",
83
- symbol: "SOL",
84
- value: 0n
85
- }),
86
- getTransactionReceipt: () => Promise.resolve({ status: "Pending" }),
87
- async requestAccounts() {
88
- await core.connect();
89
- },
90
- sendTx: (tx, account) => signAndSend(tx, account),
91
- async sendTxToChain(tx, targetChainId, account, cb) {
92
- const target = resolveTargetChain(targetChainId);
93
- if (target !== core.currentChainId()) cb?.();
94
- return await signAndSend(tx, account, target);
95
- },
96
- async signMessage(msg, account) {
97
- if (signMessage === void 0) throw new Error(`Wallet ${wallet.name} does not advertise solana:signMessage`);
98
- const [output] = await signMessage.signMessage({
99
- account: core.resolveAccount(account),
100
- message: msg
101
- });
102
- if (output === void 0) throw new Error("signMessage returned no outputs");
36
+ ...signAndSend !== void 0 && { async sendTx(tx, options) {
37
+ const output = firstOutput(await signAndSend.signAndSendTransaction({
38
+ account: core.resolveAccount(options?.account),
39
+ chain: core.resolveChainId(options?.chain),
40
+ transaction: tx
41
+ }), "signAndSendTransaction");
42
+ return bytesToBase58(output.signature);
43
+ } },
44
+ ...signMessage !== void 0 && { async signMessage(message, options) {
45
+ const output = firstOutput(await signMessage.signMessage({
46
+ account: core.resolveAccount(options?.account),
47
+ message
48
+ }), "signMessage");
103
49
  return {
104
50
  signature: output.signature,
105
51
  signedMessage: output.signedMessage
106
52
  };
107
- },
108
- ...signTx === void 0 ? {} : { async signTransaction(tx, account) {
109
- const wsAccount = core.resolveAccount(account);
110
- if (!(tx instanceof Uint8Array)) throw new TypeError("SVM signTransaction expects a serialized transaction (Uint8Array)");
111
- const [output] = await signTx.signTransaction({
112
- account: wsAccount,
113
- chain: core.currentChainId(),
53
+ } },
54
+ ...signTransaction !== void 0 && { async signTransaction(tx, options) {
55
+ return firstOutput(await signTransaction.signTransaction({
56
+ account: core.resolveAccount(options?.account),
57
+ chain: core.resolveChainId(options?.chain),
114
58
  transaction: tx
115
- });
116
- if (output === void 0) throw new Error("signTransaction returned no outputs");
117
- return output.signedTransaction;
59
+ }), "signTransaction").signedTransaction;
118
60
  } },
119
- ...signIn === void 0 ? {} : { async signIn(input) {
120
- const [output] = await signIn.signIn(input);
121
- if (output === void 0) throw new Error("signIn returned no outputs");
61
+ ...signIn !== void 0 && { async signIn(input) {
62
+ const output = firstOutput(await signIn.signIn(input), "signIn");
122
63
  return {
123
- account: buildAccount(output.account.address, core.toChain()),
64
+ account: buildAccount(output.account.address, core.currentChain()),
124
65
  signature: output.signature,
125
66
  signedMessage: output.signedMessage
126
67
  };
@@ -132,31 +73,7 @@ const buildSvmAdapter = (wallet, registerDisconnector) => {
132
73
  * discovery silently does nothing. The returned unsubscribe is safe to call
133
74
  * before the dynamic import has resolved.
134
75
  */
135
- const discoverSvmAdapters = (onAdapter) => discoverWalletStandard(onAdapter, (wallet, registerDisconnector) => buildSvmAdapter(wallet, registerDisconnector));
136
-
137
- //#endregion
138
- //#region src/chains.ts
139
- const SVM_CHAINS = {
140
- devnet: {
141
- id: "solana:devnet",
142
- name: "Solana Devnet",
143
- namespace: "solana",
144
- reference: "devnet"
145
- },
146
- mainnet: {
147
- id: "solana:mainnet",
148
- name: "Solana Mainnet",
149
- namespace: "solana",
150
- reference: "mainnet"
151
- },
152
- testnet: {
153
- id: "solana:testnet",
154
- name: "Solana Testnet",
155
- namespace: "solana",
156
- reference: "testnet"
157
- }
158
- };
159
- const SVM_CHAINS_LIST = Object.values(SVM_CHAINS);
76
+ const discoverSvmAdapters = (onAdapter, loadModule) => discoverWalletStandard(onAdapter, buildSvmAdapter, loadModule);
160
77
 
161
78
  //#endregion
162
79
  //#region src/discoverer.ts
@@ -167,5 +84,5 @@ const SVM_CHAINS_LIST = Object.values(SVM_CHAINS);
167
84
  const svmDiscoverer = { subscribe: discoverSvmAdapters };
168
85
 
169
86
  //#endregion
170
- export { SVM_CHAINS, SVM_CHAINS_LIST, buildSvmAdapter, discoverSvmAdapters, resolveWalletStandardCapabilities, svmDiscoverer };
87
+ export { buildSvmAdapter, discoverSvmAdapters, svmDiscoverer };
171
88
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/capabilities.ts","../src/wallet-standard-adapter.ts","../src/chains.ts","../src/discoverer.ts"],"sourcesContent":["import type { WalletCapabilities } from \"@usebutr/core\";\nimport { buildWalletCapabilities } from \"@usebutr/wallet-standard-shared\";\n\ntype WalletStandardCapabilityInput = {\n chainCount: number;\n features: {\n events: boolean;\n signAndSendTransaction: boolean;\n signIn: boolean;\n signMessage: boolean;\n signTransaction: boolean;\n };\n};\n\n/**\n * Solana Wallet Standard feature → butr capability mapping. The flags that\n * hold for every namespace are documented on `buildWalletCapabilities`.\n */\nconst resolveWalletStandardCapabilities = (\n input: WalletStandardCapabilityInput,\n): WalletCapabilities =>\n buildWalletCapabilities({\n chainCount: input.chainCount,\n events: input.features.events,\n sendTransaction: input.features.signAndSendTransaction,\n signIn: input.features.signIn,\n signMessage: input.features.signMessage,\n signTransaction: input.features.signTransaction,\n });\n\nexport type { WalletStandardCapabilityInput };\nexport { resolveWalletStandardCapabilities };\n","import type { WalletAdapter } from \"@usebutr/core\";\nimport { buildAccount, bytesToBase58 } from \"@usebutr/core\";\nimport {\n createWalletStandardCore,\n discoverWalletStandard,\n getFeature,\n slugify,\n} from \"@usebutr/wallet-standard-shared\";\nimport type { WalletStandardWallet } from \"@usebutr/wallet-standard-shared\";\n\nimport { resolveWalletStandardCapabilities } from \"./capabilities\";\nimport type {\n SolanaSignAndSendTransactionFeature,\n SolanaSignInFeature,\n SolanaSignMessageFeature,\n SolanaSignTransactionFeature,\n} from \"./wallet-standard-types\";\n\nconst SOLANA_PREFIX = \"solana:\";\nconst SOLANA_DECIMALS = 9;\nconst SOLANA_MAINNETS: ReadonlyArray<string> = [\"solana:mainnet\", \"solana:mainnet-beta\"];\n\n/**\n * Wallet Standard has no switch-cluster RPC: `switchChain` re-points butr's\n * view only, and `signAndSendTransaction` takes the chain per call.\n * `requestAccounts` re-runs `standard:connect`; some wallets answer silently.\n */\nconst buildSvmAdapter = (\n wallet: WalletStandardWallet,\n /** Optional. Called with a function that pushes a synthetic\n * `disconnected` event to all current subscribers. The discovery\n * layer invokes it on Wallet Standard `unregister` so a connected\n * pool entry tears down when its extension is removed. */\n registerDisconnector?: (emit: () => void) => void,\n): WalletAdapter | null => {\n const core = createWalletStandardCore({\n chainPrefix: SOLANA_PREFIX,\n id: slugify(\"svm\", wallet.name),\n label: \"SVM\",\n namespace: \"solana\",\n platform: \"Solana\",\n preferredChainIds: SOLANA_MAINNETS,\n registerDisconnector,\n trackChainChanges: true,\n wallet,\n });\n if (core === null) {\n return null;\n }\n\n const signMessage = getFeature<SolanaSignMessageFeature>(wallet, \"solana:signMessage\");\n const signAndSendTx = getFeature<SolanaSignAndSendTransactionFeature>(\n wallet,\n \"solana:signAndSendTransaction\",\n );\n const signTx = getFeature<SolanaSignTransactionFeature>(wallet, \"solana:signTransaction\");\n const signIn = getFeature<SolanaSignInFeature>(wallet, \"solana:signIn\");\n\n /**\n * Resolve a caller-supplied target into a chain the wallet advertises.\n * Accepts both the full CAIP-2 id (`solana:devnet`) and a bare reference\n * (`devnet`), since consumers reasonably reach for either.\n */\n const resolveTargetChain = (targetChainId: string): string => {\n const candidate = targetChainId.startsWith(SOLANA_PREFIX)\n ? targetChainId\n : `${SOLANA_PREFIX}${targetChainId}`;\n if (!wallet.chains.includes(candidate)) {\n throw new Error(\n `Wallet ${wallet.name} does not advertise chain \"${candidate}\". Available: ${wallet.chains.join(\", \")}`,\n );\n }\n return candidate;\n };\n\n const signAndSend = async (\n tx: unknown,\n account?: { walletAddress: string },\n chain?: string,\n ): Promise<string> => {\n if (signAndSendTx === undefined) {\n throw new Error(`Wallet ${wallet.name} does not advertise solana:signAndSendTransaction`);\n }\n const wsAccount = core.resolveAccount(account);\n if (!(tx instanceof Uint8Array)) {\n throw new TypeError(\"SVM sendTx expects a serialized transaction (Uint8Array)\");\n }\n const [output] = await signAndSendTx.signAndSendTransaction({\n account: wsAccount,\n chain: chain ?? core.currentChainId(),\n transaction: tx,\n });\n if (output === undefined) {\n throw new Error(\"signAndSendTransaction returned no outputs\");\n }\n // Base58 is the encoding every Solana explorer, `getSignatureStatuses`,\n // and the WalletConnect SVM namespace use for a transaction signature.\n return bytesToBase58(output.signature);\n };\n\n return {\n ...core,\n capabilities: resolveWalletStandardCapabilities({\n chainCount: core.chainCount,\n features: {\n events: core.hasEvents,\n signAndSendTransaction: Boolean(signAndSendTx),\n signIn: Boolean(signIn),\n signMessage: Boolean(signMessage),\n signTransaction: Boolean(signTx),\n },\n }),\n chainPlatform: \"svm\",\n\n getBalance: () =>\n Promise.resolve({\n decimals: SOLANA_DECIMALS,\n formatted: \"0\",\n symbol: \"SOL\",\n value: 0n,\n }),\n\n getTransactionReceipt: () => Promise.resolve({ status: \"Pending\" as const }),\n\n async requestAccounts() {\n await core.connect();\n },\n\n sendTx: (tx, account) => signAndSend(tx, account),\n\n // Async so an unadvertised chain surfaces as a rejection rather than a\n // synchronous throw; the declared return type is a promise either way.\n async sendTxToChain(tx, targetChainId, account, cb) {\n const target = resolveTargetChain(targetChainId);\n if (target !== core.currentChainId()) {\n cb?.();\n }\n const signature = await signAndSend(tx, account, target);\n return signature;\n },\n\n async signMessage(msg, account) {\n if (signMessage === undefined) {\n throw new Error(`Wallet ${wallet.name} does not advertise solana:signMessage`);\n }\n const [output] = await signMessage.signMessage({\n account: core.resolveAccount(account),\n message: msg,\n });\n if (output === undefined) {\n throw new Error(\"signMessage returned no outputs\");\n }\n return { signature: output.signature, signedMessage: output.signedMessage };\n },\n\n ...(signTx === undefined\n ? {}\n : {\n async signTransaction(tx, account) {\n const wsAccount = core.resolveAccount(account);\n if (!(tx instanceof Uint8Array)) {\n throw new TypeError(\n \"SVM signTransaction expects a serialized transaction (Uint8Array)\",\n );\n }\n const [output] = await signTx.signTransaction({\n account: wsAccount,\n chain: core.currentChainId(),\n transaction: tx,\n });\n if (output === undefined) {\n throw new Error(\"signTransaction returned no outputs\");\n }\n return output.signedTransaction;\n },\n }),\n\n ...(signIn === undefined\n ? {}\n : {\n async signIn(input) {\n const [output] = await signIn.signIn(input);\n if (output === undefined) {\n throw new Error(\"signIn returned no outputs\");\n }\n return {\n account: buildAccount(output.account.address, core.toChain()),\n signature: output.signature,\n signedMessage: output.signedMessage,\n };\n },\n }),\n };\n};\n\n/**\n * Requires the optional `@wallet-standard/app` peer dep; without it SVM\n * discovery silently does nothing. The returned unsubscribe is safe to call\n * before the dynamic import has resolved.\n */\nconst discoverSvmAdapters = (onAdapter: (adapter: WalletAdapter) => void): (() => void) =>\n discoverWalletStandard(onAdapter, (wallet, registerDisconnector) =>\n buildSvmAdapter(wallet, registerDisconnector),\n );\n\nexport { buildSvmAdapter, discoverSvmAdapters };\n","import type { ChainBase } from \"@usebutr/core\";\n\nconst SVM_CHAINS = {\n devnet: { id: \"solana:devnet\", name: \"Solana Devnet\", namespace: \"solana\", reference: \"devnet\" },\n mainnet: {\n id: \"solana:mainnet\",\n name: \"Solana Mainnet\",\n namespace: \"solana\",\n reference: \"mainnet\",\n },\n testnet: {\n id: \"solana:testnet\",\n name: \"Solana Testnet\",\n namespace: \"solana\",\n reference: \"testnet\",\n },\n} as const satisfies Record<string, ChainBase>;\n\nconst SVM_CHAINS_LIST: ReadonlyArray<ChainBase> = Object.values(SVM_CHAINS);\n\nexport { SVM_CHAINS, SVM_CHAINS_LIST };\n","import type { PlatformDiscoverer } from \"@usebutr/core\";\n\nimport { discoverSvmAdapters } from \"./wallet-standard-adapter\";\n\n/**\n * No legacy fallback: Solana wallets standardised on Wallet Standard before\n * butr existed, so there's no `window.solana` injected path worth shimming.\n */\nconst svmDiscoverer: PlatformDiscoverer = {\n subscribe: discoverSvmAdapters,\n};\n\nexport { svmDiscoverer };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/wallet-standard-adapter.ts","../src/discoverer.ts"],"sourcesContent":["import type { SvmAdapter, WalletAdapter } from \"@usebutr/core\";\nimport { buildAccount, bytesToBase58, SVM_CHAINS_LIST } from \"@usebutr/core\";\nimport {\n createWalletStandardCore,\n discoverWalletStandard,\n getFeature,\n slugify,\n} from \"@usebutr/wallet-standard-shared\";\nimport type {\n WalletStandardModuleLoader,\n WalletStandardWallet,\n} from \"@usebutr/wallet-standard-shared\";\n\nimport type {\n SolanaSignAndSendTransactionFeature,\n SolanaSignInFeature,\n SolanaSignMessageFeature,\n SolanaSignTransactionFeature,\n} from \"./wallet-standard-types\";\n\n/** Solana features take and return arrays so one call can batch; butr sends\n * one input, so an empty result is a wallet bug, not an empty success. */\nconst firstOutput = <Output>(outputs: ReadonlyArray<Output>, method: string): Output => {\n const [output] = outputs;\n if (output === undefined) {\n throw new Error(`${method} returned no outputs`);\n }\n return output;\n};\n\n/**\n * Wallet Standard carries the chain per call, so `sendTx` and\n * `signTransaction` route `options.chain` without moving the wallet.\n * Balances and receipts need an RPC client, which butr does not ship.\n */\nconst buildSvmAdapter = (\n wallet: WalletStandardWallet,\n /** Discovery passes this so a Wallet Standard `unregister` tears down the\n * connected pool entry. */\n registerDisconnector?: (emit: () => void) => void,\n): SvmAdapter | null => {\n const core = createWalletStandardCore({\n chains: SVM_CHAINS_LIST,\n id: slugify(\"svm\", wallet.name),\n label: \"Solana\",\n namespace: \"solana\",\n preferredChainIds: [\"solana:mainnet\", \"solana:mainnet-beta\"],\n registerDisconnector,\n trackChainChanges: true,\n wallet,\n });\n if (core === null) {\n return null;\n }\n\n const signAndSend = getFeature<SolanaSignAndSendTransactionFeature>(\n wallet,\n \"solana:signAndSendTransaction\",\n \"signAndSendTransaction\",\n );\n const signMessage = getFeature<SolanaSignMessageFeature>(\n wallet,\n \"solana:signMessage\",\n \"signMessage\",\n );\n const signTransaction = getFeature<SolanaSignTransactionFeature>(\n wallet,\n \"solana:signTransaction\",\n \"signTransaction\",\n );\n const signIn = getFeature<SolanaSignInFeature>(wallet, \"solana:signIn\", \"signIn\");\n\n return {\n ...core.base,\n chainPlatform: \"svm\",\n ...(signAndSend !== undefined && {\n async sendTx(tx, options) {\n const output = firstOutput(\n await signAndSend.signAndSendTransaction({\n account: core.resolveAccount(options?.account),\n chain: core.resolveChainId(options?.chain),\n transaction: tx,\n }),\n \"signAndSendTransaction\",\n );\n return bytesToBase58(output.signature);\n },\n }),\n ...(signMessage !== undefined && {\n async signMessage(message, options) {\n const output = firstOutput(\n await signMessage.signMessage({\n account: core.resolveAccount(options?.account),\n message,\n }),\n \"signMessage\",\n );\n return { signature: output.signature, signedMessage: output.signedMessage };\n },\n }),\n ...(signTransaction !== undefined && {\n async signTransaction(tx, options) {\n const output = firstOutput(\n await signTransaction.signTransaction({\n account: core.resolveAccount(options?.account),\n chain: core.resolveChainId(options?.chain),\n transaction: tx,\n }),\n \"signTransaction\",\n );\n return output.signedTransaction;\n },\n }),\n ...(signIn !== undefined && {\n async signIn(input) {\n const output = firstOutput(await signIn.signIn(input), \"signIn\");\n return {\n account: buildAccount(output.account.address, core.currentChain()),\n signature: output.signature,\n signedMessage: output.signedMessage,\n };\n },\n }),\n };\n};\n\n/**\n * Requires the optional `@wallet-standard/app` peer dep; without it SVM\n * discovery silently does nothing. The returned unsubscribe is safe to call\n * before the dynamic import has resolved.\n */\nconst discoverSvmAdapters = (\n onAdapter: (adapter: WalletAdapter) => void,\n loadModule?: WalletStandardModuleLoader,\n): (() => void) => discoverWalletStandard(onAdapter, buildSvmAdapter, loadModule);\n\nexport { buildSvmAdapter, discoverSvmAdapters };\n","import type { PlatformDiscoverer } from \"@usebutr/core\";\n\nimport { discoverSvmAdapters } from \"./wallet-standard-adapter\";\n\n/**\n * No legacy fallback: Solana wallets standardised on Wallet Standard before\n * butr existed, so there's no `window.solana` injected path worth shimming.\n */\nconst svmDiscoverer: PlatformDiscoverer = {\n subscribe: discoverSvmAdapters,\n};\n\nexport { svmDiscoverer };\n"],"mappings":";;;;;;AAsBA,MAAM,eAAuB,SAAgC,WAA2B;CACtF,MAAM,CAAC,UAAU;CACjB,IAAI,WAAW,QACb,MAAM,IAAI,MAAM,GAAG,OAAO,qBAAqB;CAEjD,OAAO;AACT;;;;;;AAOA,MAAM,mBACJ,QAGA,yBACsB;CACtB,MAAM,OAAO,yBAAyB;EACpC,QAAQ;EACR,IAAI,QAAQ,OAAO,OAAO,IAAI;EAC9B,OAAO;EACP,WAAW;EACX,mBAAmB,CAAC,kBAAkB,qBAAqB;EAC3D;EACA,mBAAmB;EACnB;CACF,CAAC;CACD,IAAI,SAAS,MACX,OAAO;CAGT,MAAM,cAAc,WAClB,QACA,iCACA,wBACF;CACA,MAAM,cAAc,WAClB,QACA,sBACA,aACF;CACA,MAAM,kBAAkB,WACtB,QACA,0BACA,iBACF;CACA,MAAM,SAAS,WAAgC,QAAQ,iBAAiB,QAAQ;CAEhF,OAAO;EACL,GAAG,KAAK;EACR,eAAe;EACf,GAAI,gBAAgB,UAAa,EAC/B,MAAM,OAAO,IAAI,SAAS;GACxB,MAAM,SAAS,YACb,MAAM,YAAY,uBAAuB;IACvC,SAAS,KAAK,eAAe,SAAS,OAAO;IAC7C,OAAO,KAAK,eAAe,SAAS,KAAK;IACzC,aAAa;GACf,CAAC,GACD,wBACF;GACA,OAAO,cAAc,OAAO,SAAS;EACvC,EACF;EACA,GAAI,gBAAgB,UAAa,EAC/B,MAAM,YAAY,SAAS,SAAS;GAClC,MAAM,SAAS,YACb,MAAM,YAAY,YAAY;IAC5B,SAAS,KAAK,eAAe,SAAS,OAAO;IAC7C;GACF,CAAC,GACD,aACF;GACA,OAAO;IAAE,WAAW,OAAO;IAAW,eAAe,OAAO;GAAc;EAC5E,EACF;EACA,GAAI,oBAAoB,UAAa,EACnC,MAAM,gBAAgB,IAAI,SAAS;GASjC,OARe,YACb,MAAM,gBAAgB,gBAAgB;IACpC,SAAS,KAAK,eAAe,SAAS,OAAO;IAC7C,OAAO,KAAK,eAAe,SAAS,KAAK;IACzC,aAAa;GACf,CAAC,GACD,iBAEU,CAAC,CAAC;EAChB,EACF;EACA,GAAI,WAAW,UAAa,EAC1B,MAAM,OAAO,OAAO;GAClB,MAAM,SAAS,YAAY,MAAM,OAAO,OAAO,KAAK,GAAG,QAAQ;GAC/D,OAAO;IACL,SAAS,aAAa,OAAO,QAAQ,SAAS,KAAK,aAAa,CAAC;IACjE,WAAW,OAAO;IAClB,eAAe,OAAO;GACxB;EACF,EACF;CACF;AACF;;;;;;AAOA,MAAM,uBACJ,WACA,eACiB,uBAAuB,WAAW,iBAAiB,UAAU;;;;;;;;AC9HhF,MAAM,gBAAoC,EACxC,WAAW,oBACb"}
@@ -0,0 +1,16 @@
1
+ //#region src/transaction.d.ts
2
+ type SolanaSigningPayload = {
3
+ /** Serialized message passed to a device that signs messages directly. */
4
+ message: Uint8Array;
5
+ /** Returns a copy with the signer's slot filled, preserving other signatures. */
6
+ withSignature: (signature: Uint8Array) => Uint8Array;
7
+ };
8
+ /**
9
+ * Resolves the signer's slot in a serialized legacy or v0 transaction.
10
+ * Validates the signature/header/key layout; instructions stay opaque.
11
+ * Both device signers and RPCs returning a bare signature use this boundary.
12
+ */
13
+ declare const prepareSolanaTransaction: (tx: Uint8Array, signer: string) => SolanaSigningPayload;
14
+ //#endregion
15
+ export { type SolanaSigningPayload, prepareSolanaTransaction };
16
+ //# sourceMappingURL=transaction.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction.d.ts","names":[],"sources":["../src/transaction.ts"],"mappings":";KA2BK;;EAEH,SAAS;;EAET,gBAAgB,WAAW,eAAe;;;;;;;cAQtC,2BAAwB,IAAQ,YAAU,mBAAmB"}
@@ -0,0 +1,60 @@
1
+ import { bytesToBase58 } from "@usebutr/core";
2
+
3
+ //#region src/transaction.ts
4
+ const SIGNATURE_LENGTH = 64;
5
+ const PUBLIC_KEY_LENGTH = 32;
6
+ const BLOCKHASH_LENGTH = 32;
7
+ const V0_PREFIX = 128;
8
+ /** Solana shortvec encodes a u16 in at most three bytes, seven bits per
9
+ * byte. Reject truncated, overflowing and non-canonical encodings. */
10
+ const readCompactU16 = (bytes, offset) => {
11
+ let value = 0;
12
+ for (let index = 0; index < 3; index += 1) {
13
+ const byte = bytes.at(offset + index);
14
+ if (byte === void 0 || index === 2 && byte > 3) break;
15
+ value += byte % 128 * 128 ** index;
16
+ if (byte < 128) {
17
+ if (index > 0 && byte === 0) break;
18
+ return {
19
+ end: offset + index + 1,
20
+ value
21
+ };
22
+ }
23
+ }
24
+ throw new TypeError("Malformed compact-u16 in the Solana transaction");
25
+ };
26
+ /**
27
+ * Resolves the signer's slot in a serialized legacy or v0 transaction.
28
+ * Validates the signature/header/key layout; instructions stay opaque.
29
+ * Both device signers and RPCs returning a bare signature use this boundary.
30
+ */
31
+ const prepareSolanaTransaction = (tx, signer) => {
32
+ const signatures = readCompactU16(tx, 0);
33
+ const messageStart = signatures.end + signatures.value * SIGNATURE_LENGTH;
34
+ const versionByte = tx.at(messageStart);
35
+ if (versionByte === void 0) throw new TypeError("Truncated Solana transaction message");
36
+ if (versionByte > V0_PREFIX) throw new TypeError(`Unsupported Solana message version ${versionByte - V0_PREFIX}`);
37
+ const headerStart = messageStart + (versionByte === V0_PREFIX ? 1 : 0);
38
+ const signerCount = tx[headerStart];
39
+ const keys = readCompactU16(tx, headerStart + 3);
40
+ const minimumLength = keys.end + keys.value * PUBLIC_KEY_LENGTH + BLOCKHASH_LENGTH + 1;
41
+ if (signatures.value === 0 || signerCount !== signatures.value || keys.value < signatures.value || minimumLength > tx.length) throw new TypeError("Malformed Solana transaction signature/header/key layout");
42
+ const slot = Array.from({ length: signatures.value }, (_, index) => {
43
+ const start = keys.end + index * PUBLIC_KEY_LENGTH;
44
+ return bytesToBase58(tx.subarray(start, start + PUBLIC_KEY_LENGTH));
45
+ }).indexOf(signer);
46
+ if (slot === -1) throw new Error(`${signer} is not a required signer of this transaction`);
47
+ return {
48
+ message: tx.subarray(messageStart),
49
+ withSignature: (signature) => {
50
+ if (signature.length !== SIGNATURE_LENGTH) throw new Error(`Expected a 64-byte Solana signature, received ${signature.length} bytes`);
51
+ const signed = Uint8Array.from(tx);
52
+ signed.set(signature, signatures.end + slot * SIGNATURE_LENGTH);
53
+ return signed;
54
+ }
55
+ };
56
+ };
57
+
58
+ //#endregion
59
+ export { prepareSolanaTransaction };
60
+ //# sourceMappingURL=transaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transaction.js","names":[],"sources":["../src/transaction.ts"],"sourcesContent":["import { bytesToBase58 } from \"@usebutr/core\";\n\nconst SIGNATURE_LENGTH = 64;\nconst PUBLIC_KEY_LENGTH = 32;\nconst BLOCKHASH_LENGTH = 32;\nconst V0_PREFIX = 0x80;\n\n/** Solana shortvec encodes a u16 in at most three bytes, seven bits per\n * byte. Reject truncated, overflowing and non-canonical encodings. */\nconst readCompactU16 = (bytes: Uint8Array, offset: number) => {\n let value = 0;\n for (let index = 0; index < 3; index += 1) {\n const byte = bytes.at(offset + index);\n if (byte === undefined || (index === 2 && byte > 3)) {\n break;\n }\n value += (byte % 128) * 128 ** index;\n if (byte < 128) {\n if (index > 0 && byte === 0) {\n break;\n }\n return { end: offset + index + 1, value };\n }\n }\n throw new TypeError(\"Malformed compact-u16 in the Solana transaction\");\n};\n\ntype SolanaSigningPayload = {\n /** Serialized message passed to a device that signs messages directly. */\n message: Uint8Array;\n /** Returns a copy with the signer's slot filled, preserving other signatures. */\n withSignature: (signature: Uint8Array) => Uint8Array;\n};\n\n/**\n * Resolves the signer's slot in a serialized legacy or v0 transaction.\n * Validates the signature/header/key layout; instructions stay opaque.\n * Both device signers and RPCs returning a bare signature use this boundary.\n */\nconst prepareSolanaTransaction = (tx: Uint8Array, signer: string): SolanaSigningPayload => {\n const signatures = readCompactU16(tx, 0);\n const messageStart = signatures.end + signatures.value * SIGNATURE_LENGTH;\n const versionByte = tx.at(messageStart);\n if (versionByte === undefined) {\n throw new TypeError(\"Truncated Solana transaction message\");\n }\n if (versionByte > V0_PREFIX) {\n throw new TypeError(`Unsupported Solana message version ${versionByte - V0_PREFIX}`);\n }\n const headerStart = messageStart + (versionByte === V0_PREFIX ? 1 : 0);\n const signerCount = tx[headerStart];\n const keys = readCompactU16(tx, headerStart + 3);\n const minimumLength = keys.end + keys.value * PUBLIC_KEY_LENGTH + BLOCKHASH_LENGTH + 1;\n if (\n signatures.value === 0 ||\n signerCount !== signatures.value ||\n keys.value < signatures.value ||\n minimumLength > tx.length\n ) {\n throw new TypeError(\"Malformed Solana transaction signature/header/key layout\");\n }\n const signers = Array.from({ length: signatures.value }, (_, index) => {\n const start = keys.end + index * PUBLIC_KEY_LENGTH;\n return bytesToBase58(tx.subarray(start, start + PUBLIC_KEY_LENGTH));\n });\n const slot = signers.indexOf(signer);\n if (slot === -1) {\n throw new Error(`${signer} is not a required signer of this transaction`);\n }\n\n return {\n message: tx.subarray(messageStart),\n withSignature: (signature) => {\n if (signature.length !== SIGNATURE_LENGTH) {\n throw new Error(`Expected a 64-byte Solana signature, received ${signature.length} bytes`);\n }\n const signed = Uint8Array.from(tx);\n signed.set(signature, signatures.end + slot * SIGNATURE_LENGTH);\n return signed;\n },\n };\n};\n\nexport type { SolanaSigningPayload };\nexport { prepareSolanaTransaction };\n"],"mappings":";;;AAEA,MAAM,mBAAmB;AACzB,MAAM,oBAAoB;AAC1B,MAAM,mBAAmB;AACzB,MAAM,YAAY;;;AAIlB,MAAM,kBAAkB,OAAmB,WAAmB;CAC5D,IAAI,QAAQ;CACZ,KAAK,IAAI,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG;EACzC,MAAM,OAAO,MAAM,GAAG,SAAS,KAAK;EACpC,IAAI,SAAS,UAAc,UAAU,KAAK,OAAO,GAC/C;EAEF,SAAU,OAAO,MAAO,OAAO;EAC/B,IAAI,OAAO,KAAK;GACd,IAAI,QAAQ,KAAK,SAAS,GACxB;GAEF,OAAO;IAAE,KAAK,SAAS,QAAQ;IAAG;GAAM;EAC1C;CACF;CACA,MAAM,IAAI,UAAU,iDAAiD;AACvE;;;;;;AAcA,MAAM,4BAA4B,IAAgB,WAAyC;CACzF,MAAM,aAAa,eAAe,IAAI,CAAC;CACvC,MAAM,eAAe,WAAW,MAAM,WAAW,QAAQ;CACzD,MAAM,cAAc,GAAG,GAAG,YAAY;CACtC,IAAI,gBAAgB,QAClB,MAAM,IAAI,UAAU,sCAAsC;CAE5D,IAAI,cAAc,WAChB,MAAM,IAAI,UAAU,sCAAsC,cAAc,WAAW;CAErF,MAAM,cAAc,gBAAgB,gBAAgB,YAAY,IAAI;CACpE,MAAM,cAAc,GAAG;CACvB,MAAM,OAAO,eAAe,IAAI,cAAc,CAAC;CAC/C,MAAM,gBAAgB,KAAK,MAAM,KAAK,QAAQ,oBAAoB,mBAAmB;CACrF,IACE,WAAW,UAAU,KACrB,gBAAgB,WAAW,SAC3B,KAAK,QAAQ,WAAW,SACxB,gBAAgB,GAAG,QAEnB,MAAM,IAAI,UAAU,0DAA0D;CAMhF,MAAM,OAJU,MAAM,KAAK,EAAE,QAAQ,WAAW,MAAM,IAAI,GAAG,UAAU;EACrE,MAAM,QAAQ,KAAK,MAAM,QAAQ;EACjC,OAAO,cAAc,GAAG,SAAS,OAAO,QAAQ,iBAAiB,CAAC;CACpE,CACmB,CAAC,CAAC,QAAQ,MAAM;CACnC,IAAI,SAAS,IACX,MAAM,IAAI,MAAM,GAAG,OAAO,8CAA8C;CAG1E,OAAO;EACL,SAAS,GAAG,SAAS,YAAY;EACjC,gBAAgB,cAAc;GAC5B,IAAI,UAAU,WAAW,kBACvB,MAAM,IAAI,MAAM,iDAAiD,UAAU,OAAO,OAAO;GAE3F,MAAM,SAAS,WAAW,KAAK,EAAE;GACjC,OAAO,IAAI,WAAW,WAAW,MAAM,OAAO,gBAAgB;GAC9D,OAAO;EACT;CACF;AACF"}
package/package.json CHANGED
@@ -1,7 +1,22 @@
1
1
  {
2
2
  "name": "@usebutr/svm",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "Wallet Standard adapter for butr (Solana / SVM).",
5
+ "keywords": [
6
+ "butr",
7
+ "dapp",
8
+ "phantom",
9
+ "react",
10
+ "solana",
11
+ "typescript",
12
+ "wallet",
13
+ "wallet-standard",
14
+ "web3"
15
+ ],
16
+ "homepage": "https://docs.usebutr.com/api/svm",
17
+ "bugs": {
18
+ "url": "https://github.com/pedroapfilho/usebutr/issues"
19
+ },
5
20
  "license": "MIT",
6
21
  "author": "Pedro Filho <pedro@filho.me>",
7
22
  "repository": {
@@ -10,7 +25,8 @@
10
25
  "directory": "packages/svm"
11
26
  },
12
27
  "files": [
13
- "dist"
28
+ "dist",
29
+ "CHANGELOG.md"
14
30
  ],
15
31
  "type": "module",
16
32
  "sideEffects": false,
@@ -19,19 +35,23 @@
19
35
  ".": {
20
36
  "types": "./dist/index.d.ts",
21
37
  "default": "./dist/index.js"
38
+ },
39
+ "./transaction": {
40
+ "types": "./dist/transaction.d.ts",
41
+ "default": "./dist/transaction.js"
22
42
  }
23
43
  },
24
44
  "publishConfig": {
25
45
  "access": "public"
26
46
  },
27
47
  "dependencies": {
28
- "@usebutr/core": "2.0.0",
29
- "@usebutr/wallet-standard-shared": "0.5.0"
48
+ "@usebutr/core": "3.0.0",
49
+ "@usebutr/wallet-standard-shared": "2.0.0"
30
50
  },
31
51
  "devDependencies": {
32
52
  "@wallet-standard/app": "^1.1.1",
33
- "typescript": "^6.0.3",
34
- "vitest": "^4.1.10",
53
+ "typescript": "^7.0.2",
54
+ "vitest": "^5.0.0",
35
55
  "@repo/config-vitest": "0.0.0",
36
56
  "@repo/typescript-config": "0.0.0"
37
57
  },