Development Tools

Frameworks, libraries, and utilities for EVM development


Appendix D: Development Tools

This appendix covers the essential tools for EVM smart contract development in 2025.

Frameworks

Frameworks automate tedious tasks and make development easier. In 2025, the two dominant frameworks for EVM development are Hardhat (JavaScript/TypeScript) and Foundry (Solidity-native). Both work identically on Ethereum Classic and Ethereum—they connect to any EVM chain via RPC.

Hardhat

Hardhat is the most widely used Ethereum development framework for JavaScript and TypeScript developers. It provides a flexible, extensible environment for compiling, deploying, testing, and debugging smart contracts.

Key features:

  • Hardhat Network: Built-in local blockchain with advanced debugging (console.log in Solidity, stack traces, mainnet forking)
  • TypeScript support: First-class TypeScript integration with type generation
  • Plugin ecosystem: Rich ecosystem of plugins for testing, deployment, verification
  • Multi-network: Easy configuration for ETC, ETH, and testnets

Installing Hardhat

Hardhat requires Node.js v18+ and npm. Install using nvm:

nvm install --lts
node -v  # v20.11.0
npm -v   # 10.2.4

Create a project:

mkdir Faucet
cd Faucet
npm init -y
npm install --save-dev hardhat
npx hardhat init

Select "Create a TypeScript project" for the best experience. The project structure:

Faucet
├── contracts/
│   └── Lock.sol
├── ignition/
│   └── modules/
├── test/
│   └── Lock.ts
├── hardhat.config.ts
├── package.json
└── tsconfig.json

Install additional dependencies:

npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install @openzeppelin/contracts

Configuring for ETC and ETH

Edit hardhat.config.ts:

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from "dotenv";
 
dotenv.config();
 
