@layerswap/wallet-starknet 1.7.0-next.1 → 2.0.0-next.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.
Files changed (43) hide show
  1. package/dist/esm/constants.js +1 -1
  2. package/dist/esm/index.js +27 -33
  3. package/dist/esm/init.js +100 -0
  4. package/dist/esm/service/StarknetConnectionService.js +355 -0
  5. package/dist/esm/service/createStarknetConnection.js +44 -0
  6. package/dist/esm/service/starknetConnectorManager.js +35 -0
  7. package/dist/esm/{starknetWalletStore.js → service/starknetStore.js} +11 -17
  8. package/dist/esm/starknetBalanceProvider.js +5 -3
  9. package/dist/esm/starknetGasProvider.js +3 -2
  10. package/dist/esm/starknetNftProvider.js +1 -1
  11. package/dist/esm/{useStarknetTransfer.js → transferProvider/createStarknetTransfer.js} +3 -3
  12. package/dist/tsconfig.tsbuildinfo +1 -1
  13. package/dist/types/constants.d.ts.map +1 -1
  14. package/dist/types/index.d.ts +11 -9
  15. package/dist/types/index.d.ts.map +1 -1
  16. package/dist/types/init.d.ts +8 -0
  17. package/dist/types/init.d.ts.map +1 -0
  18. package/dist/types/service/StarknetConnectionService.d.ts +56 -0
  19. package/dist/types/service/StarknetConnectionService.d.ts.map +1 -0
  20. package/dist/types/service/createStarknetConnection.d.ts +11 -0
  21. package/dist/types/service/createStarknetConnection.d.ts.map +1 -0
  22. package/dist/types/service/starknetConnectorManager.d.ts +12 -0
  23. package/dist/types/service/starknetConnectorManager.d.ts.map +1 -0
  24. package/dist/types/{starknetWalletStore.d.ts → service/starknetStore.d.ts} +10 -2
  25. package/dist/types/service/starknetStore.d.ts.map +1 -0
  26. package/dist/types/starknetBalanceProvider.d.ts +1 -1
  27. package/dist/types/starknetBalanceProvider.d.ts.map +1 -1
  28. package/dist/types/starknetGasProvider.d.ts +3 -2
  29. package/dist/types/starknetGasProvider.d.ts.map +1 -1
  30. package/dist/types/starknetNftProvider.d.ts +2 -1
  31. package/dist/types/starknetNftProvider.d.ts.map +1 -1
  32. package/dist/types/transferProvider/createStarknetTransfer.d.ts +3 -0
  33. package/dist/types/transferProvider/createStarknetTransfer.d.ts.map +1 -0
  34. package/package.json +8 -8
  35. package/dist/esm/StarknetProvider.js +0 -178
  36. package/dist/esm/useStarknetConnection.js +0 -173
  37. package/dist/types/StarknetProvider.d.ts +0 -7
  38. package/dist/types/StarknetProvider.d.ts.map +0 -1
  39. package/dist/types/starknetWalletStore.d.ts.map +0 -1
  40. package/dist/types/useStarknetConnection.d.ts +0 -16
  41. package/dist/types/useStarknetConnection.d.ts.map +0 -1
  42. package/dist/types/useStarknetTransfer.d.ts +0 -3
  43. package/dist/types/useStarknetTransfer.d.ts.map +0 -1
@@ -1,4 +1,4 @@
1
- import { KnownInternalNames } from "@layerswap/widget/internal";
1
+ import { KnownInternalNames } from "@layerswap/utils";
2
2
  export const name = 'Starknet';
3
3
  export const id = 'starknet';
4
4
  export const starknetNames = [KnownInternalNames.Networks.StarkNetMainnet, KnownInternalNames.Networks.StarkNetSepolia, KnownInternalNames.Networks.StarkNetGoerli];
package/dist/esm/index.js CHANGED
@@ -1,21 +1,29 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import StarknetProviderWrapper from "./StarknetProvider";
3
- import useStarknetConnection from "./useStarknetConnection";
1
+ import { LazyGasProvider } from "@layerswap/widget-types";
2
+ import { KnownInternalNames } from "@layerswap/utils";
4
3
  import { StarknetBalanceProvider } from "./starknetBalanceProvider";
