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

# Stellar lending Blend migration

> Atomically repay Blend debt, withdraw collateral and supply, and open an equivalent XOXNO position in one transaction.

`migrate_from_blend` moves a Blend V2 position to XOXNO in one transaction. The controller repays Blend debt, withdraws collateral and non-collateral supply, deposits the withdrawn assets as XOXNO collateral, and runs a final health check before persisting state.

## Entrypoint

```rust theme={"system"}
migrate_from_blend(
    caller: Address,
    account_id: u64,                    // 0 creates a new account
    spoke_id: u32,
    hub_id: u32,
    blend_pool: Address,                // must be governance-approved
    collateral_assets: Vec<Address>,    // WithdrawCollateral, sweep-all
    supply_assets: Vec<Address>,        // Withdraw (non-collateral supply), sweep-all
    debt_caps: Vec<(Address, i128)>,    // (debt_asset, max_repay_cap)
) -> u64
```

| Parameter           | Semantics                                                                                                      |
| ------------------- | -------------------------------------------------------------------------------------------------------------- |
| `caller`            | Must authorize the call. Owns the Blend position and the resulting XOXNO account.                              |
| `account_id`        | `0` creates a fresh account; non-zero merges into an existing account owned by `caller`.                       |
| `blend_pool`        | Target Blend pool. Must pass `is_blend_pool_approved` or the call reverts with `#42 BlendPoolNotApproved`.     |
| `collateral_assets` | Blend collateral tokens to withdraw. Each uses `WithdrawCollateral` with `i128::MAX` internally.               |
| `supply_assets`     | Blend non-collateral supply to withdraw. Each uses `Withdraw` with `i128::MAX`. Deposited as XOXNO collateral. |
| `debt_caps`         | Per debt asset, the maximum the controller may borrow to repay Blend. Must be ≥ actual Blend debt.             |

At least one of `{collateral_assets, supply_assets, debt_caps}` must be non-empty.

## Atomic flow

```mermaid theme={"system"}
flowchart TD
    A["migrate_from_blend"] --> B{"debt_caps empty?"}
    B -->|no| C["Phase 1: repay"]
    B -->|yes| D{"collateral or supply?"}
    C --> C1["Snapshot controller balances"]
    C1 --> C2["open_migration_borrow per debt (fee=0)"]
    C2 --> C3["Authorize transfer controller → blend_pool"]
    C3 --> C4["Blend submit: Repay per debt"]
    C4 --> C5["Reconcile Blend refunds → exact debt"]
    C5 --> D
    D -->|yes| E["Phase 2: withdraw"]
    D -->|no| F["strategy_finalize"]
    E --> E1["Fresh snapshot for withdraw assets"]
    E1 --> E2["Blend submit: WithdrawCollateral + Withdraw"]
    E2 --> E3["Deposit withdrawn tokens as XOXNO collateral"]
    E3 --> F
    F --> G["Health factor / LTV / min-collateral check"]
    G --> H["Emit BlendMigrationEvent"]
```

The entire flow runs inside one controller call. Any revert rolls back all Blend and XOXNO state changes.

## Debt sourcing

Migration does not use the flash-loan receiver pattern. It uses an internal zero-fee strategy borrow:

| Mechanism         | `flash_loan`                          | Migration borrow                                    |
| ----------------- | ------------------------------------- | --------------------------------------------------- |
| Entrypoint        | `controller.flash_loan`               | `open_migration_borrow` inside `migrate_from_blend` |
| Pool call         | `pool.flash_loan` → receiver callback | `pool.create_strategy`                              |
| Fee               | Configured bps                        | 0                                                   |
| Debt outcome      | Repaid in same callback               | Permanent debt on the migrated account              |
| Receiver contract | Required                              | None                                                |
| Solvency check    | At callback end                       | At `strategy_finalize`                              |

## Refund reconciliation

`debt_caps` are upper bounds, not exact debt amounts. When Blend repays less than the cap, Blend refunds the surplus to the controller. `reconcile_debt_refunds` detects the refund via balance delta and calls `repay_debt_from_controller`. The migrated XOXNO debt equals the actual Blend debt, not the cap.

## Looped positions

