> ## 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 strategies

> Controller strategies: multiply, swap debt, swap collateral, repay with collateral, and Blend migration via the swap aggregator.

Strategies compose the base supply, borrow, withdraw, repay, and pool flash-borrow primitives with an external swap aggregator, in one controller call. They keep the same account, oracle, and reentry safety model as the base operations. **Read first:** [Supply and borrow](/stellar-lending/dev/supply-and-borrow), [Accounts and risk](/stellar-lending/dev/accounts-and-risk).

## Endpoints

| Endpoint                     | What it does                                                                                                                  |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `multiply`                   | Flash-borrow the debt token, swap it into the collateral token, supply the collateral. Opens or extends a leveraged position. |
| `swap_debt`                  | Borrow a new debt token, swap into the existing debt token, repay the old debt.                                               |
| `swap_collateral`            | Withdraw collateral, swap into a new collateral asset, resupply.                                                              |
| `repay_debt_with_collateral` | Withdraw collateral, swap (or net) into the debt token, repay. Optionally close the position.                                 |
| `migrate_from_blend`         | Repay Blend debt, withdraw collateral and supply, deposit as XOXNO collateral. One transaction.                               |

## The aggregator boundary

The `swap` argument is an **opaque `Bytes` payload** (ScVal XDR). The controller does **not** decode it or validate its route structure. It forwards the bytes verbatim to the configured aggregator:

```rust theme={"system"}
// Controller → aggregator (contractclient)
fn execute_strategy(sender: Address, total_in: i128, swap_xdr: Bytes) -> i128
```

The controller calls this with `sender = <controller address>` and `total_in` set to the exact amount it commits to spend. The aggregator pulls `total_in` of the input token from the controller, performs the swap, and returns the output token. Inside the aggregator the XDR decodes to a `StrategyPayload`:

```rust theme={"system"}
StrategyPayload {
    paths: Vec<SwapPath>,   // SwapPath { hops, split_ppm }, Σ split_ppm = 1_000_000
    referral_id: u64,       // 0 = no referral fee
    token_in: Address,
    token_out: Address,
    total_min_out: i128,    // slippage floor — enforced INSIDE the aggregator
}
```

<Warning>
  **Slippage is enforced by the aggregator, not the controller.** The `total_min_out` floor lives inside the opaque bytes and is checked by `execute_strategy`. The controller adds only defense-in-depth: the swap bytes must be non-empty and `amount` positive, the router must not spend more than `total_in`, and the returned output balance delta must be positive. Build `total_min_out` correctly in your quote — the controller will not catch a bad price for you.
</Warning>

This replaces the previous design, where `swap` was a typed `AggregatorSwap`/`BatchSwap` with per-path `amount_in`/`min_amount_out` and controller-side path validation. Those types no longer exist.

## Building the swap payload

You do not hand-assemble the XDR. Request a route from the swap aggregator's quote service, which returns the encoded `StrategyPayload` bytes for the input/output pair and amount. Pass those bytes straight into the strategy call. See [Stellar Aggregator](/stellar-aggregator/overview) for the quote API and token coverage.

When the route is multi-hop or split across venues, that complexity is entirely inside the bytes; the controller and your transaction see only `swap: Bytes`.

## Position mode

`PositionMode` (`Normal = 0`, `Multiply = 1`, `Long = 2`, `Short = 3`) tags the account's intent. `multiply` accepts only `Multiply`, `Long`, or `Short` and rejects `Normal`. An account's mode is fixed at creation; the other three strategies do not change it.

## Multiply

```rust theme={"system"}
multiply(
    caller: Address,
    account_id: u64,
    spoke_id: u32,
    collateral_token: Address,
    debt_to_flash_loan: i128,
    debt_token: Address,
    mode: PositionMode,
    swap: Bytes,
    initial_payment: Option<(Address, i128)>,
    convert_swap: Option<Bytes>,
) -> u64
```

Flow: collect any `initial_payment` from `caller` → flash-borrow `debt_to_flash_loan` of `debt_token` from the pool → `execute_strategy` swaps it (plus debt-denominated payment) into `collateral_token` → supply the combined collateral → re-check LTV, health factor, and dust floors.

Rules:

