ethers, web3, and wagmi are JavaScript libraries that enable frontend applications to interact with the Ethereum blockchain, while web3modal provides a UI component for wallet connection. truffle is primarily a development framework for smart contract compilation, testing, and deployment, not intended for direct use in production frontend code. These packages serve different but sometimes overlapping roles in the Ethereum development stack, ranging from low-level JSON-RPC communication to high-level React hooks and user-facing wallet interfaces.
Building decentralized applications (dApps) requires connecting your frontend to the Ethereum blockchain. But not all libraries serve the same purpose — some handle low-level RPC calls, others manage wallet UIs, and one is strictly for backend development. Let’s clarify what each package does, how they compare technically, and where they fit in a real-world dApp architecture.
ethers and web3 are Ethereum client libraries. They let your app talk to nodes (via Infura, Alchemy, or local providers) using JSON-RPC under the hood. You use them to read blockchain data, send transactions, and interact with smart contracts.
wagmi is a React-specific abstraction over ethers (or web3) that provides hooks like useAccount, useContract, and useSendTransaction. It manages wallet state, providers, and caching automatically.
web3modal is a UI library that renders a modal popup letting users choose and connect wallets (MetaMask, Coinbase Wallet, WalletConnect, etc.). It doesn’t handle blockchain calls — it just establishes the connection.
truffle is a smart contract development framework. It compiles Solidity, runs tests in a local blockchain (Ganache), and deploys contracts. It’s meant for your development workflow — not for inclusion in browser bundles.
⚠️ Important:
truffleshould never appear in your frontend production code. It’s a Node.js tool, likehardhatorfoundry.
Connecting a wallet is the first step in any dApp. Here’s how each relevant package approaches it.
ethers requires manual setup with injected providers (like MetaMask):
// ethers: Manual wallet connection
import { ethers } from 'ethers';
if (window.ethereum) {
const provider = new ethers.BrowserProvider(window.ethereum);
await provider.send('eth_requestAccounts', []);
const signer = await provider.getSigner();
console.log(await signer.getAddress());
}
web3 uses a similar but more verbose pattern:
// web3: Manual connection
import Web3 from 'web3';
if (window.ethereum) {
const web3 = new Web3(window.ethereum);
await window.ethereum.request({ method: 'eth_requestAccounts' });
const accounts = await web3.eth.getAccounts();
console.log(accounts[0]);
}
wagmi abstracts this into a hook and works with web3modal:
// wagmi + web3modal: Declarative connection
import { createConfig, WagmiConfig, useAccount, useConnect } from 'wagmi';
import { coinbaseWallet, metaMask, walletConnect } from 'wagmi/connectors';
import { Web3Modal } from '@web3modal/react';
const config = createConfig({
connectors: [metaMask(), coinbaseWallet(), walletConnect({ projectId: '...' })]
});
function App() {
return (
<WagmiConfig config={config}>
<Web3Modal />
<MyComponent />
</WagmiConfig>
);
}
function MyComponent() {
const { address, isConnected } = useAccount();
const { connect, connectors } = useConnect();
// UI renders based on state
}
web3modal alone doesn’t connect — it just provides the UI. You still need wagmi or ethers underneath.
truffle has no role here — it doesn’t run in the browser.
Once connected, you’ll call smart contract functions. Here’s how each library handles it.
ethers uses human-readable ABI and concise syntax:
// ethers: Contract interaction
const abi = ['function balanceOf(address) view returns (uint256)'];
const contract = new ethers.Contract('0x...', abi, signer);
const balance = await contract.balanceOf('0x...');
await contract.approve('0xspender', 1000); // write
web3 requires more boilerplate and uses PromiEvents for transactions:
// web3: Contract interaction
const abi = [{ name: 'balanceOf', type: 'function', ... }];
const contract = new web3.eth.Contract(abi, '0x...');
const balance = await contract.methods.balanceOf('0x...').call();
await contract.methods.approve('0xspender', 1000).send({ from: account });
wagmi wraps this in React hooks with automatic reloading and error handling:
// wagmi: Hook-based interaction
import { useContractRead, useContractWrite } from 'wagmi';
const { data: balance } = useContractRead({
address: '0x...',
abi,
functionName: 'balanceOf',
args: ['0x...']
});
const { write } = useContractWrite({
address: '0x...',
abi,
functionName: 'approve',
args: ['0xspender', 1000]
});
web3modal and truffle do not provide contract interaction APIs in the frontend.
truffle shines in development workflows outside the browser:
// truffle: Migration script (Node.js only)
const MyToken = artifacts.require('MyToken');
module.exports = async function (deployer) {
await deployer.deploy(MyToken, 'MyToken', 'MTK');
};
You’d use truffle compile to generate contract ABIs, which you then import into your frontend (e.g., into ethers or wagmi). But truffle itself never ships to users.
ethers (minimal, direct) or wagmi + web3modal (React, high-level).truffle for compiling, testing, and deploying contracts.web3, but avoid it in new projects.wagmi uses ethers under the hood by default (though it supports web3 too).web3modal is designed to work with wagmi but can also integrate with raw ethers via custom adapters.truffle outputs artifacts (JSON files with ABI and bytecode) that any frontend library can consume.As of 2024:
web3 (v1.x) is in maintenance mode. The team recommends migrating to web3.js v4 (still evolving) or alternatives like ethers.ethers v6 is actively maintained with regular updates.wagmi and web3modal are under active development by the WalletConnect team.truffle remains widely used but faces competition from hardhat; it’s stable but not aggressively innovating.For a new React dApp:
wagmi + web3modal for rapid development with built-in best practices.ethers only if you need custom logic not covered by wagmi hooks.For a vanilla JS or non-React app:
ethers directly — it’s lean, modern, and well-documented.Never include truffle or web3 in your frontend bundle for new projects.
| Package | Browser-Safe? | React Hooks? | Wallet UI? | Contract Calls? | Dev-Only? |
|---|---|---|---|---|---|
ethers | ✅ Yes | ❌ No | ❌ No | ✅ Yes | ❌ No |
truffle | ❌ No | ❌ No | ❌ No | ❌ (Not in browser) | ✅ Yes |
wagmi | ✅ Yes | ✅ Yes | ❌ (needs web3modal) | ✅ Yes | ❌ No |
web3 | ✅ Yes | ❌ No | ❌ No | ✅ Yes | ❌ No |
web3modal | ✅ Yes | ✅ (React version) | ✅ Yes | ❌ No | ❌ No |
Choose based on your stack, not trends. If you’re in React, wagmi saves time. If you’re optimizing for performance or using Svelte/Vue/vanilla JS, ethers gives you control without bloat.
Choose ethers when you need a lightweight, modern, and well-maintained library for direct Ethereum interaction with clean APIs, strong TypeScript support, and minimal bundle size. It’s ideal for production frontend applications where you want fine-grained control over providers, signers, and contract interactions without heavy abstraction.
Choose truffle only for local development tasks like compiling, testing, and deploying smart contracts — not for browser-based frontend logic. It should not be included in your frontend bundle, as it’s designed for Node.js environments and lacks optimizations for web clients.
Choose wagmi if you’re building a React application and want a declarative, hooks-based interface that abstracts wallet connection, provider management, and contract interactions. It integrates seamlessly with web3modal and reduces boilerplate for common dApp patterns, but requires React and adds another layer of abstraction.
Avoid web3 in new projects unless maintaining legacy code. While historically dominant, it is heavier, less intuitive, and has been largely superseded by ethers in terms of API design and maintenance. The package remains functional but is no longer the recommended choice for modern dApp frontends.
Choose web3modal when you need a plug-and-play UI to let users connect their wallets (MetaMask, WalletConnect, etc.) in a consistent, accessible way. It works best alongside wagmi or ethers and handles the complexity of multi-wallet integration so you don’t have to build your own connector UI.
A complete, compact and simple library for Ethereum and ilk, written in TypeScript.
Features
For advisories and important notices, follow @ethersproject on Twitter (low-traffic, non-marketing, important information only) as well as watch this GitHub project.
For more general news, discussions, and feedback, follow or DM me, @ricmoo on Twitter or on the Ethers Discord.
For the latest changes, see the CHANGELOG.
Summaries
NodeJS
/home/ricmoo/some_project> npm install ethers
Browser (ESM)
The bundled library is available in the ./dist/ folder in this repo.
<script type="module">
import { ethers } from "./dist/ethers.min.js";
</script>
Browse the documentation online:
Ethers works closely with an ever-growing list of third-party providers to ensure getting started is quick and easy, by providing default keys to each service.
These built-in keys mean you can use ethers.getDefaultProvider() and
start developing right away.
However, the API keys provided to ethers are also shared and are intentionally throttled to encourage developers to eventually get their own keys, which unlock many other features, such as faster responses, more capacity, analytics and other features like archival data.
When you are ready to sign up and start using for your own keys, please check out the Provider API Keys in the documentation.
A special thanks to these services for providing community resources:
The ethers package only includes the most common and most core
functionality to interact with Ethereum. There are many other
packages designed to further enhance the functionality and experience.
call to reduce latency and backend request capacityMIT License (including all dependencies).