Complete reference for every public function in the IFÁ Labs Sui Swap Contract — deposit, withdraw, swap, sweep, and quote functions with verified signatures, error codes, and examples.
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.
Public entry points — deposit, withdraw, swap, sweep, quote. Use this for integration.
ifalabs::hetero_swap_core
Core logic, all error codes, and admin-only functions
ifalabs::hlp
The 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.
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,}
These are the actual error constants from hetero_swap_core. Every abort in the swap contract maps to one of these.
Constant
Value
When It Fires
E_POOL_PAUSED
2
Any state-changing call while pool.paused == true
E_ASSET_NOT_WHITELISTED
3
Asset type T has never been whitelisted in this pool
E_ASSET_DISABLED
4
Asset is whitelisted but currently disabled (enabled = false)
E_ASSET_ALREADY_WHITELISTED
5
Admin tried to whitelist an asset that’s already whitelisted
E_INVALID_ASSET_INDEX
6
Admin passed a raw_asset_index that isn’t exactly 32 bytes during whitelisting
E_INVALID_DECIMALS
7
A decimals value exceeds MAX_DECIMALS (30) — internal math guard
E_INVALID_FEE
8
Fee 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_AMOUNT
9
A deposit, withdrawal, or swap amount evaluates to zero
E_SAME_ASSET
10
swap_exact_input or quote_exact_input called with TIn == TOut
E_ORACLE_PRICE_MISSING
11
The oracle returned exists = false, or the price is 0
E_ORACLE_PRICE_STALE
12
The oracle price (or derived pair) is older than pool.max_price_age_ms
E_INSUFFICIENT_LIQUIDITY
13
The output vault doesn’t hold enough of the requested asset
E_SLIPPAGE
14
Computed output is less than your min_amount_out / min_lp_out
E_TRADE_TOO_LARGE
15
Swap output exceeds the asset’s max_trade_bps of available vault liquidity
E_WITHDRAW_TOO_LARGE
16
Withdrawal exceeds the asset’s max_withdraw_bps of available vault liquidity
E_LP_SUPPLY_ZERO
17
Withdrawal attempted while total_lp_supply == 0, or deposit math hit a zero-supply edge case
E_MATH_OVERFLOW
18
Internal fixed-point math overflowed or an accounting invariant failed
E_INVALID_RECIPIENT
19
Admin passed the zero address as protocol_fee_recipient
E_MIN_LIQUIDITY
20
A 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.
E_ASSET_NOT_WHITELISTED / E_ASSET_DISABLED if T isn’t whitelisted or is disabled
E_ZERO_AMOUNT if the deposited coin has zero value
E_ORACLE_PRICE_MISSING / E_ORACLE_PRICE_STALE if the oracle price for T is missing or stale
E_ZERO_AMOUNT if the computed USD value of the deposit is zero
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)
E_ZERO_AMOUNT if the resulting HLP mint amount rounds to zero
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.
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:
Parameter
Type
Description
pool
&mut Pool
The shared pool object
vault
&mut AssetVault<T>
The vault for the asset you want to withdraw
feed
&IfaPriceFeed
The oracle feed object
lp_coin
Coin<HLP>
The HLP tokens being burned — fully consumed
min_amount_out
u64
Minimum acceptable amount of asset T
clock
&Clock
The Sui system Clock object
ctx
&mut TxContext
Standard transaction context
Verified abort sequence:
E_POOL_PAUSED if the pool is paused
E_ASSET_NOT_WHITELISTED / E_ASSET_DISABLED if T isn’t whitelisted or is disabled
E_ZERO_AMOUNT if lp_coin has zero value
E_LP_SUPPLY_ZERO if total_lp_supply is zero (should never happen if you hold valid HLP)
E_ORACLE_PRICE_MISSING / E_ORACLE_PRICE_STALE for asset T
E_ZERO_AMOUNT if the computed output amount rounds to zero
E_SLIPPAGE if the output is less than min_amount_out
E_INSUFFICIENT_LIQUIDITY if vault doesn’t physically hold enough of asset T
E_WITHDRAW_TOO_LARGE if the withdrawal exceeds max_withdraw_bps of the vault’s current balance
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.
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:
Parameter
Type
Description
pool
&mut Pool
The 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
&IfaPriceFeed
The oracle feed object
coin_in
Coin<TIn>
The coin being swapped — fully consumed
min_amount_out
u64
Minimum acceptable amount of TOut
clock
&Clock
The Sui system Clock object
ctx
&mut TxContext
Standard transaction context
Verified abort sequence:
E_POOL_PAUSED if the pool is paused
E_SAME_ASSET if TIn and TOut are the same type
E_ASSET_NOT_WHITELISTED / E_ASSET_DISABLED for either TIn or TOut
E_ZERO_AMOUNT if coin_in has zero value
E_ORACLE_PRICE_STALE if the derived pair’s combined timestamp is stale (checked first, via quote_exact_input_internal)
E_SLIPPAGE if the net output (after both fees) is less than min_amount_out
E_INSUFFICIENT_LIQUIDITY if vault_out doesn’t hold enough for amount_out + protocol_fee
E_TRADE_TOO_LARGE if the raw output exceeds max_trade_bps of vault_out’s balance
E_MIN_LIQUIDITY if the trade would drop vault_out below its floor
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:
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 CNGNswap_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,);
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.
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.
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:
Position
Value
Description
1st
raw_amount_out
Output amount before any fees are deducted
2nd
lp_fee
LP fee amount, in output asset units
3rd
protocol_fee
Protocol fee amount, in output asset units
4th
amount_out
Net 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
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).
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.
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.
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.
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.
Function
Effect
create_pool
One-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_fees
Updates lp_fee_bps and protocol_fee_bps. Same ≤ 100 bps total validation as pool creation.
set_max_price_age_ms
Updates the pool-wide oracle staleness threshold
set_sync_interval_ms
Updates how often maybe_sync_asset actually re-syncs cached vault values
pause / unpause
Global 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.