const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.24",
    settings: {
      optimizer: { enabled: true, runs: 200 },
      evmVersion: "shanghai",
    },
  },
  networks: {
    // ETC Networks
    mordor: {
      url: process.env.MORDOR_RPC || "https://rpc.mordor.etccooperative.org",
      chainId: 63,
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
    etc: {
      url: process.env.ETC_RPC || "https://etc.rivet.link",
      chainId: 61,
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
    // ETH Networks
    sepolia: {
      url: process.env.SEPOLIA_RPC || "https://rpc.sepolia.org",
      chainId: 11155111,
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
    mainnet: {
      url: process.env.ETH_RPC || "https://eth.llamarpc.com",
      chainId: 1,
      accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
    },
  },
};
 
export default config;

Create a .env file (add to .gitignore!):

PRIVATE_KEY=your_private_key_here

Deploying with Hardhat Ignition

Hardhat uses Ignition for deployments—a declarative system that tracks deployments and handles dependencies automatically.

Create ignition/modules/Faucet.ts:

import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
 
const FaucetModule = buildModule("FaucetModule", (m) => {
  const faucet = m.contract("Faucet");
  return { faucet };
});
 
export default FaucetModule;

Deploy:

npx hardhat compile
npx hardhat ignition deploy ignition/modules/Faucet.ts --network mordor

Using the Hardhat Console

npx hardhat console --network mordor
> const [signer] = await ethers.getSigners()
> const balance = await ethers.provider.getBalance(signer.address)
> ethers.formatEther(balance)
'3.2'
 
> const Faucet = await ethers.getContractFactory("Faucet")
> const faucet = Faucet.attach("0x1234...5678")
> await faucet.withdraw(ethers.parseEther("0.1"))

Foundry

Foundry is a blazing-fast toolkit written in Rust, particularly popular for:

  • Solidity-native testing: Write tests in Solidity, not JavaScript
  • Speed: Extremely fast compilation and test execution
  • Fuzzing: Built-in property-based testing
  • Mainnet forking: Test against real mainnet state
  • Gas reporting: Detailed gas usage analysis

Foundry consists of:

  • Forge: Testing framework
  • Cast: Command-line tool for interacting with contracts
  • Anvil: Local testnet node
  • Chisel: Solidity REPL

Installing Foundry

curl -L https://foundry.paradigm.xyz | bash
foundryup
forge --version

Creating a Foundry Project

forge init Faucet
cd Faucet

Structure:

Faucet
├── src/
│   └── Counter.sol
├── test/
│   └── Counter.t.sol
├── script/
├── foundry.toml
└── lib/

Configuring for ETC

Edit foundry.toml:

[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.24"
evm_version = "shanghai"
optimizer = true
optimizer_runs = 200
 
[rpc_endpoints]
mordor = "https://rpc.mordor.etccooperative.org"
etc = "https://etc.rivet.link"
sepolia = "https://rpc.sepolia.org"
mainnet = "https://eth.llamarpc.com"

Writing Solidity Tests

// test/Faucet.t.sol
pragma solidity ^0.8.24;
 
import "forge-std/Test.sol";
import "../src/Faucet.sol";
 
contract FaucetTest is Test {
    Faucet public faucet;
    address public user;
 
    function setUp() public {
        faucet = new Faucet();
        user = makeAddr("user");
        vm.deal(address(faucet), 1 ether);
    }
 
    function test_Withdraw() public {
        vm.prank(user);
        faucet.withdraw(0.1 ether);
        assertEq(user.balance, 0.1 ether);
    }
 
    function testFuzz_Withdraw(uint256 amount) public {
        amount = bound(amount, 1, 0.1 ether);
        vm.prank(user);
        faucet.withdraw(amount);
        assertEq(user.balance, amount);
    }
}

Run tests:

forge test

Deploying with Foundry

Create script/Deploy.s.sol:

pragma solidity ^0.8.24;
 
import "forge-std/Script.sol";
import "../src/Faucet.sol";
 
contract DeployScript is Script {
    function run() public {
        vm.startBroadcast();
        new Faucet();
        vm.stopBroadcast();
    }
}

Deploy:

forge script script/Deploy.s.sol --rpc-url mordor --broadcast --private-key $PRIVATE_KEY

OpenZeppelin Contracts

The most widely used library of secure, audited smart contracts. Version 5.x supports Solidity ^0.8.20.

Key contracts:

  • ERC20, ERC721, ERC1155: Token standard implementations
  • AccessControl, Ownable: Role-based and simple access control
  • ReentrancyGuard: Protection against reentrancy attacks
  • Pausable: Emergency stop functionality
  • SafeERC20: Safe token transfer wrappers

Installing

With Hardhat:

npm install @openzeppelin/contracts

With Foundry:

forge install OpenZeppelin/openzeppelin-contracts

Add to remappings.txt:

@openzeppelin/=lib/openzeppelin-contracts/

Example: ERC20 Token

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
 
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
 
contract MyToken is ERC20, Ownable {
    constructor() ERC20("MyToken", "MTK") Ownable(msg.sender) {
        _mint(msg.sender, 1000000 * 10 ** decimals());
    }
 
    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }
}

Libraries

ethers.js

The most widely used Ethereum library in 2025, known for its clean API and TypeScript support. Default library for Hardhat.

import { ethers } from "ethers";
 
// Connect to Mordor testnet
const provider = new ethers.JsonRpcProvider(
  "https://rpc.mordor.etccooperative.org"
);
 
// Get balance
const balance = await provider.getBalance("0x...");
console.log(ethers.formatEther(balance), "METC");
 
// Connect wallet
const wallet = new ethers.Wallet(privateKey, provider);
 
// Interact with contract
const faucet = new ethers.Contract(address, abi, wallet);
await faucet.withdraw(ethers.parseEther("0.1"));

viem

A modern TypeScript-first alternative to ethers.js, designed for smaller bundle sizes. Used by wagmi and RainbowKit.

import { createPublicClient, http } from "viem";
 
// Define ETC Mordor chain
const mordor = {
  id: 63,
  name: "Mordor",
  network: "mordor",
  nativeCurrency: { name: "Mordor Ether", symbol: "METC", decimals: 18 },
  rpcUrls: {
    default: { http: ["https://rpc.mordor.etccooperative.org"] },
  },
};
 
const client = createPublicClient({
  chain: mordor,
  transport: http(),
});
 
const balance = await client.getBalance({ address: "0x..." });

Other Libraries

LibraryLanguageDocumentation
web3.jsJavaScriptdocs.web3js.org
web3.pyPythonweb3py.readthedocs.io
web3jJava/Kotlindocs.web3j.io
Nethereum.NETdocs.nethereum.com

Testing Smart Contracts

Test Frameworks Summary

FrameworkTest LanguageTesting FrameworkLocal Blockchain
HardhatTypeScript/JavaScriptMocha + ChaiHardhat Network
FoundrySolidityforge-stdAnvil

Hardhat Testing

import { expect } from "chai";
import { ethers } from "hardhat";
 
describe("Faucet", function () {
  it("Should allow withdrawal", async function () {
    const Faucet = await ethers.getContractFactory("Faucet");
    const faucet = await Faucet.deploy();
 
    const [owner] = await ethers.getSigners();
    await owner.sendTransaction({
      to: faucet.target,
      value: ethers.parseEther("1.0")
    });
 
    await expect(
      faucet.withdraw(ethers.parseEther("0.1"))
    ).to.changeEtherBalance(owner, ethers.parseEther("0.1"));
  });
});

Run: npx hardhat test

Foundry Testing

pragma solidity ^0.8.24;
 
import "forge-std/Test.sol";
import "../src/Faucet.sol";
 
contract FaucetTest is Test {
    Faucet faucet;
 
    function setUp() public {
        faucet = new Faucet();
        vm.deal(address(faucet), 1 ether);
    }
 
    function test_Withdraw() public {
        address user = makeAddr("user");
        vm.prank(user);
        faucet.withdraw(0.1 ether);
        assertEq(user.balance, 0.1 ether);
    }
}

Run: forge test

Local Test Blockchains

Hardhat Network:

npx hardhat test      # Auto-starts for tests
npx hardhat node      # Standalone mode

Features: Solidity stack traces, console.log, mainnet forking, time manipulation.

Anvil:

anvil
anvil --chain-id 63  # Mordor chain ID

Features: Fast execution, mainnet forking, state snapshots, address impersonation.

When to Use Mordor vs Local

ScenarioRecommended
Unit testsHardhat Network or Anvil
Integration testsLocal (with forking if needed)
Pre-deployment verificationMordor testnet
Testing PoW mechanicsMordor testnet
CI/CD pipelinesHardhat Network or Anvil

Command-Line Utilities

Cast (Foundry)

# Get transaction receipt
cast receipt <txhash> --rpc-url mordor
 
# Get contract code
cast code <address> --rpc-url mordor
 
# Read storage slot
cast storage <address> <slot> --rpc-url mordor
 
# Call a view function
cast call <address> "getBalance()" --rpc-url mordor
 
# Send a transaction
cast send <address> "withdraw(uint256)" 100000000000000000 --rpc-url mordor --private-key $KEY

helpeth (EthereumJS)

helpeth keyGenerate           # Generate new key
helpeth keyDetails            # Print key details
helpeth parseTx <tx>          # Parse raw transaction
helpeth unitConvert 1 ether wei  # Convert units

Agentic Development Tools

AI-assisted development is transforming EVM development. These tools enable small teams to achieve what previously required large engineering organizations.

Claude Code

Claude Code is Anthropic's official CLI for AI-assisted development. It integrates directly with your development workflow.

Key features for EVM development:

  • Direct integration with Foundry and Hardhat
  • Contract generation, review, and testing
  • On-chain data access via MCP servers
  • Project-specific configuration via CLAUDE.md

Setting Up for EVM Projects

Create a CLAUDE.md file in your project root:

# CLAUDE.md - EVM Development
 
You are developing smart contracts for Ethereum Classic and Ethereum.
 
## Project Context
- Framework: Foundry (or Hardhat)
- Networks: Mordor (ETC testnet), Sepolia (ETH testnet)
- Solidity version: 0.8.24
 
## Development Principles
- Prioritize security over convenience
- Test thoroughly before deployment
- Document all external interactions
- Consider gas optimization for production
 
## Network Details
- ETC Mainnet: chainId 61, https://etc.rivet.link
- Mordor Testnet: chainId 63, https://rpc.mordor.etccooperative.org

MCP Servers for On-Chain Data

The Model Context Protocol (MCP) connects Claude to external data sources. Configure in .claude/settings.json:

{
  "mcpServers": {
    "ethereum": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-ethereum"],
      "env": {
        "RPC_URL": "https://rpc.mordor.etccooperative.org"
      }
    }
  }
}

With MCP enabled, Claude can:

  • Query on-chain state and balances
  • Fetch and analyze deployed contract code
  • Check transaction receipts and logs
  • Verify contract deployments

Agentic Workflows

Effective patterns for AI-assisted EVM development:

Contract Generation:

Generate an ERC-20 token with fixed supply, no admin functions,
and full NatSpec documentation. Include Foundry tests.

Security Review:

Review this contract for reentrancy, access control issues,
and centralization risks. Check against common vulnerability patterns.

Test Generation:

Generate comprehensive Foundry tests including:
- Unit tests for each function
- Fuzz tests for input validation
- Invariant tests for critical properties

Deployment Verification:

Verify the deployed contract at <address> on Mordor matches
the source code. Check constructor arguments and initialization.

Best Practices

  1. Encode project principles in CLAUDE.md for consistent output
  2. Review generated code before deployment—agents can hallucinate
  3. Test on Mordor/Sepolia before mainnet deployment
  4. Use agents for routine tasks, humans for architecture decisions
  5. Combine AI review with human review for production code

See Chapter 18: Agentic Development for comprehensive coverage of AI-assisted blockchain development.

Resources