Sepolia 테스트넷에 ERC-20 토큰 배포.
ChloeToken.
구성.
Caddy WAF
Traefik
Frontend
Backend FastAPI
PostgreSQL
Ethereum RPC
Sepolia Testnet
컨트랙트.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract ChloeToken is ERC20, Ownable {
uint256 public constant INITIAL_SUPPLY = 1_000_000 * 10**18;
constructor() ERC20("ChloeToken", "CHT") Ownable(msg.sender) {
_mint(msg.sender, INITIAL_SUPPLY);
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function burn(uint256 amount) public onlyOwner {
_burn(msg.sender, amount);
}
}
OpenZeppelin ERC20 사용.
Ownable로 owner 권한 관리.
초기 발행 1,000,000 CHT.
Docker 구성.
services:
postgres:
image: postgres:15-alpine
backend:
build: ./backend
environment:
- SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/...
- ADMIN_PRIVATE_KEY=${ADMIN_PRIVATE_KEY}
frontend:
build: ./frontend
Traefik 라우팅.
labels:
- "traefik.http.routers.chloetoken.rule=Host(`chloetoken.yeonghoon.kim`)"
- "traefik.http.routers.chloetoken.entrypoints=web"
Caddy WAF.
chloetoken.yeonghoon.kim {
@allowed remote_ip [관리자 IP]/32 [회사 IP]/26
handle @allowed {
reverse_proxy traefik:8080
}
handle {
respond "Forbidden" 403
}
}
토큰 전송 요청.
{
"to_address": "0x[수신자_주소]",
"amount": "100.5",
"memo": "Test Transfer #1"
}
백엔드 처리.
to_address 검증.
관리자 개인키로 서명.
Ethereum RPC 전송.
거래 영수증 확인.
PostgreSQL에 거래 내역 저장.
응답.
{
"transactionHash": "0x...",
"from": "0x...",
"to": "0x...",
"value": "100.5",
"blockNumber": 6054789,
"status": "success"
}
메모 필드.
class TransferRequest(BaseModel):
to_address: str
amount: float
memo: Optional[str] = None
transaction = db_models.Transaction(
tx_hash=receipt['transactionHash'].hex(),
from_address=settings.ADMIN_ADDRESS,
to_address=request.to_address,
amount=request.amount,
memo=request.memo,
status="confirmed"
)
처음엔 프론트에서 개인키를 받으려 했음.
const privateKey = document.getElementById('privateKey').value;
이건 위험해서 폐기.
백엔드 환경변수로 변경.
ADMIN_PRIVATE_KEY = os.getenv('ADMIN_PRIVATE_KEY')
프론트에서 RPC 직접 호출도 해봤음.
const balance = await ethers.provider.getBalance(address);
이후 백엔드 API로 변경.
const response = await fetch('/api/v1/balance/' + address);
상태.
컨트랙트 배포.
프로덕션 배포.
IP 접근 제한.
거래 메모 기능.
토큰 발행 테스트.
owner 권한 이전 검토.
Etherscan 검증 검토.
개인키는 프론트에 두면 안 됨.
서명은 백엔드에서 처리.
접근은 IP로 제한.