Biblioteca Ethers.js Ethereum

Ejemplos de Ethers.js incluyendo proveedores, billeteras, contratos, transacciones, eventos, firmas, utilidades y patrones de interacción blockchain

💻 Ethers.js Básicos - Guía de Inicio javascript

🟢 simple ⭐⭐

Fundamentos completos de Ethers.js incluyendo proveedores, billeteras, interacción de contratos y operaciones básicas de blockchain

⏱️ 25 min 🏷️ ethers, ethereum, web3, basics
Prerequisites: Node.js, Basic understanding of Ethereum, API keys for providers
// Ethers.js Basics - Complete Getting Started Guide
// Install: npm install ethers

const { ethers } = require("ethers");

// ===== Provider Examples =====
console.log("=== PROVIDER EXAMPLES ===");

// 1. Provider from JSON-RPC URL
const provider = new ethers.providers.JsonRpcProvider("https://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY");
console.log("✅ JSON-RPC Provider created");

// 2. Provider from environment (MetaMask, etc.)
if (typeof window !== 'undefined' && window.ethereum) {
  const metamaskProvider = new ethers.providers.Web3Provider(window.ethereum);
  console.log("✅ MetaMask Provider created");
}

// 3. Provider from WebSocket (for real-time updates)
const wsProvider = new ethers.providers.WebSocketProvider("wss://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY");
console.log("✅ WebSocket Provider created");

// 4. Fallback provider (multiple providers for reliability)
const fallbackProvider = new ethers.providers.FallbackProvider([
  ethers.providers.InfuraProvider.getWebSocketProvider("mainnet"),
  ethers.providers.AlchemyProvider.getWebSocketProvider("mainnet", "YOUR-API-KEY"),
]);
console.log("✅ Fallback Provider created");

// ===== Wallet Examples =====
console.log("\n=== WALLET EXAMPLES ===");

// 1. Create random wallet
const wallet = ethers.Wallet.createRandom();
console.log("🔑 Random Wallet:");
console.log("   Address:", wallet.address);
console.log("   Private Key:", wallet.privateKey);
console.log("   Mnemonic:", wallet.mnemonic.phrase);

// 2. Wallet from private key
const privateKey = "0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
const walletFromKey = new ethers.Wallet(privateKey);
console.log("\n🔐 Wallet from Private Key:");
console.log("   Address:", walletFromKey.address);

// 3. Wallet from mnemonic
const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const walletFromMnemonic = ethers.Wallet.fromMnemonic(mnemonic);
console.log("\n📝 Wallet from Mnemonic:");
console.log("   Address:", walletFromMnemonic.address);

// 4. Connect wallet to provider
const connectedWallet = wallet.connect(provider);
console.log("\n🔗 Connected Wallet:");
console.log("   Address:", connectedWallet.address);
console.log("   Connected to:", connectedWallet.provider.connection.url);

// ===== Basic Blockchain Operations =====
console.log("\n=== BLOCKCHAIN OPERATIONS ===");

async function blockchainBasics() {
  try {
    // Get network information
    const network = await provider.getNetwork();
    console.log("🌐 Network Info:");
    console.log("   Name:", network.name);
    console.log("   Chain ID:", network.chainId);

    // Get latest block
    const block = await provider.getBlock("latest");
    console.log("\n📦 Latest Block:");
    console.log("   Number:", block.number);
    console.log("   Hash:", block.hash);
    console.log("   Timestamp:", new Date(block.timestamp * 1000).toLocaleString());
    console.log("   Gas Limit:", block.gasLimit.toString());
    console.log("   Transactions:", block.transactions.length);

    // Get account balance
    const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"; // Vitalik's address
    const balance = await provider.getBalance(address);
    console.log("\n💰 Balance Info:");
    console.log("   Address:", address);
    console.log("   Balance:", ethers.utils.formatEther(balance), "ETH");

    // Get transaction count
    const transactionCount = await provider.getTransactionCount(address);
    console.log("   Transaction Count:", transactionCount);

    // Get gas price
    const gasPrice = await provider.getGasPrice();
    console.log("\n⛽ Gas Info:");
    console.log("   Gas Price:", ethers.utils.formatUnits(gasPrice, "gwei"), "gwei");

    return { network, block, balance, gasPrice };
  } catch (error) {
    console.error("❌ Error in blockchain operations:", error.message);
    return null;
  }
}

// ===== Transaction Examples =====
console.log("\n=== TRANSACTION EXAMPLES =====");

async function transactionExamples() {
  try {
    // Send ETH transaction
    const sendWallet = new ethers.Wallet(privateKey, provider);
    const recipient = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
    const amount = ethers.utils.parseEther("0.01"); // 0.01 ETH

    console.log("💸 Sending ETH...");
    console.log("   From:", sendWallet.address);
    console.log("   To:", recipient);
    console.log("   Amount:", ethers.utils.formatEther(amount), "ETH");

    // Create transaction
    const tx = {
      to: recipient,
      value: amount,
      gasLimit: 21000, // Standard ETH transfer
    };

    // Estimate gas
    const gasEstimate = await sendWallet.estimateGas(tx);
    console.log("   Estimated Gas:", gasEstimate.toString());

    // Get current gas price
    const gasPrice = await sendWallet.getGasPrice();
    const txWithGas = { ...tx, gasPrice };

    // Calculate total cost
    const gasCost = gasEstimate.mul(gasPrice);
    const totalCost = amount.add(gasCost);
    console.log("   Gas Cost:", ethers.utils.formatEther(gasCost), "ETH");
    console.log("   Total Cost:", ethers.utils.formatEther(totalCost), "ETH");

    // Check balance
    const walletBalance = await sendWallet.getBalance();
    if (walletBalance.lt(totalCost)) {
      console.log("❌ Insufficient balance");
      return;
    }

    // Send transaction (commented out to avoid actual spend)
    console.log("   Ready to send (transaction commented out for safety)");
    /*
    const txResponse = await sendWallet.sendTransaction(txWithGas);
    console.log("   Transaction Hash:", txResponse.hash);

    // Wait for confirmation
    const receipt = await txResponse.wait();
    console.log("   Confirmed in block:", receipt.blockNumber);
    console.log("   Gas Used:", receipt.gasUsed.toString());
    */

    return { tx, gasEstimate, gasPrice };
  } catch (error) {
    console.error("❌ Error in transaction:", error.message);
    return null;
  }
}

// ===== Smart Contract Interaction =====
console.log("\n=== CONTRACT INTERACTION EXAMPLES ===");

// ERC20 ABI (minimal example)
const erc20ABI = [
  "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)",
  "event Transfer(address indexed from, address indexed to, uint256 value)",
];

// WETH contract address on mainnet
const wethAddress = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";

