Once the sprint finally slowed down, I went back through the code for a multi-chain wallet integration. The project needed wallet connections on Ethereum, Polygon, BSC, and Solana. On paper it sounded like adding a few compatibility branches. In practice, each chain brought its own wallet behavior, SDK assumptions, and failure modes.

The first thing I had to pin down was network metadata. Chain IDs, native currency symbols, RPC endpoints, and block explorers all need to be handled consistently before the wallet layer can behave predictably.

this.networkConfigs = {
    ethereum: {
        chainId: '0x1', // 1
        chainName: 'Ethereum Mainnet',
        nativeCurrency: {
            name: 'Ethereum',
            symbol: 'ETH',
            decimals: 18
        },
        rpcUrls: ['https://eth-mainnet.public.blastapi.io'],
        blockExplorerUrls: ['https://etherscan.io']
    },
    polygon: {
        chainId: '0x89', // 137
        chainName: 'Polygon Mainnet',
        nativeCurrency: {
            name: 'MATIC',
            symbol: 'MATIC',
            decimals: 18
        },
        rpcUrls: ['https://polygon-rpc.com'],
        blockExplorerUrls: ['https://polygonscan.com']
    },
    bsc: {
        chainId: '0x38', // 56
        chainName: 'BNB Smart Chain',
        nativeCurrency: {
            name: 'BNB',
            symbol: 'BNB',
            decimals: 18
        },
        rpcUrls: ['https://bsc-dataseed.binance.org'],
        blockExplorerUrls: ['https://bscscan.com']
    }
}

The bigger lesson is that multi-chain is not the same as supporting multiple wallets. The interaction model changes with the chain, and sometimes the SDK's whole mental model changes too. EVM chains can share a lot of logic through Web3.js. Solana is a different system: provider access, connection flow, PublicKey construction, and even network latency and stability all shape the experience.

// Initialize the corresponding connection based on chain type
if (savedConnection.chain === 'solana') {
    // Create connection using public Solana devnet RPC endpoint
    this.solanaConnection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
} else if (['ethereum', 'polygon', 'bsc'].includes(savedConnection.chain)) {
    // EVM chains: detect providers and create Web3 instance
    if (window.ethereum) {
        this.web3 = new Web3(window.ethereum);
        this.provider = window.ethereum;
    } else if (window.okxwallet) {
        this.web3 = new Web3(window.okxwallet);
        this.provider = window.okxwallet;
    } else if (window.okexchain) {
        this.web3 = new Web3(window.okexchain);
        this.provider = window.okexchain;
    }
}

Wallet choice also decides a lot about user experience. MetaMask is the default assumption, but you cannot assume every user has it. OKX Wallet, Phantom, and a pile of browser extensions are all competing for the same users. Many wallets inject themselves differently, and debugging that is painful.

On EVM chains, Web3.js can cover a lot of ground across MetaMask, OKX Wallet, and similar providers. On Solana, you need @solana/web3.js separately, and connection setup, permission requests, and public key structure are all different.

// EVM chain (Ethereum, Polygon, BSC) logic
if (chain === 'ethereum' || chain === 'polygon' || chain === 'bsc') {
    let provider = null; // Temporary variable for detected provider
    // Prefer common injections: MetaMask (window.ethereum)
    if (window.ethereum) {
        provider = window.ethereum;
    } else if (window.okxwallet) {
        // OKX Wallet
        provider = window.okxwallet;
    } else if (window.okexchain) {
        // OKExChain
        provider = window.okexchain;
    }
    if (provider) {
        // Set and initialize Web3 instance
        this.provider = provider;
        this.web3 = new Web3(provider);
        // Request user authorization and get accounts
        const accounts = await provider.request({ method: 'eth_requestAccounts' });
        this.account = accounts[0]; // Take the first account
        return { success: true, account: this.account };
    } else {
        // No EVM-compatible provider detected
        throw new Error('Ethereum provider not found. Please install MetaMask, OKX Wallet or another compatible wallet.');
    }

// Solana chain logic
} else if (chain === 'solana') {
    let solProvider = null; // Temporary variable for Solana provider
    if (window.solana) {
        solProvider = window.solana;
    } else if (window.okxwallet && window.okxwallet.sol) {
        solProvider = window.okxwallet.sol;
    }
    if (solProvider) {
        // Create Solana connection (devnet)
        this.solanaConnection = new Connection('https://api.devnet.solana.com', 'confirmed');
        // Request connection and get publicKey
        const connection = await solProvider.connect();
        this.account = connection.publicKey.toString();
        return { success: true, account: this.account };
    } else {
        // No Solana wallet detected
        throw new Error('Solana provider not found. Please install Phantom wallet or OKX Wallet.');
    }
} else {
    // Unsupported blockchain type
    throw new Error(`Unsupported blockchain: ${chain}`);
}

