[Blockchain] Next.js와 함께하는 DeFi 서비스 개발 가이드

25년 07월 13일 10:00LearningDeFi, Blockchain, Next.js, Solidity, Smart Contracts, Web3

🏦 Next.js와 함께하는 DeFi (탈중앙화 금융) 서비스 개발 가이드

전통 금융을 혁신하는 블록체인 기반 금융 서비스들을 Next.js와 Solidity로 구축하는 방법을 알아봅니다.

🌟 DeFi란 무엇인가?

**DeFi (Decentralized Finance)**는 블록체인 기술, 특히 스마트 컨트랙트를 활용하여 은행, 증권사 같은 중앙 중개자 없이 금융 서비스를 제공하는 시스템입니다. 이를 통해 누구나 인터넷만 연결되어 있다면 자유롭게 금융 활동에 참여할 수 있습니다.

전통 금융 vs. DeFi

항목전통 금융 (CeFi)탈중앙화 금융 (DeFi)
중개자은행, 정부, 카드사스마트 컨트랙트 (코드)
접근성KYC/AML, 지역/신용 제한누구나 참여 가능 (무허가성)
투명성제한적, 내부 정보모든 거래가 공개 (온체인)
운영 시간영업시간 제한24/7, 365일 중단 없음
상호운용성폐쇄적인 시스템레고 블록처럼 조합 가능

🔧 핵심 DeFi 서비스와 Solidity 코드 예제

1. 탈중앙화 거래소 (DEX - Decentralized Exchange)

중앙화된 오더북 없이 사용자들이 직접 토큰을 교환하는 플랫폼입니다. AMM (Automated Market Maker) 모델이 주로 사용됩니다.

Uniswap 스타일의 간단한 AMM 구현

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

// 간단한 DEX 컨트랙트
contract SimpleDEX is ReentrancyGuard {
    IERC20 public immutable tokenA;
    IERC20 public immutable tokenB;

    uint256 public reserveA;
    uint256 public reserveB;

    // 유동성 공급자의 지분을 추적
    mapping(address => uint256) public liquidity;
    uint256 public totalLiquidity;

    // 이벤트 정의
    event LiquidityAdded(address indexed provider, uint256 amountA, uint256 amountB);
    event TokensSwapped(address indexed trader, address indexed tokenIn, uint256 amountIn, uint256 amountOut);

    constructor(address _tokenA, address _tokenB) {
        tokenA = IERC20(_tokenA);
        tokenB = IERC20(_tokenB);
    }

    // 유동성 공급 함수
    function addLiquidity(uint256 _amountA, uint256 _amountB) external nonReentrant {
        // ... (생략)
    }

    // 토큰 스왑 함수 (A -> B)
    function swapAtoB(uint256 _amountAIn) external nonReentrant {
        require(_amountAIn > 0, "Input amount must be positive");

        uint256 amountBOut = getAmountOut(_amountAIn, reserveA, reserveB);
        require(amountBOut > 0, "Insufficient output");

        // 토큰 전송
        tokenA.transferFrom(msg.sender, address(this), _amountAIn);
        tokenB.transfer(msg.sender, amountBOut);

        // 유동성 풀 업데이트
        reserveA += _amountAIn;
        reserveB -= amountBOut;

        emit TokensSwapped(msg.sender, address(tokenA), _amountAIn, amountBOut);
    }

    // 교환될 토큰 양 계산 (x * y = k 모델)
    function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256) {
        uint256 amountInWithFee = amountIn * 997; // 0.3% 수수료 적용
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = (reserveIn * 1000) + amountInWithFee;
        return numerator / denominator;
    }
}

2. 대출/차용 프로토콜 (Lending/Borrowing)

암호화폐를 담보로 다른 암호화폐를 빌리거나, 자신의 자산을 예치하고 이자를 받을 수 있는 서비스입니다.

// 간단한 대출 프로토콜
contract LendingProtocol is ReentrancyGuard {
    // ... (생략)

    // 자산 예치 (공급)
    function supply(address token, uint256 amount) external nonReentrant {
        // ... (생략)
    }

    // 담보 기반 대출
    function borrow(address token, uint256 amount) external nonReentrant {
        // ... (생략)
        // isAccountHealthy(msg.sender) 와 같은 함수로 담보 비율을 확인해야 함
    }
}

