# Core Wallet Service 리팩토링 지침서

> **목적**: 지갑 생성 로직을 `core` 모듈로 통합하여 admin-api, partner-api, open-api 모두에서 사용 가능하게 한다.
> **날짜**: 2026-03-21
> **DDL 버전**: v1.7

---

## 1. 현재 문제점

| 위치 | 기능 | 문제 |
|------|------|------|
| `admin-api` PartnerManagementService | MASTER 지갑 생성 + approve 등록 | admin-api에 종속 — partner-api/open-api에서 호출 불가 |
| `admin-api` InfraWalletService | ADMIN/GAS 생성 | admin 전용이므로 유지 (변경 불필요) |
| `core` WalletService | HOT/POOL 할당, approve 관리, 잔액 동기화 | 할당(assignWallet)만 있고 **범용 지갑 생성** 메서드 없음 |

**목표**: MASTER/HOT/SETTLEMENT/POOL 지갑 생성을 `core/WalletService`로 이관하여 3개 API 모듈에서 공유.

---

## 2. 리팩토링 후 아키텍처

```
┌─────────────────────────────────────────────────────┐
│                  core/WalletService                  │
│                                                     │
│  ✅ 기존 유지:                                       │
│    assignWallet()          — HOT 할당 (파트너 유저)    │
│    getOrAssignPoolAddress() — POOL 할당              │
│    syncOnchainBalance()    — 잔액 동기화              │
│    requestApproval()       — approve 등록            │
│    retryApproval()         — approve 재시도           │
│    onApprovalConfirmed()   — approve 확인            │
│    monitorGasWallets()     — GAS 감시                │
│                                                     │
│  🆕 신규 추가:                                       │
│    createMasterWallet()    — MASTER 생성 + approve   │
│    createHotWallet()       — HOT 생성 + approve      │
│    createSettlementWallet() — SETTLEMENT 생성+approve│
│    initWalletBalances()    — 잔액 초기 레코드 생성     │
│    registerTokenApprovals() — 토큰 approve 일괄 등록  │
│                                                     │
│  🆕 공통 내부 유틸:                                   │
│    deriveWallet()          — HD 파생 공통 로직         │
│    getRelayerContractAddress() — Relayer 주소 조회    │
└──────────────────────┬──────────────────────────────┘
                       │ @Service 의존성 주입
          ┌────────────┼────────────┐
          ▼            ▼            ▼
    admin-api     partner-api    open-api
    (모든 타입)    (HOT,MASTER)   (HOT — webhook 연동)
    + InfraWallet  (SETTLEMENT)
    (ADMIN/GAS)    (POOL)
```

### 접근 권한 정리

| 지갑 타입 | admin-api | partner-api | open-api | 비고 |
|-----------|:---------:|:-----------:|:--------:|------|
| **ADMIN** | ✅ | ❌ | ❌ | InfraWalletService 유지 |
| **GAS** | ✅ | ❌ | ❌ | InfraWalletService 유지 |
| **MASTER** | ✅ | ✅ | ❌ | 파트너 생성 시 자동 + 수동 |
| **HOT** | ✅ | ✅ | ✅ | 입금 세션 시 자동 할당 |
| **SETTLEMENT** | ✅ | ✅ | ❌ | 파트너 정산용 |
| **POOL** | ✅ | ✅ | ❌ | 소수점 매칭 |

---

## 3. core/WalletService 신규 메서드

### 3-1. `deriveWallet()` — 내부 공통 유틸

