2026-03-17 18:08:53 +01:00
|
|
|
use std::num::NonZeroU128;
|
|
|
|
|
|
|
|
|
|
use amm_core::{
|
2026-03-31 20:45:57 -03:00
|
|
|
assert_supported_fee_tier, compute_liquidity_token_pda, compute_liquidity_token_pda_seed,
|
2026-04-15 14:55:04 -03:00
|
|
|
compute_lp_lock_holding_pda, compute_lp_lock_holding_pda_seed, compute_pool_pda,
|
|
|
|
|
compute_pool_pda_seed, compute_vault_pda, compute_vault_pda_seed, PoolDefinition,
|
2026-03-31 20:45:57 -03:00
|
|
|
MINIMUM_LIQUIDITY,
|
2026-03-17 18:08:53 +01:00
|
|
|
};
|
|
|
|
|
use nssa_core::{
|
|
|
|
|
account::{Account, AccountWithMetadata, Data},
|
2026-04-15 14:55:04 -03:00
|
|
|
program::{AccountPostState, ChainedCall, Claim, ProgramId},
|
2026-03-17 18:08:53 +01:00
|
|
|
};
|
2026-04-08 17:48:13 -03:00
|
|
|
use token_core::TokenDefinition;
|
2026-03-17 18:08:53 +01:00
|
|
|
|
|
|
|
|
#[expect(clippy::too_many_arguments, reason = "TODO: Fix later")]
|
|
|
|
|
pub fn new_definition(
|
|
|
|
|
pool: AccountWithMetadata,
|
|
|
|
|
vault_a: AccountWithMetadata,
|
|
|
|
|
vault_b: AccountWithMetadata,
|
|
|
|
|
pool_definition_lp: AccountWithMetadata,
|
2026-04-08 17:48:13 -03:00
|
|
|
lp_lock_holding: AccountWithMetadata,
|
2026-03-17 18:08:53 +01:00
|
|
|
user_holding_a: AccountWithMetadata,
|
|
|
|
|
user_holding_b: AccountWithMetadata,
|
|
|
|
|
user_holding_lp: AccountWithMetadata,
|
|
|
|
|
token_a_amount: NonZeroU128,
|
|
|
|
|
token_b_amount: NonZeroU128,
|
2026-03-31 20:45:57 -03:00
|
|
|
fees: u128,
|
2026-03-17 18:08:53 +01:00
|
|
|
amm_program_id: ProgramId,
|
|
|
|
|
) -> (Vec<AccountPostState>, Vec<ChainedCall>) {
|
|
|
|
|
let definition_token_a_id = token_core::TokenHolding::try_from(&user_holding_a.account.data)
|
|
|
|
|
.expect("New definition: AMM Program expects valid Token Holding account for Token A")
|
|
|
|
|
.definition_id();
|
|
|
|
|
let definition_token_b_id = token_core::TokenHolding::try_from(&user_holding_b.account.data)
|
|
|
|
|
.expect("New definition: AMM Program expects valid Token Holding account for Token B")
|
|
|
|
|
.definition_id();
|
|
|
|
|
|
|
|
|
|
let token_program = user_holding_a.account.program_owner;
|
|
|
|
|
|
2026-04-13 17:11:28 +02:00
|
|
|
// both instances of the same token program
|
2026-03-17 18:08:53 +01:00
|
|
|
assert_eq!(
|
|
|
|
|
user_holding_b.account.program_owner, token_program,
|
|
|
|
|
"User Token holdings must use the same Token Program"
|
|
|
|
|
);
|
2026-04-13 17:11:28 +02:00
|
|
|
// Verify token_a and token_b are different
|
2026-03-17 18:08:53 +01:00
|
|
|
assert!(
|
|
|
|
|
definition_token_a_id != definition_token_b_id,
|
|
|
|
|
"Cannot set up a swap for a token with itself"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
pool.account_id,
|
|
|
|
|
compute_pool_pda(amm_program_id, definition_token_a_id, definition_token_b_id),
|
|
|
|
|
"Pool Definition Account ID does not match PDA"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
vault_a.account_id,
|
|
|
|
|
compute_vault_pda(amm_program_id, pool.account_id, definition_token_a_id),
|
|
|
|
|
"Vault ID does not match PDA"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
vault_b.account_id,
|
|
|
|
|
compute_vault_pda(amm_program_id, pool.account_id, definition_token_b_id),
|
|
|
|
|
"Vault ID does not match PDA"
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
pool_definition_lp.account_id,
|
|
|
|
|
compute_liquidity_token_pda(amm_program_id, pool.account_id),
|
|
|
|
|
"Liquidity pool Token Definition Account ID does not match PDA"
|
|
|
|
|
);
|
2026-04-08 17:48:13 -03:00
|
|
|
assert_eq!(
|
|
|
|
|
lp_lock_holding.account_id,
|
|
|
|
|
compute_lp_lock_holding_pda(amm_program_id, pool.account_id),
|
|
|
|
|
"LP lock holding Account ID does not match PDA"
|
|
|
|
|
);
|
2026-03-31 20:45:57 -03:00
|
|
|
assert_supported_fee_tier(fees);
|
2026-03-17 18:08:53 +01:00
|
|
|
|
2026-04-13 17:11:28 +02:00
|
|
|
// Assert that pool is uninitialized (hard precondition)
|
2026-04-10 15:43:13 -03:00
|
|
|
assert_eq!(
|
2026-04-13 17:11:28 +02:00
|
|
|
pool.account,
|
|
|
|
|
Account::default(),
|
|
|
|
|
"Pool account must be uninitialized"
|
2026-03-17 18:08:53 +01:00
|
|
|
);
|
2026-04-15 14:55:04 -03:00
|
|
|
assert!(
|
|
|
|
|
user_holding_lp.account != Account::default() || user_holding_lp.is_authorized,
|
|
|
|
|
"Fresh user LP holding requires user authorization"
|
|
|
|
|
);
|
2026-03-17 18:08:53 +01:00
|
|
|
|
|
|
|
|
// LP Token minting calculation
|
2026-04-07 10:38:14 +02:00
|
|
|
let initial_lp = token_a_amount
|
|
|
|
|
.get()
|
|
|
|
|
.checked_mul(token_b_amount.get())
|
|
|
|
|
.expect("token_a * token_b overflows u128")
|
|
|
|
|
.isqrt();
|
2026-04-08 17:48:13 -03:00
|
|
|
assert!(
|
|
|
|
|
initial_lp > MINIMUM_LIQUIDITY,
|
|
|
|
|
"Initial liquidity must exceed minimum liquidity lock"
|
|
|
|
|
);
|
|
|
|
|
let user_lp = initial_lp - MINIMUM_LIQUIDITY;
|
2026-03-17 18:08:53 +01:00
|
|
|
|
|
|
|
|
// Update pool account
|
|
|
|
|
let mut pool_post = pool.account.clone();
|
|
|
|
|
let pool_post_definition = PoolDefinition {
|
|
|
|
|
definition_token_a_id,
|
|
|
|
|
definition_token_b_id,
|
|
|
|
|
vault_a_id: vault_a.account_id,
|
|
|
|
|
vault_b_id: vault_b.account_id,
|
|
|
|
|
liquidity_pool_id: pool_definition_lp.account_id,
|
|
|
|
|
liquidity_pool_supply: initial_lp,
|
|
|
|
|
reserve_a: token_a_amount.into(),
|
|
|
|
|
reserve_b: token_b_amount.into(),
|
2026-03-31 20:45:57 -03:00
|
|
|
fees,
|
2026-03-17 18:08:53 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
pool_post.data = Data::from(&pool_post_definition);
|
2026-04-15 14:55:04 -03:00
|
|
|
let pool_post: AccountPostState = AccountPostState::new_claimed(
|
|
|
|
|
pool_post.clone(),
|
|
|
|
|
Claim::Pda(compute_pool_pda_seed(
|
|
|
|
|
definition_token_a_id,
|
|
|
|
|
definition_token_b_id,
|
|
|
|
|
)),
|
|
|
|
|
);
|
2026-03-17 18:08:53 +01:00
|
|
|
|
|
|
|
|
let token_program_id = user_holding_a.account.program_owner;
|
|
|
|
|
|
|
|
|
|
// Chain call for Token A (user_holding_a -> Vault_A)
|
2026-04-15 14:55:04 -03:00
|
|
|
let mut vault_a_authorized = vault_a.clone();
|
|
|
|
|
vault_a_authorized.is_authorized = true;
|
2026-03-17 18:08:53 +01:00
|
|
|
let call_token_a = ChainedCall::new(
|
|
|
|
|
token_program_id,
|
2026-04-15 14:55:04 -03:00
|
|
|
vec![user_holding_a.clone(), vault_a_authorized],
|
2026-03-17 18:08:53 +01:00
|
|
|
&token_core::Instruction::Transfer {
|
|
|
|
|
amount_to_transfer: token_a_amount.into(),
|
|
|
|
|
},
|
2026-04-15 14:55:04 -03:00
|
|
|
)
|
|
|
|
|
.with_pda_seeds(vec![compute_vault_pda_seed(
|
|
|
|
|
pool.account_id,
|
|
|
|
|
definition_token_a_id,
|
|
|
|
|
)]);
|
2026-03-17 18:08:53 +01:00
|
|
|
// Chain call for Token B (user_holding_b -> Vault_B)
|
2026-04-15 14:55:04 -03:00
|
|
|
let mut vault_b_authorized = vault_b.clone();
|
|
|
|
|
vault_b_authorized.is_authorized = true;
|
2026-03-17 18:08:53 +01:00
|
|
|
let call_token_b = ChainedCall::new(
|
|
|
|
|
token_program_id,
|
2026-04-15 14:55:04 -03:00
|
|
|
vec![user_holding_b.clone(), vault_b_authorized],
|
2026-03-17 18:08:53 +01:00
|
|
|
&token_core::Instruction::Transfer {
|
|
|
|
|
amount_to_transfer: token_b_amount.into(),
|
|
|
|
|
},
|
2026-04-15 14:55:04 -03:00
|
|
|
)
|
|
|
|
|
.with_pda_seeds(vec![compute_vault_pda_seed(
|
|
|
|
|
pool.account_id,
|
|
|
|
|
definition_token_b_id,
|
|
|
|
|
)]);
|
2026-03-17 18:08:53 +01:00
|
|
|
|
2026-04-08 17:48:13 -03:00
|
|
|
// Chain call for liquidity token lock holding
|
2026-03-17 18:08:53 +01:00
|
|
|
let mut pool_lp_auth = pool_definition_lp.clone();
|
|
|
|
|
pool_lp_auth.is_authorized = true;
|
2026-04-15 14:55:04 -03:00
|
|
|
let mut lp_lock_holding_auth = lp_lock_holding.clone();
|
|
|
|
|
lp_lock_holding_auth.is_authorized = true;
|
2026-03-17 18:08:53 +01:00
|
|
|
|
2026-04-08 17:48:13 -03:00
|
|
|
let call_token_lp_lock = ChainedCall::new(
|
|
|
|
|
token_program_id,
|
2026-04-15 14:55:04 -03:00
|
|
|
vec![pool_lp_auth.clone(), lp_lock_holding_auth],
|
2026-04-13 17:11:28 +02:00
|
|
|
&token_core::Instruction::NewFungibleDefinition {
|
|
|
|
|
name: String::from("LP Token"),
|
|
|
|
|
total_supply: MINIMUM_LIQUIDITY,
|
|
|
|
|
},
|
2026-04-08 17:48:13 -03:00
|
|
|
)
|
2026-04-15 14:55:04 -03:00
|
|
|
.with_pda_seeds(vec![
|
|
|
|
|
compute_liquidity_token_pda_seed(pool.account_id),
|
|
|
|
|
compute_lp_lock_holding_pda_seed(pool.account_id),
|
|
|
|
|
]);
|
2026-04-08 17:48:13 -03:00
|
|
|
|
|
|
|
|
let mut pool_lp_after_lock = pool_lp_auth.clone();
|
2026-04-13 17:11:28 +02:00
|
|
|
pool_lp_after_lock.account.program_owner = token_program_id;
|
|
|
|
|
pool_lp_after_lock.account.data = Data::from(&TokenDefinition::Fungible {
|
|
|
|
|
name: String::from("LP Token"),
|
|
|
|
|
total_supply: MINIMUM_LIQUIDITY,
|
|
|
|
|
metadata_id: None,
|
|
|
|
|
});
|
2026-04-08 17:48:13 -03:00
|
|
|
|
|
|
|
|
let call_token_lp_user = ChainedCall::new(
|
2026-03-17 18:08:53 +01:00
|
|
|
token_program_id,
|
2026-04-08 17:48:13 -03:00
|
|
|
vec![pool_lp_after_lock, user_holding_lp.clone()],
|
|
|
|
|
&token_core::Instruction::Mint {
|
|
|
|
|
amount_to_mint: user_lp,
|
|
|
|
|
},
|
2026-03-17 18:08:53 +01:00
|
|
|
)
|
|
|
|
|
.with_pda_seeds(vec![compute_liquidity_token_pda_seed(pool.account_id)]);
|
|
|
|
|
|
2026-04-08 17:48:13 -03:00
|
|
|
let chained_calls = vec![
|
|
|
|
|
call_token_lp_lock,
|
|
|
|
|
call_token_lp_user,
|
|
|
|
|
call_token_b,
|
|
|
|
|
call_token_a,
|
|
|
|
|
];
|
2026-03-17 18:08:53 +01:00
|
|
|
|
|
|
|
|
let post_states = vec![
|
|
|
|
|
pool_post.clone(),
|
|
|
|
|
AccountPostState::new(vault_a.account.clone()),
|
|
|
|
|
AccountPostState::new(vault_b.account.clone()),
|
|
|
|
|
AccountPostState::new(pool_definition_lp.account.clone()),
|
2026-04-08 17:48:13 -03:00
|
|
|
AccountPostState::new(lp_lock_holding.account.clone()),
|
2026-03-17 18:08:53 +01:00
|
|
|
AccountPostState::new(user_holding_a.account.clone()),
|
|
|
|
|
AccountPostState::new(user_holding_b.account.clone()),
|
|
|
|
|
AccountPostState::new(user_holding_lp.account.clone()),
|
|
|
|
|
];
|
|
|
|
|
|
2026-04-08 17:48:13 -03:00
|
|
|
(post_states, chained_calls)
|
2026-03-17 18:08:53 +01:00
|
|
|
}
|