블록체인 기반 캐릭터 IP 증권형 토큰(STO) 거래 플랫폼
IPiece는 캐릭터 IP를 증권형 토큰(STO)으로 발행하여, 개인 투자자들이 소액으로 유망 캐릭터 IP에 투자하고 배당 수익을 얻을 수 있는 STO 플랫폼입니다.
- 1. 프로젝트 소개
- 2. 팀원 및 역할
- 3. 주요 기능
- 4. 기술 스택 및 선정 이유
- 5. ERD 및 프로젝트 구조
- 6. 페이지별 주요 기능
- 7. 설치 및 실행
- 8. 트러블슈팅
- 9. 관련 링크
기존 캐릭터 IP 투자 시장은 높은 진입 장벽으로 인해 대형 투자자와 기관에게만 기회가 열려 있었습니다. IPiece는 블록체인 기술을 활용해 캐릭터 IP를 증권형 토큰으로 조각(Piece)내어, 누구나 소액으로 간편하게 투자할 수 있는 민주화된 투자 환경을 제공하는 것을 목표로 합니다.
- 총 개발 기간: 2025.10.21 ~ 2025.12.5 (7주)
![]() 고태우 PM / Infra @kohtaewoo |
![]() 이조은 PL / FE / BE @LeeJoEun-01 |
![]() 강한솔 FE / BE @kkangsol |
![]() 이기현 Blockchain / BE / Infra @GIHYUN-LEE |
![]() 황병길 FE / Blockchain / Infra @Gill010147 |
- 🏦 On-premise Blockchain (Besu): 캐릭터 IP 증권형 토큰(STO) 발행·소유권 관리·배당 이력을 온체인으로 검증/기록합니다.
- 📈 공모(Primary Offering): 신규 캐릭터 IP 청약을 처리하고, 온체인/오프체인 상태를 동기화합니다.
- 🧮 호가 매칭 엔진: 주식 호가창과 유사한 주문북 기반 매칭으로 2차 거래를 실시간 체결합니다.
- 🔄 WebSocket 실시간 반영: 주문 체결·호가 변경·체결 내역을 웹소켓으로 브로드캐스트해 실시간 UI 갱신을 보장합니다.
- 📊 포트폴리오/성과 리포트: 보유 자산, 평가 손익, 배당 내역을 통합 집계·시각화합니다.
- 💳 가상계좌·정산: KRW 입출금과 거래/배당 내역을 투명하게 조회하고 정산 트랜잭션을 관리합니다.
♥️ 관심 목록 알림: 관심 IP 가격 변동을 모니터링하고 실시간 알림을 제공합니다.
| 기술 | 선정 이유 |
|---|---|
| Private Blockchain 기반으로 STO 서비스에서 필요한 투명성과 보안성을 동시에 충족합니다. | |
| Java 기반 블록체인 연동 라이브러리로, Spring Boot 환경과 자연스럽게 통합됩니다. |
| 기술 | 선정 이유 |
|---|---|
| 초기 API 테스트 및 시나리오 검증에 사용해 실제 동작을 빠르게 검증할 수 있습니다. | |
| API 문서를 자동 생성하며, 테스트 가능한 인터페이스를 제공해 협업 효율이 증가합니다. |
본 프로젝트는 Domain-Oriented Feature-Based Structure(도메인 중심 기능 구조)를 채택했습니다.
기능 영역(admin, user, market, offering 등)별로 패키지를 구성하여 유지보수성과 확장성을 극대화하고,
DDD(Domain-Driven Design)의 모듈 설계 방식과도 일관성을 맞추었습니다.
IPiece-server/
├── build.gradle
└── src
├── main
│ └── java/com/masterpiece/IPiece
│ ├── admin # 관리자 (공모 승인, 배당 관리, 거래 활성화)
│ ├── blockchain # 온체인 연동 (KRWT, TokenFactory, 지갑, 트랜잭션)
│ ├── common # 공통 모듈 (엔티티, 예외, 응답, JWT 유틸 등)
│ ├── config # 전역 설정 (Security, Swagger, WebSocket 등)
│ ├── dividends # 배당 등록/실행
│ ├── favorite # 즐겨찾기
│ ├── integration # 외부 연동 (Besu, SMS, S3)
│ ├── investment # 투자 API
│ ├── main # 메인 페이지 데이터
│ ├── market # 2차 거래 (매칭엔진, 차트, 오더북)
│ ├── mypage # 자산/계좌/거래내역/포트폴리오
│ ├── offering # 1차 공모 (진행률)
│ └── user # 회원가입/인증/JWT/블랙리스트
└── test # 컨트롤러/서비스 단위·통합 테스트
각 도메인 패키지(blockchain, dividends, market 등)는 공통적으로 api, application, domain, infra 하위 레이어 구조를 따릅니다.
-
- HTTP 진입점 레이어로, Controller·Request/Response DTO를 포함합니다.
- 프론트엔드·외부 시스템과 직접 맞닿는 REST API 인터페이스 역할을 합니다.
-
- 유스케이스를 구현하는 서비스 레이어로, 트랜잭션 단위의 비즈니스 흐름을 오케스트레이션합니다.
- 도메인 모델을 활용해 “무엇을 할지” 시나리오를 조합하지만, 상태는 갖지 않도록 설계합니다.
-
- 엔티티, 값 객체, 도메인 서비스 등 핵심 비즈니스 규칙을 담는 레이어입니다.
- 비즈니스 불변 조건과 도메인 규칙을 통해 시스템의 개념적 중심을 구성합니다.
-
- DB(JPA), 외부 API, 메시지 브로커, 스토리지 등 기술 의존 구현체를 모아두는 레이어입니다.
- 인프라 세부사항을 캡슐화해, application·domain이 인터페이스만 바라보고 동작하게 합니다.
아래는 실제 서비스 플로우 기준으로 구성한 페이지별 상세 기능 설명 + 사용 API 목록입니다.
모든 화면은 Swagger 기반 REST API를 기반으로 동작합니다.
| 제목 | 화면 캡처 |
|---|---|
| 메인 페이지 | ![]() |
GET /v1/main/home— 메인 페이지 데이터
- 공모, 거래 중인 캐릭터 IP 4개씩 노출
- 로그인 여부에 따라 Header 구성
- 공모·거래 상세 페이지로 이동
| 제목 | 화면 캡처 |
|---|---|
| 로그인 페이지 | ![]() |
POST /v1/auth/token/loginPOST /v1/auth/token/logoutPOST /v1/auth/token/refresh
| 제목 | 화면 캡처 |
|---|---|
| 본인인증 페이지 | ![]() |
| 본인인증 SMS | ![]() |
POST /v1/auth/otp/start?phone=POST /v1/auth/otp/verify?phone=&code=&birth=
| 제목 | 화면 캡처 |
|---|---|
| 회원가입 페이지 | ![]() |
GET /v1/signup/duplicate-check?id=POST /v1/signup/info(multipart/form-data)
| 제목 | 화면 캡처 |
|---|---|
| 공모 리스트 | ![]() |
| 공모 상세 페이지 | ![]() |
| 공모 참여 | ![]() |
GET /v1/offerings?cursor=— 공모 리스트GET /v1/offerings/{product_id}/detail— 공모 상세GET /v1/offerings/{product_id}/purchase/validate?quantity=— 청약 가능 여부POST /v1/offerings/{product_id}/purchase— 공모 참여
- 무한스크롤 기반 리스트
- 공모 가격/기간/진행률 표시
- 잔액·기간·공모 가능 여부 검증
- Progress Rate 실시간 업데이트
- 공모 종료 시 상태 자동 전환
| 제목 | 화면 캡처 |
|---|---|
| 거래 리스트 | ![]() |
| 거래 상세 – 차트/호가 | ![]() |
| 거래 상세 – 공시/소개 | ![]() |
GET /v1/market/{product_id}/detailsGET /v1/market/{product_id}/chart?interval=GET /v1/market/{product_id}/ordersPOST /v1/market/{product_id}/buyPOST /v1/market/{product_id}/sellGET /v1/market/{product_id}/orders/pending
- 실시간 차트 (라인/캔들)
- 실시간 호가창 (매수/매도)
- 지정가 매수/매도 주문
- Idempotency-Key 기반 중복 주문 방지
- 공시/프로젝트 소개 탭 제공
- 체결/미체결 내역 조회
| 제목 | 화면 캡처 |
|---|---|
| MY HOME – 자산 있음 | ![]() |
| MY HOME – 자산 없음 | ![]() |
| 내 계좌 화면 | ![]() |
GET /v1/mypage/myhome
- 총 자산 / 보유 자산 / 평가손익
- 포트폴리오 파이 차트
- 공모참여 내역 + 보유 종목 통합 조회
- 실시간 가격 반영
- 계좌 생성성
POST /v1/mypage/account— 가상계좌 생성GET /v1/mypage/account?date_from=&date_to=— 입출금 조회
- KRW 잔액/출금 가능 금액 표시
- 입금/출금/보류금(pending) 조회
- 거래 시 자동 저널 기록 반영
GET /v1/mypage/account/journals
- 매수/매도/입금/출금/배당 전체 기록 조회
- 날짜 범위 필터 제공
| 제목 | 화면 캡처 |
|---|---|
| 관심 화면 | ![]() |
POST /v1/products/{product_id}/favoriteDELETE /v1/products/{product_id}/favoritePOST /v1/favorites/statusGET /v1/mypage/favorites
- 즐겨찾기한 캐릭터 리스트 조회
- 실시간 가격 반영
- 클릭 시 거래 상세 페이지 이동
| 제목 | 화면 캡처 |
|---|---|
| Admin 상품 조회 | ![]() |
| Admin 상품 생성 | ![]() |
| Admin 공모 -> 2차거래 | ![]() |
| Admin 배당 관리 | ![]() |
| 컨트랙트/트랜잭션 모니터링 | ![]() |
POST /v1/admin/products— 상품 생성 (이미지 포함)POST /v1/admin/products/{productId}/enable-offering— 공모 오픈 승인
- 상품 기본 정보 + 이미지 업로드
- 공모 일정·가격·수량 설정
- 공모 OPEN 승인 처리
- 공모 종료 후 자동 TRADE 전환 지원
POST /v1/admin/dividends— 배당 등록/업데이트GET /v1/admin/dividends— 배당 리스트GET /v1/admin/dividends/{dividendId}/payouts— 지급 내역
- 배당 등록/수정
- 배당 실행 → 온체인 트랜잭션 처리
- 지급 성공/실패 내역 조회
- 배당 대상자 수·총액 자동 계산
GET /v1/admin/blockchain/contracts— 전체 컨트랙트 정보GET /v1/admin/blockchain/transactions— 온체인 트랜잭션 로그GET /v1/admin/blockchain/tokens— 발행된 모든 토큰 리스트POST /v1/blockchain/wallet/krwt/mint— KRWT 민트POST /v1/blockchain/wallet/krwt/burn— KRWT 소각
- KRWT 민팅/소각
- TokenFactory 기반 프로젝트 토큰 생성
- 트랜잭션 상태 모니터링 (PENDING/FAILED)
- Gas/Block 등 상세 메타데이터 조회
- Holder 변동 추이 확인
git clone https://github.com/your-org/IPiece-backend.git
cd IPiece-backendenv 파일에 DB, Redis, Blockchain RPC, AWS 자격증명 등을 입력합니다.
cp .env.example .env./mvnw spring-boot:run
IPiece 백엔드를 개발하며 마주한 주요 이슈와 해결 과정을 정리했습니다.
서비스 운영 기준에서 실제로 중요했던 문제들만 선별했습니다.
1️⃣ JWT 토큰 UserDetails / userId 혼용 문제
문제
- 어떤 유저는 JWT subject가 문자열(
userMadeId), 어떤 유저는 숫자(userId)여서 파싱 오류 발생.
해결
- JWT subject = Long userId 고정
- userMadeId는 claim으로 이동
- UserDetails가 필요한 로직과 userId만 필요한 로직 분리.
2️⃣ JWT subject 문자열로 인해 500 오류
For input string: "charminglee"
원인
Long.valueOf()변환 과정에서 문자열 subject가 들어옴.
해결
- 로그인 시 JWT 생성 구조 정비
- subject는 항상 숫자형 userId
- 문자열 ID는 claim 필드로 이동.
3️⃣ Multipart + JSON 파싱 오류
Failed to parse multipart servlet request
원인
- JSON + 파일 업로드가 섞인 multipart 요청에서 Swagger/프론트 Content-Type 불일치.
해결
- Controller에
consumes = multipart/form-data명시 - 프론트 요청도 multipart로 통일
- Swagger 스펙 재작성.
4️⃣ AWS S3 Access Key 오류
InvalidAccessKeyId
원인
- 로컬/서버 환경의 키가 불일치하거나 IAM 권한 부족.
해결
.env와 서버 환경변수 일치- IAM 권한 재정비 (S3 최소 권한)
- Gradle 빌드 시 환경 변수 누락 검증.
5️⃣ PostgreSQL VARCHAR 길이 초과 오류
character varying(255) too long
원인
- 이미지 URL 같은 문자열이 255자 넘음.
해결
- VARCHAR → TEXT로 스키마 변경
- JPA 필드 length 제거
- DB에는 S3 URL만 저장하도록 정책 변경.
6️⃣ Base64 이미지 저장 시 DB 부하 발생
문제
- Base64를 TEXT에 저장하니 DB 용량, 성능 이슈 발생.
해결
- 이미지 직접 저장 금지
- S3 업로드 후 URL만 DB에 저장.
7️⃣ 무한스크롤 Cursor 계산 버그
문제
- 기존 방식(nextCursor = id - 1)에서 데이터 누락 발생.
해결
- nextCursor = 마지막 item의 id 그대로 사용
- hasNext는 limit+1 방식으로 계산.
8️⃣ OFFERING → TRADE 전환 시 공모 리스트에서 사라짐
문제
- 공모 종료 후 TRADE가 되면 화면에서 제외됨.
해결
- 공모 페이지에서는 TRADE도 “종료됨”으로 계속 노출되도록 조건 수정.
9️⃣ JPA Repository 네이밍 오류
문제
findByUserId() 동작 안 함.
원인
- JPA 네이밍은 항상 “연관관계 이름 기준”.
해결
findByUser_UserId()사용- 복잡한 조건은 Querydsl/Projection 활용.
🔟 주문(거래) 처리 중 동시성 문제
문제
- 매칭/체결/잔고 업데이트 로직 간 race condition 발생 가능.
해결
- 낙관적 락 도입
- 체결 처리 비동기 메시지 분리
- 트랜잭션 범위 최소화.
























