# TRON 대역폭(Bandwidth) 부족 문제 수정 가이드

> **날짜**: 2026-03-21
> **대상 파일**: 2개
> **문제**: id=6 USDC `Account resource insufficient error` — 에너지는 충분하나 대역폭/TRX 부족

---

## 1. 문제 원인

TRON smart contract 호출에는 **에너지 + 대역폭** 둘 다 필요합니다.

| 리소스 | 용도 | approve 1회 소모량 |
|--------|------|-------------------|
| Energy | 스마트 컨트랙트 실행 | ~65,000 |
| Bandwidth | TX 데이터 전송 | ~345 bytes |

TRON 계정의 Free Bandwidth = **600/일**. approve 1회가 ~345 소모하므로:
- 1번째 approve (id=5): Free BW 341 사용 → **259 남음**
- 2번째 approve (id=6): 345 필요 > 259 남음 → **TRX에서 차감 시도**
- TRX 잔액 10 SUN (≈0) → **`Account resource insufficient`**

### 현재 HOT 지갑 상태 (TDWNxB1zpEh41mdPDXGMmK7Aaak9FdR5zc)

```
TRX Balance: 10 SUN (0.00001 TRX)
Free BW: 341/600 used → 259 남음
Energy: 32,937/133,014 used → 100,077 남음 ✅
```

에너지는 TronZap 렌탈로 충분하지만, **대역폭 부족 + TRX 없음**이 원인.

---

## 2. 해결 방안: TRX 잔액 체크 + 보충 로직

에너지 리소스 체크(Step 2) 이후, **approve 실행 전에 TRX 잔액을 확인**하고 부족하면 GAS 지갑에서 추가 전송합니다.

### 수정 1: gas.ts — 대역폭 보증 상수 추가

**파일**: `node-service/packages/common/src/constants/gas.ts`

```typescript
// 기존 상수들 아래에 추가:

/** TRON approve 시 대역폭 비용 대비 최소 TRX 잔액 (sun 단위: 1 TRX) */
export const TRON_MIN_TRX_FOR_BANDWIDTH = 1_000_000n;
```

1 TRX = 1,000,000 SUN. 대역폭 1 byte ≈ 1,000 SUN 이므로 1 TRX면 ~1,000 bytes 커버 가능.
approve TX (~345 bytes)를 3회 정도 여유 있게 커버합니다.

### 수정 2: ApprovalProcessor.ts — TRX 잔액 체크 + 보충

**파일**: `node-service/packages/wallet-activator/src/services/ApprovalProcessor.ts`

**import에 추가:**
```typescript
import {
  // ... 기존 import ...
  TRON_MIN_TRX_FOR_BANDWIDTH,  // 추가
} from '@cryptoments/common';
```

**Step 2 (에너지 리소스 확인) 이후, Step 3 (에너지 렌탈) 이전에 삽입:**

기존 코드 (~319행):
```typescript
  logger.info('TRON: Energy resource check', {
    approvalId: approval.id,
    currentEnergy,
    targetEnergy: TRON_APPROVE_ENERGY,
    neededEnergy,
  });
```

이 아래에 추가:
```typescript
  // ── Step 2.5: TRX 잔액 체크 (대역폭 비용용) ──
  const trxBalance = await provider.getBalance(walletAddress.address);
  if (BigInt(trxBalance) < TRON_MIN_TRX_FOR_BANDWIDTH) {
    logger.info('TRON: Insufficient TRX for bandwidth, sending top-up', {
      approvalId: approval.id,
      currentBalance: trxBalance,
      required: TRON_MIN_TRX_FOR_BANDWIDTH.toString(),
    });

    const gasEncryptedKey = await walletKeyRepo.findByAddressId(gasWallet.id);
    if (!gasEncryptedKey) throw new Error(`GAS wallet key not found: ${gasWallet.id}`);
    const gasPrivateKey = await keyManager.decrypt(gasEncryptedKey);

    const topUpTxHash = await provider.sendNative({
      from: gasWallet.address,
      to: walletAddress.address,
      amount: TRON_MIN_TRX_FOR_BANDWIDTH,
      privateKey: gasPrivateKey,
    });

    logger.info('TRON: TRX top-up sent', { approvalId: approval.id, txHash: topUpTxHash });

    // 💰 GAS_SUPPORT 비용 기록
    await recordGasCost({
      partner_id: partnerId,
      network_id: approval.network_id,
      tx_type: 'GAS_SUPPORT',
      tx_hash: topUpTxHash,
      reference_type: 'WALLET_APPROVAL',
      reference_id: approval.id,
      fee_native: TRON_MIN_TRX_FOR_BANDWIDTH.toString(),
      native_price_usd: trxPriceUsd ?? undefined,
      fee_usd: trxPriceUsd
        ? calcFeeUsd(TRON_MIN_TRX_FOR_BANDWIDTH.toString(), trxPriceUsd, 6)
        : undefined,
    });

    await provider.waitForConfirmation(topUpTxHash);
    logger.info('TRON: TRX top-up confirmed', { approvalId: approval.id });
  }
```

### 수정 3: TronProvider — getBalance 메서드 확인

`provider.getBalance(address)`가 SUN 단위 문자열을 반환하는지 확인하세요.
이미 구현되어 있을 가능성이 높지만, 없으면 추가:

```typescript
async getBalance(address: string): Promise<string> {
  const balance = await this.tronWeb.trx.getBalance(address);
  return balance.toString();
}
```

---

## 3. 적용 후 DB 리셋

```sql
-- id=6 리셋
UPDATE wallet_approvals
SET status = 'PENDING', retry_count = 0, error_message = NULL, gas_tx_hash = NULL
WHERE id = 6;

-- id=6 실패 기록 정리
DELETE FROM gas_cost_records
WHERE reference_type = 'WALLET_APPROVAL' AND reference_id = 6;
```

## 4. 빌드 & 재시작

```bash
cd /Users/dudgh/git/cryptoments/node-service
npm run build --workspace=packages/common
# wallet-activator 재시작
```

## 5. 검증 SQL

```sql
SELECT id, status, gas_tx_hash, approve_tx_hash, error_message
FROM wallet_approvals WHERE id = 6;

SELECT id, reference_id, tx_type, fee_native, fee_usd, tx_hash
FROM gas_cost_records
WHERE reference_type = 'WALLET_APPROVAL' AND reference_id = 6;
```

**기대값**: id=6 → GAS_SUPPORT (1 TRX) + ENERGY_RENTAL + APPROVE → status=APPROVED
