PLAN.md: Avalanche NFT 민팅 서버 (MVP v8 - 안정화)
1. 프로젝트 개요
본 프로젝트는 Avalanche C-Chain(및 향후 EVM 호환 체인)을 지원하는 NFT 민팅/배포 API 서버입니다. NestJS 프레임워크를 기반으로 구축하며, 티켓/쿠폰과 같은 유틸리티 NFT의 메타데이터 관리, 컨트랙트 배포, 비동기 민팅 주문 처리를 핵심 기능으로 합니다.
AI 핵심 지시사항: 이 문서의 모든 지침, 특히 보안(PK 캐싱), 안정성(모든 작업의 비동기 큐 처리), 메타데이터 스키마(v6), 테스트 전략을 최우선으로 준수하여 코드를 생성해야 합니다.
2. 핵심 기술 스택 및 원칙
- 프레임워크: NestJS (TypeScript). 모든 코드는 NestJS의 모듈식 아키텍처(Module, Controller, Service, Provider)를 엄격히 따라야 합니다.
- 저장소: 모노레포 (pnpm/yarn Workspaces). 루트에 package.json이 있고, contracts/와 server/ 두 워크스페이스가 존재합니다.
- 데이터베이스:
- MongoDB: Mongoose (@nestjs/mongoose)를 사용한 스키마 및 데이터 관리.
- Redis: @nestjs/bull 및 ioredis 사용. Nonce 관리, 민팅 큐, 배포 큐, 웹훅 큐에 사용됩니다.
- 블록체인: ethers.js (v6)를 사용합니다.
- 보안: AWS Secrets Manager (인메모리 캐싱 적용).
- 파일 저장소: AWS S3. (@aws-sdk/client-s3)
- 인증: 단일 글로벌 시크릿 키. (x-api-key 헤더 검증)
- 에러 핸들링: **전역 예외 필터(Global Exception Filter)**를 사용하여 일관된 JSON 에러 응답을 보장합니다.
3. 디렉토리 구조 (모노레포)
AI는 파일 생성 시 반드시 이 모노레포 구조를 따라야 합니다.
/
├─ package.json # pnpm/yarn 워크스페이스 루트
├─ /contracts/ # (P2-1) Hardhat/Foundry 컨트랙트 프로젝트
│ ├─ contracts/
│ │ └─ MijiNft.sol # C-100 (Ownable, EIP-2981)
│ ├─ hardhat.config.ts
│ └─ package.json
└─ /server/ # (핵심) NestJS 서버 프로젝트
├─ /src/
│ ├─ /modules/ # 기능별 모듈
│ │ ├─ /auth/ # (A-120) 단일 키 인증 가드
│ │ ├─ /blockchain/ # (B-100, P1-6) EVM 어댑터 (★PK 캐싱★)
│ │ ├─ /contracts/ # (C-100s) 컨트랙트 배포 (★비동기 큐★)
│ │ ├─ /assets/ # (M-050) S3 파일 업로드
│ │ ├─ /metadata/ # (M-100, M-130, M-140) 메타데이터 관리
│ │ ├─ /minting/ # (T-100s) 민팅 큐 및 프로세서
│ │ ├─ /janitor/ # (T-125) 큐 복구 스케줄러
│ │ └─ /webhook/ # (H-200) 웹훅 발송 (★비동기/재시도 큐★)
│ ├─ /common/ # 공유 모듈, DTO, 스키마
│ │ ├─ /filters/ # (신규) 전역 예외 필터
│ │ └─ /schemas/ # (신규) Mongoose 스키마 정의
│ ├─ /config/ # @nestjs/config 모듈
│ ├─ main.ts
│ └─ app.module.ts
├─ package.json
└─ tsconfig.json
4. NestJS 모듈 상세 정의
4-1. ConfigModule (/src/config/)
- .env 파일 로드, GLOBAL_SECRET_KEY, DB_URI, REDIS_URL, AWS_REGION, WEBHOOK_URL 등 관리.
- SECRETS_MANAGER_PK_ARN, PK_CACHE_TTL_SECONDS (e.g., 300)
4-2. AuthModule (/src/modules/auth/)
- auth.guard.ts (A-120): x-api-key 헤더를 ConfigService의 GLOBAL_SECRET_KEY와 비교.
4-3. BlockchainModule (B-100, P1-6) (★PK 캐싱★)
- CacheModule (@nestjs/cache-manager)을 임포트합니다.
- blockchain.service.ts (EVM 어댑터):
- ConfigService와 SecretsManagerService를 주입받습니다.
- getProvider(chainId: string): ethers.Provider: chainId에 맞는 RPC URL로 JsonRpcProvider 반환.
- getSigner(chainId: string): Promise<ethers.Wallet>: SecretsManagerService.getPrivateKeyWithCache()를 호출하여 PK를 가져온 뒤 Wallet 인스턴스 생성.
- secrets.manager.service.ts:
- CACHE_MANAGER (Cache)와 ConfigService를 주입받습니다.
- getPrivateKeyWithCache(): Promise<string>: (M1 수정)
- CACHE_MANAGER.get(‘PRIVATE_KEY’) 시도.
- 캐시가 있으면 즉시 반환.
- 캐시가 없으면, AWS SDK로 Secrets Manager 실제 호출.
- 가져온 PK를 CACHE_MANAGER.set(‘PRIVATE_KEY’, pk, config.PK_CACHE_TTL_SECONDS)로 저장 후 반환.
- nonce.manager.service.ts:
- ioredis를 주입받습니다.
- getNextNonce(chainId: string, address: string): Promise<number>: Redis INCR 명령으로 nonce:${chainId}:${address} 키 원자적 증가.
4-4. ContractsModule (C-100s) (★비동기 큐★)
- (B1 수정) @nestjs/bull을 임포트하여 deployment-queue를 등록합니다.
- DeployedContract 스키마(5-3) 및 BlockchainModule 임포트.
- contracts.controller.ts:
- @UseGuards(AuthGuard) 적용.
- POST /contracts/deploy (C-130): DeployContractDto를 contracts.service.ts로 전달.
- (신규) POST /contracts/forwarder: MinimalForwarder 컨트랙트 배포.
- (신규) POST /contracts/read: 컨트랙트 View 함수 조회.
- contracts.service.ts (Producer):
- orderDeployment():
- DTO를 받아 DeployedContract 문서를 DEPLOYING 상태로 DB에 저장.
- Bull deployment-queue에 job (e.g., { contractDbId: newContract._id })을 추가.
- 즉시 contractDbId와 status: ‘DEPLOYING’을 반환. (HTTP 타임아웃 방지)
- readContract(): ethers.Contract를 사용해 가스비 없는 조회 수행.
- contracts.processor.ts (Consumer) (신규):
- @Processor(‘deployment-queue’)
- @Process() 핸들러 handleDeployJob(job: Job):
- job.data.contractDbId로 DEPLOYING 상태의 문서 조회.
- try…catch 블록.
- BlockchainService.getSigner() 호출, ContractFactory로 배포, tx.wait().
- 성공 시: DB에 status: ‘ACTIVE’, contract_address, gas_used (C-131) 저장.
- 실패 시: catch 블록. status: ‘FAILED’, error_message 저장. (웹훅 발송 옵션)
4-5. AssetsModule (M-050)
- Asset 스키마(5-1) 임포트.
- assets.controller.ts: POST /assets/upload (파일 업로드)
- assets.service.ts: S3 업로드 및 Asset DB 저장, URL 반환.
- Metadata 스키마(5-2) 임포트.
- metadata.controller.ts:
- POST /metadata (M-100): 외부 이미지 URL을 포함한 CreateMetadataDto 수신.
- GET /:contract_address/:token_id (M-130): 메타데이터 서빙 (Public)
- POST /metadata/:id/status (M-140): 상태 업데이트.
- metadata.service.ts:
- createMetadata(): (M-100) DTO를 받아 utility 객체(원본)를 기반으로 standardFields 객체(가공)를 생성하여 Metadata 스키마에 저장.
- serveMetadata(): (M-130) standardFields와 utility 객체를 조합하여 표준 JSON 반환.
- updateStatus(): (M-140) **utility.data.status**와 standardFields.attributes 내의 “Status” 값을 동시에 업데이트.
4-7. MintingModule (T-100s)
- @nestjs/bull을 사용하여 minting-queue를 정의.
- MintingOrder 스키마(5-4), BlockchainModule, WebhookModule 임포트.
- minting.controller.ts: POST /minting/order (T-100)
- minting.service.ts (Producer): MintingOrder를 PENDING으로 저장하고 minting-queue에 Job 추가 후 즉시 응답.
- minting.processor.ts (Consumer):
- handleMintJob(): PROCESSING으로 상태 변경 -> getSigner -> getNextNonce -> TX 전송 및 wait() -> CONFIRMED 또는 FAILED로 상태 변경 -> WebhookService로 이벤트 발행.
- CONFIRMED 시점에는 CoinGecko API(60~180초 TTL Redis 캐시)를 통해 AVAX/USD/KRW 환율을 조회하고, 총 가스 비용을 AVAX·법정화폐 단위로
MintingOrder에 저장.
- contracts.processor.ts: 배포 TX 완료 후 동일한 CoinGecko 시세 정보를 조회하여
DeployedContract 문서에 가스 사용량 및 AVAX/USD/KRW 비용을 기록.
4-8. TransferModule (가스 대납 Transfer)
- TransferOrder 스키마, Bull
transfer-queue 정의.
- transfer.controller.ts
POST /transfer/signature – 테스트 환경에서 EIP-712 MinimalForwarder 서명/페이로드 생성(LOCAL_DEV_PK 사용).
POST /transfer/execute – 외부 지갑 서명 + Bull 큐 enqueue, 토큰 소유 여부 검증.
GET /transfer/:id, GET /transfer/by-tx/:txHash – 상태 조회.
- transfer.processor.ts
- MinimalForwarder.execute 호출, NonceManager로 서비스 지갑 nonce 관리.
- CoinGecko 시세를 이용해 Transfer 가스비(AVAX/USD/KRW)를 기록.
- Silent Failure 감지:
tx.wait() 후 Transaction Log를 검사하여 실제 Transfer 이벤트 유무 확인.
- Config:
TRUSTED_FORWARDER, TRANSFER_TEST_SIGNATURE 플래그, Forwarder domain 정보.
4-9. JanitorModule (T-125)
- @nestjs/schedule 및 MintingModule, ContractsModule 임포트.
- janitor.service.ts:
- @Cron(‘*/10 * * * *’) (10분마다):
-
- MintingOrder에서 status: ‘PENDING’으로 5분 이상 방치된 주문을 찾아 minting-queue에 재삽입.
-
- (B1 수정) DeployedContract에서 status: ‘DEPLOYING’으로 10분 이상 방치된 주문을 찾아 deployment-queue에 재삽입.
4-10. WebhookModule (H-200) (★비동기/재시도 큐★)
- (B2 수정) @nestjs/bull을 임포트하여 webhook-queue를 등록합니다.
- 중요: Bull 큐 등록 시 재시도 옵션 설정: { attempts: 5, backoff: { type: ‘exponential’, delay: 10000 } } (10초 후, … 5회 재시도)
- @nestjs/axios (HttpModule), @nestjs/event-emitter 임포트.
- WebhookLog 스키마(5-5) 임포트.
- webhook.service.ts (Producer):
- @OnEvent(‘mint.confirmed’), @OnEvent(‘mint.failed’) 등:
- payload를 받아 webhook-queue에 Job으로 추가합니다. (직접 HTTP 호출 금지)
- webhook.processor.ts (Consumer) (신규):
- @Processor(‘webhook-queue’)
- @Process() 핸들러 handleWebhookJob(job: Job):
- job.data.payload와 ConfigService.WEBHOOK_URL을 가져옵니다.
- try…catch 블록.
- HttpService로 POST 전송.
- 성공 시: WebhookLog에 SUCCESS (2xx) 기록.
- 실패 시 (Client Error 4xx): catch 블록. 재시도 안 함. WebhookLog에 FAILED 기록.
- 실패 시 (Server Error 5xx/Timeout): catch 블록. 에러를 throw하여 Bull이 자동으로 재시도하게 함. WebhookLog에 RETRYING 기록.
5. MongoDB 스키마 (Mongoose)
(v7 제안서와 동일한 5개 스키마. server/src/common/schemas/ 하위에 정의)
- Asset (M-050): original_file_name, s3_key, url, mime_type
- Metadata (★핵심★): chain_id, contract_address, token_id, standardFields (Object) (name, image, attributes…), utility (Object) (type, validation_url, data…), current_status
- DeployedContract (C-130): chain_id, name, symbol, contract_address, status (DEPLOYING, ACTIVE, FAILED), current_owner_address, deployment_tx_hash
- MintingOrder (T-100): chain_id, contract_address, receiver_address, metadata_id (Ref: Metadata), status (PENDING, PROCESSING, SENT, CONFIRMED, FAILED), tx_hash
- WebhookLog (H-200): event_type, target_url, related_order_id, payload_sent, status (SUCCESS, FAILED, RETRYING), response_status_code, attempt
6. 핵심 구현 지침 (AI 필독)
6-1. 보안 (P1-2: PK 캐싱)
- (M1 수정) getSigner()가 호출될 때마다 SecretsManagerService를 호출합니다. 이 서비스는 **인메모리 캐시(e.g., NestJS CacheModule)**를 사용하여, 5분(설정값) 이내에는 AWS API를 재호출하지 않고 캐시된 PK를 반환해야 합니다. (API 병목 및 비용 방지)
6-2. 안정성 (비동기 큐)
- (B1 수정) 컨트랙트 배포(C-130)는 반드시 deployment-queue를 통해 비동기 처리해야 합니다. API 컨트롤러는 절대 tx.wait()를 호출하면 안 됩니다.
- (B2 수정) 웹훅(H-200)은 반드시 webhook-queue를 통해 비동기 처리해야 합니다. Bull의 attempts 및 backoff 옵션을 활성화하여 서버 측 오류(5xx) 시 자동 재시도를 보장해야 합니다.
- (T-125) JanitorModule은 minting-queue와 deployment-queue 모두를 감시하여 멈춘(Stuck) 작업을 재시작시켜야 합니다.
6-3. 에러 핸들링 (M3: 전역 필터)
- (신규) server/src/common/filters/http-exception.filter.ts 파일을 생성합니다.
- NestJS의 ExceptionFilter를 구현하여, 모든 HttpException을 캐치합니다.
- {“statusCode”: 404, “message”: “Not Found”, “timestamp”: “…”, “path”: “…“}와 같은 일관된 JSON 에러 응답 형식을 반환해야 합니다.
- 이 필터는 main.ts에서 app.useGlobalFilters(new HttpExceptionFilter())로 전역 등록합니다.
6-4. 테스트 전략 (M2: 필수)
- (신규) 1인 개발자의 유지보수를 위해 테스트 코드는 필수입니다. AI는 모듈 생성 시 테스트 코드 생성을 요청받을 수 있습니다.
- Unit Test (.spec.ts): MetadataService.createMetadata가 utility를 standardFields로 잘 변환하는지 등, 순수 비즈니스 로직을 테스트합니다. NestJS의 DI를 활용해 BlockchainService 등은 Mock Provider로 대체합니다.
- E2E Test (.e2e-spec.ts): supertest를 사용해 API 엔드포인트를 테스트합니다. (e.g., POST /minting/order가 201과 PENDING 상태를 반환하는지). Testcontainers 또는 mongodb-memory-server 사용을 고려합니다.
7. 개발 단계 (CI/CD 우선)
- Phase 1: 프로젝트 초기화
- 모노레포 설정 (pnpm init, pnpm-workspace.yaml).
- contracts/에 Hardhat 프로젝트(MijiNft.sol) 기본 틀 생성.
- server/에 NestJS 프로젝트(nest new server) 생성.
- Phase 2: 인프라 연동 및 “Hello World” 배포 (★가장 중요★)
- server/에 ConfigModule, DatabaseModule (MongoDB) 구현.
- server/에 AuthModule (A-120) 및 HttpExceptionFilter (M3) 구현.
- GET /health 엔드포인트(AuthGuard 적용)를 만들어 실제 MongoDB에 1회 접속하는 로직 구현.
- [개발 중단 & 배포] 이 상태로 Docker 파일 작성, AWS(ECS/ECR 등)에 배포, CI/CD 파이프라인 완성.
- Phase 3: 핵심 모듈 (보안, 메타데이터, 컨트랙트)
- BlockchainModule 구현 (B-100) (특히 SecretsManagerService(M1) 캐싱 구현).
- AssetsModule (M-050) 및 MetadataModule (M-100, M-130, M-140) 구현.
- ContractsModule (C-100s) 구현 (비동기 큐(B1) 방식 적용).
- Phase 4: 민팅 및 안정화 (큐, 스케줄러)
- MintingModule (T-100s) (비동기 큐) 구현.
- WebhookModule (H-200) (재시도 큐(B2) 방식 적용).
- JanitorModule (T-125) (Cron 잡) 구현.
- 테스트(M2): 핵심 서비스들에 대한 Unit/E2E 테스트 스위트 작성.
- NestJS Swagger (@nestjs/swagger)를 이용해 OpenAPI 문서 자동화.
- TransferModule (Gasless): Forwarder 관리, 서명 검증, 대납 실행, Silent Failure 감지 구현.