@usebutr/svm 0.2.6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -25,7 +25,6 @@ type SolanaSignAndSendTransactionFeature = {
25
25
  };
26
26
  //#endregion
27
27
  //#region src/wallet-standard-adapter.d.ts
28
- declare const slugify: (name: string) => string;
29
28
  /**
30
29
  * Adapt a Solana Wallet Standard `Wallet` object into a butr
31
30
  * `WalletAdapter`. Returns `null` if the wallet doesn't advertise any
@@ -72,8 +71,6 @@ declare const buildSvmAdapter: (wallet: WalletStandardWallet,
72
71
  * layer invokes it on Wallet Standard `unregister` so a connected
73
72
  * pool entry tears down when its extension is removed. */
74
73
  registerDisconnector?: (emit: () => void) => void) => WalletAdapter | null;
75
- //#endregion
76
- //#region src/wallet-standard.d.ts
77
74
  /**
78
75
  * Subscribe to Solana Wallet Standard announcements.
79
76
  * Spec: https://github.com/wallet-standard/wallet-standard
@@ -131,16 +128,8 @@ type WalletStandardCapabilityInput = {
131
128
  };
132
129
  };
133
130
  /**
134
- * Wallet Standard capability mapping.
135
- *
136
- * - `getBalance` / `getTransactionReceipt`: Wallet Standard exposes no
137
- * balance/receipt RPC.
138
- * - `requestAccounts`: no programmatic equivalent of EIP-2255; re-running
139
- * `standard:connect` doesn't produce new accounts on any major Wallet
140
- * Standard wallet in practice (Phantom Solana, MetaMask Snap, Solflare,
141
- * Backpack).
142
- * - `switchChain`: true when more than one chain is advertised; with a
143
- * single chain there's nothing to switch to.
131
+ * Solana Wallet Standard feature → butr capability mapping. The flags that
132
+ * hold for every namespace are documented on `buildWalletCapabilities`.
144
133
  */
145
134
  declare const resolveWalletStandardCapabilities: (input: WalletStandardCapabilityInput) => WalletCapabilities;
146
135
  //#endregion
@@ -165,5 +154,5 @@ declare module "@usebutr/core" {
165
154
  }
166
155
  }
167
156
  //#endregion
168
- export { SVM_CHAINS, SVM_CHAINS_LIST, type SolanaSignAndSendTransactionFeature, type SolanaSignAndSendTransactionInput, type SolanaSignAndSendTransactionOutput, type SolanaSignMessageFeature, type SolanaSignMessageInput, type SolanaSignMessageOutput, type WalletStandardCapabilityInput, buildSvmAdapter, discoverSvmAdapters, resolveWalletStandardCapabilities, slugify, svmDiscoverer };
157
+ export { SVM_CHAINS, SVM_CHAINS_LIST, type SolanaSignAndSendTransactionFeature, type SolanaSignAndSendTransactionInput, type SolanaSignAndSendTransactionOutput, type SolanaSignMessageFeature, type SolanaSignMessageInput, type SolanaSignMessageOutput, type WalletStandardCapabilityInput, buildSvmAdapter, discoverSvmAdapters, resolveWalletStandardCapabilities, svmDiscoverer };
169
158
  //# 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/wallet-standard.ts","../src/chains.ts","../src/capabilities.ts","../src/discoverer.ts","../src/signer-augmentation.ts"],"mappings":";;;KAaK;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;;;;cCfvB,UAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA2DX,kBACJ,QAAQ,sBAKR;;;;;AAAA,wBAAwB,8BACvB;;;;;;;;;;;;;;;;;;;;;;;cCpEG,sBAAuB,YAAY,SAAS;;;cChB5C;;;;;;;;;;;;;;;;;;;;cAgBA,iBAAiB,cAAc;;;KCvBhC;EACH;EACA;IACE;IACA;IACA;IACA;IACA;;;;;;;;;;;;;;;cAgBE,oCACJ,OAAO,kCACN;;;;;;;;;cCjBG,eAAe;;;;;;;;;YCDT;IACR,KAAK"}
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":";;;KAaK;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cCoBvB,kBACJ,QAAQ,sBAKR;;;;;AAAA,wBAAwB,8BACvB;;;;;;;;;;;;;;;;;;;;;cA2KG,sBAAuB,YAAY,SAAS;;;cCvO5C;;;;;;;;;;;;;;;;;;;;cAgBA,iBAAiB,cAAc;;;KCtBhC;EACH;EACA;IACE;IACA;IACA;IACA;IACA;;;;;;;cAQE,oCACJ,OAAO,kCACN;;;;;;;;;cCVG,eAAe;;;;;;;;;YCDT;IACR,KAAK"}
package/dist/index.js CHANGED
@@ -1,48 +1,25 @@
1
- import { bytesToBase64, logWarn, sanitizeIcon } from "@usebutr/core";
2
- import { buildAccount, discoverWalletStandard, getFeature, pickAccountByAddress, pickFirstAddress, slugify as slugify$1 } from "@usebutr/wallet-standard-shared";
1
+ import { buildAccount, bytesToBase64 } from "@usebutr/core";
2
+ import { buildWalletCapabilities, createWalletStandardCore, discoverWalletStandard, getFeature, slugify } from "@usebutr/wallet-standard-shared";
3
3
 
4
4
  //#region src/capabilities.ts
