The useWalletStore Pinia store eagerly imports both wallet backends at module level:
- WalletConnect (
use-wallet-connect.ts): imports@reown/appkit+ethers— ~2 MB - Local Bridge (
use-injected-wallet.ts): importsethers+ cross-importssupportedNetworksfrom WC — ~300 KB wallet-constants.ts: importsformatUnitsfromethers— pulls ethers into a constants file
The store is on the startup path via use-logout.ts and account.ts, both of which only use disconnect. This means ~2.5 MB of wallet libraries load on every page visit, even though most users never use the wallet feature during a session.
Goal: Make the store a lightweight facade. Neither WalletConnect nor the local bridge should load until the user explicitly connects a wallet.
Heavy (must be lazy-loaded):
use-wallet-connect.ts—@reown/appkit-adapter-ethers,@reown/appkit/networks,@reown/appkit/vue,ethersuse-injected-wallet.ts—ethers(BrowserProvider, getAddress),supportedNetworksfrom WC module
Lightweight (stay eager in the store):
useUnifiedProviders— no ethers, EIP-6963 provider discoveryuseWalletProxy— no ethers, Electron IPC bridgeuseTransactionManager— only type imports from ethersuseTradeApi— API calls onlytransaction-helpers.ts— pure validation, no ethersuseWalletHelper— chain ID conversions (currently pulls in WC forEIP155string — fix below)
File: frontend/app/src/modules/onchain/wallet-constants.ts
-
Add
EIP155constant (move fromuse-wallet-connect.ts):export const EIP155 = 'eip155';
-
Add
SUPPORTED_WALLET_CHAIN_IDS(mirrorssupportedNetworkschain IDs — mainnet, base, arbitrum, optimism, bsc, gnosis, polygon, scroll):export const SUPPORTED_WALLET_CHAIN_IDS = [1, 8453, 42161, 10, 56, 100, 137, 534352] as const;
-
Replace
formatUnitsfrom ethers with a nativeformatWeihelper. The function only formats wei (18 decimals) to a decimal string — pure bigint arithmetic:function formatWei(value: bigint): string { const DIVISOR = 10n ** 18n; const integerPart = value / DIVISOR; const remainder = value % DIVISOR; if (remainder === 0n) return integerPart.toString(); const fractionalPart = remainder.toString().padStart(18, '0').replace(/0+$/, ''); return `${integerPart}.${fractionalPart}`; }
Update
calculateGasFeeto useformatWeiinstead offormatUnits. Remove theethersimport.
File: frontend/app/src/modules/onchain/use-wallet-helper.ts
- Change:
import { EIP155 } from './wallet-connect/use-wallet-connect' - To:
import { EIP155 } from './wallet-constants'
This breaks the transitive dependency on the WC module.
File: frontend/app/src/modules/onchain/wallet-connect/use-wallet-connect.ts
- Remove
export const EIP155 = 'eip155'(now inwallet-constants.ts) - Import
EIP155from../wallet-constantsfor internal use supportedNetworksstays exported (consumed via dynamic import)
File: frontend/app/src/modules/onchain/wallet-bridge/use-injected-wallet.ts
- Remove:
import { supportedNetworks } from '../wallet-connect/use-wallet-connect'(line 8) - In
switchNetwork()error-4902 handler (line 256), use dynamic import:This path only fires when a chain needs to be added to the wallet — a rare edge case.if (error.code === 4902) { const { supportedNetworks } = await import('../wallet-connect/use-wallet-connect'); const network = supportedNetworks.find(item => BigInt(item.id) === chainId); // ... rest unchanged }
After this change, use-injected-wallet.ts only imports ethers as its heavy dependency. It no longer pulls in @reown/appkit.
File: frontend/app/src/modules/onchain/use-wallet-store.ts
- import { useInjectedWallet } from './wallet-bridge/use-injected-wallet';
- import { supportedNetworks, useWalletConnect } from './wallet-connect/use-wallet-connect';
+ import { SUPPORTED_WALLET_CHAIN_IDS } from './wallet-constants';- const walletConnect = useWalletConnect();
- const injectedWallet = useInjectedWallet();// Lazy backend instances — loaded on first use
type WalletConnectInstance = ReturnType<typeof import('./wallet-connect/use-wallet-connect').useWalletConnect>;
type InjectedWalletInstance = ReturnType<typeof import('./wallet-bridge/use-injected-wallet').useInjectedWallet>;
let walletConnectInstance: WalletConnectInstance | undefined;
let injectedWalletInstance: InjectedWalletInstance | undefined;
// Local ref to mirror injectedWallet.isConnecting (since injected wallet may not be loaded)
const isConnecting = ref<boolean>(false);
async function getWalletConnect(): Promise<WalletConnectInstance> {
if (!walletConnectInstance) {
const { useWalletConnect } = await import('./wallet-connect/use-wallet-connect');
walletConnectInstance = useWalletConnect();
// Set up state sync watcher (moved from eager watcher)
watch(
[walletConnectInstance.connected, walletConnectInstance.connectedAddress,
walletConnectInstance.connectedChainId, walletConnectInstance.supportedChainIds],
() => {
if (get(walletMode) === WALLET_MODES.WALLET_CONNECT)
syncWalletState();
},
);
}
return walletConnectInstance;
}
async function getInjectedWallet(): Promise<InjectedWalletInstance> {
if (!injectedWalletInstance) {
const { useInjectedWallet } = await import('./wallet-bridge/use-injected-wallet');
injectedWalletInstance = useInjectedWallet();
// Mirror isConnecting into local ref
watch(injectedWalletInstance.isConnecting, (v) => { set(isConnecting, v); });
// Set up state sync watcher (moved from eager watcher)
watch(
[injectedWalletInstance.connected, injectedWalletInstance.connectedAddress,
injectedWalletInstance.connectedChainId],
() => {
if (get(walletMode) === WALLET_MODES.LOCAL_BRIDGE)
syncWalletState();
},
);
}
return injectedWalletInstance;
}const syncWalletState = (): void => {
if (get(walletMode) === WALLET_MODES.WALLET_CONNECT) {
if (!walletConnectInstance) return; // Not loaded yet — nothing to sync
set(connected, get(walletConnectInstance.connected));
set(connectedAddress, get(walletConnectInstance.connectedAddress));
set(connectedChainId, get(walletConnectInstance.connectedChainId));
set(supportedChainIds, get(walletConnectInstance.supportedChainIds));
}
else {
if (!injectedWalletInstance) return; // Not loaded yet — nothing to sync
set(connected, get(injectedWalletInstance.connected));
set(connectedAddress, get(injectedWalletInstance.connectedAddress));
set(connectedChainId, get(injectedWalletInstance.connectedChainId));
set(supportedChainIds, []);
}
};// Before: supportedNetworks.map(network => Number(network.id))
// After:
const supportedChainsIdForConnectedAccount = computed<number[]>(() => {
const chainIds = get(supportedChainIds);
if (chainIds.length === 0 || get(walletMode) === WALLET_MODES.LOCAL_BRIDGE) {
return [...SUPPORTED_WALLET_CHAIN_IDS];
}
return chainIds.map(item => getChainIdFromNamespace(item));
});const getBrowserProvider = (): BrowserProvider => {
if (get(walletMode) === WALLET_MODES.LOCAL_BRIDGE) {
assert(injectedWalletInstance, 'Injected wallet not initialized');
return injectedWalletInstance.getBrowserProvider();
}
assert(walletConnectInstance, 'WalletConnect not initialized');
return walletConnectInstance.getBrowserProvider();
};This is safe: getBrowserProvider is only called after connect() succeeds, which always loads the backend first.
const connect = async (): Promise<void> => {
if (get(walletMode) === WALLET_MODES.LOCAL_BRIDGE) {
try {
if (get(isPackaged)) {
await walletProxy.setupProxy();
}
const providerSelected = await unifiedProviders.checkIfSelectedProvider();
const iw = await getInjectedWallet(); // <-- lazy load
if (!providerSelected) {
await unifiedProviders.detectProviders();
const providers = get(unifiedProviders.availableProviders);
if (providers.length === 0) {
throw new Error(WALLET_ERRORS.NO_PROVIDERS);
}
else if (providers.length === 1) {
await unifiedProviders.selectProvider(providers[0].info.uuid);
await iw.connectToSelectedProvider();
}
else {
set(unifiedProviders.showProviderSelection, true);
}
}
else {
await iw.connectToSelectedProvider();
}
}
catch (error) {
logger.error(WALLET_ERRORS.CONNECTION_FAILED, error);
throw error;
}
}
else {
const wc = await getWalletConnect(); // <-- lazy load
await wc.connect();
}
};const disconnect = async (): Promise<void> => {
set(isDisconnecting, true);
try {
if (get(walletMode) === WALLET_MODES.LOCAL_BRIDGE) {
if (injectedWalletInstance) {
await injectedWalletInstance.disconnect();
}
unifiedProviders.clearProvider();
}
else {
if (walletConnectInstance) {
await walletConnectInstance.disconnect();
}
}
resetState();
}
finally {
set(isDisconnecting, false);
}
};If the backend was never loaded (user never connected), disconnect() is a no-op — correct behavior.
const switchNetwork = async (chainId: bigint): Promise<void> => {
if (get(walletMode) === WALLET_MODES.LOCAL_BRIDGE) {
const iw = await getInjectedWallet();
await iw.switchNetwork(chainId);
}
else {
const wc = await getWalletConnect();
await wc.switchNetwork(chainId);
}
};// In sendTransaction, replace:
// await walletConnect.checkWalletConnection();
// With:
if (get(walletMode) === WALLET_MODES.WALLET_CONNECT) {
const wc = await getWalletConnect();
await wc.checkWalletConnection();
}// Before: preparing: logicOr(preparing, injectedWallet.isConnecting)
// After:
preparing: logicOr(preparing, isConnecting),Remove the two watcher blocks at lines 268-279 (WC state sync and injected state sync). These are now set up lazily inside getWalletConnect() and getInjectedWallet().
Keep the watch(walletMode, ...) watcher (lines 259-265) — it calls disconnect() and syncWalletState() which are both nil-safe now.
| File | Change |
|---|---|
wallet-constants.ts |
Add EIP155, SUPPORTED_WALLET_CHAIN_IDS, formatWei; remove ethers import |
use-wallet-helper.ts |
Import EIP155 from wallet-constants instead of WC module |
use-wallet-connect.ts |
Remove EIP155 export, import from wallet-constants |
use-injected-wallet.ts |
Dynamic import for supportedNetworks in error-4902 path |
use-wallet-store.ts |
Lazy backends, nil-safe sync/disconnect, local isConnecting ref |
No changes needed to account.ts or use-logout.ts — they only use disconnect, and the store itself is now lightweight.
- Build:
cd frontend && pnpm run build— verify wallet-connect/ethers chunks are no longer in the initial dependency graph (they become async chunks) - Type check:
cd frontend && pnpm run typecheck - Unit tests:
cd frontend && pnpm run test:unit - Manual dev test (
cd frontend && pnpm run dev:web):- App loads without wallet chunks in Network tab
- Navigate to send/trade page → wallet chunks load on demand
- WalletConnect flow: connect, switch network, send tx, disconnect
- Local bridge flow: connect, switch network, send tx, disconnect
- Logout works without errors (disconnect is nil-safe)
- Mode switching between WC and local bridge works