async function contractInteraction() {
  try {
    // Create contract instance
    const wethContract = new ethers.Contract(wethAddress, erc20ABI, provider);
    console.log("📄 Contract Instance:");
    console.log("   Address:", wethContract.address);
    console.log("   Interface:", wethContract.interface.format());

    // Read contract data (view functions)
    const name = await wethContract.name();
    const symbol = await wethContract.symbol();
    const decimals = await wethContract.decimals();
    const totalSupply = await wethContract.totalSupply();

    console.log("\n📊 Contract Data:");
    console.log("   Name:", name);
    console.log("   Symbol:", symbol);
    console.log("   Decimals:", decimals);
    console.log("   Total Supply:", ethers.utils.formatUnits(totalSupply, decimals));

    // Get balance of specific address
    const address = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
    const balance = await wethContract.balanceOf(address);
    console.log("\n💼 Balance Info:");
    console.log("   Address:", address);
    console.log("   Balance:", ethers.utils.formatUnits(balance, decimals), symbol);

    // Connect wallet for write operations
    const walletWithContract = wethContract.connect(connectedWallet);
    console.log("\n✍️  Contract ready for write operations with wallet:", connectedWallet.address);

    // Listen to events (requires WebSocket provider or polling)
    console.log("\n📡 Setting up event listeners...");

    wethContract.on("Transfer", (from, to, value, event) => {
      console.log("🎯 Transfer Event:");
      console.log("   From:", from);
      console.log("   To:", to);
      console.log("   Value:", ethers.utils.formatUnits(value, decimals), symbol);
      console.log("   Block:", event.blockNumber);
    });

    // Example of calling a function (would require ETH for WETH)
    console.log("\n💱 WETH Deposit Example (commented out):");
    console.log("   To deposit ETH for WETH: walletWithContract.deposit({ value: ethers.utils.parseEther('1') })");

    return { name, symbol, decimals, totalSupply, balance };
  } catch (error) {
    console.error("❌ Error in contract interaction:", error.message);
    return null;
  }
}

// ===== Event Handling =====
console.log("\n=== EVENT HANDLING EXAMPLES =====");

async function eventHandling() {
  try {
    // Create contract with WebSocket provider for real-time events
    const wsProvider = new ethers.providers.WebSocketProvider("wss://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY");
    const contract = new ethers.Contract(wethAddress, erc20ABI, wsProvider);

    console.log("🎧 Event Listeners Setup");

    // Listen to all Transfer events
    contract.on("Transfer", (from, to, value, event) => {
      if (ethers.BigNumber.from(value).gt(ethers.utils.parseUnits("100", 18))) {
        console.log("🚨 Large Transfer Detected!");
        console.log("   From:", from);
        console.log("   To:", to);
        console.log("   Value:", ethers.utils.formatUnits(value, 18), "WETH");
        console.log("   Transaction:", event.transactionHash);
      }
    });

    // Filter events by specific address
    const vitalikAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
    const filter = contract.filters.Transfer(null, vitalikAddress);

    contract.on(filter, (from, to, value) => {
      console.log("💰 Transfer to Vitalik:");
      console.log("   From:", from);
      console.log("   Value:", ethers.utils.formatUnits(value, 18), "WETH");
    });

    // Query historical events
    const currentBlock = await wsProvider.getBlockNumber();
    const fromBlock = currentBlock - 100; // Last 100 blocks

    console.log("\n📜 Querying historical events...");
    const historicalEvents = await contract.queryFilter(
      "Transfer",
      fromBlock,
      currentBlock
    );

    console.log(`   Found ${historicalEvents.length} transfer events in last 100 blocks`);

    // Process recent events
    historicalEvents.slice(-5).forEach(event => {
      const { from, to, value } = event.args;
      console.log(`   Recent Transfer: ${ethers.utils.formatUnits(value, 18)} WETH from ${from} to ${to}`);
    });

    return { historicalEvents };
  } catch (error) {
    console.error("❌ Error in event handling:", error.message);
    return null;
  }
}

// ===== Utility Functions =====
console.log("\n=== UTILITY FUNCTIONS ===");

function utilityExamples() {
  console.log("🔧 Ethers Utilities:");

  // Address operations
  const address = "0x70997970C51812dc3A010C7d01b50e0d17dc79C8";
  console.log("\n📍 Address Operations:");
  console.log("   Address Checksum:", ethers.utils.getAddress(address));
  console.log("   Is Address:", ethers.utils.isAddress(address));
  console.log("   Compute Address (from private key):", ethers.utils.computeAddress(privateKey));

  // Hashing
  const message = "Hello, Ethereum!";
  console.log("\n🔐 Hashing:");
  console.log("   Keccak256:", ethers.utils.keccak256(ethers.utils.toUtf8Bytes(message)));
  console.log("   Solidity Keccak256:", ethers.utils.solidityKeccak256(["string"], [message]));

  // Encoding/Decoding
  console.log("\n📝 Encoding/Decoding:");
  console.log("   UTF8 to Hex:", ethers.utils.hexlify(ethers.utils.toUtf8Bytes(message)));
  console.log("   Hex to UTF8:", ethers.utils.toUtf8String("0x48656c6c6f2c20457468657265756d21"));

  // Unit conversions
  console.log("\n🔄 Unit Conversions:");
  const weiAmount = ethers.utils.parseEther("1.5");
  console.log("   1.5 ETH to Wei:", weiAmount.toString());
  console.log("   Wei back to ETH:", ethers.utils.formatEther(weiAmount));

  const gweiAmount = ethers.utils.parseUnits("20", "gwei");
  console.log("   20 Gwei to Wei:", gweiAmount.toString());
  console.log("   Wei back to Gwei:", ethers.utils.formatUnits(gweiAmount, "gwei"));

  // Big number operations
  console.log("\n🔢 Big Number Operations:");
  const big1 = ethers.BigNumber.from("1000000000000000000");
  const big2 = ethers.BigNumber.from("2000000000000000000");
  console.log("   Big Number 1:", big1.toString());
  console.log("   Big Number 2:", big2.toString());
  console.log("   Sum:", big1.add(big2).toString());
  console.log("   Product:", big1.mul(3).toString());
  console.log("   Division:", big2.div(big1).toString());

  // ABI operations
  const iface = new ethers.utils.Interface(erc20ABI);
  console.log("\n📜 ABI Operations:");
  console.log("   Functions:", iface.functions);
  console.log("   Events:", iface.events);
  console.log("   Encode function call:", iface.encodeFunctionData("balanceOf", [address]));

  return {
    address,
    hashedMessage: ethers.utils.keccak256(ethers.utils.toUtf8Bytes(message)),
    weiAmount,
    bigNumber: big1,
  };
}

// ===== Error Handling =====
console.log("\n=== ERROR HANDLING EXAMPLES =====");

