2026-01-23 16:30:54 -05:00
//! This crate contains core data structures and utilities for the AMM Program.
2026-01-22 20:48:05 -05:00
2026-01-23 16:30:54 -05:00
use nssa_core ::{
account ::{ AccountId , Data } ,
program ::{ PdaSeed , ProgramId } ,
} ;
2026-01-22 20:48:05 -05:00
use serde ::{ Deserialize , Serialize } ;
/// AMM Program Instruction.
#[ derive(Serialize, Deserialize) ]
pub enum Instruction {
2026-01-23 16:30:54 -05:00
/// Initializes a new Pool (or re-initializes an inactive Pool).
2026-01-22 20:48:05 -05:00
///
/// Required accounts:
2026-01-23 16:30:54 -05:00
/// - AMM Pool
/// - Vault Holding Account for Token A
/// - Vault Holding Account for Token B
/// - Pool Liquidity Token Definition
/// - User Holding Account for Token A (authorized)
/// - User Holding Account for Token B (authorized)
/// - User Holding Account for Pool Liquidity
NewDefinition {
token_a_amount : u128 ,
token_b_amount : u128 ,
amm_program_id : ProgramId ,
2026-01-22 20:48:05 -05:00
} ,
2026-01-23 16:30:54 -05:00
/// Adds liquidity to the Pool
2026-01-22 20:48:05 -05:00
///
/// Required accounts:
2026-01-23 16:30:54 -05:00
/// - AMM Pool (initialized)
/// - Vault Holding Account for Token A (initialized)
/// - Vault Holding Account for Token B (initialized)
/// - Pool Liquidity Token Definition (initialized)
/// - User Holding Account for Token A (authorized)
/// - User Holding Account for Token B (authorized)
/// - User Holding Account for Pool Liquidity
AddLiquidity {
min_amount_liquidity : u128 ,
max_amount_to_add_token_a : u128 ,
max_amount_to_add_token_b : u128 ,
} ,
2026-01-22 20:48:05 -05:00
2026-01-23 16:30:54 -05:00
/// Removes liquidity from the Pool
2026-01-22 20:48:05 -05:00
///
/// Required accounts:
2026-01-23 16:30:54 -05:00
/// - AMM Pool (initialized)
/// - Vault Holding Account for Token A (initialized)
/// - Vault Holding Account for Token B (initialized)
/// - Pool Liquidity Token Definition (initialized)
/// - User Holding Account for Token A (initialized)
/// - User Holding Account for Token B (initialized)
/// - User Holding Account for Pool Liquidity (authorized)
RemoveLiquidity {
remove_liquidity_amount : u128 ,
min_amount_to_remove_token_a : u128 ,
min_amount_to_remove_token_b : u128 ,
} ,
2026-01-22 20:48:05 -05:00
2026-01-23 16:30:54 -05:00
/// Swap some quantity of Tokens (either Token A or Token B)
/// while maintaining the Pool constant product.
2026-01-22 20:48:05 -05:00
///
/// Required accounts:
2026-01-23 16:30:54 -05:00
/// - AMM Pool (initialized)
/// - Vault Holding Account for Token A (initialized)
/// - Vault Holding Account for Token B (initialized)
/// - User Holding Account for Token A
/// - User Holding Account for Token B
2026-01-23 16:56:53 -05:00
/// Either User Holding Account for Token A or Token B is authorized.
2026-01-23 16:30:54 -05:00
Swap {
swap_amount_in : u128 ,
min_amount_out : u128 ,
token_definition_id_in : AccountId ,
2026-01-22 20:48:05 -05:00
} ,
}
2026-01-23 16:30:54 -05:00
const POOL_DEFINITION_DATA_SIZE : usize = 225 ;
#[ derive(Clone, Default) ]
pub struct PoolDefinition {
pub definition_token_a_id : AccountId ,
pub definition_token_b_id : AccountId ,
pub vault_a_id : AccountId ,
pub vault_b_id : AccountId ,
pub liquidity_pool_id : AccountId ,
pub liquidity_pool_supply : u128 ,
pub reserve_a : u128 ,
pub reserve_b : u128 ,
/// Fees are currently not used
pub fees : u128 ,
/// A pool becomes inactive (active = false)
/// once all of its liquidity has been removed (e.g., reserves are emptied and
/// liquidity_pool_supply = 0)
pub active : bool ,
2026-01-22 20:48:05 -05:00
}
2026-01-23 16:30:54 -05:00
impl PoolDefinition {
pub fn into_data ( self ) -> Data {
let mut bytes = [ 0 ; POOL_DEFINITION_DATA_SIZE ] ;
bytes [ 0 .. 32 ] . copy_from_slice ( & self . definition_token_a_id . to_bytes ( ) ) ;
bytes [ 32 .. 64 ] . copy_from_slice ( & self . definition_token_b_id . to_bytes ( ) ) ;
bytes [ 64 .. 96 ] . copy_from_slice ( & self . vault_a_id . to_bytes ( ) ) ;
bytes [ 96 .. 128 ] . copy_from_slice ( & self . vault_b_id . to_bytes ( ) ) ;
bytes [ 128 .. 160 ] . copy_from_slice ( & self . liquidity_pool_id . to_bytes ( ) ) ;
bytes [ 160 .. 176 ] . copy_from_slice ( & self . liquidity_pool_supply . to_le_bytes ( ) ) ;
bytes [ 176 .. 192 ] . copy_from_slice ( & self . reserve_a . to_le_bytes ( ) ) ;
bytes [ 192 .. 208 ] . copy_from_slice ( & self . reserve_b . to_le_bytes ( ) ) ;
bytes [ 208 .. 224 ] . copy_from_slice ( & self . fees . to_le_bytes ( ) ) ;
bytes [ 224 ] = self . active as u8 ;
bytes
. to_vec ( )
. try_into ( )
. expect ( " 225 bytes should fit into Data " )
2026-01-22 20:48:05 -05:00
}
2026-01-23 16:30:54 -05:00
pub fn parse ( data : & [ u8 ] ) -> Option < Self > {
if data . len ( ) ! = POOL_DEFINITION_DATA_SIZE {
None
} else {
let definition_token_a_id = AccountId ::new ( data [ 0 .. 32 ] . try_into ( ) . expect ( " Parse data: The AMM program must be provided a valid AccountId for Token A definition " ) ) ;
let definition_token_b_id = AccountId ::new ( data [ 32 .. 64 ] . try_into ( ) . expect ( " Parse data: The AMM program must be provided a valid AccountId for Vault B definition " ) ) ;
let vault_a_id = AccountId ::new ( data [ 64 .. 96 ] . try_into ( ) . expect (
" Parse data: The AMM program must be provided a valid AccountId for Vault A " ,
) ) ;
let vault_b_id = AccountId ::new ( data [ 96 .. 128 ] . try_into ( ) . expect (
" Parse data: The AMM program must be provided a valid AccountId for Vault B " ,
) ) ;
let liquidity_pool_id = AccountId ::new ( data [ 128 .. 160 ] . try_into ( ) . expect ( " Parse data: The AMM program must be provided a valid AccountId for Token liquidity pool definition " ) ) ;
let liquidity_pool_supply = u128 ::from_le_bytes ( data [ 160 .. 176 ] . try_into ( ) . expect (
" Parse data: The AMM program must be provided a valid u128 for liquidity cap " ,
) ) ;
let reserve_a = u128 ::from_le_bytes ( data [ 176 .. 192 ] . try_into ( ) . expect (
" Parse data: The AMM program must be provided a valid u128 for reserve A balance " ,
) ) ;
let reserve_b = u128 ::from_le_bytes ( data [ 192 .. 208 ] . try_into ( ) . expect (
" Parse data: The AMM program must be provided a valid u128 for reserve B balance " ,
) ) ;
let fees = u128 ::from_le_bytes (
data [ 208 .. 224 ]
. try_into ( )
. expect ( " Parse data: The AMM program must be provided a valid u128 for fees " ) ,
) ;
let active = match data [ 224 ] {
0 = > false ,
1 = > true ,
_ = > panic! ( " Parse data: The AMM program must be provided a valid bool for active " ) ,
} ;
Some ( Self {
definition_token_a_id ,
definition_token_b_id ,
vault_a_id ,
vault_b_id ,
liquidity_pool_id ,
liquidity_pool_supply ,
reserve_a ,
reserve_b ,
fees ,
active ,
} )
2026-01-22 20:48:05 -05:00
}
}
2026-01-23 16:30:54 -05:00
}
2026-01-22 20:48:05 -05:00
2026-01-23 16:30:54 -05:00
pub fn compute_pool_pda (
amm_program_id : ProgramId ,
definition_token_a_id : AccountId ,
definition_token_b_id : AccountId ,
) -> AccountId {
AccountId ::from ( (
& amm_program_id ,
& compute_pool_pda_seed ( definition_token_a_id , definition_token_b_id ) ,
) )
2026-01-22 20:48:05 -05:00
}
2026-01-23 16:30:54 -05:00
pub fn compute_pool_pda_seed (
definition_token_a_id : AccountId ,
definition_token_b_id : AccountId ,
) -> PdaSeed {
use risc0_zkvm ::sha ::{ Impl , Sha256 } ;
let ( token_1 , token_2 ) = match definition_token_a_id
. value ( )
. cmp ( definition_token_b_id . value ( ) )
{
std ::cmp ::Ordering ::Less = > ( definition_token_b_id , definition_token_a_id ) ,
std ::cmp ::Ordering ::Greater = > ( definition_token_a_id , definition_token_b_id ) ,
std ::cmp ::Ordering ::Equal = > panic! ( " Definitions match " ) ,
} ;
let mut bytes = [ 0 ; 64 ] ;
bytes [ 0 .. 32 ] . copy_from_slice ( & token_1 . to_bytes ( ) ) ;
bytes [ 32 .. ] . copy_from_slice ( & token_2 . to_bytes ( ) ) ;
PdaSeed ::new (
Impl ::hash_bytes ( & bytes )
. as_bytes ( )
. try_into ( )
. expect ( " Hash output must be exactly 32 bytes long " ) ,
)
}
2026-01-22 20:48:05 -05:00
2026-01-23 16:30:54 -05:00
pub fn compute_vault_pda (
amm_program_id : ProgramId ,
pool_id : AccountId ,
definition_token_id : AccountId ,
) -> AccountId {
AccountId ::from ( (
& amm_program_id ,
& compute_vault_pda_seed ( pool_id , definition_token_id ) ,
) )
2026-01-22 20:48:05 -05:00
}
2026-01-23 16:30:54 -05:00
pub fn compute_vault_pda_seed ( pool_id : AccountId , definition_token_id : AccountId ) -> PdaSeed {
use risc0_zkvm ::sha ::{ Impl , Sha256 } ;
2026-01-22 20:48:05 -05:00
2026-01-23 16:30:54 -05:00
let mut bytes = [ 0 ; 64 ] ;
bytes [ 0 .. 32 ] . copy_from_slice ( & pool_id . to_bytes ( ) ) ;
bytes [ 32 .. ] . copy_from_slice ( & definition_token_id . to_bytes ( ) ) ;
2026-01-22 20:48:05 -05:00
2026-01-23 16:30:54 -05:00
PdaSeed ::new (
Impl ::hash_bytes ( & bytes )
. as_bytes ( )
. try_into ( )
. expect ( " Hash output must be exactly 32 bytes long " ) ,
)
2026-01-22 20:48:05 -05:00
}
2026-01-23 16:30:54 -05:00
pub fn compute_liquidity_token_pda ( amm_program_id : ProgramId , pool_id : AccountId ) -> AccountId {
AccountId ::from ( ( & amm_program_id , & compute_liquidity_token_pda_seed ( pool_id ) ) )
2026-01-22 20:48:05 -05:00
}
2026-01-23 16:30:54 -05:00
pub fn compute_liquidity_token_pda_seed ( pool_id : AccountId ) -> PdaSeed {
use risc0_zkvm ::sha ::{ Impl , Sha256 } ;
2026-01-22 20:48:05 -05:00
2026-01-23 16:30:54 -05:00
let mut bytes = [ 0 ; 64 ] ;
bytes [ 0 .. 32 ] . copy_from_slice ( & pool_id . to_bytes ( ) ) ;
bytes [ 32 .. ] . copy_from_slice ( & [ 0 ; 32 ] ) ;
2026-01-22 20:48:05 -05:00
2026-01-23 16:30:54 -05:00
PdaSeed ::new (
Impl ::hash_bytes ( & bytes )
. as_bytes ( )
. try_into ( )
. expect ( " Hash output must be exactly 32 bytes long " ) ,
)
2026-01-22 20:48:05 -05:00
}