5
- import { LazyGasProvider } from "@layerswap/widget/types";
6
- import { AppSettings, KnownInternalNames } from "@layerswap/widget/internal";
7
4
  import { StarknetNftProvider } from "./starknetNftProvider";
8
- import React from "react";
9
- import { useStarknetTransfer } from "./useStarknetTransfer";
5
+ import { createStarknetTransfer } from "./transferProvider/createStarknetTransfer";
6
+ import { createStarknetConnection } from "./service/createStarknetConnection";
7
+ import { initStarknetProvider } from "./init";
8
+ import { id } from "./constants";
10
9
  const isStarknetNetwork = (name) => KnownInternalNames.Networks.StarkNetMainnet.includes(name) ||
11
10
  KnownInternalNames.Networks.StarkNetGoerli.includes(name) ||
12
11
  KnownInternalNames.Networks.StarkNetSepolia.includes(name);
13
- export { default as useStarknetConnection } from "./useStarknetConnection";
12
+ // The literal id in the return type lets `defineWalletDescriptor` in
13
+ // `@layerswap/wallets` verify it matches the descriptor id at compile time.
14
14
  export function createStarknetProvider(config = {}) {
15
- const { customHook, balanceProviders, gasProviders, nftProviders, transferProviders } = config;
16
- // Use custom hook if provided, otherwise use default
17
- const walletConnectionProvider = customHook || useStarknetConnection;
18
- // Use custom providers if provided, otherwise use defaults
15
+ const { customConnection, balanceProviders, gasProviders, nftProviders, transferProviders, } = config;
16
+ const init = (_ctx) => {
17
+ initStarknetProvider();
18
+ // No-op disposer; init is idempotent across remounts.
19
+ };
20
+ const createConnection = (props) => {
21
+ initStarknetProvider();
22
+ if (customConnection) {
23
+ return customConnection(props);
24
+ }
25
+ return createStarknetConnection(props);
26
+ };
19
27
  const defaultBalanceProviders = [new StarknetBalanceProvider()];
20
28
  const finalBalanceProviders = balanceProviders !== undefined
21
29
  ? (Array.isArray(balanceProviders) ? balanceProviders : [balanceProviders])
@@ -30,34 +38,20 @@ export function createStarknetProvider(config = {}) {
30
38
  const finalNftProviders = nftProviders !== undefined
31
39
  ? (Array.isArray(nftProviders) ? nftProviders : [nftProviders])
32
40
  : defaultNftProviders;
33
- const defaultTransferProviders = [useStarknetTransfer];
41
+ const defaultTransferProviders = [createStarknetTransfer];
34
42
  const finalTransferProviders = transferProviders !== undefined
35
43
  ? (Array.isArray(transferProviders) ? transferProviders : [transferProviders])
36
44
  : defaultTransferProviders;
37
45
  return {
38
- id: "starknet",
39
- wrapper: StarknetProviderWrapper,
40
- walletConnectionProvider,
46
+ id,
47
+ init,
48
+ createConnection,
41
49
  gasProvider: finalGasProviders,
42
50
  balanceProvider: finalBalanceProviders,
43
51
  nftProvider: finalNftProviders,
44
52
  transferProvider: finalTransferProviders,
45
53
  };
46
54
  }
47
- /**
48
- * @deprecated Use createStarknetProvider() instead. This export will be removed in a future version.
49
- * Note: This uses default WalletConnect configuration provided to LayerswapProvider.
50
- */
51
- export const StarknetProvider = {
52
- id: "starknet",
53
- wrapper: ({ children }) => {
54
- return (_jsx(StarknetProviderWrapper, { walletConnectConfigs: AppSettings.WalletConnectConfig, children: children }));
55
- },
56
- walletConnectionProvider: useStarknetConnection,
57
- gasProvider: [
58
- new LazyGasProvider((n) => isStarknetNetwork(n.name), () => import("./starknetGasProvider").then(m => new m.StarknetGasProvider()))
59
- ],
60
- balanceProvider: [new StarknetBalanceProvider()],
61
- nftProvider: [new StarknetNftProvider()],
62
- transferProvider: [useStarknetTransfer],
63
- };
55
+ export { createStarknetConnection } from "./service/createStarknetConnection";
56
+ export { useStarknetStore } from "./service/starknetStore";
57
+ export { starknetConnectorManager } from "./service/starknetConnectorManager";
@@ -0,0 +1,100 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _DiscoveryConnector_wallet, _DiscoveryConnector_store;
13
+ import { Connector, ConnectorNotConnectedError, UserNotConnectedError } from '@starknet-react/core';
14
+ import { starknetConnectorManager } from './service/starknetConnectorManager';
15
+ let _initialized = false;
16
+ let _loadPromise = null;
17
+ class DiscoveryConnector extends Connector {
18
+ constructor(wallet, store) {
19
+ super();
20
+ _DiscoveryConnector_wallet.set(this, void 0);
21
+ _DiscoveryConnector_store.set(this, void 0);
22
+ __classPrivateFieldSet(this, _DiscoveryConnector_wallet, wallet, "f");
23
+ __classPrivateFieldSet(this, _DiscoveryConnector_store, store, "f");
24
+ }
25
+ get id() { return `${__classPrivateFieldGet(this, _DiscoveryConnector_wallet, "f").id}-mobile`; }
26
+ get icon() { return { dark: __classPrivateFieldGet(this, _DiscoveryConnector_wallet, "f").icon, light: __classPrivateFieldGet(this, _DiscoveryConnector_wallet, "f").icon }; }
27
+ get name() { return `${__classPrivateFieldGet(this, _DiscoveryConnector_wallet, "f").name} (mobile)`; }
28
+ available() { return true; }
29
+ connect() {
30
+ window.open(__classPrivateFieldGet(this, _DiscoveryConnector_wallet, "f").downloads[__classPrivateFieldGet(this, _DiscoveryConnector_store, "f")], '_blank');
31
+ return undefined;
32
+ }
33
+ get wallet() { throw new ConnectorNotConnectedError(); }
34
+ disconnect() { throw new UserNotConnectedError(); }
35
+ account() { throw new ConnectorNotConnectedError(); }
36
+ ready() { throw new Error('Method not implemented.'); }
37
+ chainId() { throw new Error('Method not implemented.'); }
38
+ request(_call) {
39
+ throw new Error('Method not implemented.');
40
+ }
41
+ }
42
+ _DiscoveryConnector_wallet = new WeakMap(), _DiscoveryConnector_store = new WeakMap();
43
+ /**
44
+ * One-shot initialization of the Starknet connectors + manager.
45
+ * Safe to call multiple times — subsequent calls are no-ops.
46
+ */
47
+ export function initStarknetProvider() {
48
+ if (_initialized || _loadPromise)
49
+ return;
50
+ if (typeof window === 'undefined')
51
+ return;
52
+ _loadPromise = (async () => {
53
+ // @ts-ignore
54
+ const injectedModule = await import('starknetkit/injected');
55
+ // @ts-ignore
56
+ const webWalletModule = await import('starknetkit/webwallet');
57
+ // @ts-ignore
58
+ const controllerModule = await import('starknetkit/controller');
59
+ const InjectedConnector = injectedModule.InjectedConnector;
60
+ const WebWalletConnector = webWalletModule.WebWalletConnector;
61
+ const ControllerConnector = controllerModule.ControllerConnector;
62
+ const isSafari = typeof window !== 'undefined'
63
+ ? /^((?!chrome|android).)*safari/i.test(navigator.userAgent)
64
+ : false;
65
+ const isAndroid = !!navigator.userAgent.match(/Android/i);
66
+ const isIOS = !!navigator.userAgent.match(/iPhone|iPad|iPod/i);
67
+ const defaultConnectors = [];
68
+ if (!isSafari) {
69
+ defaultConnectors.push(new InjectedConnector({ options: { id: 'argentX' } }));
70
+ defaultConnectors.push(new InjectedConnector({ options: { id: 'keplr' } }));
71
+ defaultConnectors.push(new InjectedConnector({ options: { id: 'braavos' } }));
72
+ defaultConnectors.push(new InjectedConnector({ options: { id: 'xverse' } }));
73
+ }
74
+ if ((isAndroid || isIOS) && !defaultConnectors.some(c => c.id === 'braavos')) {
75
+ const starknet = (await import('@starknet-io/get-starknet-core')).default;
76
+ const discoverWallets = (await starknet.getDiscoveryWallets()).filter(w => {
77
+ return (isAndroid && w.downloads['android']) || (isIOS && w.downloads['ios']);
78
+ });
79
+ if (discoverWallets.length) {
80
+ defaultConnectors.push(...discoverWallets.map(w => new DiscoveryConnector(w, isAndroid ? 'android' : 'ios')));
81
+ }
82
+ }
83
+ defaultConnectors.push(new ControllerConnector());
84
+ defaultConnectors.push(new WebWalletConnector());
85
+ starknetConnectorManager.register(defaultConnectors);
86
+ _initialized = true;
87
+ })();
88
+ _loadPromise.catch((error) => {
89
+ // Reset so the next init call retries; without the log a failed
90
+ // connector import leaves Starknet silently missing from the modal.
91
+ console.error('[layerswap/wallets] Starknet provider initialization failed', error);
92
+ _loadPromise = null;
93
+ });
94
+ }
95
+ /** Visible for tests. Resets singleton init so a fresh init can run. */
96
+ export function _resetStarknetInit() {
97
+ _initialized = false;
98
+ _loadPromise = null;
99
+ starknetConnectorManager.dispose();
100
+ }
@@ -0,0 +1,355 @@
1
+ import { walletIconResolver } from "@layerswap/wallet-core";
2
+ import { name as PROVIDER_NAME, id as PROVIDER_ID } from '../constants';
3
+ import { resolveStarknetWalletIcon } from '../utils';
4
+ import { starknetConnectorManager } from './starknetConnectorManager';
5
+ import { useStarknetStore } from './starknetStore';
6
+ const connectorsConfigs = [
7
+ {
8
+ id: 'braavos',
9
+ name: 'Braavos',
10
+ installLink: 'https://chromewebstore.google.com/detail/braavos-starknet-wallet/jnlgamecbpmbajjfhmmmlhejkemejdma',
11
+ },
12
+ {
13
+ id: 'argentX',
14
+ name: 'Ready X',
15
+ installLink: 'https://chromewebstore.google.com/detail/argent-x-starknet-wallet/dlcobpjiigpikoobohmabehhmhfoodbb',
16
+ },
17
+ {
18
+ id: 'keplr',
19
+ name: 'Keplr',
20
+ installLink: 'https://chromewebstore.google.com/detail/keplr/dmkamcknogkgcdfhhbddcghachkejeap',
21
+ },
22
+ {
23
+ id: 'xverse',
24
+ name: 'Xverse Wallet',
25
+ installLink: 'https://chromewebstore.google.com/detail/xverse-bitcoin-crypto-wal/idnnbdplmphpflfnlkomgpfbpcgelopg',
26
+ },
27
+ ];
28
+ /**
29
+ * Validate the RPC endpoint before handing it to `RpcProvider`. The signing
30
+ * `WalletAccount` is built on top of this provider, so a non-RPC scheme injected
31
+ * via a compromised settings response (e.g. `javascript:`/`data:`) must never
32
+ * reach it. We require a well-formed secure-transport URL rather than pinning a
33
+ * host allowlist, so legitimate network/infra changes don't break resolution.
34
+ */
35
+ function assertSecureRpcUrl(url) {
36
+ if (!url)
37
+ throw new Error('Missing Starknet RPC node_url');
38
+ let parsed;
39
+ try {
40
+ parsed = new URL(url);
41
+ }
42
+ catch {
43
+ throw new Error(`Invalid Starknet RPC URL: ${url}`);
44
+ }
45
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'wss:') {
46
+ throw new Error(`Untrusted Starknet RPC protocol: ${parsed.protocol}`);
47
+ }
48
+ }
49
+ export async function resolveStarknetWallet(props) {
50
+ const { name, connector, network, disconnectWallets, address, withdrawalSupportedNetworks, autofillSupportedNetworks, asSourceSupportedNetworks } = props;
51
+ try {
52
+ const walletChain = network?.chainId;
53
+ assertSecureRpcUrl(network?.rpcUrl);
54
+ const { RpcProvider, WalletAccount } = await import('starknet');
55
+ const rpcProvider = new RpcProvider({ nodeUrl: network.rpcUrl });
56
+ const walletAccount = new WalletAccount({ provider: rpcProvider, walletProvider: connector.wallet, address });
57
+ const accounts = await walletAccount.requestAccounts(true);
58
+ const account = accounts?.[0];
59
+ const configuredName = connectorsConfigs.find(c => c.id === connector.id)?.name;
60
+ const connectorName = configuredName ?? connector.name;
61
+ const wallet = {
62
+ id: connectorName,
63
+ displayName: `${connectorName} - Starknet`,
64
+ address: account,
65
+ addresses: [account],
66
+ chainId: walletChain || '',
67
+ icon: walletIconResolver(address, resolveStarknetWalletIcon({ icon: connector.icon })),
68
+ providerName: name,
69
+ metadata: {
70
+ starknetAccount: walletAccount,
71
+ },
72
+ isActive: true,
73
+ withdrawalSupportedNetworks,
74
+ disconnect: () => disconnectWallets(),
75
+ networkIcon: network?.icon,
76
+ autofillSupportedNetworks,
77
+ asSourceSupportedNetworks,
78
+ };
79
+ return wallet;
80
+ }
81
+ catch (e) {
82
+ console.warn(`Failed to initialize wallet for ${connector.name}:`, e);
83
+ return null;
84
+ }
85
+ }
86
+ function toStarknetChainHex(chainId) {
87
+ if (!chainId)
88
+ return undefined;
89
+ if (chainId.startsWith('0x') || chainId.startsWith('0X'))
90
+ return chainId.toLowerCase();
91
+ let hex = '';
92
+ for (let i = 0; i < chainId.length; i++) {
93
+ hex += chainId.charCodeAt(i).toString(16).padStart(2, '0');
94
+ }
95
+ return `0x${hex}`;
96
+ }
97
+ export class StarknetConnectionService {
98
+ constructor() {
99
+ this._networks = [];
100
+ this._networksKey = '';
101
+ this._restoringStoredWallets = false;
102
+ this._restoreAttempts = 0;
103
+ this._maxRestoreAttempts = 20;
104
+ }
105
+ setNetworks(networks, networkAdapter) {
106
+ const key = networks.map(network => networkAdapter.getId(network)).join('|');
107
+ if (this._networksKey === key)
108
+ return;
109
+ this._networks = networks;
110
+ this._networkAdapter = networkAdapter;
111
+ this._networksKey = key;
112
+ this.requestStoredWalletHydration();
113
+ }
114
+ getStarknetNetwork() {
115
+ const network = this._networks.find(item => this._networkAdapter?.isStarknetNetwork(item));
116
+ if (!network || !this._networkAdapter)
117
+ return undefined;
118
+ return {
119
+ id: this._networkAdapter.getId(network),
120
+ displayName: this._networkAdapter.getDisplayName(network),
121
+ chainId: this._networkAdapter.getChainId(network),
122
+ rpcUrl: this._networkAdapter.getRpcUrls(network)[0],
123
+ icon: this._networkAdapter.getIcon(network),
124
+ };
125
+ }
126
+ getProviderIcon() {
127
+ return this.getStarknetNetwork()?.icon;
128
+ }
129
+ connectorIsAvailable(connector) {
130
+ try {
131
+ return typeof connector.available !== 'function'
132
+ || Boolean(connector.available());
133
+ }
134
+ catch {
135
+ return false;
136
+ }
137
+ }
138
+ hasPendingStoredWallets() {
139
+ const state = useStarknetStore.getState();
140
+ const connectedAddresses = new Set(state.connectedWallets.map(wallet => wallet.address.toLowerCase()));
141
+ return Object.values(state.starknetAccounts || {}).some(address => !connectedAddresses.has(address.toLowerCase()));
142
+ }
143
+ /**
144
+ * Drops persisted Starknet accounts that we've repeatedly failed to
145
+ * resolve back to a connected wallet (e.g. the user switched accounts in
146
+ * the wallet, or the stored connector is no longer available). Without
147
+ * this they would stay "pending" forever and keep the retry loop alive.
148
+ */
149
+ clearStalePendingWallets() {
150
+ const state = useStarknetStore.getState();
151
+ const connectedAddresses = new Set(state.connectedWallets.map(wallet => wallet.address.toLowerCase()));
152
+ const stale = Object.values(state.starknetAccounts || {}).filter(address => !connectedAddresses.has(address.toLowerCase()));
153
+ for (const address of stale)
154
+ state.removeAccount(address);
155
+ }
156
+ requestStoredWalletHydration() {
157
+ if (typeof window === 'undefined' || this._restoreTimer || this._restoringStoredWallets)
158
+ return;
159
+ // Fresh trigger (e.g. networks just changed): allow a new bounded run.
160
+ this._restoreAttempts = 0;
161
+ const attemptHydration = async () => {
162
+ this._restoreTimer = undefined;
163
+ // Unconditional re-entrancy guard at the top: if a hydration is already
164
+ // in flight (e.g. a fresh setNetworks() scheduled another run), bail so
165
+ // we never run two hydrateStoredWallets() concurrently and duplicate entries.
166
+ if (this._restoringStoredWallets)
167
+ return;
168
+ if (!this.hasPendingStoredWallets()) {
169
+ this._restoreAttempts = 0;
170
+ return;
171
+ }
172
+ if (this.getStarknetNetwork()) {
173
+ this._restoringStoredWallets = true;
174
+ try {
175
+ await this.hydrateStoredWallets();
176
+ }
177
+ finally {
178
+ this._restoringStoredWallets = false;
179
+ }
180
+ }
181
+ if (!this.hasPendingStoredWallets()) {
182
+ this._restoreAttempts = 0;
183
+ return;
184
+ }
185
+ // Still pending after this attempt. Bound the retry so a stale or
186
+ // unresolvable persisted account can't wake the page every 500ms
187
+ // for its entire lifetime. After the cap, drop the stale entries.
188
+ this._restoreAttempts += 1;
189
+ if (this._restoreAttempts >= this._maxRestoreAttempts) {
190
+ this.clearStalePendingWallets();
191
+ this._restoreAttempts = 0;
192
+ return;
193
+ }
194
+ this._restoreTimer = window.setTimeout(() => {
195
+ void attemptHydration();
196
+ }, 500);
197
+ };
198
+ this._restoreTimer = window.setTimeout(() => {
199
+ void attemptHydration();
200
+ }, 0);
201
+ }
202
+ dispose() {
203
+ if (this._restoreTimer) {
204
+ window.clearTimeout(this._restoreTimer);
205
+ this._restoreTimer = undefined;
206
+ }
207
+ this._restoringStoredWallets = false;
208
+ }
209
+ getAvailableConnectors() {
210
+ const connectors = useStarknetStore.getState().connectors;
211
+ return connectors.map(connector => {
212
+ const config = connectorsConfigs.find(c => c.id === connector.id);
213
+ const displayName = config?.name ?? connector.name;
214
+ const realConnector = starknetConnectorManager.getConnector(connector.id);
215
+ let isInjectedAndAvailable = false;
216
+ try {
217
+ isInjectedAndAvailable = !!config
218
+ && typeof realConnector?.available === 'function'
219
+ && realConnector.available();
220
+ }
221
+ catch {
222
+ isInjectedAndAvailable = false;
223
+ }
224
+ return {
225
+ name: displayName,
226
+ id: connector.id,
227
+ icon: resolveStarknetWalletIcon({ icon: connector.icon }),
228
+ type: isInjectedAndAvailable ? 'injected' : 'other',
229
+ installUrl: config?.installLink,
230
+ extensionNotFound: !!config?.installLink && !isInjectedAndAvailable,
231
+ providerName: displayName,
232
+ };
233
+ });
234
+ }
235
+ async disconnectWallets(_connectorName, address) {
236
+ try {
237
+ await starknetConnectorManager.disconnectAll();
238
+ if (address)
239
+ useStarknetStore.getState().removeAccount(address);
240
+ }
241
+ catch (e) {
242
+ // Disconnect is best-effort — log but do not rethrow.
243
+ const msg = e instanceof Error ? e.message : String(e);
244
+ console.error(`[Starknet] Failed to disconnect wallet: ${msg}`);
245
+ }
246
+ }
247
+ async connectWallet({ connector }) {
248
+ const starknetConnector = starknetConnectorManager.getConnector(connector.id);
249
+ if (!starknetConnector)
250
+ throw new Error('Connector not found');
251
+ let result = await starknetConnector.connect({});
252
+ const starknetNetwork = this.getStarknetNetwork();
253
+ if (!starknetNetwork)
254
+ throw new Error('Starknet network not found');
255
+ const walletChain = `0x${result?.chainId?.toString(16)}`;
256
+ const expectedChain = toStarknetChainHex(String(starknetNetwork.chainId ?? ''));
257
+ const wrongChain = !!expectedChain && walletChain !== expectedChain;
258
+ const networkDisplayName = starknetNetwork.displayName || starknetNetwork.id;
259
+ if (result?.account && wrongChain) {
260
+ const wallet = starknetConnector?._wallet || starknetConnector?.wallet;
261
+ if (wallet?.request) {
262
+ try {
263
+ await wallet.request({
264
+ type: 'wallet_switchStarknetChain',
265
+ params: { chainId: expectedChain },
266
+ });
267
+ result = await starknetConnector.connect({});
268
+ }
269
+ catch (switchError) {
270
+ console.log('Chain switch failed:', switchError);
271
+ await this.disconnectWallets(connector?.name, result?.account);
272
+ throw new Error(`Failed to switch network. Please switch manually to ${networkDisplayName} in your wallet.`);
273
+ }
274
+ }
275
+ else {
276
+ await this.disconnectWallets(connector?.name, result?.account);
277
+ throw new Error(`Please switch the network in your wallet to ${networkDisplayName} and connect again.`);
278
+ }
279
+ }
280
+ if (!result?.account)
281
+ return undefined;
282
+ const store = useStarknetStore.getState();
283
+ const resolvedWallet = await resolveStarknetWallet({
284
+ name: PROVIDER_NAME,
285
+ connector: starknetConnector,
286
+ network: starknetNetwork,
287
+ disconnectWallets: () => this.disconnectWallets(starknetConnector.id, result?.account),
288
+ address: result.account,
289
+ withdrawalSupportedNetworks: this.getSupportedNetworks(),
290
+ autofillSupportedNetworks: this.getSupportedNetworks(),
291
+ asSourceSupportedNetworks: this.getSupportedNetworks(),
292
+ });
293
+ store.addAccount(starknetConnector.id, result.account);
294
+ if (resolvedWallet) {
295
+ store.connectWallet(resolvedWallet);
296
+ store.setActiveWallet(resolvedWallet.address);
297
+ return resolvedWallet;
298
+ }
299
+ return undefined;
300
+ }
301
+ async switchAccount(_wallet, address) {
302
+ useStarknetStore.getState().setActiveWallet(address);
303
+ }
304
+ async hydrateStoredWallets() {
305
+ const store = useStarknetStore.getState();
306
+ const starknetAccounts = store.starknetAccounts || {};
307
+ if (Object.keys(starknetAccounts).length === 0)
308
+ return;
309
+ const starknetNetwork = this.getStarknetNetwork();
310
+ if (!starknetNetwork)
311
+ return;
312
+ const connectors = starknetConnectorManager.getConnectors();
313
+ for (const connector of connectors) {
314
+ const address = starknetAccounts[connector.id];
315
+ if (!address || !this.connectorIsAvailable(connector))
316
+ continue;
317
+ const wallet = await resolveStarknetWallet({
318
+ name: PROVIDER_NAME,
319
+ connector,
320
+ network: starknetNetwork,
321
+ disconnectWallets: () => starknetConnectorManager.disconnectAll().then(() => useStarknetStore.getState().removeAccount(address)),
322
+ withdrawalSupportedNetworks: this.getSupportedNetworks(),
323
+ autofillSupportedNetworks: this.getSupportedNetworks(),
324
+ asSourceSupportedNetworks: this.getSupportedNetworks(),
325
+ address,
326
+ });
327
+ if (wallet?.address) {
328
+ useStarknetStore.getState().connectWallet(wallet);
329
+ }
330
+ }
331
+ }
332
+ buildProvider() {
333
+ const { connectedWallets, activeWalletAddress } = useStarknetStore.getState();
334
+ const activeWallet = connectedWallets.find(wallet => wallet.address === activeWalletAddress);
335
+ return {
336
+ connectWallet: this.connectWallet.bind(this),
337
+ switchAccount: this.switchAccount.bind(this),
338
+ connectedWallets,
339
+ activeWallet,
340
+ withdrawalSupportedNetworks: this.getSupportedNetworks(),
341
+ autofillSupportedNetworks: this.getSupportedNetworks(),
342
+ asSourceSupportedNetworks: this.getSupportedNetworks(),
343
+ availableConnectors: this.getAvailableConnectors(),
344
+ name: PROVIDER_NAME,
345
+ id: PROVIDER_ID,
346
+ providerIcon: this.getProviderIcon(),
347
+ ready: useStarknetStore.getState().ready,
348
+ };
349
+ }
350
+ getSupportedNetworks() {
351
+ return this._networkAdapter
352
+ ? this._networks.filter(network => this._networkAdapter?.isStarknetNetwork(network)).map(network => this._networkAdapter.getId(network))
353
+ : [];
354
+ }
355
+ }
@@ -0,0 +1,44 @@
1
+ import { createMemoizedConnectionStore } from "@layerswap/wallet-core";
2
+ import { createStarknetTransfer } from '../transferProvider/createStarknetTransfer';
3
+ import { StarknetConnectionService } from './StarknetConnectionService';
4
+ import { useStarknetStore } from './starknetStore';
5
+ /**
6
+ * Vanilla external-store factory for the Starknet wallet connection. Replaces the
7
+ * old `useStarknetConnection` hook.
8
+ */
9
+ export function createStarknetConnection(initialProps, options = {}) {
10
+ const { extraMultiStepHandlers = [] } = options;
11
+ let networks = initialProps.networks;
12
+ let networkAdapter = initialProps.networkAdapter;
13
+ const starknetConnectionService = new StarknetConnectionService();
14
+ starknetConnectionService.setNetworks(networks, networkAdapter);
15
+ const transferProvider = createStarknetTransfer();
16
+ const transfer = transferProvider.executeTransfer;
17
+ return createMemoizedConnectionStore({
18
+ computeInputs: () => {
19
+ const state = useStarknetStore.getState();
20
+ return {
21
+ connectedWallets: state.connectedWallets,
22
+ activeWalletAddress: state.activeWalletAddress,
23
+ connectors: state.connectors,
24
+ ready: state.ready,
25
+ networks,
26
+ };
27
+ },
28
+ buildSnapshot: () => ({
29
+ ...starknetConnectionService.buildProvider(),
30
+ transfer,
31
+ multiStepHandlers: extraMultiStepHandlers,
32
+ }),
33
+ subscribe: sync => [
34
+ useStarknetStore.subscribe(sync),
35
+ ],
36
+ onUpdateProps: nextProps => {
37
+ networks = nextProps.networks;
38
+ networkAdapter = nextProps.networkAdapter;
39
+ starknetConnectionService.setNetworks(networks, networkAdapter);
40
+ },
41
+ // This store owns the module-level Starknet hydration lifecycle.
42
+ onDestroy: () => starknetConnectionService.dispose(),
43
+ });
44
+ }
@@ -0,0 +1,35 @@
1
+ import { useStarknetStore } from './starknetStore';
2
+ class StarknetConnectorManager {
3
+ constructor() {
4
+ this._connectors = [];
5
+ }
6
+ register(connectors) {
7
+ this._connectors = connectors;
8
+ const snapshots = connectors.map(c => ({
9
+ id: c.id,
10
+ name: c.name,
11
+ icon: typeof c.icon === 'string' ? c.icon : (c.icon?.dark || ''),
12
+ }));
13
+ useStarknetStore.getState()._setConnectors(snapshots);
14
+ }
15
+ getConnectors() {
16
+ return this._connectors;
17
+ }
18
+ getConnector(id) {
19
+ return this._connectors.find(c => c.id === id);
20
+ }
21
+ async disconnectAll() {
22
+ for (const connector of this._connectors) {
23
+ try {
24
+ if (typeof connector.available === 'function' && !connector.available())
25
+ continue;
26
+ await connector.disconnect();
27
+ }
28
+ catch { /* swallow */ }
29
+ }
30
+ }
31
+ dispose() {
32
+ this._connectors = [];
33
+ }
34
+ }
35
+ export const starknetConnectorManager = new StarknetConnectorManager();