async function errorHandling() {
  try {
    // Example of catching different types of errors
    const badAddress = "0xinvalid";

    try {
      ethers.utils.getAddress(badAddress);
    } catch (error) {
      console.log("❌ Invalid Address Error:", error.message);
    }

    // Network errors
    const badProvider = new ethers.providers.JsonRpcProvider("http://invalid-url");
    try {
      await badProvider.getNetwork();
    } catch (error) {
      console.log("❌ Network Error:", error.message);
    }

    // Insufficient funds simulation
    const poorWallet = new ethers.Wallet(privateKey, provider);
    const balance = await poorWallet.getBalance();

    try {
      const largeAmount = balance.add(ethers.utils.parseEther("1"));
      await poorWallet.estimateGas({
        to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
        value: largeAmount,
      });
    } catch (error) {
      console.log("❌ Insufficient Funds Error:", error.message);
    }

    return { balance };
  } catch (error) {
    console.error("❌ Unexpected error:", error.message);
    return null;
  }
}

// ===== Main Execution =====
async function main() {
  console.log("🚀 ETHERS.JS COMPLETE GUIDE");
  console.log("=" .repeat(50));

  try {
    // Execute all examples
    await blockchainBasics();
    console.log("\n" + "-".repeat(50));

    await transactionExamples();
    console.log("\n" + "-".repeat(50));

    await contractInteraction();
    console.log("\n" + "-".repeat(50));

    await eventHandling();
    console.log("\n" + "-".repeat(50));

    utilityExamples();
    console.log("\n" + "-".repeat(50));

    await errorHandling();

    console.log("\n✅ ALL EXAMPLES COMPLETED SUCCESSFULLY!");

    console.log("\n📚 Next Steps:");
    console.log("1. Replace 'YOUR-API-KEY' with actual API keys");
    console.log("2. Uncomment transaction examples to test with real ETH");
    console.log("3. Explore more complex contract interactions");
    console.log("4. Learn about ENS, gas estimation, and transaction signing");
    console.log("5. Build a complete dApp with frontend integration");

  } catch (error) {
    console.error("\n❌ FATAL ERROR:", error.message);
  }
}

// Only run main if this file is executed directly
if (require.main === module) {
  main().catch(console.error);
}

// Export functions for individual testing
module.exports = {
  blockchainBasics,
  transactionExamples,
  contractInteraction,
  eventHandling,
  utilityExamples,
  errorHandling,
};

💻 Operaciones Avanzadas de Contratos Ethers.js javascript

🟡 intermediate ⭐⭐⭐⭐

Patrones avanzados de interacción de contratos incluyendo multisig, operaciones por lotes, optimización de gas y manejo complejo de ABI

⏱️ 35 min 🏷️ ethers, advanced, contracts, optimization
Prerequisites: Ethers.js basics, Understanding of Ethereum transactions, Solidity knowledge
// Ethers.js Advanced Contract Operations
// Complex patterns for professional dApp development

const { ethers } = require("ethers");

// ===== Multisig Operations =====
console.log("=== MULTISIG OPERATIONS ===");

// Gnosis Safe ABI (simplified)
const multisigABI = [
  "function getOwners() view returns (address[])",
  "function getThreshold() view returns (uint256)",
  "function nonce() view returns (uint256)",
  "function execTransaction(address to, uint256 value, bytes calldata data, uint8 operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, bytes calldata signatures) returns (bool success)",
  "function approveHash(bytes32 hashToApprove) external",
  "function confirmTransaction(uint256 transactionId) external",
  "event ExecutionSuccess(bytes32 indexed txHash, uint256 payment)",
  "event ExecutionFailure(bytes32 indexed txHash, uint256 payment)",
];

class MultisigManager {
  constructor(contractAddress, provider, signers = []) {
    this.contract = new ethers.Contract(contractAddress, multisigABI, provider);
    this.signers = signers;
    this.provider = provider;
  }

  async getMultisigInfo() {
    const [owners, threshold, nonce] = await Promise.all([
      this.contract.getOwners(),
      this.contract.getThreshold(),
      this.contract.nonce(),
    ]);

    return {
      owners,
      threshold: threshold.toNumber(),
      nonce: nonce.toNumber(),
      contractAddress: this.contract.address,
    };
  }

  async buildTransaction(to, value = 0, data = "0x", operation = 0) {
    const info = await this.getMultisigInfo();

    const safeTx = {
      to,
      value,
      data,
      operation,
      safeTxGas: 0, // Let contract estimate
      baseGas: 0,
      gasPrice: 0,
      gasToken: ethers.constants.AddressZero,
      refundReceiver: ethers.constants.AddressZero,
      nonce: info.nonce,
    };

    // Calculate transaction hash
    const txHash = await this.calculateTransactionHash(safeTx);

    return {
      ...safeTx,
      hash: txHash,
    };
  }

  async calculateTransactionHash(tx) {
    const domain = {
      verifyingContract: this.contract.address,
    };

    const types = {
      SafeTx: [
        { name: "to", type: "address" },
        { name: "value", type: "uint256" },
        { name: "data", type: "bytes" },
        { name: "operation", type: "uint8" },
        { name: "safeTxGas", type: "uint256" },
        { name: "baseGas", type: "uint256" },
        { name: "gasPrice", type: "uint256" },
        { name: "gasToken", type: "address" },
        { name: "refundReceiver", type: "address" },
        { name: "nonce", type: "uint256" },
      ],
    };

    return ethers.utils._TypedDataEncoder.hash(domain, types, tx);
  }

  async signTransaction(tx, signerIndex = 0) {
    if (!this.signers[signerIndex]) {
      throw new Error(`Signer at index ${signerIndex} not found`);
    }

    const signature = await this.signers[signerIndex]._signTypedData(
      { verifyingContract: this.contract.address },
      {
        SafeTx: [
          { name: "to", type: "address" },
          { name: "value", type: "uint256" },
          { name: "data", type: "bytes" },
          { name: "operation", type: "uint8" },
          { name: "safeTxGas", type: "uint256" },
          { name: "baseGas", type: "uint256" },
          { name: "gasPrice", type: "uint256" },
          { name: "gasToken", type: "address" },
          { name: "refundReceiver", type: "address" },
          { name: "nonce", type: "uint256" },
        ],
      },
      tx
    );

    return {
      signer: this.signers[signerIndex].address,
      signature,
      data: ethers.utils.joinSignature(signature),
    };
  }

  async executeTransaction(tx, signatures = []) {
    if (signatures.length === 0) {
      throw new Error("At least one signature required");
    }

    // Combine signatures
    const combinedSignatures = signatures.map(s => s.data).join("");

    const contractWithSigner = this.contract.connect(this.signers[0]);

    return await contractWithSigner.execTransaction(
      tx.to,
      tx.value,
      tx.data,
      tx.operation,
      tx.safeTxGas,
      tx.baseGas,
      tx.gasPrice,
      tx.gasToken,
      tx.refundReceiver,
      combinedSignatures
    );
  }
}

// ===== Batch Operations =====
console.log("\n=== BATCH OPERATIONS ===");