```java
/**
 * HD 지갑 파생 공통 로직.
 * blockchain-api를 통해 HD 파생 후 wallet_addresses에 등록된 엔티티를 반환한다.
 *
 * @param networkId 네트워크 ID
 * @param walletType 지갑 타입 문자열 ("MASTER", "HOT", "SETTLEMENT", "POOL")
 * @param partnerId 파트너 ID (nullable — infra 지갑은 null)
 * @param partnerUserId 파트너 유저 ID (nullable — HOT 전용)
 * @return WalletDeriveResponse (walletAddressId, address 포함)
 */
private WalletDeriveResponse deriveWallet(Long networkId, String walletType,
                                           Long partnerId, String partnerUserId) {
    HdWallet hdWallet = hdWalletRepository.findByNetworkId(networkId);
    if (hdWallet == null) {
        throw new NotFoundException(ErrorCodes.HD_WALLET_NOT_FOUND);
    }

    WalletDeriveRequest request = WalletDeriveRequest.builder()
            .networkId(networkId)
            .hdWalletId(hdWallet.getId())
            .walletType(walletType)
            .partnerId(partnerId)
            .partnerUserId(partnerUserId)
            .build();

    return blockchainApiClient.deriveWallet(request);
}
```

### 3-2. `registerTokenApprovals()` — 토큰 approve 일괄 등록

```java
/**
 * 지갑의 모든 토큰 통화에 대해 wallet_approvals PENDING 등록.
 * native coin(contractAddress == null)은 제외.
 *
 * @param walletAddressId 지갑 주소 ID
 * @param networkId 네트워크 ID
 * @return 등록된 approve 수
 */
public int registerTokenApprovals(Long walletAddressId, Long networkId) {
    // 1. Relayer 컨트랙트 주소 조회
    RelayerContract relayerContract = relayerContractRepository.findByNetworkId(networkId);
    if (relayerContract == null) {
        log.warn("No relayer contract for network {}, skip approval registration", networkId);
        return 0;
    }

    // 2. 네트워크의 활성 토큰 통화 조회 (native coin 제외)
    List<Currency> tokenCurrencies = currencyRepository.findByNetworkIdAndIsActive(networkId, true)
            .stream()
            .filter(c -> c.getContractAddress() != null)
            .toList();

    // 3. 각 토큰에 대해 approve 등록 (기존 requestApproval 재활용)
    int count = 0;
    for (Currency currency : tokenCurrencies) {
        requestApproval(walletAddressId, currency.getId(), networkId,
                relayerContract.getContractAddress());
        count++;
    }

    return count;
}
```

### 3-3. `initWalletBalances()` — 잔액 초기 레코드

```java
/**
 * 지갑 주소에 대해 해당 네트워크의 모든 활성 통화 잔액 레코드를 초기화한다.
 *
 * @param walletAddressId 지갑 주소 ID
 * @param networkId 네트워크 ID
 * @return 생성된 잔액 레코드 수
 */
public int initWalletBalances(Long walletAddressId, Long networkId) {
    List<Currency> currencies = currencyRepository.findByNetworkIdAndIsActive(networkId, true);
    int count = 0;

    for (Currency currency : currencies) {
        WalletBalance existing = walletBalanceRepository
                .findByWalletAddressIdAndCurrencyId(walletAddressId, currency.getId());
        if (existing == null) {
            walletBalanceRepository.save(WalletBalance.builder()
                    .walletAddressId(walletAddressId)
                    .currencyId(currency.getId())
                    .balance(BigDecimal.ZERO)
                    .build());
            count++;
        }
    }
    return count;
}
```

### 3-4. `createMasterWallet()` — MASTER 지갑 생성

