Dapp frontend
Connecting to a wallet
MetaMask and other browser wallets inject an EIP-1193 provider at
window.ethereum.
if (window.ethereum) {
const provider = window.ethereum;
try {
await provider.request({ method: "eth_requestAccounts" });
} catch (err) {
console.error("User denied account access", err);
}
} else {
// No injected wallet — prompt to install one, or offer WalletConnect
}
eth_requestAccounts triggers the connection prompt and must be called from a
user gesture such as a click. Calling it on page load is blocked by some wallets
and is poor practice regardless.
eth_accounts (no prompt) returns the already-authorised accounts, which is how
you restore a connection on reload without asking again.
Always react to the account and chain changing — the user can switch either at any moment:
window.ethereum.on("accountsChanged", (accounts) => { /* re-read state */ });
window.ethereum.on("chainChanged", () => window.location.reload());
The wallet documentation recommends reloading on chainChanged, because
contract addresses, balances and cached state are all chain-specific.
With several wallets installed, window.ethereum is whichever one won the race
to inject. EIP-6963 solves this by having wallets announce themselves so the
user can choose; a connection library implements it for you.
Libraries
| Library | Status | Notes |
|---|---|---|
| viem | Active | TypeScript-first, small, the current default |
| ethers | Active | v6 is a significant change from v5 |
| wagmi | Active | React hooks over viem; handles connection state |
| web3.js | Sunset | ChainSafe ended maintenance in 2025 |
web3.js still works and is what most 2022-era material uses, but it is no longer maintained. New front ends should use viem, or wagmi if the project is React.
npm install viem
npm install ethers
npm install @metamask/detect-provider # provider detection helper
npm install react-toastify # transaction notifications
Rough equivalences when porting:
// web3.js
const balance = await web3.eth.getBalance(address);
web3.utils.fromWei(balance, "ether");
web3.utils.toWei("1", "ether");
// ethers v6
const balance = await provider.getBalance(address);
ethers.formatEther(balance);
ethers.parseEther("1");
ethers v6 returns native BigInt rather than BigNumber, so .add() and
.mul() are gone — use + and *. That is the main source of breakage when
upgrading from v5.
Transaction feedback
A transaction takes seconds to minutes. The interface has to show that, or users click twice.
const tx = await contract.someMethod(); // wallet prompt; resolves when submitted
toast.info(`Submitted: ${tx.hash}`);
const receipt = await tx.wait(); // resolves when mined
toast.success(`Confirmed in block ${receipt.blockNumber}`);
The promise from a contract call resolves as soon as the transaction is submitted, not when it is mined. Treating that as success is why dapps show a confirmation before anything has happened.
Handle rejection separately from failure — a user declining the wallet prompt is not an error condition:
catch (err) {
if (err.code === 4001) return; // user rejected
toast.error(err.shortMessage ?? err.message);
}
Styling with Tailwind
Utility-first CSS, so the result does not look like an unmodified template. VS Code has first-party support through the Tailwind CSS IntelliSense extension.
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Unknown at rule @tailwind
Unknown at rule @tailwindcss(unknownAtRules)
The CSS language server does not recognise Tailwind's directives. Either install
the PostCSS Language Support extension, or silence the rule in
.vscode/settings.json:
{
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore"
}
It is a warning from the editor only — the build is unaffected either way.
Build issues
webpack 5 no longer polyfills Node core modules
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules
by default.
Web3 libraries reference Node built-ins (crypto, stream, buffer) that
webpack 5 stopped shimming automatically. Three options, best first:
- Use a library that does not need them. viem and ethers v6 are built for browsers and avoid Node built-ins entirely. This removes the problem rather than patching it.
- Add the polyfills explicitly. With
node-polyfill-webpack-plugin, or per module throughresolve.fallback. - Pin an older toolchain.
npm install --save-dev [email protected]pulls in webpack 4. This works and leaves you on an unmaintained build chain with known vulnerabilities — a stopgap, not a fix.
ReferenceError: process is not defined
The same root cause: browser code reaching for a Node global. Define it in the bundler rather than hoping a restart clears it:
// vite.config.js
export default { define: { "process.env": {} } };
Restarting the dev server does sometimes make it go away, but only because the dependency graph is re-optimised — the underlying issue returns.
Related
- Development tools — local nodes, editor setup.
- Deployment and verification — getting contracts onto a network for the front end to talk to.