class BatchOperation {
  constructor(provider) {
    this.provider = provider;
    this.transactions = [];
  }

  addTransfer(to, amount) {
    this.transactions.push({
      type: 'transfer',
      to,
      amount: ethers.utils.parseEther(amount.toString()),
    });
    return this;
  }

  addContractInteraction(contractAddress, abi, functionName, args = [], value = 0) {
    const iface = new ethers.utils.Interface(abi);
    const data = iface.encodeFunctionData(functionName, args);

    this.transactions.push({
      type: 'contract',
      to: contractAddress,
      data,
      value: ethers.utils.parseEther(value.toString()),
    });
    return this;
  }

  async estimateGas(wallet) {
    const estimates = [];

    for (const tx of this.transactions) {
      try {
        if (tx.type === 'transfer') {
          const gasLimit = await wallet.estimateGas({
            to: tx.to,
            value: tx.amount,
          });
          estimates.push({ ...tx, gasLimit });
        } else {
          const gasLimit = await wallet.estimateGas({
            to: tx.to,
            data: tx.data,
            value: tx.value,
          });
          estimates.push({ ...tx, gasLimit });
        }
      } catch (error) {
        console.error(`Gas estimation failed for transaction: ${error.message}`);
        estimates.push({ ...tx, gasLimit: ethers.BigNumber.from(210000) });
      }
    }

    return estimates;
  }

  async execute(wallet, options = {}) {
    const gasPrice = options.gasPrice || await wallet.getGasPrice();
    const estimates = await this.estimateGas(wallet);

    const results = [];

    for (const tx of estimates) {
      try {
        const txParams = {
          to: tx.to,
          value: tx.value,
          gasLimit: tx.gasLimit,
          gasPrice,
        };

        if (tx.data) {
          txParams.data = tx.data;
        }

        const txResponse = await wallet.sendTransaction(txParams);
        const receipt = await txResponse.wait();

        results.push({
          success: true,
          hash: txResponse.hash,
          receipt,
          gasUsed: receipt.gasUsed,
        });
      } catch (error) {
        results.push({
          success: false,
          error: error.message,
          transaction: tx,
        });
      }
    }

    return results;
  }

  // Multicall alternative (using a multicall contract)
  async executeMulticall(wallet, multicallAddress) {
    const multicallABI = [
      "function aggregate((address target, bytes callData)[] calls) returns (uint256 blockNumber, bytes[] returnData)",
    ];

    const multicall = new ethers.Contract(multicallAddress, multicallABI, wallet);
    const calls = [];

    for (const tx of this.transactions) {
      calls.push({
        target: tx.to,
        callData: tx.data || "0x",
      });
    }

    try {
      const result = await multicall.aggregate(calls);
      return result;
    } catch (error) {
      console.error(`Multicall failed: ${error.message}`);
      return null;
    }
  }
}

// ===== Gas Optimization =====
console.log("\n=== GAS OPTIMIZATION ===");

class GasOptimizer {
  constructor(provider) {
    this.provider = provider;
    this.cache = new Map();
  }

  async getOptimizedGasPrice(wallet, options = {}) {
    const {
      speed = 'standard', // 'safe', 'standard', 'fast'
      maxGasPrice = ethers.utils.parseUnits('100', 'gwei'),
      multiplier = 1.1,
    } = options;

    let gasPrice;

    if (this.provider.getFeeData) {
      // EIP-1559 (Ethereum London fork and later)
      const feeData = await this.provider.getFeeData();

      if (feeData.maxFeePerGas && feeData.maxPriorityFeePerGas) {
        const multipliers = {
          safe: 0.9,
          standard: 1.0,
          fast: 1.2,
        };

        const m = multipliers[speed] || 1.0;

        gasPrice = {
          maxFeePerGas: feeData.maxFeePerGas.mul(multiplier).mul(m),
          maxPriorityFeePerGas: feeData.maxPriorityFeePerGas.mul(multiplier).mul(m),
        };
      } else {
        gasPrice = feeData.gasPrice || await wallet.getGasPrice();
      }
    } else {
      gasPrice = await wallet.getGasPrice();
    }

    // Apply multiplier
    if (typeof gasPrice === 'object') {
      gasPrice.maxFeePerGas = ethers.BigNumber.from(gasPrice.maxFeePerGas).mul(multiplier).div(100);
      gasPrice.maxPriorityFeePerGas = ethers.BigNumber.from(gasPrice.maxPriorityFeePerGas).mul(multiplier).div(100);
    } else {
      gasPrice = ethers.BigNumber.from(gasPrice).mul(multiplier).div(100);
    }

    // Cap maximum gas price
    if (typeof gasPrice === 'object') {
      if (gasPrice.maxFeePerGas.gt(maxGasPrice)) {
        gasPrice.maxFeePerGas = maxGasPrice;
      }
    } else {
      if (gasPrice.gt(maxGasPrice)) {
        gasPrice = maxGasPrice;
      }
    }

    return gasPrice;
  }

  async estimateAndOptimize(transaction, wallet, options = {}) {
    const {
      gasBuffer = 1.2, // 20% buffer
      maxGasLimit = 8000000,
    } = options;

    try {
      // Initial gas estimation
      let gasEstimate = await wallet.estimateGas(transaction);

      // Apply buffer
      gasEstimate = gasEstimate.mul(Math.floor(gasBuffer * 100)).div(100);

      // Cap maximum
      if (gasEstimate.gt(maxGasLimit)) {
        gasEstimate = ethers.BigNumber.from(maxGasLimit);
      }

      // Check if we can optimize by adjusting data
      if (transaction.data) {
        const optimizedData = this.optimizeCalldata(transaction.data);
        if (optimizedData !== transaction.data) {
          const optimizedTx = { ...transaction, data: optimizedData };
          const optimizedGas = await wallet.estimateGas(optimizedTx);

          if (optimizedGas.lt(gasEstimate)) {
            return {
              ...transaction,
              data: optimizedData,
              gasLimit: optimizedGas,
              originalGasLimit: gasEstimate,
              savings: gasEstimate.sub(optimizedGas),
            };
          }
        }
      }

      return {
        ...transaction,
        gasLimit: gasEstimate,
      };
    } catch (error) {
      console.error(`Gas estimation failed: ${error.message}`);
      return {
        ...transaction,
        gasLimit: ethers.BigNumber.from(500000), // Fallback
      };
    }
  }

  optimizeCalldata(calldata) {
    // Remove leading zeros from calldata
    if (calldata.startsWith('0x')) {
      const hex = calldata.slice(2);
      // Remove unnecessary padding zeros
      const optimized = hex.replace(/0+$/, '');
      return '0x' + optimized;
    }
    return calldata;
  }

  async simulateTransaction(transaction, blockNumber = 'latest') {
    try {
      return await this.provider.call(transaction, blockNumber);
    } catch (error) {
      return { error: error.message };
    }
  }
}