```java
/**
 * 파트너의 MASTER 지갑을 특정 네트워크에 생성한다.
 * HD 파생 → 잔액 초기화 → 토큰 approve 등록.
 *
 * @param partnerId 파트너 ID
 * @param networkId 네트워크 ID
 * @return 생성된 WalletAddress
 */
public WalletAddress createMasterWallet(Long partnerId, Long networkId) {
    // 1. 기존 MASTER 지갑 존재 확인
    WalletAddress existing = walletAddressRepository
            .findByPartnerIdAndNetworkIdAndWalletType(partnerId, networkId, WalletType.MASTER);
    if (existing != null) {
        log.info("MASTER wallet already exists: partnerId={}, networkId={}, address={}",
                partnerId, networkId, existing.getAddress());
        return existing;
    }

    // 2. HD 파생
    WalletDeriveResponse derived = deriveWallet(networkId, "MASTER", partnerId, null);

    // 3. WalletAddress 조회 (blockchain-api가 이미 INSERT 완료)
    WalletAddress masterWallet = walletAddressRepository.findOne(derived.getWalletAddressId());
    if (masterWallet == null) {
        throw new NotFoundException(ErrorCodes.WALLET_ADDRESS_NOT_FOUND);
    }

    // 4. 잔액 초기화 (네트워크 전체 통화)
    initWalletBalances(masterWallet.getId(), networkId);

    // 5. 토큰 approve 등록
    int approvals = registerTokenApprovals(masterWallet.getId(), networkId);

    log.info("MASTER wallet created: partnerId={}, networkId={}, address={}, approvals={}",
            partnerId, networkId, masterWallet.getAddress(), approvals);

    return masterWallet;
}
```

### 3-5. `createHotWallet()` — HOT 지갑 생성

```java
/**
 * 파트너의 HOT 지갑을 생성한다.
 * HOT 지갑은 approve를 최초 입금 시까지 지연(APPROVE_TIMING_DESIGN 참조).
 * 따라서 생성 시에는 approve 등록 없음 — wallet-activator가 첫 입금 후 처리.
 *
 * @param partnerId 파트너 ID
 * @param networkId 네트워크 ID
 * @param partnerUserId 파트너 유저 ID (nullable — 없으면 파트너 공용 HOT)
 * @return 생성된 WalletAddress
 */
public WalletAddress createHotWallet(Long partnerId, Long networkId, String partnerUserId) {
    // 1. 기존 HOT 지갑 존재 확인 (partnerUserId 있으면 유저별, 없으면 파트너 공용)
    if (partnerUserId != null) {
        WalletAddress existing = walletAddressRepository
                .findByPartnerIdAndPartnerUserIdAndNetworkId(partnerId, partnerUserId, networkId);
        if (existing != null) {
            return existing;
        }
    }

    // 2. HD 파생
    WalletDeriveResponse derived = deriveWallet(networkId, "HOT", partnerId, partnerUserId);

    // 3. WalletAddress 조회
    WalletAddress hotWallet = walletAddressRepository.findOne(derived.getWalletAddressId());
    if (hotWallet == null) {
        throw new NotFoundException(ErrorCodes.WALLET_ADDRESS_NOT_FOUND);
    }

    // 4. 잔액 초기화
    initWalletBalances(hotWallet.getId(), networkId);

    // 5. HOT는 approve 등록하지 않음 (첫 입금 후 wallet-activator가 처리)
    log.info("HOT wallet created: partnerId={}, networkId={}, partnerUserId={}, address={}",
            partnerId, networkId, partnerUserId, hotWallet.getAddress());

    return hotWallet;
}
```

### 3-6. `createSettlementWallet()` — SETTLEMENT 지갑 생성