Balance checks look like a small step until units and symbols get involved. Solana uses lamports, EVM chains use wei, and the display layer should not have to care. We wrapped formatting in the service layer and returned balance plus symbol to the frontend component. It looks ordinary from the outside, but there are plenty of details underneath.

async getBalance() {
  if (this.selectedChain === 'solana') {
    const lamports = await this.solanaConnection.getBalance(new PublicKey(this.account));
    return { balance: lamports / 1e9, symbol: 'SOL' };
  } else {
    const wei = await this.web3.eth.getBalance(this.account);
    return { balance: this.web3.utils.fromWei(wei), symbol: 'ETH/MATIC/BNB' };
  }
}

Wallet switching and chain switching are separate problems. A user can switch MetaMask to the wrong network, and if you do not prompt or switch them back, later calls will fail.

async switchNetwork(chainName) {
  const networkConfig = this.networkConfigs[chainName];
  try {
    await this.provider.request({
      method: 'wallet_switchEthereumChain',
      params: [{ chainId: networkConfig.chainId }],
    });
  } catch (e) {
    if (e.code === 4902) {
      await this.provider.request({
        method: 'wallet_addEthereumChain',
        params: [networkConfig],
      });
    }
  }
}

If you are building a Web3 project, do not go ALL IN on every chain from day one. First understand how the target chains differ, then look at your user base and pick the main chain to support. Start with one chain, separate chain state, wallet, account, and provider cleanly, and expansion will be much smoother. Not every project needs every chain connected. But once you do it, expect to deal with each chain's own personality.

Multi-chain is not a trend anymore. It is the default threshold. The door just opens with more complexity than it looks.

Supporting code

