Skip to main content
This page is the complete reference for the IFÁ Labs Swap Contract. It covers both the periphery module (ifalabs::hetero_swap_periphery) — the intended entry point for integrators — and the core module (ifalabs::hetero_swap_core), which contains the actual logic, error codes, and admin functions. For the underlying price feed this contract consumes, see Sui Function Reference.

Package Structure

ModuleDescription
ifalabs::hetero_swap_peripheryPublic entry points — deposit, withdraw, swap, sweep, quote. Use this for integration.
ifalabs::hetero_swap_coreCore logic, all error codes, and admin-only functions
ifalabs::hlpThe HLP liquidity provider token type
Every periphery function is a thin wrapper that delegates directly to the matching core function. There is no safety difference between calling periphery vs. core — but periphery is the documented, stable interface.

Data Types

Pool

The central shared object coordinating the swap contract.
public struct Pool has key {
    id:                     UID,
    paused:                 bool,
    lp_treasury_cap:        TreasuryCap<HLP>,
    total_lp_supply:        u64,
    accounted_value_usd:    u256,
    lp_fee_bps:             u64,
    protocol_fee_bps:       u64,
    max_price_age_ms:       u64,
    sync_interval_ms:       u64,
    protocol_fee_recipient: address,
    assets:                 VecSet<TypeName>,
    asset_configs:          Table<TypeName, AssetConfig>,
}
Pass by &mut Pool for state-changing operations and &Pool for read-only quotes and summaries.

AssetConfig

Per-asset configuration stored inside the Pool. Returned as part of get_asset_summary.
public struct AssetConfig has copy, drop, store {
    enabled:           bool,
    asset_index:       Bytes32,  // the oracle asset ID for this token
    coin_decimals:     u8,
    target_weight_bps: u64,
    max_trade_bps:     u64,
    max_withdraw_bps:  u64,
    min_liquidity:     u64,
}

AssetVault<T>

A generic shared object holding token reserves and cached valuation for asset type T.
public struct AssetVault<phantom T> has key, store {
    id:               UID,
    balance:          Balance<T>,
    cached_value_usd: u256,  // updated periodically via maybe_sync_asset
    last_sync_ms:     u64,
}
You must pass the vault matching the coin type you’re operating on — Move’s generics enforce this at compile time.

ProtocolFeeVault<T>

A generic shared object accumulating the protocol’s fee share for asset type T.
public struct ProtocolFeeVault<phantom T> has key, store {
    id:      UID,
    balance: Balance<T>,
}
Required as an argument for swap_exact_input and sweep — not for deposits or withdrawals.

Error Codes

These are the actual error constants from hetero_swap_core. Every abort in the swap contract maps to one of these.
ConstantValueWhen It Fires
E_POOL_PAUSED2Any state-changing call while pool.paused == true
E_ASSET_NOT_WHITELISTED3Asset type T has never been whitelisted in this pool
E_ASSET_DISABLED4Asset is whitelisted but currently disabled (enabled = false)
E_ASSET_ALREADY_WHITELISTED5Admin tried to whitelist an asset that’s already whitelisted
E_INVALID_ASSET_INDEX6Admin passed a raw_asset_index that isn’t exactly 32 bytes during whitelisting
E_INVALID_DECIMALS7A decimals value exceeds MAX_DECIMALS (30) — internal math guard
E_INVALID_FEE8Fee or weight parameter exceeds BPS_DENOMINATOR (10,000), or lp_fee_bps + protocol_fee_bps exceeds MAX_TOTAL_FEE_BPS (100, i.e. 1%)
E_ZERO_AMOUNT9A deposit, withdrawal, or swap amount evaluates to zero
E_SAME_ASSET10swap_exact_input or quote_exact_input called with TIn == TOut
E_ORACLE_PRICE_MISSING11The oracle returned exists = false, or the price is 0
E_ORACLE_PRICE_STALE12The oracle price (or derived pair) is older than pool.max_price_age_ms
E_INSUFFICIENT_LIQUIDITY13The output vault doesn’t hold enough of the requested asset
E_SLIPPAGE14Computed output is less than your min_amount_out / min_lp_out
E_TRADE_TOO_LARGE15Swap output exceeds the asset’s max_trade_bps of available vault liquidity
E_WITHDRAW_TOO_LARGE16Withdrawal exceeds the asset’s max_withdraw_bps of available vault liquidity
E_LP_SUPPLY_ZERO17Withdrawal attempted while total_lp_supply == 0, or deposit math hit a zero-supply edge case
E_MATH_OVERFLOW18Internal fixed-point math overflowed or an accounting invariant failed
E_INVALID_RECIPIENT19Admin passed the zero address as protocol_fee_recipient
E_MIN_LIQUIDITY20A withdrawal or swap would drop a vault below its configured min_liquidity floor
E_ORACLE_PRICE_STALE and E_ORACLE_PRICE_MISSING are the two errors you will hit most often during integration testing. Both originate from the same get_price_checked internal function that every deposit, withdrawal, and swap calls before touching any balances.