5
5
  /**
6
- * Wallet Standard capability mapping.
7
- *
8
- * - `getBalance` / `getTransactionReceipt`: Wallet Standard exposes no
9
- * balance/receipt RPC.
10
- * - `requestAccounts`: no programmatic equivalent of EIP-2255; re-running
11
- * `standard:connect` doesn't produce new accounts on any major Wallet
12
- * Standard wallet in practice (Phantom Solana, MetaMask Snap, Solflare,
13
- * Backpack).
14
- * - `switchChain`: true when more than one chain is advertised; with a
15
- * single chain there's nothing to switch to.
6
+ * Solana Wallet Standard feature → butr capability mapping. The flags that
7
+ * hold for every namespace are documented on `buildWalletCapabilities`.
16
8
  */
17
- const resolveWalletStandardCapabilities = (input) => ({
18
- getBalance: false,
19
- getTransactionReceipt: false,
20
- requestAccounts: false,
9
+ const resolveWalletStandardCapabilities = (input) => buildWalletCapabilities({
10
+ chainCount: input.chainCount,
11
+ events: input.features.events,
21
12
  sendTransaction: input.features.signAndSendTransaction,
22
13
  signIn: input.features.signIn,
23
14
  signMessage: input.features.signMessage,
24
- signTransaction: input.features.signTransaction,
25
- subscribe: input.features.events,
26
- switchAccount: false,
27
- switchChain: input.chainCount > 1
15
+ signTransaction: input.features.signTransaction
28
16
  });
29
17
 
30
18
  //#endregion
31
19
  //#region src/wallet-standard-adapter.ts
32
20
  const SOLANA_PREFIX = "solana:";
33
21
  const SOLANA_DECIMALS = 9;
34
- const slugify = (name) => slugify$1("svm", name);
35
- const pickSolanaChain = (wallet) => {
36
- const mainnet = wallet.chains.find((c) => c === "solana:mainnet" || c === "solana:mainnet-beta");
37
- if (mainnet) return mainnet;
38
- return wallet.chains.find((c) => c.startsWith(SOLANA_PREFIX)) ?? null;
39
- };
40
- const buildSolanaChain = (chainId, walletName) => ({
41
- id: chainId,
42
- name: walletName,
43
- namespace: "solana",
44
- reference: chainId.slice(7)
45
- });
22
+ const SOLANA_MAINNETS = ["solana:mainnet", "solana:mainnet-beta"];
46
23
  /**
47
24
  * Adapt a Solana Wallet Standard `Wallet` object into a butr
48
25
  * `WalletAdapter`. Returns `null` if the wallet doesn't advertise any
@@ -84,52 +61,40 @@ const buildSolanaChain = (chainId, walletName) => ({
84
61
  * selection can pick one from `accounts` at call time.
85
62
  */
86
63
  const buildSvmAdapter = (wallet, registerDisconnector) => {
87
- const solanaChainId = pickSolanaChain(wallet);
88
- if (solanaChainId === null) return null;
89
- const connect = getFeature(wallet, "standard:connect");
90
- if (connect === void 0) return null;
91
- const disconnect = getFeature(wallet, "standard:disconnect");
92
- const events = getFeature(wallet, "standard:events");
64
+ const core = createWalletStandardCore({
65
+ chainPrefix: SOLANA_PREFIX,
66
+ id: slugify("svm", wallet.name),
67
+ label: "SVM",
68
+ namespace: "solana",
69
+ platform: "Solana",
70
+ preferredChainIds: SOLANA_MAINNETS,
71
+ registerDisconnector,
72
+ trackChainChanges: true,
73
+ wallet
74
+ });
75
+ if (core === null) return null;
93
76
  const signMessage = getFeature(wallet, "solana:signMessage");
94
77
  const signAndSendTx = getFeature(wallet, "solana:signAndSendTransaction");
95
78
  const signTx = getFeature(wallet, "solana:signTransaction");
96
79
  const signIn = getFeature(wallet, "solana:signIn");
97
- let currentChainId = solanaChainId;
98
- const currentChain = () => buildSolanaChain(currentChainId, wallet.name);
99
- const listeners = /* @__PURE__ */ new Set();
100
- const notifyAccountChanged = () => {
101
- if (wallet.accounts.length === 0) return;
102
- const chain = currentChain();
103
- const built = wallet.accounts.map((a) => buildAccount(a.address, chain));
104
- const first = built[0];
105
- if (first === void 0) return;
106
- for (const listener of listeners) listener({
107
- account: first,
108
- accounts: built,
109
- type: "accountChanged"
110
- });
111
- };
112
- registerDisconnector?.(() => {
113
- for (const listener of listeners) listener({ type: "disconnected" });
114
- });
115
80
  const signAndSend = async (tx, account) => {
116
81
  if (signAndSendTx === void 0) throw new Error(`Wallet ${wallet.name} does not advertise solana:signAndSendTransaction`);
117
- const wsAccount = account ? pickAccountByAddress(wallet.accounts, account.walletAddress) : wallet.accounts[0];
118
- if (wsAccount === void 0) throw new Error("No connected account");
82
+ const wsAccount = core.resolveAccount(account);
119
83
  if (!(tx instanceof Uint8Array)) throw new TypeError("SVM sendTx expects a serialized transaction (Uint8Array)");
120
84
  const [output] = await signAndSendTx.signAndSendTransaction({
121
85
  account: wsAccount,
122
- chain: currentChainId,
86
+ chain: core.currentChainId(),
123
87
  transaction: tx
124
88
  });
125
89
  if (output === void 0) throw new Error("signAndSendTransaction returned no outputs");
126
90
  return bytesToBase64(output.signature);
127
91
  };
128
92
  return {
93
+ ...core,
129
94
  capabilities: resolveWalletStandardCapabilities({
130
95
  chainCount: wallet.chains.length,
131
96
  features: {
132
- events: Boolean(events),
97
+ events: core.hasEvents,
133
98
  signAndSendTransaction: Boolean(signAndSendTx),
134
99
  signIn: Boolean(signIn),
135
100
  signMessage: Boolean(signMessage),
@@ -137,38 +102,15 @@ const buildSvmAdapter = (wallet, registerDisconnector) => {
137
102
  }
138
103
  }),
139
104
  chainPlatform: "svm",
140
- async connect(opts) {
141
- await connect.connect(opts?.silent === true ? { silent: true } : void 0);
142
- },
143
- async disconnect() {
144
- if (disconnect !== void 0) try {
145
- await disconnect.disconnect();
146
- } catch (error) {
147
- logWarn("[butr] Wallet Standard disconnect threw:", error);
148
- }
149
- },
150
- getAccount: () => {
151
- const address = pickFirstAddress(wallet.accounts);
152
- if (address === null) return Promise.resolve(null);
153
- return Promise.resolve(buildAccount(address, currentChain()));
154
- },
155
- getAccounts: () => {
156
- const chain = currentChain();
157
- return Promise.resolve(wallet.accounts.map((a) => buildAccount(a.address, chain)));
158
- },
159
105
  getBalance: () => Promise.resolve({
160
106
  decimals: SOLANA_DECIMALS,
161
107
  formatted: "0",
162
108
  symbol: "SOL",
163
109
  value: 0n
164
110
  }),
165
- getSigner: () => Promise.resolve(wallet),
166
111
  getTransactionReceipt: () => Promise.resolve({ status: "Pending" }),
167
- icon: sanitizeIcon(wallet.icon),
168
- id: slugify(wallet.name),
169
- name: wallet.name,
170
112
  async requestAccounts() {
171
- await connect.connect();
113
+ await core.connect();
172
114
  },
173
115
  sendTx: (tx, account) => signAndSend(tx, account),
174
116
  sendTxToChain: (tx, _targetChainId, account, cb) => {
@@ -177,10 +119,8 @@ const buildSvmAdapter = (wallet, registerDisconnector) => {
177
119
  },
178
120
  async signMessage(msg, account) {
179
121
  if (signMessage === void 0) throw new Error(`Wallet ${wallet.name} does not advertise solana:signMessage`);
180
- const wsAccount = account ? pickAccountByAddress(wallet.accounts, account.walletAddress) : wallet.accounts[0];
181
- if (wsAccount === void 0) throw new Error("No connected account");
182
122
  const [output] = await signMessage.signMessage({
183
- account: wsAccount,
123
+ account: core.resolveAccount(account),
184
124
  message: msg
185
125
  });
186
126
  if (output === void 0) throw new Error("signMessage returned no outputs");
@@ -190,12 +130,11 @@ const buildSvmAdapter = (wallet, registerDisconnector) => {
190
130
  };
191
131
  },
192
132
  ...signTx === void 0 ? {} : { async signTransaction(tx, account) {
193
- const wsAccount = account ? pickAccountByAddress(wallet.accounts, account.walletAddress) : wallet.accounts[0];
194
- if (wsAccount === void 0) throw new Error("No connected account");
133
+ const wsAccount = core.resolveAccount(account);
195
134
  if (!(tx instanceof Uint8Array)) throw new TypeError("SVM signTransaction expects a serialized transaction (Uint8Array)");
196
135
  const [output] = await signTx.signTransaction({
197
136
  account: wsAccount,
198
- chain: currentChainId,
137
+ chain: core.currentChainId(),
199
138
  transaction: tx
200
139
  });
201
140
  if (output === void 0) throw new Error("signTransaction returned no outputs");
@@ -205,59 +144,13 @@ const buildSvmAdapter = (wallet, registerDisconnector) => {
205
144
  const [output] = await signIn.signIn(input);
206
145
  if (output === void 0) throw new Error("signIn returned no outputs");
207
146
  return {
208
- account: buildAccount(output.account.address, currentChain()),
147
+ account: buildAccount(output.account.address, core.toChain()),
209
148
  signature: output.signature,
210
149
  signedMessage: output.signedMessage
211
150
  };
212
- } },
213
- subscribe(listener) {
214
- listeners.add(listener);
215
- let unsubWallet = null;
216
- if (events !== void 0) {
217
- const unsub = events.on("change", (changes) => {
218
- if (changes.chains !== void 0) {
219
- const next = changes.chains.find((c) => c === "solana:mainnet" || c === "solana:mainnet-beta") ?? changes.chains.find((c) => c.startsWith(SOLANA_PREFIX));
220
- if (next !== void 0) currentChainId = next;
221
- }
222
- if (changes.accounts !== void 0) {
223
- if (changes.accounts.length === 0) {
224
- listener({ type: "disconnected" });
225
- return;
226
- }
227
- const chain = currentChain();
228
- const built = changes.accounts.map((a) => buildAccount(a.address, chain));
229
- const first = built[0];
230
- if (first === void 0) return;
231
- listener({
232
- account: first,
233
- accounts: built,
234
- type: "accountChanged"
235
- });
236
- return;
237
- }
238
- if (changes.chains !== void 0) notifyAccountChanged();
239
- });
240
- unsubWallet = () => {
241
- unsub();
242
- };
243
- }
244
- return () => {
245
- listeners.delete(listener);
246
- unsubWallet?.();
247
- };
248
- },
249
- switchChain: (chain) => {
250
- if (chain.namespace !== "solana") throw new Error(`SVM adapter received non-Solana chain "${chain.id}". Pass a chain with namespace "solana".`);
251
- if (!wallet.chains.includes(chain.id)) throw new Error(`Wallet ${wallet.name} does not advertise chain "${chain.id}". Available: ${wallet.chains.join(", ")}`);
252
- currentChainId = chain.id;
253
- notifyAccountChanged();
254
- return Promise.resolve();
255
- }
151
+ } }
256
152
  };
257
153
  };
258
-
259
- //#endregion
260
- //#region src/wallet-standard.ts
261
154
  /**
262
155
  * Subscribe to Solana Wallet Standard announcements.
263
156
  * Spec: https://github.com/wallet-standard/wallet-standard
@@ -312,11 +205,8 @@ const SVM_CHAINS_LIST = Object.values(SVM_CHAINS);
312
205
  * before butr existed, so there's no `window.solana` injected path
313
206
  * worth shimming.
314
207
  */
315
- const svmDiscoverer = {
316
- platform: "svm",
317
- subscribe: discoverSvmAdapters
318
- };
208
+ const svmDiscoverer = { subscribe: discoverSvmAdapters };
319
209
 
320
210
  //#endregion
321
- export { SVM_CHAINS, SVM_CHAINS_LIST, buildSvmAdapter, discoverSvmAdapters, resolveWalletStandardCapabilities, slugify, svmDiscoverer };
211
+ export { SVM_CHAINS, SVM_CHAINS_LIST, buildSvmAdapter, discoverSvmAdapters, resolveWalletStandardCapabilities, svmDiscoverer };
322
212
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["kitSlugify"],"sources":["../src/capabilities.ts","../src/wallet-standard-adapter.ts","../src/wallet-standard.ts","../src/chains.ts","../src/discoverer.ts"],"sourcesContent":["import type { WalletCapabilities } from \"@usebutr/core\";\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 * Wallet Standard capability mapping.\n *\n * - `getBalance` / `getTransactionReceipt`: Wallet Standard exposes no\n * balance/receipt RPC.\n * - `requestAccounts`: no programmatic equivalent of EIP-2255; re-running\n * `standard:connect` doesn't produce new accounts on any major Wallet\n * Standard wallet in practice (Phantom Solana, MetaMask Snap, Solflare,\n * Backpack).\n * - `switchChain`: true when more than one chain is advertised; with a\n * single chain there's nothing to switch to.\n */\nconst resolveWalletStandardCapabilities = (\n input: WalletStandardCapabilityInput,\n): WalletCapabilities => ({\n getBalance: false,\n getTransactionReceipt: false,\n requestAccounts: false,\n sendTransaction: input.features.signAndSendTransaction,\n signIn: input.features.signIn,\n signMessage: input.features.signMessage,\n signTransaction: input.features.signTransaction,\n subscribe: input.features.events,\n switchAccount: false,\n switchChain: input.chainCount > 1,\n});\n\nexport type { WalletStandardCapabilityInput };\nexport { resolveWalletStandardCapabilities };\n","import type { ChainBase, ConnectorEvent, WalletAdapter } from \"@usebutr/core\";\nimport { bytesToBase64, logWarn, sanitizeIcon } from \"@usebutr/core\";\nimport {\n buildAccount,\n getFeature,\n pickAccountByAddress,\n pickFirstAddress,\n slugify as kitSlugify,\n} from \"@usebutr/wallet-standard-shared\";\nimport type {\n StandardConnectFeature,\n StandardDisconnectFeature,\n StandardEventsFeature,\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;\n\nconst slugify = (name: string): string => kitSlugify(\"svm\", name);\n\nconst pickSolanaChain = (wallet: WalletStandardWallet): string | null => {\n const mainnet = wallet.chains.find((c) => c === \"solana:mainnet\" || c === \"solana:mainnet-beta\");\n if (mainnet) {\n return mainnet;\n }\n return wallet.chains.find((c) => c.startsWith(SOLANA_PREFIX)) ?? null;\n};\n\nconst buildSolanaChain = (chainId: string, walletName: string): ChainBase => ({\n id: chainId,\n // Same stance as the EIP-6963 side: we don't ship a chain-id → name\n // table. Consumers overlay via structural typing.\n name: walletName,\n namespace: \"solana\",\n reference: chainId.slice(SOLANA_PREFIX.length),\n});\n\n/**\n * Adapt a Solana Wallet Standard `Wallet` object into a butr\n * `WalletAdapter`. Returns `null` if the wallet doesn't advertise any\n * Solana chain or doesn't expose `standard:connect`; both required\n * for butr to do anything useful with it.\n *\n * **Capability gating**\n *\n * - `signMessage` is only present if the wallet advertises\n * `solana:signMessage`. Without it, calling `adapter.signMessage`\n * rejects with a clear error.\n * - `sendTx` / `sendTxToChain` use `solana:signAndSendTransaction`.\n * Without it, those methods reject.\n * - `subscribe` is wired through `standard:events`. Wallets that\n * don't expose events get a no-op unsubscribe.\n *\n * **Known limitations**\n *\n * - `getBalance` returns `{ value: 0n, formatted: \"0\", … }`. Solana\n * balance reads require an RPC connection that's outside butr's\n * scope; consumers wrap their own RPC client.\n * - `getTransactionReceipt` returns `{ status: \"Pending\" }`. Same\n * reason; needs an RPC.\n * - `switchChain` updates butr's view of the current Solana cluster\n * locally and routes subsequent `signAndSendTransaction` calls\n * through the new chain. Wallet Standard has no \"tell the wallet\n * to switch cluster\" RPC; the wallet's own UI controls its global\n * cluster setting, and `signAndSendTransaction` accepts the chain\n * per-call. The chain must be one the wallet advertises in\n * `wallet.chains`; otherwise the call rejects.\n * - `requestAccounts` re-runs `standard:connect`. Some wallets show\n * their account picker again; others (those that \"remember\"\n * authorisations) silently return the existing accounts. butr\n * refreshes the pool entry's `accounts` array either way.\n * - `switchAccount` is intentionally unimplemented. Wallet Standard\n * has no silent \"use address X\" feature; the user picks the active\n * account through the wallet's own UI. `signAndSendTransaction`\n * accepts an `account` input, so consumers needing per-tx address\n * selection can pick one from `accounts` at call time.\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 solanaChainId = pickSolanaChain(wallet);\n if (solanaChainId === null) {\n return null;\n }\n const connect = getFeature<StandardConnectFeature>(wallet, \"standard:connect\");\n if (connect === undefined) {\n return null;\n }\n\n const disconnect = getFeature<StandardDisconnectFeature>(wallet, \"standard:disconnect\");\n const events = getFeature<StandardEventsFeature>(wallet, \"standard:events\");\n 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 let currentChainId = solanaChainId;\n const currentChain = (): ChainBase => buildSolanaChain(currentChainId, wallet.name);\n\n // Local listener set so `switchChain` (and `requestAccounts`) can\n // synthesise `accountChanged` events that flow into butr's pool.\n // Wallet-native events from `standard:events` are bridged alongside.\n const listeners = new Set<(event: ConnectorEvent) => void>();\n const notifyAccountChanged = () => {\n if (wallet.accounts.length === 0) {\n return;\n }\n const chain = currentChain();\n const built = wallet.accounts.map((a) => buildAccount(a.address, chain));\n const first = built[0];\n if (first === undefined) {\n return;\n }\n for (const listener of listeners) {\n listener({ account: first, accounts: built, type: \"accountChanged\" });\n }\n };\n\n registerDisconnector?.(() => {\n for (const listener of listeners) {\n listener({ type: \"disconnected\" });\n }\n });\n\n const signAndSend = async (tx: unknown, account?: { walletAddress: string }): Promise<string> => {\n if (signAndSendTx === undefined) {\n throw new Error(`Wallet ${wallet.name} does not advertise solana:signAndSendTransaction`);\n }\n const wsAccount = account\n ? pickAccountByAddress(wallet.accounts, account.walletAddress)\n : wallet.accounts[0];\n if (wsAccount === undefined) {\n throw new Error(\"No connected account\");\n }\n 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: currentChainId,\n transaction: tx,\n });\n if (output === undefined) {\n throw new Error(\"signAndSendTransaction returned no outputs\");\n }\n return bytesToBase64(output.signature);\n };\n\n return {\n capabilities: resolveWalletStandardCapabilities({\n chainCount: wallet.chains.length,\n features: {\n events: Boolean(events),\n signAndSendTransaction: Boolean(signAndSendTx),\n signIn: Boolean(signIn),\n signMessage: Boolean(signMessage),\n signTransaction: Boolean(signTx),\n },\n }),\n chainPlatform: \"svm\",\n\n async connect(opts) {\n // Forward `silent` so eager hydration restores already-authorized\n // accounts without re-opening the wallet's approval UI. Wallets\n // that don't honour `silent` fall back to their normal behaviour.\n await connect.connect(opts?.silent === true ? { silent: true } : undefined);\n },\n\n async disconnect() {\n if (disconnect !== undefined) {\n try {\n await disconnect.disconnect();\n } catch (error) {\n logWarn(\"[butr] Wallet Standard disconnect threw:\", error);\n }\n }\n },\n\n getAccount: () => {\n const address = pickFirstAddress(wallet.accounts);\n if (address === null) {\n return Promise.resolve(null);\n }\n return Promise.resolve(buildAccount(address, currentChain()));\n },\n\n getAccounts: () => {\n const chain = currentChain();\n return Promise.resolve(wallet.accounts.map((a) => buildAccount(a.address, chain)));\n },\n\n // Wallet Standard exposes no balance feature. Consumers wrap their\n // own RPC client; this default keeps the type signature honoured\n // without lying about a value we don't have.\n getBalance: () =>\n Promise.resolve({\n decimals: SOLANA_DECIMALS,\n formatted: \"0\",\n symbol: \"SOL\",\n value: 0n,\n }),\n\n // Consumers wrap this in their preferred provider (e.g. an Anchor\n // provider built around the wallet's signTransaction feature).\n getSigner: () => Promise.resolve(wallet),\n\n getTransactionReceipt: () => Promise.resolve({ status: \"Pending\" as const }),\n\n icon: sanitizeIcon(wallet.icon),\n id: slugify(wallet.name),\n name: wallet.name,\n\n async requestAccounts() {\n // Wallet Standard has no equivalent of EIP-2255's\n // `wallet_requestPermissions`. The closest we can do is re-run\n // `standard:connect`, which prompts the user on wallets that\n // surface their picker each time and silently returns the\n // existing list on wallets that remember authorisations.\n // butr's runtime calls `getAccounts()` afterwards to refresh\n // the pool entry, so newly-exposed addresses appear either way.\n await connect.connect();\n },\n\n sendTx: (tx, account) => signAndSend(tx, account),\n\n sendTxToChain: (tx, _targetChainId, account, cb) => {\n // Solana Wallet Standard's signAndSendTransaction takes the\n // target chain directly. `targetChainId` is the decimal-string\n // form butr uses, but the wallet expects a CAIP-2 chain string.\n // For now we honour the connector's primary `solanaChainId` and\n // let consumers route per-chain at a higher level if they need\n // multi-cluster support.\n cb?.();\n return signAndSend(tx, account);\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 wsAccount = account\n ? pickAccountByAddress(wallet.accounts, account.walletAddress)\n : wallet.accounts[0];\n if (wsAccount === undefined) {\n throw new Error(\"No connected account\");\n }\n const [output] = await signMessage.signMessage({ account: wsAccount, message: msg });\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 = account\n ? pickAccountByAddress(wallet.accounts, account.walletAddress)\n : wallet.accounts[0];\n if (wsAccount === undefined) {\n throw new Error(\"No connected account\");\n }\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: 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, currentChain()),\n signature: output.signature,\n signedMessage: output.signedMessage,\n };\n },\n }),\n\n subscribe(listener) {\n listeners.add(listener);\n let unsubWallet: (() => void) | null = null;\n if (events !== undefined) {\n const unsub = events.on(\"change\", (changes) => {\n // Chain change: the wallet switched cluster. Re-point\n // currentChainId so subsequent signAndSendTransaction routes\n // through it, mirroring `switchChain`'s local-state model.\n if (changes.chains !== undefined) {\n const next =\n changes.chains.find((c) => c === \"solana:mainnet\" || c === \"solana:mainnet-beta\") ??\n changes.chains.find((c) => c.startsWith(SOLANA_PREFIX));\n if (next !== undefined) {\n currentChainId = next;\n }\n }\n\n if (changes.accounts !== undefined) {\n if (changes.accounts.length === 0) {\n listener({ type: \"disconnected\" });\n return;\n }\n // Forward the FULL accounts list; Wallet Standard's\n // change.accounts reflects the wallet's current exposure\n // set. Mirroring it into the pool entry keeps the array in\n // sync with what the wallet actually allows us to sign with,\n // so single-account-exposure wallets (Phantom Solana,\n // MetaMask Snap) don't accumulate stale addresses.\n const chain = currentChain();\n const built = changes.accounts.map((a) => buildAccount(a.address, chain));\n const first = built[0];\n if (first === undefined) {\n return;\n }\n listener({ account: first, accounts: built, type: \"accountChanged\" });\n return;\n }\n\n if (changes.chains !== undefined) {\n notifyAccountChanged();\n }\n });\n unsubWallet = () => {\n unsub();\n };\n }\n return () => {\n listeners.delete(listener);\n unsubWallet?.();\n };\n },\n\n switchChain: (chain) => {\n if (chain.namespace !== \"solana\") {\n throw new Error(\n `SVM adapter received non-Solana chain \"${chain.id}\". Pass a chain with namespace \"solana\".`,\n );\n }\n // The wallet must advertise this cluster; otherwise\n // signAndSendTransaction will reject the chain string later.\n if (!wallet.chains.includes(chain.id)) {\n throw new Error(\n `Wallet ${wallet.name} does not advertise chain \"${chain.id}\". Available: ${wallet.chains.join(\", \")}`,\n );\n }\n currentChainId = chain.id;\n // Synthesise an `accountChanged` so butr's reducer updates the\n // pool entry's chain; the wallet itself has no event to fire here.\n notifyAccountChanged();\n return Promise.resolve();\n },\n };\n};\n\nexport { buildSvmAdapter, slugify };\n","import type { WalletAdapter } from \"@usebutr/core\";\nimport { discoverWalletStandard } from \"@usebutr/wallet-standard-shared\";\n\nimport { buildSvmAdapter } from \"./wallet-standard-adapter\";\n\n/**\n * Subscribe to Solana Wallet Standard announcements.\n * Spec: https://github.com/wallet-standard/wallet-standard\n *\n * Runtime requires the `@wallet-standard/app` package, which is an\n * **optional peer dependency** of butr. EVM-only consumers don't need\n * to install it; discovery will silently skip the SVM side if the\n * module isn't resolvable.\n *\n * Install for SVM auto-discovery:\n *\n * npm install @wallet-standard/app\n *\n * The function returns a synchronous unsubscribe that's safe to call\n * before the dynamic import has resolved.\n *\n * Implementation: the discovery loop (dynamic import, dedupe, register\n * / unregister bridge) lives in `@usebutr/wallet-standard-shared`. This\n * file's only job is to hand the kit a per-wallet builder.\n */\nconst discoverSvmAdapters = (onAdapter: (adapter: WalletAdapter) => void): (() => void) =>\n discoverWalletStandard(onAdapter, (wallet, registerDisconnector) =>\n buildSvmAdapter(wallet, registerDisconnector),\n );\n\nexport { discoverSvmAdapters };\n","import type { ChainBase } from \"@usebutr/core\";\n\n// Solana chain identifiers follow the Wallet Standard convention\n// (`solana:mainnet` / `solana:devnet` / `solana:testnet`) rather than\n// the strict CAIP-2 genesis-hash form (`solana:5eykt…`). Phantom,\n// Solflare, Backpack and the rest of the Wallet Standard ecosystem\n// advertise these shortnames in `wallet.chains`, and the SVM adapter's\n// `switchChain` rejects anything not in that set. The genesis-hash form\n// remains valid CAIP-2 but isn't what real wallets exchange.\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\";\n\n/**\n * SVM's `PlatformDiscoverer`: Solana Wallet Standard announcements. No\n * legacy fallback; Solana wallets standardised on Wallet Standard\n * before butr existed, so there's no `window.solana` injected path\n * worth shimming.\n */\nconst svmDiscoverer: PlatformDiscoverer = {\n platform: \"svm\",\n subscribe: discoverSvmAdapters,\n};\n\nexport { svmDiscoverer };\n"],"mappings":"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"}
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, bytesToBase64 } 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 * Adapt a Solana Wallet Standard `Wallet` object into a butr\n * `WalletAdapter`. Returns `null` if the wallet doesn't advertise any\n * Solana chain or doesn't expose `standard:connect`; both required\n * for butr to do anything useful with it.\n *\n * **Capability gating**\n *\n * - `signMessage` is only present if the wallet advertises\n * `solana:signMessage`. Without it, calling `adapter.signMessage`\n * rejects with a clear error.\n * - `sendTx` / `sendTxToChain` use `solana:signAndSendTransaction`.\n * Without it, those methods reject.\n * - `subscribe` is wired through `standard:events`. Wallets that\n * don't expose events get a no-op unsubscribe.\n *\n * **Known limitations**\n *\n * - `getBalance` returns `{ value: 0n, formatted: \"0\", … }`. Solana\n * balance reads require an RPC connection that's outside butr's\n * scope; consumers wrap their own RPC client.\n * - `getTransactionReceipt` returns `{ status: \"Pending\" }`. Same\n * reason; needs an RPC.\n * - `switchChain` updates butr's view of the current Solana cluster\n * locally and routes subsequent `signAndSendTransaction` calls\n * through the new chain. Wallet Standard has no \"tell the wallet\n * to switch cluster\" RPC; the wallet's own UI controls its global\n * cluster setting, and `signAndSendTransaction` accepts the chain\n * per-call. The chain must be one the wallet advertises in\n * `wallet.chains`; otherwise the call rejects.\n * - `requestAccounts` re-runs `standard:connect`. Some wallets show\n * their account picker again; others (those that \"remember\"\n * authorisations) silently return the existing accounts. butr\n * refreshes the pool entry's `accounts` array either way.\n * - `switchAccount` is intentionally unimplemented. Wallet Standard\n * has no silent \"use address X\" feature; the user picks the active\n * account through the wallet's own UI. `signAndSendTransaction`\n * accepts an `account` input, so consumers needing per-tx address\n * selection can pick one from `accounts` at call time.\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 const signAndSend = async (tx: unknown, account?: { walletAddress: string }): 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: core.currentChainId(),\n transaction: tx,\n });\n if (output === undefined) {\n throw new Error(\"signAndSendTransaction returned no outputs\");\n }\n return bytesToBase64(output.signature);\n };\n\n return {\n ...core,\n capabilities: resolveWalletStandardCapabilities({\n // Deliberately the wallet's whole chain list, not the Solana-only\n // slice: a wallet advertising a single `solana:` cluster alongside\n // other namespaces can still switch clusters from butr's side.\n chainCount: wallet.chains.length,\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 // Wallet Standard exposes no balance feature. Consumers wrap their\n // own RPC client; this default keeps the type signature honoured\n // without lying about a value we don't have.\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 // Wallet Standard has no equivalent of EIP-2255's\n // `wallet_requestPermissions`. The closest we can do is re-run\n // `standard:connect`, which prompts the user on wallets that\n // surface their picker each time and silently returns the\n // existing list on wallets that remember authorisations.\n // butr's runtime calls `getAccounts()` afterwards to refresh\n // the pool entry, so newly-exposed addresses appear either way.\n await core.connect();\n },\n\n sendTx: (tx, account) => signAndSend(tx, account),\n\n sendTxToChain: (tx, _targetChainId, account, cb) => {\n // Solana Wallet Standard's signAndSendTransaction takes the\n // target chain directly. `targetChainId` is the decimal-string\n // form butr uses, but the wallet expects a CAIP-2 chain string.\n // For now we honour the connector's current chain and let\n // consumers route per-chain at a higher level if they need\n // multi-cluster support.\n cb?.();\n return signAndSend(tx, account);\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 * Subscribe to Solana Wallet Standard announcements.\n * Spec: https://github.com/wallet-standard/wallet-standard\n *\n * Runtime requires the `@wallet-standard/app` package, which is an\n * **optional peer dependency** of butr. EVM-only consumers don't need\n * to install it; discovery will silently skip the SVM side if the\n * module isn't resolvable.\n *\n * Install for SVM auto-discovery:\n *\n * npm install @wallet-standard/app\n *\n * The function returns a synchronous unsubscribe that's safe to call\n * before the dynamic import has resolved.\n *\n * Implementation: the discovery loop (dynamic import, dedupe, register\n * / unregister bridge) lives in `@usebutr/wallet-standard-shared`. This\n * file's only job is to hand the kit a per-wallet builder.\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\n// Solana chain identifiers follow the Wallet Standard convention\n// (`solana:mainnet` / `solana:devnet` / `solana:testnet`) rather than\n// the strict CAIP-2 genesis-hash form (`solana:5eykt…`). Phantom,\n// Solflare, Backpack and the rest of the Wallet Standard ecosystem\n// advertise these shortnames in `wallet.chains`, and the SVM adapter's\n// `switchChain` rejects anything not in that set. The genesis-hash form\n// remains valid CAIP-2 but isn't what real wallets exchange.\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 * SVM's `PlatformDiscoverer`: Solana Wallet Standard announcements. No\n * legacy fallback; Solana wallets standardised on Wallet Standard\n * before butr existed, so there's no `window.solana` injected path\n * worth shimming.\n */\nconst svmDiscoverer: PlatformDiscoverer = {\n subscribe: discoverSvmAdapters,\n};\n\nexport { svmDiscoverer };\n"],"mappings":";;;;;;;;AAkBA,MAAM,qCACJ,UAEA,wBAAwB;CACtB,YAAY,MAAM;CAClB,QAAQ,MAAM,SAAS;CACvB,iBAAiB,MAAM,SAAS;CAChC,QAAQ,MAAM,SAAS;CACvB,aAAa,MAAM,SAAS;CAC5B,iBAAiB,MAAM,SAAS;AAClC,CAAC;;;;ACVH,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;AACxB,MAAM,kBAAyC,CAAC,kBAAkB,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CvF,MAAM,mBACJ,QAKA,yBACyB;CACzB,MAAM,OAAO,yBAAyB;EACpC,aAAa;EACb,IAAI,QAAQ,OAAO,OAAO,IAAI;EAC9B,OAAO;EACP,WAAW;EACX,UAAU;EACV,mBAAmB;EACnB;EACA,mBAAmB;EACnB;CACF,CAAC;CACD,IAAI,SAAS,MACX,OAAO;CAGT,MAAM,cAAc,WAAqC,QAAQ,oBAAoB;CACrF,MAAM,gBAAgB,WACpB,QACA,+BACF;CACA,MAAM,SAAS,WAAyC,QAAQ,wBAAwB;CACxF,MAAM,SAAS,WAAgC,QAAQ,eAAe;CAEtE,MAAM,cAAc,OAAO,IAAa,YAAyD;EAC/F,IAAI,kBAAkB,QACpB,MAAM,IAAI,MAAM,UAAU,OAAO,KAAK,kDAAkD;EAE1F,MAAM,YAAY,KAAK,eAAe,OAAO;EAC7C,IAAI,EAAE,cAAc,aAClB,MAAM,IAAI,UAAU,0DAA0D;EAEhF,MAAM,CAAC,UAAU,MAAM,cAAc,uBAAuB;GAC1D,SAAS;GACT,OAAO,KAAK,eAAe;GAC3B,aAAa;EACf,CAAC;EACD,IAAI,WAAW,QACb,MAAM,IAAI,MAAM,4CAA4C;EAE9D,OAAO,cAAc,OAAO,SAAS;CACvC;CAEA,OAAO;EACL,GAAG;EACH,cAAc,kCAAkC;GAI9C,YAAY,OAAO,OAAO;GAC1B,UAAU;IACR,QAAQ,KAAK;IACb,wBAAwB,QAAQ,aAAa;IAC7C,QAAQ,QAAQ,MAAM;IACtB,aAAa,QAAQ,WAAW;IAChC,iBAAiB,QAAQ,MAAM;GACjC;EACF,CAAC;EACD,eAAe;EAKf,kBACE,QAAQ,QAAQ;GACd,UAAU;GACV,WAAW;GACX,QAAQ;GACR,OAAO;EACT,CAAC;EAEH,6BAA6B,QAAQ,QAAQ,EAAE,QAAQ,UAAmB,CAAC;EAE3E,MAAM,kBAAkB;GAQtB,MAAM,KAAK,QAAQ;EACrB;EAEA,SAAS,IAAI,YAAY,YAAY,IAAI,OAAO;EAEhD,gBAAgB,IAAI,gBAAgB,SAAS,OAAO;GAOlD,KAAK;GACL,OAAO,YAAY,IAAI,OAAO;EAChC;EAEA,MAAM,YAAY,KAAK,SAAS;GAC9B,IAAI,gBAAgB,QAClB,MAAM,IAAI,MAAM,UAAU,OAAO,KAAK,uCAAuC;GAE/E,MAAM,CAAC,UAAU,MAAM,YAAY,YAAY;IAC7C,SAAS,KAAK,eAAe,OAAO;IACpC,SAAS;GACX,CAAC;GACD,IAAI,WAAW,QACb,MAAM,IAAI,MAAM,iCAAiC;GAEnD,OAAO;IAAE,WAAW,OAAO;IAAW,eAAe,OAAO;GAAc;EAC5E;EAEA,GAAI,WAAW,SACX,CAAC,IACD,EACE,MAAM,gBAAgB,IAAI,SAAS;GACjC,MAAM,YAAY,KAAK,eAAe,OAAO;GAC7C,IAAI,EAAE,cAAc,aAClB,MAAM,IAAI,UACR,mEACF;GAEF,MAAM,CAAC,UAAU,MAAM,OAAO,gBAAgB;IAC5C,SAAS;IACT,OAAO,KAAK,eAAe;IAC3B,aAAa;GACf,CAAC;GACD,IAAI,WAAW,QACb,MAAM,IAAI,MAAM,qCAAqC;GAEvD,OAAO,OAAO;EAChB,EACF;EAEJ,GAAI,WAAW,SACX,CAAC,IACD,EACE,MAAM,OAAO,OAAO;GAClB,MAAM,CAAC,UAAU,MAAM,OAAO,OAAO,KAAK;GAC1C,IAAI,WAAW,QACb,MAAM,IAAI,MAAM,4BAA4B;GAE9C,OAAO;IACL,SAAS,aAAa,OAAO,QAAQ,SAAS,KAAK,QAAQ,CAAC;IAC5D,WAAW,OAAO;IAClB,eAAe,OAAO;GACxB;EACF,EACF;CACN;AACF;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAM,uBAAuB,cAC3B,uBAAuB,YAAY,QAAQ,yBACzC,gBAAgB,QAAQ,oBAAoB,CAC9C;;;;AC1OF,MAAM,aAAa;CACjB,QAAQ;EAAE,IAAI;EAAiB,MAAM;EAAiB,WAAW;EAAU,WAAW;CAAS;CAC/F,SAAS;EACP,IAAI;EACJ,MAAM;EACN,WAAW;EACX,WAAW;CACb;CACA,SAAS;EACP,IAAI;EACJ,MAAM;EACN,WAAW;EACX,WAAW;CACb;AACF;AAEA,MAAM,kBAA4C,OAAO,OAAO,UAAU;;;;;;;;;;ACf1E,MAAM,gBAAoC,EACxC,WAAW,oBACb"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usebutr/svm",
3
- "version": "0.2.6",
3
+ "version": "1.0.0",
4
4
  "description": "Wallet Standard adapter for butr (Solana / SVM).",
5
5
  "license": "MIT",
6
6
  "author": "Pedro Filho <pedro@filho.me>",
@@ -25,8 +25,8 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@usebutr/core": "0.4.2",
29
- "@usebutr/wallet-standard-shared": "0.2.6"
28
+ "@usebutr/core": "1.0.0",
29
+ "@usebutr/wallet-standard-shared": "0.4.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@wallet-standard/app": "^1.1.1",