// Import the web3 service that manages wallet connection and state
import web3Service from '@/services/web3Service.js';
export default {
    name: 'WalletConnector',
    data() {
        return {
            showDialog: false,        // Whether to show the connect wallet dialog
            showNetworks: false,      // Whether to show the network dropdown
            showAccountMenu: false,   // Whether to show the account menu
            selectedChain: 'solana',  // Currently selected blockchain, default Solana
            detectedWallets: [],      // Locally detected wallets
            isConnected: false,       // Whether a wallet is connected
            connectedAccount: '',     // Connected account address
            connectedChain: '',       // Connected chain identifier
            errorMessage: '',         // Error message
            loading: false,           // Loading state for async operations
            // Supported networks and icons
            networks: [
                { id: 'ethereum', name: 'Ethereum', icon: '/images/eth.png' },
                { id: 'solana', name: 'Solana', icon: '/images/solana.svg' },
                { id: 'polygon', name: 'Polygon', icon: '/images/Polygon.png' },
                { id: 'bsc', name: 'BNB Chain', icon: '/images/BNB Chain.png' }
            ],
            // Supported wallets and their chain configuration
            wallets: [
                { id: 'rainbow', name: 'Rainbow', icon: '/images/2.svg', chains: ['ethereum', 'polygon'] },
                { id: 'metamask', name: 'MetaMask', icon: '/images/3.svg', chains: ['ethereum', 'polygon', 'bsc'] },
                { id: 'walletconnect', name: 'WalletConnect', icon: '/images/5.svg', chains: ['ethereum', 'polygon', 'bsc'] },
                { id: 'phantom', name: 'Phantom', icon: '/images/8.svg', chains: ['solana'] },
                { id: 'okx', name: 'OKX Wallet', icon: '/images/1.png', chains: ['ethereum', 'polygon', 'bsc', 'solana'] }
            ]
        }
    },
    computed: {
        // Filter recommended wallets for the current chain that have not been detected
        filteredWallets() {
            const detectedIds = this.detectedWallets.map(w => w.id); // Detected wallet IDs
            return this.wallets
                .filter(wallet => wallet.chains.includes(this.selectedChain)) // Supports the current chain
                .filter(wallet => !detectedIds.includes(wallet.id));         // Exclude installed wallets
        }
    },
    mounted() {
        this.detectInstalledWallets();  // Detect local wallets after mount
        this.checkSavedConnection();    // Restore saved connection state
        document.addEventListener('click', this.handleClickOutside);
    },
    beforeUnmount() {
        document.removeEventListener('click', this.handleClickOutside);
    },
    methods: {
        // Close the account menu when clicking outside
        handleClickOutside(event) {
            const isInside = event.target.closest('.connected-btn');
            if (this.showAccountMenu && !isInside) {
                this.showAccountMenu = false;
            }
        },
        // Check for saved connection info and restore it if present
        async checkSavedConnection() {
            const saved = web3Service.getSavedConnection();
            if (saved) {
                this.isConnected = true;
                this.connectedAccount = saved.account;
                this.connectedChain = saved.chain;
                // Notify the parent component
                this.$emit('wallet-connected', {
                    wallet: saved.wallet,
                    chain: saved.chain,
                    account: saved.account,
                    chainId: saved.chainId
                });
            }
        },
        // Detect locally installed wallet providers
        detectInstalledWallets() {
            this.detectedWallets = [];
            // MetaMask
            if (window.ethereum && window.ethereum.isMetaMask) {
                this.detectedWallets.push({ id: 'metamask', name: 'MetaMask', icon: '/images/3.svg' });
            }
            // Phantom
            if (window.solana && window.solana.isPhantom) {
                this.detectedWallets.push({ id: 'phantom', name: 'Phantom', icon: '/images/8.svg' });
            }
            // Coinbase Wallet
            if (window.ethereum && window.ethereum.isCoinbaseWallet) {
                this.detectedWallets.push({ id: 'coinbase', name: 'Coinbase Wallet', icon: '/images/4.svg' });
            }
            // Enhanced OKX Wallet detection
            if (window.okxwallet || window.okexchain) {
                this.detectedWallets.push({ id: 'okx', name: 'OKX Wallet', icon: '/images/1.png' });
            }
        },
        // Close the dialog and reset the network dropdown
        closeDialog() {
            this.showDialog = false;
            this.showNetworks = false;
        },
        // Switch the current network
        async selectNetwork(networkId) {
            this.selectedChain = networkId;
            this.showNetworks = false;

            // If a wallet is connected and the chain is EVM, try switching networks
            if (this.isConnected && ['ethereum', 'polygon', 'bsc'].includes(networkId)) {
                try {
                    const result = await web3Service.switchNetwork(networkId);
                    if (result.success) {
                        this.connectedChain = networkId;
                        // Notify the parent component that the network changed
                        this.$emit('network-changed', networkId);
                    }
                } catch (error) {
                    console.error('Failed to switch network:', error);
                    alert(`Failed to switch network: ${error.message}`);
                }
            }
        },
        // Connect the specified wallet to the specified chain
        async connectWallet(walletId, chain) {
            try {
                this.loading = true;    // Turn on loading state
                this.errorMessage = ''; // Clear errors
                // Common wallet logic
                const result = await web3Service.initialize(chain);
                if (result.success) {
                    // If it is an EVM chain, try switching to the correct network first
                    if (['ethereum', 'polygon', 'bsc'].includes(chain)) {
                        try {
                            await web3Service.switchNetwork(chain);
                        } catch (error) {
                            console.error('Failed to switch network:', error);
                            throw new Error(`Failed to switch network: ${error.message}`);
                        }
                    }
                    web3Service.saveConnection(walletId, chain, result.account);
                    this.isConnected = true;
                    this.connectedAccount = result.account;
                    this.connectedChain = chain;
                    this.closeDialog();
                    this.$emit('wallet-connected', { wallet:walletId, chain, account:result.account });
                } else {
                    throw new Error(result.error || 'Failed to connect wallet');
                }
            } catch (error) {
                this.errorMessage = error.message || 'Failed to connect wallet';
                alert(`Failed to connect wallet: ${this.errorMessage}`);
            } finally {
                this.loading = false; // Turn off loading state
            }
        },
        // Get network name by network ID
        getNetworkName(networkId) {
            const net = this.networks.find(n => n.id === networkId);
            return net ? net.name : 'Unknown';
        },
        // Shorten address display, e.g. 0x12...Ab34
        shortenAddress(address) {
            if (!address) return '';
            return address.substring(0,4) + '...' + address.substring(address.length-4);
        },
        // Copy address to clipboard
        copyAddress() {
            if (this.connectedAccount) {
                navigator.clipboard.writeText(this.connectedAccount)
                    .then(() => { alert('Address copied to clipboard'); })
                    .catch(err => { console.error('Address', err); });
            }
            this.showAccountMenu = false;
        },
        // Disconnect the current wallet
        disconnect() {
            web3Service.clearConnection(); // Clear local connection info
            this.isConnected = false;
            this.connectedAccount = '';
            this.connectedChain = '';
            this.showAccountMenu = false;
            this.$emit('wallet-disconnected'); // Notify parent component
        }
    }
}
import Web3 from 'web3';
import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
class Web3Service {
    constructor() {
        // Initialize Web3Service instance properties
        this.web3 = null;                     // Web3 instance for EVM chains (Ethereum/Polygon/BSC)
        this.solanaConnection = null;         // Connection instance for Solana
        this.selectedChain = null;            // Currently selected blockchain identifier
        this.account = null;                  // Currently connected wallet account address
        this.provider = null;                 // Current wallet provider (e.g. window.ethereum)

        // Network configuration
        this.networkConfigs = {
            ethereum: {
                chainId: '0x1', // 1
                chainName: 'Ethereum Mainnet',
                nativeCurrency: {
                    name: 'Ethereum',
                    symbol: 'ETH',
                    decimals: 18
                },
                rpcUrls: ['https://eth-mainnet.public.blastapi.io'],
                blockExplorerUrls: ['https://etherscan.io']
            },
            polygon: {
                chainId: '0x89', // 137
                chainName: 'Polygon Mainnet',
                nativeCurrency: {
                    name: 'MATIC',
                    symbol: 'MATIC',
                    decimals: 18
                },
                rpcUrls: ['https://polygon-rpc.com'],
                blockExplorerUrls: ['https://polygonscan.com']
            },
            bsc: {
                chainId: '0x38', // 56
                chainName: 'BNB Smart Chain',
                nativeCurrency: {
                    name: 'BNB',
                    symbol: 'BNB',
                    decimals: 18
                },
                rpcUrls: ['https://bsc-dataseed.binance.org'],
                blockExplorerUrls: ['https://bscscan.com']
            }
        };

        // Try to load and restore saved connection info
        this.loadSavedConnection();
    }

