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

# Swap aggregator ABI

> The on-chain router contract: execute_strategy, the fee and referral model, owner-only administration, and open reads.

The swap aggregator is the on-chain router that executes swap routes. Controller
strategies call it, and **you can call it directly** — it is not restricted to
protocol use.

<Note>
  This page documents the **contract**. For the off-chain quote service that
  *produces* the route bytes — the HTTP API, token coverage, and prepared
  transaction envelopes — see
  [Stellar Aggregator](/docs/stellar-aggregator/overview). You will normally use both:
  the service builds the payload, this contract executes it.
</Note>

<Warning>
  The lending protocol treats this contract as **untrusted**. The controller grants
  it authority for one stated input amount, ignores whatever it returns, and
  settles on measured balance deltas instead. It is also **not owned by
  governance** — it has its own owner and its own two-step ownership transfer.
</Warning>

## The one entry point that matters

```rust theme={"system"}
execute_strategy(sender: Address, total_in: i128, swap_xdr: Bytes) -> i128
```

It decodes `swap_xdr` as a `StrategyPayload`, pulls `total_in` from `sender`,
runs any LP burn, then the swap paths, then any LP mint, applies fees, enforces
the minimum output, and returns the delivered amount.

Anyone may call it. `sender` authorizes the pull.

A payload that fails to decode raises `#13 InvalidRouteXdr`. The payload's shape
— two registries plus a packed instruction stream — is documented in
[Strategies](/docs/stellar-lending/dev/strategies#the-aggregator-boundary).

<Warning>
  **Slippage is enforced here, not by the controller.** Your minimum output lives
  inside the payload bytes, and `execute_strategy` is the only thing that checks
  it. Falling short raises `#5 SlippageExceeded`. Build that floor correctly in
  your quote — nothing downstream will catch a bad price for you.
</Warning>

## Fees

Two fees can apply, both in basis points, both capped at **`FEE_CAP = 1000`**
(10%). Exceeding the cap raises `#21 FeeTooHigh`.

| Fee          | Set by                                                  | Paid to                          |
| ------------ | ------------------------------------------------------- | -------------------------------- |
| Static fee   | Contract owner, via `set_static_fee`                    | The contract owner's fee bucket  |
| Referral fee | Contract owner, via `add_referral` / `set_referral_fee` | That referral's configured owner |

A payload carries a referral id in its header (`u32`, where `0` means none). Fees
accrue into per-token buckets and are claimed separately — they are not swept
into the swap output.

## Claiming fees

| Function                              | Auth       | Notes                                                                                                                              |
| ------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `claim_referral_fees(id, tokens)`     | **Anyone** | Pays that referral's accrued balances to its **configured owner**, not to the caller. The only claim path that is not owner-gated. |
| `claim_admin_fees(recipient, tokens)` | Owner only | Pays the static-fee bucket to `recipient`.                                                                                         |

<Note>
  `claim_referral_fees` being permissionless is safe by construction: the recipient
  is read from stored referral config, so calling it on someone else's behalf just
  pays them. You cannot redirect it.
</Note>

## Owner-only administration

All of these carry `#[only_owner]`.

| Function                                                   | Purpose                                                       |
| ---------------------------------------------------------- | ------------------------------------------------------------- |
| `set_static_fee(fee_bps)`                                  | Sets the protocol-wide static fee. Capped at 1000 bps.        |
| `add_to_whitelist(token)` / `remove_from_whitelist(token)` | Manages the token whitelist.                                  |
| `add_referral(owner, fee_bps) -> u64`                      | Registers a referral and returns its id. Capped at 1000 bps.  |
| `set_referral_fee(id, fee_bps)`                            | Changes a referral's rate.                                    |
| `set_referral_active(id, active)`                          | Enables or disables a referral.                               |
| `set_referral_owner(id, new_owner)`                        | Re-points where that referral's fees go.                      |
| `claim_admin_fees(recipient, tokens)`                      | Pays out the static-fee bucket.                               |
| `sweep_balance(recipient, tokens)`                         | Recovers stray token balances. **Leaves fee buckets intact.** |
| `upgrade(new_wasm_hash)`                                   | Replaces the contract Wasm.                                   |

Ownership is the standard two-step: `transfer_ownership(new_owner,
live_until_ledger)`, then `accept_ownership()` by the incoming owner.
`get_owner()` returns `None` if ownership was renounced or never set.

## Open reads

| Function                                  | Returns                                     |
| ----------------------------------------- | ------------------------------------------- |
| `admin() -> Address`                      | The configured admin.                       |
| `get_owner() -> Option<Address>`          | Current owner, or `None`.                   |
| `static_fee_bps() -> u32`                 | The static fee.                             |
| `referral(id) -> Option<ReferralConfig>`  | A referral's owner, rate, and active flag.  |
| `referral_counter() -> u64`               | How many referrals have been registered.    |
| `is_whitelisted(token) -> bool`           | Whitelist membership.                       |
| `whitelisted_tokens() -> Vec<Address>`    | The full whitelist.                         |
| `admin_fee_balance(token) -> i128`        | Unclaimed static-fee balance for one token. |
| `referral_fee_balance(id, token) -> i128` | Unclaimed referral balance for one token.   |

## Error codes

This contract uses **its own** error enum, unrelated to the lending protocol's
codes. A `#5` here is not a `#5` from the controller.

| Code | Name                | Meaning                                             |
| ---- | ------------------- | --------------------------------------------------- |
| `1`  | `EmptyBatch`        | The route contained no instructions.                |
| `3`  | `InvalidAmount`     | A declared amount is zero or negative.              |
| `4`  | `BrokenTokenChain`  | One hop's output token is not the next hop's input. |
| `5`  | `SlippageExceeded`  | Delivered output fell below the payload's minimum.  |
| `7`  | `ZeroOutput`        | The route produced nothing.                         |
| `9`  | `IntegerOverflow`   | Arithmetic overflowed.                              |
| `11` | `ZeroSplitPpm`      | A split weight was zero.                            |
| `12` | `SplitPpmMismatch`  | Split weights do not sum to `1_000_000`.            |
| `13` | `InvalidRouteXdr`   | `swap_xdr` failed to decode as a `StrategyPayload`. |
| `20` | `NotAdmin`          | Caller is not the admin.                            |
| `21` | `FeeTooHigh`        | A fee exceeds `FEE_CAP = 1000` bps.                 |
| `22` | `ReferralNotFound`  | No referral registered under that id.               |
| `25` | `SameToken`         | A hop's input and output token are the same.        |
| `26` | `LpTokenMismatch`   | The LP share token does not match the pool.         |
| `27` | `MinSharesNotMet`   | An LP mint produced fewer shares than required.     |
| `28` | `MinAmountsNotMet`  | An LP burn returned less than required.             |
| `29` | `ExcessiveResidual` | Too much input was left unspent.                    |
| `30` | `InternalInvariant` | An internal invariant failed. Report it.            |

## Next

<CardGroup cols={2}>
  <Card title="Strategies" icon="layer-group" href="/docs/stellar-lending/dev/strategies">
    How controller strategies call this contract, and the payload layout.
  </Card>

  <Card title="Stellar Aggregator" icon="orbit" href="/docs/stellar-aggregator/overview">
    The quote service that builds the route bytes.
  </Card>

  <Card title="Security model" icon="shield-halved" href="/docs/stellar-lending/dev/security-model">
    Why the controller treats this contract as untrusted.
  </Card>

  <Card title="Addresses" icon="address-book" href="/docs/stellar-lending/dev/addresses">
    Deployed swap-aggregator addresses per network.
  </Card>
</CardGroup>
