본문으로 건너뛰기

← 블로그 · Projects

[케이스 스터디] PEPEBear - Solana 기반 커뮤니티 플랫폼의 온체인/오프체인 설계

#Next.js #TypeScript #Solana #Web3 #블록체인 #케이스 스터디

PEPEBear — Solana 기반 커뮤니티 플랫폼 케이스 스터디

제품명: PEPEBear 개발 기간: 2025.11 기술 스택: Next.js, TypeScript, React, Solana(@solana/web3.js, Wallet Adapter), Anchor Framework 포지셔닝: Solana 블록체인 위에 게이미피케이션을 얹은 커뮤니티 참여 플랫폼

PEPEBear는 토리스(Toris)가 Solana 생태계에서 설계·개발한 Web3 커뮤니티 플랫폼이다. 토큰 커뮤니티의 참여 활동을 포인트·레벨·업적이라는 게이미피케이션 구조로 조직하고, 온체인 데이터를 실시간에 가깝게 시각화한다. 이 글은 한 달이라는 짧은 개발 주기 안에서 내린 아키텍처 결정 — 특히 온체인/오프체인 데이터 분리와 커뮤니티 주도의 밸런스 조정 — 을 기록한다.

1. 문제 정의와 제품 가설

토큰 커뮤니티의 고질적인 문제는 참여 구조의 부재다. 커뮤니티 구성원의 활동은 대부분 채팅방 안에서 휘발되고, 기여도가 기록되거나 보상 구조로 연결되지 않는다. 홀더 현황 같은 온체인 데이터는 공개되어 있지만, 커뮤니티가 함께 볼 수 있는 형태로 가공되어 있지 않다.

여기서 제품 가설을 세웠다.

“커뮤니티 활동을 포인트·레벨·업적으로 구조화하고 온체인 데이터를 투명하게 보여주면, 일회성 방문자가 지속적인 참여자로 바뀐다.”

타깃은 Solana 기반 토큰 커뮤니티의 구성원이다. 설계 원칙으로 처음부터 분명히 한 것이 있다. 이 플랫폼의 가치는 자산의 가격이 아니라 참여의 기록과 데이터의 투명성에 있다. 따라서 제품의 모든 화면과 메커니즘은 커뮤니티 활동과 온체인 사실의 시각화에 집중하고, 그 외의 서사는 배제했다.

2. 제약 조건

  • 개발 기간 1개월: 2025년 11월 한 달 안에 동작하는 제품을 내야 했다. 아키텍처의 모든 선택이 이 시간 제약 아래에 있었다.
  • 트랜잭션 비용의 전가 구조: Web3에서는 상태 변경 비용(수수료)을 사용자가 지불한다. 기능 하나를 온체인에 올리는 결정이 곧 사용자에게 비용을 청구하는 결정이다.
  • 스마트 컨트랙트의 비가역성: 배포된 컨트랙트는 수정이 어렵다. 웹 서비스처럼 “일단 배포하고 고치는” 방식이 통하지 않는다.
  • RPC 인프라 제약: 자체 검증자 노드 없이 공용 RPC 엔드포인트에 의존해야 했고, 무료 엔드포인트에는 요청 제한이 있다.
  • 적대적 환경 가정: 포인트·랭킹이 걸린 시스템은 조작 시도를 전제로 설계해야 한다.

3. 기술 설계와 트레이드오프

3.1 스택 선택: Solana + Anchor + Next.js

체인은 Solana를 선택했다. 결정 근거는 두 가지다. 트랜잭션 확정이 빠르고 수수료가 낮다는 것 — 게이미피케이션처럼 상호작용 빈도가 높은 제품에서 건당 비용과 대기 시간은 UX를 직접 결정한다. 컨트랙트 계층은 Anchor Framework를 사용했다. 원시 Solana 프로그램 개발 대비 계정 검증과 직렬화의 보일러플레이트를 줄여주므로, 1개월 일정에서 검증 로직의 본질에 집중할 수 있었다.

프론트엔드는 Next.js + TypeScript다. @solana/wallet-adapter-react가 React 생태계에 잘 통합되어 있어 Phantom, Solflare 등 복수 지갑 지원을 표준화된 인터페이스로 처리할 수 있다는 점이 컸다.

3.2 온체인/오프체인 데이터 분리 — 이 프로젝트의 핵심 결정

가장 중요한 아키텍처 결정은 무엇을 체인에 올리고 무엇을 올리지 않을 것인가였다.

모든 상태를 온체인에 두는 설계는 이론적으로 가장 투명하지만, 두 가지 이유로 기각했다. 첫째, 모든 포인트 적립이 트랜잭션이 되면 사용자가 활동할 때마다 수수료를 내야 한다. 둘째, 트랜잭션 확정 대기 시간이 모든 인터랙션에 끼어들어 UX가 무너진다. 반대로 모든 것을 오프체인에 두면 빠르고 저렴하지만, 조작 불가능성이라는 블록체인의 존재 이유가 사라진다.