Liquidity Functions

deposit_liquidity

Deposits a supported asset into the pool and mints HLP tokens proportional to the deposit’s USD value.
public entry fun deposit_liquidity<T>(
    pool:       &mut Pool,
    vault:      &mut AssetVault<T>,
    feed:       &IfaPriceFeed,
    coin:       Coin<T>,
    min_lp_out: u64,
    clock:      &Clock,
    ctx:        &mut TxContext,
)
Parameters:
ParameterTypeDescription
pool&mut PoolThe shared pool object
vault&mut AssetVault<T>The vault matching the coin type being deposited
feed&IfaPriceFeedThe IFÁ Labs oracle feed object
coinCoin<T>The coin object being deposited — fully consumed
min_lp_outu64Minimum acceptable HLP tokens — slippage protection
clock&ClockThe Sui system Clock object (0x6)
ctx&mut TxContextStandard transaction context
Verified abort sequence:
  1. E_POOL_PAUSED if the pool is paused
  2. E_ASSET_NOT_WHITELISTED / E_ASSET_DISABLED if T isn’t whitelisted or is disabled
  3. E_ZERO_AMOUNT if the deposited coin has zero value
  4. E_ORACLE_PRICE_MISSING / E_ORACLE_PRICE_STALE if the oracle price for T is missing or stale
  5. E_ZERO_AMOUNT if the computed USD value of the deposit is zero
  6. E_LP_SUPPLY_ZERO if this is not the first deposit and accounted_value_usd is somehow zero (an invariant guard, not a normal user-facing case)
  7. E_ZERO_AMOUNT if the resulting HLP mint amount rounds to zero
  8. E_SLIPPAGE if the resulting HLP mint amount is less than min_lp_out
Behaviour notes:
  • The first-ever deposit into the pool mints HLP at a fixed ratio (USD value scaled from 30 decimals down to 9 LP decimals) — there is no existing supply to price against.
  • Every subsequent deposit mints HLP proportional to deposit_value_usd / accounted_value_usd × total_lp_supply.
  • HLP is minted directly to ctx.sender() — you cannot deposit on behalf of another address.
Example (TypeScript):
import { Transaction } from "@mysten/sui/transactions";

const tx = new Transaction();
tx.moveCall({
  target: `${PACKAGE_ID}::hetero_swap_periphery::deposit_liquidity`,
  typeArguments: [SUI_COIN_TYPE],
  arguments: [
    tx.object(POOL_ID),
    tx.object(SUI_VAULT_ID),
    tx.object(FEED_ID),
    tx.object(coinObjectId),
    tx.pure.u64(minLpOut),
    tx.object("0x6"),
  ],
});

withdraw_liquidity

Burns HLP tokens to redeem a proportional share of a specific asset from the pool.
public entry fun withdraw_liquidity<T>(
    pool:           &mut Pool,
    vault:          &mut AssetVault<T>,
    feed:           &IfaPriceFeed,
    lp_coin:        Coin<HLP>,
    min_amount_out: u64,
    clock:          &Clock,
    ctx:            &mut TxContext,
)
Parameters:
ParameterTypeDescription
pool&mut PoolThe shared pool object
vault&mut AssetVault<T>The vault for the asset you want to withdraw
feed&IfaPriceFeedThe oracle feed object
lp_coinCoin<HLP>The HLP tokens being burned — fully consumed
min_amount_outu64Minimum acceptable amount of asset T
clock&ClockThe Sui system Clock object
ctx&mut TxContextStandard transaction context
Verified abort sequence:
  1. E_POOL_PAUSED if the pool is paused
  2. E_ASSET_NOT_WHITELISTED / E_ASSET_DISABLED if T isn’t whitelisted or is disabled
  3. E_ZERO_AMOUNT if lp_coin has zero value
  4. E_LP_SUPPLY_ZERO if total_lp_supply is zero (should never happen if you hold valid HLP)
  5. E_ORACLE_PRICE_MISSING / E_ORACLE_PRICE_STALE for asset T
  6. E_ZERO_AMOUNT if the computed output amount rounds to zero
  7. E_SLIPPAGE if the output is less than min_amount_out
  8. E_INSUFFICIENT_LIQUIDITY if vault doesn’t physically hold enough of asset T
  9. E_WITHDRAW_TOO_LARGE if the withdrawal exceeds max_withdraw_bps of the vault’s current balance
  10. E_MIN_LIQUIDITY if the withdrawal would drop the vault below its configured floor
