Web3 Frontend Integration

Fortschrittliche Web3 Frontend-Integrationsbeispiele inklusive Ethers.js/Web3.js, Multi-Wallet-Unterstützung, DeFi-Protokoll-Interaktionen und NFT-Marktplatz-Schnittstellen

💻 Erweiterte Ethers.js Integration typescript

🟡 intermediate ⭐⭐⭐⭐

Vollständiges Web3 Frontend mit Ethers.js, inklusive Kontraktinteraktionen, Ereignis-Überwachung, Gas-Schätzung, Transaktionsüberwachung und Fehlerbehandlung

⏱️ 40 min 🏷️ ethers, web3, frontend, dapp, blockchain
Prerequisites: JavaScript, React, Ethereum basics, Web3 concepts
// Advanced Ethers.js Frontend Integration
// Complete Web3 DApp with Ethers.js v6
// Dependencies: ethers@^6.0.0, @walletconnect/web3-provider@^2.0.0

import { ethers } from 'ethers';
import { providers } from '@walletconnect/web3-provider';
import { watchContractEvent } from '@ethersproject/contracts';
import { Writable } from '@ethersproject/contracts';

// Contract ABIs (normally imported from JSON files)
const ERC20_ABI = [
    // ... ERC-20 ABI
    "function name() view returns (string)",
    "function symbol() view returns (string)",
    "function decimals() view returns (uint8)",
    "function totalSupply() view returns (uint256)",
    "function balanceOf(address) view returns (uint256)",
    "function transfer(address, uint256) returns (bool)",
    "function allowance(address, address) view returns (uint256)",
    "function approve(address, uint256) returns (bool)",
    "function transferFrom(address, address, uint256) returns (bool)",
    "event Transfer(address indexed from, address indexed to, uint256 value)",
    "event Approval(address indexed owner, address indexed spender, uint256 value)"
];

const NFT_MARKETPLACE_ABI = [
    // NFT Marketplace ABI
    "function listNFT(uint256, uint256) external",
    "function buyNFT(uint256) external payable",
    "function cancelListing(uint256) external",
    "function updateListing(uint256, uint256) external",
    "event NFTListed(uint256 indexed tokenId, uint256 price, address indexed seller)",
    "event NFTSold(uint256 indexed tokenId, address indexed seller, address indexed buyer, uint256 price)",
    "event ListingCancelled(uint256 indexed tokenId)"
];

interface Web3Config {
    infuraProjectId?: string;
    alchemyApiKey?: string;
    defaultChainId: number;
    supportedNetworks: NetworkConfig[];
    rpcUrls: { [chainId: number]: string };
}

interface NetworkConfig {
    chainId: number;
    name: string;
    rpcUrl: string;
    blockExplorerUrl: string;
    nativeCurrency: {
        name: string;
        symbol: string;
        decimals: number;
    };
}

class Web3Manager {
    private provider: ethers.providers.Provider | null = null;
    private signer: ethers.Signer | null = null;
    private contracts: Map<string, ethers.Contract> = new Map();
    private eventListeners: Map<string, ethers.Listener[]> = new Map();
    private config: Web3Config;
    private network: NetworkConfig | null = null;
    private isWalletConnected: boolean = false;

    constructor(config: Web3Config) {
        this.config = config;
        this.initializeProvider();
    }

    private initializeProvider() {
        // Initialize with Infura fallback
        const infuraUrl = this.config.infuraProjectId
            ? `https://mainnet.infura.io/v3/${this.config.infuraProjectId}`
            : 'https://eth-mainnet.alchemyapi.io/v2/demo';

        this.provider = new ethers.providers.JsonRpcProvider(infuraUrl);
        console.log('🔗 Initialized with RPC provider');
    }

    // Wallet Connection Methods
    async connectWallet(walletType: 'metamask' | 'walletconnect' = 'metamask') {
        try {
            let provider: ethers.providers.Web3Provider;

            switch (walletType) {
                case 'metamask':
                    provider = await this.connectMetaMask();
                    break;
                case 'walletconnect':
                    provider = await this.connectWalletConnect();
                    break;
                default:
                    throw new Error(`Unsupported wallet type: ${walletType}`);
            }

            this.provider = provider;
            this.signer = provider.getSigner();
            this.isWalletConnected = true;

            // Get current network
            const network = await provider.getNetwork();
            this.network = this.config.supportedNetworks.find(
                (config: NetworkConfig) => config.chainId === network.chainId
            );

            if (!this.network) {
                throw new Error(`Network ${network.chainId} not supported`);
            }

            console.log('✅ Wallet connected:', {
                network: network.name,
                chainId: network.chainId,
                account: await this.getConnectedAccount()
            });

            return true;

        } catch (error) {
            console.error('❌ Wallet connection failed:', error);
            throw error;
        }
    }

