Skip to main content
Strategies compose supply, borrow, withdraw, and repay with an external swap aggregator, in one controller call. Prices still come from the price aggregator. Swap routes are untrusted — the controller never reads what the router claims it did, only how its own balances moved.

Endpoints

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:
sender is the controller’s own address. total_in is the exact amount the controller commits to spend. The aggregator pulls total_in of the input token from the controller, runs the swap, and returns how much output it delivered. Inside the aggregator the XDR decodes to a StrategyPayload. It is a compact program, not a list of routes:
Instructions reference the two registries by u8 index, so an address or amount used by several hops is carried exactly once. Each hop is a fixed 5-byte record rather than a full XDR struct. The ops blob starts with a 10-byte header: Split weights are u24 big-endian parts-per-million and sum to 1_000_000.
The aggregator enforces slippage, not the controller. Your minimum output lives inside the opaque bytes — amounts[header[3]] — and only execute_strategy checks it.The controller adds defense in depth, nothing more: the swap bytes must be non-empty, the amount positive, the router must not spend more than total_in (#501 RouterOverspend), and the output balance delta must be positive (#502 NoSwapOutput). None of that catches a bad price. Get your minimum output right in the quote.

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 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 tags what the account is for: Normal = 0, Multiply = 1, Long = 2, Short = 3. multiply and flash_position accept only Multiply, Long, or Short. They reject Normal with #111 InvalidPositionMode. The mode is fixed when the account is created and never changes. swap_debt, swap_collateral, and repay_debt_with_collateral do not touch it.

Multiply

The flow, in order:
  1. Collect any initial_payment from caller.
  2. Borrow debt_to_flash_loan of debt from the pool as strategy debt.
  3. Swap it — plus any debt-denominated payment — into collateral through execute_strategy.
  4. Supply the combined collateral to the account.
  5. Re-check the LTV gate, health factor, and the minimum-borrow floor.
Rules:
  • mode is Multiply, Long, or Short (#111 otherwise); the collateral and debt assets must differ (#7 AssetsAreTheSame).
  • account_id = 0 creates the account. Otherwise the passed mode must equal the account’s stored mode (#25 AccountModeMismatch) and the spoke must match (#310 SpokeMismatch).
  • initial_payment may be in the collateral asset (added directly), the debt asset (added to the flash amount), or a third asset — which requires convert_swap to convert it to the collateral asset, else #500 ConvertStepsRequired. Supplying one emits InitialMultiplyPaymentEvent.
  • Indexer note: the leverage borrow leg is tagged Multiply (6); the deposit leg is tagged Supply (0).

Swap debt

Borrows amount of new_debt, swaps to existing_debt, repays the old debt; over-repayment is refunded to caller. The two assets must differ and existing_debt’s hub must be active. Indexer note: both legs carry SwDebtR (8) — the new borrow and the old repay.

Swap collateral

Withdraws amount of current, swaps to new, redeposits. The two assets must differ and current’s hub must be active. The new collateral must pass the deposit preflight (collateralizable on the account’s spoke, caps and position limits). Legs are tagged SwColWd (9) and Supply (0).

Repay debt with collateral

Withdraws collateral_amount, swaps to debt, repays. When collateral and debt are the same market the position is netted directly on the pool — no swap, no router trust boundary, and the single leg is tagged RpColNet (14). Otherwise the legs are RpColWd (10) and RpColR (11). close_position = true also withdraws all remaining collateral to caller once no debt remains, giving a one-call full exit (CloseWd, 12). It reverts #122 CannotCloseWithRemainingDebt if borrow positions are still open.

Migrate from Blend

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 for the two-phase submit model, refund reconciliation, and parameter guide.

Strategy borrow vs flash loan

Strategy finalization

Every strategy ends in strategy_finalize: restamp listed collateral risk parameters from the current spoke config, apply the post-pool risk gates (LTV coverage, health factor, and the minimum-borrow-collateral floor), persist, and emit the position batch. All strategy entrypoints are pause-gated and require account owner or delegate authority.

Reentry and trust

Strategy swaps and Blend submit calls reuse the flash-loan reentrancy guard. While either is running, any mutating controller entrypoint reverts with #400 FlashLoanOngoing. A compromised or misconfigured aggregator cannot call back into the protocol mid-swap. The controller authorizes exactly one token pull — total_in of the input token — and checks its own balance deltas afterward. It never trusts the router’s return value.

Failure modes

Next

Blend migration

Atomic Blend V2 to XOXNO position migration.

Flash loans

The single-asset flash loan receiver pattern.

Swap aggregator ABI

The router contract itself: execute_strategy, fees, and referrals.

Stellar Aggregator

The quote service that produces the swap bytes.