Overview
이번 시간에는 온체인 상호작용을 포함시키고, 본격적으로 프로젝트의 규모를 키우기 위한 구조적 개선을 설명합니다. case5-onchain-sepolia 브랜치에는 많은 작은 변화들이 있지만, 이번 포스트에서는 큰 변화 세 가지만 설명합니다.
- 모노레포 구조로 전환했습니다.
- 스마트 컨트랙트를 실체 체인에 배포합니다.
- 작업 결과를 GitHub Pages로 배포합니다.
이 결과로 여러분들은 다음과 유사한 페이지 결과를 GitHub Pages로 배포하는 결과를 얻게 됩니다. 물론 더 다양한 상호작용을 위해서는 AWS, Vercel 같은 PaaS를 사용해볼 수 있겠지만, 본 시리즈에서는 정적인 사이트 안에서 구현 가능한 수준만 다루고자 합니다. (SWR 등의 라이브러리까지 활용하면 정적 사이트에서도 충분히 많은 걸 할 수 있어요!)
1. 모노레포 프로젝트
프로젝트의 구조적 변화는 실제 기능과는 전혀 관계 없습니다. 하지만 여러분들이 새롭게 바뀐 프로젝트의 구조를 이해하고 적응하기 위해서는 설명이 필요합니다. 모노레포(mono-repo)라는 하나의 레포를 사용하는 패턴입니다. 그림과 같이 여러 개의 작은 프로젝트를 하나의 레포에서 관리하는 것이죠. 언제 이런 패턴이 유용할까요? 개인적으로는 하나의 프로젝트가 서로 다른 다수의 프레임워크를 동시에 다루어야 하는 경우에 매우 적합하다고 생각합니다.
이전에 다룬 것처럼 프레임워크는 일련의 사용 방법을 사용자에게 강제하지만 대신에 편의성을 제공합니다. 예를 들어, 우리가 사용하고 있는 foundry는 기본적으로 시스템에 설치되어 동작하기 때문에, 도구를 사용할 때 터미널의 현재 경로가 명령의 결과에 큰 영향을 미칩니다. 만약 프로젝트 밖에서 명령을 호출하는 상황이 생긴다면 매우 불편하겠죠? 또 다른 장점은 개별 프로젝트의 기능을 독립적으로 선언하고 관리하기 쉽다는 점입니다. 이러한 점들은 개선 후의 package.json 파일을 보면 더 체감이 되리라 생각합니다.
알아둘 것은 2가지, pnpm run -r과 pnpm run --filter 구문입니다. -r은 상위 워크스페이스에서 모든 하위 워크스페이스에 재귀적으로 명령을 전파하기 위한 옵션입니다. 이와 달리 --filter 옵션은 특정 패턴에 맞는 워크스페이스에 대해서만 명령을 전파합니다. 이를 통해 별도의 경로 변경 또는 명령어를 전역에 고유하도록 설정하지 않아도 되고, 개별 워크스페이스는 내부적으로 필요한 명령만 정의하고, 필요한 상호 작용이 있다면 필터를 걸어 손쉽게 훅도 설정할 수 있습니다.
* 물론 기존의 패키지 매니저를 기반으로 모노레포 구조를 만들었지만, 최근에는 모노레포 전용 도구들도 많이 나오고 있는 추세입니다. Vercel의 Turborepo가 대표적인 JS/TS 기반 모노레토 도구이고, 그 밖에도 다양한 도구들이 있으니 참고하시면 좋을 것 같아요.
2. 온체인 컨트랙트 배포
이전까지는 항상 로컬 Anvil에 배포하고 그 결과를 받아오는 구조였다면, 이제는 NEXT_PHASE 환경 변수를 활용해서 개발 환경에서는 Anvil을 사용하고, 운영 환경에서는 Sepolia Testnet을 사용하게 됩니다. 참고로 NEXT_PHASE는 직접 설정해주지 않더라도, next 프레임워크에서 자동으로 설정해주는 변수이고, next/constants 모듈을 참조하면 보다 더 자세한 내용을 확인할 수 있습니다. 소스를 보니 foundry의 환경변수를 참조하고 있네요. 정확히 어떤 값들이 필요한지 확인해봅시다.
PRIVATE_KEY는 EOA의 개인 키를 의미합니다. SEPOLIA_RPC_URL은 사용하고자 하는 sepolia test network의 RPC URL을 의미하고, ETHERSCAN_API_KEY는 etherscan의 API를 활용하기 위해 발급된 API 키입니다. 이렇게만 설명하니 동어 반복처럼 느껴지나요? 😂 그럼 각 요소의 값을 어떻게 생성할 수 있는지 간단히 알아보겠습니다.
2-A. PRIVATE_KEY
MetaMask 시작하기 | MetaMask Help Center
The basics you need to set up MetaMask.
support.metamask.io
How to export an account's private key | MetaMask Help Center
Want to import your MetaMask wallet into a different wallet? Here's how to do it safely.
support.metamask.io
Metamask는 가장 범용적으로 사용되는 암호화폐 지갑 앱입니다. 첫 번째 링크를 참조하거나 인터넷에 검색해보면 쉽게 계정을 생성할 수 있어요. 그리고 개인키는 두 번째 링크에서 설명하는 방식으로 얻을 수 있습니다. 하지만 본질적으로는 계정 자체가 어떤 앱에 종속되는 형태는 아닙니다. 적합한 mnemonic만 있다면 BIP(Bitcoin Improvement Proposal): BIP-39, BIP-32, BIP-44에 따라 누구든 로컬에서 계정을 생성할 수 있습니다. 그런 이유로 아래 코드를 참고하시면 좋을 것 같아요. 혹시라도 본인 계정이 결과로 나오지 않는다면, AddressIndex를 1씩 증가시켜보면 됩니다. 물론 여기서 자세히 다루지는 않을테니 이런 내용이 있다는 것만 알고 계시면 될 것 같아요.
코드 전문
from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins, Bip44Changes
from bip_utils.utils.mnemonic import Mnemonic
from eth_utils import to_checksum_address, keccak
# 1. 니모닉 입력
raw_mnemonic = "turkey deposit bench narrow moon world flat display size measure wealth august"
mnemonic: Mnemonic = Mnemonic(raw_mnemonic.split())
# 2. 시드 생성
seed_bytes = Bip39SeedGenerator(mnemonic).Generate()
# 3. BIP44 이더리움 경로 설정: m/44'/60'/0'/0/0
bip44_wallet = Bip44.FromSeed(seed_bytes, Bip44Coins.ETHEREUM)
bip44_addr = bip44_wallet.Purpose().Coin().Account(0).Change(Bip44Changes.CHAIN_EXT).AddressIndex(0)
# 4. 개인키와 공개키 얻기
private_key = bip44_addr.PrivateKey().Raw().ToHex()
public_key_bytes = bip44_addr.PublicKey().RawUncompressed().ToBytes()[1:] # remove 0x04 prefix
# 5. 이더리움 주소 생성 (Keccak-256(public_key) 후 마지막 20바이트)
eth_address = to_checksum_address(keccak(public_key_bytes)[-20:])
if __name__ == '__main__':
print("🔐 Private Key:", private_key)
print("📬 Ethereum Address:", eth_address)
2-B. SEPOLIA_RPC_URL
RPC의 개념은 이전에 3번째 시리즈에서 잠시 소개한 적이 있으니 넘어가겠습니다.
RPC url을 얻는 방법도 다양합니다. 제가 예시로 공개한 url은 infura를 통해 개인적으로 발급받은 것이고, 굳이 그럴 필요 없이 이전에 소개한 ChainList에 공개된, 체인 ID가 11155111이고, https 프로토콜을 사용하는 어떤 url도 가능합니다. (https://1rpc.io/sepolia)
참고로 Sepolia testnet에 트랜잭션을 전송하려면 gas 비용이 필요합니다. 이는 일종의 수수료 개념인데요, 이 역시도 faucet을 통해 제공받는 다양한 경로가 있습니다. (구글에 sepolia faucet 검색)
2-C. ETHERSCAN_API_KEY
etherscan은 가장 널리 활용되는 Block Explorer입니다. 최근에는 오픈소스인 blockscout 쪽도 꽤나 인기있지만, 일단은 etherscan을 사용하고자 합니다. API_KEY가 필요한 것은 이 익스플로러에 컨트랙트 코드를 인증하기 위함입니다. 인증된 컨트랙트는 다음과 같이 소스 코드가 보이게 됩니다. 인증되지 않은 경우, 바이트코드 형태로 보여서 별도의 디컴파일러 사용 없이는 보기가 불편하거든요.
발급받는 방법은 간단합니다. 로그인 후 https://etherscan.io/apidashboard의 API Keys에서 발급받을 수 있습니다.
2-D. 스마트 컨트랙트 배포
환경변수가 잘 설정되어 있다면, 기존 배포 명령에 체인만 명시하는 것으로 손쉬운 배포가 가능합니다.
-f (--fork-url)옵션은 기본적으로 전체 RPC url을 모두 명시해야 하지만, foundry.toml 파일에 rpc_endpoints를 명시했다면 이처럼 alias를 활용해서 명령을 간소화 할 수 있습니다. 자세한 내용은 다음 링크를 참조하세요
3. GitHub Pages 배포
Next.js에서 작성된 front에서의 작업물은 기본적으로 SSR 방식으로 제공되지만, 설정에 따라 CSR, SSG도 가능합니다. 그 중 우리는 Static Site Generator 방식을 활용해서 정적 페이지를 만들고, 그 결과를 GitHub Pages로 배포해봅시다. 가장 중요한 설정은 다음 파일에 있습니다. 이 중 nextConfig.output = "export" 설정을 해야만 front/out 폴더에 정적 페이지 형태로 결과가 남습니다. start:ci (CI=true pnpm build && pnpm dlx serve@latest out) 명령을 통해 예상 결과물을 살펴볼 수 있도록 제공해두었으니 참고하세요!
Pages로 배포하기 위해선 포크한 레포에서 'GitHub Actions로 배포' 설정을 해주어야 합니다. 이후에는 .github/workflows/deploy.yaml에 명시된 배포 워크플로우를 트리거하면 끝입니다. actions/configure-pages@v5를 통해 baseUrl을 설정하는 디테일이나 캐싱 등 GitHub Actions 활용과 관련한 내용은 자세히 다루지 않겠습니다.
마치며
이번 시간을 끝으로 DApp에 필요한 정말 기본적인 요소들을 다루었습니다. 사실 DApp이라고 하면 온체인 데이터 및 컨트랙트 간의 상호작용이 더 필요한 것이 사실입니다. 하지만 이 시리즈의 시작 자체는 아무것도 모르는 분들을 위한 가이드 목적으로 시작했기 때문에 다소 제약이 많았던 것이 사실입니다. 실제 DApp과 관련된 다양한 기능 구현은 https://speedrunethereum.com/을 활용하여 연습해보시는 것을 추천드립니다. 다음 시리즈부터는 보다 DApp스러운 기능 또는 그러한 작업을 원활하게 수행하기 위한 템플릿 성격의 기능들을 추가해볼 예정입니다.
'Learn > UPSIDE' 카테고리의 다른 글
업사이드 2기 성과 공유회 후기 (0) | 2025.06.21 |
---|---|
업사이드 수료생 후기 및 지원 팁 (5) | 2025.06.01 |
업사이드 아카데미 3기 모집 (4) | 2025.05.29 |
8 - Next 기반 프로젝트 (1) | 2025.03.02 |
7 - Next 기반 프로젝트 (개념) (1) | 2025.02.28 |