```java
/**
 * 파트너의 SETTLEMENT 지갑을 생성한다.
 * SETTLEMENT은 출금 대기 잔액을 보관하는 지갑으로, approve 등록 필요.
 *
 * @param partnerId 파트너 ID
 * @param networkId 네트워크 ID
 * @return 생성된 WalletAddress
 */
public WalletAddress createSettlementWallet(Long partnerId, Long networkId) {
    // 1. 기존 SETTLEMENT 지갑 존재 확인
    WalletAddress existing = walletAddressRepository
            .findByPartnerIdAndNetworkIdAndWalletType(partnerId, networkId, WalletType.SETTLEMENT);
    if (existing != null) {
        log.info("SETTLEMENT wallet already exists: partnerId={}, networkId={}, address={}",
                partnerId, networkId, existing.getAddress());
        return existing;
    }

    // 2. HD 파생
    WalletDeriveResponse derived = deriveWallet(networkId, "SETTLEMENT", partnerId, null);

    // 3. WalletAddress 조회
    WalletAddress settlementWallet = walletAddressRepository.findOne(derived.getWalletAddressId());
    if (settlementWallet == null) {
        throw new NotFoundException(ErrorCodes.WALLET_ADDRESS_NOT_FOUND);
    }

    // 4. 잔액 초기화
    initWalletBalances(settlementWallet.getId(), networkId);

    // 5. 토큰 approve 등록
    int approvals = registerTokenApprovals(settlementWallet.getId(), networkId);

    log.info("SETTLEMENT wallet created: partnerId={}, networkId={}, address={}, approvals={}",
            partnerId, networkId, settlementWallet.getAddress(), approvals);

    return settlementWallet;
}
```

### 3-7. `createAllWalletsForPartner()` — 파트너 전체 지갑 일괄 생성

```java
/**
 * 파트너 활성 네트워크별 MASTER + HOT + SETTLEMENT 지갑을 일괄 생성한다.
 * 파트너 생성(onboarding) 시 호출.
 *
 * @param partnerId 파트너 ID
 * @return 네트워크별 생성 결과 목록
 */
public List<WalletCreationResult> createAllWalletsForPartner(Long partnerId) {
    List<BlockchainNetwork> activeNetworks = blockchainNetworkRepository.findByIsActive(true);
    List<WalletCreationResult> results = new ArrayList<>();

    for (BlockchainNetwork network : activeNetworks) {
        Long networkId = network.getId();

        try {
            WalletAddress master = createMasterWallet(partnerId, networkId);
            WalletAddress hot = createHotWallet(partnerId, networkId, null);

            results.add(WalletCreationResult.builder()
                    .networkId(networkId)
                    .chainSymbol(network.getChainSymbol())
                    .masterAddress(master.getAddress())
                    .masterWalletId(master.getId())
                    .hotAddress(hot.getAddress())
                    .hotWalletId(hot.getId())
                    .build());

            log.info("Partner wallets created: partnerId={}, network={}", partnerId, network.getChainSymbol());
        } catch (Exception e) {
            log.error("Wallet creation failed: partnerId={}, network={}", partnerId, network.getChainSymbol(), e);
            results.add(WalletCreationResult.builder()
                    .networkId(networkId)
                    .chainSymbol(network.getChainSymbol())
                    .error(e.getMessage())
                    .build());
        }
    }

    return results;
}
```

### 3-8. `WalletCreationResult` DTO

**파일**: `core/src/main/java/com/cryptoments/core/wallet/WalletCreationResult.java`

```java
package com.cryptoments.core.wallet;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * 지갑 생성 결과 DTO.
 */
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class WalletCreationResult {
    /** 네트워크 ID */
    private Long networkId;
    /** 체인 심볼 (BSC, POLYGON, TRON) */
    private String chainSymbol;
    /** MASTER 지갑 주소 */
    private String masterAddress;
    /** MASTER wallet_addresses.id */
    private Long masterWalletId;
    /** HOT 지갑 주소 */
    private String hotAddress;
    /** HOT wallet_addresses.id */
    private Long hotWalletId;
    /** 에러 메시지 (실패 시) */
    private String error;
}
```

---

## 4. WalletService 의존성 추가

현재 `WalletService`에 없는 Repository/Client를 추가해야 한다.

### 4-1. 신규 import 및 필드 추가

```java
// 기존 import에 추가
import com.cryptoments.common.entity.BlockchainNetwork;
import com.cryptoments.common.entity.Currency;
import com.cryptoments.common.entity.RelayerContract;
import com.cryptoments.common.repository.BlockchainNetworkRepository;
import com.cryptoments.common.repository.CurrencyRepository;
import com.cryptoments.common.repository.RelayerContractRepository;

// 기존 필드에 추가
private final BlockchainNetworkRepository blockchainNetworkRepository;
private final CurrencyRepository currencyRepository;
private final RelayerContractRepository relayerContractRepository;
```