When the same token is both collateral and debt (for example USDC collateral plus USDC debt), the controller uses a two-phase submit:

1. **Phase 1** — repay debt with an isolated balance snapshot so refunds do not alias with withdrawals.
2. **Phase 2** — withdraw collateral and supply with a fresh snapshot.

The same asset may appear in both `collateral_assets` and `debt_caps`. Duplicate debt entries in `debt_caps` revert with `#AssetsAreTheSame`.

## Blend request types

| Request            | Constant                      | Amount                 | Purpose                     |
| ------------------ | ----------------------------- | ---------------------- | --------------------------- |
| Repay              | `REQ_REPAY = 5`               | `cap` from `debt_caps` | Clear Blend debt            |
| WithdrawCollateral | `REQ_WITHDRAW_COLLATERAL = 3` | `i128::MAX`            | Sweep all collateral        |
| Withdraw           | `REQ_WITHDRAW = 1`            | `i128::MAX`            | Sweep non-collateral supply |

Non-collateral Blend supply becomes collateral on XOXNO. XOXNO has no non-collateral supply concept.

## Authorization

| Leg                                      | Who authorizes                                                 |
| ---------------------------------------- | -------------------------------------------------------------- |
| `migrate_from_blend`                     | `caller.require_auth()`                                        |
| Blend `submit(from=caller)`              | Caller in the transaction auth tree (per phase)                |
| `transfer(controller → blend_pool, cap)` | Controller `authorize_as_current_contract` before repay submit |

## Reentry guard

`guarded_submit` sets `FlashLoanOngoing` during Blend calls. Mutating controller entrypoints reject reentry while the guard is set (`#400 FlashLoanOngoing`).

## Governance

Blend pools are allow-listed through governance:

```bash theme={"system"}
# Schedule via governance (production):
propose_approve_blend_pool(proposer, blend_pool_address, salt)
# → execute after timelock
```

Approved pools are listed in `configs/blend_pools.json`. Query approval with `is_blend_pool_approved(blend_pool)`.

## Events

```rust theme={"system"}
BlendMigrationEvent {
    topics: ["strategy", "blend_migration"],
    account_id,
    blend_pool,
    collateral_count,
    supply_count,
    debt_count,
}
```

## Failure modes

| Condition                                                   | Error                                       |
| ----------------------------------------------------------- | ------------------------------------------- |
| Empty `collateral_assets`, `supply_assets`, and `debt_caps` | `#InvalidPayments`                          |
| Unapproved `blend_pool`                                     | `#42 BlendPoolNotApproved`                  |
| Duplicate debt asset in `debt_caps`                         | `#AssetsAreTheSame`                         |
| `debt_cap` below actual Blend debt                          | Blend health check revert (atomic rollback) |
| End state fails health / LTV / min collateral               | `#InsufficientCollateral` at finalize       |
| Caller does not own target account                          | Ownership mismatch                          |

## Example invoke

```bash theme={"system"}
stellar contract invoke --id <controller> --network <network> \
  --source <user> \
  -- migrate_from_blend \
  --caller <user> \
  --account-id 0 \
  --spoke-id 1 \
  --hub-id 1 \
  --blend-pool <approved_blend_pool> \
  --collateral-assets '[<XLM>]' \
  --supply-assets '[]' \
  --debt-caps '[["<USDC>", "1000000000"]]'
```

Set each `debt_cap` to at least the actual Blend debt for that asset. Use `get_account_positions` and Blend views to size caps before invoking.

## Next

<CardGroup cols={2}>
  <Card title="Strategies" icon="route" href="/stellar-lending/dev/strategies">
    Multiply, swap debt, swap collateral, and repay-with-collateral flows.
  </Card>

  <Card title="Flash loans" icon="bolt" href="/stellar-lending/dev/flash-loans">
    The flash-loan receiver pattern — distinct from migration's zero-fee borrow.
  </Card>

  <Card title="Accounts and risk" icon="user-shield" href="/stellar-lending/dev/accounts-and-risk">
    Account model, health factor, and spoke constraints after migration.
  </Card>

  <Card title="Controller ABI" icon="code" href="/stellar-lending/dev/controller-abi">
    Full `migrate_from_blend` signature and related admin entrypoints.
  </Card>
</CardGroup>
