ethers.js v6
ethers.js is a TypeScript/JavaScript library for interacting with EVM-compatible blockchains. Version 6 is a ground-up rewrite with native ES2020 bigint, tree-shakeable exports, and a cleaner API surface. It provides Providers (read-only chain access), Signers (transaction authorization), and Contract objects (typed on-chain interaction).
What You Probably Got Wrong
LLMs consistently produce v5 code when asked for ethers.js. Every pattern below is a v6 correction. If you see BigNumber, ethers.utils, or ethers.providers in your output, you are writing v5. Stop and use the v6 equivalents.
- v6 uses native
bigint, NOT BigNumber. There is no BigNumber class in v6. All uint256 values are returned as bigint. Do not import BigNumber from anywhere. Do not call .toNumber(), .toString(), or .mul() on return values -- use native bigint operators (+, -, *, /, %, **).
ethers.parseEther(), NOT ethers.utils.parseEther(). The utils namespace does not exist in v6. All utility functions are top-level exports: parseEther, formatEther, parseUnits, formatUnits, id, keccak256, toUtf8Bytes, AbiCoder, etc.
ethers.providers does not exist. Provider classes are top-level: JsonRpcProvider, BrowserProvider, WebSocketProvider, FallbackProvider, InfuraProvider, AlchemyProvider. Do not write new ethers.providers.JsonRpcProvider().
BrowserProvider replaced Web3Provider. To wrap a browser wallet (MetaMask's window.ethereum), use new BrowserProvider(window.ethereum), not new Web3Provider(window.ethereum).
- Contract constructor changed. v5:
new ethers.Contract(address, abi, signerOrProvider). v6: same signature, but the third argument is now called a "runner" and can be a Provider (read-only) or Signer (read-write). The .connect() method returns a new Contract instance with a different runner.
contract.getFunction("name") for explicit method access. v6 still supports contract.functionName() but the recommended pattern for clarity is contract.getFunction("transfer").
Wallet extends AbstractSigner, NOT Signer. The class hierarchy changed. Wallet takes a private key and a Provider. new Wallet(privateKey, provider).
getDefaultProvider() is still available but rate-limited. For production, always use an explicit JsonRpcProvider with your own RPC endpoint.
- Event filters use
contract.on("EventName", callback). The v6 event API is similar to v5 but the filter builder changed. Use contract.filters.EventName(arg1, arg2) -- returns an ethers DeferredTopicFilter.
receipt.status is a number (0 or 1), not a string. Check receipt.status === 1 for success.
TransactionResponse.wait() can return null. If the transaction is dropped or replaced, wait() returns null. Always handle this case.
- ENS resolution only works on networks that support it. Calling
provider.resolveName("vitalik.eth") on a non-mainnet provider without ENS deployment returns null, not an error.
Interface replaced ethers.utils.Interface. Use new Interface(abi) directly, imported from ethers.
Installation
npm install ethers
v6 requires Node.js 16+ and TypeScript 5.0+ (if using TypeScript).
{
"dependencies": {
"ethers": "^6.13.0"
}
}
Quick Start
Create a Provider (Read-Only)
import { JsonRpcProvider } from "ethers";
const RPC_URL = process.env.RPC_URL;
if (!RPC_URL) throw new Error("RPC_URL environment variable required");
const provider = new JsonRpcProvider(RPC_URL);
const blockNumber = await provider.getBlockNumber();
console.log(`Current block: ${blockNumber}`);
Create a Wallet (Read-Write)
import { JsonRpcProvider, Wallet } from "ethers";
const RPC_URL = process.env.RPC_URL;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
if (!RPC_URL) throw new Error("RPC_URL required");
if (!PRIVATE_KEY) throw new Error("PRIVATE_KEY required");
const provider = new JsonRpcProvider(RPC_URL);
const wallet = new Wallet(PRIVATE_KEY, provider);
console.log(`Address: ${wallet.address}`);
Browser Wallet (MetaMask, etc.)
import { BrowserProvider } from "ethers";
// window.ethereum is the EIP-1193 provider injected by the wallet
if (!window.ethereum) throw new Error("No wallet detected");
const provider = new BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
console.log(`Connected: ${await signer.getAddress()}`);
Providers
JsonRpcProvider
Direct connection to any JSON-RPC endpoint. Use for server-side scripts, bots, and backends.
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY");
const balance = await provider.getBalance("vitalik.eth");
console.log(`Balance: ${balance} wei`);
const network = await provider.getNetwork();
console.log(`Chain ID: ${network.chainId}`);
WebSocketProvider
Persistent connection for real-time event subscriptions. Required for provider.on("block", ...).
import { WebSocketProvider } from "ethers";
const wsProvider = new WebSocketProvider("wss://eth-mainnet.g.alchemy.com/v2/YOUR_KEY");
wsProvider.on("block", (blockNumber: number) => {
console.log(`New block: ${blockNumber}`);
});
BrowserProvider
Wraps any EIP-1193 provider (MetaMask, WalletConnect, Coinbase Wallet). Use in frontend dApps.
import { BrowserProvider } from "ethers";
const provider = new BrowserProvider(window.ethereum);
// Request account access (triggers wallet popup)
const signer = await provider.getSigner();
const address = await signer.getAddress();
const balance = await provider.getBalance(address);
FallbackProvider
Redundant provider with quorum voting. Uses multiple backends and returns the result agreed upon by a quorum.
import { FallbackProvider, JsonRpcProvider } from "ethers";
const providers = [
new JsonRpcProvider("https://rpc1.example.com"),
new JsonRpcProvider("https://rpc2.example.com"),
new JsonRpcProvider("https://rpc3.example.com"),
];
// quorum: 2 means at least 2 providers must agree
const fallbackProvider = new FallbackProvider(providers, undefined, { quorum: 2 });
Signers and Wallets
Wallet from Private Key
import { Wallet, JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
console.log(`Address: ${wallet.address}`);
console.log(`Chain: ${(await wallet.provider!.getNetwork()).chainId}`);
Wallet from Mnemonic
import { Wallet, Mnemonic } from "ethers";
// BIP-39 mnemonic to HD wallet (default path: m/44'/60'/0'/0/0)
const mnemonic = Mnemonic.fromPhrase("test test test test test test test test test test test junk");
const wallet = Wallet.fromPhrase(mnemonic.phrase);
console.log(`Address: ${wallet.address}`);
console.log(`Private key: ${wallet.privateKey}`);
Random Wallet
import { Wallet } from "ethers";
const randomWallet = Wallet.createRandom();
console.log(`Address: ${randomWallet.address}`);
console.log(`Mnemonic: ${randomWallet.mnemonic?.phrase}`);
Signing Messages
import { Wallet, verifyMessage } from "ethers";
const wallet = new Wallet(process.env.PRIVATE_KEY!);
const message = "Hello, Ethereum!";
const signature = await wallet.signMessage(message);
// Recover signer address from signature
const recovered = verifyMessage(message, signature);
console.log(`Signer: ${recovered}`);
console.log(`Match: ${recovered === wallet.address}`);
Signing Typed Data (EIP-712)
import { Wallet } from "ethers";
const wallet = new Wallet(process.env.PRIVATE_KEY!);
const domain = {
name: "MyDApp",
version: "1",
chainId: 1n,
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
};
const types = {
Transfer: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "nonce", type: "uint256" },
],
};
const value = {
to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
amount: 1000000n,
nonce: 0n,
};
const signature = await wallet.signTypedData(domain, types, value);
Contracts
Read-Only Contract (Provider)
import { Contract, JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const ERC20_ABI = [
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)",
"function balanceOf(address) view returns (uint256)",
"function totalSupply() view returns (uint256)",
];
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const contract = new Contract(USDC, ERC20_ABI, provider);
const [name, symbol, decimals, totalSupply] = await Promise.all([
contract.name(),
contract.symbol(),
contract.decimals(),
contract.totalSupply(),
]);
// decimals returns bigint in v6
console.log(`${name} (${symbol}), decimals: ${decimals}`);
console.log(`Total supply: ${totalSupply}`); // bigint
Read-Write Contract (Signer)
import { Contract, Wallet, JsonRpcProvider, parseUnits } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
const ERC20_ABI = [
"function transfer(address to, uint256 amount) returns (bool)",
"function approve(address spender, uint256 amount) returns (bool)",
"function allowance(address owner, address spender) view returns (uint256)",
"function balanceOf(address) view returns (uint256)",
];
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const contract = new Contract(USDC, ERC20_ABI, wallet);
const recipient = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
// 100 USDC (6 decimals)
const amount = parseUnits("100", 6);
const tx = await contract.transfer(recipient, amount);
console.log(`TX hash: ${tx.hash}`);
const receipt = await tx.wait();
if (receipt === null) throw new Error("Transaction dropped or replaced");
if (receipt.status !== 1) throw new Error("Transaction reverted");
console.log(`Confirmed in block ${receipt.blockNumber}`);
Human-Readable ABI
v6 supports human-readable ABI strings. Use these for simple interactions instead of full JSON ABIs.
const abi = [
"function name() view returns (string)",
"function transfer(address to, uint256 amount) returns (bool)",
"event Transfer(address indexed from, address indexed to, uint256 value)",
"error InsufficientBalance(uint256 available, uint256 required)",
];
Full JSON ABI
For complex contracts or when you need type generation, use the full JSON ABI.
const abi = [
{
name: "transfer",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ name: "", type: "bool" }],
},
] as const;
ContractFactory (Deploying Contracts)
import { ContractFactory, Wallet, JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
const abi = [
"constructor(string name, string symbol, uint8 decimals)",
"function name() view returns (string)",
"function symbol() view returns (string)",
];
// bytecode from solc compilation output
const bytecode = "0x608060...";
const factory = new ContractFactory(abi, bytecode, wallet);
const contract = await factory.deploy("MyToken", "MTK", 18);
const deployTx = contract.deploymentTransaction();
if (!deployTx) throw new Error("No deployment transaction");
console.log(`Deploying: ${deployTx.hash}`);
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log(`Deployed at: ${address}`);
Connecting a Contract to a Different Runner
import { Contract, JsonRpcProvider, Wallet } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
const abi = ["function transfer(address to, uint256 amount) returns (bool)"];
const address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
// Read-only contract
const readContract = new Contract(address, abi, provider);
// Connect to signer for write operations -- returns a NEW instance
const writeContract = readContract.connect(wallet) as Contract;
Utility Functions
Parsing and Formatting
import { parseEther, formatEther, parseUnits, formatUnits } from "ethers";
// ETH (18 decimals)
const weiAmount: bigint = parseEther("1.5"); // 1500000000000000000n
const ethAmount: string = formatEther(weiAmount); // "1.5"
// Custom decimals (USDC = 6)
const usdcWei: bigint = parseUnits("100.50", 6); // 100500000n
const usdcStr: string = formatUnits(usdcWei, 6); // "100.5"
// Gwei (9 decimals)
const gweiWei: bigint = parseUnits("30", "gwei"); // 30000000000n
const gweiStr: string = formatUnits(gweiWei, "gwei"); // "30.0"
Hashing
import { keccak256, toUtf8Bytes, id, solidityPackedKeccak256 } from "ethers";
// keccak256 of raw bytes
const hash = keccak256(toUtf8Bytes("Hello"));
// id() is shorthand for keccak256(toUtf8Bytes(...))
const eventTopic = id("Transfer(address,address,uint256)");
// Solidity-style packed encoding then keccak256
const packed = solidityPackedKeccak256(
["address", "uint256"],
["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", 42n]
);
ABI Encoding and Decoding
import { AbiCoder } from "ethers";
const coder = AbiCoder.defaultAbiCoder();
// Encode
const encoded = coder.encode(
["address", "uint256", "bool"],
["0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", 1000n, true]
);
// Decode
const [addr, amount, flag] = coder.decode(
["address", "uint256", "bool"],
encoded
);
console.log(`Address: ${addr}, Amount: ${amount}, Flag: ${flag}`);
Interface for Encoding Function Calls
import { Interface } from "ethers";
const iface = new Interface([
"function transfer(address to, uint256 amount) returns (bool)",
"function approve(address spender, uint256 amount) returns (bool)",
]);
// Encode a function call
const calldata = iface.encodeFunctionData("transfer", [
"0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
parseUnits("100", 6),
]);
// Decode a function call
const decoded = iface.decodeFunctionData("transfer", calldata);
console.log(`To: ${decoded[0]}, Amount: ${decoded[1]}`);
// Decode function result
const resultData = "0x0000000000000000000000000000000000000000000000000000000000000001";
const result = iface.decodeFunctionResult("transfer", resultData);
console.log(`Success: ${result[0]}`);
Transactions
Send ETH
import { Wallet, JsonRpcProvider, parseEther } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
const tx = await wallet.sendTransaction({
to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
value: parseEther("0.1"),
});
console.log(`TX hash: ${tx.hash}`);
const receipt = await tx.wait();
if (receipt === null) throw new Error("Transaction dropped or replaced");
if (receipt.status !== 1) throw new Error("Transaction reverted");
console.log(`Gas used: ${receipt.gasUsed}`);
console.log(`Effective gas price: ${receipt.gasPrice}`);
EIP-1559 Transaction with Gas Control
import { Wallet, JsonRpcProvider, parseEther, parseUnits } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
const feeData = await provider.getFeeData();
const tx = await wallet.sendTransaction({
to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
value: parseEther("0.1"),
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
});
const receipt = await tx.wait();
if (receipt === null) throw new Error("Transaction dropped or replaced");
if (receipt.status !== 1) throw new Error("Transaction reverted");
Transaction Receipt Details
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const receipt = await provider.getTransactionReceipt("0x...");
if (!receipt) throw new Error("Transaction not found");
console.log(`Status: ${receipt.status === 1 ? "success" : "reverted"}`);
console.log(`Block: ${receipt.blockNumber}`);
console.log(`Gas used: ${receipt.gasUsed}`);
console.log(`Logs: ${receipt.logs.length}`);
Events
Listening to Events (Real-Time)
import { Contract, WebSocketProvider } from "ethers";
const provider = new WebSocketProvider("wss://eth-mainnet.g.alchemy.com/v2/YOUR_KEY");
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const abi = [
"event Transfer(address indexed from, address indexed to, uint256 value)",
];
const contract = new Contract(USDC, abi, provider);
contract.on("Transfer", (from: string, to: string, value: bigint, event) => {
console.log(`Transfer: ${from} -> ${to}: ${value}`);
console.log(`Block: ${event.log.blockNumber}`);
});
Querying Past Events
import { Contract, JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const abi = [
"event Transfer(address indexed from, address indexed to, uint256 value)",
];
const contract = new Contract(USDC, abi, provider);
// Query last 1000 blocks
const currentBlock = await provider.getBlockNumber();
const events = await contract.queryFilter("Transfer", currentBlock - 1000, currentBlock);
for (const event of events) {
if (event.args) {
console.log(`${event.args.from} -> ${event.args.to}: ${event.args.value}`);
}
}
Filtered Events
import { Contract, JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
const abi = [
"event Transfer(address indexed from, address indexed to, uint256 value)",
];
const contract = new Contract(USDC, abi, provider);
// Filter: transfers FROM a specific address
const fromFilter = contract.filters.Transfer("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
// Filter: transfers TO a specific address
const toFilter = contract.filters.Transfer(null, "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
const currentBlock = await provider.getBlockNumber();
const transfers = await contract.queryFilter(fromFilter, currentBlock - 5000, currentBlock);
console.log(`Found ${transfers.length} transfers`);
ENS Resolution
import { JsonRpcProvider } from "ethers";
// ENS resolution requires a mainnet provider (or a network with ENS deployed)
const provider = new JsonRpcProvider(process.env.RPC_URL);
// Forward resolution: name -> address
const address = await provider.resolveName("vitalik.eth");
if (address === null) throw new Error("ENS name not found");
console.log(`vitalik.eth = ${address}`);
// Reverse resolution: address -> name
const name = await provider.lookupAddress("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045");
console.log(`Name: ${name}`); // may be null if no reverse record
ENS Avatar
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const resolver = await provider.getResolver("vitalik.eth");
if (!resolver) throw new Error("No resolver for this name");
const avatar = await resolver.getAvatar();
console.log(`Avatar: ${avatar}`);
const contentHash = await resolver.getContentHash();
console.log(`Content hash: ${contentHash}`);
Error Handling
Catching Contract Reverts
import { Contract, JsonRpcProvider, isError } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const abi = [
"function transfer(address to, uint256 amount) returns (bool)",
"error InsufficientBalance(uint256 available, uint256 required)",
];
const contract = new Contract("0x...", abi, provider);
try {
const tx = await contract.transfer("0x...", 1000n);
const receipt = await tx.wait();
if (receipt === null) throw new Error("Transaction dropped");
if (receipt.status !== 1) throw new Error("Transaction reverted");
} catch (error: unknown) {
if (isError(error, "CALL_EXCEPTION")) {
// Contract revert with reason or custom error
console.error(`Revert reason: ${error.reason}`);
console.error(`Error data: ${error.data}`);
} else if (isError(error, "INSUFFICIENT_FUNDS")) {
console.error("Not enough ETH for gas + value");
} else if (isError(error, "NONCE_EXPIRED")) {
console.error("Nonce already used -- transaction may be a duplicate");
} else if (isError(error, "REPLACEMENT_UNDERPRICED")) {
console.error("Replacement transaction gas price too low");
} else if (isError(error, "NETWORK_ERROR")) {
console.error("Network connectivity issue");
} else if (isError(error, "TIMEOUT")) {
console.error("Request timed out");
} else {
throw error;
}
}
The isError Utility
v6 provides isError(error, code) for type-safe error checking. This replaces the v5 pattern of checking error.code directly.
import { isError } from "ethers";
try {
await provider.getBlock("invalid");
} catch (error: unknown) {
if (isError(error, "INVALID_ARGUMENT")) {
console.error(`Invalid argument: ${error.argument} = ${error.value}`);
} else if (isError(error, "SERVER_ERROR")) {
console.error(`RPC error: ${error.info}`);
} else {
throw error;
}
}
Multicall Pattern
Batch multiple read calls into a single RPC request using Multicall3.
import { Contract, JsonRpcProvider, Interface } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
// Multicall3 is deployed at the same address on all major EVM chains
const MULTICALL3 = "0xcA11bde05977b3631167028862bE2a173976CA11";
const multicallAbi = [
"function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) view returns (tuple(bool success, bytes returnData)[])",
];
const multicall = new Contract(MULTICALL3, multicallAbi, provider);
const erc20Iface = new Interface([
"function balanceOf(address) view returns (uint256)",
"function decimals() view returns (uint8)",
]);
const tokens = [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
"0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT
"0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI
];
const wallet = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
const calls = tokens.map((token) => ({
target: token,
allowFailure: false,
callData: erc20Iface.encodeFunctionData("balanceOf", [wallet]),
}));
const results = await multicall.aggregate3(calls);
for (let i = 0; i < tokens.length; i++) {
const balance = erc20Iface.decodeFunctionResult("balanceOf", results[i].returnData)[0];
console.log(`${tokens[i]}: ${balance}`);
}
v5 to v6 Migration Quick Reference
| v5 Pattern |
v6 Equivalent |
ethers.providers.JsonRpcProvider |
JsonRpcProvider (top-level import) |
ethers.providers.Web3Provider |
BrowserProvider |
ethers.utils.parseEther("1.0") |
parseEther("1.0") |
ethers.utils.formatEther(wei) |
formatEther(wei) |
ethers.utils.parseUnits("100", 6) |
parseUnits("100", 6) |
ethers.utils.formatUnits(val, 6) |
formatUnits(val, 6) |
ethers.utils.keccak256(...) |
keccak256(...) |
ethers.utils.id(...) |
id(...) |
ethers.utils.Interface |
Interface |
ethers.utils.AbiCoder |
AbiCoder |
ethers.BigNumber.from(...) |
Native BigInt(...) or 123n |
bn.add(other) |
a + b |
bn.mul(other) |
a * b |
bn.div(other) |
a / b |
bn.eq(other) |
a === b |
bn.gt(other) |
a > b |
bn.isZero() |
a === 0n |
bn.toNumber() |
Number(a) (only if value fits in Number.MAXSAFEINTEGER) |
bn.toString() |
a.toString() |
contract.deployed() |
contract.waitForDeployment() |
contract.address |
await contract.getAddress() |
tx.wait() returns receipt |
tx.wait() returns receipt or null |
receipt.status === 1 |
Same (unchanged) |
Common Patterns
Check Balance Before Transfer
import { JsonRpcProvider, Wallet, parseEther, formatEther } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
async function safeTransferEth(to: string, ethAmount: string): Promise<string> {
const value = parseEther(ethAmount);
const balance = await provider.getBalance(wallet.address);
const feeData = await provider.getFeeData();
// 21000 gas for simple ETH transfer
const estimatedGasCost = 21000n * (feeData.maxFeePerGas ?? feeData.gasPrice ?? 0n);
const totalCost = value + estimatedGasCost;
if (balance < totalCost) {
throw new Error(
`Insufficient balance. Have: ${formatEther(balance)} ETH, Need: ${formatEther(totalCost)} ETH`
);
}
const tx = await wallet.sendTransaction({ to, value });
const receipt = await tx.wait();
if (receipt === null) throw new Error("Transaction dropped or replaced");
if (receipt.status !== 1) throw new Error("Transaction reverted");
return tx.hash;
}
ERC-20 Approve and Transfer
import { Contract, Wallet, JsonRpcProvider, parseUnits, MaxUint256 } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
const ERC20_ABI = [
"function approve(address spender, uint256 amount) returns (bool)",
"function allowance(address owner, address spender) view returns (uint256)",
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
];
async function ensureAllowance(
tokenAddress: string,
spender: string,
amount: bigint
): Promise<void> {
const token = new Contract(tokenAddress, ERC20_ABI, wallet);
const currentAllowance: bigint = await token.allowance(wallet.address, spender);
if (currentAllowance >= amount) return;
const tx = await token.approve(spender, amount);
const receipt = await tx.wait();
if (receipt === null) throw new Error("Approval transaction dropped");
if (receipt.status !== 1) throw new Error("Approval reverted");
}
Wait for N Confirmations
import { Wallet, JsonRpcProvider, parseEther } from "ethers";
const provider = new JsonRpcProvider(process.env.RPC_URL);
const wallet = new Wallet(process.env.PRIVATE_KEY!, provider);
const tx = await wallet.sendTransaction({
to: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
value: parseEther("0.01"),
});
// Wait for 3 confirmations
const receipt = await tx.wait(3);
if (receipt === null) throw new Error("Transaction dropped or replaced");
if (receipt.status !== 1) throw new Error("Transaction reverted");
console.log(`Confirmed with ${3} confirmations at block ${receipt.blockNumber}`);
Reference Links