Skip to main content

Development tools

Current landscape

The tooling changed substantially after 2023. What a tutorial from that era recommends is often no longer maintained.

ToolStatusUse for
FoundryActiveTesting and scripting in Solidity; fastest test runner
HardhatActiveJavaScript/TypeScript workflows, rich plugin ecosystem
RemixActiveBrowser IDE; quick experiments, no install
TruffleSunsetLegacy projects only — see Truffle
GanacheSunsetReplaced 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

AnvilHardhat NetworkGanache
Part ofFoundryHardhatSunset
Mainnet forkingYesYesYes
console.log in SolidityYesYesNo
MaintainedYesYesNo
# 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".