### 4-2. 생성자 수정

```java
public WalletService(BlockchainApiClient blockchainApiClient,
                     HdWalletRepository hdWalletRepository,
                     WalletAddressRepository walletAddressRepository,
                     WalletBalanceRepository walletBalanceRepository,
                     WalletApprovalRepository walletApprovalRepository,
                     DepositAddressPoolRepository depositAddressPoolRepository,
                     // 🆕 신규 의존성
                     BlockchainNetworkRepository blockchainNetworkRepository,
                     CurrencyRepository currencyRepository,
                     RelayerContractRepository relayerContractRepository) {
    // 기존 6개 필드 할당...
    this.blockchainNetworkRepository = blockchainNetworkRepository;
    this.currencyRepository = currencyRepository;
    this.relayerContractRepository = relayerContractRepository;
}
```

---

## 5. WalletAddressRepository — findBy 메서드 추가

현재 `WalletAddressRepository`에 필요한 조회 메서드가 부족할 수 있다. 다음 메서드가 존재하는지 확인하고, 없으면 추가한다.

```java
@XRepository
public interface WalletAddressRepository extends IXRepository<Long, WalletAddress> {
    // 기존 메서드 (이미 있을 수 있음)
    List<WalletAddress> findByWalletType(WalletType walletType);
    List<WalletAddress> findByNetworkIdAndWalletType(Long networkId, WalletType walletType);
    WalletAddress findByPartnerIdAndPartnerUserIdAndNetworkId(Long partnerId, String partnerUserId, Long networkId);

    // 🆕 필요 시 추가
    WalletAddress findByPartnerIdAndNetworkIdAndWalletType(Long partnerId, Long networkId, WalletType walletType);
}
```

> **주의**: `findByPartnerIdAndNetworkIdAndWalletType`은 동일 파트너+네트워크+타입 조합이 유니크한 경우에만 단일 반환이 유효하다.
> MASTER/SETTLEMENT은 파트너+네트워크당 1개이므로 단일 반환 OK.
> HOT은 파트너+네트워크당 여러 개 가능 (유저별) → createHotWallet에서는 partnerUserId 기반 조회 사용.

---

## 6. admin-api PartnerManagementService 리팩토링

### 6-1. `activateChainsAndCreateMasterWallets()` 제거 → 위임

기존 `activateChainsAndCreateMasterWallets()` 메서드를 core `WalletService.createAllWalletsForPartner()`로 교체한다.

**Before** (admin-api/PartnerManagementService.java, line 245~326):
```java
private List<MasterWalletInfo> activateChainsAndCreateMasterWallets(Partner partner) {
    // ... 80줄의 지갑 생성 + approve 등록 로직 ...
}
```

**After**:
```java
/**
 * 파트너 체인 활성화 + 지갑 생성 (core WalletService로 위임).
 */
private List<MasterWalletInfo> activateChainsAndCreateWallets(Partner partner) {
    // 1. partner_chain_configs 생성은 여전히 admin 책임
    List<BlockchainNetwork> activeNetworks = blockchainNetworkRepository.findByIsActive(true);
    for (BlockchainNetwork network : activeNetworks) {
        List<Currency> currencies = currencyRepository.findByNetworkIdAndIsActive(network.getId(), true);
        for (Currency currency : currencies) {
            PartnerChainConfig config = PartnerChainConfig.builder()
                    .partnerId(partner.getId())
                    .networkId(network.getId())
                    .currencyId(currency.getId())
                    .depositMethod(DepositMethod.HD_WALLET)
                    .build();
            partnerChainConfigRepository.save(config);
        }
    }

    // 2. 지갑 생성은 core WalletService로 위임
    List<WalletCreationResult> walletResults = walletService.createAllWalletsForPartner(partner.getId());

    // 3. 결과 변환
    return walletResults.stream()
            .filter(r -> r.getError() == null)
            .map(r -> MasterWalletInfo.builder()
                    .networkId(r.getNetworkId())
                    .chainSymbol(r.getChainSymbol())
                    .walletAddressId(r.getMasterWalletId())
                    .address(r.getMasterAddress())
                    .build())
            .toList();
}
```

