Master advanced Ethers.js patterns for production applications.
ENS (Ethereum Name Service) lets you use human-readable names instead of addresses.
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
);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);
}
}
}Use multicall to fetch data from multiple contracts in a single request.
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;
}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;
}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);
}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);
}
}