Overview
Pair accounts link RWA tokens to stablecoins, enabling trading between them. Each pair is owned by a specific Liquidity Provider (LP) who controls its configuration, fees, and liquidity.
Account Structures
Pair Account
pub struct Pair {
pub redemption_fee_bps : u16 , // Fee for Stable → RWA swaps
pub discount_rate_bps : u16 , // Fee for RWA → Stable swaps
pub stable_coin_mint_address : Pubkey , // Stablecoin token mint
pub asset_token_mint_address : Pubkey , // RWA token mint
pub liquidity_provider : Pubkey , // LP owner address
pub paused : bool , // Pair-level pause flag
pub bump : u8 , // PDA bump seed
}
PDA Seeds : ["pair", liquidity_provider, stable_mint, asset_mint]
LpStableConfig Account
pub struct LpStableConfig {
pub stable_coin_mint_address : Pubkey , // Stablecoin mint
pub paused : bool , // Pause all pairs with this config
pub liquidity_provider : Pubkey , // LP address
pub bump : u8 , // PDA bump seed
}
PDA Seeds : ["lp_stable_config", stable_mint, liquidity_provider]
UserVaultInfo Account
pub struct UserVaultInfo {
pub user : Pubkey , // LP wallet
pub mint_address : Pubkey , // Token mint
pub used : u16 , // Number of pairs using this vault
pub bump : u8 , // PDA bump seed
}
PDA Seeds : ["user_vault_info", mint_address, liquidity_provider]
Pair Instructions
init_pair
Create a new trading pair for an LP.
pub fn init_pair (
ctx : Context < InitPair >,
liquidity_provider : Pubkey ,
redemption_fee_bps : u16 ,
discount_rate_bps : u16 ,
) -> Result <()>
Parameters
Parameter Type Description liquidity_providerPubkeyLP wallet that will control the pair redemption_fee_bpsu16Fee for Stable → RWA (0-10000 BPS) discount_rate_bpsu16Fee for RWA → Stable (0-10000 BPS)
Required Accounts
#[derive( Accounts )]
pub struct InitPair <' info > {
pub global_config : Account <' info , GlobalConfig >,
#[account(
seeds = [ ASSET_PREFIX , stable_mint . key() . as_ref()],
bump = stable_asset_config . bump,
constraint = stable_asset_config . asset_type == AssetType :: Stable
)]
pub stable_asset_config : Account <' info , AssetConfig >,
#[account(
seeds = [ ASSET_PREFIX , asset_mint . key() . as_ref()],
bump = rwa_asset_config . bump,
constraint = rwa_asset_config . asset_type == AssetType :: Rwa
)]
pub rwa_asset_config : Account <' info , AssetConfig >,
#[account(
init,
payer = payer,
space = 8 + Pair :: INIT_SPACE ,
seeds = [ PAIR_PREFIX , liquidity_provider . as_ref(), stable_mint . key() . as_ref(), asset_mint . key() . as_ref()],
bump ,
)]
pub pair : Account <' info , Pair >,
// LP stable config (created if needed)
#[account(
init_if_needed,
payer = payer,
space = 8 + LpStableConfig :: INIT_SPACE ,
seeds = [ LP_STABLE_CONFIG_PREFIX , stable_mint . key() . as_ref(), liquidity_provider . as_ref()],
bump ,
)]
pub lp_stable_config : Account <' info , LpStableConfig >,
// Vaults and vault info accounts...
// (created if needed for RWA and stablecoin)
pub stable_mint : InterfaceAccount <' info , Mint >,
pub asset_mint : InterfaceAccount <' info , Mint >,
#[account(
constraint = global_config . admin == admin . key()
@ MultiliquidError :: Unauthorized
)]
pub admin : Signer <' info >,
#[account( mut )]
pub payer : Signer <' info >,
pub system_program : Program <' info , System >,
pub token_program : Interface <' info , TokenInterface >,
pub stable_token_program : Interface <' info , TokenInterface >,
}
Behavior
Creates Pair PDA account
Creates or updates LpStableConfig for LP/stablecoin
Creates vault accounts if they don’t exist
Creates UserVaultInfo accounts to track vault usage
Increments used_in_pairs_count on both asset configs
Pair starts paused by default
Access Control
Access : Admin only
Pairs start paused by default. The LP must call update_pair to set fees and unpause the pair before trading can begin.
Example
await program . methods
. initPair (
liquidityProviderPubkey ,
50 , // 0.5% redemption fee
25 // 0.25% discount rate
)
. accounts ({
globalConfig ,
stableAssetConfig ,
rwaAssetConfig ,
pair ,
lpStableConfig ,
stableVault ,
rwaVault ,
stableUserVaultInfo ,
rwaUserVaultInfo ,
stableMint ,
assetMint ,
admin: adminWallet . publicKey ,
payer: adminWallet . publicKey ,
systemProgram: SystemProgram . programId ,
tokenProgram: TOKEN_PROGRAM_ID ,
stableTokenProgram: TOKEN_PROGRAM_ID ,
})
. rpc ();
update_pair
Update pair configuration (fees and pause state).
pub fn update_pair (
ctx : Context < UpdatePair >,
redemption_fee_bps : u16 ,
discount_rate_bps : u16 ,
paused : bool ,
) -> Result <()>
Parameters
Parameter Type Description redemption_fee_bpsu16New redemption fee (Stable → RWA) discount_rate_bpsu16New discount rate (RWA → Stable) pausedboolNew pause state
Required Accounts
#[derive( Accounts )]
pub struct UpdatePair <' info > {
pub global_config : Account <' info , GlobalConfig >,
#[account(
mut ,
seeds = [ PAIR_PREFIX , liquidity_provider . key() . as_ref(), stable_mint . key() . as_ref(), asset_mint . key() . as_ref()],
bump = pair . bump,
has_one = liquidity_provider ,
)]
pub pair : Account <' info , Pair >,
pub stable_mint : InterfaceAccount <' info , Mint >,
pub asset_mint : InterfaceAccount <' info , Mint >,
pub liquidity_provider : Signer <' info >,
}
Behavior
Updates fee configuration
Updates pause state
Requires program to be unpaused
Access Control
Access : LP (pair owner) only
Example
// Unpause the pair and set final fees
await program . methods
. updatePair (
50 , // 0.5% redemption fee
25 , // 0.25% discount rate
false // Unpause
)
. accounts ({
globalConfig ,
pair ,
stableMint ,
assetMint ,
liquidityProvider: lpWallet . publicKey ,
})
. rpc ();
close_pair
Permanently close a trading pair.
pub fn close_pair (
ctx : Context < ClosePair >,
) -> Result <()>
Required Accounts
#[derive( Accounts )]
pub struct ClosePair <' info > {
pub global_config : Account <' info , GlobalConfig >,
pub stable_asset_config : Account <' info , AssetConfig >,
pub rwa_asset_config : Account <' info , AssetConfig >,
#[account(
mut ,
close = admin,
seeds = [ PAIR_PREFIX , liquidity_provider . key() . as_ref(), stable_mint . key() . as_ref(), asset_mint . key() . as_ref()],
bump = pair . bump,
has_one = liquidity_provider ,
)]
pub pair : Account <' info , Pair >,
// Vault accounts to close (if usage count reaches 0)
pub stable_vault : InterfaceAccount <' info , TokenAccount >,
pub rwa_vault : InterfaceAccount <' info , TokenAccount >,
// User vault info accounts
pub stable_user_vault_info : Account <' info , UserVaultInfo >,
pub rwa_user_vault_info : Account <' info , UserVaultInfo >,
// LP stable config (closed if no more pairs)
pub lp_stable_config : Account <' info , LpStableConfig >,
pub stable_mint : InterfaceAccount <' info , Mint >,
pub asset_mint : InterfaceAccount <' info , Mint >,
pub liquidity_provider : Signer <' info >,
#[account(
constraint = global_config . admin == admin . key()
)]
pub admin : SystemAccount <' info >, // Receives rent
pub token_program : Interface <' info , TokenInterface >,
pub stable_token_program : Interface <' info , TokenInterface >,
}
Behavior
Closes Pair account
Decrements used_in_pairs_count on both asset configs
Returns remaining vault tokens to LP
Closes vault accounts if no longer used by other pairs
Closes LpStableConfig if no more pairs
Returns rent to admin
Pair closure is permanent. Once closed, a pair cannot be reopened. Admin must create a new pair if trading is needed again.
Access Control
Access : LP (pair owner) only
set_paused_for_lp_stable_config
Set pause state for all pairs of an LP/stablecoin combination.
pub fn set_paused_for_lp_stable_config (
ctx : Context < SetPausedForLpStableConfig >,
paused : bool ,
) -> Result <()>
Parameters
Parameter Type Description pausedboolNew pause state
Required Accounts
#[derive( Accounts )]
pub struct SetPausedForLpStableConfig <' info > {
pub global_config : Account <' info , GlobalConfig >,
#[account(
mut ,
seeds = [ LP_STABLE_CONFIG_PREFIX , stable_mint . key() . as_ref(), liquidity_provider . key() . as_ref()],
bump = lp_stable_config . bump,
)]
pub lp_stable_config : Account <' info , LpStableConfig >,
pub stable_mint : InterfaceAccount <' info , Mint >,
// Either admin OR liquidity_provider can call
pub authority : Signer <' info >,
/// CHECK: LP address for verification
pub liquidity_provider : UncheckedAccount <' info >,
}
Behavior
Updates pause state on LpStableConfig
Affects ALL pairs using this LP/stablecoin combination
Access Control
Access : Admin OR LP (config owner)
Liquidity Instructions
add_liquidity
Deposit tokens into a vault.
pub fn add_liquidity (
ctx : Context < AddLiquidity >,
amount : u64 ,
) -> Result <()>
Parameters
Parameter Type Description amountu64Amount of tokens to deposit
Required Accounts
#[derive( Accounts )]
pub struct AddLiquidity <' info > {
pub global_config : Account <' info , GlobalConfig >,
#[account(
mut ,
seeds = [ VAULT_PREFIX , mint . key() . as_ref(), liquidity_provider . key() . as_ref()],
bump ,
)]
pub vault : InterfaceAccount <' info , TokenAccount >,
#[account(
mut ,
constraint = lp_token_account . owner == liquidity_provider . key()
)]
pub lp_token_account : InterfaceAccount <' info , TokenAccount >,
pub mint : InterfaceAccount <' info , Mint >,
pub liquidity_provider : Signer <' info >,
pub token_program : Interface <' info , TokenInterface >,
}
Behavior
Transfers tokens from LP’s account to vault
Requires program to be unpaused
Amount must be greater than 0
Access Control
Access : LP (vault owner) only
Example
await program . methods
. addLiquidity ( new BN ( 1000_000000 )) // 1000 tokens
. accounts ({
globalConfig ,
vault: stableVault ,
lpTokenAccount: lpStableTokenAccount ,
mint: stableMint ,
liquidityProvider: lpWallet . publicKey ,
tokenProgram: TOKEN_PROGRAM_ID ,
})
. rpc ();
remove_liquidity
Withdraw tokens from a vault.
pub fn remove_liquidity (
ctx : Context < RemoveLiquidity >,
amount : u64 ,
) -> Result <()>
Parameters
Parameter Type Description amountu64Amount of tokens to withdraw
Required Accounts
Same as add_liquidity.
Behavior
Transfers tokens from vault to LP’s account
Requires program to be unpaused
Amount must be greater than 0
Vault must have sufficient balance
Access Control
Access : LP (vault owner) only
Vault Architecture
Shared Vault Model
Vaults are shared across pairs for the same LP/token combination:
LP Alice + USDC mint → One USDC vault
LP Alice + ULTRA mint → One ULTRA vault
Pair 1: Alice's USDC ↔ ULTRA (uses both vaults)
Pair 2: Alice's USDC ↔ JTRSY (uses USDC vault + new JTRSY vault)
UserVaultInfo Tracking
The UserVaultInfo account tracks how many pairs use each vault:
Incremented when pair is created
Decremented when pair is closed
Vault closed only when used reaches 0
This ensures vaults aren’t closed while still in use by other pairs.
Error Codes
Error Description UnauthorizedCaller is not admin or LP owner ProgramPausedProgram is paused PairPausedPair is paused AmountMustBePositiveAmount is zero InsufficientLiquidityVault has insufficient balance InvalidAssetTypeAsset type mismatch (RWA vs Stable) FeesOutOfRangeFee BPS exceeds 9900 AssetConfigInUseCannot change asset type while in use
Next: Price Sources Learn about NAV pricing sources and oracle integration