
AMM, LP, Slippage, Impermanent Loss, Stablecoin AMM (Curve)
To journey from "Centralized Order Book Exchanges" all the way to "AMM + Curve Stablecoin Pools,"

Web3 Full-Stack Development Field Guide: ERC-721 NFT Issuance System
Building a full-stack DApp from scratch: Contracts, Frontend Interface, and Backend Event Listening.
Subscribe to over_akk

AMM, LP, Slippage, Impermanent Loss, Stablecoin AMM (Curve)
To journey from "Centralized Order Book Exchanges" all the way to "AMM + Curve Stablecoin Pools,"

Web3 Full-Stack Development Field Guide: ERC-721 NFT Issuance System
Building a full-stack DApp from scratch: Contracts, Frontend Interface, and Backend Event Listening.


<100 subscribers
<100 subscribers
Stage | Day | Core Theme | Java/Backend Analogy | Key Skills |
|---|---|---|---|---|
Foundation | Day 1 | Env Setup & Compilation | Maven/Gradle Init + | Node.js Env, Hardhat Config, Dependency Mgmt, ESM vs CommonJS |
Security | Day 2 | TDD (Test Driven Dev) | JUnit + Mockito | Mocha/Chai Assertions, Ethers.js, Negative Testing (Revert), Boundary Checks |
Architecture | Day 3 | Contract Upgrades (Proxy) | Hot Swap / Interface vs Impl | UUPS Proxy Pattern, Storage Layout, |
Interaction | Day 4 | Scripts & Automation |
| Hardhat Tasks, Automation Scripts, Provider vs Signer, Gas Estimation |
Simulation | Day 5 | Mainnet Forking | Docker Containers / Testcontainers | Mainnet Simulation, Account Impersonation, Interaction with Uniswap/Aave |
Release | Day 6 | Testnet Deployment | CI/CD / Staging Release | Alchemy/Infura Config, Sepolia Testnet, Etherscan Source Verification |
Optimization | Day 7 | Gas Optimization & Audit | JVM Tuning / Code Coverage | Gas Reporter Analysis, Solidity Coverage, Delivery Standards |
The goal was to establish a TypeScript-based Hardhat project and compile the first ERC-20 token contract. This is the Web3 equivalent of Maven Init + Hello World.
Just as Java relies on Maven/Gradle, Solidity development requires a toolchain to manage compilation, dependencies, testing, and deployment. Hardhat is the current industry standard; it includes a built-in local Ethereum network (Localhost), offering a developer experience closest to traditional backend development.
Pitfall 1: The "Version Conflict Loop" (npm error ERESOLVE)
Symptom: npm init fails with unable to resolve dependency tree, indicating incompatibility between Hardhat v3 and plugins.
Root Cause: Hardhat v3 was recently released, but the ecosystem plugins (like hardhat-ethers) still depend on v2. This is similar to the friction when Spring Boot 3 was first released while third-party libraries were still catching up.
Solution: Downgrade immediately. Manually modify package.json to lock hardhat: "^2.22.0" and run npm install.
Pitfall 2: Configuration "Dialect" Conflict (Error HH19)
Symptom: Error Your project is an ESM project... but your config uses .js extension.
Root Cause: package.json contained "type": "module" (ESM), but Hardhat defaults to loading configuration via CommonJS.
Solution: Simplification. Remove "type": "module" and revert to the robust CommonJS mode.
Pitfall 3: Phantom Dependencies (MODULE_NOT_FOUND)
Symptom: Modules are installed, but scripts fail claiming the module cannot be found.
Root Cause: Residual fragments in node_modules from previous failed installations.
Solution: The "Nuclear Option":
rm -rf node_modules package-lock.json
npm install
Successfully compiled HKToken.sol resulting in the green text: Compiled successfully.
Used Mocha and Chai to write unit tests, verifying transfer logic and simulating "malicious attack" scenarios.
In Java, a Bug is just an Exception; in Blockchain, a Bug = Financial Loss. Smart contracts are immutable once deployed (unless using Proxy patterns), so test coverage must effectively be 100%.
Hardhat Network: An ephemeral blockchain running in memory.
Signers: Simulated users with private keys (similar to Mock Users).
Assert Revert: Verifying that a transaction fails as expected.
Using Ethers.js (OOP style) over Viem, as it aligns better with Java Object intuition.
// Negative Testing: Verify that transfers from an empty wallet revert
it("Security Check: Insufficient balance should revert", async function () {
await expect(token.connect(addr1).transfer(addr2.address, 1000n))
.to.be.reverted; // Similar to JUnit's assertThrows
});
This was the most intensive session.
Implemented the UUPS Proxy Pattern, seamlessly upgrading a token from V1 (Basic) to V2 (With Slogan), and executing the full flow on a persistent local network.
Blockchain code is immutable, so how do we upgrade? The answer is the "Body Double" (Proxy) pattern.
Proxy Contract: Holds the state (Storage). Address never changes.
Implementation Contract: Holds the logic (Code).
DelegateCall: The Proxy executes the Implementation's code within its own context (manipulating its own storage).
Upgrade: Simply point the Proxy to a new Implementation address.
Pitfall 1: Solidity OOP Rules (Trying to override non-virtual function)
Symptom: Compilation error regarding function overrides.
Root Cause: Like Java's @Override, parent functions must be marked virtual, and children must be marked override. Solidity is extremely strict about this.
Pitfall 2: The Amnesiac Blockchain (Contract not found)
Symptom: deploy.ts runs successfully, but the subsequent upgrade.ts fails, claiming the contract is missing.
Root Cause: npx hardhat run spins up a new, empty in-memory blockchain every time. Once the script finishes, the chain is destroyed.
Solution: Persistent Localhost Mode.
Terminal A: Run npx hardhat node (Like starting a long-running Tomcat server).
Terminal B: Deploy/Upgrade using --network localhost.
npx hardhat run scripts/deploy-proxy.ts --network localhost
# Note the address, update upgrade.ts
npx hardhat run scripts/upgrade.ts --network localhost
Pitfall 3: Configuration Loading (upgrades is undefined)
Symptom: Code references upgrades plugin, but it is undefined at runtime.
Solution: Explicitly import "@openzeppelin/hardhat-upgrades" at the top of hardhat.config.ts, ensuring it loads after toolbox.
Transitioning from "writing Java classes" to "understanding the EVM State Machine".
Environment Configuration is the biggest blocker: Web3 toolchains iterate rapidly. Version conflicts are the norm.
Security is the First Principle: All logic must assume it is under attack. Test code is more critical than business logic.
Understand the Low Level: Understanding the difference between "Ephemeral" and "Persistent" networks is key to solving state-related errors.
Current Stage: Day 4 - Interaction.
Java/Backend Analogy: Main Method / Shell Scripts.
Role Shift:
Days 1-3: You wrote Library or Service classes (Logic resting on disk).
Day 4: You are writing public static void main or deploy.sh. You need to execute this logic within a specific environment (Network).
Newcomers often confuse these. In backend Ops, they are distinct:
Scripts: Deployment/Initialization Flows.
Analogy: Your InstallWizard or DB_Init.sql.
Usage: Deploying contracts, setting initial params. Usually one-off.
Command: npx hardhat run scripts/deploy.js
Hardhat Tasks: CLI Ops Tools.
Analogy: Shell scripts for Ops, or Spring Boot Actuator endpoints.
Usage: Ad-hoc operations. e.g., "Check Balance", "Force Pause Contract".
Command: npx hardhat balance --account 0x123...
The core of Ethers.js interaction, and a major shift from Web2:
Provider: Read-Only Connection.
Analogy: A Database SELECT permission, or a read-only REST Client.
Function: Connect to nodes, read block data, read contract state (view/pure functions). No Private Key required.
Signer: Read-Write Connection.
Analogy: DB connection with INSERT/UPDATE privileges, holding the "Security Token" (Private Key).
Function: Send transactions, change chain state, pay Gas. Private Key required.
Trap: Trying to call a state-changing function (
transfer) using only a Provider will fail.
Analogy: Budget Assessment before running a cloud job.
Significance: An infinite loop in Java spikes CPU; in Solidity, it burns real money.
Action: Use contract.estimateGas.methodName() in scripts to prevent Out of Gas (OOG) failures.
Structure code around asynchronous orchestration:
// Typical Day 4 Automation Mindset
async function main() {
// 1. Identity (Signer)
const [admin] = await ethers.getSigners();
// 2. Class Loading (Factory)
const Factory = await ethers.getContractFactory("MyContract");
// 3. Instantiation (Deploy)
const contract = await Factory.deploy();
await contract.deployed(); // Wait for network confirmation (Rare in Web2)
// 4. Interaction
console.log("Current Value:", await contract.getValue()); // Read
const tx = await contract.setValue(100); // Write
await tx.wait(); // Wait for confirmation again
}
Prologue: Days 1-4 were building in a desert (Localhost). Day 5 is "Folding Space". We clone the bustling Ethereum Mainnet directly to our local machine. You now have "God Mode" over a parallel universe.
Instead of deploying to a slow Testnet to test "Will my contract work with Uniswap?", we use Forking.
Real Data: The local Uniswap pool actually contains billions in liquidity (mirrored from the specific block height).
Virtual Operations: You can dump the market in this fork without affecting reality.
Isolated State: Restart the node, and everything resets.
Config hierarchy is critical.
networks: {
hardhat: { // ๐ Must be under hardhat network
forking: {
url: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
blockNumber: 19200000, // ๐ก Pin the block number for caching performance!
enabled: true
}
}
}
In a Fork, you don't need private keys. You can become anyone (e.g., Vitalik or a Binance Hot Wallet).
Scenario: Need massive USDT to test logic.
Action: Impersonate a Whale.
// 1. Tell the network: I want to be this address
await network.provider.request({
method: "hardhat_impersonateAccount",
params: ["0xd8da6..."], // Vitalik's address
});
// 2. Get the Signer object
const whaleSigner = await ethers.getSigner("0xd8da6...");
We stop deploying and start connecting.
Prerequisites: Address (from Etherscan) + Interface (ABI).
Human-Readable ABI: No need for the full JSON.
const DAI_ABI = [
"function balanceOf(address owner) view returns (uint256)",
"function transfer(address to, uint amount) returns (bool)"
];
const daiContract = new ethers.Contract(DAI_ADDRESS, DAI_ABI, signer);
Error 1: bad address checksum
Cause: Ethereum addresses use mixed-case for checksum validation (EIP-55).
Solution: Lowercase the address. Ethers.js will skip the checksum validation.
Error 2: Provider or Contract? (Confusion)
ETH (The Native Asset): Managed by Provider -> provider.getBalance(address).
Tokens (The Smart Contracts): Managed by Contract -> contract.balanceOf(address).
Error 3: Whales with no Gas
Symptom: "Sender doesn't have enough funds to send tx".
Cause: The Whale has 100M USDT, but 0 ETH for Gas.
Solution: God Mode Printing. Use hardhat_setBalance to force-set the Whale's ETH balance.
Prologue: Moving from the vacuum of Localhost to the "Staging Environment" of the internet: Sepolia Testnet. Real latency, real Gas, real visibility.
Sepolia Deployment: Deploying to a public network where data is globally accessible.
Source Verification: Uploading Solidity source to Etherscan to match the Bytecode. This is the "Certificate of Trust" in Web3.
Security: .env Management Never hardcode private keys.
.env content (GitIgnore this!):
SEPOLIA_RPC_URL="https://eth-sepolia.g.alchemy.com/..."
PRIVATE_KEY="your_private_key_without_0x_prefix"
ETHERSCAN_API_KEY="your_etherscan_key"
Hardhat Config:
require("dotenv").config();
// ...
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY], // ๐ Reference the key
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
Obstacle: Standard faucets require Mainnet balance (Anti-Sybil). Solution: PoW Faucet (e.g., Sepolia PoW Faucet). Trade CPU time for Testnet ETH.
Step 1: Deploy
npx hardhat run scripts/deploy.js --network sepolia
Wait for block confirmation...
Step 2: Verify
npx hardhat verify --network sepolia <CONTRACT_ADDRESS> <CONSTRUCTOR_ARGS>
Result: A green checkmark on Etherscan.
Error: injecting env (0) / Private Key Undefined
Cause: Forgot to save .env file, or dotenv config is not at the top of hardhat.config.js.
Error: Address vs Private Key
Cause: Putting the Public Address (0x...) into the PRIVATE_KEY field.
Solution: Export the actual Private Key from MetaMask.
Warning: Sourcify Verification Skipped
Status: Ignore. As long as Etherscan verifies successfully, you are good.
Prologue: Day 6 was "Zero to One" (Deployment). Day 7 is "One to One Hundred". Writing code that works is merely passing; writing code that is Gas Efficient and Secure is the core competency of a Web3 Engineer.
What is Gas Optimization? In the EVM, every instruction costs the user money (Gas).
Storage: Permanently etching data onto the blockchain. Extremely Expensive.
Memory: Temporary scratchpad during computation. Cleared after transaction. Cheap.
Why Optimize?
User Responsibility: Bad code can cost users tens or hundreds of dollars for a simple transaction.
Preventing Failure: Unoptimized loops can hit the Block Gas Limit, causing transactions to fail permanently as data grows.
Professionalism: Gas optimization is the dividing line between a "Junior Developer" and a "Senior Engineer".
We built two contracts with identical functionality but different implementations: BadContract vs. GoodContract.
EVM storage slots are 256 bits (32 bytes).
Bad: Defining multiple uint256 variables for small values. Each takes a full slot.
Good: Sequential definition of smaller types (e.g., uint64). The EVM packs them into a single slot.
Bad: require(x > 0, "Error: The value must be greater than zero..."). You pay for every character stored.
Good: Use Custom Errors (error InvalidValue();) with revert InvalidValue();. Only stores a 4-byte selector. Extremely cheap.
This yields the most dramatic savings.
๐ด Bad Code (Storage Thrashing):
uint256 public total; // Storage variable
function sumArray(uint256[] memory nums) public {
for(uint256 i = 0; i < nums.length; i++) {
total += nums[i]; // Writes to Blockchain Disk (SSTORE) on EVERY iteration!
}
}
๐ข Good Code (Memory Caching):
function sumArray(uint256[] calldata nums) public {
uint256 _tempTotal = total; // 1. Read from Storage to Stack/Memory ONCE
for(uint256 i = 0; i < nums.length; i++) {
_tempTotal += nums[i]; // 2. Compute in Memory (Almost free)
}
total = _tempTotal; // 3. Write to Storage ONCE at the end
}
Running Hardhat tests on an array of length 10:
Operation | BadContract (Gas) | GoodContract (Gas) | Savings |
|---|---|---|---|
Update (Write) | ~43,813 | ~26,500 | 40% Saved |
SumArray (Loop) | ~60,000+ (Explodes with length) | ~31,506 (Constant) | 50%+ Saved |
๐ณ Pitfall: Stale Artifacts (The "Phantom Code" Anomaly)
Symptom: You change the Solidity logic (e.g., double the array size), but npx hardhat test reports the exact same Gas usage.
Cause: Hardhat aggressively caches compilation artifacts to speed up dev. If it thinks files haven't changed, it runs the old bytecode.
Solution: The "Nuclear" Clean.
This deletes cache and artifacts, forcing a full recompile.
๐ก Pro Tips:
Realistic Testing: Do not test Gas with value 0. The EVM handles zero values specially (Gas refunds), which skews estimates. Use realistic non-zero values.
Calldata vs. Memory: For read-only array arguments, always use calldata. It avoids the cost of copying data to memory.
Achievement: Built a pro-grade Web3 environment and ran "Hello World".
Tech Stack: Node.js, VS Code, Hardhat Init, Solidity Basics (Lock.sol), Localhost (npx hardhat node).
Transformation: Conquered the fear of the terminal; mastered compile and run commands.
Achievement: Learned to prove "My code works".
Tech Stack:
Testing: Mocha/Chai with expect assertions.
Debugging: On-chain console.log (rare in traditional dev).
Interaction: Scripting user behaviors (Deposit, Withdraw, Time-locks).
Transformation: Evolved from "Blind Coding" to TDD (Test Driven Development).
Achievement: Stepped out of the sandbox into the actual Blockchain network.
Tech Stack:
Mainnet Forking: "God Mode" โ Simulating rich accounts and interacting with live DeFi protocols locally.
Public Testnet (Sepolia): Live Fire Exercise. Node config, Faucets.
Wallet Security: Deep understanding of .env and .gitignore.
Verification: Open-sourcing code on Etherscan for the green checkmark .
Transformation: Gained the capability to deliver and publish real DApps.
Achievement: Dove into the EVM internals to save users money.
Tech Stack:
Gas Profiling: Visualizing costs with hardhat-gas-reporter.
Storage vs. Memory: optimizing the most expensive resource.
Custom Errors: Dropping expensive strings.
Troubleshooting: Solving the classic Stale Artifacts issue (npx hardhat clean).
Stage | Day | Core Theme | Java/Backend Analogy | Key Skills |
|---|---|---|---|---|
Foundation | Day 1 | Env Setup & Compilation | Maven/Gradle Init + | Node.js Env, Hardhat Config, Dependency Mgmt, ESM vs CommonJS |
Security | Day 2 | TDD (Test Driven Dev) | JUnit + Mockito | Mocha/Chai Assertions, Ethers.js, Negative Testing (Revert), Boundary Checks |
Architecture | Day 3 | Contract Upgrades (Proxy) | Hot Swap / Interface vs Impl | UUPS Proxy Pattern, Storage Layout, |
Interaction | Day 4 | Scripts & Automation |
| Hardhat Tasks, Automation Scripts, Provider vs Signer, Gas Estimation |
Simulation | Day 5 | Mainnet Forking | Docker Containers / Testcontainers | Mainnet Simulation, Account Impersonation, Interaction with Uniswap/Aave |
Release | Day 6 | Testnet Deployment | CI/CD / Staging Release | Alchemy/Infura Config, Sepolia Testnet, Etherscan Source Verification |
Optimization | Day 7 | Gas Optimization & Audit | JVM Tuning / Code Coverage | Gas Reporter Analysis, Solidity Coverage, Delivery Standards |
The goal was to establish a TypeScript-based Hardhat project and compile the first ERC-20 token contract. This is the Web3 equivalent of Maven Init + Hello World.
Just as Java relies on Maven/Gradle, Solidity development requires a toolchain to manage compilation, dependencies, testing, and deployment. Hardhat is the current industry standard; it includes a built-in local Ethereum network (Localhost), offering a developer experience closest to traditional backend development.
Pitfall 1: The "Version Conflict Loop" (npm error ERESOLVE)
Symptom: npm init fails with unable to resolve dependency tree, indicating incompatibility between Hardhat v3 and plugins.
Root Cause: Hardhat v3 was recently released, but the ecosystem plugins (like hardhat-ethers) still depend on v2. This is similar to the friction when Spring Boot 3 was first released while third-party libraries were still catching up.
Solution: Downgrade immediately. Manually modify package.json to lock hardhat: "^2.22.0" and run npm install.
Pitfall 2: Configuration "Dialect" Conflict (Error HH19)
Symptom: Error Your project is an ESM project... but your config uses .js extension.
Root Cause: package.json contained "type": "module" (ESM), but Hardhat defaults to loading configuration via CommonJS.
Solution: Simplification. Remove "type": "module" and revert to the robust CommonJS mode.
Pitfall 3: Phantom Dependencies (MODULE_NOT_FOUND)
Symptom: Modules are installed, but scripts fail claiming the module cannot be found.
Root Cause: Residual fragments in node_modules from previous failed installations.
Solution: The "Nuclear Option":
rm -rf node_modules package-lock.json
npm install
Successfully compiled HKToken.sol resulting in the green text: Compiled successfully.
Used Mocha and Chai to write unit tests, verifying transfer logic and simulating "malicious attack" scenarios.
In Java, a Bug is just an Exception; in Blockchain, a Bug = Financial Loss. Smart contracts are immutable once deployed (unless using Proxy patterns), so test coverage must effectively be 100%.
Hardhat Network: An ephemeral blockchain running in memory.
Signers: Simulated users with private keys (similar to Mock Users).
Assert Revert: Verifying that a transaction fails as expected.
Using Ethers.js (OOP style) over Viem, as it aligns better with Java Object intuition.
// Negative Testing: Verify that transfers from an empty wallet revert
it("Security Check: Insufficient balance should revert", async function () {
await expect(token.connect(addr1).transfer(addr2.address, 1000n))
.to.be.reverted; // Similar to JUnit's assertThrows
});
This was the most intensive session.
Implemented the UUPS Proxy Pattern, seamlessly upgrading a token from V1 (Basic) to V2 (With Slogan), and executing the full flow on a persistent local network.
Blockchain code is immutable, so how do we upgrade? The answer is the "Body Double" (Proxy) pattern.
Proxy Contract: Holds the state (Storage). Address never changes.
Implementation Contract: Holds the logic (Code).
DelegateCall: The Proxy executes the Implementation's code within its own context (manipulating its own storage).
Upgrade: Simply point the Proxy to a new Implementation address.
Pitfall 1: Solidity OOP Rules (Trying to override non-virtual function)
Symptom: Compilation error regarding function overrides.
Root Cause: Like Java's @Override, parent functions must be marked virtual, and children must be marked override. Solidity is extremely strict about this.
Pitfall 2: The Amnesiac Blockchain (Contract not found)
Symptom: deploy.ts runs successfully, but the subsequent upgrade.ts fails, claiming the contract is missing.
Root Cause: npx hardhat run spins up a new, empty in-memory blockchain every time. Once the script finishes, the chain is destroyed.
Solution: Persistent Localhost Mode.
Terminal A: Run npx hardhat node (Like starting a long-running Tomcat server).
Terminal B: Deploy/Upgrade using --network localhost.
npx hardhat run scripts/deploy-proxy.ts --network localhost
# Note the address, update upgrade.ts
npx hardhat run scripts/upgrade.ts --network localhost
Pitfall 3: Configuration Loading (upgrades is undefined)
Symptom: Code references upgrades plugin, but it is undefined at runtime.
Solution: Explicitly import "@openzeppelin/hardhat-upgrades" at the top of hardhat.config.ts, ensuring it loads after toolbox.
Transitioning from "writing Java classes" to "understanding the EVM State Machine".
Environment Configuration is the biggest blocker: Web3 toolchains iterate rapidly. Version conflicts are the norm.
Security is the First Principle: All logic must assume it is under attack. Test code is more critical than business logic.
Understand the Low Level: Understanding the difference between "Ephemeral" and "Persistent" networks is key to solving state-related errors.
Current Stage: Day 4 - Interaction.
Java/Backend Analogy: Main Method / Shell Scripts.
Role Shift:
Days 1-3: You wrote Library or Service classes (Logic resting on disk).
Day 4: You are writing public static void main or deploy.sh. You need to execute this logic within a specific environment (Network).
Newcomers often confuse these. In backend Ops, they are distinct:
Scripts: Deployment/Initialization Flows.
Analogy: Your InstallWizard or DB_Init.sql.
Usage: Deploying contracts, setting initial params. Usually one-off.
Command: npx hardhat run scripts/deploy.js
Hardhat Tasks: CLI Ops Tools.
Analogy: Shell scripts for Ops, or Spring Boot Actuator endpoints.
Usage: Ad-hoc operations. e.g., "Check Balance", "Force Pause Contract".
Command: npx hardhat balance --account 0x123...
The core of Ethers.js interaction, and a major shift from Web2:
Provider: Read-Only Connection.
Analogy: A Database SELECT permission, or a read-only REST Client.
Function: Connect to nodes, read block data, read contract state (view/pure functions). No Private Key required.
Signer: Read-Write Connection.
Analogy: DB connection with INSERT/UPDATE privileges, holding the "Security Token" (Private Key).
Function: Send transactions, change chain state, pay Gas. Private Key required.
Trap: Trying to call a state-changing function (
transfer) using only a Provider will fail.
Analogy: Budget Assessment before running a cloud job.
Significance: An infinite loop in Java spikes CPU; in Solidity, it burns real money.
Action: Use contract.estimateGas.methodName() in scripts to prevent Out of Gas (OOG) failures.
Structure code around asynchronous orchestration:
// Typical Day 4 Automation Mindset
async function main() {
// 1. Identity (Signer)
const [admin] = await ethers.getSigners();
// 2. Class Loading (Factory)
const Factory = await ethers.getContractFactory("MyContract");
// 3. Instantiation (Deploy)
const contract = await Factory.deploy();
await contract.deployed(); // Wait for network confirmation (Rare in Web2)
// 4. Interaction
console.log("Current Value:", await contract.getValue()); // Read
const tx = await contract.setValue(100); // Write
await tx.wait(); // Wait for confirmation again
}
Prologue: Days 1-4 were building in a desert (Localhost). Day 5 is "Folding Space". We clone the bustling Ethereum Mainnet directly to our local machine. You now have "God Mode" over a parallel universe.
Instead of deploying to a slow Testnet to test "Will my contract work with Uniswap?", we use Forking.
Real Data: The local Uniswap pool actually contains billions in liquidity (mirrored from the specific block height).
Virtual Operations: You can dump the market in this fork without affecting reality.
Isolated State: Restart the node, and everything resets.
Config hierarchy is critical.
networks: {
hardhat: { // ๐ Must be under hardhat network
forking: {
url: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
blockNumber: 19200000, // ๐ก Pin the block number for caching performance!
enabled: true
}
}
}
In a Fork, you don't need private keys. You can become anyone (e.g., Vitalik or a Binance Hot Wallet).
Scenario: Need massive USDT to test logic.
Action: Impersonate a Whale.
// 1. Tell the network: I want to be this address
await network.provider.request({
method: "hardhat_impersonateAccount",
params: ["0xd8da6..."], // Vitalik's address
});
// 2. Get the Signer object
const whaleSigner = await ethers.getSigner("0xd8da6...");
We stop deploying and start connecting.
Prerequisites: Address (from Etherscan) + Interface (ABI).
Human-Readable ABI: No need for the full JSON.
const DAI_ABI = [
"function balanceOf(address owner) view returns (uint256)",
"function transfer(address to, uint amount) returns (bool)"
];
const daiContract = new ethers.Contract(DAI_ADDRESS, DAI_ABI, signer);
Error 1: bad address checksum
Cause: Ethereum addresses use mixed-case for checksum validation (EIP-55).
Solution: Lowercase the address. Ethers.js will skip the checksum validation.
Error 2: Provider or Contract? (Confusion)
ETH (The Native Asset): Managed by Provider -> provider.getBalance(address).
Tokens (The Smart Contracts): Managed by Contract -> contract.balanceOf(address).
Error 3: Whales with no Gas
Symptom: "Sender doesn't have enough funds to send tx".
Cause: The Whale has 100M USDT, but 0 ETH for Gas.
Solution: God Mode Printing. Use hardhat_setBalance to force-set the Whale's ETH balance.
Prologue: Moving from the vacuum of Localhost to the "Staging Environment" of the internet: Sepolia Testnet. Real latency, real Gas, real visibility.
Sepolia Deployment: Deploying to a public network where data is globally accessible.
Source Verification: Uploading Solidity source to Etherscan to match the Bytecode. This is the "Certificate of Trust" in Web3.
Security: .env Management Never hardcode private keys.
.env content (GitIgnore this!):
SEPOLIA_RPC_URL="https://eth-sepolia.g.alchemy.com/..."
PRIVATE_KEY="your_private_key_without_0x_prefix"
ETHERSCAN_API_KEY="your_etherscan_key"
Hardhat Config:
require("dotenv").config();
// ...
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY], // ๐ Reference the key
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
Obstacle: Standard faucets require Mainnet balance (Anti-Sybil). Solution: PoW Faucet (e.g., Sepolia PoW Faucet). Trade CPU time for Testnet ETH.
Step 1: Deploy
npx hardhat run scripts/deploy.js --network sepolia
Wait for block confirmation...
Step 2: Verify
npx hardhat verify --network sepolia <CONTRACT_ADDRESS> <CONSTRUCTOR_ARGS>
Result: A green checkmark on Etherscan.
Error: injecting env (0) / Private Key Undefined
Cause: Forgot to save .env file, or dotenv config is not at the top of hardhat.config.js.
Error: Address vs Private Key
Cause: Putting the Public Address (0x...) into the PRIVATE_KEY field.
Solution: Export the actual Private Key from MetaMask.
Warning: Sourcify Verification Skipped
Status: Ignore. As long as Etherscan verifies successfully, you are good.
Prologue: Day 6 was "Zero to One" (Deployment). Day 7 is "One to One Hundred". Writing code that works is merely passing; writing code that is Gas Efficient and Secure is the core competency of a Web3 Engineer.
What is Gas Optimization? In the EVM, every instruction costs the user money (Gas).
Storage: Permanently etching data onto the blockchain. Extremely Expensive.
Memory: Temporary scratchpad during computation. Cleared after transaction. Cheap.
Why Optimize?
User Responsibility: Bad code can cost users tens or hundreds of dollars for a simple transaction.
Preventing Failure: Unoptimized loops can hit the Block Gas Limit, causing transactions to fail permanently as data grows.
Professionalism: Gas optimization is the dividing line between a "Junior Developer" and a "Senior Engineer".
We built two contracts with identical functionality but different implementations: BadContract vs. GoodContract.
EVM storage slots are 256 bits (32 bytes).
Bad: Defining multiple uint256 variables for small values. Each takes a full slot.
Good: Sequential definition of smaller types (e.g., uint64). The EVM packs them into a single slot.
Bad: require(x > 0, "Error: The value must be greater than zero..."). You pay for every character stored.
Good: Use Custom Errors (error InvalidValue();) with revert InvalidValue();. Only stores a 4-byte selector. Extremely cheap.
This yields the most dramatic savings.
๐ด Bad Code (Storage Thrashing):
uint256 public total; // Storage variable
function sumArray(uint256[] memory nums) public {
for(uint256 i = 0; i < nums.length; i++) {
total += nums[i]; // Writes to Blockchain Disk (SSTORE) on EVERY iteration!
}
}
๐ข Good Code (Memory Caching):
function sumArray(uint256[] calldata nums) public {
uint256 _tempTotal = total; // 1. Read from Storage to Stack/Memory ONCE
for(uint256 i = 0; i < nums.length; i++) {
_tempTotal += nums[i]; // 2. Compute in Memory (Almost free)
}
total = _tempTotal; // 3. Write to Storage ONCE at the end
}
Running Hardhat tests on an array of length 10:
Operation | BadContract (Gas) | GoodContract (Gas) | Savings |
|---|---|---|---|
Update (Write) | ~43,813 | ~26,500 | 40% Saved |
SumArray (Loop) | ~60,000+ (Explodes with length) | ~31,506 (Constant) | 50%+ Saved |
๐ณ Pitfall: Stale Artifacts (The "Phantom Code" Anomaly)
Symptom: You change the Solidity logic (e.g., double the array size), but npx hardhat test reports the exact same Gas usage.
Cause: Hardhat aggressively caches compilation artifacts to speed up dev. If it thinks files haven't changed, it runs the old bytecode.
Solution: The "Nuclear" Clean.
This deletes cache and artifacts, forcing a full recompile.
๐ก Pro Tips:
Realistic Testing: Do not test Gas with value 0. The EVM handles zero values specially (Gas refunds), which skews estimates. Use realistic non-zero values.
Calldata vs. Memory: For read-only array arguments, always use calldata. It avoids the cost of copying data to memory.
Achievement: Built a pro-grade Web3 environment and ran "Hello World".
Tech Stack: Node.js, VS Code, Hardhat Init, Solidity Basics (Lock.sol), Localhost (npx hardhat node).
Transformation: Conquered the fear of the terminal; mastered compile and run commands.
Achievement: Learned to prove "My code works".
Tech Stack:
Testing: Mocha/Chai with expect assertions.
Debugging: On-chain console.log (rare in traditional dev).
Interaction: Scripting user behaviors (Deposit, Withdraw, Time-locks).
Transformation: Evolved from "Blind Coding" to TDD (Test Driven Development).
Achievement: Stepped out of the sandbox into the actual Blockchain network.
Tech Stack:
Mainnet Forking: "God Mode" โ Simulating rich accounts and interacting with live DeFi protocols locally.
Public Testnet (Sepolia): Live Fire Exercise. Node config, Faucets.
Wallet Security: Deep understanding of .env and .gitignore.
Verification: Open-sourcing code on Etherscan for the green checkmark .
Transformation: Gained the capability to deliver and publish real DApps.
Achievement: Dove into the EVM internals to save users money.
Tech Stack:
Gas Profiling: Visualizing costs with hardhat-gas-reporter.
Storage vs. Memory: optimizing the most expensive resource.
Custom Errors: Dropping expensive strings.
Troubleshooting: Solving the classic Stale Artifacts issue (npx hardhat clean).
Share Dialog
Share Dialog
No activity yet