### 6-2. WalletService 주입 추가

```java
// admin-api/PartnerManagementService.java 에 추가
private final WalletService walletService;

// 생성자에 파라미터 추가
public PartnerManagementService(/* ... 기존 파라미터 ... */,
                                 WalletService walletService) {
    // ...
    this.walletService = walletService;
}
```

### 6-3. 수동 지갑 재생성 API 리팩토링

기존 admin-api에 `POST /api/admin/partners/{id}/wallets/create-master` 등 수동 재생성 엔드포인트가 있다면,
해당 서비스도 `walletService.createMasterWallet(partnerId, networkId)` 로 교체.

---

## 7. partner-api 지갑 생성 기능 추가

### 7-1. 새 Controller: `PartnerWalletController.java`

**파일**: `partner-api/src/main/java/com/cryptoments/partnerapi/controller/PartnerWalletController.java`

```java
package com.cryptoments.partnerapi.controller;

import com.cryptoments.partnerapi.dto.request.WalletCreateRequest;
import com.cryptoments.partnerapi.dto.response.WalletCreateResponse;
import com.cryptoments.partnerapi.service.PartnerWalletService;
import one.axim.framework.rest.controller.XSessionController;
import one.axim.framework.rest.session.SessionData;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

/**
 * 파트너 지갑 관리 API.
 * MASTER, HOT, SETTLEMENT, POOL 생성 가능. ADMIN/GAS는 불가.
 */
@RestController
@RequestMapping("/api/partner/wallets")
public class PartnerWalletController extends XSessionController<SessionData> {

    private final PartnerWalletService walletService;

    public PartnerWalletController(PartnerWalletService walletService) {
        this.walletService = walletService;
    }

    /** HOT 지갑 생성 */
    @PostMapping("/hot")
    public ResponseEntity<WalletCreateResponse> createHotWallet(
            @RequestBody WalletCreateRequest request) {
        Long partnerId = getSession().getPartnerId();
        return ResponseEntity.ok(walletService.createHotWallet(partnerId, request));
    }

    /** MASTER 지갑 생성 */
    @PostMapping("/master")
    public ResponseEntity<WalletCreateResponse> createMasterWallet(
            @RequestBody WalletCreateRequest request) {
        Long partnerId = getSession().getPartnerId();
        return ResponseEntity.ok(walletService.createMasterWallet(partnerId, request));
    }

    /** SETTLEMENT 지갑 생성 */
    @PostMapping("/settlement")
    public ResponseEntity<WalletCreateResponse> createSettlementWallet(
            @RequestBody WalletCreateRequest request) {
        Long partnerId = getSession().getPartnerId();
        return ResponseEntity.ok(walletService.createSettlementWallet(partnerId, request));
    }
}
```

### 7-2. 새 Service: `PartnerWalletService.java`

**파일**: `partner-api/src/main/java/com/cryptoments/partnerapi/service/PartnerWalletService.java`