// ===== Advanced ABI Handling =====
console.log("\n=== ADVANCED ABI HANDLING =====");

class ABIManager {
  constructor() {
    this.interfaces = new Map();
    this.caches = new Map();
  }

  loadInterface(name, abi) {
    const iface = new ethers.utils.Interface(abi);
    this.interfaces.set(name, iface);
    return iface;
  }

  getInterface(name) {
    return this.interfaces.get(name);
  }

  decodeLog(log, abi) {
    const iface = typeof abi === 'string' ? this.getInterface(abi) : new ethers.utils.Interface(abi);

    try {
      return iface.parseLog(log);
    } catch (error) {
      console.warn(`Failed to decode log: ${error.message}`);
      return null;
    }
  }

  decodeTransactionResult(transaction, abi) {
    const iface = typeof abi === 'string' ? this.getInterface(abi) : new ethers.utils.Interface(abi);

    try {
      return iface.decodeFunctionResult(transaction.data, transaction.value);
    } catch (error) {
      console.warn(`Failed to decode result: ${error.message}`);
      return null;
    }
  }

  encodeFunctionCall(contractName, functionName, args, abi) {
    const iface = typeof abi === 'string' ? this.getInterface(abi) : new ethers.utils.Interface(abi);

    try {
      return iface.encodeFunctionData(functionName, args);
    } catch (error) {
      console.error(`Failed to encode function call: ${error.message}`);
      return null;
    }
  }

  // Dynamic ABI loading
  async loadABIFromAddress(address, provider) {
    const cacheKey = `abi_${address}`;

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

    try {
      // This would typically use an API like Etherscan
      // For example purposes, we'll return a mock ABI
      const mockABI = [
        "function name() view returns (string)",
        "function symbol() view returns (string)",
      ];

      const iface = new ethers.utils.Interface(mockABI);
      this.caches.set(cacheKey, iface);
      return iface;
    } catch (error) {
      console.error(`Failed to load ABI for ${address}: ${error.message}`);
      return null;
    }
  }
}

// ===== Event Processing =====
console.log("\n=== EVENT PROCESSING ===");

class EventProcessor {
  constructor(provider) {
    this.provider = provider;
    this.filters = new Map();
    this.handlers = new Map();
  }

  addEventListener(contractAddress, eventName, handler, abi) {
    const key = `${contractAddress}_${eventName}`;

    if (!this.filters.has(key)) {
      const contract = new ethers.Contract(contractAddress, abi, this.provider);
      const filter = contract.filters[eventName]();
      this.filters.set(key, { contract, filter });
    }

    if (!this.handlers.has(key)) {
      this.handlers.set(key, []);
    }

    this.handlers.get(key).push(handler);
  }

  async startListening() {
    for (const [key, { contract, filter }] of this.filters) {
      contract.on(filter, (...args) => {
        const handlers = this.handlers.get(key) || [];
        handlers.forEach(handler => {
          try {
            handler(...args);
          } catch (error) {
            console.error(`Event handler error: ${error.message}`);
          }
        });
      });
    }
  }

  async getHistoricalEvents(contractAddress, eventName, fromBlock, toBlock, abi) {
    const contract = new ethers.Contract(contractAddress, abi, this.provider);
    const filter = contract.filters[eventName]();

    try {
      return await contract.queryFilter(filter, fromBlock, toBlock);
    } catch (error) {
      console.error(`Failed to get historical events: ${error.message}`);
      return [];
    }
  }

  // Event aggregation and analytics
  async aggregateEvents(contractAddress, eventName, fromBlock, toBlock, abi, aggregationFn) {
    const events = await this.getHistoricalEvents(contractAddress, eventName, fromBlock, toBlock, abi);
    return aggregationFn(events);
  }
}

// ===== Main Examples =====
async function advancedExamples() {
  console.log("🚀 ADVANCED ETHERS.JS EXAMPLES");

  try {
    // Initialize provider (use your preferred provider)
    const provider = new ethers.providers.JsonRpcProvider("https://eth-mainnet.alchemyapi.io/v2/YOUR-API-KEY");

    // Example 1: Multisig Operations
    console.log("\n1. Multisig Operations:");
    const multisig = new MultisigManager(
      "0x...YOUR_MULTISIG_ADDRESS...",
      provider
    );

    // Example transaction to multisig
    const tx = await multisig.buildTransaction(
      "0x...RECIPIENT...",
      ethers.utils.parseEther("0.1"),
      "0x" // Optional calldata
    );

    console.log("   Multisig transaction built:", tx.hash);

    // Example 2: Batch Operations
    console.log("\n2. Batch Operations:");
    const wallet = new ethers.Wallet("YOUR_PRIVATE_KEY", provider);
    const batch = new BatchOperation(provider);

    batch.addTransfer("0x...RECIPIENT1...", 0.1);
    batch.addTransfer("0x...RECIPIENT2...", 0.05);

    console.log("   Batch created with", batch.transactions.length, "transactions");

    // Example 3: Gas Optimization
    console.log("\n3. Gas Optimization:");
    const gasOptimizer = new GasOptimizer(provider);

    const optimizedTx = await gasOptimizer.estimateAndOptimize(
      {
        to: "0x...RECIPIENT...",
        value: ethers.utils.parseEther("0.1"),
      },
      wallet,
      { speed: 'standard' }
    );

    console.log("   Optimized gas limit:", optimizedTx.gasLimit.toString());

    // Example 4: Advanced ABI Handling
    console.log("\n4. Advanced ABI Handling:");
    const abiManager = new ABIManager();

    const erc20ABI = [
      "function name() view returns (string)",
      "function symbol() view returns (string)",
      "function balanceOf(address) view returns (uint256)",
    ];

    const iface = abiManager.loadInterface("ERC20", erc20ABI);
    console.log("   Loaded ERC20 interface with", Object.keys(iface.functions).length, "functions");

    // Example 5: Event Processing
    console.log("\n5. Event Processing:");
    const eventProcessor = new EventProcessor(provider);

    // Setup event listener (example)
    eventProcessor.addEventListener(
      "0x...CONTRACT_ADDRESS...",
      "Transfer",
      (from, to, value, event) => {
        console.log(`   Transfer: ${ethers.utils.formatEther(value)} from ${from} to ${to}`);
      },
      erc20ABI
    );

    console.log("   Event processor configured");

    console.log("\n✅ Advanced examples completed successfully!");

  } catch (error) {
    console.error("\n❌ Error in advanced examples:", error.message);
  }
}