결론은 신뢰 요구 수준에 따른 분리였다.

데이터저장 위치근거
검증이 필요한 핵심 상태온체인 (스마트 컨트랙트)조작 불가능성이 가치의 원천
부가적 활동 데이터, UI 상태오프체인비용·속도 우선, 조작 유인 낮음

기준은 단순하다. “이 데이터가 조작되면 다른 사용자가 손해를 보는가?” 그렇다면 온체인에서 검증하고, 아니라면 오프체인에 둔다. 조작 방지 검증 로직은 클라이언트가 아니라 스마트 컨트랙트에 구현했다. 클라이언트 코드는 누구나 열어볼 수 있으므로, 신뢰 경계는 반드시 컨트랙트 안쪽에 있어야 한다.

3.3 지갑 연동 — Web3의 첫 관문을 이탈 지점으로 만들지 않기

Web3 제품의 온보딩은 지갑 연결에서 시작하고, 상당수 이탈도 여기서 발생한다. 연결 과정이 복잡하거나 실패가 불친절하면 사용자는 돌아오지 않는다.

import { useWallet } from '@solana/wallet-adapter-react';

const { publicKey, connect, disconnect } = useWallet();

const handleConnect = async () => {
  try {
    await connect();
    // 연결 성공: publicKey?.toBase58()로 주소 확보
  } catch (error) {
    // 사용자 거부, 네트워크 오류 등을 구분해 안내
  }
};

Wallet Adapter로 복수 지갑(Phantom, Solflare)을 단일 인터페이스로 다루되, 설계의 무게는 에러 경로에 실었다. 지갑 연결 실패는 단일 케이스가 아니다 — 사용자가 승인 창에서 거부한 경우, 네트워크 오류, 지갑 미설치가 각각 다른 안내를 요구한다. 실패 사유를 구분해 사용자 언어로 표시하는 것이, 성공 경로를 구현하는 것만큼의 코드량을 차지했다.

3.4 온체인 데이터 추적과 RPC 전략

홀더 랭킹과 잔고 표시를 위해 Solana RPC로 온체인 데이터를 조회한다.

import { Connection, PublicKey } from '@solana/web3.js';

const connection = new Connection('https://api.mainnet-beta.solana.com');

const getTokenBalance = async (walletAddress: string) => {
  const publicKey = new PublicKey(walletAddress);
  const balance = await connection.getTokenAccountBalance(publicKey);
  return balance.value.uiAmount;
};

여기서의 트레이드오프는 실시간성 대 RPC 비용이다. WebSocket 구독과 폴링을 비교한 뒤, 랭킹처럼 초 단위 정밀도가 필수적이지 않은 화면은 주기적 폴링 + 캐싱으로 설계했다. 공용 RPC의 요청 제한 안에서 안정적으로 동작하는 것이 초실시간성보다 우선이었다. 캐싱은 동일 데이터에 대한 중복 조회를 흡수해 RPC 예산을 아끼는 역할을 겸한다.

랭킹 변화는 애니메이션으로 시각화했다. 순위가 바뀌는 순간을 눈에 보이게 만드는 것이 커뮤니티 화면의 체류 가치를 높인다고 판단했기 때문이다. 다만 애니메이션은 렌더링 비용을 수반하므로, 변화가 감지된 항목만 애니메이션을 트리거하는 방식으로 최적화했다.

3.5 트랜잭션 파이프라인 — 실패를 정상 경로로 설계

블록체인 트랜잭션은 실패가 예외가 아니라 일상이다. 네트워크 혼잡, 지연, 사용자의 서명 거부까지 — 트랜잭션 파이프라인은 실패를 정상 상태의 하나로 취급하도록 설계했다.

  • 상태 추적: 제출부터 확정까지 트랜잭션 상태를 추적해 사용자에게 현재 단계를 보여준다. “버튼을 눌렀는데 아무 일도 없는” 구간을 없애는 것이 목적이다.
  • 재시도 로직: 일시적 네트워크 문제로 인한 실패는 자동 재시도로 흡수한다.
  • 에러 번역: RPC가 반환하는 원시 에러를 사용자가 행동할 수 있는 메시지로 번역한다.

보안 측면에서는 서명 요청의 투명성을 원칙으로 삼았다. 사용자가 트랜잭션에 서명하는 순간은 Web3에서 가장 민감한 신뢰의 순간이다. 무엇에 서명하는지 명확한 정보를 제공하고, 불필요한 서명 요청을 만들지 않았다. 컨트랙트는 메인넷 배포 전 테스트넷에서 충분한 검증을 거쳤다 — 비가역적 배포 앞에서 테스트는 비용이 아니라 보험이다.