    private async connectMetaMask(): Promise<ethers.providers.Web3Provider> {
        if (typeof window.ethereum === 'undefined') {
            throw new Error('MetaMask is not installed');
        }

        // Request account access
        await window.ethereum.request({ method: 'eth_requestAccounts' });

        // Create provider
        const provider = new ethers.providers.Web3Provider(window.ethereum);

        // Listen for network changes
        window.ethereum.on('chainChanged', () => {
            this.handleNetworkChange();
        });

        // Listen for account changes
        window.ethereum.on('accountsChanged', () => {
            this.handleAccountChange();
        });

        return provider;
    }

    private async connectWalletConnect(): Promise<ethers.providers.Web3Provider> {
        const wcProvider = new providers.Web3Provider({
            infuraId: this.config.infuraProjectId
        });

        await wcProvider.enable();

        return wcProvider;
    }

    async disconnectWallet() {
        try {
            this.provider = null;
            this.signer = null;
            this.isWalletConnected = false;

            console.log('🔌 Wallet disconnected');
        } catch (error) {
            console.error('❌ Failed to disconnect wallet:', error);
        }
    }

    private handleNetworkChange() {
        console.log('⚡ Network changed, reloading...');
        window.location.reload();
    }

    private handleAccountChange() {
        console.log('👤 Account changed');
        this.emit('accountChanged');
    }

    // Utility Methods
    async getConnectedAccount(): Promise<string | null> {
        if (!this.signer) return null;
        return await this.signer.getAddress();
    }

    async getBalance(address?: string): Promise<string> {
        const account = address || await this.getConnectedAccount();
        if (!account) return '0';

        const balance = await this.provider!.getBalance(account);
        return ethers.utils.formatEther(balance);
    }

    async switchNetwork(chainId: number): Promise<void> {
        try {
            if (window.ethereum) {
                await window.ethereum.request({
                    method: 'wallet_switchEthereumChain',
                    params: [{ chainId: `0x${chainId.toString(16)}` }]
                });
            } else {
                throw new Error('Network switching not supported by current wallet');
            }
        } catch (error) {
            console.error('❌ Network switch failed:', error);
            throw error;
        }
    }

    async addNetwork(networkConfig: NetworkConfig): Promise<void> {
        try {
            if (window.ethereum) {
                await window.ethereum.request({
                    method: 'wallet_addEthereumChain',
                    params: [networkConfig]
                });
            } else {
                throw new Error('Network addition not supported by current wallet');
            }
        } catch (error) {
            console.error('❌ Failed to add network:', error);
            throw error;
        }
    }

    // Contract Interactions
    getContract(address: string, abi: any[]): ethers.Contract {
        const cacheKey = `${address}-${JSON.stringify(abi)}`;

        if (this.contracts.has(cacheKey)) {
            return this.contracts.get(cacheKey)!;
        }

        if (!this.signer) {
            throw new Error('Wallet not connected');
        }

        const contract = new ethers.Contract(address, abi, this.signer);
        this.contracts.set(cacheKey, contract);
        return contract;
    }

    // ERC-20 Token Methods
    async transferToken(tokenAddress: string, to: string, amount: string): Promise<ethers.TransactionResponse> {
        try {
            const contract = this.getContract(tokenAddress, ERC20_ABI);
            const decimals = await contract.decimals();
            const parsedAmount = ethers.utils.parseUnits(amount, decimals);

            const tx = await contract.transfer(to, parsedAmount);

            return await this.waitForTransaction(tx);
        } catch (error) {
            console.error('❌ Token transfer failed:', error);
            throw error;
        }
    }

