S4 — SPLITTER. Execute a full VFalgo plan in ONE call: parallel strands (split), each with sequential hops (multi-hop), across any mix of Soroswap / Aquarius / Phoenix. Slippage is enforced on the SUM of all strand outputs (atomic across the whole plan).
strand_in = floor(amount_in * parts / total_parts); the last strand takes the remainder so the split sums to amount_in exactly. Within a strand, each hop consumes the previous hop's output.
fn swap(
env: soroban_sdk::Env,
user: soroban_sdk::Address,
token_in: soroban_sdk::Address,
token_out: soroban_sdk::Address,
amount_in: i128,
amount_out_min: i128,
deadline: u64,
plan: soroban_sdk::Vec,
) -> i128
One Aquarius swap through the router's swap_chained.
pool_tokens — the pool's ordered token vector (canonical, by
contract-id). For USDC/AQUA: [AQUA_SAC, USDC_SAC].
pool_index — the pool hash (BytesN<32>) from get_pools.
pool — the pool contract address (the router pulls
token_in into it; used for the auth subtree).
token_in/out — SAC contract ids.
swap_chained(user, swaps_chain, token_in, in_amount, out_min): swaps_chain = [ (pool_tokens, pool_index, token_out) ] (single hop)
Returns the amount of token_out delivered (u128 -> i128).
fn swap_aqua(
env: soroban_sdk::Env,
user: soroban_sdk::Address,
aqua_router: soroban_sdk::Address,
pool: soroban_sdk::Address,
pool_tokens: soroban_sdk::Vec,
pool_index: soroban_sdk::BytesN<32>,
token_in: soroban_sdk::Address,
token_out: soroban_sdk::Address,
amount_in: i128,
amount_out_min: i128,
) -> i128
One Phoenix swap. Phoenix trades through the POOL contract directly (no router), unlike Soroswap/Aquarius.
pool.swap(sender, offer_asset, offer_amount, max_belief_price: Option<i64>, max_spread_bps: Option<i64>) -> i128 (amount of the other asset received)
sender is the CONTRACT (it holds the funds and receives output).
We pass None/None for price & spread limits; the final slippage
guard enforces amount_out_min end-to-end.
fn swap_phoenix(
env: soroban_sdk::Env,
user: soroban_sdk::Address,
pool: soroban_sdk::Address,
token_in: soroban_sdk::Address,
token_out: soroban_sdk::Address,
amount_in: i128,
amount_out_min: i128,
) -> i128
One Soroswap swap along path. (S1 — proven on mainnet.)
fn swap_soroswap(
env: soroban_sdk::Env,
user: soroban_sdk::Address,
soroswap_router: soroban_sdk::Address,
pool: soroban_sdk::Address,
token_in: soroban_sdk::Address,
token_out: soroban_sdk::Address,
amount_in: i128,
amount_out_min: i128,
path: soroban_sdk::Vec,
deadline: u64,
) -> i128
CROSS-PROTOCOL chain in ONE call (S3): leg 1 Aquarius, leg 2 Soroswap. token_in --[aqua]--> mid_token --[soroswap]--> token_out.
The contract holds the intermediate (mid_token) and feeds its ACTUAL balance into leg 2 — the exact mechanic the parts splitter (S4) generalizes. Soroswap's own aggregator cannot do this: one DexDistribution path is single-protocol.
Auth targets are the ones proven on mainnet:
fn swap_aqua_then_soroswap(
env: soroban_sdk::Env,
user: soroban_sdk::Address,
aqua_router: soroban_sdk::Address,
aqua_pool_tokens: soroban_sdk::Vec,
aqua_pool_index: soroban_sdk::BytesN<32>,
token_in: soroban_sdk::Address,
mid_token: soroban_sdk::Address,
soroswap_router: soroban_sdk::Address,
soroswap_pool: soroban_sdk::Address,
soroswap_path: soroban_sdk::Vec,
token_out: soroban_sdk::Address,
amount_in: i128,
amount_out_min: i128,
deadline: u64,
) -> i128