4. 커뮤니티와의 검증과 조율

Web3 제품의 특수성은 사용자가 곧 커뮤니티라는 점이다. PEPEBear의 게이미피케이션 설계는 책상에서 완성되지 않았고, 커뮤니티와의 반복 루프를 통해 조정됐다.

4.1 게임 밸런스 — 설계자의 가설과 참여자의 현실

포인트 적립량, 레벨 구간, 업적 조건 같은 밸런스 수치는 처음에는 설계자의 가설일 뿐이다. 실제 사용자 테스트를 돌려보니 가설과 현실의 간극이 드러났다. 진행이 특정 구간에서 지루해지거나, 특정 활동의 보상이 체감상 과하거나 부족한 지점들이다. 이런 것은 로그만으로는 보이지 않고, 참여자의 피드백으로만 확인된다.

사용자 테스트 결과를 근거로 밸런스를 조정하는 루프를 돌렸다. 여기서 얻은 원칙: 게이미피케이션의 수치는 코드가 아니라 데이터로 취급하라. 조정이 잦을 것을 전제로, 밸런스 값을 쉽게 바꿀 수 있는 구조로 두는 것이 맞다. 그리고 이것이 온체인/오프체인 분리 결정과 맞물린다 — 자주 조정해야 하는 밸런스 로직을 비가역적인 컨트랙트에 새겨 넣었다면 조정 루프 자체가 불가능했을 것이다.

4.2 조작 시도를 전제로 한 조율

랭킹과 포인트가 걸린 시스템에서 일부 사용자는 반드시 지름길을 찾는다. 이것은 비난할 일이 아니라 설계에 반영할 입력이다. 클라이언트에서 조작 가능한 경로를 점검하고, 핵심 검증을 스마트 컨트랙트로 옮기는 작업은 “커뮤니티가 시스템을 어떻게 사용(및 오용)하는가”에 대한 관찰의 결과였다.

4.3 신뢰는 투명성에서 나온다

커뮤니티 플랫폼에서 랭킹의 권위는 데이터의 출처에서 나온다. 홀더 랭킹을 운영자가 입력하는 값이 아니라 온체인 데이터에서 직접 읽어 오는 구조로 만든 것은, “운영자도 조작할 수 없는 순위표”라는 신뢰를 커뮤니티에 제공하기 위해서였다. Web3 커뮤니티는 검증 가능성에 민감하며, 그 감각에 맞는 데이터 파이프라인이 곧 제품의 신뢰 자산이 된다.

5. 운영과 결과

PEPEBear는 Vercel에 배포되어 운영 중이다(pepe-bear.vercel.app). 1개월의 개발 주기 안에서 지갑 연동, 게이미피케이션 시스템(포인트·레벨·업적), 실시간 온체인 데이터 추적, 스마트 컨트랙트 연동, 애니메이션 기반 홀더 랭킹까지 플랫폼의 핵심 루프를 완성해 출시했다.

남은 과제도 명확하다.

  • 컨트랙트 최적화: 현재 컨트랙트에는 연산 최적화 여지가 남아 있다. 불필요한 연산을 줄여 수수료 부담을 낮추는 것이 다음 단계다.
  • 온보딩 UX: 지갑이라는 개념 자체가 진입 장벽인 사용자층이 여전히 존재한다. Web3 특유의 마찰을 더 줄이는 것이 과제다.
  • 커뮤니티 기능 확장: 참여 루프를 더 두텁게 만들 기능들 — 다만 무엇을 온체인 검증 대상으로 삼을지의 기준은 유지한 채로.

6. 재사용 가능한 교훈

  1. 신뢰 요구 수준으로 저장 위치를 정하라. “조작되면 남이 손해 보는가?”가 온체인/오프체인 분리의 기준이다. 전부 온체인은 비용으로, 전부 오프체인은 신뢰로 무너진다.
  2. 자주 바뀔 로직을 비가역적인 곳에 넣지 마라. 밸런스 수치는 조정 루프를 전제로 오프체인에, 검증 규칙은 컨트랙트에.
  3. 실패를 정상 경로로 설계하라. 트랜잭션 상태 추적, 재시도, 에러 번역은 부가 기능이 아니라 Web3 UX의 본체다.
  4. 신뢰 경계는 클라이언트 바깥에. 사용자가 코드를 읽고 변조할 수 있는 곳에는 검증을 두지 않는다.
  5. 커뮤니티는 QA 조직이자 설계 파트너다. 밸런스의 간극도, 조작 경로도, 신뢰의 기준도 커뮤니티 관찰에서 나왔다. 피드백 루프를 제품 개발 프로세스의 일부로 공식화할 가치가 있다.

링크