ERC-20 토큰을 A부터 Z까지 만들기 (Hardhat + OpenZeppelin)
Source: Dev.to
Disclaimer
교육 목적만을 위한 것입니다. 금전적 가치가 없으며, 실제 브랜드나 스테이블코인과 연관되지 않습니다.
왜 이 글을 썼는가
많은 사람들이 암호화폐에 입문하면서 같은 질문을 합니다:
- “왜 내 지갑에 무작위 토큰이 나타났나요?”
- “잔액은 보이는데 왜 팔 수 없나요?”
- “ERC‑20 토큰은 실제로 어떻게 생성되나요?”
- “가짜 토큰인데 왜 진짜처럼 보이나요?”
이를 진정으로 이해하는 가장 쉬운 방법은 한 번이라도 직접 ERC‑20 토큰을 만들어 보는 것입니다. 직접 만들면 모든 것이 갑자기 이해됩니다: 잔액, 지갑, 승인, 가짜 토큰, 승인 과정 등.
우리가 만들게 될 것
우리는 다음과 같은 깔끔한 교육용 ERC‑20 토큰을 만들 것입니다:
- OpenZeppelin ERC‑20 구현
- 역할 기반 접근 제어
- 일시 중지 가능한 전송
- 토큰 발행
- Hardhat을 이용한 배포
- MetaMask에 토큰 추가
- 지갑이 잔액을 감지하는 방식 이해
1. ERC‑20란 무엇인가 (쉬운 설명)
ERC‑20는 통화가 아닙니다. 최소한 ERC‑20 토큰은 다음과 같은 함수를 구현합니다:
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function totalSupply() external view returns (uint256);
function name() external view returns (string);
function symbol() external view returns (string);
function decimals() external view returns (uint8);
Important
토큰이 가격과 실제 가치를 가지려면 시장과 유동성이 존재해야 합니다. 유동성이 없으면 → 가격이 없고 → 실제 가치도 없습니다.
2. 환경 설정
요구 사항
- Node.js (LTS)
- npm
- MetaMask 지갑
- Sepolia 테스트 ETH
- RPC 제공자 (Alchemy, Infura 등)
프로젝트 생성
mkdir my-token
cd my-token
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox dotenv
npx hardhat
프롬프트가 표시되면 Create a JavaScript project를 선택하세요.
3. OpenZeppelin 설치
OpenZeppelin은 감사를 거친, 프로덕션 등급의 스마트 계약을 제공합니다.
npm install @openzeppelin/contracts
Source:
4. ERC‑20 토큰 계약 작성
contracts/MyToken.sol 파일을 생성합니다:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/*
MyToken — an educational ERC-20 token.
NOT money. NOT a stablecoin.
*/
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Pausable.sol";
contract MyToken is ERC20, AccessControl, Pausable {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
constructor(uint256 initialSupply)
ERC20("My Educational Token", "MYT")
{
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
_grantRole(MINTER_ROLE, msg.sender);
_grantRole(PAUSER_ROLE, msg.sender);
_mint(msg.sender, initialSupply);
}
function mint(address to, uint256 amount)
external
onlyRole(MINTER_ROLE)
{
_mint(to, amount);
}
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() external onlyRole(PAUSER_ROLE) {
_unpause();
}
function _update(address from, address to, uint256 amount)
internal
override
whenNotPaused
{
super._update(from, to, amount);
}
}
핵심 개념
name과symbol은 단순히 문자열일 뿐입니다.- 계약은 잔액을 저장합니다.
- 토큰은 발행(민트)된 이후에만 존재합니다.
- 발행하지 않으면 잔액은 0이 됩니다.
5. Hardhat 구성
hardhat.config.js 파일을 만들거나(편집) 합니다:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: "0.8.20",
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
},
},
};
6. 환경 변수
Create a .env file (never commit this file):
SEPOLIA_RPC=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
PRIVATE_KEY=0xYOUR_PRIVATE_KEY
7. 배포 스크립트
scripts/deploy.js 파일을 생성합니다:
const hre = require("hardhat");
async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying with address:", deployer.address);
const initialSupply = hre.ethers.parseUnits("1000", 18);
const MyToken = await hre.ethers.getContractFactory("MyToken");
const token = await MyToken.deploy(initialSupply);
await token.waitForDeployment();
console.log("Token deployed at:", await token.getAddress());
}
main().catch(console.error);
8. 컴파일 및 배포
npx hardhat compile
npx hardhat run scripts/deploy.js --network sepolia
계약 주소를 받게 됩니다.
9. 토큰을 MetaMask에 추가하기
- MetaMask를 Sepolia 네트워크로 전환합니다.
- Assets → Import Token 으로 이동합니다.
- 계약 주소를 붙여넣습니다. MetaMask가 자동으로 심볼과 소수점을 채워줍니다.
잔액이 0으로 표시되면 다음을 확인하세요:
- 올바른 네트워크인지
- 토큰을 발행한 주소가 실제로 토큰을 받았는지
- 소수점 설정
- MetaMask를 새로 고침(캐시 삭제)
10. 토큰이 “갑자기” 나타날 수 있는 이유
지갑은 토큰을 저장하지 않습니다. 지갑은 단순히 다음을 호출합니다:
balanceOf(yourAddress)
따라서 누구든지 당신의 주소로 토큰을 허가나 요청 없이 발행할 수 있으며, 지갑은 여전히 그것을 표시합니다.
11. 이름, 심볼, 아이콘은 아무 의미가 없다
- 토큰 이름에 대한 전역 레지스트리가 없습니다.
- 온체인에서 상표 집행이 이루어지지 않습니다.
- “공식 토큰” 플래그가 없습니다.
누구든지 이름, 심볼, 소수점 자리수, 로고를 마음대로 지정하여 계약을 배포할 수 있습니다. 유일하게 신뢰할 수 있는 토큰 식별자는 계약 주소입니다.
12. 핵심 보안 교훈
이 기본 원칙들을 이해하면 많은 사기를 쉽게 알 수 있습니다:
- “Free tokens” → 의미 없는 잔액
- 가짜 스테이블코인 → 멋진 이름을 가진 ERC‑20 계약일 뿐
- 승인 함정 → 위험한 권한
지식이 두려움을 이깁니다.
결론
ERC‑20 토큰은 단순히 스마트 계약일 뿐입니다. 직접 토큰을 한 번 만들어 보면 잔액, 승인, 발행 및 이를 둘러싼 보안 함정을 이해하는 가장 빠른 방법입니다.