Scholarpeak

Advanced Patterns

Master advanced Ethers.js patterns for production applications.

ENS Names & Address Resolution

ENS (Ethereum Name Service) lets you use human-readable names instead of addresses.

typescript
import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://eth.llamarpc.com');

// Resolve ENS name to address
const address = await provider.resolveName('vitalik.eth');
console.log('Address:', address);

// Reverse lookup (address to ENS name)
const name = await provider.lookupAddress('0xd8dA6BF26964aF9D7eEd9e03E53415D37AA96045');
console.log('Name:', name); // "vitalik.eth"

// Get avatar URL from ENS
const avatar = await provider.getAvatar('vitalik.eth');
console.log('Avatar:', avatar);

// Use ENS names directly in contract calls
const contract = new ethers.Contract(
  'uniswap.eth', // Use ENS name!
  ABI,
  provider
);

Robust Error Handling

typescript
import { ethers } from 'ethers';

async function sendTransactionSafely(signer) {
  try {
    // Estimate gas first
    const estimatedGas = await signer.estimateGas({
      to: '0x...',
      value: ethers.parseEther('1')
    });

    console.log('Estimated gas:', estimatedGas.toString());

    // Send with error handling
    const tx = await signer.sendTransaction({
      to: '0x...',
      value: ethers.parseEther('1'),
      gasLimit: estimatedGas * 120n / 100n // Add 20% buffer
    });

    const receipt = await tx.wait();

    if (!receipt) {
      console.error('Transaction failed - no receipt');
      return;
    }

    if (receipt.status === 0) {
      console.error('Transaction reverted');
      return;
    }

    console.log('Success!', receipt.transactionHash);

  } catch (error) {
    // Handle specific errors
    if (error.code === 'INSUFFICIENT_FUNDS') {
      console.error('Not enough ETH to send');
    } else if (error.code === 'NONCE_EXPIRED') {
      console.error('Transaction nonce expired');
    } else if (error.code === 'CALL_EXCEPTION') {
      console.error('Contract call failed:', error.reason);
    } else {
      console.error('Unknown error:', error);
    }
  }
}

Batching Multiple Calls

Use multicall to fetch data from multiple contracts in a single request.

typescript
import { ethers } from 'ethers';

async function batchFetchBalances(provider, addresses) {
  const ERC20_ABI = [
    'function balanceOf(address) public view returns (uint256)',
    'function decimals() public view returns (uint8)'
  ];

  const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
  const contract = new ethers.Contract(USDC, ERC20_ABI, provider);

  // Batch calls
  try {
    const calls = addresses.map(addr => 
      contract.balanceOf(addr)
    );

    const balances = await Promise.all(calls);

    balances.forEach((balance, i) => {
      console.log(`${addresses[i]}: ${ethers.formatUnits(balance, 6)} USDC`);
    });

  } catch (error) {
    console.error('Batch call failed:', error);
  }
}

// Better: Use ethers staticCall for better performance
async function batchCallsOptimized(provider, addresses) {
  const ERC20_ABI = ['function balanceOf(address) public view returns (uint256)'];
  const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
  const contract = new ethers.Contract(USDC, ERC20_ABI, provider);

  const balances = await Promise.all(
    addresses.map(addr => contract.balanceOf.staticCall(addr))
  );

  return balances;
}

Multi-Chain Support

typescript
import { ethers } from 'ethers';

const chains = {
  mainnet: {
    name: 'Ethereum',
    rpc: 'https://eth.llamarpc.com',
    chainId: 1
  },
  polygon: {
    name: 'Polygon',
    rpc: 'https://rpc.ankr.com/polygon',
    chainId: 137
  },
  arbitrum: {
    name: 'Arbitrum',
    rpc: 'https://rpc.ankr.com/arbitrum',
    chainId: 42161
  }
};

// Create providers for multiple chains
const providers = {};
for (const [key, chain] of Object.entries(chains)) {
  providers[key] = new ethers.JsonRpcProvider(chain.rpc);
}

// Get balance on each chain
async function getBalancesMultiChain(address) {
  const balances = {};

  for (const [chain, provider] of Object.entries(providers)) {
    try {
      const balance = await provider.getBalance(address);
      balances[chain] = ethers.formatEther(balance);
    } catch (error) {
      balances[chain] = 'Error';
    }
  }

  return balances;
}

Gas Optimization Strategies

typescript
import { ethers } from 'ethers';

async function optimizeGas(signer) {
  const provider = signer.provider;

  // Get current gas prices
  const feeData = await provider.getFeeData();
  console.log('Current base fee:', ethers.formatUnits(feeData.gasPrice, 'gwei'), 'gwei');

  // Strategy 1: Set higher priority fee during congestion
  const tx1 = {
    to: '0x...',
    value: ethers.parseEther('1'),
    maxFeePerGas: feeData.maxFeePerGas,
    maxPriorityFeePerGas: ethers.parseUnits('2', 'gwei') // Tip
  };

  // Strategy 2: Batch multiple operations
  const tx2 = {
    to: '0x...',
    data: '0x...' // Encoded function call for multiple operations
  };

  // Strategy 3: Use cheaper chains for testing
  const testnetProvider = new ethers.JsonRpcProvider(
    'https://sepolia.infura.io/v3/YOUR_API_KEY'
  );

  // Always estimate before sending
  const estimated = await provider.estimateGas(tx1);
  console.log('Estimated gas:', estimated.toString());

  // Send with gas buffer
  const finalTx = {
    ...tx1,
    gasLimit: estimated * 120n / 100n // 20% buffer
  };

  await signer.sendTransaction(finalTx);
}

Retries & Resilience

typescript
import { ethers } from 'ethers';

// Retry logic for failed operations
async function retryWithBackoff(
  fn,
  maxRetries = 3,
  delayMs = 1000
) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      console.log(`Attempt ${attempt} failed: ${error.message}`);

      if (attempt === maxRetries) {
        throw error;
      }

      // Exponential backoff: 1s, 2s, 4s, etc.
      const delay = delayMs * Math.pow(2, attempt - 1);
      console.log(`Retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

// Usage
async function reliableTransaction(signer) {
  await retryWithBackoff(async () => {
    const tx = await signer.sendTransaction({
      to: '0x...',
      value: ethers.parseEther('0.1')
    });
    return await tx.wait();
  });
}

// Fallback providers for reliability
const mainProvider = new ethers.JsonRpcProvider(
  'https://eth.llamarpc.com'
);

const fallbackProvider = new ethers.JsonRpcProvider(
  'https://sepolia.infura.io/v3/YOUR_API_KEY'
);

// Try main, fall back if it fails
async function getBalanceFallback(address) {
  try {
    return await mainProvider.getBalance(address);
  } catch (error) {
    console.warn('Main provider failed, using fallback');
    return await fallbackProvider.getBalance(address);
  }
}