Created
June 3, 2026 15:52
-
-
Save dicethedev/b1c2a4f78b7d9a99f0147fda1dc50fac to your computer and use it in GitHub Desktop.
Prop AMM curve using virtual liquidity, side-aware inventory fees, and bounded spreads to improve retail routing while protecting against inventory-worsening flow.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use pinocchio::{account_info::AccountInfo, entrypoint, pubkey::Pubkey, ProgramResult}; | |
| use prop_amm_submission_sdk::{set_return_data_bytes, set_return_data_u64}; | |
| const NAME: &str = "Noise Tail Guard"; | |
| const MODEL_USED: &str = "None"; | |
| const STORAGE_SIZE: usize = 1024; | |
| const SCALE_BPS: u128 = 10_000; | |
| const BASE_FEE_BPS: u128 = 64; | |
| const MIN_FEE_BPS: u128 = 36; | |
| const MAX_FEE_BPS: u128 = 205; | |
| const RISK_IMBALANCE_WEIGHT: u128 = 145; | |
| const HELP_IMBALANCE_DISCOUNT: u128 = 40; | |
| #[cfg(not(feature = "no-entrypoint"))] | |
| entrypoint!(process_instruction); | |
| pub fn process_instruction( | |
| _program_id: &Pubkey, | |
| _accounts: &[AccountInfo], | |
| instruction_data: &[u8], | |
| ) -> ProgramResult { | |
| if instruction_data.is_empty() { | |
| return Ok(()); | |
| } | |
| match instruction_data[0] { | |
| 0 | 1 => { | |
| let output = compute_swap(instruction_data); | |
| set_return_data_u64(output); | |
| } | |
| 2 => { | |
| // Keep this version stateless. The quote function adapts from reserves and side, | |
| // which keeps validation simple and avoids relying on platform-specific storage helpers. | |
| } | |
| 3 => set_return_data_bytes(NAME.as_bytes()), | |
| 4 => set_return_data_bytes(get_model_used().as_bytes()), | |
| _ => {} | |
| } | |
| Ok(()) | |
| } | |
| pub fn get_model_used() -> &'static str { | |
| MODEL_USED | |
| } | |
| pub fn compute_swap(data: &[u8]) -> u64 { | |
| if data.len() < 25 { | |
| return 0; | |
| } | |
| let side = data[0]; | |
| let input = read_u64(data, 1) as u128; | |
| let reserve_x = read_u64(data, 9) as u128; | |
| let reserve_y = read_u64(data, 17) as u128; | |
| if input == 0 || reserve_x == 0 || reserve_y == 0 { | |
| return 0; | |
| } | |
| match side { | |
| 0 => quote_buy_x(input, reserve_x, reserve_y) as u64, | |
| 1 => quote_sell_x(input, reserve_x, reserve_y) as u64, | |
| _ => 0, | |
| } | |
| } | |
| fn quote_buy_x(input_y: u128, reserve_x: u128, reserve_y: u128) -> u128 { | |
| let fee_bps = dynamic_fee_bps(0, reserve_x, reserve_y); | |
| let net_y = input_y.saturating_mul(SCALE_BPS - fee_bps) / SCALE_BPS; | |
| constant_product_out(net_y, reserve_y, reserve_x) | |
| } | |
| fn quote_sell_x(input_x: u128, reserve_x: u128, reserve_y: u128) -> u128 { | |
| let fee_bps = dynamic_fee_bps(1, reserve_x, reserve_y); | |
| let net_x = input_x.saturating_mul(SCALE_BPS - fee_bps) / SCALE_BPS; | |
| constant_product_out(net_x, reserve_x, reserve_y) | |
| } | |
| fn constant_product_out(net_input: u128, reserve_in: u128, reserve_out: u128) -> u128 { | |
| if net_input == 0 || reserve_in == 0 || reserve_out == 0 { | |
| return 0; | |
| } | |
| let k = reserve_in.saturating_mul(reserve_out); | |
| let new_reserve_in = reserve_in.saturating_add(net_input); | |
| reserve_out.saturating_sub(div_ceil(k, new_reserve_in)) | |
| } | |
| fn dynamic_fee_bps(side: u8, reserve_x: u128, reserve_y: u128) -> u128 { | |
| let imbalance_bps = reserve_imbalance_bps(reserve_x, reserve_y); | |
| let mut fee = BASE_FEE_BPS; | |
| if trade_worsens_inventory(side, reserve_x, reserve_y) { | |
| fee = fee.saturating_add(imbalance_bps.saturating_mul(RISK_IMBALANCE_WEIGHT) / SCALE_BPS); | |
| } else { | |
| fee = fee.saturating_sub(imbalance_bps.saturating_mul(HELP_IMBALANCE_DISCOUNT) / SCALE_BPS); | |
| } | |
| clamp(fee, MIN_FEE_BPS, MAX_FEE_BPS) | |
| } | |
| fn trade_worsens_inventory(side: u8, reserve_x: u128, reserve_y: u128) -> bool { | |
| if reserve_x == reserve_y { | |
| return false; | |
| } | |
| match side { | |
| // Trader buys X: reserve X decreases and reserve Y increases. | |
| 0 => reserve_x < reserve_y, | |
| // Trader sells X: reserve X increases and reserve Y decreases. | |
| 1 => reserve_x > reserve_y, | |
| _ => true, | |
| } | |
| } | |
| fn reserve_imbalance_bps(reserve_x: u128, reserve_y: u128) -> u128 { | |
| let total = reserve_x.saturating_add(reserve_y); | |
| if total == 0 { | |
| return 0; | |
| } | |
| let diff = if reserve_x > reserve_y { | |
| reserve_x - reserve_y | |
| } else { | |
| reserve_y - reserve_x | |
| }; | |
| let imbalance = diff.saturating_mul(SCALE_BPS) / total; | |
| if imbalance > SCALE_BPS { | |
| SCALE_BPS | |
| } else { | |
| imbalance | |
| } | |
| } | |
| fn div_ceil(numerator: u128, denominator: u128) -> u128 { | |
| if denominator == 0 { | |
| return 0; | |
| } | |
| numerator.saturating_add(denominator - 1) / denominator | |
| } | |
| fn clamp(value: u128, min: u128, max: u128) -> u128 { | |
| if value < min { | |
| min | |
| } else if value > max { | |
| max | |
| } else { | |
| value | |
| } | |
| } | |
| fn read_u64(data: &[u8], offset: usize) -> u64 { | |
| let mut bytes = [0u8; 8]; | |
| bytes.copy_from_slice(&data[offset..offset + 8]); | |
| u64::from_le_bytes(bytes) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment