ethers.js
Ethers.js is the lightweight Ethereum library most dapps use to talk to wallets and smart contracts from the browser or Node. It abstracts JSON-RPC, ABI encoding, BigInts, signing, and event listening. Pair it with a wallet provider (MetaMask, WalletConnect) for in-browser dapps or a JSON-RPC URL for backend reads.
Connect a wallet, call a contract, listen for events
EXAMPLE
import { ethers } from 'ethers';
// 1) Get a provider
// Browser dapp via injected wallet (MetaMask / Coinbase Wallet)
const provider = new ethers.BrowserProvider(window.ethereum);
await window.ethereum.request({ method: 'eth_requestAccounts' });
const signer = await provider.getSigner();
const me = await signer.getAddress();
// Backend / read-only
// const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
console.log('connected as', me, 'on chain', (await provider.getNetwork()).chainId);
// 2) Build a contract instance from an ABI fragment
const erc20Abi = [
'function balanceOf(address) view returns (uint256)',
'function decimals() view returns (uint8)',
'function transfer(address to, uint256 amount) returns (bool)',
'event Transfer(address indexed from, address indexed to, uint256 value)',
];
const usdc = new ethers.Contract('0xA0b86991c6218B36c1d19D4a2e9Eb0cE3606eB48', erc20Abi, signer);
// 3) Read state — pure / view functions cost nothing
const [dec, bal] = await Promise.all([usdc.decimals(), usdc.balanceOf(me)]);
console.log('USDC balance:', ethers.formatUnits(bal, dec));
// 4) Write — sends a tx, prompts the wallet for signature
const recipient = '0x000000000000000000000000000000000000dEaD';
const amount = ethers.parseUnits('1.50', dec); // BigInt
const tx = await usdc.transfer(recipient, amount);
console.log('submitted', tx.hash);
const receipt = await tx.wait(1); // wait for 1 confirmation
console.log('mined in block', receipt.blockNumber, 'gas used', receipt.gasUsed.toString());
// 5) Subscribe to events in real time
usdc.on('Transfer', (from, to, value, event) => {
if (from === me || to === me) {
console.log('Transfer involving me:',
ethers.formatUnits(value, dec), 'tx', event.log.transactionHash);
}
});
// 6) Historical events with a filter (last 10k blocks)
const fromBlock = (await provider.getBlockNumber()) - 10_000;
const sent = usdc.filters.Transfer(me, null); // I am the sender
const logs = await usdc.queryFilter(sent, fromBlock);
console.log(\`sent ${logs.length} transfers in the last 10k blocks\`);
// 7) Sign a plain message (for sign-in / SIWE)
const msg = 'Sign-in to Shop @ 2026-06-11T09:30:00Z';
const sig = await signer.signMessage(msg);
const recovered = ethers.verifyMessage(msg, sig);
console.log('recovered =', recovered, '== me?', recovered === me);
// 8) Handle chain switches without reloading
window.ethereum.on('chainChanged', () => location.reload());
window.ethereum.on('accountsChanged', (a) => console.log('account changed:', a[0]));
Why it matters
Always await tx.wait() before assuming a state change happened — the transaction hash means "submitted", not "succeeded". On L1 a single confirmation is usually enough for UI; for high-value transfers prefer two or three confirmations, and bake reorg awareness into your back-end indexing.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(privateKey, provider);
const contract = new ethers.Contract(addr, abi, wallet);
console.log(await contract.balanceOf(wallet.address));
Try it Yourself »
Discussion
Loading…