You choose which asset to receive by which AssetVault<T> you pass — not by anything encoded in the HLP token itself. HLP represents pool-wide ownership, not a claim on any specific asset. A withdrawal can fail with E_INSUFFICIENT_LIQUIDITY even while you hold valid HLP, if the specific vault you’re withdrawing from is thin — even though the pool overall has sufficient value.

Swap Functions

swap_exact_input

Swaps an exact amount of one asset for another, priced via the oracle’s derived pair calculation.
public entry fun swap_exact_input<TIn, TOut>(
    pool:                   &mut Pool,
    vault_in:               &mut AssetVault<TIn>,
    vault_out:              &mut AssetVault<TOut>,
    protocol_fee_vault_out: &mut ProtocolFeeVault<TOut>,
    feed:                   &IfaPriceFeed,
    coin_in:                Coin<TIn>,
    min_amount_out:         u64,
    clock:                  &Clock,
    ctx:                    &mut TxContext,
)
Parameters:
ParameterTypeDescription
pool&mut PoolThe shared pool object
vault_in&mut AssetVault<TIn>Vault for the asset you’re sending
vault_out&mut AssetVault<TOut>Vault for the asset you’re receiving
protocol_fee_vault_out&mut ProtocolFeeVault<TOut>Fee vault for the output asset
feed&IfaPriceFeedThe oracle feed object
coin_inCoin<TIn>The coin being swapped — fully consumed
min_amount_outu64Minimum acceptable amount of TOut
clock&ClockThe Sui system Clock object
ctx&mut TxContextStandard transaction context
Verified abort sequence:
  1. E_POOL_PAUSED if the pool is paused
  2. E_SAME_ASSET if TIn and TOut are the same type
  3. E_ASSET_NOT_WHITELISTED / E_ASSET_DISABLED for either TIn or TOut
  4. E_ZERO_AMOUNT if coin_in has zero value
  5. E_ORACLE_PRICE_STALE if the derived pair’s combined timestamp is stale (checked first, via quote_exact_input_internal)
  6. E_SLIPPAGE if the net output (after both fees) is less than min_amount_out
  7. E_INSUFFICIENT_LIQUIDITY if vault_out doesn’t hold enough for amount_out + protocol_fee
  8. E_TRADE_TOO_LARGE if the raw output exceeds max_trade_bps of vault_out’s balance
  9. E_MIN_LIQUIDITY if the trade would drop vault_out below its floor
  10. E_ORACLE_PRICE_MISSING / E_ORACLE_PRICE_STALE again — re-fetched individually per-asset for USD accounting (in addition to the derived-pair check above)
Fee mechanics (verified from source): The swap computes a raw_amount_out from the oracle-derived exchange rate, then deducts two fees from it:
lp_fee       = raw_amount_out × lp_fee_bps / 10,000
protocol_fee = raw_amount_out × protocol_fee_bps / 10,000
amount_out   = raw_amount_out - lp_fee - protocol_fee
The lp_fee portion stays in vault_out (implicitly benefiting all LPs by not being paid out). The protocol_fee portion is physically moved into protocol_fee_vault_out. Example:
// Swap SUI for CNGN
swap_exact_input<SUI, CNGN>(
    &mut pool,
    &mut sui_vault,
    &mut cngn_vault,
    &mut cngn_protocol_fee_vault,
    &feed,
    sui_coin_in,
    min_cngn_out,
    &clock,
    ctx,
);

sweep

Converts a dust balance of one asset directly into a target asset. This is exposed only in the periphery module — hetero_swap_core has no separate sweep function. It calls swap_exact_input internally with renamed type parameters.
public entry fun sweep<TDust, TTarget>(
    pool:                      &mut Pool,
    dust_vault:                &mut AssetVault<TDust>,
    target_vault:              &mut AssetVault<TTarget>,
    protocol_fee_vault_target: &mut ProtocolFeeVault<TTarget>,
    feed:                      &IfaPriceFeed,
    dust_coin:                 Coin<TDust>,
    min_target_amount_out:     u64,
    clock:                     &Clock,
    ctx:                       &mut TxContext,
)
Behaviour: Identical to swap_exact_input in every respect — same abort sequence, same fee mechanics. There is no relaxed validation path for sweep transactions; it is swap_exact_input under a different name.
To sweep multiple dust tokens into one target asset, compose multiple sweep calls — one per dust token — within a single Sui Programmable Transaction Block (PTB). Each call independently enforces every check listed for swap_exact_input.