// Utility function for testing
function testAdvancedFeatures() {
  console.log("\n=== TESTING ADVANCED FEATURES ===");

  // Test batch operations
  const batch = new BatchOperation(null);
  batch.addTransfer("0x1234...", 1.5);
  batch.addTransfer("0x5678...", 2.3);
  console.log("Batch operations test:", batch.transactions.length, "transactions");

  // Test gas optimizer
  const optimizer = new GasOptimizer(null);
  const optimizedData = optimizer.optimizeCalldata("0x0000000000000000000000000000000000000000000000000000000000000001");
  console.log("Calldata optimization:", optimizedData);

  // Test ABI manager
  const abiManager = new ABIManager();
  const simpleABI = ["function test() view returns (string)"];
  abiManager.loadInterface("Test", simpleABI);
  console.log("ABI manager test:", abiManager.interfaces.size, "interfaces loaded");
}

// Export for use in other modules
module.exports = {
  MultisigManager,
  BatchOperation,
  GasOptimizer,
  ABIManager,
  EventProcessor,
  advancedExamples,
  testAdvancedFeatures,
};

// Run examples if executed directly
if (require.main === module) {
  advancedExamples();
  testAdvancedFeatures();
}

console.log("\n📚 Advanced Ethers.js patterns implemented:");
console.log("✅ Multisig transaction management");
console.log("✅ Batch operations and multicall");
console.log("✅ Gas optimization strategies");
console.log("✅ Advanced ABI handling");
console.log("✅ Event processing and analytics");
console.log("✅ Error handling and recovery");

💻 Integración de dApp Ethers.js javascript

🟡 intermediate ⭐⭐⭐

Patrones completos de integración de dApp incluyendo conexión MetaMask, despliegue de contratos, integración UI y gestión de estado

⏱️ 30 min 🏷️ ethers, dapp, frontend, wallet
Prerequisites: Ethers.js basics, Understanding of web apps, MetaMask or similar wallet
// Ethers.js dApp Integration
// Complete frontend integration with wallet providers, contracts, and UI

// ===== Wallet Connection Manager =====
class WalletConnectionManager {
  constructor() {
    this.provider = null;
    this.signer = null;
    this.account = null;
    this.chainId = null;
    this.listeners = new Map();
  }

  async connectWallet(walletType = 'metamask') {
    try {
      if (walletType === 'metamask') {
        if (!window.ethereum) {
          throw new Error('MetaMask is not installed');
        }

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

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

        // Get signer
        this.signer = this.provider.getSigner();
        this.account = await this.signer.getAddress();

        // Get network
        const network = await this.provider.getNetwork();
        this.chainId = network.chainId;

        // Setup event listeners
        this.setupEventListeners();

        console.log(`✅ Connected to ${walletType}`);
        console.log(`   Account: ${this.account}`);
        console.log(`   Chain ID: ${this.chainId}`);

        this.emit('connected', {
          account: this.account,
          chainId: this.chainId,
          walletType,
        });

        return { account: this.account, chainId: this.chainId };
      }
    } catch (error) {
      console.error(`❌ Failed to connect to ${walletType}:`, error.message);
      this.emit('error', error);
      throw error;
    }
  }

  async disconnectWallet() {
    try {
      this.provider = null;
      this.signer = null;
      this.account = null;
      this.chainId = null;

      // Clear event listeners
      this.clearEventListeners();

      this.emit('disconnected');
      console.log('✅ Wallet disconnected');
    } catch (error) {
      console.error('❌ Error disconnecting wallet:', error.message);
      this.emit('error', error);
    }
  }

  setupEventListeners() {
    if (!window.ethereum) return;

    // Account change
    window.ethereum.on('accountsChanged', (accounts) => {
      if (accounts.length === 0) {
        this.disconnectWallet();
      } else {
        this.account = accounts[0];
        this.emit('accountChanged', this.account);
      }
    });

    // Chain change
    window.ethereum.on('chainChanged', (chainId) => {
      this.chainId = parseInt(chainId, 16);
      this.emit('chainChanged', this.chainId);
    });

    // Connect
    window.ethereum.on('connect', (connectInfo) => {
      this.chainId = parseInt(connectInfo.chainId, 16);
      this.emit('connected', { chainId: this.chainId });
    });

    // Disconnect
    window.ethereum.on('disconnect', (error) => {
      this.emit('disconnected', error);
    });
  }

  clearEventListeners() {
    if (!window.ethereum) return;

    // Remove all listeners (MetaMask doesn't expose individual removal)
    window.ethereum.removeAllListeners();
  }

  // Event emitter methods
  on(event, callback) {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, []);
    }
    this.listeners.get(event).push(callback);
  }

  off(event, callback) {
    if (this.listeners.has(event)) {
      const callbacks = this.listeners.get(event);
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
  }

  emit(event, data) {
    if (this.listeners.has(event)) {
      this.listeners.get(event).forEach(callback => {
        try {
          callback(data);
        } catch (error) {
          console.error(`Event listener error for ${event}:`, error);
        }
      });
    }
  }

  async switchNetwork(chainId) {
    try {
      await window.ethereum.request({
        method: 'wallet_switchEthereumChain',
        params: [{ chainId: `0x${chainId.toString(16)}` }],
      });
    } catch (error) {
      // This error code indicates that the chain has not been added to MetaMask
      if (error.code === 4902) {
        await this.addNetwork(chainId);
      } else {
        throw error;
      }
    }
  }

  async addNetwork(chainId) {
    const networks = {
      1: {
        chainId: '0x1',
        chainName: 'Ethereum Mainnet',
        rpcUrls: ['https://mainnet.infura.io/v3/YOUR-PROJECT-ID'],
        nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
      },
      5: {
        chainId: '0x5',
        chainName: 'Goerli Testnet',
        rpcUrls: ['https://goerli.infura.io/v3/YOUR-PROJECT-ID'],
        nativeCurrency: { name: 'ETH', symbol: 'ETH', decimals: 18 },
      },
      // Add more networks as needed
    };

    const networkConfig = networks[chainId];
    if (!networkConfig) {
      throw new Error(`Network configuration not found for chainId ${chainId}`);
    }

    await window.ethereum.request({
      method: 'wallet_addEthereumChain',
      params: [networkConfig],
    });
  }

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

    try {
      const signature = await this.signer.signMessage(message);
      return signature;
    } catch (error) {
      console.error('❌ Failed to sign message:', error.message);
      throw error;
    }
  }
}

// ===== Contract Manager =====
class ContractManager {
  constructor(walletManager) {
    this.walletManager = walletManager;
    this.contracts = new Map();
    this.deployedContracts = new Map();
  }

  loadContract(name, address, abi) {
    if (!this.walletManager.signer) {
      throw new Error('Wallet not connected');
    }

    const contract = new ethers.Contract(address, abi, this.walletManager.signer);
    this.contracts.set(name, contract);

    console.log(`✅ Contract ${name} loaded at ${address}`);
    return contract;
  }

  getContract(name) {
    const contract = this.contracts.get(name);
    if (!contract) {
      throw new Error(`Contract ${name} not found`);
    }
    return contract;
  }

