Education › Blockchain › Stage 3: Building dApps

Connecting a frontend

ethers.js and viem, wallet connections, and reading from and writing to contracts from a web app.

Intermediate ~33 min read Module 10 of 16

A smart contract with no interface is just an address most people can't use. A decentralised application — a dApp — is a normal web frontend that talks to a contract instead of a company's server. This lesson shows how that connection works: the libraries that speak to the chain, the crucial difference between reading (free) and writing (a transaction), and how a wallet like MetaMask signs the user's transactions so your site never touches their keys. By the end you'll understand the full path from a button click to an on-chain state change.

After this module you can
  • Explain what a dApp is and how its frontend differs from a normal web app
  • Use a library (ethers.js/viem) with a provider and a signer
  • Distinguish reading state (a free call) from writing state (a transaction)
  • Describe the wallet-based flow that keeps user keys off your site

What a dApp really is

A dApp (decentralised application) is, in most cases, an ordinary website — HTML, CSS, JavaScript — whose *backend logic* lives in a smart contract on a public blockchain instead of on a company's servers. The frontend still runs in the user's browser and can be hosted anywhere; what's different is where it sends its important operations. Instead of calling your own API to, say, record a vote, the frontend asks the user's wallet to send a transaction to a contract, and the contract records the vote on-chain. The user's data and the core logic aren't in your control, which is the whole point: the app keeps working even if your website disappears, because the contract is still there.

The library, the provider, and the signer

Your JavaScript can't talk to the blockchain directly; it uses a library — ethers.js or viem are the popular ones — that speaks the chain's protocol. Two objects from that library matter most. A provider is a read-only connection to the chain: with it you can query balances, read contract state, and fetch past events, all for free. A signer represents an account that can *authorise* transactions — it's backed by the user's wallet, and using it is what triggers the wallet to pop up and ask the user to confirm. The rule of thumb: provider to read, signer to write. You get the signer from the browser wallet the user has connected, so your site can request transactions without ever seeing the private key.

javascript
import { ethers } from "ethers";

// The injected wallet (e.g. MetaMask) exposes window.ethereum.
const provider = new ethers.BrowserProvider(window.ethereum);

// Ask the user to connect, then get a signer for their account.
await provider.send("eth_requestAccounts", []);
const signer = await provider.getSigner();

// A contract object needs the address and the ABI (its function list).
const counter = new ethers.Contract(address, abi, signer);

Reading is free; writing is a transaction

This is the distinction that shapes every dApp. Reading the chain — calling a view function, checking a balance — costs nothing, happens instantly, and needs no wallet confirmation, because you're only querying data every node already has. Writing — calling a function that changes state — is a transaction: it costs gas, must be signed by the user's wallet, and takes time to be mined and confirmed. So a read returns a value immediately, while a write returns a transaction you then wait on. Designing a dApp is largely about knowing which operations are reads (show them freely, update the UI often) and which are writes (prompt the wallet, show a pending state, wait for confirmation).

javascript
// READ: free, instant, no wallet prompt (a view function)
const current = await counter.count();
console.log("count is", current);

// WRITE: a transaction -> wallet prompts, costs gas, must be mined
const tx = await counter.increment();   // user confirms in the wallet
await tx.wait();                        // wait for it to be confirmed
console.log("now", await counter.count());

The ABI (Application Binary Interface) mentioned in the code is the contract's public interface described as data — the list of its functions and events — which the library needs to encode your calls correctly. Your framework produces the ABI when it compiles the contract, so you pass it straight through to the frontend.

The wallet keeps keys off your site

The security model here is elegant and worth appreciating. Your site never asks for, sees, or stores the user's private key. Instead, when your code calls a state-changing function through a signer, the request goes to the user's wallet, which shows the user exactly what they're about to sign — the contract, the function, the value — and only signs and broadcasts if they approve. Your frontend just prepares the transaction and reacts to the result. This means a malicious or buggy dApp can *ask* for a transaction but can't move funds without the user's explicit, informed consent, and a compromised website still can't steal keys it never had.

Watch out

The flip side: users must actually read what they sign. A deceptive dApp can request a transaction or a token approval that does more than it appears to. This is why the security lesson matters on both sides — and why teaching users to check the wallet prompt is part of building trustworthy dApps.

The full click-to-chain flow

Now the whole path connects. The user clicks 'Connect wallet'; your site requests accounts and gets a signer. The user clicks a button that changes state; your code calls the contract function through the signer, which prompts the wallet; the user confirms; the wallet signs and broadcasts the transaction; nodes verify it and a validator includes it in a block; your code was awaiting the transaction and, once it's confirmed, reads the new state back through the provider and updates the UI. Every layer from the earlier lessons — keys, transactions, gas, nodes, consensus — is in that one interaction, now with a friendly button in front of it.

  • Connect: request accounts → get a signer from the wallet.
  • Read: query state through the provider, free and instant.
  • Write: call a function via the signer → the wallet prompts the user.
  • Confirm: the user approves; the wallet signs and broadcasts.
  • Update: await the transaction, then re-read state and refresh the UI.

That is a complete dApp interaction. The full-stack dApp project in this track builds exactly this — a contract plus a frontend that connects a wallet, reads state, and sends transactions. Next, though, the most important lesson for anyone whose contract will hold value: security.

Hands-on practice

Trace a dApp interaction

  1. Explain how a dApp's frontend differs from a normal web app in where its core logic runs.
  2. Describe the roles of a provider and a signer, and which one you use to read versus write.
  3. Write, in pseudocode, a read of a contract's value and a write that changes it, noting which prompts the wallet.
  4. Explain what the ABI is and why the library needs it.
  5. Walk through the full flow from 'connect wallet' to the UI updating after a confirmed transaction.
Cheat sheet

Connecting a frontend — at a glance

Main things to focus on

  • A dApp is a normal frontend whose backend logic is a smart contract on a public chain.
  • A library (ethers.js/viem) speaks the chain's protocol from JavaScript.
  • Provider = read-only, free, instant; signer = authorises transactions via the wallet.
  • Reading a view function is free; writing is a gas-costing, signed, mined transaction.
  • The ABI is the contract's function/event list the library needs to encode calls.
  • The wallet signs transactions, so your site never touches the user's private key.

The dApp idea

frontendordinary HTML/JS in the browser
backend = contractlogic lives on-chain
survives your sitecontract persists independently
user owns datastate is on the public chain

Library objects

ethers.js / viemlibraries that speak the chain
providerread-only connection; free
signerauthorises transactions via wallet
Contract(addr, abi)an object to call functions on

Read vs write

read (view)free, instant, no prompt
write (tx)gas, signed, mined
tx.wait()wait for confirmation
ABIthe interface the library encodes with

Wallet flow

eth_requestAccountsask the user to connect
wallet promptuser reviews and approves
keys stay in walletsite never sees the key
read what you signusers must check the prompt

Common pitfalls

  • Trying to write state with a provider — you need a signer for transactions.
  • Expecting a write to return a value instantly; it returns a transaction you must await.
  • Forgetting the ABI, so the library can't encode calls to the contract.
  • Assuming reads cost gas — querying view functions is free.
  • Building a dApp that hides what a transaction does; users must be able to review the wallet prompt.
Quiz

Check your understanding

5 questions · 4 to pass · answers are explained as you go. Your best score is saved on this device only.

Progress and quiz scores are saved in this browser only. Back up or restore on the hub.

Was this lesson useful? Tell me what to improve →