Building

Development Setup

Development Setup

Module 1 of Building


Development Framework Comparison

FeatureFoundryHardhat
LanguageRust/SolidityJavaScript/TypeScript
SpeedVery fastModerate
TestingSolidityJS/TS + Solidity
ScriptingSolidityJavaScript
CommunityGrowingLarge
Best ForContract devsFull-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

  1. Foundry for contract-focused development (fast, Solidity tests)
  2. Hardhat for full-stack dApps (JS/TS integration)
  3. OpenZeppelin for battle-tested implementations
  4. Always use testnets before mainnet
  5. Never commit private keys - use .env
  6. Verify contracts on Etherscan for transparency
  7. Write tests first - they'll save you