  async deployContract(name, factoryClass, constructorArgs = []) {
    if (!this.walletManager.signer) {
      throw new Error('Wallet not connected');
    }

    try {
      console.log(`🚀 Deploying ${name}...`);

      const factory = new factoryClass();
      const contract = await factory.deploy(...constructorArgs);

      console.log(`⏳ Waiting for deployment...`);
      await contract.deployed();

      console.log(`✅ ${name} deployed at: ${contract.address}`);

      this.contracts.set(name, contract);
      this.deployedContracts.set(name, {
        address: contract.address,
        transactionHash: contract.deployTransaction.hash,
        gasUsed: contract.deployTransaction.gasLimit,
      });

      this.walletManager.emit('contractDeployed', {
        name,
        address: contract.address,
        transactionHash: contract.deployTransaction.hash,
      });

      return contract;
    } catch (error) {
      console.error(`❌ Failed to deploy ${name}:`, error.message);
      this.walletManager.emit('error', error);
      throw error;
    }
  }

  async callContractMethod(contractName, methodName, args = [], options = {}) {
    try {
      const contract = this.getContract(contractName);

      console.log(`📞 Calling ${contractName}.${methodName} with args:`, args);

      const gasEstimate = await contract.estimateGas[methodName](...args);
      const gasPrice = await this.walletManager.provider.getGasPrice();

      const txOptions = {
        gasLimit: gasEstimate.mul(120).div(100), // 20% buffer
        gasPrice,
        ...options,
      };

      const tx = await contract[methodName](...args, txOptions);
      console.log(`⏳ Transaction sent: ${tx.hash}`);

      const receipt = await tx.wait();
      console.log(`✅ Transaction confirmed in block: ${receipt.blockNumber}`);

      this.walletManager.emit('transactionConfirmed', {
        contractName,
        methodName,
        transactionHash: tx.hash,
        blockNumber: receipt.blockNumber,
        gasUsed: receipt.gasUsed,
      });

      return { tx, receipt };
    } catch (error) {
      console.error(`❌ Failed to call ${contractName}.${methodName}:`, error.message);
      this.walletManager.emit('error', error);
      throw error;
    }
  }

  async readContractMethod(contractName, methodName, args = []) {
    try {
      const contract = this.getContract(contractName);

      const result = await contract[methodName](...args);

      console.log(`📖 Read ${contractName}.${methodName}:`, result);
      return result;
    } catch (error) {
      console.error(`❌ Failed to read ${contractName}.${methodName}:`, error.message);
      throw error;
    }
  }

  setupContractEventListener(contractName, eventName, callback) {
    const contract = this.getContract(contractName);

    contract.on(eventName, (...args) => {
      console.log(`🎯 ${contractName} event: ${eventName}`, args);
      callback(...args);
    });

    console.log(`👂 Listening for ${eventName} events on ${contractName}`);
  }
}

// ===== UI State Manager =====
class UIStateManager {
  constructor() {
    this.state = {
      walletConnected: false,
      account: null,
      chainId: null,
      balance: '0',
      contracts: {},
      loading: false,
      error: null,
    };
    this.subscribers = [];
  }

  setState(updates) {
    this.state = { ...this.state, ...updates };
    this.notifySubscribers();
  }

  getState() {
    return this.state;
  }

  subscribe(callback) {
    this.subscribers.push(callback);
    callback(this.state); // Send current state immediately

    // Return unsubscribe function
    return () => {
      const index = this.subscribers.indexOf(callback);
      if (index > -1) {
        this.subscribers.splice(index, 1);
      }
    };
  }

  notifySubscribers() {
    this.subscribers.forEach(callback => {
      try {
        callback(this.state);
      } catch (error) {
        console.error('State subscriber error:', error);
      }
    });
  }

  async updateBalance(provider, account) {
    try {
      const balance = await provider.getBalance(account);
      this.setState({ balance: ethers.utils.formatEther(balance) });
    } catch (error) {
      console.error('Failed to update balance:', error.message);
    }
  }
}

// ===== dApp Component Examples =====

// React-like component structure
class WalletConnectComponent {
  constructor(walletManager, stateManager) {
    this.walletManager = walletManager;
    this.stateManager = stateManager;
    this.element = null;
    this.unsubscribe = null;
  }

  mount(elementId) {
    this.element = document.getElementById(elementId);
    if (!this.element) {
      throw new Error(`Element ${elementId} not found`);
    }

    this.unsubscribe = this.stateManager.subscribe((state) => {
      this.render(state);
    });

    this.render(this.stateManager.getState());

    // Setup event listeners
    this.walletManager.on('connected', (data) => {
      this.stateManager.setState({
        walletConnected: true,
        account: data.account,
        chainId: data.chainId,
      });
    });

    this.walletManager.on('disconnected', () => {
      this.stateManager.setState({
        walletConnected: false,
        account: null,
        chainId: null,
        balance: '0',
      });
    });
  }

  render(state) {
    if (!this.element) return;

    if (state.walletConnected) {
      this.element.innerHTML = `
        <div class="wallet-connected">
          <div class="account-info">
            <span class="account">${this.formatAddress(state.account)}</span>
            <span class="balance">${parseFloat(state.balance).toFixed(4)} ETH</span>
            <span class="chain">Chain: ${state.chainId}</span>
          </div>
          <button onclick="window.dApp.disconnectWallet()">Disconnect</button>
        </div>
      `;
    } else {
      this.element.innerHTML = `
        <div class="wallet-disconnected">
          <button onclick="window.dApp.connectWallet()">Connect Wallet</button>
        </div>
      `;
    }
  }

  formatAddress(address) {
    if (!address) return '';
    return `${address.slice(0, 6)}...${address.slice(-4)}`;
  }

  unmount() {
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }
}

// Contract Interaction Component
class ContractInteractionComponent {
  constructor(contractManager, stateManager) {
    this.contractManager = contractManager;
    this.stateManager = stateManager;
    this.element = null;
    this.unsubscribe = null;
  }

  mount(elementId) {
    this.element = document.getElementById(elementId);
    if (!this.element) {
      throw new Error(`Element ${elementId} not found`);
    }

    this.unsubscribe = this.stateManager.subscribe((state) => {
      this.render(state);
    });

    this.render(this.stateManager.getState());
  }

  render(state) {
    if (!this.element) return;

    if (!state.walletConnected) {
      this.element.innerHTML = '<p>Please connect your wallet to interact with contracts</p>';
      return;
    }

    this.element.innerHTML = `
      <div class="contract-interaction">
        <h3>Contract Interaction</h3>

        <div class="form-group">
          <label>Value to Store:</label>
          <input type="number" id="valueInput" placeholder="Enter value" />
        </div>

        <div class="form-group">
          <label>Message:</label>
          <input type="text" id="messageInput" placeholder="Enter message" />
        </div>

        <button onclick="window.dApp.storeValue()">Store Value</button>
        <button onclick="window.dApp.retrieveValue()">Retrieve Value</button>

        <div id="contractResult"></div>
      </div>
    `;
  }