Quote Functions (Read-Only)

These simulate the result of an operation without executing it. None mutate state. All still perform the same oracle freshness checks as their state-changing counterparts — a quote can abort with E_ORACLE_PRICE_STALE exactly like a real swap would.

quote_exact_input

public fun quote_exact_input<TIn, TOut>(
    pool:      &Pool,
    feed:      &IfaPriceFeed,
    amount_in: u64,
    clock:     &Clock,
): (u64, u64, u64, u64)
Returns — verified exact order from source:
PositionValueDescription
1straw_amount_outOutput amount before any fees are deducted
2ndlp_feeLP fee amount, in output asset units
3rdprotocol_feeProtocol fee amount, in output asset units
4thamount_outNet output — raw_amount_out - lp_fee - protocol_fee. This is what you actually receive.
Example:
const tx = new Transaction();
tx.moveCall({
  target: `${PACKAGE_ID}::hetero_swap_periphery::quote_exact_input`,
  typeArguments: [SUI_COIN_TYPE, CNGN_COIN_TYPE],
  arguments: [
    tx.object(POOL_ID),
    tx.object(FEED_ID),
    tx.pure.u64(amountIn),
    tx.object("0x6"),
  ],
});

const result = await client.devInspectTransactionBlock({
  transactionBlock: tx,
  sender: callerAddress,
});

// result.results[0].returnValues — four u64 values in the order above
// returnValues[3] is the amount you should use to set min_amount_out
// with an appropriate slippage buffer before submitting the real swap

quote_sweep

Identical signature, identical internal logic to quote_exact_input — provided as a semantically named alias for previewing a sweep operation.
public fun quote_sweep<TDust, TTarget>(
    pool:        &Pool,
    feed:        &IfaPriceFeed,
    dust_amount: u64,
    clock:       &Clock,
): (u64, u64, u64, u64)
Same four-value return order as quote_exact_input.

preview_deposit

public fun preview_deposit<T>(
    pool:   &Pool,
    feed:   &IfaPriceFeed,
    amount: u64,
    clock:  &Clock,
): u64
Returns: A single u64 — the expected HLP token amount to be minted. Uses the exact same math as deposit_liquidity, so this value should match what you actually receive (assuming no price change between the quote and the real transaction).

preview_withdraw

public fun preview_withdraw<T>(
    pool:      &Pool,
    feed:      &IfaPriceFeed,
    lp_amount: u64,
    clock:     &Clock,
): u64
Returns: A single u64 — the expected amount of asset T you’d receive for burning lp_amount of HLP.
preview_withdraw does not check whether the target vault actually holds enough of asset T to fulfill the withdrawal, nor does it check max_withdraw_bps or min_liquidity. It only computes the proportional USD value and converts it to asset T. A real withdraw_liquidity call with the same inputs can still abort with E_INSUFFICIENT_LIQUIDITY, E_WITHDRAW_TOO_LARGE, or E_MIN_LIQUIDITY even if the preview succeeded.

Pool and Asset Inspection

get_pool_summary

public fun get_pool_summary(pool: &Pool): (
    bool,    // paused
    u64,     // total_lp_supply
    u256,    // accounted_value_usd
    u64,     // lp_fee_bps
    u64,     // protocol_fee_bps
    u64,     // max_price_age_ms
    u64,     // sync_interval_ms
    address, // protocol_fee_recipient
    u64,     // number of whitelisted assets (vec_set::length)
)
Returns — verified exact order from source. Note this differs from a naive guess: total_lp_supply and accounted_value_usd come before the fee fields, not after.

get_asset_summary

public fun get_asset_summary<T>(
    pool:      &Pool,
    vault:     &AssetVault<T>,
    fee_vault: &ProtocolFeeVault<T>,
): (
    bool,        // is_whitelisted<T>(pool)
    AssetConfig, // full per-asset config struct
    u64,         // vault.balance (raw token amount currently held)
    u256,        // vault.cached_value_usd (last synced USD value, NOT live)
    u64,         // vault.last_sync_ms
    u64,         // fee_vault.balance (accumulated protocol fees, raw token amount)
)
The u256 value at position 4 is vault.cached_value_usd — a cached value updated only when maybe_sync_asset runs (on deposits, withdrawals, swaps, or an explicit sync_asset call, and only if sync_interval_ms has elapsed since the last sync). It is not necessarily the vault’s live USD value at query time. For an always-current value, compute it yourself from vault_balance<T>(vault) and a fresh oracle price.

