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

From Java Architect to Web3 Novice: Hardhat Development Bootcamp
A transition guide designed for Java architects, mapping Backend concepts to Web3 reality using Hardhat.



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

From Java Architect to Web3 Novice: Hardhat Development Bootcamp
A transition guide designed for Java architects, mapping Backend concepts to Web3 reality using Hardhat.
Subscribe to over_akk
Subscribe to over_akk
<100 subscribers
<100 subscribers


We are going to build a smart contract compliant with the ERC-721 standard.
You can conceptualize this as an on-chain "Card Dispenser." Every card (NFT) possesses a unique Token ID and belongs to a specific wallet address.
Standardization: Adhering to the ERC-721 standard is mandatory for your NFT to be recognized and displayed on platforms like OpenSea and MetaMask.
Decentralized Storage: Storing image data directly on-chain is prohibitively expensive. Instead, we use a "pointer" (TokenURI) that resolves to IPFS.
Access Control: Utilizing Ownable ensures only the administrator (you) can modify rules, preventing unauthorized minting.

We will strictly avoid the default hardhat-toolbox because its bundled plugins often conflict with newer dependencies. We will manually assemble the environment.
Execute Commands:
# 1. Create and enter the directory
mkdir coffee-nft && cd coffee-nft
# 2. Initialize npm environment
npm init -y
# 3. Install Hardhat
npm install --save-dev hardhat
# 4. Initialize Hardhat (Select "Create a JavaScript project")
npx hardhat init
# 5. š„ Critical Step: Manual Dependency Installation (Solves npm error ERESOLVE)
npm install --save-dev @nomicfoundation/hardhat-ethers ethers chai @nomicfoundation/hardhat-verify hardhat-gas-reporter solidity-coverage @openzeppelin/contracts dotenv
Create a new file CoffeeCard.sol inside the contracts/ directory.

Logic Breakdown:
Inheritance: Inherits ERC721 (Base functionality) and Ownable (Access control).
Storage: Uses ERC721URIStorage (Enables unique metadata URIs for each token).
Security: Implements safeMint (Prevents minting to non-receiver compliant smart contracts, avoiding token loss).
Code Content:
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract CoffeeCard is ERC721, ERC721URIStorage, Ownable {
uint256 private _nextTokenId;
// Constructor: Set Token Name and Symbol
constructor() ERC721("CoffeeLifetimeCard", "CLC") Ownable(msg.sender) {}
// Mint Function: Core Logic
function safeMint(address to, string memory uri) public onlyOwner {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri); // Bind Metadata
}
// --- Required Overrides (Solidity Syntax Requirements) ---
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
We need to instantiate the code onto the blockchain.

