Web3.js: Interacting with Ethereum from JavaScript
In this tutorial, you'll learn Web3.js for connecting to Ethereum nodes, reading contract states, sending transactions, and listening to events from JavaScript. Why it matters: Web3.js is the original JavaScript library for Ethereum interaction, used by thousands of dApps to bridge frontend interfaces with smart contracts on the blockchain. By the end, you'll build a complete JavaScript application that interacts with Ethereum.
Web3.js is a collection of libraries that allow you to interact with a local or remote Ethereum node using HTTP, IPC, or WebSocket connections from JavaScript applications.
Setting Up Web3.js
Install Web3.js via npm and connect to an Ethereum node.
npm init -y
npm install web3
const Web3 = require("web3");
// Connect to a public Ethereum node
const web3 = new Web3("https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY");
// Verify connection
web3.eth.net.isListening().then(console.log).catch(console.error);
// Expected output: true
For local development with Ganache:
const web3 = new Web3("http://127.0.0.1:7545");
async function checkConnection() {
const isListening = await web3.eth.net.isListening();
console.log("Connected:", isListening);
const blockNumber = await web3.eth.getBlockNumber();
console.log("Latest block:", blockNumber);
const accounts = await web3.eth.getAccounts();
console.log("Available accounts:", accounts);
}
checkConnection();
// Expected output:
// Connected: true
// Latest block: 0
// Available accounts: ['0x90F8...', '0xFFcf...', ...]
flowchart LR A[JavaScript dApp] --> B[Web3.js Library] B --> C[Provider] C --> D[Ethereum Node
Infura/Alchemy/Local] D --> E[Blockchain] B --> F[Contract ABI + Address] F --> G[Contract Methods
call() - read] G --> H[send() - write] F --> I[Events
Subscribe / Past]
Reading Contract State
Use the call() method to read data from a contract without paying gas. This simulates the transaction locally.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract Token {
string public name = "MyToken";
string public symbol = "MTK";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
constructor() {
totalSupply = 1000000 * 10**18;
balanceOf[msg.sender] = totalSupply;
}
}
const fs = require("fs");
// Load ABI and address
const contractABI = JSON.parse(
fs.readFileSync("./build/contracts/Token.json", "utf8")
).abi;
const contractAddress = "0x8CdaF0CD259887258Bc13a92C0a6dA92698644C0";
const tokenContract = new web3.eth.Contract(contractABI, contractAddress);
async function readContractState() {
// Read token details (call - no gas)
const name = await tokenContract.methods.name().call();
const symbol = await tokenContract.methods.symbol().call();
const totalSupply = await tokenContract.methods.totalSupply().call();
const balance = await tokenContract.methods.balanceOf("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1").call();
console.log(`Token: ${name} (${symbol})`);
console.log(`Total supply: ${totalSupply}`);
console.log(`Balance: ${web3.utils.fromWei(balance, "ether")} ${symbol}`);
}
readContractState();
// Expected output:
// Token: MyToken (MTK)
// Total supply: 1000000000000000000000000
// Balance: 1000000 MTK
Sending Transactions
Use the send() method to modify contract state. This requires gas and must be signed by a private key.
const senderAddress = "0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1";
const privateKey = "0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d";
// Transfer tokens
const recipient = "0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0";
const amount = web3.utils.toWei("100", "ether");
async function sendTransaction() {
// Create transaction data
const txData = tokenContract.methods.transfer(recipient, amount).encodeABI();
// Get nonce and gas price
const nonce = await web3.eth.getTransactionCount(senderAddress);
const gasPrice = await web3.eth.getGasPrice();
// Build transaction
const tx = {
from: senderAddress,
to: contractAddress,
data: txData,
nonce: nonce,
gas: 100000,
gasPrice: gasPrice,
chainId: 1337, // Ganache chain ID
};
// Sign and send
const signedTx = await web3.eth.accounts.signTransaction(tx, privateKey);
const receipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
console.log("Transaction hash:", receipt.transactionHash);
console.log("Block number:", receipt.blockNumber);
console.log("Gas used:", receipt.gasUsed);
// Verify new balances
const senderBalance = await tokenContract.methods.balanceOf(senderAddress).call();
const recipientBalance = await tokenContract.methods.balanceOf(recipient).call();
console.log(`Sender balance: ${web3.utils.fromWei(senderBalance, "ether")}`);
console.log(`Recipient balance: ${web3.utils.fromWei(recipientBalance, "ether")}`);
}
sendTransaction();
// Expected output:
// Transaction hash: 0x7a3f...c9e2
// Block number: 3
// Gas used: 51728
// Sender balance: 999900
// Recipient balance: 100
Listening to Events
Subscribe to contract events for real-time updates or query past events.
// Subscribe to Transfer events
const eventSubscription = tokenContract.events.Transfer({
filter: { from: senderAddress }, // Optional filter
fromBlock: 0, // Start from genesis
});
// Listen for new events
eventSubscription.on("data", (event) => {
console.log("New Transfer event:");
console.log(` From: ${event.returnValues.from}`);
console.log(` To: ${event.returnValues.to}`);
console.log(` Value: ${web3.utils.fromWei(event.returnValues.value, "ether")}`);
console.log(` Block: ${event.blockNumber}`);
console.log(` Tx: ${event.transactionHash}`);
});
// Handle errors
eventSubscription.on("error", console.error);
// Get past events (already happened)
async function getPastEvents() {
const pastEvents = await tokenContract.getPastEvents("Transfer", {
filter: {},
fromBlock: 0,
toBlock: "latest",
});
console.log(`Found ${pastEvents.length} past Transfer events`);
pastEvents.forEach((event, i) => {
console.log(`${i + 1}. ${event.returnValues.from} -> ${event.returnValues.to}: ${web3.utils.fromWei(event.returnValues.value, "ether")}`);
});
}
// Expected output:
// New Transfer event:
// From: 0x90F8...
// To: 0xFFcf...
// Value: 100
// Block: 3
// Tx: 0x7a3f...
Error Handling and Utilities
Web3.js provides utilities for address validation, unit conversion, and hashing.
const Web3 = require("web3");
const web3 = new Web3();
// Address validation
function validateAddress(address) {
const isValid = web3.utils.isAddress(address);
console.log(`${address} is ${isValid ? "valid" : "invalid"}`);
return isValid;
}
// Expected: true
validateAddress("0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1");
// Expected: false
validateAddress("0x1234");
// Unit conversion
console.log(web3.utils.fromWei("1000000000000000000", "ether"));
// Expected: 1
console.log(web3.utils.toWei("2.5", "ether"));
// Expected: 2500000000000000000
console.log(web3.utils.fromWei("50000000000", "gwei"));
// Expected: 50
// Hashing
console.log(web3.utils.sha3("hello"));
// Expected: 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8
console.log(web3.utils.keccak256("Transfer(address,address,uint256)"));
// Expected: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
// Transaction error handling
async function safeTransaction() {
try {
const result = await tokenContract.methods.transfer("0x0000", 100).call();
} catch (error) {
if (error.message.includes("revert")) {
console.error("Transaction reverted:", error.message);
} else if (error.message.includes("insufficient funds")) {
console.error("Not enough ETH for gas");
} else if (error.message.includes("nonce too low")) {
console.error("Nonce mismatch - transaction already submitted");
} else {
console.error("Unknown error:", error.message);
}
}
}
// Expected output: Transaction reverted: Returned error: VM Exception while processing transaction: revert
Common Errors and Misunderstandings
1. Missing ABI or Wrong Address
Using the wrong contract address or mismatched ABI causes call or send to fail. Always verify the deployed contract address and regenerate the ABI after compilation.
2. Private Key Exposure
Never hardcode private keys in frontend code. Use MetaMask or wallet providers for user signing. Only use private keys in backend scripts.
3. Gas Estimation Failures
Underestimating gas causes out-of-gas errors. Use estimateGas() before sending or set a higher gas limit.
4. Nonce Management
Manual nonce management is error-prone. Use wallet providers (MetaMask) that handle nonces automatically.
5. Event Subscription Memory Leaks
Unsubscribe from events when components unmount. Failing to do so causes memory leaks and duplicate event handlers.
Practice Questions
What is the difference between
call()andsend()in Web3.js?call()simulates a transaction locally without gas cost and cannot modify state.send()broadcasts a signed transaction that modifies state and costs gas.How do you convert between ETH and Wei in Web3.js? Use
web3.utils.toWei(amount, "ether")to convert ETH to Wei andweb3.utils.fromWei(amount, "ether")to convert Wei to ETH.What information does a transaction receipt contain? It contains the transaction hash, block number, gas used, status (success/failure), logs (events), and contract address (for deployments).
How do you subscribe to contract events? Use
contract.events.EventName({ filter, fromBlock }).on("data", handler). Filter uses indexed event parameters.What is the purpose of the contract ABI? The ABI (Application Binary Interface) describes the contract's functions, events, and their parameters. Web3.js uses it to encode/decode contract interactions.
Challenge
Build a Node.js CLI tool using Web3.js that monitors a wallet address for incoming ERC-20 token transfers, logs them to a CSV file with timestamps, and sends an email alert when a transfer exceeds a configurable threshold amount.
Real-World Task
Create a Web3.js-powered dashboard with React that connects to an Ethereum node, displays the latest block, tracks gas prices, shows a user's ETH and token balances, and allows sending ETH to any address with transaction status display. Use React hooks for real-time updates.
Frequently Asked Questions
Next Steps
After mastering Web3.js, explore ethers.js for a modern alternative, then build full-stack dApps with React integration and MetaMask wallet connection.
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro