---
name: lending-instructions
description: Load when working with KapanRouter instructions, UTXO tracking, flash loan flows, or debugging transaction failures
---

# Kapan Lending Instructions System

The KapanRouter uses a **UTXO-based instruction system** where each operation can consume inputs from previous outputs and produce new outputs. Understanding this is critical for building correct flows.

## Core Concepts

### UTXO (Unspent Transaction Output)
Each instruction can create outputs that subsequent instructions reference by index. The router maintains an `outputs[]` array during execution.

### Two Instruction Types

1. **Router Instructions** (`protocolName: "router"`) - Handled by KapanRouter directly
2. **Protocol Instructions** (`protocolName: "aave"`, `"compound"`, etc.) - Delegated to gateways

## Router Instructions

Defined in `utils/v2/instructionHelpers.ts:4-13`:

| Type | Creates UTXO? | Description |
|------|--------------|-------------|
| `FlashLoan` | **Yes** | Borrows from flash loan provider, creates output for repayment amount |
| `PullToken` | **No** | Pulls tokens from user to router (requires user approval) |
| `PushToken` | **No** | Sends tokens from UTXO to user |
| `ToOutput` | **Yes** | Creates a UTXO from hardcoded amount/token (no actual transfer) |
| `Approve` | **Yes** (empty) | Approves gateway to spend UTXO. Creates empty output for index sync! |
| `Split` | **Yes** (2) | Splits UTXO into fee portion + remainder (for Aave flash loan fees) |
| `Add` | **Yes** | Combines two UTXOs of same token |
| `Subtract` | **Yes** | Subtracts one UTXO from another |

### Critical: Approve Creates Empty Output
The `Approve` instruction creates an empty output to maintain index synchronization. This is why output indices jump after approvals:
```typescript
// After Approve(2), the next output is index 3, not 2!
addRouter(encodeApprove(2, "aave"), true); // createsUtxo=true
```

## Lending Operations

Defined in `utils/v2/instructionHelpers.ts:26-36`:

| Op | Creates UTXO? | Description |
|----|--------------|-------------|
| `Deposit` | No | Supply tokens to lending protocol |
| `DepositCollateral` | No | Supply as collateral (Morpho, Compound) |
| `WithdrawCollateral` | **Yes** | Withdraw collateral, creates output |
| `Borrow` | **Yes** | Borrow tokens, creates output |
| `Repay` | **Yes** | Repay debt, creates refund output (usually 0) |
| `GetBorrowBalance` | **Yes** | Query debt balance, creates output with amount |
| `GetSupplyBalance` | **Yes** | Query supply balance, creates output with amount |
| `Swap` | **Yes** (2) | Swap via DEX, creates (tokenOut, refund) outputs |
| `SwapExactOut` | **Yes** (2) | Exact output swap, creates (tokenOut, refund) outputs |

## Input Index Conventions

- `inputIndex: 999` - Ignore input, use `amount` parameter directly
- `inputIndex: 0-998` - Read amount/token from `outputs[inputIndex]`

## Flash Loan Providers

```typescript
enum FlashLoanProvider {
  BalancerV2 = 0,  // 0% fee
  BalancerV3 = 1,  // 0% fee  
  Aave = 2,        // ~5-9 bps fee
  ZeroLend = 3,    // ~5 bps fee
  UniswapV3 = 4,   // Requires pool address
  Morpho = 5,      // 0% fee
}
```

**Important**: Aave/ZeroLend flash loans create an output with the **repayment amount** (principal + fee), not the borrowed amount. The router receives the principal but must track repayment separately.

## Common Flows

### Basic Deposit
```typescript
[
  PullToken(amount, token, user),      // No output
  Approve(0, protocol),                // Output[0] = empty (for sync)
  DepositCollateral(token, user, 0, inputIndex=0),  // No output
]
```

### Withdraw Max
```typescript
[
  GetSupplyBalance(token, user),       // Output[0] = balance
  WithdrawCollateral(token, user, inputIndex=0),  // Output[1] = withdrawn
  PushToken(1, user),                  // No output
]
```

### Flash Loan Pattern
```typescript
[
  ToOutput(amount, token),             // Output[0] = virtual UTXO
  FlashLoan(provider, inputIndex=0),   // Output[1] = repayment amount
  // ... use borrowed funds (router has Output[0] worth of tokens)
  // ... must leave Output[1] worth in router for repayment
]
```

### Aave Flash Loan with Fee (Max Withdrawal)
When using Aave flash loans for max operations, use `Split` to account for fees:
```typescript
[
  GetSupplyBalance(token),             // Output[0] = 100
  Split(0, 9),                         // Output[1] = 0.09 (fee buffer), Output[2] = 99.91
  FlashLoan(Aave, 2),                  // Output[3] = 100 (repayment), router has 99.91
  // ... operations using Output[2] (what we actually have)
  // ... withdraw Output[3] worth to repay
]
```

## File Locations

- **Instruction Helpers**: `packages/nextjs/utils/v2/instructionHelpers.ts`
- **Flow Builders**: `packages/nextjs/hooks/useKapanRouterV2.tsx:219-936`
- **Move Builder**: `packages/nextjs/hooks/useKapanRouterV2.tsx:1519-1631`
- **Starknet Instructions**: `packages/nextjs/hooks/useStarknetMovePosition.ts`

## Debugging Tips

1. **Track UTXO indices manually** - Draw out the output array as you build instructions
2. **Approve creates empty output** - Always increment your expected index after Approve
3. **Flash loan outputs** - Remember: Aave Output = repayment (more than borrowed)
4. **Use simulation** - `simulateInstructions()` catches most errors before execution
5. **Check authorization** - `getAuthorizations()` returns needed approvals

## Protocol Context Encoding

Different protocols need different context bytes:

```typescript
// Aave: empty context
context = "0x"

// Compound: market address
context = encodeAbiParameters([{ type: "address" }], [marketAddress])

// Morpho: full MarketParams tuple
context = encodeMorphoContext({ loanToken, collateralToken, oracle, irm, lltv })
```