    async getTokenInfo(tokenAddress: string) {
        try {
            const contract = this.getContract(tokenAddress, ERC20_ABI);

            const [name, symbol, decimals, totalSupply] = await Promise.all([
                contract.name(),
                contract.symbol(),
                contract.decimals(),
                contract.totalSupply()
            ]);

            return {
                address: tokenAddress,
                name,
                symbol,
                decimals: Number(decimals),
                totalSupply: ethers.utils.formatEther(totalSupply)
            };
        } catch (error) {
            console.error('❌ Failed to get token info:', error);
            throw error;
        }
    }

    async getTokenBalance(tokenAddress: string, userAddress?: string): Promise<string> {
        try {
            const contract = this.getContract(tokenAddress, ERC20_ABI);
            const account = userAddress || await this.getConnectedAccount();

            if (!account) return '0';

            const balance = await contract.balanceOf(account);
            const decimals = await contract.decimals();

            return ethers.utils.formatUnits(balance, decimals);
        } catch (error) {
            console.error('❌ Failed to get token balance:', error);
            throw error;
        }
    }

    // NFT Marketplace Methods
    async listNFT(marketplaceAddress: string, tokenId: string, price: string) {
        try {
            const contract = this.getContract(marketplaceAddress, NFT_MARKETPLACE_ABI);
            const parsedPrice = ethers.utils.parseEther(price);

            const tx = await contract.listNFT(tokenId, parsedPrice);
            return await this.waitForTransaction(tx);
        } catch (error) {
            console.error('❌ NFT listing failed:', error);
            throw error;
        }
    }

    async buyNFT(marketplaceAddress: string, tokenId: string, maxPrice?: string) {
        try {
            const contract = this.getContract(marketplaceAddress, NFT_MARKETPLACE_ABI);

            let tx: ethers.ContractTransaction;
            if (maxPrice) {
                const maxPriceWei = ethers.utils.parseEther(maxPrice);
                tx = await contract.buyNFT(tokenId, {
                    value: maxPriceWei
                });
            } else {
                tx = await contract.buyNFT(tokenId);
            }

            return await this.waitForTransaction(tx);
        } catch (error) {
            console.error('❌ NFT purchase failed:', error);
            throw error;
        }
    }

    // Transaction Management
    async estimateGas(transaction: ethers.Transaction): Promise<string> {
        try {
            const gasEstimate = await this.provider!.estimateGas(transaction);
            return gasEstimate.toString();
        } catch (error) {
            console.error('❌ Gas estimation failed:', error);
            return '0';
        }
    }

    async getTransactionReceipt(txHash: string) {
        try {
            return await this.provider!.getTransactionReceipt(txHash);
        } catch (error) {
            console.error('❌ Failed to get transaction receipt:', error);
            throw error;
        }
    }

    async waitForTransaction(
        transaction: ethers.ContractTransaction,
        confirmations: number = 1,
        timeout: number = 30000
    ): Promise<ethers.TransactionResponse> {
        try {
            // Send transaction
            const tx = await transaction.send();

            // Wait for confirmation
            const receipt = await tx.wait(confirmations);

            // Emit transaction event
            this.emit('transactionComplete', {
                hash: receipt.hash,
                status: receipt.status === 1 ? 'success' : 'failed',
                gasUsed: receipt.gasUsed.toString(),
                blockNumber: receipt.blockNumber.toString(),
                timestamp: Date.now()
            });

            return receipt;

        } catch (error) {
            // Emit error event
            this.emit('transactionError', {
                error: error.message,
                timestamp: Date.now()
            });
            throw error;
        }
    }

    // Event Listening
    listenToContractEvents(
        contractAddress: string,
        eventName: string,
        callback: (event: any) => void
    ): () => void {
        try {
            const contract = this.getContract(contractAddress, []);

            // Clean up existing listeners
            this.removeAllListeners(contractAddress, eventName);

            // Add new listener
            const listener = (...args: any[]) => {
                const event = contract.interface.parseLog(args[0], args[1], args[2]);
                callback(event);
            };

            contract.on(eventName, listener);

            // Store listener for cleanup
            const listeners = this.eventListeners.get(contractAddress) || [];
            listeners.push({ eventName, listener });
            this.eventListeners.set(contractAddress, listeners);

            return () => this.removeAllListeners(contractAddress, eventName);

        } catch (error) {
            console.error('❌ Failed to set up event listener:', error);
            throw error;
        }
    }

