@usebutr/svm 2.0.2 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # @usebutr/svm
2
2
 
3
+ ## 3.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [[`488fe13`](https://github.com/pedroapfilho/usebutr/commit/488fe13fe9cddb99ff6c5d60f798140e6ac4076f)]:
8
+ - @usebutr/core@3.0.1
9
+ - @usebutr/wallet-standard-shared@2.0.1
10
+
11
+ ## 3.0.0
12
+
13
+ ### Major Changes
14
+
15
+ - [#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.
16
+
17
+ - `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.
18
+ - 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.
19
+ - `getAccounts()` resolves the active account first. `getAccount` and `switchAccount` are removed.
20
+ - `getSigner()` resolves a tagged `WalletSigner` (`eip1193`, `wallet-standard`, `walletconnect`, `ledger-*`, …), so it can be narrowed with `switch (signer.kind)` instead of cast.
21
+ - `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()`.
22
+ - `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`.
23
+ - `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`.
24
+ - `WalletPersistence` is `load()` / `save(state)`, and `createWalletStorage` replaces `new WalletStorage`. Storage keys are unchanged.
25
+ - 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`.
26
+ - `@usebutr/react` requires React 19, which its `use()` call already needed.
27
+ - 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.
28
+ - Ledger's Solana `signTransaction` returns the full signed transaction, Sui signs the intent message, and Bitcoin `signMessage` returns a BIP-137 signature.
29
+ - `@usebutr/testing`'s fake adapter resolves a `signer` you pass instead of registering a test-only signer kind.
30
+ - `setAccount` selects an exposed account without changing any account's chain. Unknown accounts are ignored; chain changes come from the adapter.
31
+ - WalletConnect EVM events use the namespace in `session_event`, including empty account lists that end the connection. Local chain switches still update the manager.
32
+ - Failed persistence saves wait for every key write to finish before reporting failure, so delayed writes cannot overwrite a later disconnect.
33
+ - `@usebutr/svm/transaction` provides the shared legacy/v0 signing codec used by Ledger and WalletConnect, with consistent layout and signature validation.
34
+
35
+ See the migration guide at https://docs.usebutr.com/migration.
36
+
37
+ ### Patch Changes
38
+
39
+ - Updated dependencies [[`1263f86`](https://github.com/pedroapfilho/usebutr/commit/1263f8666afd43b4f7dbf8df1763709c3edf09d9), [`5d1f234`](https://github.com/pedroapfilho/usebutr/commit/5d1f23403263f7a1f8a48be061727972fe749b5f)]:
40
+ - @usebutr/core@3.0.0
41
+ - @usebutr/wallet-standard-shared@2.0.0
42
+
3
43
  ## 2.0.2
4
44
 
5
45
  ### Patch Changes
package/README.md CHANGED
@@ -11,22 +11,49 @@ connection-state library. Your application owns the picker UI and chain client.
11
11
  npm install @usebutr/react @usebutr/core @usebutr/svm zustand @wallet-standard/app
12
12
  ```
13
13
 
14
- React 18+ 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.
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
15
 
16
16
  ## Usage
17
17
 
18
18
  ```tsx
19
19
  import type { ReactNode } from "react";
20
- import { createWalletSource } from "@usebutr/core";
20
+ import type { WalletManagerConfig } from "@usebutr/core";
21
21
  import { discoverSvmAdapters } from "@usebutr/svm";
22
22
  import { WalletManagerProvider } from "@usebutr/react";
23
23
 
24
- const discovery = createWalletSource(discoverSvmAdapters);
24
+ // Every `discover*Adapters` export is a wallet source as-is.
25
+ const config: WalletManagerConfig = { sources: [discoverSvmAdapters] };
26
+
25
27
  export const WalletProvider = ({ children }: { children: ReactNode }) => (
26
- <WalletManagerProvider discovery={discovery}>{children}</WalletManagerProvider>
28
+ <WalletManagerProvider config={config}>{children}</WalletManagerProvider>
27
29
  );
28
30
  ```
29
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
+
30
57
  ## Documentation
31
58
 
32
59
  - [Package reference](https://docs.usebutr.com/api/svm)
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { ChainBase, PlatformDiscoverer, SignInInput, WalletAdapter, WalletCapabilities } from "@usebutr/core";
2
- import { WalletStandardFeature, WalletStandardModuleLoader, 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;
@@ -49,21 +49,15 @@ type SolanaSignInFeature = {
49
49
  };
50
50
  //#endregion
51
51
  //#region src/wallet-standard-adapter.d.ts
52
- declare const isSolanaSignMessageFeature: (feature: WalletStandardFeature) => feature is WalletStandardFeature & SolanaSignMessageFeature;
53
- declare const isSolanaSignAndSendTransactionFeature: (feature: WalletStandardFeature) => feature is WalletStandardFeature & SolanaSignAndSendTransactionFeature;
54
- declare const isSolanaSignTransactionFeature: (feature: WalletStandardFeature) => feature is WalletStandardFeature & SolanaSignTransactionFeature;
55
- declare const isSolanaSignInFeature: (feature: WalletStandardFeature) => feature is WalletStandardFeature & SolanaSignInFeature;
56
52
  /**
57
- * Wallet Standard has no switch-cluster RPC: `switchChain` re-points butr's
58
- * view only, and `signAndSendTransaction` takes the chain per call.
59
- * `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.
60
56
  */
61
57
  declare const buildSvmAdapter: (wallet: WalletStandardWallet,
62
- /** Optional. Called with a function that pushes a synthetic
63
- * `disconnected` event to all current subscribers. The discovery
64
- * layer invokes it on Wallet Standard `unregister` so a connected
65
- * pool entry tears down when its extension is removed. */
66
- 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;
67
61
  /**
68
62
  * Requires the optional `@wallet-standard/app` peer dep; without it SVM
69
63
  * discovery silently does nothing. The returned unsubscribe is safe to call
@@ -71,46 +65,6 @@ registerDisconnector?: (emit: () => void) => void) => WalletAdapter | null;
71
65
  */
72
66
  declare const discoverSvmAdapters: (onAdapter: (adapter: WalletAdapter) => void, loadModule?: WalletStandardModuleLoader) => (() => void);
73
67
  //#endregion
74
- //#region src/chains.d.ts
75
- declare const SVM_CHAINS: {
76
- readonly devnet: {
77
- readonly id: "solana:devnet";
78
- readonly name: "Solana Devnet";
79
- readonly namespace: "solana";
80
- readonly reference: "devnet";
81
- };
82
- readonly mainnet: {
83
- readonly id: "solana:mainnet";
84
- readonly name: "Solana Mainnet";
85
- readonly namespace: "solana";
86
- readonly reference: "mainnet";
87
- };
88
- readonly testnet: {
89
- readonly id: "solana:testnet";
90
- readonly name: "Solana Testnet";
91
- readonly namespace: "solana";
92
- readonly reference: "testnet";
93
- };
94
- };
95
- declare const SVM_CHAINS_LIST: ReadonlyArray<ChainBase>;
96
- //#endregion
97
- //#region src/capabilities.d.ts
98
- type WalletStandardCapabilityInput = {
99
- chainCount: number;
100
- features: {
101
- events: boolean;
102
- signAndSendTransaction: boolean;
103
- signIn: boolean;
104
- signMessage: boolean;
105
- signTransaction: boolean;
106
- };
107
- };
108
- /**
109
- * Solana Wallet Standard feature → butr capability mapping. The flags that
110
- * hold for every namespace are documented on `buildWalletCapabilities`.
111
- */
112
- declare const resolveWalletStandardCapabilities: (input: WalletStandardCapabilityInput) => WalletCapabilities;
113
- //#endregion
114
68
  //#region src/discoverer.d.ts
115
69
  /**
116
70
  * No legacy fallback: Solana wallets standardised on Wallet Standard before
@@ -118,17 +72,5 @@ declare const resolveWalletStandardCapabilities: (input: WalletStandardCapabilit
118
72
  */
119
73
  declare const svmDiscoverer: PlatformDiscoverer;
120
74
  //#endregion
121
- //#region src/signer-augmentation.d.ts
122
- /**
123
- * Augment `@usebutr/core`'s `SignerForPlatform` registry with the SVM
124
- * entry. SVM's `getSigner()` returns the Wallet Standard wallet
125
- * object, which consumers narrow further via its `features` map.
126
- */
127
- declare module "@usebutr/core" {
128
- interface SignerForPlatform {
129
- svm: WalletStandardWallet;
130
- }
131
- }
132
- //#endregion
133
- 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, isSolanaSignAndSendTransactionFeature, isSolanaSignInFeature, isSolanaSignMessageFeature, isSolanaSignTransactionFeature, 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 };
134
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":";;;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;;;;cC3CI,6BAA0B,SACrB,0BACR,WAAW,wBAAwB;cAGhC,wCAAqC,SAChC,0BACR,WAAW,wBAAwB;cAGhC,iCAA8B,SACzB,0BACR,WAAW,wBAAwB;cAGhC,wBAAqB,SAChB,0BACR,WAAW,wBAAwB;;;;;;cAQhC,kBAAe,QACX,sBAKe;;;;;AAAA,wBAAC,8BACvB;;;;;;cA+JG,sBAAmB,YACX,SAAS,wBAAsB,aAC9B;;;cCzNT;;aACM;aAAqB;aAAuB;aAAqB;;;aAEzE;aACA;aACA;aACA;;;aAGA;aACA;aACA;aACA;;;cAIE,iBAAiB,cAAc;;;KCfhC;EACH;EACA;IACE;IACA;IACA;IACA;IACA;;;;;;;cAQE,oCAAiC,OAC9B,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,167 +1,79 @@
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"];
23
- const isSolanaSignMessageFeature = (feature) => "signMessage" in feature && typeof feature.signMessage === "function";
24
- const isSolanaSignAndSendTransactionFeature = (feature) => "signAndSendTransaction" in feature && typeof feature.signAndSendTransaction === "function";
25
- const isSolanaSignTransactionFeature = (feature) => "signTransaction" in feature && typeof feature.signTransaction === "function";
26
- const isSolanaSignInFeature = (feature) => "signIn" in feature && typeof feature.signIn === "function";
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
+ };
27
12
  /**
28
- * Wallet Standard has no switch-cluster RPC: `switchChain` re-points butr's
29
- * view only, and `signAndSendTransaction` takes the chain per call.
30
- * `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.
31
16
  */
32
17
  const buildSvmAdapter = (wallet, registerDisconnector) => {
33
18
  const core = createWalletStandardCore({
34
- chainPrefix: SOLANA_PREFIX,
19
+ chains: SVM_CHAINS_LIST,
35
20
  id: slugify("svm", wallet.name),
36
- label: "SVM",
21
+ label: "Solana",
37
22
  namespace: "solana",
38
- platform: "Solana",
39
- preferredChainIds: SOLANA_MAINNETS,
23
+ preferredChainIds: ["solana:mainnet", "solana:mainnet-beta"],
40
24
  registerDisconnector,
41
25
  trackChainChanges: true,
42
26
  wallet
43
27
  });
44
28
  if (core === null) return null;
45
- const signMessage = getFeature(wallet, "solana:signMessage", isSolanaSignMessageFeature);
46
- const signAndSendTx = getFeature(wallet, "solana:signAndSendTransaction", isSolanaSignAndSendTransactionFeature);
47
- const signTx = getFeature(wallet, "solana:signTransaction", isSolanaSignTransactionFeature);
48
- const signIn = getFeature(wallet, "solana:signIn", isSolanaSignInFeature);
49
- /**
50
- * Resolve a caller-supplied target into a chain the wallet advertises.
51
- * Accepts both the full CAIP-2 id (`solana:devnet`) and a bare reference
52
- * (`devnet`), since consumers reasonably reach for either.
53
- */
54
- const resolveTargetChain = (targetChainId) => {
55
- const candidate = targetChainId.startsWith(SOLANA_PREFIX) ? targetChainId : `${SOLANA_PREFIX}${targetChainId}`;
56
- if (!wallet.chains.includes(candidate)) throw new Error(`Wallet ${wallet.name} does not advertise chain "${candidate}". Available: ${wallet.chains.join(", ")}`);
57
- return candidate;
58
- };
59
- const signAndSend = async (tx, account, chain) => {
60
- if (signAndSendTx === void 0) throw new Error(`Wallet ${wallet.name} does not advertise solana:signAndSendTransaction`);
61
- const wsAccount = core.resolveAccount(account);
62
- if (!(tx instanceof Uint8Array)) throw new TypeError("SVM sendTx expects a serialized transaction (Uint8Array)");
63
- const [output] = await signAndSendTx.signAndSendTransaction({
64
- account: wsAccount,
65
- chain: chain ?? core.currentChainId(),
66
- transaction: tx
67
- });
68
- if (output === void 0) throw new Error("signAndSendTransaction returned no outputs");
69
- return bytesToBase58(output.signature);
70
- };
71
- const adapter = {
72
- ...core,
73
- capabilities: resolveWalletStandardCapabilities({
74
- chainCount: core.chainCount,
75
- features: {
76
- events: core.hasEvents,
77
- signAndSendTransaction: Boolean(signAndSendTx),
78
- signIn: Boolean(signIn),
79
- signMessage: Boolean(signMessage),
80
- signTransaction: Boolean(signTx)
81
- }
82
- }),
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");
33
+ return {
34
+ ...core.base,
83
35
  chainPlatform: "svm",
84
- getBalance: () => Promise.resolve({
85
- decimals: SOLANA_DECIMALS,
86
- formatted: "0",
87
- symbol: "SOL",
88
- value: 0n
89
- }),
90
- getTransactionReceipt: () => Promise.resolve({ status: "Pending" }),
91
- async requestAccounts() {
92
- await core.connect();
93
- },
94
- sendTx: (tx, account) => signAndSend(tx, account),
95
- async sendTxToChain(tx, targetChainId, account, cb) {
96
- const target = resolveTargetChain(targetChainId);
97
- if (target !== core.currentChainId()) cb?.();
98
- return await signAndSend(tx, account, target);
99
- },
100
- async signMessage(msg, account) {
101
- if (signMessage === void 0) throw new Error(`Wallet ${wallet.name} does not advertise solana:signMessage`);
102
- const [output] = await signMessage.signMessage({
103
- account: core.resolveAccount(account),
104
- message: msg
105
- });
106
- 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");
107
49
  return {
108
50
  signature: output.signature,
109
51
  signedMessage: output.signedMessage
110
52
  };
111
- }
112
- };
113
- if (signTx !== void 0) adapter.signTransaction = async (tx, account) => {
114
- const wsAccount = core.resolveAccount(account);
115
- if (!(tx instanceof Uint8Array)) throw new TypeError("SVM signTransaction expects a serialized transaction (Uint8Array)");
116
- const [output] = await signTx.signTransaction({
117
- account: wsAccount,
118
- chain: core.currentChainId(),
119
- transaction: tx
120
- });
121
- if (output === void 0) throw new Error("signTransaction returned no outputs");
122
- return output.signedTransaction;
123
- };
124
- if (signIn !== void 0) adapter.signIn = async (input) => {
125
- const [output] = await signIn.signIn(input);
126
- if (output === void 0) throw new Error("signIn returned no outputs");
127
- return {
128
- account: buildAccount(output.account.address, core.toChain()),
129
- signature: output.signature,
130
- signedMessage: output.signedMessage
131
- };
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),
58
+ transaction: tx
59
+ }), "signTransaction").signedTransaction;
60
+ } },
61
+ ...signIn !== void 0 && { async signIn(input) {
62
+ const output = firstOutput(await signIn.signIn(input), "signIn");
63
+ return {
64
+ account: buildAccount(output.account.address, core.currentChain()),
65
+ signature: output.signature,
66
+ signedMessage: output.signedMessage
67
+ };
68
+ } }
132
69
  };