    // Load and restore saved wallet connection from localStorage
    loadSavedConnection() {
        try {
            const savedConnection = this.getSavedConnection(); // Get saved connection data
            if (savedConnection) {
                // Restore chain type and account
                this.selectedChain = savedConnection.chain;
                this.account = savedConnection.account;

                // Initialize the corresponding connection by chain type
                if (savedConnection.chain === 'solana') {
                    // Create connection using public Solana devnet RPC endpoint
                    this.solanaConnection = new Connection('https://api.mainnet-beta.solana.com', 'confirmed');
                } else if (['ethereum', 'polygon', 'bsc'].includes(savedConnection.chain)) {
                    // EVM chains: detect providers and create Web3 instance
                    if (window.ethereum) {
                        this.web3 = new Web3(window.ethereum);
                        this.provider = window.ethereum;
                    } else if (window.okxwallet) {
                        this.web3 = new Web3(window.okxwallet);
                        this.provider = window.okxwallet;
                    } else if (window.okexchain) {
                        this.web3 = new Web3(window.okexchain);
                        this.provider = window.okexchain;
                    }
                }
            }
        } catch (error) {
            console.error('Error loading saved connection:', error);
        }
    }

    // Save wallet connection info to localStorage
    saveConnection(wallet, chain, account, chainId) {
        try {
            // Build the connection data object to save
            const connectionData = {
                wallet,                        // Wallet type (metamask/phantom/okx, etc.)
                chain,                         // Chain type (ethereum/solana, etc.)
                account,                       // Wallet address
                chainId,                       // Chain ID
                timestamp: Date.now()          // Saved timestamp
            };
            // Serialize and save to localStorage
            localStorage.setItem('walletConnection', JSON.stringify(connectionData));
            // Update service state
            this.selectedChain = chain;
            this.account = account;
        } catch (error) {
            console.error('Error saving wallet connection:', error);
        }
    }

