# NODEJS_TRON_ENERGY_RENTAL_PHASE2_GUIDE — 집금/실현 폴러 에너지 렌탈 적용

> 작성: 2026-07-28. Phase 1: `NODEJS_TRON_ENERGY_RENTAL_FIX_GUIDE.md` (WithdrawalPoller, 커밋 7a54dbb).
> 배경: `CollectionBatchRunner`(집금)·`RealizationPoller`(수익실현)가 TRON `executeTransfer`를 **에너지 렌탈 없이** 브로드캐스트 — 건당 릴레이어 TRX ~7.6 소각 의존 (§29 발견).

## Phase 2 목표

1. **공용 헬퍼 추출**: Phase 1에서 WithdrawalPoller에 넣은 렌탈 로직을 `relayer-api/src/services/tronEnergyRental.ts`로 추출, 3개 폴러가 공유.
2. **CollectionBatchRunner / RealizationPoller TVM 분기에 렌탈 적용**.

## 1. 공용 헬퍼 `tronEnergyRental.ts`

```ts
export interface RentEnergyParams {
    /** USDT 실이동 주체 (estimate from) — 출금=MASTER, 집금=HOT, 실현=MASTER */
    usdtHolderAddress: string;
    /** 수신 주소 (fresh 판정 대상) */
    toAddress: string;
    /** 에너지 위임 대상 = 브로드캐스트하는 relayer EOA (tx origin) */
    relayerAddress: string;
    tokenContract: string;
    provider: ChainProvider;
    /** TronZap external_id (예: withdrawal-503, collection-12, realization-7) */
    externalId: string;
    /** 1 tx 안의 전송 건수 (배치면 N, 기본 1) */
    transferCount?: number;
}
export interface RentEnergyResult { energyAmount: number; costTrx: number; tronZapTxId: string; isFresh: boolean; }
```

- 상수(Phase 1 값 이동): `FUNDED=71_000`, `FRESH=140_000`, `CONTRACT_OVERHEAD_ENERGY=6_000`, `RELAYER_MIN_TRX_SUN=50_000_000n`.
- 로직 = Phase 1과 동일: estimate(from=usdtHolder) → fresh 판정(toAddress USDT 잔액, 실패 시 fresh) → `energyAmount = max(estimate.amount + OVERHEAD, floor)` → 단, **배치**면:
  `energyAmount = max(estimate.amount, perTransfer) × transferCount + OVERHEAD` (perTransfer = fresh?140k:71k — estimate는 1건 기준이므로 건수 곱)
- createEnergyTransaction(address=relayerAddress, duration 1, external_id) → waitForCompletion → TRX<50 경보(logger.error) — 전부 Phase 1 코드 그대로 이동.
- WithdrawalPoller는 헬퍼 호출로 교체 (동작 동일 — 회귀 없어야 함).

## 2. CollectionBatchRunner (집금 HOT→MASTER)

- TVM 분기(~217행)에서 브로드캐스트 **전에** 헬퍼 호출:
  - usdtHolder = HOT(전송 from), to = MASTER, relayerAddress = 브로드캐스트 EOA(코드에서 실제 서명 주체 확인), transferCount = 배치 내 전송 건수 (1 tx 1건이면 1 — **코드 실측으로 판단**).
  - MASTER는 보통 funded지만 잔액 0 가능 → fresh 판정 로직 그대로.
- external_id: `collection-{batchId}`.
- 렌탈 실패 시 해당 배치 throw → 기존 실패 처리/재시도 경로 유지 (새 상태 만들지 말 것).

## 3. RealizationPoller (수익실현 MASTER→수취 주소)

- TVM 분기(~151행) 동일 적용. usdtHolder = MASTER, to = 실현 수취 주소(코드에서 확인 — SETTLEMENT/외부), external_id `realization-{id}`.
- 수취 주소가 fresh일 가능성 높음 — fresh 판정 필수.

## 4. 비용 기록

- WithdrawalPoller가 ENERGY_RENTAL을 `gas_cost_records`에 기록하는 기존 패턴 확인 후, 두 폴러에도 **동일 패턴**으로 기록 추가 (reference type/id는 각 도메인 것). 기존 repo 함수 시그니처에 안 맞으면 무리하게 바꾸지 말고 로그만 남기고 보고.

## 규칙 (Phase 1과 동일)

- **src만 수정, dist 금지, 빌드 금지** (오케스트레이터가 tsc).
- 기존 스타일/로거 유지, 수정 근거 주석 (2026-07-28, §29 Phase 2).
- TVM 분기 receipt 미대기 이슈는 **건드리지 말 것** (별도 백로그).

## 완료 기준 / 보고

- 3개 폴러 모두 헬퍼 사용, WithdrawalPoller 동작 불변.
- 보고: 파일별 변경, transferCount 실측 결과(배치 구조), relayer EOA 서명 주체 확인 결과, gas_cost_records 기록 적용 여부.