    private removeAllListeners(contractAddress: string, eventName?: string) {
        const listeners = this.eventListeners.get(contractAddress);
        if (!listeners) return;

        const contract = this.contracts.get(`${contractAddress}-[]`);
        if (!contract) return;

        listeners.forEach(({ eventName: name, listener }) => {
            if (!eventName || name === eventName) {
                contract.removeAllListeners(name);
            }
        });
    }

    // Gas Optimization
    async optimizeTransaction(transaction: ethers.Transaction): Promise<ethers.Transaction> {
        try {
            // Set reasonable gas limit if not provided
            if (!transaction.gasLimit) {
                const gasEstimate = await this.estimateGas(transaction);
                transaction.gasLimit = Math.floor(Number(gasEstimate) * 1.2); // 20% buffer
            }

            // Set gas price if not provided
            if (!transaction.gasPrice && this.network) {
                const feeData = await this.provider!.getFeeData();
                transaction.gasPrice = feeData.gasPrice;
            }

            // Set nonce if not provided
            if (transaction.nonce === undefined) {
                const nonce = await this.signer!.getTransactionCount();
                transaction.nonce = nonce;
            }

            return transaction;
        } catch (error) {
            console.error('❌ Transaction optimization failed:', error);
            return transaction;
        }
    }

    // Batch Operations
    async batchTransactions(transactions: ethers.Transaction[]): Promise<ethers.TransactionResponse[]> {
        try {
            // Process transactions in parallel with delays to avoid nonce conflicts
            const results = await Promise.all(
                transactions.map(async (tx, index) => {
                    // Add small delay between transactions
                    await new Promise(resolve => setTimeout(resolve, 100 * index));

                    return this.waitForTransaction(tx);
                })
            );

            return results;
        } catch (error) {
            console.error('❌ Batch transactions failed:', error);
            throw error;
        }
    }

    // Event Emission
    private listeners: Map<string, ((event: any) => void)[]> = new Map();

    emit(eventName: string, data: any) {
        const listeners = this.listeners.get(eventName) || [];
        listeners.forEach(listener => listener(data));
    }

    on(eventName: string, listener: (event: any) => void) {
        if (!this.listeners.has(eventName)) {
            this.listeners.set(eventName, []);
        }
        this.listeners.get(eventName)!.push(listener);
    }

    off(eventName: string, listener: (event: any) => void) {
        const listeners = this.listeners.get(eventName) || [];
        const index = listeners.indexOf(listener);
        if (index > -1) {
            listeners.splice(index, 1);
        }
    }

    // Multi-chain Support
    async switchChain(newChainId: number) {
        const targetNetwork = this.config.supportedNetworks.find(
            (config: NetworkConfig) => config.chainId === newChainId
        );

        if (!targetNetwork) {
            throw new Error(`Chain ${newChainId} not supported`);
        }

        try {
            // Switch wallet to new chain
            await this.switchNetwork(newChainId);

            // Update provider
            this.provider = new ethers.providers.JsonRpcProvider(targetNetwork.rpcUrl);
            this.network = targetNetwork;

            this.emit('chainChanged', {
                chainId: newChainId,
                network: targetNetwork.name
            });

        } catch (error) {
            console.error('❌ Chain switch failed:', error);
            throw error;
        }
    }

    getCurrentNetwork(): NetworkConfig | null {
        return this.network;
    }

    getNetworks(): NetworkConfig[] {
        return this.config.supportedNetworks;
    }

    // Storage and Persistence
    saveState() {
        const state = {
            isWalletConnected: this.isWalletConnected,
            network: this.network,
            connectedAccount: this.signer ? null : undefined
        };

        localStorage.setItem('web3-state', JSON.stringify(state));
    }

    loadState() {
        try {
            const saved = localStorage.getItem('web3-state');
            if (saved) {
                const state = JSON.parse(saved);
                this.isWalletConnected = state.isWalletConnected || false;
                this.network = state.network || null;

                if (state.connectedAccount) {
                    this.reconnectAccount(state.connectedAccount);
                }
            }
        } catch (error) {
            console.warn('⚠️ Failed to load state:', error);
        }
    }