Other Read-Only Functions

These are simpler getters also exposed by hetero_swap_core, useful for building dashboards without needing the full summary structs.
FunctionReturnsDescription
is_whitelisted<T>(pool)boolWhether asset T has ever been whitelisted
is_enabled<T>(pool)boolWhether asset T is whitelisted and currently enabled
get_asset_config<T>(pool)AssetConfigThe full config struct for asset T. Aborts E_ASSET_NOT_WHITELISTED if never whitelisted.
vault_balance<T>(vault)u64Current raw token balance in the vault — always live, not cached
protocol_fee_balance<T>(fee_vault)u64Current accumulated protocol fee balance for asset T
total_lp_supply(pool)u64Current total HLP supply
accounted_value_usd(pool)u256Pool-wide accounted USD value, scaled to 30 decimals
fees(pool)(u64, u64)(lp_fee_bps, protocol_fee_bps)
sync_interval_ms(pool)u64Current sync interval in milliseconds
max_price_age_ms(pool)u64Current oracle staleness threshold in milliseconds
cached_value_usd<T>(vault)u256The vault’s last-synced cached USD value (same caveat as above)
last_sync_ms<T>(vault)u64Timestamp of the vault’s last sync

Admin-Only Functions

These all require the AdminCap — held by the IFÁ Labs team. Listed for transparency and so you understand what configuration changes are possible, not because integrators call them.
FunctionEffect
create_poolOne-time pool creation. Validates lp_fee_bps + protocol_fee_bps ≤ 100 (1%).
create_asset_vault<T>Creates an empty AssetVault<T> shared object for a new asset
create_protocol_fee_vault<T>Creates an empty ProtocolFeeVault<T> shared object
whitelist_asset<T>Registers asset T with its oracle index, decimals, target weight, trade/withdraw limits, and minimum liquidity. Aborts E_ASSET_ALREADY_WHITELISTED if called twice for the same type.
set_asset_enabled<T>Enables or disables trading for an already-whitelisted asset, without removing its configuration
set_feesUpdates lp_fee_bps and protocol_fee_bps. Same ≤ 100 bps total validation as pool creation.
set_max_price_age_msUpdates the pool-wide oracle staleness threshold
set_sync_interval_msUpdates how often maybe_sync_asset actually re-syncs cached vault values
pause / unpauseGlobal kill switch — pause sets E_POOL_PAUSED to fire on every state-changing call
collect_protocol_fees<T>Sweeps the entire ProtocolFeeVault<T> balance to pool.protocol_fee_recipient
sync_asset<T>Manually triggers maybe_sync_asset for a specific vault, outside the normal deposit/withdraw/swap flow
Notice pause is a true global switch — there is no per-asset pause. If you need to react to a paused pool in your own integration, check get_pool_summary().0 (the first return value) before attempting any operation.

Function Quick Reference

FunctionTypeMutates StateCan Abort On
deposit_liquidity<T>EntryYesPause, whitelist/disabled, zero amount, stale price, slippage
withdraw_liquidity<T>EntryYesPause, whitelist/disabled, zero amount, LP supply zero, stale price, slippage, insufficient liquidity, withdraw-too-large, min-liquidity
swap_exact_input<TIn, TOut>EntryYesPause, same-asset, whitelist/disabled, zero amount, stale price, slippage, insufficient liquidity, trade-too-large, min-liquidity
sweep<TDust, TTarget>EntryYesIdentical to swap_exact_input
quote_exact_input<TIn, TOut>Read-onlyNoSame-asset, zero amount, stale price
quote_sweep<TDust, TTarget>Read-onlyNoSame as quote_exact_input
preview_deposit<T>Read-onlyNoZero amount, stale price
preview_withdraw<T>Read-onlyNoZero amount, LP supply zero, stale price
get_pool_summaryRead-onlyNoNever
get_asset_summary<T>Read-onlyNoAsset not whitelisted
is_whitelisted<T> / is_enabled<T>Read-onlyNoNever
vault_balance<T> / protocol_fee_balance<T>Read-onlyNoNever

Next Steps

Sui Swap Contract Overview

Architecture, core concepts, and what’s deployed on testnet.

Swap Contract Addresses

Package ID, Pool ID, AdminCap, and every asset vault on Sui Testnet.

Sui Function Reference

The underlying oracle functions this contract consumes.

Testnet Faucet

Claim testnet tokens to try deposits and swaps yourself.