An engineering leader reads two languages: the contract (Solidity, on-chain) and the app that calls it (TypeScript via ethers.js, off-chain). The ABI is the seam between them. Let's read both.
Web3.js was sunset in March 2025 by its maintainer ChainSafe.1 For new work the field uses ethers.js (most popular, TypeScript-friendly) or viem (newer, fully type-safe, smaller). If a candidate reaches for Web3.js on a greenfield project, that's a small but real currency flag worth probing. We'll use ethers.js (v6).
The ABI (Application Binary Interface) is the key idea: it's a machine-readable description of the contract's functions and events — the ERC-20 interface from Lesson 3, turned into JSON the client uses to encode calls. Your TypeScript never sees Solidity; it sees the ABI.
You're not writing this; you're interrogating it. Here's the Lesson 2 registry again, annotated with what a reviewer looks for:
contract ShareRegistry { mapping(address => uint256) public balances; // state: the ledger address public agent; // ← WHO is privileged? modifier onlyAgent() { // ← access control (Lesson 6 #1 risk) require(msg.sender == agent, "not agent"); _; } function forcedTransfer(address from, address to, uint256 amt) public onlyAgent { // ← privileged: who can call it? guarded? balances[from] -= amt; balances[to] += amt; emit Transfer(from, to, amt); // ← event = off-chain feed (Lesson 3) } event Transfer(address indexed from, address indexed to, uint256 amt); }
view (free)? ② Every privileged function — is it guarded by a modifier, and who is the authority? (access control, Lesson 6). ③ What does each require enforce — and what happens on failure (revert, Lesson 2)? ④ What events are emitted (your off-chain truth, Lesson 3)? ⑤ Anything that handles value or external calls (reentrancy surface). You can read for these without writing a line.Three objects are the whole mental model — and they map straight onto Lesson 2:
| ethers object | What it is | Maps to (Lesson 2) |
|---|---|---|
Provider | A read-only connection to a node. Query state, call view functions. | Free reads — no gas, no transaction |
Signer | Wraps an account + its key. Signs and sends state-changing transactions. | The EOA + its private key (custody!) |
Contract | address + ABI + (Provider or Signer). Gives you typed methods. | A handle to the contract account |
import { ethers } from "ethers"; // read-only: a Provider is enough const provider = new ethers.JsonRpcProvider(RPC_URL); // the ABI — human-readable form (this IS the interface, Lesson 3) const abi = [ "function balanceOf(address) view returns (uint256)", "function transfer(address to, uint256 amount)", "event Transfer(address indexed from, address indexed to, uint256 value)", ]; const token = new ethers.Contract(ADDRESS, abi, provider); const bal: bigint = await token.balanceOf(user); // view → free, instant
// state change: connect a Signer (holds the key — Lesson 6 custody) const signer = await provider.getSigner(); const token = new ethers.Contract(ADDRESS, abi, signer); const tx = await token.transfer(to, ethers.parseUnits("100", 18)); // tx is SENT but not yet final — mining is async const receipt = await tx.wait(); // wait for confirmation (a block) // if a require() failed on-chain, this REVERTS and throws (Lesson 2)
// subscribe to events — how your back office stays in sync token.on("Transfer", (from, to, amount, event) => { reconcile(from, to, amount); // update off-chain books });
Notice how little is new: it's just async TypeScript calling typed methods. The concepts doing the work — free reads vs gas-costing writes, the key behind the Signer, reverts, events as the sync feed — are all things you already learned. ethers.js is a thin, familiar skin over the model.
| In code you see… | …which is the concept |
|---|---|
provider read vs signer write | view (free) vs state-changing (gas) — Lesson 2 |
| The ABI array | The interface — Lesson 3 |
| The Signer's key | Custody / Fireblocks / MPC — Lesson 6 |
tx.wait() can throw | Revert / atomicity — Lesson 2 |
.on("Transfer", …) | Events as the off-chain bridge — Lessons 3 & 5 |
| A backend Signer (not a browser wallet) | Server-side signing → the agent key → secure it (Lesson 6) |
tx.wait(), trusting an event without confirmations). Competence means reading across the boundary.tx.wait() confirms → event emitted → backend reconciles. A weak one stops at "it calls the API."From memory. Interleaves Lessons 2, 3 & 6.
view call — no state change, no transaction, free (Lesson 2). Transferring changes state, so it must be signed by a key (the Signer) and costs gas.The canonical reference, well-written and TypeScript-first: ethers.js v6 — Getting Started. For the Solidity side, the Solidity docs (skim "Contracts" and "Visibility and Getters"). Context on the tooling shift: ChainSafe — Web3.js sunset; and the modern alternative viem.