    private async reconnectAccount(account: string) {
        try {
            if (this.provider && this.isWalletConnected) {
                // Verify the account is still available
                const availableAccounts = await this.provider.send('eth_requestAccounts', []);
                if (availableAccounts.includes(account)) {
                    // Account is available, update signer
                    this.provider = new ethers.providers.Web3Provider(window.ethereum);
                    this.signer = this.provider.getSigner();
                } else {
                    // Account no longer available, clear connection
                    this.isWalletConnected = false;
                    this.signer = null;
                }
            }
        } catch (error) {
            console.warn('⚠️ Failed to reconnect account:', error);
        }
    }

    // DeFi Integration Examples
    async swapTokens(
        routerAddress: string,
        tokenA: string,
        tokenB: string,
        amountIn: string,
        amountOutMin: string,
        slippage: number = 0.5
    ): Promise<ethers.TransactionResponse> {
        try {
            // Simplified Uniswap V2 Router example
            const routerAbi = [
                "function WETH() external view returns (address)",
                "function swapExactTokensForTokens(uint256,uint256,address,bytes[])",
                "function getAmountsOut(uint256,address[]) view returns (uint256[])",
                "function swapExactETHForTokens(uint256,address[],uint256) external payable",
                "function swapExactTokensForETH(address[],uint256,uint256) external"
            ];

            const router = this.getContract(routerAddress, routerAbi);

            // Calculate minimum output
            const decimalsA = await this.getTokenDecimals(tokenA);
            const decimalsB = await this.getTokenDecimals(tokenB);
            const parsedAmountIn = ethers.utils.parseUnits(amountIn, decimalsA);

            // This is a simplified example - actual implementation would require
            // getting pool data and calculating proper amounts
            const amountOut = await router.getAmountsOut(parsedAmountIn, [tokenA, tokenB]);
            const minAmountOut = amountOut[1].mul(100 - slippage).div(100);

            const tx = await router.swapExactTokensForTokens(
                parsedAmountIn,
                minAmountOut,
                [tokenA, tokenB],
                this.signer?.address()
            );

            return await this.waitForTransaction(tx);

        } catch (error) {
            console.error('❌ Token swap failed:', error);
            throw error;
        }
    }

    private async getTokenDecimals(tokenAddress: string): Promise<number> {
        try {
            const contract = this.getContract(tokenAddress, ERC20_ABI);
            const decimals = await contract.decimals();
            return Number(decimals);
        } catch (error) {
            return 18; // Default to 18 decimals for most tokens
        }
    }

    // Liquidity Pool Management
    async addLiquidity(
        poolAddress: string,
        tokenA: string,
        tokenB: string,
        amountA: string,
        amountB: string,
        slippage: number = 0.5
    ): Promise<ethers.TransactionResponse> {
        try {
            // Simplified liquidity pool example
            const poolAbi = [
                "function addLiquidity(uint256,uint256,address,address)",
                "function removeLiquidity(uint256,uint256)",
                "function getReserves() view returns (uint256,uint256)",
                "event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB)"
            ];

            const pool = this.getContract(poolAddress, poolAbi);
            const decimalsA = await this.getTokenDecimals(tokenA);
            const decimalsB = await this.getTokenDecimals(tokenB);

            const parsedAmountA = ethers.utils.parseUnits(amountA, decimalsA);
            const parsedAmountB = ethers.utils.parseUnits(amountB, decimalsB);

            const tx = await pool.addLiquidity(parsedAmountA, parsedAmountB, tokenA, tokenB);
            return await this.waitForTransaction(tx);

        } catch (error) {
            console.error('❌ Add liquidity failed:', error);
            throw error;
        }
    }

    // Utility Methods
    formatEther(wei: string): string {
        try {
            return ethers.utils.formatEther(wei);
        } catch {
            return wei;
        }
    }

    parseEther(ether: string): ethers.BigNumber {
        try {
            return ethers.utils.parseEther(ether);
        } catch {
            return ethers.BigNumber(0);
        }
    }

    formatUnits(value: ethers.BigNumber, decimals: number): string {
        try {
            return ethers.utils.formatUnits(value, decimals);
        } catch {
            return value.toString();
        }
    }

    parseUnits(value: string, decimals: number): ethers.BigNumber {
        try {
            return ethers.utils.parseUnits(value, decimals);
        } catch {
            return ethers.BigNumber(0);
        }
    }

    isValidAddress(address: string): boolean {
        try {
            return ethers.utils.isAddress(address);
        } catch {
            return false;
        }
    }

