By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.
Web3.js and Ethers.js are JavaScript libraries that let a web page talk directly to the Ethereum Virtual Machine (EVM). They expose the blockchain as a set of read‑only calls (call) and state‑changing transactions (sendTransaction) so a front‑end can, for example, let a user swap tokens on Uniswap, mint an NFT, or cast a vote in a DAO without ever leaving the page.
call
sendTransaction
Web3
js const web3 = new Web3(window.ethereum); const token = new web3.eth.Contract(ERC20_ABI, tokenAddress);
js const provider = new ethers.providers.Web3Provider(window.ethereum); const token = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
Provider – The bridge between JavaScript and the blockchain (e.g., Infura, Alchemy, or the injected window.ethereum). It supplies block data, gas prices, and the ability to sign transactions.
window.ethereum
Signer – An object that holds a private key (or uses MetaMask) and can sign and send transactions.
js const signer = provider.getSigner(); // MetaMask user const tokenWithSigner = token.connect(signer);
ABI (Application Binary Interface) – A JSON description of a contract’s functions, events, and types. The front‑end uses the ABI to encode calls and decode responses.
call vs sendTransaction – call runs locally on a node, consumes no gas, and never changes state. sendTransaction creates a signed transaction, pays gas, and updates the ledger.
js // read balance (free) const bal = await token.balanceOf(address); // call // transfer tokens (costs gas) const tx = await tokenWithSigner.transfer(to, amount); // sendTransaction
Transfer(address,address,uint256)
js token.on('Transfer', (from, to, value, event) => { console.log(`${from} → ${to}: ${value}`); });
estimateGas
js const gas = await tokenWithSigner.estimateGas.transfer(to, amount);
MetaMask / WalletConnect – Browser extensions or mobile bridges that inject a provider (window.ethereum). They handle user approvals, nonce management, and chain switching.
EIP‑1559 Transaction – New fee model (maxFeePerGas, maxPriorityFeePerGas). Ethers.js automatically builds these fields when the network supports them.
maxFeePerGas
maxPriorityFeePerGas
js const tx = await tokenWithSigner.transfer(to, amount, { maxPriorityFeePerGas: ethers.utils.parseUnits('2', 'gwei') });
ContractFactory
js const factory = new ethers.ContractFactory(ABI, BYTECODE, signer); const deployed = await factory.deploy(arg1, arg2); await deployed.deployed(); // wait for mining
bash npx hardhat compile # produces .json with ABI & bytecode
js const factory = new ethers.ContractFactory(ABI, BYTECODE, signer); const contract = await factory.deploy(initialSupply); await contract.deployed(); console.log('Deployed at', contract.address);
Expose the ABI & address to the front‑end (store in src/constants.js or fetch from a subgraph).
src/constants.js
Initialize the provider & signer in the UI js await window.ethereum.request({ method: 'eth_requestAccounts' }); const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner();
js await window.ethereum.request({ method: 'eth_requestAccounts' }); const provider = new ethers.providers.Web3Provider(window.ethereum); const signer = provider.getSigner();
js const token = new ethers.Contract(tokenAddress, ERC20_ABI, provider); const tokenWithSigner = token.connect(signer); const tx = await tokenWithSigner.transfer(recipient, ethers.utils.parseUnits('10', 18)); await tx.wait(); // wait for confirmation
js token.on('Transfer', (from, to, amount) => refreshBalances());
Mistake: Using await token.transfer(...) without a signer. Correction: Connect the contract to a signer (token.connect(signer)) so the transaction can be signed and broadcast. Without a signer the call is a read‑only call, and no state changes occur.
await token.transfer(...)
token.connect(signer)
Mistake: Forgetting to await tx.wait() before reading the updated state. Correction: The transaction is mined asynchronously; await tx.wait() guarantees the receipt is on‑chain, preventing UI race conditions.
await tx.wait()
Mistake: Hard‑coding the provider URL (e.g., https://mainnet.infura.io/v3/...) in production. Correction: Detect window.ethereum first; fall back to a read‑only RPC only for read‑only pages. This avoids forcing users to trust a single node and respects wallet‑driven signing.
https://mainnet.infura.io/v3/...
Mistake: Ignoring chainId mismatches (e.g., UI thinks it’s on Polygon while MetaMask is on Ethereum). Correction: Compare await provider.getNetwork() with the expected chainId and prompt the user to switch networks via wallet_switchEthereumChain.
chainId
await provider.getNetwork()
wallet_switchEthereumChain
Mistake: Not handling reverted transactions (try/catch only around await token.transfer). Correction: Wrap the whole await tx.wait() in a try block; the revert reason is only available after the receipt is mined.
try/catch
await token.transfer
try
call vs delegatecall – Interviewers love to ask why delegatecall is dangerous in upgradeable proxies. The answer: it runs the callee’s code in the caller’s storage context, enabling state‑corruption if the callee is malicious.
delegatecall
ERC‑20 vs ERC‑777 – Be ready to explain that ERC‑777 adds hooks (tokensReceived) and can reject transfers, while ERC‑20 is simpler but lacks these safety callbacks.
tokensReceived
EIP‑1559 fee fields – Know the difference between maxFeePerGas (total ceiling) and maxPriorityFeePerGas (miner tip). Show how Ethers.js fills them automatically when the network supports the new fee market.
Optimistic vs ZK Rollups – Optimistic rollups assume transactions are valid and challenge fraud later; ZK rollups provide a validity proof on‑chain instantly. Discuss latency, data availability, and when each is preferred for a dApp.
Scenario: A dApp reads a token balance with await token.balanceOf(addr) and then immediately shows a “Transfer” button. Answer: The UI must still request the user’s signature before sending a transaction; reading a balance is free (call), but transferring requires a signed sendTransaction.
await token.balanceOf(addr)
Scenario: A contract uses tx.origin to restrict admin functions. Answer: Dangerous because a malicious contract can trick a user into calling it, making tx.origin equal the user’s address and bypassing the restriction. Use msg.sender instead.
tx.origin
msg.sender
Scenario: You see a transaction receipt with status: 0. Answer: The transaction reverted; the UI should surface the revert reason (if available) and not assume state changed.
status: 0
contract.connect(signer)
uint256
pragma solidity ^0.8.17
Transfer
Approval
eth_requestAccounts
0x1
contract.queryFilter('Transfer', fromBlock, toBlock)
Join 4M+ learners. Unlock unlimited quizzes, wrong-answer tracking, flashcards + reminders, study guides, and 1-on-1 challenges.