✦ BLOCKCHAIN DEV GUIDE SERIES — FINAL ✦
실전 프로젝트 완전 정복 A-Z
DeFi 스테이킹 · NFT 게임 · DAO 거버넌스 · 풀스택 DApp · 포트폴리오 · 취업 전략
🏆 이 가이드를 완료하면?
💻 프로젝트 결과물
- DeFi 스테이킹 플랫폼 (완성)
- P2E NFT 게임 (완성)
- DAO 거버넌스 플랫폼 (완성)
- 포트폴리오 사이트 (완성)
🎯 취업 결과
- 블록체인 개발자 이력서 작성
- GitHub 포트폴리오 완성
- 기술 면접 완벽 대비
- 취업 로드맵 완성
🏆 BlockchainDevGuide 시리즈 완주 현황
📚 전체 학습 목차
| 챕터 | 주제 | 핵심 기술 | 난이도 |
|---|---|---|---|
| 01 | DeFi 스테이킹 플랫폼 | ERC20, 이자 계산, 보상 시스템 | ★★★☆☆ |
| 02 | P2E NFT 게임 | ERC-721, Chainlink VRF, 전투 시스템 | ★★★★☆ |
| 03 | DAO 거버넌스 플랫폼 | Governor, Timelock, Treasury | ★★★★☆ |
| 04 | Next.js 풀스택 DApp | wagmi, RainbowKit, The Graph | ★★★★☆ |
| 05 | 멀티체인 배포 전략 | Polygon, Arbitrum, Optimism, Base | ★★★★☆ |
| 06 | CI/CD & 모니터링 | GitHub Actions, Tenderly, OpenZeppelin Defender | ★★★☆☆ |
| 07 | 포트폴리오 구성 전략 | GitHub, README, 기술 블로그 | ★★★☆☆ |
| 08 | 이력서 & 취업 전략 | 블록체인 취업 시장 분석, 기업별 전략 | ★★★☆☆ |
| 09 | 기술 면접 완전 정복 | 라이브 코딩, 시스템 설계, 심층 질문 | ★★★★★ |
| 10 | 최종 로드맵 & 커리어 성장 | 시니어 개발자 → CTO 성장 전략 | ★★★★☆ |
📘 Chapter 01
프로젝트 1: DeFi 스테이킹 플랫폼 — 처음부터 끝까지
ERC-20 토큰 + 스테이킹 컨트랙트 + 이자 계산 + Next.js 프론트엔드 + 메인넷 배포
🎯 프로젝트 목표
사용자가 토큰을 예치(stake)하면 시간에 따라 이자를 받고, 언제든 출금(unstake)할 수 있는 DeFi 스테이킹 플랫폼을 처음부터 끝까지 구현합니다. Yearn Finance, Curve Finance 스타일의 현업 수준 완성도.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title StakingRewards
* @dev Synthetix 스타일 스테이킹 컨트랙트
* - 분당 보상률로 이자 계산 (rewardRate)
* - 스테이킹 즉시 이자 누적 시작
* - 복리 계산: rewardPerTokenStored 활용
* - 긴급 정지(Pausable) 기능 포함
*/
contract StakingRewards is ReentrancyGuard, Pausable, Ownable {
using SafeERC20 for IERC20;
IERC20 public immutable stakingToken; // 예치 토큰 (예: USDC)
IERC20 public immutable rewardsToken; // 보상 토큰 (예: 거버넌스 토큰)
// 보상 분배 설정
uint256 public duration = 30 days; // 보상 기간
uint256 public finishAt; // 보상 종료 시점
uint256 public updatedAt; // 마지막 업데이트 시점
uint256 public rewardRate; // 초당 보상 토큰 수
// 복리 계산을 위한 누적값
uint256 public rewardPerTokenStored; // 토큰 1개당 누적 보상
// 사용자별 상태
mapping(address => uint256) public userRewardPerTokenPaid; // 사용자의 마지막 보상 기준점
mapping(address => uint256) public rewards; // 미청구 보상
mapping(address => uint256) public balanceOf; // 스테이킹 잔액
uint256 public totalSupply; // 총 스테이킹량
event Staked(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);
event RewardsClaimed(address indexed user, uint256 reward);
event RewardAdded(uint256 reward);
constructor(address _stakingToken, address _rewardsToken) Ownable(msg.sender) {
stakingToken = IERC20(_stakingToken);
rewardsToken = IERC20(_rewardsToken);
}
// ===== 핵심 수식: 토큰 1개당 누적 보상 계산 =====
function rewardPerToken() public view returns (uint256) {
if (totalSupply == 0) return rewardPerTokenStored;
return rewardPerTokenStored + (
rewardRate * (lastTimeRewardApplicable() - updatedAt) * 1e18 / totalSupply
);
}
// 보상 종료 시점 또는 현재 시점 중 작은 값
function lastTimeRewardApplicable() public view returns (uint256) {
return finishAt < block.timestamp ? finishAt : block.timestamp;
}
// 사용자가 받을 수 있는 보상 계산
function earned(address account) public view returns (uint256) {
return (
(balanceOf[account] * (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18
) + rewards[account];
}
modifier updateReward(address account) {
rewardPerTokenStored = rewardPerToken();
updatedAt = lastTimeRewardApplicable();
if (account != address(0)) {
rewards[account] = earned(account);
userRewardPerTokenPaid[account] = rewardPerTokenStored;
}
_;
}
/**
* @notice 토큰 스테이킹
*/
function stake(uint256 amount) external nonReentrant whenNotPaused updateReward(msg.sender) {
require(amount > 0, "AMOUNT_ZERO");
balanceOf[msg.sender] += amount;
totalSupply += amount;
stakingToken.safeTransferFrom(msg.sender, address(this), amount);
emit Staked(msg.sender, amount);
}
/**
* @notice 스테이킹 해제 (원금 회수)
*/
function withdraw(uint256 amount) external nonReentrant updateReward(msg.sender) {
require(amount > 0, "AMOUNT_ZERO");
require(balanceOf[msg.sender] >= amount, "INSUFFICIENT");
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
stakingToken.safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
/**
* @notice 보상 토큰 청구
*/
function claimReward() external nonReentrant updateReward(msg.sender) {
uint256 reward = rewards[msg.sender];
if (reward > 0) {
rewards[msg.sender] = 0;
rewardsToken.safeTransfer(msg.sender, reward);
emit RewardsClaimed(msg.sender, reward);
}
}
/**
* @notice 스테이킹 해제 + 보상 청구 한번에
*/
function exit() external {
withdraw(balanceOf[msg.sender]);
claimReward();
}
/**
* @notice 보상 풀 설정 (owner only)
*/
function setRewardsDuration(uint256 _duration) external onlyOwner {
require(block.timestamp > finishAt, "REWARD_ONGOING");
duration = _duration;
}
function notifyRewardAmount(uint256 amount) external onlyOwner updateReward(address(0)) {
if (block.timestamp >= finishAt) {
rewardRate = amount / duration;
} else {
uint256 remainingRewards = (finishAt - block.timestamp) * rewardRate;
rewardRate = (amount + remainingRewards) / duration;
}
require(rewardRate > 0, "REWARD_RATE_ZERO");
require(rewardRate * duration <= rewardsToken.balanceOf(address(this)), "INSUFFICIENT_REWARDS");
finishAt = block.timestamp + duration;
updatedAt = block.timestamp;
emit RewardAdded(amount);
}
}
1.2 Hardhat 배포 스크립트 + 검증
// scripts/deploy-staking.js
const { ethers, run } = require("hardhat");
async function main() {
const [deployer] = await ethers.getSigners();
console.log("배포 계정:", deployer.address);
console.log("ETH 잔액:", ethers.utils.formatEther(await deployer.getBalance()));
// 1. 스테이킹 토큰 배포 (USDC 대용)
const StakingToken = await ethers.getContractFactory("StakingToken");
const stakingToken = await StakingToken.deploy("Staking Token", "STK", ethers.utils.parseEther("1000000"));
await stakingToken.deployed();
console.log("StakingToken:", stakingToken.address);
// 2. 보상 토큰 배포
const RewardsToken = await ethers.getContractFactory("RewardsToken");
const rewardsToken = await RewardsToken.deploy("Rewards Token", "RWD", ethers.utils.parseEther("100000"));
await rewardsToken.deployed();
console.log("RewardsToken:", rewardsToken.address);
// 3. 스테이킹 컨트랙트 배포
const StakingRewards = await ethers.getContractFactory("StakingRewards");
const staking = await StakingRewards.deploy(stakingToken.address, rewardsToken.address);
await staking.deployed();
console.log("StakingRewards:", staking.address);
// 4. 보상 풀 설정 (30일 동안 10000 RWD)
await rewardsToken.transfer(staking.address, ethers.utils.parseEther("10000"));
await staking.setRewardsDuration(30 * 24 * 60 * 60);
await staking.notifyRewardAmount(ethers.utils.parseEther("10000"));
console.log("보상 풀 설정 완료 — 30일 x 10000 RWD");
// 5. Etherscan 소스 코드 검증 (Sepolia)
if (network.name !== "hardhat") {
await new Promise(r => setTimeout(r, 30000)); // 30초 대기 (블록 확인)
await run("verify:verify", {
address: staking.address,
constructorArguments: [stakingToken.address, rewardsToken.address],
});
console.log("✅ Etherscan 검증 완료");
}
}
main().catch(console.error);
📘 Chapter 02
프로젝트 2: P2E NFT 게임 — 전투·레벨업·마켓플레이스
Chainlink VRF 랜덤 스탯 + 전투 시스템 + 경험치·레벨업 + NFT 마켓플레이스 통합
🎮 게임 컨셉
각 NFT 캐릭터는 Chainlink VRF로 랜덤하게 결정된 스탯(체력·공격·방어)을 가집니다. 사용자는 몬스터와 전투하여 경험치와 토큰 보상을 획득하고, NFT를 마켓플레이스에서 거래할 수 있습니다. Axie Infinity 스타일의 간소화 버전.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
/**
* @title P2EGame
* @dev P2E NFT 게임 스마트 컨트랙트
* 캐릭터 민팅 → VRF 스탯 배정 → 전투 → 경험치/레벨업 → 보상
*/
contract P2EGame is ERC721URIStorage, VRFConsumerBaseV2, ReentrancyGuard {
VRFCoordinatorV2Interface immutable COORDINATOR;
uint64 subscriptionId;
bytes32 keyHash;
uint32 callbackGasLimit = 200000;
uint16 requestConfirmations = 3;
// 캐릭터 스탯 구조체
struct Character {
string name;
uint256 hp; // 최대 체력 (50-200)
uint256 attack; // 공격력 (10-100)
uint256 defense; // 방어력 (5-50)
uint256 speed; // 속도 (1-10)
uint256 level; // 현재 레벨
uint256 exp; // 현재 경험치
uint256 lastFight; // 마지막 전투 시간 (쿨다운)
}
// 몬스터 구조체
struct Monster {
string name;
uint256 hp;
uint256 attack;
uint256 expReward;
uint256 tokenReward; // wei 단위 ETH 보상
}
// tokenId → Character
mapping(uint256 => Character) public characters;
// requestId → tokenId (VRF 콜백용)
mapping(uint256 => uint256) public vrfRequestToToken;
// 몬스터 배열
Monster[] public monsters;
uint256 public nextTokenId;
uint256 public constant MINT_PRICE = 0.01 ether;
uint256 public constant FIGHT_COOLDOWN = 1 hours;
uint256 public constant EXP_PER_LEVEL = 100;
IERC20 public gameToken; // 보상 토큰
event CharacterMinted(uint256 indexed tokenId, address indexed owner);
event StatsAssigned(uint256 indexed tokenId, uint256 hp, uint256 atk, uint256 def);
event BattleResult(uint256 indexed tokenId, uint256 monsterId, bool won, uint256 expGained, uint256 reward);
event LevelUp(uint256 indexed tokenId, uint256 newLevel);
constructor(
address vrfCoordinator,
uint64 _subscriptionId,
bytes32 _keyHash,
address _gameToken
) ERC721("P2E Heroes", "HERO") VRFConsumerBaseV2(vrfCoordinator) {
COORDINATOR = VRFCoordinatorV2Interface(vrfCoordinator);
subscriptionId = _subscriptionId;
keyHash = _keyHash;
gameToken = IERC20(_gameToken);
// 초기 몬스터 설정
monsters.push(Monster("슬라임", 50, 5, 20, 0.001 ether));
monsters.push(Monster("고블린", 100, 15, 50, 0.003 ether));
monsters.push(Monster("오크", 200, 30, 100, 0.005 ether));
monsters.push(Monster("드래곤", 500, 80, 300, 0.02 ether));
}
/**
* @notice NFT 캐릭터 민팅 + VRF 스탯 요청
*/
function mintCharacter(string calldata name) external payable returns (uint256 tokenId) {
require(msg.value >= MINT_PRICE, "INSUFFICIENT_ETH");
tokenId = nextTokenId++;
_safeMint(msg.sender, tokenId);
// 기본 스탯으로 초기화 (VRF 콜백 전까지)
characters[tokenId] = Character({
name: name,
hp: 50, // 임시값
attack: 10,
defense: 5,
speed: 1,
level: 1,
exp: 0,
lastFight: 0
});
// VRF 난수 요청 (스탯 결정용)
uint256 requestId = COORDINATOR.requestRandomWords(
keyHash, subscriptionId, requestConfirmations,
callbackGasLimit, 4 // 4개 난수: hp, attack, defense, speed
);
vrfRequestToToken[requestId] = tokenId;
emit CharacterMinted(tokenId, msg.sender);
}
/**
* @notice Chainlink VRF 콜백 — 스탯 배정
*/
function fulfillRandomWords(uint256 requestId, uint256[] memory randomWords) internal override {
uint256 tokenId = vrfRequestToToken[requestId];
characters[tokenId].hp = 50 + (randomWords[0] % 151); // 50~200
characters[tokenId].attack = 10 + (randomWords[1] % 91); // 10~100
characters[tokenId].defense = 5 + (randomWords[2] % 46); // 5~50
characters[tokenId].speed = 1 + (randomWords[3] % 10); // 1~10
emit StatsAssigned(tokenId, characters[tokenId].hp,
characters[tokenId].attack, characters[tokenId].defense);
}
/**
* @notice 전투 시스템 — 몬스터와 싸우기
* @param tokenId 내 캐릭터 NFT tokenId
* @param monsterId 상대 몬스터 인덱스
*/
function fight(uint256 tokenId, uint256 monsterId) external nonReentrant {
require(ownerOf(tokenId) == msg.sender, "NOT_OWNER");
require(monsterId < monsters.length, "INVALID_MONSTER");
require(
block.timestamp >= characters[tokenId].lastFight + FIGHT_COOLDOWN,
"COOLDOWN"
);
Character storage ch = characters[tokenId];
Monster memory mon = monsters[monsterId];
// 간단한 전투 결과 계산
// DPS = attack / monster_defense
// 플레이어 DPS > 몬스터 DPS 시 승리
uint256 playerDPS = ch.attack * 100 / (mon.defense > 0 ? mon.defense : 1);
uint256 monsterDPS = mon.attack * 100 / (ch.defense > 0 ? ch.defense : 1);
bool won = playerDPS > monsterDPS;
ch.lastFight = block.timestamp;
uint256 expGained = 0;
uint256 tokenReward = 0;
if (won) {
expGained = mon.expReward;
tokenReward = mon.tokenReward;
ch.exp += expGained;
// 레벨업 체크
uint256 newLevel = 1 + ch.exp / EXP_PER_LEVEL;
if (newLevel > ch.level) {
ch.level = newLevel;
// 레벨업 시 스탯 증가
ch.hp += 20;
ch.attack += 5;
ch.defense += 3;
emit LevelUp(tokenId, newLevel);
}
// 토큰 보상 지급
if (tokenReward > 0) {
gameToken.transfer(msg.sender, tokenReward);
}
}
emit BattleResult(tokenId, monsterId, won, expGained, tokenReward);
}
// 캐릭터 정보 조회
function getCharacter(uint256 tokenId) external view returns (Character memory) {
return characters[tokenId];
}
}
📘 Chapter 03
멀티체인 배포 전략 — 메인넷에서 L2까지
Ethereum, Polygon, Arbitrum, Optimism, Base — 각 체인별 배포 전략과 가스 최적화
| 체인 | TPS | 가스비 | 특징 | 적합 용도 |
|---|---|---|---|---|
| Ethereum | 15-30 | 높음 ($5-100) | 최고 보안, 최대 TVL | 고가치 DeFi, NFT |
| Polygon | 7000+ | 매우 낮음 ($0.01) | EVM 호환, 대규모 생태계 | 게임, NFT, 대량 거래 |
| Arbitrum | 40000+ | 낮음 ($0.1-1) | Optimistic Rollup, ETH 보안 | DeFi, 파생상품 |
| Base | 2000+ | 낮음 ($0.05-0.5) | Coinbase 운영, OP Stack | 소비자향 DApp |
# hardhat.config.js — 멀티체인 네트워크 설정
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: {
version: "0.8.19",
settings: {
optimizer: { enabled: true, runs: 200 },
viaIR: true, // IR 컴파일러 활성화 (가스 최적화)
},
},
networks: {
// Testnet
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 11155111,
},
polygonMumbai: {
url: process.env.POLYGON_MUMBAI_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 80001,
},
arbitrumGoerli: {
url: process.env.ARBITRUM_GOERLI_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 421613,
},
// Mainnet
mainnet: {
url: process.env.MAINNET_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 1,
gasPrice: "auto", // EIP-1559 자동
},
polygon: {
url: process.env.POLYGON_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 137,
},
arbitrum: {
url: process.env.ARBITRUM_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 42161,
},
base: {
url: process.env.BASE_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
chainId: 8453,
},
},
etherscan: {
apiKey: {
mainnet: process.env.ETHERSCAN_API_KEY,
sepolia: process.env.ETHERSCAN_API_KEY,
polygon: process.env.POLYGONSCAN_API_KEY,
arbitrumOne: process.env.ARBISCAN_API_KEY,
base: process.env.BASESCAN_API_KEY,
},
},
gasReporter: {
enabled: process.env.REPORT_GAS === "true",
currency: "USD",
coinmarketcap: process.env.COINMARKETCAP_API_KEY,
},
};
// 멀티체인 배포 스크립트
// npx hardhat run scripts/deploy.js --network polygon
// npx hardhat run scripts/deploy.js --network arbitrum
// npx hardhat run scripts/deploy.js --network base
📘 Chapter 04
CI/CD & 모니터링 — 프로덕션 운영 체계
GitHub Actions 자동화 + Tenderly 모니터링 + OpenZeppelin Defender 보안 자동화
# .github/workflows/ci.yml — 스마트 컨트랙트 CI/CD
name: Smart Contract CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Install Node.js
uses: actions/setup-node@v3
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: Run Hardhat tests
run: npx hardhat test
- name: Run coverage
run: npx hardhat coverage
env:
COVERAGE_THRESHOLD: 90
- name: Run Slither analysis
uses: crytic/slither-action@v0.3.0
with:
target: contracts/
slither-args: --exclude-dependencies
fail-on: high # High 심각도 발견 시 CI 실패
- name: Gas report
run: REPORT_GAS=true npx hardhat test
env:
COINMARKETCAP_API_KEY: secrets.CMC_API_KEY
deploy-testnet:
needs: test
runs-on: ubuntu-latest
if: github.ref == refs/heads/develop
steps:
- uses: actions/checkout@v3
- run: npm ci
- name: Deploy to Sepolia
run: npx hardhat run scripts/deploy.js --network sepolia
env:
SEPOLIA_RPC_URL: secrets.SEPOLIA_RPC_URL
PRIVATE_KEY: secrets.DEPLOY_PRIVATE_KEY
deploy-mainnet:
needs: test
runs-on: ubuntu-latest
if: github.ref == refs/heads/main
environment: production # 수동 승인 필요
steps:
- uses: actions/checkout@v3
- run: npm ci
- name: Deploy to Mainnet
run: npx hardhat run scripts/deploy.js --network mainnet
env:
MAINNET_RPC_URL: secrets.MAINNET_RPC_URL
PRIVATE_KEY: secrets.DEPLOY_PRIVATE_KEY
# Tenderly 실시간 모니터링 설정
# tenderly.yaml
version: v1.0
project_slug: my-defi-project
contracts:
- name: StakingRewards
address: "0xYourContractAddress"
network: 1 # Ethereum Mainnet
# 알림 설정 (대규모 출금 감지)
alerts:
- name: "Large Withdrawal Alert"
type: tx_event
filter:
contract: StakingRewards
event_name: Withdrawn
condition: amount > 100000000000000000000 # 100 토큰 이상
notifications:
- type: slack
webhook_url: YOUR_SLACK_WEBHOOK
- type: email
email: security@yourprotocol.com
- name: "Emergency Pause Trigger"
type: tx_event
filter:
contract: StakingRewards
event_name: Paused
notifications:
- type: pagerduty
routing_key: YOUR_PAGERDUTY_KEY
📘 Chapter 05
포트폴리오 구성 전략 — 채용 담당자를 사로잡는 방법
GitHub, README, 기술 블로그, Etherscan 배포 기록으로 완성하는 현업 수준 포트폴리오
💼 채용 담당자가 가장 먼저 보는 것
- GitHub 프로필 — 잔디(커밋 히스토리), 스타 수, 팔로워
- Etherscan 배포 기록 — 실제 메인넷/테스트넷 배포 여부
- README 품질 — 프로젝트 설명, 아키텍처, 설치 방법
- 테스트 커버리지 — 90% 이상 여부
- 기술 블로그 — 깊이 있는 기술 이해 증명
| 포트폴리오 항목 | 필수 여부 | 준비 기간 | 임팩트 |
|---|---|---|---|
| GitHub 3개 이상 프로젝트 | 필수 | 4-8주 | ★★★★★ |
| Etherscan 검증된 배포 | 필수 | 1일 | ★★★★☆ |
| 95% 이상 테스트 커버리지 | 권장 | 2-3주 | ★★★★☆ |
| 기술 블로그 5개 이상 | 권장 | 지속 | ★★★★☆ |
| 오픈소스 기여 (PR) | 선택 | 지속 | ★★★★★ |
| 감사 보고서 or 버그 바운티 | 선택 | 지속 | ★★★★★ |
📘 Chapter 06
블록체인 개발자 이력서 & 취업 전략
국내외 블록체인 취업 시장 분석 + 기업별 맞춤 전략 + 이력서 작성 팁
🇰🇷 국내 주요 블록체인 기업
- 카카오 (카이아/클레이튼)
- 라인 (핀시아/LINE NEXT)
- SK Planet (기업 블록체인)
- 두나무 (업비트, 람다256)
- 빗썸 (거래소)
- 코빗, 코인원
- 메타콩즈, 클레이스왑
- NFT/GameFi 스타트업 多
🌏 해외 주요 블록체인 기업
- Uniswap Labs
- Aave Companies
- OpenSea
- Coinbase (엔지니어링)
- Chainlink Labs
- Alchemy, Infura
- Trail of Bits (보안 감사)
- 원격 근무 가능 DeFi 프로토콜 多
📄 블록체인 개발자 이력서 핵심 섹션
# 기술 스택 (구체적으로)
Languages: Solidity ^0.8.x, TypeScript, Python
Frameworks: Hardhat, Foundry, Next.js 14, wagmi v2
Protocols: Uniswap V3, Aave V3, OpenZeppelin
Tools: Slither, Echidna, The Graph, IPFS
Chains: Ethereum, Polygon, Arbitrum, Base
# 프로젝트 (숫자로 임팩트 표현)
- DeFi 스테이킹 플랫폼: TVL $500K 달성, 95% 테스트 커버리지
- P2E NFT 게임: 1,000+ 민팅, Polygon 배포, Gas 40% 최적화
- 보안 감사: Code4rena 콘테스트 H-02 취약점 발견 ($2,000 보상)
# 오픈소스 기여
- OpenZeppelin PR #XXXX: [기여 내용]
- Foundry Issue #XXXX: [버그 리포트/수정]
🎤 Chapter 07
블록체인 개발자 기술 면접 완전 정복 — 최종 Q&A TOP 15
실제 면접에서 자주 나오는 고급 질문들과 완벽한 모범 답안 — 시리즈 총정리
Q1. Solidity에서 storage, memory, calldata의 차이점은?
A: storage는 블록체인에 영구 저장(비쌈, SLOAD 2100gas/SSTORE 20000gas). memory는 함수 실행 동안만 존재하는 임시 공간(저렴). calldata는 외부 함수 호출 시 입력 데이터로 읽기 전용, 복사 비용 없음(가장 저렴). 가스 최적화: storage 변수를 루프에서 반복 읽을 때 memory에 캐싱하고, 배열/문자열 파라미터는 memory 대신 calldata 사용.
Q2. EIP-712 구조화된 데이터 서명이란 무엇이고 언제 사용하나요?
A: EIP-712는 구조화된 데이터(타입 + 값)에 서명하는 표준입니다. 사용자가 서명하는 내용을 MetaMask에서 사람이 읽을 수 있게 표시합니다. 활용: ① ERC-2612 permit() — approve 없이 서명으로 allowance 설정 ② Seaport/OpenSea 주문 서명 ③ Uniswap Permit2. 구현: domainSeparator + structHash를 keccak256으로 해시 후 ecrecover로 서명자 복원.
Q3. The Graph를 사용하는 이유와 Subgraph 작성 방법은?
A: 이더리움은 과거 이벤트 조회가 어렵고(getLogs 비용), 복잡한 조건 필터링이 불가합니다. The Graph는 이벤트를 인덱싱하여 GraphQL로 빠르게 조회합니다. Subgraph 구성: ① schema.graphql(엔티티 정의) ② subgraph.yaml(ABI, 이벤트 매핑) ③ mapping.ts(AssemblyScript, 이벤트 → 엔티티 변환). 배포: graph deploy를 Hosted Service 또는 Decentralized Network에 배포.
Q4. Optimistic Rollup과 ZK Rollup의 차이점은?
A: Optimistic Rollup(Arbitrum, Optimism)은 트랜잭션이 유효하다고 가정하고 7일 챌린지 기간을 두며, 출금 시 7일 대기 필요. EVM 완전 호환. ZK Rollup(zkSync, Starknet)은 영지식 증명으로 즉각 검증, 출금 빠름. 하지만 EVM 호환성 제한적이고 증명 생성 비용 높음. 현재 트렌드: EVM 호환 ZK(zkSync Era, Polygon zkEVM)이 확산 중.
Q5. 스마트 컨트랙트 업그레이드 결정 시 고려해야 할 사항은?
A: ① 정말 필요한가 — 업그레이드 가능 = 중앙화 리스크(admin이 악의적 코드 배포 가능). ② Timelock + 다중서명으로 업그레이드 제어. ③ Storage 레이아웃 호환성 유지. ④ 사용자 공지 기간. ⑤ 감사 재실시. 현업 기준: 완전히 불변(immutable)이거나 Timelock(최소 48시간) + Multisig(최소 3/5) 조합을 권장.
Q6. IPFS CID(Content Identifier)의 동작 원리는?
A: CID는 파일 내용의 cryptographic hash입니다. 내용이 같으면 항상 같은 CID, 내용이 1바이트라도 다르면 완전히 다른 CID. NFT에서 tokenURI가 IPFS CID를 가리키면 메타데이터 위변조가 불가합니다(해시가 달라지기 때문). NFT 민팅 시: 이미지 → IPFS 업로드 → CID1 획득 → JSON 메타데이터 작성(image: ipfs://CID1) → 메타데이터 IPFS 업로드 → CID2 획득 → tokenURI = ipfs://CID2.
Q7. DeFi 프로토콜에서 가스 최적화를 위해 어떤 전략을 사용하셨나요?
A: ① Storage 패킹 — uint128 두 개를 하나의 슬롯에 묶어 SLOAD 1회로 두 값 읽기 ② Calldata 사용 — 외부 함수의 배열/구조체 파라미터를 calldata로 선언 ③ Custom Error — require 문자열 제거 ④ 불필요한 emit 제거 ⑤ 루프 내 storage 캐싱 ⑥ unchecked 블록 적용 ⑦ 비트 연산으로 나눗셈 대체(2의 거듭제곱). 프로젝트에서 이 방법들로 평균 가스 35% 절감 달성.
Q8. wagmi v2와 ethers.js를 함께 사용하는 경우는?
A: wagmi v2는 내부적으로 viem을 사용합니다. 대부분의 경우 wagmi hooks만으로 충분하지만, 서버 사이드(API Routes, Node.js 스크립트)에서는 wagmi를 쓸 수 없으므로 viem 또는 ethers.js를 직접 사용합니다. 예: 배포 스크립트는 Hardhat+ethers, 프론트엔드는 wagmi+viem, The Graph subgraph 쿼리는 Apollo Client 사용.
Q9. ERC-4337 Account Abstraction이란?
A: ERC-4337은 스마트 컨트랙트 지갑을 EOA(외부 소유 계정)처럼 사용할 수 있게 합니다. 핵심 개념: ① UserOperation — 트랜잭션 대신 사용 ② Bundler — UserOp를 묶어 체인에 제출 ③ Paymaster — 가스비를 대신 납부(가스리스 UX). 활용: 소셜 로그인 지갑, 가스리스 트랜잭션, 지갑 복구, 일괄 처리. Safe, Biconomy, ZeroDev 등이 구현체 제공.
Q10. 스마트 컨트랙트 개발 시 자주 하는 실수 TOP 5는?
A: ① 이벤트 누락 — 중요 상태 변경 후 event emit 없음 → 오프체인 추적 불가 ② approve 미검사 — ERC-20 approve 반환값 체크 안 함 ③ 제로 어드레스 미체크 — address(0) 전송으로 토큰 소각 ④ 가스 리밋 하드코딩 — .gas(2300)로 재진입 방지 시도 → 하드포크 후 부족 ⑤ block.timestamp 과신 — 난수나 짧은 잠금에 사용.
Q11. Foundry와 Hardhat 중 어떤 것을 선호하시나요? 이유는?
A: 테스트는 Foundry, 배포는 Hardhat을 선호합니다. Foundry는 Solidity로 테스트 작성(언어 전환 없음), forge test 실행 속도 10배 빠름, fuzz/invariant 테스트 내장, forge coverage 상세함. Hardhat은 배포 스크립트 생태계(hardhat-deploy), Etherscan 검증, TypeChain 타입 생성이 성숙. 두 도구를 함께 사용하는 것이 현업에서 점점 일반화되고 있습니다.
Q12. 라이브 코딩: ERC-20 + 스테이킹 컨트랙트를 30분 안에 구현하라면?
A: ① ERC20 컨트랙트 작성 (OZ ERC20 상속, 1분) ② mapping(address→uint) stakes, lastStaked 선언 ③ stake(amount) — CEI 패턴, transferFrom ④ withdraw(amount) — nonReentrant, earned() 계산 후 transfer ⑤ earned(address) — (stakes[user] * APY * (now - lastStaked)) / (365days * 100) ⑥ claimReward() — rewards 청구. 핵심: 먼저 인터페이스/구조 설계 후 구현, 테스트는 간단한 케이스부터.
Q13. 블록체인에서 개인정보 보호(Privacy) 어떻게 구현하나요?
A: 기본적으로 블록체인은 모든 것이 공개됩니다. 프라이버시 구현 방법: ① Commit-Reveal — 값을 해시로 먼저 제출 ② Stealth Address — 수신자마다 일회성 주소 생성(EIP-5564) ③ ZK Proof — Tornado Cash 방식(현재 OFAC 제재), Aztec Network ④ MACI — 최소 반감 가능 투표(Clr.fund). 현업에서는 민감한 데이터는 오프체인에 암호화 저장, 해시만 온체인에 기록하는 하이브리드 접근이 일반적.
Q14. 블록체인 개발자로서 5년 후 커리어 목표는?
A (모범 답안): 단기(1년): 실제 운영 DeFi 프로토콜의 스마트 컨트랙트 개발자로 참여, TVL $1M 이상 프로젝트 출시. 중기(3년): 보안 감사 경력 추가, ImmuneFi Critical 버그 발견, 오픈소스 기여 Top Contributor. 장기(5년): 블록체인 아키텍처 설계자 또는 보안 감사 리더. 전문화 방향: ZK 증명, MEV, 크로스체인 인프라 중 선택 집중.
Q15. 최근 가장 관심 있는 블록체인 기술 트렌드는?
A (2025 기준 트렌드): ① ERC-4337 Account Abstraction의 보편화 — 가스리스 UX로 Web2 수준 DApp 가능 ② Intent-Based Architecture — CoW Protocol, UniswapX 방식으로 사용자가 결과만 명시 ③ Restaking (EigenLayer) — ETH 보안을 다른 프로토콜에도 제공 ④ ZK Everything — 개인정보 + 확장성을 동시에 ⑤ 모듈러 블록체인 (Celestia, Avail) — 실행/합의/데이터 가용성 분리. 현재 [관심 기술]을 직접 구현하며 학습 중입니다.
✅ BlockchainDevGuide 시리즈 최종 학습 체크리스트
이 모든 항목을 완료하면 현업 블록체인 개발자로 취업 가능한 수준입니다
📗 Guide0001-0002
- 블록체인 개념 완전 이해
- JavaScript/TypeScript 숙련
- Git/GitHub 능숙
- Node.js 개발 환경
📗 Guide0003-0004
- Solidity 문법 완전 습득
- ERC-20/721 직접 구현
- Hardhat 테스트 작성
- Sepolia 배포 경험
📗 Guide0005
- ERC-1155 멀티토큰
- Uniswap V3 유동성
- Flash Loan 구현
- DAO 거버넌스 배포
📕 Guide0006
- 보안 취약점 5가지
- Slither CI 통합
- Echidna 퍼징 테스트
- 감사 보고서 작성
📙 Guide0007
- DeFi 스테이킹 플랫폼
- P2E NFT 게임
- 멀티체인 배포
- CI/CD 파이프라인
🎯 취업 준비
- GitHub 포트폴리오 완성
- 이력서 작성 완료
- 면접 Q&A TOP 45 암기
- 지원서 제출!
✦ CONGRATULATIONS ✦
🎓 BlockchainDevGuide
시리즈 완주!
Guide0001부터 Guide0007까지 — 블록체인 개발자 A-Z를 완전 정복했습니다!
이제 여러분은 현업에서 즉시 일할 수 있는 블록체인 풀스택 개발자입니다.
🚀 다음 단계
포트폴리오 완성 → 이력서 제출 → 면접 준비 → 취업 성공!
응원합니다! 🎉