# Phase 1+2 테스트 발견 버그 수정 지침서

**날짜**: 2026-03-20
**대상**: blockchain-api (Node.js), admin-api (Spring Boot)

---

## Bug #1: syncBalances — 네이티브 통화 동기화 누락 (🔴 High)

**파일**: `node-service/packages/blockchain-api/src/services/BalanceService.ts`
**증상**: `POST /api/balance/sync` 호출 시 `{"results":[]}` 반환
**원인**: 87~89행에서 `contract_address`가 null이면 skip → 네이티브 통화(BNB, POL, TRX) 무시

### 수정

`syncBalances()` 함수 내 for 루프(77~115행)의 온체인 잔액 조회 부분을 수정:

**Before (87~95행)**:
```typescript
// 온체인 잔액 조회
if (!currency.contract_address) {
  logger.warn(`Currency has no contract_address: currencyId=${bal.currency_id}, walletAddressId=${id}`);
  continue;
}
const onchainRaw = await provider.getTokenBalance(
  wallet.address,
  currency.contract_address,
);
const onchainBalance = onchainRaw.toString();
```

**After**:
```typescript
// 온체인 잔액 조회 — 네이티브 vs 토큰 분기
let onchainRaw: bigint;
if (!currency.contract_address) {
  // 네이티브 통화 (BNB, POL, TRX)
  onchainRaw = await provider.getNativeBalance(wallet.address);
} else {
  // ERC-20 토큰 (USDT, USDC)
  onchainRaw = await provider.getTokenBalance(
    wallet.address,
    currency.contract_address,
  );
}
const onchainBalance = onchainRaw.toString();
```

### Bug #1-B: mysql2 BIGINT → string 타입 불일치 (🔴 Critical)

**추가 발견**: mysql2 드라이버가 BIGINT 컬럼을 `string`으로 반환하기 때문에 `wallet.network_id`가 `"2"` (string), Map key는 `2` (number)라서 매칭 실패.

**에러 로그**: `ChainProvider not registered for networkId=2` (실제로는 등록돼 있으나 타입 불일치)

**수정**: 74행, 80행에 `Number()` 래핑

```typescript
// 74행 — Before:
const provider = chainProviderFactory.get(wallet.network_id);
// After:
const provider = chainProviderFactory.get(Number(wallet.network_id));

// 80행 — Before:
const currency = currencyMap.get(bal.currency_id);
// After:
const currency = currencyMap.get(Number(bal.currency_id));
```

### 검증

```bash
curl -s -X POST http://localhost:3001/api/balance/sync \
  -H "Content-Type: application/json" \
  -d '{"walletAddressIds": [1, 2, 3]}' | jq .
# 기대: results 배열에 각 지갑의 USDT/USDC/NATIVE 잔액 포함
# NATIVE(BNB, TRX)도 results에 나와야 함
```

수정 후 DB 확인:
```sql
SELECT wb.wallet_address_id, c.symbol, wb.balance
FROM wallet_balances wb
JOIN currencies c ON c.id = wb.currency_id
WHERE wb.wallet_address_id IN (1, 2, 3)
ORDER BY wb.wallet_address_id, c.id;
-- balance 값이 0이 아닌 실제 온체인 잔액으로 업데이트되어야 함
```

---

## Bug #2: infra-wallets — ADMIN 지갑 미노출 (🟡 Medium)

**모듈**: admin-api (Spring Boot)
**증상**: `GET /api/admin/infra-wallets` 호출 시 GAS 3개만 반환, ADMIN 3개 누락
**원인**: InfraWalletService 또는 Mapper에서 `wallet_type IN ('GAS')` 조건만 사용하는 것으로 추정

### 수정 방향

InfraWalletMapper 또는 InfraWalletService에서 조회 조건 확인:

```java
// Before (추정)
@Select("SELECT * FROM wallet_addresses WHERE wallet_type = 'GAS' AND status = 'ACTIVE'")

// After — ADMIN도 포함
@Select("SELECT * FROM wallet_addresses WHERE wallet_type IN ('ADMIN', 'GAS') AND status = 'ACTIVE'")
```

**파일 탐색 힌트**:
```bash
grep -rn "infra.wallet\|InfraWallet\|infra-wallet" admin-api/src/ --include="*.java"
grep -rn "GAS\|wallet_type" admin-api/src/**/InfraWallet*.java
```

### 검증

```bash
curl -s http://localhost:8080/api/admin/infra-wallets \
  -H "Access-Token: <token>" | jq '.pageRows | length'
# 기대: 6 (ADMIN 3 + GAS 3)
```

---

## DDL 변경: wallet_balances 자동 생성 Trigger (✅ 적용 완료)

DB에 이미 적용됨. DDL 파일에 반영 필요.

```sql
-- wallet_addresses INSERT 시 해당 네트워크의 모든 활성 통화에 대해 wallet_balances 자동 생성
DROP TRIGGER IF EXISTS trg_wallet_addresses_after_insert;

DELIMITER $$
CREATE TRIGGER trg_wallet_addresses_after_insert
AFTER INSERT ON wallet_addresses
FOR EACH ROW
BEGIN
    INSERT INTO wallet_balances (wallet_address_id, currency_id, balance)
    SELECT NEW.id, c.id, 0
    FROM currencies c
    WHERE c.network_id = NEW.network_id
      AND c.is_active = 1;
END$$
DELIMITER ;
```

**효과**: 지갑 생성 시 별도 코드 없이 자동으로 balance 레코드 초기화. Node.js(HD derive, create-admin, create-infra, register relayer)와 Spring Boot 양쪽에서 코드 수정 불필요.

---

## Bug #3: relayer-api — nonce/queue 조회 API 미구현 (🟡 Medium)

**모듈**: relayer-api (Node.js)
**증상**: `/api/relayer/nonce/:id`, `/api/collection/pending`, `/api/withdrawal/pending` 모두 404
**원인**: 해당 라우트가 아직 구현되지 않음. 현재 relayer-api에는 4개 엔드포인트만 존재 (register, unregister, list, status)
**우선순위**: Phase 3 E2E에서 Poller가 직접 DB를 폴링하므로 비블로커. 운영 모니터링용으로 Phase 4 이후 구현 가능.

### 추가 필요 엔드포인트 (참고)

```
GET /api/relayer/nonce/:walletId        — DB nonce_trackers 조회
GET /api/collection/pending             — collection_queue WHERE status='QUEUED'
GET /api/collection/stats               — 상태별 집계
GET /api/withdrawal/pending             — withdrawals WHERE status IN ('APPROVED','PROCESSING')
GET /api/withdrawal/stats               — 상태별 집계
```

---

## 수정 우선순위

| # | 이슈 | 심각도 | Phase 3 블로커? |
|---|------|--------|---------------|
| 1 | syncBalances 네이티브 누락 | 🔴 High | ✅ Yes — 잔액 동기화 필수 |
| 2 | infra-wallets ADMIN 미노출 | 🟡 Medium | ❌ No — 기능에 영향 없음 |
| 3 | relayer-api 조회 API 미구현 | 🟡 Medium | ❌ No — Poller는 직접 DB 접근 |

**→ Bug #1만 Phase 3 전에 수정 필수. 나머지는 Phase 4 이후.**