* `mode` is `Multiply`, `Long`, or `Short`; `collateral_token != debt_token`.
* The strategy borrow uses the `RiskIncreasing` oracle policy.
* `initial_payment` may be in the collateral token (added directly), the debt token (added to the flash amount), or a third token — which requires `convert_swap` to convert it to the collateral token, else `#500 ConvertStepsRequired`.

## Swap debt

```rust theme={"system"}
swap_debt(caller, account_id, existing_debt_token, amount, new_debt_token, swap: Bytes)
```

Borrows `amount` of `new_debt_token`, swaps to `existing_debt_token`, repays the old debt; over-repayment is refunded to `caller`. The two debt tokens must differ.

## Swap collateral

```rust theme={"system"}
swap_collateral(caller, account_id, current_collateral, amount, new_collateral, swap: Bytes)
```

Withdraws `current_collateral`, swaps to `new_collateral`, resupplies. Debt-free accounts use the `RiskDecreasing` oracle policy; accounts with debt use `RiskIncreasing`. The new collateral must pass deposit preflight (collateralizable on the account's spoke, within position limits).

## Repay debt with collateral

```rust theme={"system"}
repay_debt_with_collateral(
    caller, account_id, collateral_token, collateral_amount,
    debt_token, swap: Bytes, close_position: bool,
)
```

Withdraws `collateral_amount`, swaps to `debt_token` (or nets directly when the tokens are the same — no route needed), repays. When `close_position = true` and all debt is cleared, withdraws all remaining collateral to `caller`.

## Migrate from Blend

```rust theme={"system"}
migrate_from_blend(
    caller, account_id, spoke_id, hub_id,
    blend_pool, collateral_assets, supply_assets, debt_caps,
) -> u64
```

Repays Blend debt with a zero-fee internal strategy borrow, withdraws collateral and non-collateral supply, and deposits the result as XOXNO collateral. The `blend_pool` must be governance-approved. Solvency is checked at `strategy_finalize`, not during the Blend calls.

This flow does not use the flash-loan receiver pattern. See [Blend migration](/stellar-lending/dev/blend-migration) for the two-phase submit model, refund reconciliation, and parameter guide.

## Strategy borrow vs flash loan

| Mechanism      | `flash_loan`                          | Strategy borrow (`multiply`, `migrate_from_blend`) |
| -------------- | ------------------------------------- | -------------------------------------------------- |
| Pool call      | `pool.flash_loan` → receiver callback | `pool.create_strategy`                             |
| Fee            | Configured bps                        | 0 for migration; configured for multiply           |
| Debt outcome   | Repaid in same callback               | Permanent account debt                             |
| Solvency check | At callback end                       | At `strategy_finalize`                             |

## Reentry and trust

Strategy swaps and Blend `submit` calls reuse the flash-loan reentrancy guard (`FlashLoanOngoing`). A compromised or misconfigured aggregator cannot call back into mutating controller entrypoints during the swap. The controller authorizes exactly one token pull (`total_in` of the input token) for the aggregator and verifies its own balance deltas afterward.

## Failure modes

| Error                       | Cause                                                                    | Fix                                                      |
| --------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------- |
| `#500 ConvertStepsRequired` | `multiply` initial payment is a third token but `convert_swap` is `None` | Provide a `convert_swap` route for payment → collateral. |
| `#27 AggregatorNotSet`      | No aggregator configured                                                 | Governance must schedule `set_aggregator`.               |
| `#42 BlendPoolNotApproved`  | `blend_pool` not on the allow-list                                       | Governance must schedule `approve_blend_pool`.           |
| `#102 HealthFactorTooLow`   | Post-strategy health factor below 1                                      | Reduce leverage or improve the quote (`total_min_out`).  |
| Aggregator slippage revert  | Output below `total_min_out`                                             | Re-quote; the floor is inside the swap bytes.            |

## Next

<CardGroup cols={2}>
  <Card title="Blend migration" icon="arrow-right" href="/stellar-lending/dev/blend-migration">
    Atomic Blend V2 to XOXNO position migration.
  </Card>

  <Card title="Flash loans" icon="bolt" href="/stellar-lending/dev/flash-loans">
    The single-asset flash loan receiver pattern.
  </Card>

  <Card title="Stellar Aggregator" icon="shuffle" href="/stellar-aggregator/overview">
    The quote service that produces the swap bytes.
  </Card>
</CardGroup>