Operations:
Create a .env file and populate it with your SEPOLIA_RPC_URL and PRIVATE_KEY.
Create scripts/deploy_coffee.js:
JavaScript
const hre = require("hardhat");
async function main() {
const contract = await hre.ethers.deployContract("CoffeeCard");
await contract.waitForDeployment();
console.log(`Contract deployed to: ${contract.target}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run Command:
Bash
npx hardhat run scripts/deploy_coffee.js --network sepolia
Record Address: Assume it is 0xC3F7...

A React-based web application integrating RainbowKit (UI Components) and Wagmi (React Hooks for Ethereum).
Lowering Barriers: Users cannot mint via command line; they interact with UI buttons.
Security Isolation: The web app handles display; the Wallet (e.g., MetaMask) handles signing. The web app never touches the Private Key.
Real-time Feedback: The frontend polls the chain state to inform the user (e.g., "Transaction Successful").

Note: Ensure you step out of the hardhat directory!
Execute Commands:
# 2. Create Frontend Project (Select React + JavaScript)
npm create vite@latest coffee-frontend -- --template react
# 3. ā ļø Enter Frontend Directory (Common rookie mistake)
cd coffee-frontend
# 4. Install the Web3 Stack
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query
This initializes the connection to the blockchain.
Pitfalls & Solutions:
Pitfall: wagmi updates frequently; older tutorials often reference incorrect mainnet import paths.
Solution: Import chain information strictly from wagmi/chains. Note that the new RainbowKit config does not require a chains parameter passed to the provider directly.
Code Content:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';
import '@rainbow-me/rainbowkit/styles.css'; // Import Wallet Styles
import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { WagmiProvider } from 'wagmi';
import { sepolia } from 'wagmi/chains'; // ā
Correct Import Path
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const config = getDefaultConfig({
appName: 'My NFT App',
projectId: 'YOUR_PROJECT_ID', // Use a public ID for testing
chains: [sepolia],
ssr: false
});
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<App />
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
</React.StrictMode>,
);
This contains the core business logic: Button Click -> Invoke Wallet -> Call Contract.
Core Technical Point:
Minimal ABI: Do not copy the entire multi-thousand-line JSON. Only define the interface for the safeMint function we actually use to reduce bundle size.
Code Content:
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useAccount, useWriteContract } from 'wagmi';
// Paste your deployed contract address here
const CONTRACT_ADDRESS = "0xC3F7Ab...";
// Minimal ABI
const ABI = [
{
"inputs": [
{ "internalType": "address", "name": "to", "type": "address" },
{ "internalType": "string", "name": "uri", "type": "string" }
],
"name": "safeMint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
function App() {
const { isConnected, address } = useAccount();
const { writeContract, isPending, isSuccess } = useWriteContract();
const handleMint = () => {
writeContract({
address: CONTRACT_ADDRESS,
abi: ABI,
functionName: 'safeMint',
args: [address, "ipfs://QmYourMetadata"], // Mint to current user
});
};
return (
<div style={{ padding: 50, textAlign: 'center' }}>
<h1>ā Coffee Membership Minting</h1>
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: 20 }}>
<ConnectButton />
</div>
{isConnected && (
<button onClick={handleMint} disabled={isPending}>
{isPending ? 'Minting...' : 'Mint Now'}
</button>
)}
{isSuccess && <p style={{color: 'green'}}>š Mint Successful!</p>}
</div>
);
}
export default App;

A server-side script (running on Node.js) that acts like a "Radar," monitoring the blockchain 24/7.

Passive Trigger: The blockchain is a "passive" database; it does not push notifications to you.
Closing the Business Loop: Without a listener, you don't know who bought your NFT. You need a listening script to trigger subsequent off-chain actions (e.g., Sending emails, updating a legacy SQL database, Discord Bot notifications).
Return to the Hardhat Project Directory and create scripts/monitor.js.
Code Content:
JavaScript
const hre = require("hardhat");
async function main() {
const contractAddress = "0xC3F7Ab..."; // Your contract address
// Get Contract Instance (Hardhat reads ABI automatically from artifacts)
const contract = await hre.ethers.getContractAt("CoffeeCard", contractAddress);
console.log(`š” Listening to contract: ${contractAddress}`);
// Listen for the 'Transfer' Event (Standard ERC721 Event)
// Params: from (sender), to (receiver), tokenId (ID)
contract.on("Transfer", (from, to, tokenId) => {
console.log(`\nšØ New Transaction Captured!`);
console.log(`Token ID: ${tokenId}`);
console.log(`Receiver: ${to}`);
console.log(`Timestamp: ${new Date().toLocaleString()}`);
});
}
main().catch(console.error);
Run Command:
Bash
npx hardhat run scripts/monitor.js --network sepolia
Refer to this table if you encounter errors. These are distilled from production experience.
Error Phenomenon | Root Cause Analysis | Solution |
|---|---|---|
| Dependency Hell (Version conflict). | Use |
| Typo/Case sensitivity. Linux file systems are case-sensitive. | Check if the file is named with |
| Test framework read the file, but found no test cases. | 1. Ensure file is saved ( |
| Wagmi update changed import paths. |


We are going to build a smart contract compliant with the ERC-721 standard.
You can conceptualize this as an on-chain "Card Dispenser." Every card (NFT) possesses a unique Token ID and belongs to a specific wallet address.
Standardization: Adhering to the ERC-721 standard is mandatory for your NFT to be recognized and displayed on platforms like OpenSea and MetaMask.
Decentralized Storage: Storing image data directly on-chain is prohibitively expensive. Instead, we use a "pointer" (TokenURI) that resolves to IPFS.
Access Control: Utilizing Ownable ensures only the administrator (you) can modify rules, preventing unauthorized minting.

We will strictly avoid the default hardhat-toolbox because its bundled plugins often conflict with newer dependencies. We will manually assemble the environment.
Execute Commands:
# 1. Create and enter the directory
mkdir coffee-nft && cd coffee-nft
# 2. Initialize npm environment
npm init -y
# 3. Install Hardhat
npm install --save-dev hardhat
# 4. Initialize Hardhat (Select "Create a JavaScript project")
npx hardhat init
# 5. š„ Critical Step: Manual Dependency Installation (Solves npm error ERESOLVE)
npm install --save-dev @nomicfoundation/hardhat-ethers ethers chai @nomicfoundation/hardhat-verify hardhat-gas-reporter solidity-coverage @openzeppelin/contracts dotenv
Create a new file CoffeeCard.sol inside the contracts/ directory.

Logic Breakdown:
Inheritance: Inherits ERC721 (Base functionality) and Ownable (Access control).
Storage: Uses ERC721URIStorage (Enables unique metadata URIs for each token).
Security: Implements safeMint (Prevents minting to non-receiver compliant smart contracts, avoiding token loss).
Code Content:
Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract CoffeeCard is ERC721, ERC721URIStorage, Ownable {
uint256 private _nextTokenId;
// Constructor: Set Token Name and Symbol
constructor() ERC721("CoffeeLifetimeCard", "CLC") Ownable(msg.sender) {}
// Mint Function: Core Logic
function safeMint(address to, string memory uri) public onlyOwner {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri); // Bind Metadata
}
// --- Required Overrides (Solidity Syntax Requirements) ---
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) {
return super.supportsInterface(interfaceId);
}
}
We need to instantiate the code onto the blockchain.

Operations:
Create a .env file and populate it with your SEPOLIA_RPC_URL and PRIVATE_KEY.
Create scripts/deploy_coffee.js:
JavaScript
const hre = require("hardhat");
async function main() {
const contract = await hre.ethers.deployContract("CoffeeCard");
await contract.waitForDeployment();
console.log(`Contract deployed to: ${contract.target}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run Command:
Bash
npx hardhat run scripts/deploy_coffee.js --network sepolia
Record Address: Assume it is 0xC3F7...

A React-based web application integrating RainbowKit (UI Components) and Wagmi (React Hooks for Ethereum).
Lowering Barriers: Users cannot mint via command line; they interact with UI buttons.
Security Isolation: The web app handles display; the Wallet (e.g., MetaMask) handles signing. The web app never touches the Private Key.
Real-time Feedback: The frontend polls the chain state to inform the user (e.g., "Transaction Successful").

Note: Ensure you step out of the hardhat directory!
Execute Commands:
# 2. Create Frontend Project (Select React + JavaScript)
npm create vite@latest coffee-frontend -- --template react
# 3. ā ļø Enter Frontend Directory (Common rookie mistake)
cd coffee-frontend
# 4. Install the Web3 Stack
npm install @rainbow-me/rainbowkit wagmi viem @tanstack/react-query
This initializes the connection to the blockchain.
Pitfalls & Solutions:
Pitfall: wagmi updates frequently; older tutorials often reference incorrect mainnet import paths.
Solution: Import chain information strictly from wagmi/chains. Note that the new RainbowKit config does not require a chains parameter passed to the provider directly.
Code Content:
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App.jsx';
import './index.css';
import '@rainbow-me/rainbowkit/styles.css'; // Import Wallet Styles
import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit';
import { WagmiProvider } from 'wagmi';
import { sepolia } from 'wagmi/chains'; // ā
Correct Import Path
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const config = getDefaultConfig({
appName: 'My NFT App',
projectId: 'YOUR_PROJECT_ID', // Use a public ID for testing
chains: [sepolia],
ssr: false
});
const queryClient = new QueryClient();
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider>
<App />
</RainbowKitProvider>
</QueryClientProvider>
</WagmiProvider>
</React.StrictMode>,
);
This contains the core business logic: Button Click -> Invoke Wallet -> Call Contract.
Core Technical Point:
Minimal ABI: Do not copy the entire multi-thousand-line JSON. Only define the interface for the safeMint function we actually use to reduce bundle size.
Code Content:
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useAccount, useWriteContract } from 'wagmi';
// Paste your deployed contract address here
const CONTRACT_ADDRESS = "0xC3F7Ab...";
// Minimal ABI
const ABI = [
{
"inputs": [
{ "internalType": "address", "name": "to", "type": "address" },
{ "internalType": "string", "name": "uri", "type": "string" }
],
"name": "safeMint",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
];
function App() {
const { isConnected, address } = useAccount();
const { writeContract, isPending, isSuccess } = useWriteContract();
const handleMint = () => {
writeContract({
address: CONTRACT_ADDRESS,
abi: ABI,
functionName: 'safeMint',
args: [address, "ipfs://QmYourMetadata"], // Mint to current user
});
};
return (
<div style={{ padding: 50, textAlign: 'center' }}>
<h1>ā Coffee Membership Minting</h1>
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: 20 }}>
<ConnectButton />
</div>
{isConnected && (
<button onClick={handleMint} disabled={isPending}>
{isPending ? 'Minting...' : 'Mint Now'}
</button>
)}
{isSuccess && <p style={{color: 'green'}}>š Mint Successful!</p>}
</div>
);
}
export default App;