    // Cleanup
    destroy() {
        // Remove all event listeners
        this.eventListeners.forEach((listeners, contractAddress) => {
            listeners.forEach(({ listener }) => {
                this.off('eventChanged', listener);
            });
        });

        // Clear references
        this.providers = null;
        this.signer = null;
        this.contracts.clear();
        this.eventListeners.clear();
    }
}

// React Hook for Web3 Integration
// web3Hook.ts
import { useState, useEffect, useCallback } from 'react';

export const useWeb3 = () => {
    const [web3Manager, setWeb3Manager] = useState<Web3Manager | null>(null);
    const [isConnected, setIsConnected] = useState(false);
    const [account, setAccount] = useState<string | null>(null);
    const [network, setNetwork] = useState<any>(null);
    const [balance, setBalance] = useState<string>('0');

    // Initialize Web3Manager
    useEffect(() => {
        const config = {
            infuraProjectId: process.env.NEXT_PUBLIC_INFURA_PROJECT_ID,
            defaultChainId: 1,
            supportedNetworks: [
                {
                    chainId: 1,
                    name: 'Ethereum Mainnet',
                    rpcUrl: 'https://mainnet.infura.io/v3/YOUR_PROJECT_ID',
                    blockExplorerUrl: 'https://etherscan.io',
                    nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }
                },
                {
                    chainId: 137,
                    name: 'Polygon',
                    rpcUrl: 'https://polygon-rpc.com',
                    blockExplorerUrl: 'https://polygonscan.com',
                    nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18 }
                },
                {
                    chainId: 56,
                    name: 'BSC',
                    rpcUrl: 'https://bsc-dataseid1.noderealapi.com',
                    blockExplorerUrl: 'https://bscscan.com',
                    nativeCurrency: { name: 'BNB', symbol: 'BNB', decimals: 18 }
                }
            ]
        };

        const manager = new Web3Manager(config);
        manager.loadState();

        setWeb3Manager(manager);
        setIsConnected(manager.isWalletConnected);
        setNetwork(manager.getCurrentNetwork());
        setAccount(await manager.getConnectedAccount());

        // Save state on changes
        manager.on('accountChanged', async () => {
            setAccount(await manager.getConnectedAccount());
        });

        manager.on('chainChanged', (data) => {
            setNetwork(data.network);
        });

        manager.on('balanceChanged', (data) => {
            setBalance(data.balance);
        });

        // Cleanup on unmount
        return () => {
            manager.destroy();
        };
    }, []);

    const connectWallet = useCallback(async (walletType: 'metamask' | 'walletconnect' = 'metamask') => {
        if (!web3Manager) return;

        try {
            await web3Manager.connectWallet(walletType);
            setIsConnected(true);
            setAccount(await web3Manager.getConnectedAccount());
        } catch (error) {
            console.error('Failed to connect wallet:', error);
            throw error;
        }
    }, [web3Manager]);

    const disconnectWallet = useCallback(async () => {
        if (!web3Manager) return;

        try {
            await web3Manager.disconnectWallet();
            setIsConnected(false);
            setAccount(null);
        } catch (error) {
            console.error('Failed to disconnect wallet:', error);
            throw error;
        }
    }, [web3Manager]);

    const switchNetwork = useCallback(async (chainId: number) => {
        if (!web3Manager) return;

        try {
            await web3Manager.switchChain(chainId);
            setNetwork(web3Manager.getCurrentNetwork());
        } catch (error) {
            console.error('Failed to switch network:', error);
            throw error;
        }
    }, [web3Manager]);

    const getContract = useCallback((address: string, abi: any[]) => {
        return web3Manager?.getContract(address, abi);
    }, [web3Manager]);

    const transferToken = useCallback(async (tokenAddress: string, to: string, amount: string) => {
        return web3Manager?.transferToken(tokenAddress, to, amount);
    }, [web3Manager]);

    const getTokenBalance = useCallback(async (tokenAddress: string) => {
        return web3Manager?.getTokenBalance(tokenAddress);
    }, [web3Manager]);

    const buyNFT = useCallback(async (marketplaceAddress: string, tokenId: string, maxPrice?: string) => {
        return web3Manager?.buyNFT(marketplaceAddress, tokenId, maxPrice);
    }, [web3Manager]);

    return {
        web3Manager,
        isConnected,
        account,
        network,
        balance,
        connectWallet,
        disconnectWallet,
        switchNetwork,
        getContract,
        transferToken,
        getTokenBalance,
        buyNFT
    };
};

