> ## Documentation Index
> Fetch the complete documentation index at: https://docs.li.fi/llms.txt
> Use this file to discover all available pages before exploring further.

# Debt Migration

> Migrate an Aave V3 borrow position to Morpho Blue atomically, using two concurrent flash loans.

<Tip>
  **Advanced flash-loan pattern.** Moves an open borrow position from one lending protocol to another in a single signed transaction — no intermediate capital, no window where the position is unhealthy.
</Tip>

<Warning>
  **Unaudited preview — not for production funds.** This recipe relies on flashloans, which are available in the [ETHGlobal preview](/composer/composer-api/reference/addresses#ethglobal-hackathon) only. Those contracts are **unaudited** and not deployed to production — use this flow for experimentation only, not with significant value.
</Warning>

## Scenario

A user holds a WETH-collateralised USDC borrow on **Aave V3** (Base) and wants the same position on **Morpho Blue** instead — to capture a better rate, say. Doing this manually means finding capital to repay Aave before you can withdraw the collateral, which is exactly the chicken-and-egg that flash loans solve.

The flow borrows two flash loans concurrently and unwinds both within the same transaction:

* **Flash loan #1 (Aave V3, USDC)** repays the Aave debt before the Morpho borrow exists.
* **Flash loan #2 (Balancer V2, WETH)** supplies the Morpho collateral before the Aave collateral is freed.

The Morpho borrow is sized to exactly `principal + Aave fee` so it settles flash loan #1 with zero residual; the WETH freed from Aave settles flash loan #2 (Balancer V2 charges no fee).

## What this recipe demonstrates

* The [`flashloan`](/composer/composer-api/materialisers/flashloan) materialiser sourcing two flow inputs from two different providers.
* Settling each leg with `lifi.flashloanRepay`, keyed by `leg` to the input it repays.
* Threading receipt handles across protocols: `aave.repay` → `lifi.zap` withdraw → `morphoBlue.supplyCollateral` / `morphoBlue.borrow`.
* A safe `sweepTo`: the Aave aToken collateral is **fully unwound** (withdrawn to WETH), so nothing non-transferable is left on the proxy. (Contrast with flows that keep an aToken position — see the [`sweepTo` caveat](/composer/composer-api/concepts/sweeping-and-amounts#non-transferable-terminal-resources).)

## Full example

Adapted from [`aaveToMorphoDebtMigration.ts`](https://github.com/lifinance/composer-sdk-examples/blob/main/examples/staged/aaveToMorphoDebtMigration.ts) in the `composer-sdk-examples` repo. Two variants that route the destination debt through a swap — [`...WithSwap`](https://github.com/lifinance/composer-sdk-examples/blob/main/examples/staged/aaveToMorphoDebtMigrationWithSwap.ts) and [`...WithSwapExactOut`](https://github.com/lifinance/composer-sdk-examples/blob/main/examples/staged/aaveToMorphoDebtMigrationWithSwapExactOut.ts) — build on the same shape.

```ts theme={"system"}
import { createComposeSdk, materialisers, resources } from '@lifi/composer-sdk';

// Base mainnet (chain 8453)
const BASE_WETH = '0x4200000000000000000000000000000000000006';
const BASE_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

// Aave V3 on Base (pool + aWETH receipt + variable-debt USDC)
const AAVE_V3_POOL_BASE = '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5';
const A_BAS_WETH = '0xD4a0e0b9149BCee3C920d2E00b5dE09138fd8bb7';
const VDEBT_BAS_USDC = '0x59dca05b6c26dbd64b5381374aAaC5CD05644C28';

// Morpho Blue WETH/USDC market on Base
const MORPHO_WETH_USDC_MARKET = {
  loanToken: BASE_USDC,
  collateralToken: BASE_WETH,
  oracle: '0xFEa2D58cEfCb9fcb597723c6bAE66fFE4193aFE4',
  irm: '0x46415998764C29aB2a25CbeA6254146D50D22687',
  lltv: '860000000000000000',
};

const collateralAmount = '1000000000000000000'; // 1 WETH
const debtAmount = '1000000000'; // 1,000 USDC
const debtFlashloanFee = '500000'; // 5 bps of principal (Aave V3 flashloan fee)

// Morpho borrow must equal principal + Aave flashloan fee so `borrowed`
// settles the USDC flashloan leg exactly.
const morphoBorrowAmount = (
  BigInt(debtAmount) + BigInt(debtFlashloanFee)
).toString();

const sdk = createComposeSdk({ baseUrl: 'https://composer.li.quest' });

const builder = sdk.flow(8453, {
  name: 'aave-to-morpho-debt-migration',
  inputs: {
    initialCollateral: resources.erc20(BASE_WETH, 8453),
    debtFlashloan: resources.erc20(BASE_USDC, 8453),
    collateralFlashloan: resources.erc20(BASE_WETH, 8453),
  },
});

// --- Phase 1: provision the Aave V3 borrow position ---

// WETH → aBasWETH (Aave V3 supply via routing edge).
const supplyToAave = builder.lifi.zap('supply-to-aave', {
  bind: { amountIn: builder.inputs.initialCollateral },
  config: { resourceOut: resources.erc20(A_BAS_WETH, 8453) },
});

// Borrow USDC against the supplied collateral; proceeds are swept to the signer.
builder.aave.borrow('borrow-from-aave', {
  bind: {},
  config: {
    pool: AAVE_V3_POOL_BASE,
    asset: BASE_USDC,
    variableDebtToken: VDEBT_BAS_USDC,
    amount: debtAmount,
  },
});

// --- Phase 2: migrate to Morpho with two concurrent flash loans ---

// Repay the Aave USDC debt with flash loan #1 (mode: 'max' clamps to the debt).
builder.aave.repay('repay-aave', {
  bind: {
    assetIn: builder.inputs.debtFlashloan,
    onBehalfOf: builder.context.executionAddress,
  },
  config: { pool: AAVE_V3_POOL_BASE, mode: 'max' },
});

// Withdraw the freed WETH from Aave (aBasWETH → WETH).
const withdrawFromAave = builder.lifi.zap('withdraw-from-aave', {
  bind: { amountIn: supplyToAave.amountOut },
  config: { resourceOut: resources.erc20(BASE_WETH, 8453) },
});

// Open the Morpho position with flash loan #2's WETH.
builder.morphoBlue.supplyCollateral('supply-to-morpho', {
  bind: { assetIn: builder.inputs.collateralFlashloan },
  config: { marketParams: MORPHO_WETH_USDC_MARKET, mode: 'exact' },
});

// Borrow exactly principal + fee from Morpho to settle the USDC leg.
const morphoBorrow = builder.morphoBlue.borrow('borrow-from-morpho', {
  bind: {},
  config: { marketParams: MORPHO_WETH_USDC_MARKET, amount: morphoBorrowAmount },
});

// Settle the USDC flash loan with the Morpho borrow.
builder.lifi.flashloanRepay('repay-debt-flashloan', {
  bind: { funds: morphoBorrow.borrowed },
  config: { leg: 'debtFlashloan' },
});

// Settle the WETH flash loan with the Aave withdrawal (Balancer V2: no fee).
builder.lifi.flashloanRepay('repay-collateral-flashloan', {
  bind: { funds: withdrawFromAave.amountOut },
  config: { leg: 'collateralFlashloan' },
});

const request = sdk.request(builder.build(), {
  signer: '0xYourSignerAddress',
  inputs: {
    initialCollateral: materialisers.directDeposit({ amount: collateralAmount }),
    debtFlashloan: materialisers.flashloan({
      providerKind: 'aave-v3',
      amount: debtAmount,
    }),
    collateralFlashloan: materialisers.flashloan({
      providerKind: 'balancer-v2',
      amount: collateralAmount,
    }),
  },
  sweepTo: builder.context.sender,
});
```

## What to observe

* **Inputs.** Three resource inputs: the user's collateral (`directDeposit`) plus two `flashloan`-sourced inputs from different providers.
* **Why two providers.** Aave V3 supplies the USDC to repay; Balancer V2 supplies the WETH collateral (and charges no fee, so its leg is settled by the withdrawn principal alone).
* **Sizing the Morpho borrow.** `principal + debtFlashloanFee` so the `borrowed` resource settles the USDC leg with zero leftover.
* **Repayment is mandatory.** Both flash loans must be repaid in-flow via `lifi.flashloanRepay`, or simulation reverts.
* **`sweepTo` is safe here** because the Aave aToken is fully withdrawn to WETH — no bound collateral receipt remains on the proxy. When a flow *keeps* an aToken position, omit the blanket `sweepTo` and transfer only loose tokens (see the [caveat](/composer/composer-api/concepts/sweeping-and-amounts#non-transferable-terminal-resources)).