```java
package com.cryptoments.partnerapi.service;

import com.cryptoments.common.entity.WalletAddress;
import com.cryptoments.core.wallet.WalletService;
import com.cryptoments.partnerapi.dto.request.WalletCreateRequest;
import com.cryptoments.partnerapi.dto.response.WalletCreateResponse;
import org.springframework.stereotype.Service;

/**
 * 파트너 지갑 생성 서비스 — core WalletService를 래핑.
 * 파트너 세션 컨텍스트 검증 후 core로 위임.
 */
@Service
public class PartnerWalletService {

    private final WalletService walletService;

    public PartnerWalletService(WalletService walletService) {
        this.walletService = walletService;
    }

    public WalletCreateResponse createHotWallet(Long partnerId, WalletCreateRequest request) {
        WalletAddress wallet = walletService.createHotWallet(
                partnerId, request.getNetworkId(), request.getPartnerUserId());
        return toResponse(wallet);
    }

    public WalletCreateResponse createMasterWallet(Long partnerId, WalletCreateRequest request) {
        WalletAddress wallet = walletService.createMasterWallet(partnerId, request.getNetworkId());
        return toResponse(wallet);
    }

    public WalletCreateResponse createSettlementWallet(Long partnerId, WalletCreateRequest request) {
        WalletAddress wallet = walletService.createSettlementWallet(partnerId, request.getNetworkId());
        return toResponse(wallet);
    }

    private WalletCreateResponse toResponse(WalletAddress wallet) {
        return WalletCreateResponse.builder()
                .walletAddressId(wallet.getId())
                .address(wallet.getAddress())
                .networkId(wallet.getNetworkId())
                .walletType(wallet.getWalletType().name())
                .derivationPath(wallet.getDerivationPath())
                .build();
    }
}
```

### 7-3. DTO 정의

**`WalletCreateRequest.java`** — `partner-api/.../dto/request/`

```java
package com.cryptoments.partnerapi.dto.request;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class WalletCreateRequest {
    /** 네트워크 ID (필수) */
    private Long networkId;
    /** 파트너 유저 ID (HOT 전용 — nullable) */
    private String partnerUserId;
}
```

**`WalletCreateResponse.java`** — `partner-api/.../dto/response/`

```java
package com.cryptoments.partnerapi.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * 지갑 생성 응답.
 */
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
public class WalletCreateResponse {
    /** wallet_addresses.id */
    private Long walletAddressId;
    /** 블록체인 주소 */
    private String address;
    /** 네트워크 ID */
    private Long networkId;
    /** 지갑 타입 (MASTER, HOT, SETTLEMENT, POOL) */
    private String walletType;
    /** HD 파생 경로 */
    private String derivationPath;
}
```

---

## 8. 기존 assignWallet() 리팩토링

기존 `assignWallet()` 은 HOT 할당 전용으로, 내부에서 직접 HD 파생 + 잔액 생성을 한다.
`createHotWallet()`와 로직이 겹치므로 **assignWallet → createHotWallet 위임**으로 변경한다.

```java
/**
 * 파트너 유저에게 HD 지갑 주소를 할당한다. (기존 호환)
 * 내부적으로 createHotWallet()을 호출한다.
 */
public WalletAddress assignWallet(Long partnerId, String partnerUserId,
                                  Long networkId, Long currencyId) {
    return createHotWallet(partnerId, networkId, partnerUserId);
}
```

> **참고**: `currencyId` 파라미터는 더 이상 사용하지 않는다. `initWalletBalances()`가 네트워크 전체 통화를 초기화하므로.
> 기존 호출처(DepositService 등)에서 `currencyId`를 전달하더라도 무시된다.
> 호출처 시그니처 변경은 선택사항이며, 당장은 호환을 위해 파라미터를 유지한다.

---

## 9. 적용 순서 (Checklist)