// Multi-chain Provider Manager
class MultiChainManager {
    private providers: Map<number, ethers.providers.JsonRpcProvider> = new Map();
    private wallets: Map<number, ethers.Wallet> = new Map();
    private currentChainId: number = 1;

    constructor(config: { [chainId: number]: string }) {
        // Initialize providers for all supported chains
        Object.entries(config).forEach(([chainId, rpcUrl]) => {
            this.providers.set(Number(chainId), new ethers.providers.JsonRpcProvider(rpcUrl));
        });
    }

    getProvider(chainId?: number): ethers.providers.JsonRpcProvider {
        return this.providers.get(chainId || this.currentChainId);
    }

    switchChain(chainId: number): void {
        this.currentChainId = chainId;
    }

    getCurrentChainId(): number {
        return this.currentChainId;
    }

    async executeOnChain<T>(
        chainId: number,
        operation: (provider: ethers.providers.JsonRpcProvider) => Promise<T>
    ): Promise<T> {
        const provider = this.getProvider(chainId);
        return await operation(provider);
    }

    async executeOnAllChains<T>(
        operation: (provider: ethers.providers.JsonRpcProvider, chainId: number) => Promise<T>
    ): Promise<Map<number, T>> {
        const results = new Map<number, T>();

        for (const [chainId, provider] of this.providers) {
            try {
                const result = await operation(provider, chainId);
                results.set(chainId, result);
            } catch (error) {
                console.error(`Error on chain ${chainId}:`, error);
                results.set(chainId, error);
            }
        }

        return results;
    }
}

export {
    Web3Manager,
    MultiChainManager,
    useWeb3
};

// Usage example
// main.ts
async function main() {
    console.log('🚀 Web3 Frontend Integration Demo');

    // Initialize Web3 manager
    const web3Manager = new Web3Manager({
        infuraProjectId: process.env.INFURA_PROJECT_ID,
        defaultChainId: 1,
        supportedNetworks: [
            {
                chainId: 1,
                name: 'Ethereum Mainnet',
                rpcUrl: 'https://mainnet.infura.io/v3/YOUR_PROJECT_ID',
                blockExplorerUrl: 'https://etherscan.io',
                nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }
            },
            {
                chainId: 137,
                name: 'Polygon',
                rpcUrl: 'https://polygon-rpc.com',
                blockExplorerUrl: 'https://polygonscan.com',
                nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18 }
            }
        ]
    });

    try {
        // Initialize and connect wallet
        await web3Manager.initialize();
        await web3Manager.connectWallet('metamask');

        // Get wallet balance
        const balance = await web3Manager.getBalance();
        console.log(`💰 Wallet balance: ${balance} ETH`);

        // Get token info
        const tokenAddress = '0xA0b86a33E6D13A1F297Cd2B55E1A7B80A5C2C6EBD24A1C2A;
        const tokenInfo = await web3Manager.getTokenInfo(tokenAddress);
        console.log('📊 Token Info:', tokenInfo);

        // Transfer tokens
        if (parseFloat(balance) > 0.1) {
            const recipient = '0x742d35Cc6634C0532925a3b8D4C9db96C4b4d8b6';
            const amount = '0.01';

            console.log(`💸 Transferring ${amount} tokens to ${recipient}...`);
            const receipt = await web3Manager.transferToken(tokenAddress, recipient, amount);
            console.log(`✅ Transfer complete: ${receipt.hash}`);
        }

        // Listen to contract events
        const marketplaceAddress = '0x1234567890123456789012345678901234567890';
        const stopListening = web3Manager.listenToContractEvents(
            marketplaceAddress,
            'NFTSold',
            (event) => {
                console.log(`🎨 NFT Sold! TokenId: ${event.tokenId}, Buyer: ${event.buyer}, Price: ${web3Manager.formatEther(event.price.toString())} ETH`);
            }
        );

        // Listen for transaction events
        web3Manager.on('transactionComplete', (event) => {
            console.log(`✅ Transaction ${event.hash} ${event.status}`);
        });

        // Wait for some events
        await new Promise(resolve => setTimeout(resolve, 10000));

        stopListening();

    } catch (error) {
        console.error('❌ Demo failed:', error);
    }
}

if (require.main === module) {
    main();
}