    // Clear wallet connection info from localStorage and reset service state
    clearConnection() {
        try {
            localStorage.removeItem('walletConnection'); // Remove stored record
            // Reset all connection-related properties
            this.selectedChain = null;
            this.account = null;
            this.web3 = null;
            this.solanaConnection = null;
            this.provider = null;
        } catch (error) {
            console.error('Error clearing wallet connection:', error);
        }
    }

    // Initialize the blockchain connection for the specified chain type
    async initialize(chain) {
        try {
            this.selectedChain = chain; // Set current chain type
            // EVM chain (Ethereum, Polygon, BSC) logic
            if (chain === 'ethereum' || chain === 'polygon' || chain === 'bsc') {
                let provider = null; // Temporary variable for detected provider
                // Prefer common injections: MetaMask (window.ethereum)
                if (window.ethereum) {
                    provider = window.ethereum;
                } else if (window.okxwallet) {
                    // OKX Wallet
                    provider = window.okxwallet;
                } else if (window.okexchain) {
                    // OKExChain
                    provider = window.okexchain;
                }
                if (provider) {
                    // Set and initialize Web3 instance
                    this.provider = provider;
                    this.web3 = new Web3(provider);
                    // Request user authorization and get accounts
                    const accounts = await provider.request({ method: 'eth_requestAccounts' });
                    this.account = accounts[0]; // Take the first account
                    return { success: true, account: this.account };
                } else {
                    // No EVM-compatible provider detected
                    throw new Error('Ethereum provider not found. Please install MetaMask, OKX Wallet or another compatible wallet.');
                }

            // Solana chain logic
            } else if (chain === 'solana') {
                let solProvider = null; // Temporary variable for Solana provider
                if (window.solana) {
                    solProvider = window.solana;
                } else if (window.okxwallet && window.okxwallet.sol) {
                    solProvider = window.okxwallet.sol;
                }
                if (solProvider) {
                    // Create Solana connection (devnet)
                    this.solanaConnection = new Connection('https://api.devnet.solana.com', 'confirmed');
                    // Request connection and get publicKey
                    const connection = await solProvider.connect();
                    this.account = connection.publicKey.toString();
                    return { success: true, account: this.account };
                } else {
                    // No Solana wallet detected
                    throw new Error('Solana provider not found. Please install Phantom wallet or OKX Wallet.');
                }
            } else {
                // Unsupported blockchain type
                throw new Error(`Unsupported blockchain: ${chain}`);
            }
        } catch (error) {
            return { success: false, error: error.message };
        }
    }