133
- return adapter;
134
70
  };
135
71
  /**
136
72
  * Requires the optional `@wallet-standard/app` peer dep; without it SVM
137
73
  * discovery silently does nothing. The returned unsubscribe is safe to call
138
74
  * before the dynamic import has resolved.
139
75
  */
140
- const discoverSvmAdapters = (onAdapter, loadModule) => discoverWalletStandard(onAdapter, (wallet, registerDisconnector) => buildSvmAdapter(wallet, registerDisconnector), loadModule);
141
-
142
- //#endregion
143
- //#region src/chains.ts
144
- const SVM_CHAINS = {
145
- devnet: {
146
- id: "solana:devnet",
147
- name: "Solana Devnet",
148
- namespace: "solana",
149
- reference: "devnet"
150
- },
151
- mainnet: {
152
- id: "solana:mainnet",
153
- name: "Solana Mainnet",
154
- namespace: "solana",
155
- reference: "mainnet"
156
- },
157
- testnet: {
158
- id: "solana:testnet",
159
- name: "Solana Testnet",
160
- namespace: "solana",
161
- reference: "testnet"
162
- }
163
- };
164
- const SVM_CHAINS_LIST = Object.values(SVM_CHAINS);
76
+ const discoverSvmAdapters = (onAdapter, loadModule) => discoverWalletStandard(onAdapter, buildSvmAdapter, loadModule);
165
77
 
