Overview
The swap instruction is the core trading operation of the Multiliquid Program, enabling atomic swaps between Real World Asset (RWA) tokens and stablecoins. It supports both swap directions and both exact-in and exact-out modes.
Key Responsibilities
Execute bidirectional swaps between RWAs and stablecoins
Calculate swap amounts using NAV-based pricing
Apply protocol fees and LP fees
Validate all pause states before execution
Emit comprehensive swap events
Swap Instruction
Function Signature
pub fn swap (
ctx : Context < Swap >,
amount : u64 ,
min_amount_out : Option < u64 >,
max_amount_in : Option < u64 >,
swap_direction : SwapDirection ,
swap_type : SwapType ,
) -> Result <()>
Parameters
Parameter Type Description amountu64Primary amount (input for ExactIn, output for ExactOut) min_amount_outOption<u64>Minimum output (slippage protection for ExactIn) max_amount_inOption<u64>Maximum input (slippage protection for ExactOut) swap_directionSwapDirectionDirection of the swap swap_typeSwapTypeExactIn or ExactOut mode
Swap Direction
pub enum SwapDirection {
StableToAsset , // Stablecoin → RWA
AssetToStable , // RWA → Stablecoin
}
Swap Type
pub enum SwapType {
ExactIn , // User specifies exact input amount
ExactOut , // User specifies exact output amount
}
Required Accounts
#[derive( Accounts )]
pub struct Swap <' info > {
// Signer
pub user : Signer <' info >,
// Pair account (PDA seeded by liquidity_provider, stable mint, asset mint)
pub pair : Account <' info , Pair >,
// Program authority PDA (signs vault transfers)
pub program_authority : UncheckedAccount <' info >,
// Liquidity provider wallet (referenced in PDA seeds)
pub liquidity_provider : UncheckedAccount <' info >,
// Token mints
pub stable_coin_mint_address : InterfaceAccount <' info , Mint >,
pub asset_token_mint_address : InterfaceAccount <' info , Mint >,
// User token accounts
pub asset_token_user_token_account : InterfaceAccount <' info , TokenAccount >,
pub stable_coin_user_token_account : InterfaceAccount <' info , TokenAccount >,
// Vault accounts (PDA-owned, seeded by mint + liquidity_provider)
pub stable_coin_vault_token_account : InterfaceAccount <' info , TokenAccount >,
pub asset_token_vault_token_account : InterfaceAccount <' info , TokenAccount >,
// Configuration accounts
pub global_config : Account <' info , GlobalConfig >,
pub fee_token_account : InterfaceAccount <' info , TokenAccount >,
pub rwa_config : Account <' info , AssetConfig >,
pub stable_config : Account <' info , AssetConfig >,
pub lp_stable_config : Account <' info , LpStableConfig >,
// Token programs (one per mint, supporting Token and Token-2022)
pub token_program_stable : Interface <' info , TokenInterface >,
pub token_program_asset : Interface <' info , TokenInterface >,
// NAV accounts passed via remaining_accounts
// ... up to 5 NAV source accounts per asset
}
Validation Checks
Before executing the swap, the instruction validates:
Pause States : Program, both assets, pair, and LP config must be unpaused
Asset Types : RWA must be AssetType::Rwa, stablecoin must be AssetType::Stable
Amount : Must be greater than zero
NAV Prices : Both assets must return valid, non-zero NAV prices
Slippage : Calculated output/input must satisfy min/max constraints
Events
#[event]
pub struct SwapExecuted {
pub requestor : Pubkey , // User wallet
pub amount_in : u64 , // Actual input amount
pub protocol_fee_amount : u64 , // Protocol fees collected
pub discount_bps : u16 , // LP fee applied (redemption or discount)
pub amount_out : u64 , // Actual output amount
pub pair : Pubkey , // Pair account address
pub swap_direction : SwapDirection ,
pub swap_type : SwapType ,
}
Swap Calculation
NAV-Based Pricing
Deterministic Pricing : The Multiliquid Program uses NAV-based pricing, not AMM-style liquidity curves. Prices are determined by asset NAV values, not by liquidity depth. There is no price impact from trade size.
The swap amount is calculated using the exchange rate between asset NAVs:
output_amount = input_amount × input_nav / output_nav
Fee Application
Fees are applied in a specific order depending on swap direction:
StableToAsset (Stablecoin → RWA)
Combined Fees : Protocol fees and redemption fee are calculated simultaneously from the input amount
Exchange Rate : Apply NAV ratio to the post-fee amount to calculate RWA output
total_fees_bps = protocol_fees_bps + redemption_fee_bps
protocol_fees = ceil(amount × protocol_fees_bps / 10000)
total_fees = ceil(amount × total_fees_bps / 10000)
amount_after_fees = amount - total_fees
rwa_output = amount_after_fees × stable_nav / rwa_nav
AssetToStable (RWA → Stablecoin)
Exchange Rate : Apply NAV ratio to calculate stablecoin equivalent
Discount Rate : Apply LP discount fee
Protocol Fees : Deducted from discounted amount
stable_equivalent = rwa_amount × rwa_nav / stable_nav
after_discount = stable_equivalent - (stable_equivalent × discount_rate_bps / 10000)
stable_output = after_discount - protocol_fee
Decimal Normalization
All calculations use 128-bit arithmetic with proper decimal handling:
NAV prices normalized to 9 decimals
Intermediate calculations use combined decimal precision
Final amounts rounded appropriately (ceil for fees, floor for outputs)
Usage Examples
ExactIn: Stable → RWA
Swap exactly 1000 USDC for RWA tokens with minimum output protection:
import { BN } from "@coral-xyz/anchor" ;
const amount = new BN ( 1000_000000 ); // 1000 USDC (6 decimals)
const minAmountOut = new BN ( 990_000000 ); // Minimum 990 RWA tokens
await program . methods
. swap (
amount ,
minAmountOut , // min_amount_out
null , // max_amount_in (not used for ExactIn)
{ stableToAsset: {} },
{ exactIn: {} }
)
. accounts ({
user: wallet . publicKey ,
pair ,
programAuthority ,
liquidityProvider ,
stableCoinMintAddress ,
assetTokenMintAddress ,
assetTokenUserTokenAccount ,
stableCoinUserTokenAccount ,
stableCoinVaultTokenAccount ,
assetTokenVaultTokenAccount ,
globalConfig ,
feeTokenAccount ,
rwaConfig ,
stableConfig ,
lpStableConfig ,
tokenProgramStable: TOKEN_PROGRAM_ID ,
tokenProgramAsset: TOKEN_PROGRAM_ID ,
})
. remainingAccounts ( navAccounts ) // NAV source accounts
. rpc ();
ExactOut: Stable → RWA
Receive exactly 1000 RWA tokens, spending at most a specified USDC amount:
const amount = new BN ( 1000_000000 ); // Exact 1000 RWA tokens out
const maxAmountIn = new BN ( 1010_000000 ); // Maximum 1010 USDC in
await program . methods
. swap (
amount ,
null , // min_amount_out (not used for ExactOut)
maxAmountIn , // max_amount_in
{ stableToAsset: {} },
{ exactOut: {} }
)
. accounts ({
// ... same accounts as above
})
. remainingAccounts ( navAccounts )
. rpc ();
ExactIn: RWA → Stable
Swap exactly 1000 RWA tokens for stablecoins:
const amount = new BN ( 1000_000000 ); // 1000 RWA tokens
const minAmountOut = new BN ( 995_000000 ); // Minimum 995 USDC
await program . methods
. swap (
amount ,
minAmountOut ,
null ,
{ assetToStable: {} },
{ exactIn: {} }
)
. accounts ({
// ... accounts
})
. remainingAccounts ( navAccounts )
. rpc ();
ExactOut: RWA → Stable
Receive exactly 1000 USDC, spending at most a specified RWA amount:
const amount = new BN ( 1000_000000 ); // Exact 1000 USDC out
const maxAmountIn = new BN ( 1005_000000 ); // Maximum 1005 RWA in
await program . methods
. swap (
amount ,
null ,
maxAmountIn ,
{ assetToStable: {} },
{ exactOut: {} }
)
. accounts ({
// ... accounts
})
. remainingAccounts ( navAccounts )
. rpc ();
Error Codes
Error Description ProgramPausedProgram is paused RwaPausedRWA asset is paused StablePausedStablecoin or LP stable config is paused PairPausedTrading pair is paused AmountMustBePositiveAmount is zero InvalidAssetTypeAsset types don’t match expected (RWA vs Stable) AmountOutTooLowOutput less than minimum AmountInTooHighInput more than maximum InvalidNavNAV sources returned invalid or divergent data MathOverflowCalculation overflow
Access Control
Access : Permissionless
Any wallet can execute swaps provided:
All pause states are inactive
User has sufficient token balance
User has approved token transfers
Valid NAV prices are available
Next: Global Configuration Learn about program-wide configuration management