3. 스테이킹 (Staking)

특정 토큰을 프로토콜에 예치(락업)하고, 그 대가로 보상을 받는 시스템입니다. 네트워크 보안에 기여하거나, 거버넌스에 참여하는 등의 역할을 합니다.

// 간단한 스테이킹 보상 컨트랙트
contract StakingRewards is ReentrancyGuard {
    // ... (생략)

    // 스테이킹 함수
    function stake(uint256 amount) external nonReentrant {
        // ... (생략)
    }

    // 보상 수령 함수
    function getReward() external nonReentrant {
        // ... (생략)
    }
}

💻 Next.js로 DeFi 대시보드 만들기

Next.js와 ethers.js 또는 thirdweb 같은 라이브러리를 사용하면 스마트 컨트랙트와 상호작용하는 동적인 프론트엔드를 쉽게 구축할 수 있습니다.

// components/DeFiDashboard.tsx
import { useState } from "react";
import { useContract, useAddress, useContractRead, Web3Button } from "@thirdweb-dev/react";
import { ethers } from "ethers";

export default function DeFiDashboard() {
  const address = useAddress();
  const [stakingAmount, setStakingAmount] = useState("");

  // thirdweb SDK를 사용하여 컨트랙트 인스턴스 생성
  const { contract: stakingContract } = useContract("YOUR_STAKING_CONTRACT_ADDRESS");

  // 컨트랙트의 public 변수 읽기
  const { data: stakedBalance } = useContractRead(stakingContract, "balances", [address]);
  const { data: earnedRewards } = useContractRead(stakingContract, "earned", [address]);

  return (
    <div className="max-w-4xl mx-auto p-4">
      <h1 className="text-2xl font-bold mb-6">My DeFi Dashboard</h1>
      
      {/* 스테이킹 섹션 */}
      <div className="bg-gray-800 rounded-lg p-6 text-white">
        <h2 className="text-xl font-semibold mb-4">Staking</h2>
        <p>Staked Balance: {stakedBalance ? ethers.utils.formatEther(stakedBalance) : "0"} STK</p>
        <p>Rewards: {earnedRewards ? ethers.utils.formatEther(earnedRewards) : "0"} RWD</p>
        
        <input
          type="number"
          value={stakingAmount}
          onChange={(e) => setStakingAmount(e.target.value)}
          className="w-full p-2 rounded text-black mt-4"
          placeholder="Amount to stake"
        />
        
        {/* Web3Button을 사용하여 트랜잭션 처리 */}
        <Web3Button
          contractAddress="YOUR_STAKING_CONTRACT_ADDRESS"
          action={(contract) => contract.call("stake", [ethers.utils.parseEther(stakingAmount)])}
          onSuccess={() => alert("Staking successful!")}
          onError={(error) => alert(`Error: ${error.message}`)}
          className="w-full bg-blue-500 py-2 rounded mt-2"
        >
          Stake Now
        </Web3Button>
      </div>
    </div>
  );
}

🔒 보안과 위험 관리

DeFi는 혁신적이지만, 스마트 컨트랙트 버그, 오라클 문제, 유동성 위험 등 다양한 위험이 따릅니다. 개발 시 다음 사항을 반드시 고려해야 합니다.

  • 재진입 공격 (Reentrancy) 방지: OpenZeppelin의 ReentrancyGuard 사용 또는 Checks-Effects-Interactions 패턴 적용.
  • 정수 오버플로우/언더플로우: Solidity 0.8.x 이상 버전 사용.
  • 접근 제어: Ownable 등 명확한 권한 관리 패턴 사용.
  • 코드 감사: 외부 전문 업체를 통한 스마트 컨트랙트 감사.
  • 테스트: 테스트 커버리지를 최대한 높여 엣지 케이스 검증.

DeFi는 빠르게 발전하는 분야이며, 높은 수익의 기회와 기술적 도전 과제를 동시에 제공합니다. 이 가이드를 통해 Next.js 개발자들이 DeFi 생태계에 더 쉽게 참여하고, 안전하고 혁신적인 서비스를 만드는 데 도움이 되기를 바랍니다.