Guider/Blockchain/BlockchainDevGuide0007
Blockchain#07

BlockchainDevGuide0007

실전 프로젝트 완전 정복

✦ BLOCKCHAIN DEV GUIDE SERIES — FINAL ✦

실전 프로젝트 완전 정복 A-Z

DeFi 스테이킹 · NFT 게임 · DAO 거버넌스 · 풀스택 DApp · 포트폴리오 · 취업 전략

Guide0007 — FINAL 학습 기간: 4-6주 🎓 취업 준비 완성

🏆 이 가이드를 완료하면?

💻 프로젝트 결과물

  • DeFi 스테이킹 플랫폼 (완성)
  • P2E NFT 게임 (완성)
  • DAO 거버넌스 플랫폼 (완성)
  • 포트폴리오 사이트 (완성)

🎯 취업 결과

  • 블록체인 개발자 이력서 작성
  • GitHub 포트폴리오 완성
  • 기술 면접 완벽 대비
  • 취업 로드맵 완성

🏆 BlockchainDevGuide 시리즈 완주 현황

✅ Guide0001 블록체인 개념 ✅ Guide0002 프로그래밍 기초 ✅ Guide0003 스마트 컨트랙트 ✅ Guide0004 개발환경·배포 ✅ Guide0005 NFT·DeFi 심화 ✅ Guide0006 보안·감사 🔥 Guide0007 실전 프로젝트 ← 최종!

📚 전체 학습 목차

챕터 주제 핵심 기술 난이도
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 배포 기록으로 완성하는 현업 수준 포트폴리오

💼 채용 담당자가 가장 먼저 보는 것

  1. GitHub 프로필 — 잔디(커밋 히스토리), 스타 수, 팔로워
  2. Etherscan 배포 기록 — 실제 메인넷/테스트넷 배포 여부
  3. README 품질 — 프로젝트 설명, 아키텍처, 설치 방법
  4. 테스트 커버리지 — 90% 이상 여부
  5. 기술 블로그 — 깊이 있는 기술 이해 증명
포트폴리오 항목 필수 여부 준비 기간 임팩트
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를 완전 정복했습니다!
이제 여러분은 현업에서 즉시 일할 수 있는 블록체인 풀스택 개발자입니다.

7
가이드 완료
500K+
총 학습 글자
105
면접 Q&A
가능성

🚀 다음 단계

포트폴리오 완성 → 이력서 제출 → 면접 준비 → 취업 성공!
응원합니다! 🎉

반응형