Development Setup
Module 1 of Building
Development Framework Comparison
| Feature | Foundry | Hardhat |
|---|---|---|
| Language | Rust/Solidity | JavaScript/TypeScript |
| Speed | Very fast | Moderate |
| Testing | Solidity | JS/TS + Solidity |
| Scripting | Solidity | JavaScript |
| Community | Growing | Large |
| Best For | Contract devs | Full-stack devs |
Recommendation: Use Foundry for smart contract work, Hardhat for JS-heavy projects.
Foundry Setup
Installation
# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Verify installation
forge --version
cast --version
anvil --version
Create New Project
# Initialize new project
forge init my-project
cd my-project
# Project structure
├── src/ # Solidity contracts
│ └── Counter.sol
├── test/ # Test files
│ └── Counter.t.sol
├── script/ # Deployment scripts
├── lib/ # Dependencies
└── foundry.toml # Configuration
Install Dependencies
# Install OpenZeppelin
forge install OpenZeppelin/openzeppelin-contracts
# Install other libraries
forge install transmissions11/solmate
forge install foundry-rs/forge-std
# Update remappings
forge remappings > remappings.txt
Configuration (foundry.toml)
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.20"
optimizer = true
optimizer_runs = 200
[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
sepolia = "${SEPOLIA_RPC_URL}"
[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
sepolia = { key = "${ETHERSCAN_API_KEY}" }
Hardhat Setup
Installation
# Create new project
mkdir my-hardhat-project
cd my-hardhat-project
npm init -y
# Install Hardhat
npm install --save-dev hardhat
# Initialize project
npx hardhat init
# Select "Create a TypeScript project"
# Install common dependencies
npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install @openzeppelin/contracts
Project Structure
├── contracts/ # Solidity contracts
├── scripts/ # Deployment scripts
├── test/ # Test files
├── hardhat.config.ts # Configuration
└── package.json
Configuration (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.20",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.PRIVATE_KEY
? [process.env.PRIVATE_KEY]
: []
},
mainnet: {
url: process.env.MAINNET_RPC_URL || "",
accounts: process.env.PRIVATE_KEY
? [process.env.PRIVATE_KEY]
: []
}
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY
}
};
export default config;
Essential Libraries
OpenZeppelin Contracts
Battle-tested implementations:
// ERC-20 Token
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {
_mint(msg.sender, 1000000 * 10**18);
}
}
// ERC-721 NFT
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyNFT is ERC721 {
uint256 private _tokenId;
constructor() ERC721("MyNFT", "MNFT") {}
function mint(address to) public {
_mint(to, _tokenId++);
}
}
Solmate (Gas-Optimized)
import "solmate/tokens/ERC20.sol";
import "solmate/auth/Owned.sol";
contract GasOptimizedToken is ERC20, Owned {
constructor() ERC20("Token", "TKN", 18) Owned(msg.sender) {
_mint(msg.sender, 1000000e18);
}
}
Local Development
Anvil (Foundry)
# Start local node
anvil
# With forking
anvil --fork-url https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
# Custom options
anvil --port 8545 --accounts 10 --balance 10000
Hardhat Network
# Start local node
npx hardhat node
# With forking
npx hardhat node --fork https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
Testing
Foundry Testing
// test/Counter.t.sol
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/Counter.sol";
contract CounterTest is Test {
Counter counter;
function setUp() public {
counter = new Counter();
}
function test_Increment() public {
counter.increment();
assertEq(counter.number(), 1);
}
function testFuzz_SetNumber(uint256 x) public {
counter.setNumber(x);
assertEq(counter.number(), x);
}
function testFail_Decrement() public {
// Expect this to revert
counter.decrement(); // Would underflow
}
}
Run Tests
# Run all tests
forge test
# With verbosity
forge test -vvv
# Specific test
forge test --match-test test_Increment
# With gas report
forge test --gas-report
# Coverage
forge coverage
Hardhat Testing
// test/Counter.ts
import { expect } from "chai";
import { ethers } from "hardhat";
describe("Counter", function () {
it("Should increment", async function () {
const Counter = await ethers.getContractFactory("Counter");
const counter = await Counter.deploy();
await counter.increment();
expect(await counter.number()).to.equal(1);
});
});
Deployment
Foundry Script
// script/Deploy.s.sol
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "../src/Counter.sol";
contract DeployScript is Script {
function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);
Counter counter = new Counter();
console.log("Counter deployed to:", address(counter));
vm.stopBroadcast();
}
}
Run Deployment
# Dry run (simulation)
forge script script/Deploy.s.sol --rpc-url sepolia
# Actual deployment
forge script script/Deploy.s.sol \
--rpc-url sepolia \
--broadcast \
--verify
# Resume failed verification
forge script script/Deploy.s.sol \
--rpc-url sepolia \
--resume
Hardhat Deployment
// scripts/deploy.ts
import { ethers } from "hardhat";
async function main() {
const Counter = await ethers.getContractFactory("Counter");
const counter = await Counter.deploy();
await counter.waitForDeployment();
console.log("Counter deployed to:", await counter.getAddress());
}
main().catch((error) => {
console.error(error);
process.exit(1);
});
# Deploy
npx hardhat run scripts/deploy.ts --network sepolia
# Verify
npx hardhat verify --network sepolia DEPLOYED_ADDRESS
Useful Cast Commands
Cast is Foundry's CLI for interacting with contracts:
# Read contract
cast call 0x... "balanceOf(address)" 0xYourAddress --rpc-url mainnet
# Send transaction
cast send 0x... "transfer(address,uint256)" 0xTo 1000000000000000000 \
--private-key $PRIVATE_KEY \
--rpc-url mainnet
# Get ETH balance
cast balance 0x... --rpc-url mainnet
# Convert units
cast to-wei 1.5 ether # 1500000000000000000
cast from-wei 1500000000000000000 # 1.5
# Decode calldata
cast 4byte-decode 0xa9059cbb...
# Get storage slot
cast storage 0x... 0 --rpc-url mainnet
# Gas price
cast gas-price --rpc-url mainnet
Environment Setup
.env File
# .env (NEVER commit this!)
PRIVATE_KEY=0x...
MAINNET_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/...
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/...
ETHERSCAN_API_KEY=...
.gitignore
# Foundry
out/
cache/
broadcast/
# Hardhat
artifacts/
cache/
typechain-types/
# Environment
.env
.env.local
# Dependencies
node_modules/
lib/
VS Code Setup
Extensions
- Solidity by Juan Blanco
- Solidity Visual Developer by tintinweb
- Prettier Solidity
settings.json
{
"solidity.formatter": "prettier",
"[solidity]": {
"editor.defaultFormatter": "JuanBlanco.solidity"
},
"solidity.compileUsingRemoteVersion": "v0.8.20",
"editor.formatOnSave": true
}
Key Takeaways
- Foundry for contract-focused development (fast, Solidity tests)
- Hardhat for full-stack dApps (JS/TS integration)
- OpenZeppelin for battle-tested implementations
- Always use testnets before mainnet
- Never commit private keys - use .env
- Verify contracts on Etherscan for transparency
- Write tests first - they'll save you