    // Get native token balance (supports ETH/MATIC/BNB/SOL)
    async getBalance() {

        try {
            // Verify account is connected
            if (!this.account) {
                throw new Error('No connected account.');
            }

            // Define chain config including symbol and unit conversion
            const chainConfig = {
                ethereum: { symbol: 'ETH', unit: 'ether' },
                polygon: { symbol: 'MATIC', unit: 'ether' },
                bsc: { symbol: 'BNB', unit: 'ether' },
                solana: { symbol: 'SOL', unit: 'lamports' }
            };

            // Check whether current chain is supported
            if (!chainConfig[this.selectedChain]) {
                throw new Error(`Unsupported blockchain: ${this.selectedChain}`);
            }

            const { symbol, unit } = chainConfig[this.selectedChain];

            // Get EVM chain balance
            if (['ethereum', 'polygon', 'bsc'].includes(this.selectedChain)) {
                if (!this.web3) {
                    throw new Error('Web3 not initialized.');
                }

                // Get balance (wei unit)
                const balanceWei = await this.web3.eth.getBalance(this.account);
                // Convert to ether unit and keep 6 decimals for readability
                const balance = Number(this.web3.utils.fromWei(balanceWei, unit)).toFixed(6);

                return {
                    success: true,
                    balance: parseFloat(balance), // Return number type, avoid string
                    symbol
                };
            }

            // Get Solana chain balance
            if (this.selectedChain === 'solana') {
                if (!this.solanaConnection) {
                    throw new Error('Solana connection not initialized.');
                }

                // Get Solana PublicKey
                let publicKeyObj;
                publicKeyObj  = new PublicKey(this.account); // OKX wallet

                // Get balance (lamports unit)
                const balanceLamports = await this.solanaConnection.getBalance(publicKeyObj);
                console.log(balanceLamports)

                // Convert to SOL unit and keep 6 decimals
                const balance = (balanceLamports / 1e9).toFixed(6);

                return {
                    success: true,
                    balance: parseFloat(balance), // Return number type
                    symbol
                };
            }
        } catch (error) {
            console.error(`Error getting balance for ${this.selectedChain}:`, error);
            return {
                success: false,
                error: error.message || 'Failed to fetch balance'
            };
        }
    }

    // Simple example: execute token purchase (simulated)
    async purchaseTokens(amount, contractAddress) {
        // Parameter validation
        if (!this.account || !amount) {
            throw new Error('Account or amount not specified');
        }
        // In a real scenario, create contract instance, estimate gas, and send transaction
        console.log(`Purchasing tokens with ${amount} on ${this.selectedChain}`);
        console.log(`Contract: ${contractAddress}`);
        console.log(`From account: ${this.account}`);
        // Simulate successful transaction and return fake hash
        return {
            success: true,
            hash: '0x' + Math.random().toString(16).substring(2, 42),
            amount: amount
        };
    }

    // Read saved connection info from localStorage
    getSavedConnection() {
        try {
            const savedData = localStorage.getItem('walletConnection');
            if (savedData) {
                return JSON.parse(savedData); // Parse JSON and return
            }
            return null;
        } catch (error) {
            console.error('Error retrieving saved connection:', error);
            return null;
        }
    }

    // Switch to specified EVM network
    async switchNetwork(chainName) {
        if (!this.provider) {
            throw new Error('No provider available');
        }
        const networkConfig = this.networkConfigs[chainName];
        if (!networkConfig) {
            throw new Error(`Unsupported network: ${chainName}`);
        }
        try {
            // Try switching to an existing network
            await this.provider.request({
                method: 'wallet_switchEthereumChain',
                params: [{ chainId: networkConfig.chainId }],
            });
            return { success: true };
        } catch (switchError) {
            // If network does not exist (error code 4902), try adding it
            if (switchError.code === 4902) {
                try {
                    await this.provider.request({
                        method: 'wallet_addEthereumChain',
                        params: [networkConfig],
                    });
                    return { success: true };
                } catch (addError) {
                    throw new Error(`Failed to add network: ${addError.message}`);
                }
            } else {
                throw new Error(`Failed to switch network: ${switchError.message}`);
            }
        }
    }
}

export default new Web3Service();