| # | 작업 | 모듈 | 파일 |
|---|------|------|------|
| 1 | `WalletCreationResult.java` 생성 | core | `core/wallet/WalletCreationResult.java` |
| 2 | `WalletAddressRepository`에 `findByPartnerIdAndNetworkIdAndWalletType` 추가 | common | `repository/WalletAddressRepository.java` |
| 3 | `WalletService`에 의존성 3개 추가 (BlockchainNetworkRepo, CurrencyRepo, RelayerContractRepo) | core | `core/wallet/WalletService.java` |
| 4 | `WalletService`에 신규 메서드 7개 추가 (3-1 ~ 3-7) | core | `core/wallet/WalletService.java` |
| 5 | `assignWallet()` → `createHotWallet()` 위임으로 리팩토링 (섹션 8) | core | `core/wallet/WalletService.java` |
| 6 | `PartnerManagementService.activateChainsAndCreateMasterWallets()` → 위임으로 교체 (섹션 6) | admin-api | `service/PartnerManagementService.java` |
| 7 | `PartnerManagementService`에 `WalletService` 주입 추가 | admin-api | `service/PartnerManagementService.java` |
| 8 | `WalletCreateRequest.java` 생성 | partner-api | `dto/request/WalletCreateRequest.java` |
| 9 | `WalletCreateResponse.java` 생성 | partner-api | `dto/response/WalletCreateResponse.java` |
| 10 | `PartnerWalletService.java` 생성 | partner-api | `service/PartnerWalletService.java` |
| 11 | `PartnerWalletController.java` 생성 | partner-api | `controller/PartnerWalletController.java` |
| 12 | `./gradlew :core:compileJava` — core 컴파일 확인 | - | - |
| 13 | `./gradlew :admin-api:compileJava` — admin-api 컴파일 확인 | - | - |
| 14 | `./gradlew :partner-api:compileJava` — partner-api 컴파일 확인 | - | - |

---

## 10. 주의사항

1. **InfraWalletService는 변경 없음** — ADMIN/GAS는 admin 전용 유지.

2. **partner_chain_configs 생성은 admin 책임** — 지갑 생성과 분리.
   - 파트너 생성 시 admin이 chain config를 만들고, 지갑 생성은 core로 위임.
   - partner-api에서 지갑만 추가 생성할 때는 chain config가 이미 존재해야 함.

3. **HOT approve 타이밍** — `APPROVE_TIMING_DESIGN.md` 참조.
   - MASTER/SETTLEMENT: 생성 즉시 approve 등록 → wallet-activator가 GAS 전송 후 온체인 approve.
   - HOT: 생성 시 approve 미등록. 첫 입금 확인 후 wallet-activator가 자동 등록+실행.

4. **트랜잭션 범위** — `createMasterWallet()`, `createHotWallet()` 등은 `@Transactional` 불필요.
   - blockchain-api 호출이 포함되므로 긴 트랜잭션은 위험.
   - DB 작업(잔액/approve 등록)은 각각 독립적으로 안전.

5. **open-api 호출** — 현재 open-api에서 직접 지갑 생성할 엔드포인트는 불필요.
   - open-api의 `WebhookProcessingService.processDeposit()`에서 `core.DepositService` → `core.WalletService.assignWallet()` 체인으로 이미 HOT 할당 가능.
   - 향후 open-api에서 직접 생성 API가 필요하면 partner-api와 동일 패턴으로 추가.

---

## 11. 변경 영향 범위

```
변경 파일:
  common/   WalletAddressRepository.java (메서드 1개 추가)
  core/     WalletService.java (의존성 3개 + 메서드 7개 추가 + assignWallet 리팩토링)
  core/     WalletCreationResult.java (신규)
  admin-api/ PartnerManagementService.java (activateChainsAndCreateMasterWallets → 위임)

신규 파일:
  partner-api/ dto/request/WalletCreateRequest.java
  partner-api/ dto/response/WalletCreateResponse.java
  partner-api/ service/PartnerWalletService.java
  partner-api/ controller/PartnerWalletController.java

변경 없음:
  admin-api/ InfraWalletService.java (ADMIN/GAS 전용 유지)
  admin-api/ WalletManagementService.java (조회 전용 유지)
  admin-api/ WalletApprovalService.java (approve 관리 유지)
  open-api/  (현재 단계에서는 변경 없음)
```