166
78
  //#endregion
167
79
  //#region src/discoverer.ts
@@ -172,5 +84,5 @@ const SVM_CHAINS_LIST = Object.values(SVM_CHAINS);
172
84
  const svmDiscoverer = { subscribe: discoverSvmAdapters };
173
85
 
174
86
  //#endregion
175
- export { SVM_CHAINS, SVM_CHAINS_LIST, buildSvmAdapter, discoverSvmAdapters, isSolanaSignAndSendTransactionFeature, isSolanaSignInFeature, isSolanaSignMessageFeature, isSolanaSignTransactionFeature, resolveWalletStandardCapabilities, svmDiscoverer };
87
+ export { buildSvmAdapter, discoverSvmAdapters, svmDiscoverer };
176
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 { TransactionInput, 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 {\n WalletStandardFeature,\n WalletStandardModuleLoader,\n WalletStandardWallet,\n} 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\nconst isSolanaSignMessageFeature = (\n feature: WalletStandardFeature,\n): feature is WalletStandardFeature & SolanaSignMessageFeature =>\n \"signMessage\" in feature && typeof feature.signMessage === \"function\";\n\nconst isSolanaSignAndSendTransactionFeature = (\n feature: WalletStandardFeature,\n): feature is WalletStandardFeature & SolanaSignAndSendTransactionFeature =>\n \"signAndSendTransaction\" in feature && typeof feature.signAndSendTransaction === \"function\";\n\nconst isSolanaSignTransactionFeature = (\n feature: WalletStandardFeature,\n): feature is WalletStandardFeature & SolanaSignTransactionFeature =>\n \"signTransaction\" in feature && typeof feature.signTransaction === \"function\";\n\nconst isSolanaSignInFeature = (\n feature: WalletStandardFeature,\n): feature is WalletStandardFeature & SolanaSignInFeature =>\n \"signIn\" in feature && typeof feature.signIn === \"function\";\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(wallet, \"solana:signMessage\", isSolanaSignMessageFeature);\n const signAndSendTx = getFeature(\n wallet,\n \"solana:signAndSendTransaction\",\n isSolanaSignAndSendTransactionFeature,\n );\n const signTx = getFeature(wallet, \"solana:signTransaction\", isSolanaSignTransactionFeature);\n const signIn = getFeature(wallet, \"solana:signIn\", isSolanaSignInFeature);\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: TransactionInput,\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 return bytesToBase58(output.signature);\n };\n\n const adapter: WalletAdapter = {\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 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\n if (signTx !== undefined) {\n adapter.signTransaction = async (tx, account) => {\n const wsAccount = core.resolveAccount(account);\n if (!(tx instanceof Uint8Array)) {\n throw new TypeError(\"SVM signTransaction expects a serialized transaction (Uint8Array)\");\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 if (signIn !== undefined) {\n adapter.signIn = async (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 return adapter;\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) =>\n discoverWalletStandard(\n onAdapter,\n (wallet, registerDisconnector) => buildSvmAdapter(wallet, registerDisconnector),\n loadModule,\n );\n\nexport {\n buildSvmAdapter,\n discoverSvmAdapters,\n isSolanaSignAndSendTransactionFeature,\n isSolanaSignInFeature,\n isSolanaSignMessageFeature,\n isSolanaSignTransactionFeature,\n};\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,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebutr/svm",
3
- "version": "2.0.2",
3
+ "version": "3.0.1",
4
4
  "description": "Wallet Standard adapter for butr (Solana / SVM).",
5
5
  "keywords": [
6
6
  "butr",
@@ -35,19 +35,23 @@
35
35
  ".": {
36
36
  "types": "./dist/index.d.ts",
37
37
  "default": "./dist/index.js"
38
+ },
39
+ "./transaction": {
40
+ "types": "./dist/transaction.d.ts",
41
+ "default": "./dist/transaction.js"
38
42
  }
39
43
  },
40
44
  "publishConfig": {
41
45
  "access": "public"
42
46
  },
43
47
  "dependencies": {
44
- "@usebutr/core": "2.0.1",
45
- "@usebutr/wallet-standard-shared": "1.0.0"
48
+ "@usebutr/core": "3.0.1",
49
+ "@usebutr/wallet-standard-shared": "2.0.1"
46
50
  },
47
51
  "devDependencies": {
48
52
  "@wallet-standard/app": "^1.1.1",
49
53
  "typescript": "^7.0.2",
50
- "vitest": "^4.1.10",
54
+ "vitest": "^5.0.1",
51
55
  "@repo/config-vitest": "0.0.0",
52
56
  "@repo/typescript-config": "0.0.0"
53
57
  },