Step 1: 환경 구성 + ERC20 컨트랙트 — 상세 가이드
README 개발 플랜 Step 1에 따른 순차 작업 가이드입니다.
목표: EVM 로컬 개발 환경을 만들고, 배포·호출 가능한 ERC20 컨트랙트(이름/심볼/민트 권한)를 작성한다.
산출물: Hardhat 또는 Foundry 프로젝트, ERC20 컨트랙트(이름·심볼·민트 권한), 로컬 컴파일·배포·호출 확인
추가 참고
- C. 로컬 블록체인에서 테스트하기: 로컬 노드 상시 실행, 테스트 ETH·ERC20 민팅·전송, Remix/Ganache UI 사용법.
- D. 브라우저 UI + 상태 저장/복원: 브라우저에서 계정·잔액 조회, Anvil
--state로 상태 저장/불러오기 설계.
폴더 구조 (도구별 분리)
각 도구는 서로 다른 폴더에서 진행합니다. 둘 다 사용할 경우 두 폴더를 모두 만들고, 하나만 사용할 경우 해당 폴더만 만들면 됩니다.
| 도구 | 작업 폴더 | 설명 |
|---|---|---|
| Hardhat | hardhat/ |
Node.js 기반, JavaScript/TypeScript 스크립트·테스트 |
| Foundry | foundry/ |
Forge/Anvil/Cast 기반, Solidity 네이티브 테스트·배포 |
프로젝트 루트 기준 예시:
faucet4Testing/
├── hardhat/ # Hardhat 프로젝트 (선택 시)
│ ├── contracts/
│ ├── scripts/
│ ├── test/
│ └── hardhat.config.js
├── foundry/ # Foundry 프로젝트 (선택 시)
│ ├── src/
│ ├── script/
│ ├── test/
│ └── lib/
├── docs/
└── README.md
아래에서 Hardhat 경로와 Foundry 경로 중 하나를 선택해 해당 폴더에서만 순차 진행하면 됩니다.
공통: 사전 요구사항
Hardhat 사용 시
- Node.js v22.10.0 이상 권장 (Hardhat 3.x 기준). v18~20 사용 시 경고가 나올 수 있음.
- npm v9 이상 또는 yarn / pnpm
node -v # 22.10.0 이상 권장 (Hardhat 3.x)
npm -v
Foundry 사용 시
- Rust 설치 후 Foundry 설치: https://book.getfoundry.sh/getting-started/installation
curl -L https://foundry.paradigm.xyz | bash
foundryup
forge --version
anvil --version
cast --version
A. Hardhat 버전 — hardhat/ 폴더에서 진행
모든 명령은 프로젝트 루트가 아닌 hardhat/ 폴더에서 실행합니다.
Ethers.js / Viem: 배포·호출 예시는 Ethers와 Viem 두 가지를 모두 안내합니다. Viem만 설치된 경우(Hardhat 3 + Node Test Runner + Viem)는 A.5.1 배포 스크립트와 A.6의 Viem 호출 확인 예시를 사용하세요.
A.1 hardhat/ 폴더 생성 및 초기화
프로젝트 루트(faucet4Testing/)에서:
mkdir -p hardhat
cd hardhat
npx hardhat init
- Create a JavaScript project (또는 TypeScript) 선택
- 패키지 매니저·Git 옵션은 프로젝트에 맞게 선택
sudo는 사용하지 않는 것이 좋습니다 (권한·경로 문제를 일으킬 수 있음).
생성되는 구조:
hardhat/
├── contracts/ # Solidity 컨트랙트
├── scripts/ # 배포·유틸 스크립트
├── test/ # 테스트
├── hardhat.config.js (또는 .ts)
└── package.json
이후 단계도 현재 디렉터리가 hardhat/인지 확인한 뒤 진행합니다.
A.1.1 초기화 시 자주 나오는 오류와 해결
다음과 비슷한 메시지가 나올 수 있습니다.
오류 예시 (터미널 출력)
Need to install the following packages:
hardhat@3.x.x
Ok to proceed? (y) y
WARNING: You are using Node.js 20.17.0 which is not supported by Hardhat.
Please upgrade to 22.10.0 or a later LTS version (even major version number)
Error HHE3: No Hardhat config file found.
You can initialize a new project by running Hardhat with --init
For more info go to https://hardhat.org/HHE3 or run Hardhat with --show-stack-traces
해결 방안
| 원인 | 해결 |
|---|---|
| Node.js 버전 | Hardhat 3.x는 Node 22.10.0 이상을 요구합니다. node -v로 확인 후, nodejs.org에서 LTS 설치 또는 nvm use 22(nvm 사용 시)로 버전 변경. |
| 초기화 옵션 | npx hardhat -init(하이픈 하나)이 아니라 npx hardhat --init(하이픈 두 개) 또는 **npx hardhat init**으로 실행. 사용 중인 Hardhat 버전 안내에 맞춰 선택. |
| 실행 위치 | 설정 파일이 없으면 "No Hardhat config file found"가 납니다. 반드시 hardhat/ 폴더 안에서 npx hardhat init(또는 --init)을 실행하세요. 루트나 다른 디렉터리에서 실행하지 않습니다. |
| sudo 사용 | sudo npx hardhat ...는 권한·경로가 꼬일 수 있으므로 사용하지 않습니다. 전역 설치는 npm install -g hardhat 등으로 하고, 프로젝트 내에서는 npx hardhat만 사용합니다. sudo 없이 쓰려면 아래 권한 조정 참고. |
sudo 없이 사용하려면 — 권한 조정
이전에 sudo npm / sudo npx를 썼다면, 전역 디렉터리나 프로젝트가 root 소유가 되어 권한 오류가 날 수 있습니다. 한 번만 아래 중 하나를 적용하면 됩니다.
방법 1: 소유자 변경 (권장)
npm 캐시·전역 prefix를 현재 사용자 소유로 바꿉니다.npm config get prefix로 prefix 확인 후(예:/usr/local), 해당 경로의lib/node_modules,bin등이 root 소유면:# npm 캐시 sudo chown -R "$(whoami)" ~/.npm # 전역 설치 경로 (prefix가 /usr/local 이라면) sudo chown -R "$(whoami)" /usr/local/lib/node_modules sudo chown -R "$(whoami)" /usr/local/bin이후에는
npm install -g ...,npx ...를 sudo 없이 사용할 수 있습니다.방법 2: chmod로 쓰기 허용
특정 디렉터리만 그룹/others 쓰기 허용할 때. (보안상 필요한 범위만 적용하는 것이 좋습니다.)# 현재 프로젝트의 node_modules만 현재 사용자가 쓸 수 있게 (이미 본인 소유면 불필요) chmod -R u+rwX hardhat/node_modules # 전역 node_modules를 모든 사용자 쓰기 가능하게 (비추천; 가능하면 방법 1 사용) # sudo chmod -R 775 /usr/local/lib/node_modules프로젝트는 보통
chown -R $(whoami) hardhat으로 폴더 소유자를 바꾸는 편이 더 안전합니다.방법 3: 사용자 전용 npm prefix 사용
전역 설치는 사용자 홈 아래로 두면 sudo가 필요 없습니다.mkdir -p ~/.npm-global npm config set prefix ~/.npm-global # 셸 설정(.zshrc 등)에 추가: export PATH="$HOME/.npm-global/bin:$PATH"
정리: hardhat/로 이동 → npx hardhat init 또는 npx hardhat --init 실행, Node 22.10.0 이상, sudo 생략.
A.1.2 Hardhat 버전 선택 (3 Beta vs 2)
npx hardhat --init 실행 후 다음처럼 버전을 묻는 화면이 나옵니다.
? Which version of Hardhat would you like to use? …
❯ Hardhat 3 Beta (recommended for new projects)
Hardhat 2 (older version)
각 옵션 특성
| 구분 | Hardhat 3 Beta | Hardhat 2 |
|---|---|---|
| 상태 | 신규 프로젝트용 권장, 2024~2025 기준 프로덕션 사용 가능 | 기존 안정 버전, 레거시 |
| 설정 | 선언형 설정(defineConfig), ESM 기본. config는 ES 모듈 필수 |
명령형 설정, CommonJS 가능 |
| 네트워크 | 명시적 네트워크 관리, 한 프로세스에서 여러 연결 동시 사용 가능 | 단일 hre.network 고정 |
| 테스트 | Solidity 테스트(Foundry 스타일) 지원, 플러그인으로 Mocha/Node 테스트 러너 선택 | Mocha + JS/TS 테스트 위주 |
| 빌드 | 빌드 프로파일, 타입된 artifacts 기본 생성, npm 연동 강화 | 기존 컴파일·아티팩트 방식 |
| Node | Node 22.10.0 이상 필요 | Node 18 등 이전 버전도 동작 |
| 플러그인 | 훅 기반 확장, config에 플러그인·태스크 명시적 등록 | extendConfig / extendEnvironment 등 기존 방식 |
차이 요약
- Hardhat 3: 완전 재작성. ESM·선언형 설정·다중 네트워크·Solidity 테스트 등 새 기능이 많고, 기존 Hardhat 2 프로젝트와 설정·API가 호환되지 않음. 신규 프로젝트에 적합.
- Hardhat 2: 예전 문서·블로그·플러그인 대부분이 이 버전 기준. 기존 프로젝트 유지·마이그레이션 없이 빠르게 시작할 때 선택.
권장
- 새로 시작하는 Faucet4Testing처럼 신규 프로젝트 → Hardhat 3 Beta 선택을 권장합니다. (프롬프트에서 “Hardhat 3 Beta (recommended for new projects)”)
- 이미 Hardhat 2로 된 레포를 유지해야 하거나, 특정 플러그인(hardhat-deploy, gas 리포터 등)이 아직 Hardhat 3를 지원하지 않으면 Hardhat 2를 선택해도 됩니다.
선택 후 생성되는 config·스크립트 형식이 버전마다 다르므로, 본 가이드의 이후 단계(배포 스크립트 등)는 선택한 버전에 맞춰 Hardhat 공식 문서를 참고해 조정하면 됩니다.
A.1.3 프로젝트 타입 선택 (Hardhat 3)
Hardhat 3 Beta 선택 후, 프로젝트 타입을 묻는 화면이 나옵니다.
? What type of project would you like to initialize? …
❯ A TypeScript Hardhat project using Node Test Runner and Viem
A TypeScript Hardhat project using Mocha and Ethers.js
A minimal Hardhat project
각 옵션 특성
| 구분 | Node Test Runner + Viem | Mocha + Ethers.js | Minimal |
|---|---|---|---|
| 테스트 러너 | Node.js 내장 테스트 러너 | Mocha | 없음(직접 추가) |
| 블록체인 라이브러리 | Viem | Ethers.js | 없음(직접 추가) |
| 언어 | TypeScript | TypeScript | 최소(TS/JS 설정 본인 선택) |
| 의존성 | 적음, Node 내장 활용 | Mocha·Ethers 추가 | 최소 |
| 테스트 속도 | 보통 더 빠름 | 플러그인·설정에 따라 다름 | — |
| 문서·예제 | 늘어나는 추세 | 매우 많음(기존 Hardhat 예제 대부분) | — |
옵션별 설명
Node Test Runner + Viem
- Node Test Runner: Node.js 18+ 내장 테스트 러너.
describe/it/test지원, 별도 Mocha 설치 없이 테스트 실행 가능. - Viem: TypeScript 네이티브, 가벼운 Ethereum 클라이언트. 최신 Hardhat/Next.js 생태계에서 많이 사용.
- Hardhat 3에서 첫 번째(권장) 옵션으로 나오는 구성입니다.
- Node Test Runner: Node.js 18+ 내장 테스트 러너.
Mocha + Ethers.js
- Mocha: 오래 쓰인 테스트 프레임워크. 블록체인 튜토리얼·블로그 예제가 대부분 이 조합.
- Ethers.js: 스마트 컨트랙트 연동에 널리 쓰이는 라이브러리. README에도 “ethers v6 또는 viem”으로 명시되어 있음.
- 기존 자료를 참고하며 진행하기 좋습니다.
A minimal Hardhat project
- 테스트 러너·Ethers/Viem 등 아무것도 포함하지 않은 최소 템플릿.
- 본인이 테스트 도구·라이브러리를 골라서 넣고 싶을 때 선택합니다.
권장
- Faucet4Testing처럼 새로 시작하는 프로젝트에서는 “A TypeScript Hardhat project using Node Test Runner and Viem” 선택을 권장합니다. Hardhat 3 기본 권장 구성이고, 이후 Step 4 백엔드에서 viem을 쓸 때 설정을 맞추기 쉽습니다.
- 기존 Ethers.js 예제·문서를 많이 참고할 예정이면 **“A TypeScript Hardhat project using Mocha and Ethers.js”**를 선택해도 됩니다. README의 “ethers v6 또는 viem” 중 Ethers 쪽에 맞춥니다.
- Minimal은 테스트·라이브러리를 직접 구성하고 싶을 때만 선택하면 됩니다.
선택한 타입에 따라 생성되는 hardhat.config, test/, scripts/ 구조와 사용하는 API(viem vs ethers)가 달라지므로, 본 가이드의 배포 스크립트·테스트 예시는 선택한 조합에 맞게 import·문법만 바꿔서 사용하면 됩니다.
A.2 OpenZeppelin Contracts 설치
hardhat/ 디렉터리에서:
npm install @openzeppelin/contracts
(또는 yarn add @openzeppelin/contracts / pnpm add @openzeppelin/contracts)
A.3 ERC20 컨트랙트 작성
파일: hardhat/contracts/FaucetToken.sol
요구사항: 이름(name), 심볼(symbol), 민트 권한(owner만 mint 가능)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract FaucetToken is ERC20, Ownable {
constructor(
string memory name_,
string memory symbol_,
address initialOwner_
) ERC20(name_, symbol_) Ownable(initialOwner_) {}
/// @notice 민트 권한: owner만 호출 가능
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
}
name_,symbol_: 예) "Faucet Test Token", "FAUCET"initialOwner_: 민트 권한을 가질 주소 (배포자 또는 Faucet 서버 주소)
A.4 컴파일
hardhat/에서:
npx hardhat compile
artifacts/, cache/가 생성되면 성공입니다.
A.4.1 artifacts/와 cache/ 폴더
컴파일 후 생성되는 두 폴더의 역할과 확인할 사항입니다.
artifacts/ — 컴파일 결과물
- Solidity 컴파일 최종 결과가 저장되는 폴더. 스크립트·테스트·프론트엔드에서 컨트랙트 연동 시 사용합니다.
- 포함 내용: 컨트랙트별
ContractName.json(ABI, 바이트코드, 메타데이터),artifacts.d.ts(TypeScript 타입),build-info/*.json. - 체크: 수정한 컨트랙트에 대해
contracts/.../ContractName.json존재 여부, JSON 안에abi포함 여부,.gitignore에artifacts/등록 여부.
cache/ — 컴파일 캐시
- 증분 컴파일용 캐시. 변경 없는 컨트랙트는 재컴파일하지 않고 이전 결과를 재사용해 빌드 시간을 줄입니다.
- 포함 내용:
compile-cache.json(소스 → 아티팩트 경로 매핑),build-info/, 테스트용 빌드는test-artifacts/등. - 체크: 소스 수정 후
compile시 캐시 갱신 여부. 컴파일이 꼬이면npx hardhat clean후 다시npx hardhat compile..gitignore에cache/등록.
| 폴더 | 기능 | 확인 포인트 |
|---|---|---|
| artifacts/ | 컴파일 결과물(ABI, 바이트코드). 배포·연동 시 사용 | 컨트랙트별 JSON·ABI 존재, .gitignore 등록 |
| cache/ | 증분 컴파일 캐시 | 문제 시 hardhat clean 후 재컴파일, .gitignore 등록 |
둘 다 생성되는 것이 정상이며, 커밋하지 않아도 됩니다.
A.5 로컬 노드 실행 및 배포
터미널 1 — hardhat/에서 로컬 노드 기동:
cd hardhat
npx hardhat node
터미널 2 — 배포 스크립트 작성 후 실행.
Ethers.js가 설치된 경우 — 아래 CommonJS 예시 사용. Viem만 설치된 경우(Hardhat 3 + Node Test Runner + Viem)는 A.5.1 Viem 배포 스크립트를 사용하세요.
파일: hardhat/scripts/deploy.js (Ethers 사용 시)
const hre = require("hardhat");
async function main() {
const [deployer] = await hre.ethers.getSigners();
const name = "Faucet Test Token";
const symbol = "FAUCET";
const FaucetToken = await hre.ethers.getContractFactory("FaucetToken");
const token = await FaucetToken.deploy(name, symbol, deployer.address);
await token.waitForDeployment();
const address = await token.getAddress();
console.log("FaucetToken deployed to:", address);
console.log("Owner (minter):", deployer.address);
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
A.5.1 Viem만 설치된 경우 — 배포 스크립트
Hardhat 3 + @nomicfoundation/hardhat-toolbox-viem (Ethers 없이 Viem만) 사용 시 아래처럼 작성합니다. ESM·TypeScript 기준입니다.
파일: hardhat/scripts/deploy.ts
import { network } from "hardhat";
async function main() {
const { viem, networkName } = await network.connect();
const [walletClient] = await viem.getWalletClients();
const deployerAddress = walletClient!.account.address;
const name = "Faucet Test Token";
const symbol = "FAUCET";
console.log(`Deploying FaucetToken to ${networkName}...`);
const token = await viem.deployContract("FaucetToken", [
name,
symbol,
deployerAddress,
]);
console.log("FaucetToken deployed to:", token.address);
console.log("Owner (minter):", deployerAddress);
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
배포 실행 (터미널 2, hardhat/에서):
- Viem:
npx hardhat run scripts/deploy.ts --network localhost - Ethers:
npx hardhat run scripts/deploy.js --network localhost
A.6 호출 확인
같은 로컬 노드에서 balanceOf, mint 동작을 확인합니다.
A.6.0 Hardhat console이란 — 목적과 할 수 있는 작업
npx hardhat console --network localhost로 들어가는 Hardhat console은 다음을 위한 대화형 REPL입니다.
| 구분 | 설명 |
|---|---|
| 목적 | 로컬 노드(localhost)에 연결된 상태에서, 스크립트 파일 없이 한 줄씩 컨트랙트 배포·호출·조회를 시험해 보는 도구입니다. |
| 목표 | Step 1에서는 배포된 FaucetToken에 대해 mint, balanceOf를 호출해, 토큰 지급·잔액 조회가 정상 동작하는지 빠르게 확인하는 것입니다. |
할 수 있는 작업 예시
- 연결된 네트워크에서 지갑(계정) 목록 가져오기
- 컨트랙트 배포 또는 이미 배포된 컨트랙트 인스턴스 가져오기
- view 호출 (예:
balanceOf) — 가스 없이 조회 - 트랜잭션 보내기 (예:
mint) — 서명 후 전송
주의: ss, help, mint처럼 변수명만 입력하면 “Uncaught ReferenceError: xxx is not defined”가 납니다. console에서는 사용 가능한 전역 객체(예: Ethers 사용 시 ethers, hre)를 통해 호출해야 합니다.
Viem만 설치된 프로젝트에서는 console에 ethers가 없을 수 있어, 아래 Viem만 설치된 경우처럼 스크립트(verify-deploy.ts)로 확인하는 것을 권장합니다.
Ethers.js가 설치된 경우 — Hardhat 콘솔 사용.
hardhat/에서:
npx hardhat console --network localhost
콘솔 내 (주소는 배포 시 출력된 값으로 교체):
const FaucetToken = await ethers.getContractFactory("FaucetToken");
const token = await FaucetToken.attach("DEPLOYED_CONTRACT_ADDRESS");
const [owner] = await ethers.getSigners();
await token.mint(owner.address, ethers.parseEther("1000"));
const balance = await token.balanceOf(owner.address);
console.log("Balance:", ethers.formatEther(balance)); // 1000.0
Viem만 설치된 경우 — 콘솔 대신 스크립트로 확인합니다. 배포 시 출력한 컨트랙트 주소를 DEPLOYED_CONTRACT_ADDRESS 자리에 넣고 실행하세요.
파일: hardhat/scripts/verify-deploy.ts
import { network } from "hardhat";
async function main() {
const { viem } = await network.connect();
const [walletClient] = await viem.getWalletClients();
const ownerAddress = walletClient!.account.address;
const tokenAddress = "DEPLOYED_CONTRACT_ADDRESS" as `0x${string}`;
const token = await viem.getContractAt("FaucetToken", tokenAddress);
await token.write.mint([ownerAddress, 1000n * 10n ** 18n]);
const balance = await token.read.balanceOf([ownerAddress]);
console.log("Balance:", (Number(balance) / 1e18).toString());
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
실행: npx hardhat run scripts/verify-deploy.ts --network localhost
owner가 아닌 주소로 mint 호출 시 revert되는지도 확인하면 좋습니다.
B. Foundry 버전 — foundry/ 폴더에서 진행
모든 명령은 foundry/ 폴더에서 실행합니다.
B.1 foundry/ 폴더 생성 및 초기화
프로젝트 루트에서:
mkdir -p foundry
cd foundry
forge init
--no-git옵션을 쓰면 상위가 이미 Git 저장소일 때 서브폴더만 초기화됩니다:forge init --no-git
생성되는 구조:
foundry/
├── src/ # Solidity 컨트랙트
├── script/ # 배포 스크립트
├── test/ # Solidity 테스트
├── lib/ # 의존성 (OpenZeppelin 등)
├── foundry.toml
└── .gitignore
B.2 OpenZeppelin Contracts 설치
foundry/에서:
forge install OpenZeppelin/openzeppelin-contracts --no-commit
lib/openzeppelin-contracts/가 생깁니다.
remappings 설정 — foundry.toml에 다음이 있는지 확인합니다. 없으면 추가합니다.
remappings = [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/"
]
(또는 remappings.txt 파일에 한 줄: @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/)
B.3 ERC20 컨트랙트 작성
파일: foundry/src/FaucetToken.sol
Hardhat과 동일한 로직, import만 Foundry remapping에 맞게 사용합니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract FaucetToken is ERC20, Ownable {
constructor(
string memory name_,
string memory symbol_,
address initialOwner_
) ERC20(name_, symbol_) Ownable(initialOwner_) {}
/// @notice 민트 권한: owner만 호출 가능
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
}
B.4 컴파일
foundry/에서:
forge build
out/ 디렉터리가 생성되면 성공입니다.
B.5 로컬 노드(Anvil) 실행 및 배포
터미널 1 — foundry/에서 Anvil 기동:
cd foundry
anvil
- 기본 RPC:
http://127.0.0.1:8545 - 콘솔에 출력되는 테스트 개인키 중 하나를 복사해 둡니다 (예:
0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80).
배포 스크립트 작성
파일: foundry/script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import "../src/FaucetToken.sol";
contract DeployScript is Script {
function run() external {
// Anvil 기본 첫 번째 계정 키 (로컬 전용)
uint256 deployerPrivateKey = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80;
address deployer = vm.addr(deployerPrivateKey);
vm.startBroadcast(deployerPrivateKey);
FaucetToken token = new FaucetToken(
"Faucet Test Token",
"FAUCET",
deployer
);
vm.stopBroadcast();
console.log("FaucetToken deployed to:", address(token));
console.log("Owner (minter):", deployer);
}
}
forge-std가 없다면 먼저 설치합니다 (foundry/에서):
forge install foundry-rs/forge-std --no-commit
터미널 2 — 배포 실행 (foundry/에서):
forge script script/Deploy.s.sol:DeployScript \
--rpc-url http://127.0.0.1:8545 \
--broadcast
- 스크립트에서
vm.startBroadcast(deployerPrivateKey)에 테스트 키를 넣었으므로--private-key는 생략 가능합니다. - 배포 후 콘솔에 찍힌 컨트랙트 주소를 복사해 둡니다.
B.6 호출 확인 (cast)
배포된 컨트랙트 주소를 CONTRACT_ADDRESS로 넣어 실행합니다.
mint 호출 (Anvil 첫 번째 계정이 owner이므로):
cast send CONTRACT_ADDRESS "mint(address,uint256)" YOUR_ADDRESS 1000000000000000000000 \
--rpc-url http://127.0.0.1:8545 \
--private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
balanceOf 조회:
cast call CONTRACT_ADDRESS "balanceOf(address)(uint256)" YOUR_ADDRESS --rpc-url http://127.0.0.1:8545
YOUR_ADDRESS는 Anvil 첫 계정 주소로 하려면:
cast wallet address --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
이 주소로 mint한 뒤 balanceOf가 1000e18으로 나오면 정상 동작입니다.
C. 로컬 블록체인에서 테스트하기
로컬에서 계속 켜 둔 블록체인을 쓰고, 테스트 ETH와 ERC20 민팅·전송을 하며, 필요하면 GUI로도 확인하는 방법을 정리합니다.
C.1 하고 싶은 것 정리
| 목표 | 내용 |
|---|---|
| 1. 로컬 블록체인 프로세스 상시 실행 | Ganache처럼 한 번 띄워 두고 개발·테스트에 사용 |
| 2. 테스트 ETH + ERC20 민팅·전송 | 로컬 환경에서 토큰 발급·전송 시나리오 구현 |
| 3. 터미널 + GUI | 터미널 스크립트뿐 아니라 화면에서 잔액·트랜잭션 확인 |
아래는 Hardhat 노드를 기본으로 하고, GUI는 Remix 또는 Ganache UI로 보완하는 구성입니다.
C.2 로컬 블록체인 프로세스 — "Ganache처럼" 계속 실행
C.2.1 Hardhat Node (권장 — 이미 사용 중인 경우)
프로젝트에서 쓰는 로컬 노드입니다. 한 터미널에서 계속 켜 두면 Ganache처럼 동작합니다.
cd hardhat
npx hardhat node
- RPC URL:
http://127.0.0.1:8545 - 계정: 20개 생성, 각 계정에 10,000 ETH 지급 (테스트용)
- 특징: 터미널 전용. 재시작하면 체인 상태 초기화(스냅샷 저장 없음).
노드를 백그라운드로 두고 싶다면:
npx hardhat node &
또는 tmux / screen 안에서 실행해 두면 됩니다.
참고: 이미 8545 포트를 쓰는 프로세스가 있으면 EADDRINUSE가 납니다. 그때는 lsof -i :8545로 PID를 확인한 뒤 kill <PID>로 종료하거나, 이미 떠 있는 노드를 그대로 사용하면 됩니다.
C.2.2 Anvil (Foundry)
Foundry를 쓰는 경우:
cd foundry
anvil
- RPC:
http://127.0.0.1:8545 - 계정·개인키가 터미널에 출력되며, ETH가 지급된 상태로 시작합니다.
C.2.3 Ganache UI (GUI로 로컬 체인 관리)
데스크톱 앱으로 로컬 체인을 띄우고, 잔액·트랜잭션·블록을 화면에서 볼 수 있습니다.
- 다운로드: Ganache 공식 에서 Ganache (GUI) 설치
- 사용: 앱 실행 → "Quickstart" 또는 "New Workspace" → 로컬 체인 시작
- RPC: 보통
http://127.0.0.1:7545(설정에서 확인) - 계정: 기본 10개, ETH 지급. 개인키·주소를 GUI에서 복사 가능
- 상태 저장: Workspace로 저장하면 나중에 다시 열어 이어서 사용 가능
Hardhat에서 이 체인을 쓰려면 hardhat.config에 네트워크를 추가합니다 (아래 C.6 참고).
C.3 테스트 ETH + ERC20 민팅·전송 (로컬 환경)
C.3.1 테스트 ETH
- Hardhat node / Anvil: 실행 시 생성되는 계정에 이미 테스트 ETH가 들어 있음. 별도 faucet 없이 사용.
- Ganache: 앱이 지급한 ETH를 그대로 사용.
즉, "로컬 블록체인 프로세스"를 띄우기만 하면 테스트 ETH는 준비된 상태입니다.
C.3.2 ERC20 민팅·전송
이미 있는 FaucetToken 컨트랙트를 로컬 노드에 배포한 뒤, owner가 mint → transfer 하면 됩니다.
터미널에서 (스크립트)
- 배포:
npx hardhat run scripts/deploy.ts --network localhost
→ FaucetToken 배포, owner는 배포한 계정. - mint:
verify-deploy.ts처럼token.write.mint([주소, amount])호출. - transfer: 컨트랙트의
transfer(to, amount)를 스크립트나 다음 GUI에서 호출.
GUI에서 (Remix)
- Remix에서 로컬 노드에 연결(아래 C.4.1) → "Deploy"로 FaucetToken 배포
→ "At Address"로 이미 배포된 주소 붙여넣기 →mint,transfer,balanceOf버튼으로 호출.
정리하면:
- 로컬 노드 한 번 실행 (Hardhat node / Anvil / Ganache)
- FaucetToken 배포 (스크립트 또는 Remix)
- owner로
mint(받을주소, 금액)호출 transfer(받을주소, 금액)또는 Remix에서 버튼으로 전송
C.4 GUI 환경에서 쓰기
C.4.1 Remix IDE — 로컬 노드 연결
Remix는 브라우저 기반 IDE라서, 로컬 노드를 "외부 RPC"로 붙이면 배포·호출을 전부 GUI로 할 수 있습니다.
- Remix 열기: https://remix.ethereum.org
- 로컬 노드 실행: 터미널에서
npx hardhat node(또는 Anvil / Ganache) 실행해 두기. - Remix에서 연결:
- 왼쪽 "Deploy and run transactions" 플러그인 선택
- Environment에서 "External Http Provider" 또는 "Hardhat Provider" 선택
- URL에
http://127.0.0.1:8545입력 (Ganache면http://127.0.0.1:7545등) - "Connect" 또는 "OK"
- 계정: Hardhat/Anvil/Ganache에서 쓰는 계정들이 드롭다운에 나타납니다. 선택해서 사용.
- 컨트랙트:
- "Compile"에서 FaucetToken 컴파일 (또는 Hardhat에서 컴파일한 ABI/Bytecode 복사)
- "Deploy"에서 생성자 인자(name, symbol, owner) 넣고 배포
- 배포된 컨트랙트 주소로 "At Address"에 넣으면 mint, transfer, balanceOf 등을 버튼으로 호출 가능.
이렇게 하면 로컬 블록체인 + 테스트 ETH + ERC20 민팅·전송을 GUI(Remix) 로 구현할 수 있습니다.
C.4.2 Ganache UI
- 로컬 체인 자체를 GUI로 실행·관리하고 싶을 때 사용.
- 계정 목록, 잔액, 블록, 트랜잭션을 데스크톱 앱에서 확인.
- 컨트랙트 배포·호출은 Remix에서 "External Http Provider"로 Ganache RPC(예: 7545)를 넣어서 하면 됩니다.
C.4.3 MetaMask + 로컬 노드 (선택)
- MetaMask에서 Custom RPC 추가:
네트워크 추가 → RPC URLhttp://127.0.0.1:8545(또는 Ganache 7545)
체인 ID는 Hardhat 31337, Ganache는 설정값 확인. - Hardhat node 출력된 개인키로 계정 import → Remix나 dApp에서 그 계정으로 트랜잭션 서명 가능.
C.5 한 번에 정리 — 추천 흐름
터미널만 쓰는 경우
npx hardhat node로 로컬 노드 계속 실행npx hardhat run scripts/deploy.ts --network localhost로 FaucetToken 배포verify-deploy.ts등으로 mint / balanceOf 확인- 필요 시 스크립트에서
transfer호출
GUI까지 쓰는 경우
- 로컬 노드 실행 (
npx hardhat node또는 Ganache UI) - Remix 열기 → External Http Provider
http://127.0.0.1:8545연결 - Remix에서 FaucetToken 배포 후 "At Address"로 붙여 넣기
- mint, transfer, balanceOf를 Remix 버튼으로 실행하며 확인
Ganache UI를 쓰는 경우
- Ganache 앱에서 Workspace 생성 후 로컬 체인 시작
- Hardhat config에
ganache네트워크 추가 (host, port, network_id) npx hardhat run scripts/deploy.ts --network ganache로 배포- Remix에서 Ganache RPC(예: 7545)로 연결해 같은 식으로 배포·호출
C.6 Hardhat config 예시 (Ganache 사용 시)
Ganache UI를 쓰면서 Hardhat 스크립트로 배포하려면 hardhat.config.ts에 예를 들어 다음을 추가합니다.
// 예: Ganache 기본 포트 7545
networks: {
localhost: { url: "http://127.0.0.1:8545" },
ganache: {
url: "http://127.0.0.1:7545",
chainId: 1337, // Ganache 기본값, 필요 시 맞춤
},
},
그다음:
npx hardhat run scripts/deploy.ts --network ganache
C.7 요약 (로컬 블록체인 테스트)
| 하고 싶은 것 | 방법 |
|---|---|
| 1. 로컬 블록체인 상시 실행 | npx hardhat node 한 터미널에 계속 실행. 또는 Ganache UI로 실행. |
| 2. 테스트 ETH + ERC20 민팅·전송 | 노드 실행 시 계정에 ETH 있음. FaucetToken 배포 후 mint/transfer는 스크립트 또는 Remix에서 호출. |
| 3. 터미널 + GUI | 터미널: Hardhat 스크립트. GUI: Remix(External Http Provider로 localhost 연결) 또는 Ganache UI로 체인·계정 확인. |
D. 브라우저 UI + 상태 저장/복원
목표: 브라우저에서 로컬 체인을 보고, 실행한 내용(상태)을 저장했다가 다시 실행할 때 그대로 불러와 쓰기.
D.1 하고 싶은 것 정리
| 항목 | 내용 |
|---|---|
| 브라우저에서 보기 | 계정 목록, 잔액, 블록, 트랜잭션 등을 웹 화면에서 확인 |
| 상태 저장 | 노드를 종료할 때 현재 체인 상태(계정·잔액·컨트랙트·스토리지 등)를 파일로 저장 |
| 상태 불러오기 | 다시 실행할 때 저장한 파일을 읽어서, 그 시점 그대로 이어서 사용 |
D.2 "상태 저장/불러오기" — Hardhat vs Anvil
브라우저 UI와 백엔드는 RPC(JSON-RPC) 로만 노드에 붙습니다.
그래서 Hardhat node를 쓰든 Anvil(Foundry) 을 쓰든, 로컬에서 실행 중인 노드의 주소(예: http://127.0.0.1:8545)만 맞추면 같은 브라우저·백엔드로 연결해서 볼 수 있습니다.
차이는 "그 노드가 상태를 파일로 저장/복원해 주는지" 여부뿐입니다.
Hardhat node
- 기본 동작: 종료하면 메모리만 사용하므로 상태가 전부 사라짐.
- 공식적으로 "종료 시 파일로 덤프 → 다음에 그 파일로 복원" 기능은 없음.
- 테스트용 스냅샷(
takeSnapshot/restore)은 같은 프로세스 안에서만 동작.
Anvil (Foundry)
--state <경로>옵션 하나로 "불러오기 + 저장" 모두 가능.- 실행 시: 해당 경로에 파일이 있으면 그 상태로 시작.
- 종료 시: 현재 상태를 그 경로에 저장.
- 예:
anvil --state ./data/state.json- 첫 실행: 빈 체인으로 시작 → 종료 시
./data/state.json생성. - 이후 실행:
./data/state.json이 있으면 그 상태로 시작.
- 첫 실행: 빈 체인으로 시작 → 종료 시
정리
- "저장했다가 다시 실행할 때 그대로 불러와 쓰기"를 하려면 Anvil +
--state를 쓰는 구성이 적합합니다. - Hardhat은 컴파일·스크립트(배포 등) 에만 쓰고, "실제로 돌리는 노드 + 상태 유지"는 Anvil을 쓰는 방식으로 가면 됩니다.
D.3 전체 구성 제안
[ 브라우저 ] ←→ [ 백엔드(선택) ] ←→ [ Anvil (--state) ]
│ │ │
│ HTTP/WS │ RPC 8545 │ state.json
│ │ 또는 프록시 │ 저장/로드
└────────────────────┴──────────────────────┘
- Anvil:
anvil --state ./data/state.json으로 실행.
RPC:http://127.0.0.1:8545(기본).
종료 시./data/state.json에 상태 저장, 다음 실행 시 자동 로드. - 브라우저 UI:
http://localhost:8545로 직접 붙이거나,
CORS/보안을 위해 백엔드에서 RPC를 프록시해도 됨. - 백엔드(선택):
Anvil 프로세스 시작/종료(--state경로 지정),
"지금 상태 저장" 버튼 → Anvil 종료(자동 덤프) 후 같은 옵션으로 재시작 등 자동화 가능.
D.4 브라우저 UI에서 할 일 (최소)
- 연결: RPC URL 입력 또는 기본
http://127.0.0.1:8545사용. - 계정:
eth_accounts+eth_getBalance로 목록·잔액 표시. - 블록:
eth_blockNumber+eth_getBlockByNumber로 최근 N개 표시. - 트랜잭션: 블록에서
transactions보거나eth_getTransactionByHash로 상세 표시. - (선택) "상태 저장" 버튼: 백엔드가 있으면 "Anvil 종료(덤프) 후 재시작" 트리거.
백엔드 없이 쓰면: "Anvil을--state옵션으로 띄우고, 종료할 때 Ctrl+C로 끄면 자동 저장" 안내만 해도 됨.
D.5 상태 저장/불러오기 사용 방법 (Anvil)
터미널에서 직접
# 프로젝트 루트 또는 원하는 디렉터리
mkdir -p data
anvil --state ./data/state.json
- 첫 실행: 빈 체인 + 기본 계정들. 작업 후 Ctrl+C로 종료 →
./data/state.json생성. - 다음 실행: 같은 명령 다시 실행 →
./data/state.json이 있으면 저장된 상태 그대로 로드.
포트를 바꾸고 싶을 때
anvil --state ./data/state.json --port 8546
브라우저 UI에서 RPC URL을 http://127.0.0.1:8546 으로 맞추면 됩니다.
D.6 Hardhat이 필요한 경우
- 컨트랙트 컴파일: 지금처럼
hardhat/에서npx hardhat compile그대로 사용. - 배포 스크립트: Hardhat에서 네트워크만 Anvil로 연결.
예:hardhat.config에anvil: { url: "http://127.0.0.1:8545" }추가 후npx hardhat run scripts/deploy.ts --network anvil
→ Anvil 위에 FaucetToken 등 배포. 이 상태도 Anvil 종료 시--state파일에 함께 저장됨.
즉, "만들고 배포하는 건 Hardhat, 돌리고 저장/불러오기는 Anvil" 로 나누면 됩니다.
D.7 구현 단계 제안
| 단계 | 내용 |
|---|---|
| 1 | Anvil 상태 저장 확인: anvil --state ./data/state.json 로 띄우고, FaucetToken 배포 후 종료 → 재실행해서 잔액·컨트랙트가 그대로인지 확인. |
| 2 | 브라우저 UI (최소): HTML+JS 또는 React/Vue 등으로 http://127.0.0.1:8545 에 연결해 계정·잔액·블록 목록만 표시. (ethers/viem으로 RPC 호출.) |
| 3 | (선택) 백엔드: Node/Express 등에서 Anvil 자식 프로세스로 anvil --state ... 실행/종료 API, 필요 시 RPC 프록시. |
| 4 | (선택) UI에서 "노드 시작/종료": 백엔드가 있으면 버튼으로 Anvil 시작·종료(저장) 트리거. |
D.8 요약 (브라우저 UI + 상태 저장)
- 브라우저에서 보기: RPC(
http://127.0.0.1:8545)로 연결하는 웹 페이지 하나면 충분. 계정·잔액·블록·트랜잭션 조회만 해도 "가나슈처럼 보는" 수준 구현 가능. - 실행 내용 저장 후 다시 불러오기: Anvil 의
--state <파일경로>로 가능. Hardhat node는 이 기능이 없으므로, "상태 유지"가 필요하면 Anvil을 쓰고, Hardhat은 컴파일·배포용으로만 쓰는 구성을 권장합니다. - 난이도: 브라우저 UI만 먼저 만들면 1~2일, Anvil
--state로 저장/불러오기까지 포함해도 터미널 사용법만 정리하면 바로 적용 가능합니다.
Step 1 완료 체크리스트
- Hardhat 경로:
hardhat/에 프로젝트가 있고,contracts/FaucetToken.sol에 name/symbol/민트 권한 구현됨
또는 Foundry 경로:foundry/에 프로젝트가 있고,src/FaucetToken.sol에 동일 요구사항 구현됨 - OpenZeppelin ERC20 + Ownable 기반으로 owner만 mint 가능함
-
npx hardhat compile(Hardhat) 또는forge build(Foundry)로 컴파일 성공 - 로컬 노드(Hardhat node / Anvil)에서 배포 후
balanceOf,mint호출로 동작 확인함 - README "로컬 실행 방법"에 사용한 경로(
hardhat/또는foundry/)와 컴파일·배포 명령 한 줄 이상 정리해 두면 좋음 - (선택) 로컬 노드 상시 실행·Remix/Ganache 연결 등은 C. 로컬 블록체인에서 테스트하기 참고
- (선택) 브라우저 UI에서 계정·잔액 보기, 상태 저장 후 재실행이 필요하면 D. 브라우저 UI + 상태 저장/복원 참고
위가 완료되면 Step 2(테스트·배포 스크립트)로 진행하면 됩니다.