  async storeValue() {
    const valueInput = document.getElementById('valueInput');
    const messageInput = document.getElementById('messageInput');
    const resultDiv = document.getElementById('contractResult');

    try {
      const value = parseInt(valueInput.value);
      const message = messageInput.value;

      this.stateManager.setState({ loading: true });

      const { tx, receipt } = await this.contractManager.callContractMethod(
        'SimpleStorage',
        'store',
        [value, message]
      );

      resultDiv.innerHTML = `
        <div class="success">
          <h4>✅ Transaction Confirmed</h4>
          <p>Hash: ${tx.hash}</p>
          <p>Block: ${receipt.blockNumber}</p>
          <p>Gas Used: ${receipt.gasUsed.toString()}</p>
        </div>
      `;
    } catch (error) {
      resultDiv.innerHTML = `
        <div class="error">
          <h4>❌ Error</h4>
          <p>${error.message}</p>
        </div>
      `;
    } finally {
      this.stateManager.setState({ loading: false });
    }
  }

  async retrieveValue() {
    const resultDiv = document.getElementById('contractResult');

    try {
      this.stateManager.setState({ loading: true });

      const result = await this.contractManager.readContractMethod(
        'SimpleStorage',
        'retrieve'
      );

      resultDiv.innerHTML = `
        <div class="result">
          <h4>📖 Retrieved Value</h4>
          <p>Value: ${result[0].toString()}</p>
          <p>Message: ${result[1]}</p>
        </div>
      `;
    } catch (error) {
      resultDiv.innerHTML = `
        <div class="error">
          <h4>❌ Error</h4>
          <p>${error.message}</p>
        </div>
      `;
    } finally {
      this.stateManager.setState({ loading: false });
    }
  }
}

// ===== Main dApp Class =====
class DApp {
  constructor() {
    this.walletManager = new WalletConnectionManager();
    this.stateManager = new UIStateManager();
    this.contractManager = new ContractManager(this.walletManager);

    this.components = {};

    // Make dApp globally available for button clicks
    window.dApp = this;
  }

  async initialize() {
    console.log('🚀 Initializing dApp...');

    try {
      // Initialize components
      this.components.walletConnect = new WalletConnectComponent(
        this.walletManager,
        this.stateManager
      );

      this.components.contractInteraction = new ContractInteractionComponent(
        this.contractManager,
        this.stateManager
      );

      // Mount components
      this.components.walletConnect.mount('wallet-connect');
      this.components.contractInteraction.mount('contract-interaction');

      // Setup error handling
      this.walletManager.on('error', (error) => {
        this.stateManager.setState({ error: error.message });
        console.error('dApp error:', error);
      });

      // Try to auto-connect if previously connected
      if (window.ethereum && window.ethereum.selectedAddress) {
        await this.connectWallet();
      }

      console.log('✅ dApp initialized successfully');
    } catch (error) {
      console.error('❌ Failed to initialize dApp:', error.message);
    }
  }

  async connectWallet() {
    try {
      await this.walletManager.connectWallet('metamask');

      // Update balance
      await this.stateManager.updateBalance(
        this.walletManager.provider,
        this.walletManager.account
      );

      // Load example contract
      const simpleStorageABI = [
        "function store(uint256 value, string memory message)",
        "function retrieve() view returns (uint256, string memory)",
      ];

      // Example contract address (replace with your deployed contract)
      const contractAddress = "0x...YOUR_CONTRACT_ADDRESS...";
      this.contractManager.loadContract('SimpleStorage', contractAddress, simpleStorageABI);
    } catch (error) {
      console.error('Failed to connect wallet:', error);
    }
  }

  async disconnectWallet() {
    try {
      await this.walletManager.disconnectWallet();
    } catch (error) {
      console.error('Failed to disconnect wallet:', error);
    }
  }

  // Delegate methods for component interaction
  async storeValue() {
    return this.components.contractInteraction.storeValue();
  }

  async retrieveValue() {
    return this.components.contractInteraction.retrieveValue();
  }
}

// ===== HTML Template =====
const htmlTemplate = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Ethers.js dApp</title>
  <style>
    body {
      font-family: Arial, sans-serif;
      max-width: 800px;
      margin: 0 auto;
      padding: 20px;
    }

    .wallet-connected {
      display: flex;
      justify-content: space-between;
      align-items: center;
      background: #f0f0f0;
      padding: 10px;
      border-radius: 5px;
    }

    .account-info {
      display: flex;
      gap: 20px;
    }

    button {
      background: #007bff;
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 5px;
      cursor: pointer;
    }

    button:hover {
      background: #0056b3;
    }

    .contract-interaction {
      margin-top: 20px;
      padding: 20px;
      border: 1px solid #ddd;
      border-radius: 5px;
    }

    .form-group {
      margin-bottom: 15px;
    }

    .form-group label {
      display: block;
      margin-bottom: 5px;
    }

    .form-group input {
      width: 100%;
      padding: 8px;
      border: 1px solid #ddd;
      border-radius: 3px;
    }

    .success {
      background: #d4edda;
      color: #155724;
      padding: 10px;
      border-radius: 3px;
      margin-top: 10px;
    }

    .error {
      background: #f8d7da;
      color: #721c24;
      padding: 10px;
      border-radius: 3px;
      margin-top: 10px;
    }

    .result {
      background: #e2e3e5;
      padding: 10px;
      border-radius: 3px;
      margin-top: 10px;
    }
  </style>
</head>
<body>
  <h1>Ethers.js dApp Example</h1>

  <div id="wallet-connect"></div>

  <div id="contract-interaction"></div>

  <script src="dapp.js"></script>
  <script>
    // Initialize dApp when page loads
    document.addEventListener('DOMContentLoaded', () => {
      const dapp = new DApp();
      dapp.initialize();
    });
  </script>
</body>
</html>
`;

console.log("🌐 Ethers.js dApp integration ready!");
console.log("\n📋 Features implemented:");
console.log("✅ Wallet connection management");
console.log("✅ Contract interaction");
console.log("✅ State management");
console.log("✅ UI components");
console.log("✅ Event handling");
console.log("✅ Error handling");
console.log("✅ Transaction management");

console.log("\n📁 To use this dApp:");
console.log("1. Save the HTML template as index.html");
console.log("2. Save this JavaScript as dapp.js");
console.log("3. Open index.html in your browser");
console.log("4. Install MetaMask and connect");
console.log("5. Deploy your own contracts and update addresses");

// Export for use in other modules
module.exports = {
  WalletConnectionManager,
  ContractManager,
  UIStateManager,
  WalletConnectComponent,
  ContractInteractionComponent,
  DApp,
  htmlTemplate,
};

// Auto-initialize if loaded in browser
if (typeof window !== 'undefined') {
  window.DApp = DApp;
  window.WalletConnectionManager = WalletConnectionManager;
  window.ContractManager = ContractManager;
}