Development tools
Current landscape
The tooling changed substantially after 2023. What a tutorial from that era recommends is often no longer maintained.
| Tool | Status | Use for |
|---|---|---|
| Foundry | Active | Testing and scripting in Solidity; fastest test runner |
| Hardhat | Active | JavaScript/TypeScript workflows, rich plugin ecosystem |
| Remix | Active | Browser IDE; quick experiments, no install |
| Truffle | Sunset | Legacy projects only — see Truffle |
| Ganache | Sunset | Replaced by Anvil (Foundry) or the Hardhat network |
ConsenSys announced the end of Truffle and Ganache support in September 2023 and completed the sunset in 2024. Both still run, but receive no fixes and lag behind current Solidity and EVM versions. New work should start on Foundry or Hardhat; the official Truffle documentation itself points at Hardhat.
Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
forge init my-project
forge build
forge test -vvv # -vvv shows traces for failing tests
forge test --gas-report
anvil # local development node
Tests are written in Solidity, so there is no context switch between contract
and test language, and no JavaScript ABI layer to get wrong. forge test runs
in a fraction of the time a JavaScript-based suite takes, which changes how
often you run it.
Hardhat
npm install --save-dev hardhat
npx hardhat init
npx hardhat compile
npx hardhat test
npx hardhat node # local development node
npx hardhat ignition deploy ./ignition/modules/MyModule.ts --network sepolia
Hardhat Ignition replaced the older scripts/deploy.js pattern for deployments:
it is declarative, resumable after a failure, and records what was deployed
where.
Choose Hardhat when the project already has a TypeScript front end and shared tooling. Choose Foundry when the work is contract-heavy and test speed matters. Many projects use both — Foundry for tests, Hardhat for deployment.
Local development nodes
| Anvil | Hardhat Network | Ganache | |
|---|---|---|---|
| Part of | Foundry | Hardhat | Sunset |
| Mainnet forking | Yes | Yes | Yes |
console.log in Solidity | Yes | Yes | No |
| Maintained | Yes | Yes | No |
# Fork mainnet state locally, so real contracts and balances are available
anvil --fork-url https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
npx hardhat node --fork https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
Forking is the feature that makes local testing realistic — integrations against Uniswap, an ERC-20 you do not control, or an oracle can be exercised without a testnet deployment.
Ganache
Kept for reference; the CLI supported detached instances:
# Start in the background and stop it after the tests
GANACHE=$(ganache --detach) && npm run test; ganache instances stop "$GANACHE"
ganache instances list
ganache instances stop NAME
PowerShell needs different syntax — assignment takes a space around = and
command substitution uses parentheses:
$GANACHE = ganache --detach
npm run test
ganache instances stop $GANACHE
Remove-Variable GANACHE
Editor setup
Visual Studio Code with one of:
- Solidity by Juan Blanco — long-established, compiler version switching.
- Solidity by Nomic Foundation (Hardhat) — language server with go-to- definition and inline compiler diagnostics. The better choice for new setups.
Add solhint for linting and forge fmt or prettier-plugin-solidity for
formatting, and run both in CI.
Remix
The browser IDE at remix.ethereum.org. Nothing to install, and it connects to MetaMask for deployment.
To work on local files from the browser IDE:
npm install -g @remix-project/remixd
remixd -s ./ --remix-ide https://remix.ethereum.org
Then choose connect to localhost in Remix's file explorer. remixd exposes
the directory you point it at to the browser, so run it from the project
directory, not from / or your home directory.
Path aliases
jsconfig.json (or tsconfig.json) lets the front end import by alias instead
of counting ../ segments:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@pages/*": ["pages/*"],
"@components/*": ["components/*"],
"@styles/*": ["styles/*"]
}
}
}
This configures the editor and the bundler's resolver. Jest, Vitest and any other tool with its own resolver need the same mapping repeated in their own configuration, which is the usual reason an alias works in the browser and fails in tests.
JSON-RPC
Every node exposes the same JSON-RPC interface, which is what all the libraries above sit on top of.
{"jsonrpc":"2.0","method":"eth_accounts","params":[],"id":1}
curl -X POST http://127.0.0.1:8545 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
Useful methods when debugging: eth_getCode, eth_getStorageAt, eth_call,
eth_estimateGas, eth_getTransactionReceipt. Being able to reach for curl
saves a lot of time when a library is behaving unexpectedly — it separates
"the node says no" from "the library is wrong".