← 블로그 · Projects
[케이스 스터디] CryptoTrade.gg - 온체인 트레이딩 이력 조회 서비스의 설계와 트레이드오프
#TypeScript #Next.js #블록체인 #데이터 시각화 #케이스 스터디 #Toris
CryptoTrade.gg — 온체인 트레이딩 이력 조회 서비스 케이스 스터디
제품명: CryptoTrade.gg 개발 기간: 2025.02 – 2025.04 (약 2개월) 기술 스택: TypeScript, Next.js, Chart.js, Recharts 포지셔닝: 지갑 주소 기반 트레이딩 이력 조회 및 시각화 서비스
CryptoTrade.gg는 토리스(Toris)가 설계·개발·운영한 온체인 데이터 제품이다. 게임 전적 조회 서비스가 플레이어의 기록을 객관적 데이터로 보여주듯, 트레이더의 온체인 활동을 지갑 주소 하나로 조회하고 시각화하는 것을 목표로 했다. 이 글은 그 과정에서 내린 기술적 의사결정과, 사용자 피드백이 제품을 어떻게 바꿨는지를 기록한다.
1. 문제 정의와 제품 가설
암호화폐 커뮤니티에는 구조적인 정보 비대칭이 있다. 트레이더들은 커뮤니티에서 자신의 성과를 공유하지만, 제3자가 그 주장을 검증할 수단이 마땅치 않다. 스크린샷은 조작이 쉽고, 거래소 내역은 비공개다. 반면 온체인 거래 기록은 누구나 열람할 수 있는 공개 데이터인데도, 원시 트랜잭션을 사람이 읽을 수 있는 “전적”으로 바꿔주는 도구는 부족했다.
여기서 제품 가설을 세웠다.
“트레이더의 주장(스크린샷)이 아니라 블록체인에 기록된 사실을 조회 화면으로 만들면, 커뮤니티의 검증 비용이 낮아진다.”
타깃 사용자는 두 부류였다. 첫째, 다른 트레이더의 실제 성과를 확인하고 싶은 커뮤니티 참여자. 둘째, 자신의 거래 이력을 객관적으로 복기하고 싶은 트레이더 본인. 중요한 원칙으로, 이 서비스는 특정 자산이나 거래를 권유하지 않는다. 이미 공개된 데이터를 읽기 쉽게 만드는 조회 도구로 제품의 경계를 명확히 그었다.
2. 제약 조건
1인 스튜디오의 2개월짜리 프로젝트라는 점이 모든 설계를 지배했다.
- 인프라 예산: 자체 블록체인 노드를 운영할 여력이 없었다. 노드 운영은 지속적인 서버 비용과 관리 부담을 요구하는데, 검증 단계의 제품에는 과도한 투자였다.
- 멀티체인 요구: 이더리움, BSC 등 체인마다 주소 형식·데이터 구조·API가 다르다. 처음부터 모든 체인을 지원할 수 없었다.
- 데이터 볼륨: 활발한 지갑 하나가 수천 건의 트레이드를 갖는다. 브라우저에서 이를 그대로 렌더링하면 사용성이 무너진다.
- 데이터 신선도: 블록체인은 계속 갱신되므로, 조회 시점의 데이터가 곧 낡은 데이터가 된다.
3. 기술 설계와 트레이드오프
3.1 스택 선택: Next.js + TypeScript
프론트엔드와 데이터 조회 레이어를 하나의 코드베이스로 다루기 위해 Next.js를 선택했다. API 라우트로 외부 데이터 소스를 감싸면 클라이언트에 API 키를 노출하지 않고 응답을 서버 측에서 캐싱할 수 있다. TypeScript는 선택이 아니라 필수였다. 체인마다 다른 트랜잭션 스키마를 다루는 코드에서, 타입 정의는 데이터 구조에 대한 살아있는 문서 역할을 했다.
3.2 데이터 수집: 자체 노드 대신 외부 API + 캐싱
첫 번째 큰 트레이드오프는 데이터 소스였다. 선택지는 세 가지였다.
| 선택지 | 장점 | 단점 |
|---|---|---|
| 자체 노드 운영 | 제한 없는 조회, 완전한 통제 | 서버 비용, 운영 부담, 2개월 일정에 부적합 |
| 외부 데이터 API | 즉시 시작, 정제된 데이터 | 요청 제한(rate limit), 외부 의존성 |
| 하이브리드 | 유연함 | 초기 복잡도 과다 |
외부 API를 택하고, 요청 제한이라는 단점을 캐싱 레이어로 상쇄했다. 같은 지갑에 대한 반복 조회는 캐시에서 응답하고, 원본 조회는 주기적 갱신 스케줄로 묶었다. “노드를 직접 운영해야 진짜 Web3”라는 관점도 있지만, 검증 단계의 제품에서 인프라는 가설 검증에 필요한 최소한이면 충분하다는 것이 우리의 판단이었다.
// 데이터 조회 레이어: 외부 소스를 감싸고 정규화한다
const fetchTradeHistory = async (walletAddress: string) => {
const response = await fetch(
`https://api.example.com/trades?address=${walletAddress}`
);
const data = await response.json();
return processTradeData(data); // 체인별 원시 데이터 → 공통 트레이드 모델
};
핵심은 processTradeData에 있다. 체인별 원시 응답을 공통 트레이드 모델로 정규화하는 이 계층 덕분에, 시각화 코드는 어느 체인의 데이터인지 몰라도 된다. 이후 체인을 추가할 때 수정 범위가 이 어댑터 한 곳으로 제한된다.
3.3 입력 검증: 신뢰 경계의 첫 관문
지갑 주소는 이 서비스의 유일한 사용자 입력이자 외부 API 호출의 트리거다. 잘못된 주소로 외부 API를 호출하는 것은 요청 한도 낭비이고, 모호한 실패는 사용자 이탈로 이어진다. 그래서 검증을 클라이언트 단에서 먼저 수행했다.
// 체인별 주소 형식 검증 — 외부 요청 전에 실패를 조기 차단한다
const isValidAddress = (address: string, chain: string): boolean => {
if (chain === 'ethereum') {
return /^0x[a-fA-F0-9]{40}$/.test(address);
}
// 체인별 검증 로직 확장 지점
return false;
};
체인마다 주소 형식이 다르므로 검증도 체인별로 분기했고, 체크섬 검증으로 오타로 인한 “존재하지만 의도하지 않은 주소” 조회를 줄였다. 검증 실패 시에는 “잘못된 주소입니다”가 아니라 어느 체인 형식에 맞지 않는지를 알려주는 에러 메시지를 제공했다. 입력 검증은 보안 조치인 동시에 UX 설계라는 것을 이 지점에서 배웠다.
3.4 시각화와 렌더링 성능
수익/손실 추이, 거래량, 승률 통계, 시간대별 거래 패턴 네 가지 뷰를 Chart.js와 Recharts로 구현했다. 여기서 두 번째 트레이드오프가 나왔다. 정확성 대 반응성이다.
수천 건의 트레이드를 가진 지갑에서 모든 데이터 포인트를 차트에 그리면 렌더링이 눈에 띄게 느려진다. 세 가지 기법을 조합했다.
- 차트 샘플링: 추이 차트에는 샘플링된 데이터만 표시한다. 추세를 읽는 용도의 차트에서 개별 포인트의 정밀도는 우선순위가 낮다고 판단했다.
- 페이지네이션: 상세 거래 목록은 나눠서 로드한다. 여기서는 반대로 정확성이 우선이므로 샘플링하지 않는다.
- 가상 스크롤: 긴 목록의 DOM 노드 수를 화면에 보이는 만큼으로 제한한다.
같은 데이터라도 “추세를 보는 화면”과 “건별 기록을 확인하는 화면”의 요구가 다르다는 것 — 시각화 성능 문제는 결국 화면의 목적을 정의하는 문제였다.
3.5 데이터 신선도 전략
실시간성에 대해서는 의도적으로 보수적인 선택을 했다. WebSocket 기반 실시간 스트리밍을 검토했지만, 트레이딩 이력 조회는 초 단위 실시간성이 필수적인 유스케이스가 아니다. 대신 두 단계로 설계했다.
- 주기적 백그라운드 갱신: 스케줄링된 업데이트로 데이터를 최신에 가깝게 유지
- 수동 새로고침: 사용자가 “지금 시점”의 데이터를 원할 때 직접 갱신을 트리거
WebSocket은 향후 실시간 요구가 검증되면 도입하는 것으로 남겨뒀다. 필요가 증명되지 않은 실시간 인프라를 미리 짓지 않는 것도 설계 결정이다.
4. 사용자·커뮤니티와의 검증
만들어 놓고 끝나는 도구가 아니라 트레이더들이 실제로 쓰는 도구가 되려면, 커뮤니티의 회계 감각과 제품의 계산 로직이 일치해야 했다. 초기 사용자와 커뮤니티 피드백에서 세 가지 조율 지점이 드러났다.
4.1 “이 수익률 계산이 내 계산과 다르다” — 가스비 논쟁
가장 뜨거웠던 피드백은 손익 계산에서 가스비를 어떻게 다룰 것인가였다. 트레이더마다 회계 방식이 달랐다. 가스비를 비용으로 포함해 “실질 수익”을 봐야 한다는 입장과, 거래 자체의 성과와 네트워크 비용은 분리해서 봐야 한다는 입장이 갈렸다.
우리가 어느 한쪽을 정답으로 강제하면 반대쪽 사용자에게는 “계산이 틀린 서비스”가 된다. 결론은 판단을 제품이 내리지 않고 사용자에게 돌려주는 것이었다. 가스비를 별도 항목으로 표시하고 포함 여부를 사용자가 선택하게 했다. 데이터 제품에서 논쟁적인 집계 기준은 옵션으로 제공하는 것이 정직하다는 원칙을 여기서 얻었다.
4.2 “이건 트레이드가 아닌데?” — 트랜잭션 분류의 정확성
온체인 활동은 단순 매수/매도만 있는 게 아니다. 토큰 스왑, 유동성 공급, 컨트랙트 상호작용이 뒤섞여 있다. 초기 버전이 이런 트랜잭션을 뭉뚱그려 처리하자, 실제 지갑으로 조회해 본 사용자들로부터 “유동성 공급이 거래로 잡혀서 승률이 왜곡된다”는 유형의 지적이 나왔다.
이 피드백이 트랜잭션 분류 체계를 만들게 했다. 트레이드 타입을 분류하고 타입별로 다른 집계 로직을 적용했다. 개발자가 상상한 데이터 모델과 실사용자의 지갑에 실제로 담긴 데이터 사이의 간극은, 실제 지갑으로 조회해 보는 사용자들만이 찾아줄 수 있었다.
4.3 “언제 기준 가격인가” — 시점 문제
손익은 어느 시점의 가격으로 평가하느냐에 따라 달라진다. 시간대별 가격 변동을 무시하면 계산 결과가 커뮤니티의 체감과 어긋난다. 실시간 가격 API를 연동해 거래 시점 기준의 손익을 계산하도록 개선했다. 이 역시 “숫자가 이상하다”는 사용자 감각에서 출발한 개선이었다.
세 사례의 공통점은 이렇다. 온체인 데이터 제품의 신뢰는 기능의 화려함이 아니라 계산의 정합성에서 나온다. 그리고 그 정합성의 기준은 개발자가 아니라 커뮤니티가 쥐고 있다.
5. 운영과 결과
CryptoTrade.gg는 Vercel에 배포되어 운영 중이다(tradinggg.vercel.app). 2개월의 개발 기간 동안 지갑 주소 조회, 멀티체인 검증, 4종의 시각화 뷰, 캐싱 기반 데이터 파이프라인까지 검증 단계 제품의 골격을 완성했다.
현재의 한계도 명확히 인지하고 있다.
- 체인 커버리지: 지원 체인이 제한적이다. 어댑터 계층 덕분에 확장 비용은 낮춰뒀지만, 체인별 데이터 소스 확보가 선행 과제다.
- 분석 깊이: 현재는 기록의 시각화에 집중되어 있다. 거래 패턴 분석, 리스크 지표 같은 고급 분석은 다음 단계다.
- 커뮤니티 기능: 조회 도구를 넘어 트레이더들이 전적을 공유·비교하는 공간으로 확장할 여지가 있다. 다만 이는 검증 도구라는 제품 정체성을 지키는 선에서 설계되어야 한다.
6. 재사용 가능한 교훈
이 프로젝트에서 얻은 원칙들은 이후 토리스의 다른 제품에도 적용되고 있다.
- 정규화 계층에 투자하라. 이질적인 외부 데이터 소스는 어댑터에서 공통 모델로 변환한다. 확장 비용이 그 한 곳에 갇힌다.
- 논쟁적인 집계 기준은 옵션으로. 가스비 포함 여부처럼 사용자마다 정답이 다른 기준을 제품이 독단하면 신뢰를 잃는다.
- 인프라는 가설 검증에 필요한 최소한으로. 자체 노드 대신 외부 API + 캐싱. WebSocket 대신 스케줄 갱신 + 수동 새로고침. 필요가 증명되면 그때 올린다.
- 같은 데이터, 다른 화면 요구. 추세용 차트는 샘플링하고, 기록용 목록은 페이지네이션한다. 성능 최적화는 화면의 목적 정의에서 시작한다.
- 데이터 제품의 QA는 실사용자의 데이터로. 트랜잭션 분류 오류는 개발자의 테스트 지갑이 아니라 커뮤니티의 실제 지갑이 찾아냈다.