A server-side script (running on Node.js) that acts like a "Radar," monitoring the blockchain 24/7.

Passive Trigger: The blockchain is a "passive" database; it does not push notifications to you.
Closing the Business Loop: Without a listener, you don't know who bought your NFT. You need a listening script to trigger subsequent off-chain actions (e.g., Sending emails, updating a legacy SQL database, Discord Bot notifications).
Return to the Hardhat Project Directory and create scripts/monitor.js.
Code Content:
JavaScript
const hre = require("hardhat");
async function main() {
const contractAddress = "0xC3F7Ab..."; // Your contract address
// Get Contract Instance (Hardhat reads ABI automatically from artifacts)
const contract = await hre.ethers.getContractAt("CoffeeCard", contractAddress);
console.log(`š” Listening to contract: ${contractAddress}`);
// Listen for the 'Transfer' Event (Standard ERC721 Event)
// Params: from (sender), to (receiver), tokenId (ID)
contract.on("Transfer", (from, to, tokenId) => {
console.log(`\nšØ New Transaction Captured!`);
console.log(`Token ID: ${tokenId}`);
console.log(`Receiver: ${to}`);
console.log(`Timestamp: ${new Date().toLocaleString()}`);
});
}
main().catch(console.error);
Run Command:
Bash
npx hardhat run scripts/monitor.js --network sepolia
Refer to this table if you encounter errors. These are distilled from production experience.
Error Phenomenon | Root Cause Analysis | Solution |
|---|---|---|
| Dependency Hell (Version conflict). | Use |
| Typo/Case sensitivity. Linux file systems are case-sensitive. | Check if the file is named with |
| Test framework read the file, but found no test cases. | 1. Ensure file is saved ( |
| Wagmi update changed import paths. |
import { sepolia } from 'wagmi/chains'
| Command run in the wrong directory. | You must |
Error persists after code fix | Vite has cached the old broken configuration. | Execute |
import { sepolia } from 'wagmi/chains'
| Command run in the wrong directory. | You must |
Error persists after code fix | Vite has cached the old broken configuration. | Execute |
Share Dialog
Share Dialog
No activity yet