Load when working with KapanRouter instructions, UTXO tracking, flash loan flows, or debugging transaction failures
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.
Each instruction can create outputs that subsequent instructions reference by index. The router maintains an outputs[] array during execution.
protocolName: "router") - Handled by KapanRouter directlyprotocolName: "aave", "compound", etc.) - Delegated to gatewaysDefined 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 |
The Approve instruction creates an empty output to maintain index synchronization. This is why output indices jump after approvals:
// After Approve(2), the next output is index 3, not 2!
addRouter(encodeApprove(2, "aave"), true); // createsUtxo=true
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 |
inputIndex: 999 - Ignore input, use amount parameter directlyinputIndex: 0-998 - Read amount/token from outputs[inputIndex]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.
[
PullToken(amount, token, user), // No output
Approve(0, protocol), // Output[0] = empty (for sync)
DepositCollateral(token, user, 0, inputIndex=0), // No output
]
[
GetSupplyBalance(token, user), // Output[0] = balance
WithdrawCollateral(token, user, inputIndex=0), // Output[1] = withdrawn
PushToken(1, user), // No output
]
[
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
]
When using Aave flash loans for max operations, use Split to account for fees:
[
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
]
packages/nextjs/utils/v2/instructionHelpers.tspackages/nextjs/hooks/useKapanRouterV2.tsx:219-936packages/nextjs/hooks/useKapanRouterV2.tsx:1519-1631packages/nextjs/hooks/useStarknetMovePosition.tssimulateInstructions() catches most errors before executiongetAuthorizations() returns needed approvalsDifferent protocols need different context bytes:
// Aave: empty context
context = "0x"
// Compound: market address
context = encodeAbiParameters([{ type: "address" }], [marketAddress])
// Morpho: full MarketParams tuple
context = encodeMorphoContext({ loanToken, collateralToken, oracle, irm, lltv })