├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── logging_config.yaml ├── programs ├── lb_clmm │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ │ ├── constants.rs │ │ ├── errors.rs │ │ ├── events.rs │ │ ├── instructions │ │ ├── add_liquidity.rs │ │ ├── add_liquidity_by_strategy.rs │ │ ├── add_liquidity_by_strategy_one_side.rs │ │ ├── add_liquidity_by_weight.rs │ │ ├── add_liquidity_by_weight_one_side.rs │ │ ├── add_liquidity_single_side_precise.rs │ │ ├── claim_fee.rs │ │ ├── claim_reward.rs │ │ ├── close_position.rs │ │ ├── close_preset_parameter.rs │ │ ├── fund_reward.rs │ │ ├── increase_oracle_length.rs │ │ ├── initialize_bin_array.rs │ │ ├── initialize_bin_array_bitmap_extension.rs │ │ ├── initialize_lb_pair.rs │ │ ├── initialize_permission_lb_pair.rs │ │ ├── initialize_position.rs │ │ ├── initialize_position_by_operator.rs │ │ ├── initialize_position_pda.rs │ │ ├── initialize_preset_parameters.rs │ │ ├── initialize_reward.rs │ │ ├── migrate_bin_array.rs │ │ ├── migrate_position.rs │ │ ├── mod.rs │ │ ├── position_authorize.rs │ │ ├── remove_all_liquidity.rs │ │ ├── remove_liquidity.rs │ │ ├── set_activation_slot.rs │ │ ├── set_lock_release_slot.rs │ │ ├── set_pre_activation_slot_duration.rs │ │ ├── set_pre_activation_swap_address.rs │ │ ├── swap.rs │ │ ├── toggle_pair_status.rs │ │ ├── update_fee_owner.rs │ │ ├── update_fee_parameters.rs │ │ ├── update_fees_and_rewards.rs │ │ ├── update_position_operator.rs │ │ ├── update_reward_duration.rs │ │ ├── update_reward_funder.rs │ │ ├── update_whitelisted_wallet.rs │ │ ├── withdraw_ineligible_reward.rs │ │ └── withdraw_protocol_fee.rs │ │ ├── lib.rs │ │ ├── manager │ │ ├── bin_array_manager.rs │ │ └── mod.rs │ │ ├── math │ │ ├── bin_math.rs │ │ ├── mod.rs │ │ ├── price_math.rs │ │ ├── safe_math.rs │ │ ├── u128x128_math.rs │ │ ├── u64x64_math.rs │ │ ├── utils_math.rs │ │ └── weight_to_amounts.rs │ │ ├── state │ │ ├── action_access.rs │ │ ├── bin.rs │ │ ├── bin_array_bitmap_extension.rs │ │ ├── lb_pair.rs │ │ ├── mod.rs │ │ ├── oracle.rs │ │ ├── parameters.rs │ │ ├── position.rs │ │ └── preset_parameters.rs │ │ └── utils │ │ ├── mod.rs │ │ ├── pda.rs │ │ └── seeds.rs └── raydium_amm │ ├── Cargo.toml │ ├── Xargo.toml │ └── src │ ├── entrypoint.rs │ ├── error.rs │ ├── instruction.rs │ ├── invokers.rs │ ├── lib.rs │ ├── log.rs │ ├── math.rs │ ├── processor.rs │ └── state.rs └── src ├── arbitrage ├── calc_arb.rs ├── mod.rs ├── simulate.rs ├── strategies.rs ├── streams.rs └── types.rs ├── common ├── config.rs ├── constants.rs ├── database.rs ├── debug.rs ├── maths.rs ├── mod.rs ├── types.rs └── utils.rs ├── data ├── graphs.rs └── mod.rs ├── lib.rs ├── main.rs ├── markets ├── meteora.rs ├── mod.rs ├── orca.rs ├── orca_whirpools.rs ├── pools.rs ├── raydium.rs ├── raydium_clmm.rs ├── types.rs └── utils.rs ├── strategies ├── mod.rs └── pools.rs └── transactions ├── cache ├── lut_addresses.json └── lut_addresses_test.json ├── create_transaction.rs ├── markets └── idl │ ├── lb_clmm.json │ ├── raydium_amm.json │ └── whirlpool.json ├── meteoradlmm_swap.rs ├── mod.rs ├── orca_whirpools_swap.rs ├── raydium_swap.rs └── utils.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /cache 3 | /src/markets/cache/orca_whirpools-markets.json 4 | /src/markets/cache/orca-markets.json 5 | /src/markets/cache/raydium-markets.json 6 | /src/markets/cache/raydiumclmm-markets.json 7 | /logs 8 | /results 9 | /node_modules 10 | /history 11 | .env 12 | PseudoCode.md 13 | src/markets/cache/meteora-markets.json 14 | best_paths_selected/* 15 | optimism_transactions/* 16 | src/transactions/cache/pda_list.json 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*", 4 | ] 5 | 6 | [package] 7 | name = "MEV_Bot_Solana" 8 | version = "0.1.0" 9 | edition = "2021" 10 | 11 | [dependencies] 12 | lb_clmm = { path = "./programs/lb_clmm" } 13 | raydium_amm = { path = "./programs/raydium_amm" } 14 | url = "2.3.1" 15 | dotenv = "0.15.0" 16 | anyhow = "1.0.70" 17 | itertools = "0.11.0" 18 | serde = "1.0.188" 19 | serde_json = { version = "1.0.107", features = ["std"] } 20 | bounded-vec-deque = "0.1.1" 21 | 22 | # Telegram 23 | teloxide = { version = "0.12", features = ["macros"] } 24 | 25 | futures = "0.3.5" 26 | futures-util = "*" 27 | tokio = { version = "1.29.0", features = ["full"] } 28 | tokio-stream = { version = "0.1", features = ['sync'] } 29 | tokio-tungstenite = "*" 30 | async-trait = "0.1.74" 31 | eth-encode-packed = "0.1.0" 32 | rlp = { version = "0.5", features = ["derive"] } 33 | 34 | csv = "1.2.2" 35 | colored = "2.0.0" 36 | log = "0.4.21" 37 | fern = { version = "0.6.2", features = ["colored"] } 38 | chrono = "0.4.23" 39 | indicatif = "0.17.5" 40 | ndarray = "0.15.6" 41 | web3 = "0.19.0" 42 | eyre = "0.6.12" 43 | reqwest = {version = "0.12.3", features = ["blocking","json", "stream"] } 44 | strum = "0.26.2" 45 | strum_macros = "0.26.2" 46 | exitfailure = "0.5.1" 47 | solana-sdk = "1.18.9" 48 | solana-client = "1.18.10" 49 | solana-program = "1.18.10" 50 | thiserror = "1.0.58" 51 | hex = "0.4.3" 52 | solana-account-decoder = "1.18.12" 53 | solana-pubsub-client = "1.18.12" 54 | base64 = "0.22.1" 55 | decimal = "2.1.0" 56 | rust_decimal = {version = "1.35.0", features = ["maths"]} 57 | num = "0.4.2" 58 | borsh = "1.5.0" 59 | rust_decimal_macros = "1.34.2" 60 | log4rs = "1.3.0" 61 | rust_socketio = {version = "*", features = ["async"]} 62 | ws = "0.9.2" 63 | anchor-client = {version = "0.30.0", features = ["async"]} 64 | clap = "4.5.4" 65 | anchor-lang = { version = "0.30.0", features = ["event-cpi"] } 66 | anchor-spl = "0.30.0" 67 | serum_dex = "0.5.4" 68 | safe-transmute = "0.11.3" 69 | spl-associated-token-account = "3.0.2" 70 | anchor-safe-math = "0.5.0" 71 | num-bigint = "0.4.5" 72 | solana-transaction-status = "1.18.15" 73 | plotters = { version = "^0.3.5", default_features = false, features = ["evcxr", "all_series", "all_elements"] } 74 | piston_window = "0.132.0" 75 | plotters-piston = "0.3.0" 76 | systemstat = "0.2.3" 77 | mongodb = {version = "2.8.2", default-features = false, features = ["async-std-runtime"] } 78 | 79 | [profile.release] 80 | codegen-units = 1 81 | lto = "fat" 82 | 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solana MEV Arbitrage Bot 2 | ## Overview 3 | A high-frequency trading bot designed to identify and exploit arbitrage opportunities across various decentralized exchanges (DEXs) on the Solana blockchain. 4 | 5 | ## Features 6 | 7 | - **Multi-DEX Support**: Works with Raydium, Orca Whirlpools, and Meteora DEXs 8 | - **Real-time Pool Monitoring**: Continuously scans for new liquidity pools 9 | - **Advanced Arbitrage Detection**: Identifies profitable 1-hop and 2-hop arbitrage paths 10 | - **Simulation Engine**: Tests potential trades before execution 11 | - **Optimized Execution**: Prioritizes the most profitable opportunities 12 | - **Performance Tracking**: Records all arbitrage attempts and results 13 | 14 | ## Supported DEXs 15 | 16 | - Raydium (CLMM and standard pools) 17 | - Orca Whirlpools 18 | - Meteora 19 | 20 | ## Code Structure 21 | ``` 22 | src/ 23 | ├── arbitrage/ 24 | │ ├── calc_arb.rs # Arbitrage calculation logic 25 | │ ├── simulate.rs # Trade simulation 26 | │ ├── streams.rs # Real-time data streams 27 | │ └── types.rs # Data structures 28 | ├── markets/ 29 | │ ├── meteora.rs # Meteora DEX integration 30 | │ ├── orca_whirpools.rs # Orca integration 31 | │ ├── raydium.rs # Raydium integration 32 | │ └── types.rs # Market data structures 33 | └── common/ # Shared utilities and constants 34 | ``` 35 | 36 | ## Key Components 37 | 38 | ### Pool Discovery 39 | ```rust 40 | pub async fn get_fresh_pools(tokens: Vec) -> HashMap { 41 | // Scans supported DEXs for new pools containing specified tokens 42 | // Implements rate limiting between requests 43 | } 44 | 45 | ## Configuration 46 | 47 | Edit `src/common/constants.rs` to configure: 48 | 49 | - RPC endpoints 50 | - DEX program IDs 51 | - Rate limits 52 | - Profit thresholds 53 | - MongoDB connection settings 54 | 55 | 56 | ## Performance Optimization 57 | 58 | The bot includes several optimization features: 59 | 60 | - Batch processing with `get_multiple_accounts` for efficient RPC usage 61 | - Market filtering based on liquidity thresholds 62 | - Real-time WebSocket subscriptions for immediate market updates 63 | - MongoDB for persistent storage and performance analysis 64 | - Error rate limiting for problematic paths 65 | 66 | 67 | ## Monitoring 68 | 69 | The bot outputs: 70 | 71 | - Real-time progress bars 72 | - Detailed logs of arbitrage opportunities with emoji indicators (💦, 👀, 📊) 73 | - JSON files with trade results 74 | - MongoDB integration for persistent storage and analysis 75 | 76 | 77 | ## Disclaimer 78 | 79 | This is experimental software. Use at your own risk. The authors are not responsible for any funds lost while using this bot. 80 | 81 | ## License 82 | \`\`\` 83 | 84 | This Markdown file contains all the documentation you provided about the bot's configuration, performance optimization, monitoring capabilities, and disclaimer. 85 | 86 | -------------------------------------------------------------------------------- /logging_config.yaml: -------------------------------------------------------------------------------- 1 | appenders: 2 | my_stdout: 3 | kind: console 4 | encoder: 5 | pattern: "{h({d(%Y-%m-%d %H:%M:%S)(utc)} - [{l}] {m}{n})}" 6 | root: 7 | level: info 8 | appenders: 9 | - my_stdout -------------------------------------------------------------------------------- /programs/lb_clmm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lb_clmm" 3 | version = "0.6.1" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "lb_clmm" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | localnet = [] 18 | alpha-access = [] 19 | test-bpf = [] 20 | staging = [] 21 | 22 | [dependencies] 23 | anchor-lang = { version = "0.30.0", features = ["event-cpi"] } 24 | anchor-spl = "0.30.0" 25 | uint = "0.8.5" 26 | bytemuck = { version = "1.13.1", features = ["derive", "min_const_generics"] } 27 | ruint = "1.3.0" 28 | num-traits = "0.2.16" 29 | num-integer = "0.1.45" 30 | mpl-token-metadata = "3.0.1" 31 | solana-program = "1.18.10" 32 | num_enum = "0.7.1" 33 | 34 | [dev-dependencies] 35 | proptest = "1.2.0" 36 | rand = "0.7.3" 37 | solana-program-test = "1.16.0" 38 | solana-sdk = "1.18.9" 39 | async-trait = "0.1.74" 40 | assert_matches = "1.5.0" 41 | -------------------------------------------------------------------------------- /programs/lb_clmm/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/constants.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use anchor_lang::solana_program::{pubkey, pubkey::Pubkey}; 3 | 4 | // TODO: Macro to compute the constants which changes based on the bit system used ? 5 | // Smallest step between bin is 0.01%, 1 bps 6 | #[constant] 7 | pub const BASIS_POINT_MAX: i32 = 10000; 8 | 9 | /// Maximum number of bin a bin array able to contains. 10 | #[constant] 11 | pub const MAX_BIN_PER_ARRAY: usize = 70; 12 | 13 | /// Maximum number of bin per position contains. 14 | #[constant] 15 | pub const MAX_BIN_PER_POSITION: usize = 70; 16 | 17 | /// Minimum bin ID supported. Computed based on 1 bps. 18 | #[constant] 19 | pub const MIN_BIN_ID: i32 = -443636; 20 | 21 | /// Maximum bin ID supported. Computed based on 1 bps. 22 | #[constant] 23 | pub const MAX_BIN_ID: i32 = 443636; 24 | 25 | /// Maximum fee rate. 10% 26 | #[constant] 27 | pub const MAX_FEE_RATE: u64 = 100_000_000; 28 | 29 | #[constant] 30 | pub const FEE_PRECISION: u64 = 1_000_000_000; 31 | 32 | /// Maximum protocol share of the fee. 25% 33 | #[constant] 34 | pub const MAX_PROTOCOL_SHARE: u16 = 2_500; 35 | 36 | /// Host fee. 20% 37 | #[constant] 38 | pub const HOST_FEE_BPS: u16 = 2_000; 39 | 40 | pub const U24_MAX: u32 = 0xffffff; 41 | 42 | // Number of rewards supported by pool 43 | #[constant] 44 | pub const NUM_REWARDS: usize = 2; 45 | 46 | // Minimum reward duration 47 | #[constant] 48 | pub const MIN_REWARD_DURATION: u64 = 1; 49 | 50 | #[constant] 51 | pub const MAX_REWARD_DURATION: u64 = 31536000; // 1 year = 365 * 24 * 3600 52 | 53 | pub const DEFAULT_OBSERVATION_LENGTH: u64 = 100; 54 | pub const SAMPLE_LIFETIME: u64 = 120; // 2 55 | #[constant] 56 | pub const EXTENSION_BINARRAY_BITMAP_SIZE: usize = 12; 57 | 58 | #[constant] 59 | pub const BIN_ARRAY_BITMAP_SIZE: i32 = 512; 60 | 61 | pub const MAX_BASE_FACTOR_STEP: u16 = 100; // 100 bps, 1% 62 | 63 | pub const MAX_FEE_UPDATE_WINDOW: i64 = 0; 64 | 65 | #[constant] 66 | pub const MAX_REWARD_BIN_SPLIT: usize = 15; 67 | 68 | #[cfg(feature = "localnet")] 69 | pub static ALPHA_ACCESS_COLLECTION_MINTS: [Pubkey; 1] = 70 | [pubkey!("J1S9H3QjnRtBbbuD4HjPV6RpRhwuk4zKbxsnCHuTgh9w")]; 71 | 72 | #[cfg(not(feature = "localnet"))] 73 | pub static ALPHA_ACCESS_COLLECTION_MINTS: [Pubkey; 1] = 74 | [pubkey!("5rwhXUgAAdbVEaFQzAwgrcWwoCqYGzR1Mo2KwUYfbRuS")]; 75 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/errors.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | #[derive(PartialEq)] 5 | pub enum LBError { 6 | #[msg("Invalid start bin index")] 7 | InvalidStartBinIndex, 8 | 9 | #[msg("Invalid bin id")] 10 | InvalidBinId, 11 | 12 | #[msg("Invalid input data")] 13 | InvalidInput, 14 | 15 | #[msg("Exceeded amount slippage tolerance")] 16 | ExceededAmountSlippageTolerance, 17 | 18 | #[msg("Exceeded bin slippage tolerance")] 19 | ExceededBinSlippageTolerance, 20 | 21 | #[msg("Composition factor flawed")] 22 | CompositionFactorFlawed, 23 | 24 | #[msg("Non preset bin step")] 25 | NonPresetBinStep, 26 | 27 | #[msg("Zero liquidity")] 28 | ZeroLiquidity, 29 | 30 | #[msg("Invalid position")] 31 | InvalidPosition, 32 | 33 | #[msg("Bin array not found")] 34 | BinArrayNotFound, 35 | 36 | #[msg("Invalid token mint")] 37 | InvalidTokenMint, 38 | 39 | #[msg("Invalid account for single deposit")] 40 | InvalidAccountForSingleDeposit, 41 | 42 | #[msg("Pair insufficient liquidity")] 43 | PairInsufficientLiquidity, 44 | 45 | #[msg("Invalid fee owner")] 46 | InvalidFeeOwner, 47 | 48 | #[msg("Invalid fee withdraw amount")] 49 | InvalidFeeWithdrawAmount, 50 | 51 | #[msg("Invalid admin")] 52 | InvalidAdmin, 53 | 54 | #[msg("Identical fee owner")] 55 | IdenticalFeeOwner, 56 | 57 | #[msg("Invalid basis point")] 58 | InvalidBps, 59 | 60 | #[msg("Math operation overflow")] 61 | MathOverflow, 62 | 63 | #[msg("Type cast error")] 64 | TypeCastFailed, 65 | 66 | #[msg("Invalid reward index")] 67 | InvalidRewardIndex, 68 | 69 | #[msg("Invalid reward duration")] 70 | InvalidRewardDuration, 71 | 72 | #[msg("Reward already initialized")] 73 | RewardInitialized, 74 | 75 | #[msg("Reward not initialized")] 76 | RewardUninitialized, 77 | 78 | #[msg("Identical funder")] 79 | IdenticalFunder, 80 | 81 | #[msg("Reward campaign in progress")] 82 | RewardCampaignInProgress, 83 | 84 | #[msg("Reward duration is the same")] 85 | IdenticalRewardDuration, 86 | 87 | #[msg("Invalid bin array")] 88 | InvalidBinArray, 89 | 90 | #[msg("Bin arrays must be continuous")] 91 | NonContinuousBinArrays, 92 | 93 | #[msg("Invalid reward vault")] 94 | InvalidRewardVault, 95 | 96 | #[msg("Position is not empty")] 97 | NonEmptyPosition, 98 | 99 | #[msg("Unauthorized access")] 100 | UnauthorizedAccess, 101 | 102 | #[msg("Invalid fee parameter")] 103 | InvalidFeeParameter, 104 | 105 | #[msg("Missing oracle account")] 106 | MissingOracle, 107 | 108 | #[msg("Insufficient observation sample")] 109 | InsufficientSample, 110 | 111 | #[msg("Invalid lookup timestamp")] 112 | InvalidLookupTimestamp, 113 | 114 | #[msg("Bitmap extension account is not provided")] 115 | BitmapExtensionAccountIsNotProvided, 116 | 117 | #[msg("Cannot find non-zero liquidity binArrayId")] 118 | CannotFindNonZeroLiquidityBinArrayId, 119 | 120 | #[msg("Bin id out of bound")] 121 | BinIdOutOfBound, 122 | 123 | #[msg("Insufficient amount in for minimum out")] 124 | InsufficientOutAmount, 125 | 126 | #[msg("Invalid position width")] 127 | InvalidPositionWidth, 128 | 129 | #[msg("Excessive fee update")] 130 | ExcessiveFeeUpdate, 131 | 132 | #[msg("Pool disabled")] 133 | PoolDisabled, 134 | 135 | #[msg("Invalid pool type")] 136 | InvalidPoolType, 137 | 138 | #[msg("Whitelist for wallet is full")] 139 | ExceedMaxWhitelist, 140 | 141 | #[msg("Invalid index")] 142 | InvalidIndex, 143 | 144 | #[msg("Reward not ended")] 145 | RewardNotEnded, 146 | 147 | #[msg("Must withdraw ineligible reward")] 148 | MustWithdrawnIneligibleReward, 149 | 150 | #[msg("Invalid strategy parameters")] 151 | InvalidStrategyParameters, 152 | 153 | #[msg("Liquidity locked")] 154 | LiquidityLocked, 155 | 156 | #[msg("Invalid lock release slot")] 157 | InvalidLockReleaseSlot, 158 | } 159 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/events.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::event; 2 | use anchor_lang::prelude::*; 3 | 4 | #[event] 5 | pub struct CompositionFee { 6 | // Sender's public key 7 | pub from: Pubkey, 8 | // Bin id 9 | pub bin_id: i16, 10 | // Amount of fee in token X 11 | pub token_x_fee_amount: u64, 12 | // Amount of fee in token Y 13 | pub token_y_fee_amount: u64, 14 | // Amount of protocol fee in token X 15 | pub protocol_token_x_fee_amount: u64, 16 | // Amount of protocol fee in token Y 17 | pub protocol_token_y_fee_amount: u64, 18 | } 19 | 20 | #[event] 21 | pub struct AddLiquidity { 22 | // Liquidity pool pair 23 | pub lb_pair: Pubkey, 24 | // Sender's public key 25 | pub from: Pubkey, 26 | // Address of the position 27 | pub position: Pubkey, 28 | // Amount of token X, and Y deposited 29 | pub amounts: [u64; 2], 30 | // Pair active bin during deposit 31 | pub active_bin_id: i32, 32 | } 33 | 34 | #[event] 35 | pub struct RemoveLiquidity { 36 | // Liquidity pool pair 37 | pub lb_pair: Pubkey, 38 | // Sender's public key 39 | pub from: Pubkey, 40 | // Address of the position 41 | pub position: Pubkey, 42 | // Amount of token X, and Y withdrawn 43 | pub amounts: [u64; 2], 44 | // Pair active bin during withdrawal 45 | pub active_bin_id: i32, 46 | } 47 | 48 | #[event] 49 | pub struct Swap { 50 | // Liquidity pool pair 51 | pub lb_pair: Pubkey, 52 | // Address initiated the swap 53 | pub from: Pubkey, 54 | // Initial active bin ID 55 | pub start_bin_id: i32, 56 | // Finalized active bin ID 57 | pub end_bin_id: i32, 58 | // In token amount 59 | pub amount_in: u64, 60 | // Out token amount 61 | pub amount_out: u64, 62 | // Direction of the swap 63 | pub swap_for_y: bool, 64 | // Include protocol fee 65 | pub fee: u64, 66 | // Part of fee 67 | pub protocol_fee: u64, 68 | // Fee bps 69 | pub fee_bps: u128, 70 | // Host fee 71 | pub host_fee: u64, 72 | } 73 | 74 | #[event] 75 | pub struct ClaimReward { 76 | // Liquidity pool pair 77 | pub lb_pair: Pubkey, 78 | // Position address 79 | pub position: Pubkey, 80 | // Owner of the position 81 | pub owner: Pubkey, 82 | // Index of the farm reward the owner is claiming 83 | pub reward_index: u64, 84 | // Total amount of reward claimed 85 | pub total_reward: u64, 86 | } 87 | 88 | #[event] 89 | pub struct FundReward { 90 | // Liquidity pool pair 91 | pub lb_pair: Pubkey, 92 | // Address of the funder 93 | pub funder: Pubkey, 94 | // Index of the farm reward being funded 95 | pub reward_index: u64, 96 | // Amount of farm reward funded 97 | pub amount: u64, 98 | } 99 | 100 | #[event] 101 | pub struct InitializeReward { 102 | // Liquidity pool pair 103 | pub lb_pair: Pubkey, 104 | // Mint address of the farm reward 105 | pub reward_mint: Pubkey, 106 | // Address of the funder 107 | pub funder: Pubkey, 108 | // Index of the farm reward being initialized 109 | pub reward_index: u64, 110 | // Duration of the farm reward in seconds 111 | pub reward_duration: u64, 112 | } 113 | 114 | #[event] 115 | pub struct UpdateRewardDuration { 116 | // Liquidity pool pair 117 | pub lb_pair: Pubkey, 118 | // Index of the farm reward being updated 119 | pub reward_index: u64, 120 | // Old farm reward duration 121 | pub old_reward_duration: u64, 122 | // New farm reward duration 123 | pub new_reward_duration: u64, 124 | } 125 | 126 | #[event] 127 | pub struct UpdateRewardFunder { 128 | // Liquidity pool pair 129 | pub lb_pair: Pubkey, 130 | // Index of the farm reward being updated 131 | pub reward_index: u64, 132 | // Address of the old farm reward funder 133 | pub old_funder: Pubkey, 134 | // Address of the new farm reward funder 135 | pub new_funder: Pubkey, 136 | } 137 | 138 | #[event] 139 | pub struct PositionClose { 140 | // Address of the position 141 | pub position: Pubkey, 142 | // Owner of the position 143 | pub owner: Pubkey, 144 | } 145 | 146 | #[event] 147 | pub struct ClaimFee { 148 | // Liquidity pool pair 149 | pub lb_pair: Pubkey, 150 | // Address of the position 151 | pub position: Pubkey, 152 | // Owner of the position 153 | pub owner: Pubkey, 154 | // Fee amount in token X 155 | pub fee_x: u64, 156 | // Fee amount in token Y 157 | pub fee_y: u64, 158 | } 159 | 160 | #[event] 161 | pub struct LbPairCreate { 162 | // Liquidity pool pair 163 | pub lb_pair: Pubkey, 164 | // Bin step 165 | pub bin_step: u16, 166 | // Address of token X 167 | pub token_x: Pubkey, 168 | // Address of token Y 169 | pub token_y: Pubkey, 170 | } 171 | 172 | #[event] 173 | pub struct PositionCreate { 174 | // Liquidity pool pair 175 | pub lb_pair: Pubkey, 176 | // Address of the position 177 | pub position: Pubkey, 178 | // Owner of the position 179 | pub owner: Pubkey, 180 | } 181 | 182 | #[event] 183 | pub struct FeeParameterUpdate { 184 | // Liquidity pool pair 185 | pub lb_pair: Pubkey, 186 | // Protocol share in BPS 187 | pub protocol_share: u16, 188 | // Base factor of base fee rate 189 | pub base_factor: u16, 190 | } 191 | 192 | #[event] 193 | pub struct IncreaseObservation { 194 | // Oracle address 195 | pub oracle: Pubkey, 196 | // Oracle length 197 | pub new_observation_length: u64, 198 | } 199 | 200 | #[event] 201 | pub struct WithdrawIneligibleReward { 202 | // Liquidity pool pair 203 | pub lb_pair: Pubkey, 204 | // Reward mint 205 | pub reward_mint: Pubkey, 206 | // Amount of ineligible reward withdrawn 207 | pub amount: u64, 208 | } 209 | 210 | #[event] 211 | pub struct UpdatePositionOperator { 212 | // Position public key 213 | pub position: Pubkey, 214 | // Old operator 215 | pub old_operator: Pubkey, 216 | // New operator 217 | pub new_operator: Pubkey, 218 | } 219 | 220 | #[event] 221 | pub struct UpdatePositionLockReleaseSlot { 222 | // Position public key 223 | pub position: Pubkey, 224 | // Current slot 225 | pub current_slot: u64, 226 | // New lock release slot 227 | pub new_lock_release_slot: u64, 228 | // Old lock release slot 229 | pub old_lock_release_slot: u64, 230 | // Sender public key 231 | pub sender: Pubkey, 232 | } 233 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/add_liquidity.rs: -------------------------------------------------------------------------------- 1 | use crate::authorize_modify_position; 2 | use crate::state::bin_array_bitmap_extension::BinArrayBitmapExtension; 3 | use crate::state::position::PositionV2; 4 | use crate::state::{bin::BinArray, lb_pair::LbPair}; 5 | use anchor_lang::prelude::*; 6 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 7 | 8 | pub struct CompositeDepositInfo { 9 | pub liquidity_share: u128, 10 | pub protocol_token_x_fee_amount: u64, 11 | pub protocol_token_y_fee_amount: u64, 12 | } 13 | 14 | #[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Debug)] 15 | pub struct BinLiquidityDistribution { 16 | /// Define the bin ID wish to deposit to. 17 | pub bin_id: i32, 18 | /// DistributionX (or distributionY) is the percentages of amountX (or amountY) you want to add to each bin. 19 | pub distribution_x: u16, 20 | /// DistributionX (or distributionY) is the percentages of amountX (or amountY) you want to add to each bin. 21 | pub distribution_y: u16, 22 | } 23 | 24 | #[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Debug)] 25 | pub struct LiquidityParameter { 26 | /// Amount of X token to deposit 27 | pub amount_x: u64, 28 | /// Amount of Y token to deposit 29 | pub amount_y: u64, 30 | /// Liquidity distribution to each bins 31 | pub bin_liquidity_dist: Vec, 32 | } 33 | 34 | #[event_cpi] 35 | #[derive(Accounts)] 36 | pub struct ModifyLiquidity<'info> { 37 | #[account( 38 | mut, 39 | has_one = lb_pair, 40 | constraint = authorize_modify_position(&position, sender.key())? 41 | )] 42 | pub position: AccountLoader<'info, PositionV2>, 43 | 44 | #[account( 45 | mut, 46 | has_one = reserve_x, 47 | has_one = reserve_y, 48 | has_one = token_x_mint, 49 | has_one = token_y_mint, 50 | )] 51 | pub lb_pair: AccountLoader<'info, LbPair>, 52 | 53 | #[account( 54 | mut, 55 | has_one = lb_pair, 56 | )] 57 | pub bin_array_bitmap_extension: Option>, 58 | 59 | #[account( 60 | mut, 61 | token::mint = token_x_mint 62 | )] 63 | pub user_token_x: Box>, 64 | #[account( 65 | mut, 66 | token::mint = token_y_mint 67 | )] 68 | pub user_token_y: Box>, 69 | 70 | #[account(mut)] 71 | pub reserve_x: Box>, 72 | #[account(mut)] 73 | pub reserve_y: Box>, 74 | 75 | pub token_x_mint: Box>, 76 | pub token_y_mint: Box>, 77 | 78 | #[account( 79 | mut, 80 | has_one = lb_pair 81 | )] 82 | pub bin_array_lower: AccountLoader<'info, BinArray>, 83 | #[account( 84 | mut, 85 | has_one = lb_pair 86 | )] 87 | pub bin_array_upper: AccountLoader<'info, BinArray>, 88 | 89 | pub sender: Signer<'info>, 90 | pub token_x_program: Interface<'info, TokenInterface>, 91 | pub token_y_program: Interface<'info, TokenInterface>, 92 | } 93 | 94 | pub fn handle<'a, 'b, 'c, 'info>( 95 | ctx: Context<'a, 'b, 'c, 'info, ModifyLiquidity<'info>>, 96 | liquidity_parameter: LiquidityParameter, 97 | ) -> Result<()> { 98 | Ok(()) 99 | } 100 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/add_liquidity_by_strategy_one_side.rs: -------------------------------------------------------------------------------- 1 | use super::add_liquidity_by_strategy::StrategyParameters; 2 | use crate::errors::LBError; 3 | use crate::math::weight_to_amounts::to_amount_ask_side; 4 | use crate::math::weight_to_amounts::to_amount_bid_side; 5 | use crate::to_weight_ascending_order; 6 | use crate::to_weight_descending_order; 7 | use crate::to_weight_spot_balanced; 8 | use crate::ModifyLiquidityOneSide; 9 | use crate::StrategyType; 10 | use anchor_lang::prelude::*; 11 | 12 | #[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Debug, Default)] 13 | pub struct LiquidityParameterByStrategyOneSide { 14 | /// Amount of X token or Y token to deposit 15 | pub amount: u64, 16 | /// Active bin that integrator observe off-chain 17 | pub active_id: i32, 18 | /// max active bin slippage allowed 19 | pub max_active_bin_slippage: i32, 20 | /// strategy parameters 21 | pub strategy_parameters: StrategyParameters, 22 | } 23 | 24 | impl LiquidityParameterByStrategyOneSide { 25 | pub fn to_amounts_into_bin( 26 | &self, 27 | active_id: i32, 28 | bin_step: u16, 29 | deposit_for_y: bool, 30 | ) -> Result> { 31 | let min_bin_id = self.strategy_parameters.min_bin_id; 32 | let max_bin_id = self.strategy_parameters.max_bin_id; 33 | 34 | let weights = match self.strategy_parameters.strategy_type { 35 | StrategyType::SpotOneSide => Some(to_weight_spot_balanced( 36 | self.strategy_parameters.min_bin_id, 37 | self.strategy_parameters.max_bin_id, 38 | )), 39 | StrategyType::CurveOneSide => { 40 | if deposit_for_y { 41 | Some(to_weight_ascending_order(min_bin_id, max_bin_id)) 42 | } else { 43 | Some(to_weight_descending_order(min_bin_id, max_bin_id)) 44 | } 45 | } 46 | StrategyType::BidAskOneSide => { 47 | if deposit_for_y { 48 | Some(to_weight_descending_order(min_bin_id, max_bin_id)) 49 | } else { 50 | Some(to_weight_ascending_order(min_bin_id, max_bin_id)) 51 | } 52 | } 53 | _ => None, 54 | } 55 | .ok_or(LBError::InvalidStrategyParameters)?; 56 | 57 | if deposit_for_y { 58 | to_amount_bid_side(active_id, self.amount, &weights) 59 | } else { 60 | to_amount_ask_side(active_id, self.amount, bin_step, &weights) 61 | } 62 | } 63 | } 64 | 65 | pub fn handle<'a, 'b, 'c, 'info>( 66 | ctx: Context<'a, 'b, 'c, 'info, ModifyLiquidityOneSide<'info>>, 67 | liquidity_parameter: &LiquidityParameterByStrategyOneSide, 68 | ) -> Result<()> { 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/add_liquidity_by_weight.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::MAX_BIN_PER_POSITION; 2 | use crate::errors::LBError; 3 | use crate::math::weight_to_amounts::{to_amount_ask_side, to_amount_bid_side, to_amount_both_side}; 4 | use crate::ModifyLiquidity; 5 | use anchor_lang::prelude::*; 6 | 7 | #[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Debug, Default)] 8 | pub struct BinLiquidityDistributionByWeight { 9 | /// Define the bin ID wish to deposit to. 10 | pub bin_id: i32, 11 | /// weight of liquidity distributed for this bin id 12 | pub weight: u16, 13 | } 14 | 15 | #[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Debug)] 16 | pub struct LiquidityParameterByWeight { 17 | /// Amount of X token to deposit 18 | pub amount_x: u64, 19 | /// Amount of Y token to deposit 20 | pub amount_y: u64, 21 | /// Active bin that integrator observe off-chain 22 | pub active_id: i32, 23 | /// max active bin slippage allowed 24 | pub max_active_bin_slippage: i32, 25 | /// Liquidity distribution to each bins 26 | pub bin_liquidity_dist: Vec, 27 | } 28 | 29 | impl LiquidityParameterByWeight { 30 | fn bin_count(&self) -> u32 { 31 | self.bin_liquidity_dist.len() as u32 32 | } 33 | 34 | pub fn validate<'a, 'info>(&'a self, active_id: i32) -> Result<()> { 35 | let bin_count = self.bin_count(); 36 | require!(bin_count > 0, LBError::InvalidInput); 37 | 38 | require!( 39 | bin_count <= MAX_BIN_PER_POSITION as u32, 40 | LBError::InvalidInput 41 | ); 42 | 43 | let bin_shift = if active_id > self.active_id { 44 | active_id - self.active_id 45 | } else { 46 | self.active_id - active_id 47 | }; 48 | 49 | require!( 50 | bin_shift <= self.max_active_bin_slippage.into(), 51 | LBError::ExceededBinSlippageTolerance 52 | ); 53 | 54 | // bin dist must be in consecutive order and weight is non-zero 55 | for (i, val) in self.bin_liquidity_dist.iter().enumerate() { 56 | require!(val.weight != 0, LBError::InvalidInput); 57 | // bin id must in right order 58 | if i != 0 { 59 | require!( 60 | val.bin_id > self.bin_liquidity_dist[i - 1].bin_id, 61 | LBError::InvalidInput 62 | ); 63 | } 64 | } 65 | let first_bin_id = self.bin_liquidity_dist[0].bin_id; 66 | let last_bin_id = self.bin_liquidity_dist[self.bin_liquidity_dist.len() - 1].bin_id; 67 | 68 | if first_bin_id > active_id { 69 | require!(self.amount_x != 0, LBError::InvalidInput); 70 | } 71 | if last_bin_id < active_id { 72 | require!(self.amount_y != 0, LBError::InvalidInput); 73 | } 74 | 75 | Ok(()) 76 | } 77 | 78 | // require bin id to be sorted before doing this 79 | pub fn to_amounts_into_bin<'a, 'info>( 80 | &'a self, 81 | active_id: i32, 82 | bin_step: u16, 83 | amount_x_in_active_bin: u64, // amount x in active bin 84 | amount_y_in_active_bin: u64, // amount y in active bin 85 | ) -> Result> { 86 | // only bid side 87 | if active_id > self.bin_liquidity_dist[self.bin_liquidity_dist.len() - 1].bin_id { 88 | let amounts = to_amount_bid_side( 89 | active_id, 90 | self.amount_y, 91 | &self 92 | .bin_liquidity_dist 93 | .iter() 94 | .map(|x| (x.bin_id, x.weight)) 95 | .collect::>(), 96 | )?; 97 | 98 | let amounts = amounts 99 | .iter() 100 | .map(|x| (x.0, 0, x.1)) 101 | .collect::>(); 102 | 103 | return Ok(amounts); 104 | } 105 | // only ask side 106 | if active_id < self.bin_liquidity_dist[0].bin_id { 107 | let amounts = to_amount_ask_side( 108 | active_id, 109 | self.amount_x, 110 | bin_step, 111 | &self 112 | .bin_liquidity_dist 113 | .iter() 114 | .map(|x| (x.bin_id, x.weight)) 115 | .collect::>(), 116 | )?; 117 | 118 | let amounts = amounts 119 | .iter() 120 | .map(|x| (x.0, x.1, 0)) 121 | .collect::>(); 122 | 123 | return Ok(amounts); 124 | } 125 | 126 | to_amount_both_side( 127 | active_id, 128 | bin_step, 129 | amount_x_in_active_bin, 130 | amount_y_in_active_bin, 131 | self.amount_x, 132 | self.amount_y, 133 | &self 134 | .bin_liquidity_dist 135 | .iter() 136 | .map(|x| (x.bin_id, x.weight)) 137 | .collect::>(), 138 | ) 139 | } 140 | } 141 | 142 | pub fn handle<'a, 'b, 'c, 'info>( 143 | ctx: &Context<'a, 'b, 'c, 'info, ModifyLiquidity<'info>>, 144 | liquidity_parameter: &LiquidityParameterByWeight, 145 | ) -> Result<()> { 146 | Ok(()) 147 | } 148 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/add_liquidity_by_weight_one_side.rs: -------------------------------------------------------------------------------- 1 | use crate::authorize_modify_position; 2 | use crate::constants::MAX_BIN_PER_POSITION; 3 | use crate::errors::LBError; 4 | use crate::math::weight_to_amounts::to_amount_ask_side; 5 | use crate::math::weight_to_amounts::to_amount_bid_side; 6 | use crate::state::bin_array_bitmap_extension::BinArrayBitmapExtension; 7 | use crate::state::position::PositionV2; 8 | use crate::state::{bin::BinArray, lb_pair::LbPair}; 9 | use anchor_lang::prelude::*; 10 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 11 | 12 | use super::add_liquidity_by_weight::BinLiquidityDistributionByWeight; 13 | 14 | #[derive(AnchorSerialize, AnchorDeserialize, Eq, PartialEq, Clone, Debug)] 15 | pub struct LiquidityOneSideParameter { 16 | /// Amount of X token or Y token to deposit 17 | pub amount: u64, 18 | /// Active bin that integrator observe off-chain 19 | pub active_id: i32, 20 | /// max active bin slippage allowed 21 | pub max_active_bin_slippage: i32, 22 | /// Liquidity distribution to each bins 23 | pub bin_liquidity_dist: Vec, 24 | } 25 | 26 | impl LiquidityOneSideParameter { 27 | fn bin_count(&self) -> u32 { 28 | self.bin_liquidity_dist.len() as u32 29 | } 30 | 31 | fn validate<'a, 'info>(&'a self, active_id: i32) -> Result<()> { 32 | require!(self.amount != 0, LBError::InvalidInput); 33 | 34 | let bin_count = self.bin_count(); 35 | require!(bin_count > 0, LBError::InvalidInput); 36 | 37 | require!( 38 | bin_count <= MAX_BIN_PER_POSITION as u32, 39 | LBError::InvalidInput 40 | ); 41 | 42 | let bin_shift = if active_id > self.active_id { 43 | active_id - self.active_id 44 | } else { 45 | self.active_id - active_id 46 | }; 47 | 48 | require!( 49 | bin_shift <= self.max_active_bin_slippage.into(), 50 | LBError::ExceededBinSlippageTolerance 51 | ); 52 | 53 | // bin dist must be in consecutive order and weight is non-zero 54 | for (i, val) in self.bin_liquidity_dist.iter().enumerate() { 55 | require!(val.weight != 0, LBError::InvalidInput); 56 | // bin id must in right order 57 | if i != 0 { 58 | require!( 59 | val.bin_id > self.bin_liquidity_dist[i - 1].bin_id, 60 | LBError::InvalidInput 61 | ); 62 | } 63 | } 64 | Ok(()) 65 | } 66 | 67 | // require bin id to be sorted before doing this 68 | fn to_amounts_into_bin<'a, 'info>( 69 | &'a self, 70 | active_id: i32, 71 | bin_step: u16, 72 | deposit_for_y: bool, 73 | ) -> Result> { 74 | if deposit_for_y { 75 | to_amount_bid_side( 76 | active_id, 77 | self.amount, 78 | &self 79 | .bin_liquidity_dist 80 | .iter() 81 | .map(|x| (x.bin_id, x.weight)) 82 | .collect::>(), 83 | ) 84 | } else { 85 | to_amount_ask_side( 86 | active_id, 87 | self.amount, 88 | bin_step, 89 | &self 90 | .bin_liquidity_dist 91 | .iter() 92 | .map(|x| (x.bin_id, x.weight)) 93 | .collect::>(), 94 | ) 95 | } 96 | } 97 | } 98 | 99 | #[event_cpi] 100 | #[derive(Accounts)] 101 | pub struct ModifyLiquidityOneSide<'info> { 102 | #[account( 103 | mut, 104 | has_one = lb_pair, 105 | constraint = authorize_modify_position(&position, sender.key())? 106 | )] 107 | pub position: AccountLoader<'info, PositionV2>, 108 | 109 | #[account(mut)] 110 | pub lb_pair: AccountLoader<'info, LbPair>, 111 | 112 | #[account( 113 | mut, 114 | has_one = lb_pair, 115 | )] 116 | pub bin_array_bitmap_extension: Option>, 117 | 118 | #[account(mut)] 119 | pub user_token: Box>, 120 | 121 | #[account(mut)] 122 | pub reserve: Box>, 123 | 124 | pub token_mint: Box>, 125 | 126 | #[account( 127 | mut, 128 | has_one = lb_pair 129 | )] 130 | pub bin_array_lower: AccountLoader<'info, BinArray>, 131 | #[account( 132 | mut, 133 | has_one = lb_pair 134 | )] 135 | pub bin_array_upper: AccountLoader<'info, BinArray>, 136 | 137 | pub sender: Signer<'info>, 138 | pub token_program: Interface<'info, TokenInterface>, 139 | } 140 | 141 | pub fn handle<'a, 'b, 'c, 'info>( 142 | ctx: &Context<'a, 'b, 'c, 'info, ModifyLiquidityOneSide<'info>>, 143 | liquidity_parameter: &LiquidityOneSideParameter, 144 | ) -> Result<()> { 145 | Ok(()) 146 | } 147 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/add_liquidity_single_side_precise.rs: -------------------------------------------------------------------------------- 1 | use crate::ModifyLiquidityOneSide; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)] 5 | pub struct AddLiquiditySingleSidePreciseParameter { 6 | pub bins: Vec, 7 | pub decompress_multiplier: u64, 8 | } 9 | 10 | #[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)] 11 | pub struct CompressedBinDepositAmount { 12 | pub bin_id: i32, 13 | pub amount: u32, 14 | } 15 | 16 | pub fn handle<'a, 'b, 'c, 'info>( 17 | ctx: Context<'a, 'b, 'c, 'info, ModifyLiquidityOneSide<'info>>, 18 | parameter: AddLiquiditySingleSidePreciseParameter, 19 | ) -> Result<()> { 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/claim_fee.rs: -------------------------------------------------------------------------------- 1 | use crate::authorize_claim_fee_position; 2 | use crate::state::{bin::BinArray, lb_pair::LbPair, position::PositionV2}; 3 | use anchor_lang::prelude::*; 4 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 5 | #[event_cpi] 6 | #[derive(Accounts)] 7 | pub struct ClaimFee<'info> { 8 | #[account( 9 | mut, 10 | has_one = reserve_x, 11 | has_one = reserve_y, 12 | has_one = token_x_mint, 13 | has_one = token_y_mint, 14 | )] 15 | pub lb_pair: AccountLoader<'info, LbPair>, 16 | 17 | #[account( 18 | mut, 19 | has_one = lb_pair, 20 | constraint = authorize_claim_fee_position(&position, sender.key())? 21 | )] 22 | pub position: AccountLoader<'info, PositionV2>, 23 | 24 | #[account( 25 | mut, 26 | has_one = lb_pair 27 | )] 28 | pub bin_array_lower: AccountLoader<'info, BinArray>, 29 | #[account( 30 | mut, 31 | has_one = lb_pair 32 | )] 33 | pub bin_array_upper: AccountLoader<'info, BinArray>, 34 | 35 | pub sender: Signer<'info>, 36 | 37 | #[account(mut)] 38 | pub reserve_x: Box>, 39 | #[account(mut)] 40 | pub reserve_y: Box>, 41 | 42 | #[account(mut)] 43 | pub user_token_x: Box>, 44 | #[account(mut)] 45 | pub user_token_y: Box>, 46 | 47 | pub token_x_mint: Box>, 48 | pub token_y_mint: Box>, 49 | 50 | pub token_program: Interface<'info, TokenInterface>, 51 | } 52 | 53 | pub fn handle(ctx: Context) -> Result<()> { 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/claim_reward.rs: -------------------------------------------------------------------------------- 1 | use crate::authorize_modify_position; 2 | use crate::state::{bin::BinArray, lb_pair::LbPair, position::PositionV2}; 3 | use anchor_lang::prelude::*; 4 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 5 | 6 | #[event_cpi] 7 | #[derive(Accounts)] 8 | #[instruction(reward_index: u64)] 9 | pub struct ClaimReward<'info> { 10 | #[account(mut)] 11 | pub lb_pair: AccountLoader<'info, LbPair>, 12 | 13 | #[account( 14 | mut, 15 | has_one = lb_pair, 16 | constraint = authorize_modify_position(&position, sender.key())? 17 | )] 18 | pub position: AccountLoader<'info, PositionV2>, 19 | 20 | #[account( 21 | mut, 22 | has_one = lb_pair 23 | )] 24 | pub bin_array_lower: AccountLoader<'info, BinArray>, 25 | #[account( 26 | mut, 27 | has_one = lb_pair 28 | )] 29 | pub bin_array_upper: AccountLoader<'info, BinArray>, 30 | 31 | pub sender: Signer<'info>, 32 | 33 | #[account(mut)] 34 | pub reward_vault: Box>, 35 | pub reward_mint: Box>, 36 | 37 | #[account(mut)] 38 | pub user_token_account: Box>, 39 | 40 | pub token_program: Interface<'info, TokenInterface>, 41 | } 42 | 43 | // TODO: Should we pass in range of bin we are going to collect reward ? It could help us in heap / compute unit issue by chunking into multiple tx. 44 | pub fn handle(ctx: Context, index: u64) -> Result<()> { 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/close_position.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::authorize_modify_position; 4 | use crate::state::{bin::BinArray, lb_pair::LbPair, position::PositionV2}; 5 | 6 | #[event_cpi] 7 | #[derive(Accounts)] 8 | pub struct ClosePosition<'info> { 9 | #[account( 10 | mut, 11 | has_one = lb_pair, 12 | constraint = authorize_modify_position(&position, sender.key())?, 13 | close = rent_receiver 14 | )] 15 | pub position: AccountLoader<'info, PositionV2>, 16 | 17 | #[account(mut)] 18 | pub lb_pair: AccountLoader<'info, LbPair>, 19 | 20 | #[account( 21 | mut, 22 | has_one = lb_pair 23 | )] 24 | pub bin_array_lower: AccountLoader<'info, BinArray>, 25 | #[account( 26 | mut, 27 | has_one = lb_pair 28 | )] 29 | pub bin_array_upper: AccountLoader<'info, BinArray>, 30 | 31 | pub sender: Signer<'info>, 32 | 33 | /// CHECK: Account to receive closed account rental SOL 34 | #[account(mut)] 35 | pub rent_receiver: UncheckedAccount<'info>, 36 | } 37 | 38 | pub fn handle(ctx: Context) -> Result<()> { 39 | Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/close_preset_parameter.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_admin; 2 | use crate::errors::LBError; 3 | use crate::state::preset_parameters::PresetParameter; 4 | use anchor_lang::prelude::*; 5 | 6 | #[derive(Accounts)] 7 | pub struct ClosePresetParameter<'info> { 8 | #[account( 9 | mut, 10 | close = rent_receiver 11 | )] 12 | pub preset_parameter: Account<'info, PresetParameter>, 13 | 14 | #[account( 15 | mut, 16 | constraint = assert_eq_admin(admin.key()) @ LBError::InvalidAdmin 17 | )] 18 | pub admin: Signer<'info>, 19 | 20 | /// CHECK: Account to receive closed account rental SOL 21 | #[account(mut)] 22 | pub rent_receiver: UncheckedAccount<'info>, 23 | } 24 | 25 | pub fn handle(_ctx: Context) -> Result<()> { 26 | // Anchor handle everything 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/fund_reward.rs: -------------------------------------------------------------------------------- 1 | use crate::state::{bin::BinArray, lb_pair::LbPair}; 2 | use anchor_lang::prelude::*; 3 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 4 | 5 | #[event_cpi] 6 | #[derive(Accounts)] 7 | #[instruction(reward_index: u64)] 8 | pub struct FundReward<'info> { 9 | #[account(mut)] 10 | pub lb_pair: AccountLoader<'info, LbPair>, 11 | 12 | #[account(mut)] 13 | pub reward_vault: Box>, 14 | pub reward_mint: Box>, 15 | #[account(mut)] 16 | pub funder_token_account: Box>, 17 | 18 | pub funder: Signer<'info>, 19 | 20 | #[account( 21 | mut, 22 | has_one = lb_pair 23 | )] 24 | pub bin_array: AccountLoader<'info, BinArray>, 25 | 26 | pub token_program: Interface<'info, TokenInterface>, 27 | } 28 | 29 | pub fn handle( 30 | ctx: Context, 31 | index: u64, 32 | amount: u64, 33 | carry_forward: bool, 34 | ) -> Result<()> { 35 | Ok(()) 36 | } 37 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/increase_oracle_length.rs: -------------------------------------------------------------------------------- 1 | use crate::state::oracle::Oracle; 2 | use anchor_lang::prelude::*; 3 | 4 | #[event_cpi] 5 | #[derive(Accounts)] 6 | #[instruction(length_to_add: u64)] 7 | pub struct IncreaseOracleLength<'info> { 8 | #[account( 9 | mut, 10 | realloc = Oracle::new_space(length_to_add, &oracle)?, 11 | realloc::payer = funder, 12 | realloc::zero = false 13 | )] 14 | pub oracle: AccountLoader<'info, Oracle>, 15 | 16 | #[account(mut)] 17 | pub funder: Signer<'info>, 18 | 19 | pub system_program: Program<'info, System>, 20 | } 21 | 22 | pub fn handle(ctx: Context, length_to_add: u64) -> Result<()> { 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/initialize_bin_array.rs: -------------------------------------------------------------------------------- 1 | use crate::state::{bin::BinArray, lb_pair::LbPair}; 2 | use crate::utils::seeds::BIN_ARRAY; 3 | use anchor_lang::prelude::*; 4 | 5 | #[derive(Accounts)] 6 | #[instruction(index: i64)] 7 | pub struct InitializeBinArray<'info> { 8 | pub lb_pair: AccountLoader<'info, LbPair>, 9 | 10 | #[account( 11 | init, 12 | payer = funder, 13 | seeds = [ 14 | BIN_ARRAY, 15 | lb_pair.key().as_ref(), 16 | &index.to_le_bytes() 17 | ], 18 | bump, 19 | space = 8 + BinArray::INIT_SPACE 20 | )] 21 | pub bin_array: AccountLoader<'info, BinArray>, 22 | 23 | #[account(mut)] 24 | pub funder: Signer<'info>, 25 | 26 | pub system_program: Program<'info, System>, 27 | } 28 | 29 | pub fn handle(ctx: Context, index: i64) -> Result<()> { 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/initialize_bin_array_bitmap_extension.rs: -------------------------------------------------------------------------------- 1 | use crate::state::bin_array_bitmap_extension::BinArrayBitmapExtension; 2 | use crate::state::lb_pair::LbPair; 3 | use crate::utils::seeds::BIN_ARRAY_BITMAP_SEED; 4 | use anchor_lang::prelude::*; 5 | 6 | #[derive(Accounts)] 7 | pub struct InitializeBinArrayBitmapExtension<'info> { 8 | pub lb_pair: AccountLoader<'info, LbPair>, 9 | /// Initialize an account to store if a bin array is initialized. 10 | #[account( 11 | init, 12 | seeds = [ 13 | BIN_ARRAY_BITMAP_SEED, 14 | lb_pair.key().as_ref(), 15 | ], 16 | bump, 17 | payer = funder, 18 | space = 8 + BinArrayBitmapExtension::INIT_SPACE 19 | )] 20 | pub bin_array_bitmap_extension: AccountLoader<'info, BinArrayBitmapExtension>, 21 | #[account(mut)] 22 | pub funder: Signer<'info>, 23 | pub system_program: Program<'info, System>, 24 | pub rent: Sysvar<'info, Rent>, 25 | } 26 | 27 | pub fn handle(ctx: Context) -> Result<()> { 28 | Ok(()) 29 | } 30 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/initialize_lb_pair.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::DEFAULT_OBSERVATION_LENGTH; 2 | use crate::errors::LBError; 3 | use crate::state::bin_array_bitmap_extension::BinArrayBitmapExtension; 4 | use crate::state::lb_pair::LbPair; 5 | use crate::state::oracle::Oracle; 6 | use crate::state::preset_parameters::PresetParameter; 7 | use crate::utils::seeds::BIN_ARRAY_BITMAP_SEED; 8 | use crate::utils::seeds::ORACLE; 9 | use anchor_lang::prelude::*; 10 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 11 | use std::cmp::{max, min}; 12 | 13 | #[event_cpi] 14 | #[derive(Accounts)] 15 | #[instruction(active_id: i32, bin_step: u16)] 16 | pub struct InitializeLbPair<'info> { 17 | #[account( 18 | init, 19 | seeds = [ 20 | min(token_mint_x.key(), token_mint_y.key()).as_ref(), 21 | max(token_mint_x.key(), token_mint_y.key()).as_ref(), 22 | &bin_step.to_le_bytes(), 23 | &preset_parameter.base_factor.to_le_bytes() 24 | ], 25 | bump, 26 | payer = funder, 27 | space = 8 + LbPair::INIT_SPACE 28 | )] 29 | pub lb_pair: AccountLoader<'info, LbPair>, 30 | 31 | #[account( 32 | init, 33 | seeds = [ 34 | BIN_ARRAY_BITMAP_SEED, 35 | lb_pair.key().as_ref(), 36 | ], 37 | bump, 38 | payer = funder, 39 | space = 8 + BinArrayBitmapExtension::INIT_SPACE 40 | )] 41 | pub bin_array_bitmap_extension: Option>, 42 | 43 | #[account(constraint = token_mint_x.key() != token_mint_y.key())] 44 | pub token_mint_x: Box>, 45 | pub token_mint_y: Box>, 46 | 47 | #[account( 48 | init, 49 | seeds = [ 50 | lb_pair.key().as_ref(), 51 | token_mint_x.key().as_ref() 52 | ], 53 | bump, 54 | payer = funder, 55 | token::mint = token_mint_x, 56 | token::authority = lb_pair, 57 | )] 58 | pub reserve_x: Box>, 59 | #[account( 60 | init, 61 | seeds = [ 62 | lb_pair.key().as_ref(), 63 | token_mint_y.key().as_ref() 64 | ], 65 | bump, 66 | payer = funder, 67 | token::mint = token_mint_y, 68 | token::authority = lb_pair, 69 | )] 70 | pub reserve_y: Box>, 71 | 72 | #[account( 73 | init, 74 | seeds = [ 75 | ORACLE, 76 | lb_pair.key().as_ref() 77 | ], 78 | bump, 79 | payer = funder, 80 | space = Oracle::space(DEFAULT_OBSERVATION_LENGTH) 81 | )] 82 | pub oracle: AccountLoader<'info, Oracle>, 83 | 84 | #[account( 85 | constraint = bin_step == preset_parameter.bin_step @ LBError::NonPresetBinStep, 86 | )] 87 | pub preset_parameter: Account<'info, PresetParameter>, 88 | 89 | #[account(mut)] 90 | pub funder: Signer<'info>, 91 | 92 | // #[account(address = Token2022::id())] 93 | pub token_program: Interface<'info, TokenInterface>, 94 | pub system_program: Program<'info, System>, 95 | pub rent: Sysvar<'info, Rent>, 96 | } 97 | 98 | pub fn handle(ctx: Context, active_id: i32, bin_step: u16) -> Result<()> { 99 | Ok(()) 100 | } 101 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/initialize_permission_lb_pair.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_launch_pool_admin; 2 | use crate::constants::DEFAULT_OBSERVATION_LENGTH; 3 | use crate::errors::LBError; 4 | use crate::events::LbPairCreate; 5 | use crate::state::bin_array_bitmap_extension::BinArrayBitmapExtension; 6 | use crate::state::lb_pair::LbPair; 7 | use crate::state::lb_pair::PairType; 8 | use crate::state::oracle::Oracle; 9 | use crate::state::preset_parameters::PresetParameter; 10 | use crate::utils::seeds::BIN_ARRAY_BITMAP_SEED; 11 | use crate::utils::seeds::ORACLE; 12 | use anchor_lang::prelude::*; 13 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 14 | use std::cmp::{max, min}; 15 | 16 | #[derive(AnchorSerialize, AnchorDeserialize)] 17 | pub struct InitPermissionPairIx { 18 | pub active_id: i32, 19 | pub bin_step: u16, 20 | pub base_factor: u16, 21 | pub min_bin_id: i32, 22 | pub max_bin_id: i32, 23 | pub lock_duration_in_slot: u64, 24 | } 25 | 26 | #[event_cpi] 27 | #[derive(Accounts)] 28 | #[instruction(ix_data: InitPermissionPairIx)] 29 | pub struct InitializePermissionLbPair<'info> { 30 | pub base: Signer<'info>, 31 | 32 | #[account( 33 | init, 34 | seeds = [ 35 | base.key().as_ref(), 36 | min(token_mint_x.key(), token_mint_y.key()).as_ref(), 37 | max(token_mint_x.key(), token_mint_y.key()).as_ref(), 38 | &ix_data.bin_step.to_le_bytes(), 39 | ], 40 | bump, 41 | payer = admin, 42 | space = 8 + LbPair::INIT_SPACE 43 | )] 44 | pub lb_pair: AccountLoader<'info, LbPair>, 45 | 46 | #[account( 47 | init, 48 | seeds = [ 49 | BIN_ARRAY_BITMAP_SEED, 50 | lb_pair.key().as_ref(), 51 | ], 52 | bump, 53 | payer = admin, 54 | space = 8 + BinArrayBitmapExtension::INIT_SPACE 55 | )] 56 | pub bin_array_bitmap_extension: Option>, 57 | 58 | #[account(constraint = token_mint_x.key() != token_mint_y.key())] 59 | pub token_mint_x: Box>, 60 | pub token_mint_y: Box>, 61 | 62 | #[account( 63 | init, 64 | seeds = [ 65 | lb_pair.key().as_ref(), 66 | token_mint_x.key().as_ref() 67 | ], 68 | bump, 69 | payer = admin, 70 | token::mint = token_mint_x, 71 | token::authority = lb_pair, 72 | )] 73 | pub reserve_x: Box>, 74 | #[account( 75 | init, 76 | seeds = [ 77 | lb_pair.key().as_ref(), 78 | token_mint_y.key().as_ref() 79 | ], 80 | bump, 81 | payer = admin, 82 | token::mint = token_mint_y, 83 | token::authority = lb_pair, 84 | )] 85 | pub reserve_y: Box>, 86 | 87 | #[account( 88 | init, 89 | seeds = [ 90 | ORACLE, 91 | lb_pair.key().as_ref() 92 | ], 93 | bump, 94 | payer = admin, 95 | space = Oracle::space(DEFAULT_OBSERVATION_LENGTH) 96 | )] 97 | pub oracle: AccountLoader<'info, Oracle>, 98 | 99 | #[account( 100 | mut, 101 | constraint = assert_eq_launch_pool_admin(admin.key()) @ LBError::InvalidAdmin, 102 | )] 103 | pub admin: Signer<'info>, 104 | 105 | pub token_program: Interface<'info, TokenInterface>, 106 | pub system_program: Program<'info, System>, 107 | pub rent: Sysvar<'info, Rent>, 108 | } 109 | 110 | pub fn handle( 111 | ctx: Context, 112 | ix_data: InitPermissionPairIx, 113 | ) -> Result<()> { 114 | Ok(()) 115 | } 116 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/initialize_position.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::state::{lb_pair::LbPair, position::PositionV2}; 4 | 5 | #[event_cpi] 6 | #[derive(Accounts)] 7 | pub struct InitializePosition<'info> { 8 | #[account(mut)] 9 | pub payer: Signer<'info>, 10 | 11 | #[account( 12 | init, 13 | payer = payer, 14 | space = 8 + PositionV2::INIT_SPACE, 15 | )] 16 | pub position: AccountLoader<'info, PositionV2>, 17 | 18 | pub lb_pair: AccountLoader<'info, LbPair>, 19 | 20 | pub owner: Signer<'info>, 21 | 22 | pub system_program: Program<'info, System>, 23 | pub rent: Sysvar<'info, Rent>, 24 | } 25 | 26 | pub fn handle(ctx: Context, lower_bin_id: i32, width: i32) -> Result<()> { 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/initialize_position_by_operator.rs: -------------------------------------------------------------------------------- 1 | use crate::state::lb_pair::LbPair; 2 | use crate::state::position::PositionV2; 3 | use crate::utils::seeds; 4 | use anchor_lang::prelude::*; 5 | 6 | #[event_cpi] 7 | #[derive(Accounts)] 8 | #[instruction(lower_bin_id: i32, width: i32)] 9 | pub struct InitializePositionByOperator<'info> { 10 | #[account(mut)] 11 | pub payer: Signer<'info>, 12 | 13 | pub base: Signer<'info>, 14 | #[account( 15 | init, 16 | seeds = [ 17 | seeds::POSITION.as_ref(), 18 | lb_pair.key().as_ref(), 19 | base.key().as_ref(), 20 | lower_bin_id.to_le_bytes().as_ref(), 21 | width.to_le_bytes().as_ref(), 22 | ], 23 | bump, 24 | payer = payer, 25 | space = 8 + PositionV2::INIT_SPACE, 26 | )] 27 | pub position: AccountLoader<'info, PositionV2>, 28 | 29 | pub lb_pair: AccountLoader<'info, LbPair>, 30 | 31 | /// operator 32 | pub operator: Signer<'info>, 33 | 34 | pub system_program: Program<'info, System>, 35 | 36 | pub rent: Sysvar<'info, Rent>, 37 | } 38 | 39 | /// There is scenario that operator create and deposit position with non-valid owner 40 | /// Then fund will be lost forever, so only whitelisted operators are able to perform this action 41 | pub fn handle( 42 | ctx: Context, 43 | lower_bin_id: i32, 44 | width: i32, 45 | owner: Pubkey, 46 | fee_owner: Pubkey, 47 | ) -> Result<()> { 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/initialize_position_pda.rs: -------------------------------------------------------------------------------- 1 | use crate::state::lb_pair::LbPair; 2 | use crate::state::position::PositionV2; 3 | use crate::utils::seeds; 4 | use anchor_lang::prelude::*; 5 | 6 | #[event_cpi] 7 | #[derive(Accounts)] 8 | #[instruction(lower_bin_id: i32, width: i32)] 9 | pub struct InitializePositionPda<'info> { 10 | #[account(mut)] 11 | pub payer: Signer<'info>, 12 | 13 | pub base: Signer<'info>, 14 | #[account( 15 | init, 16 | seeds = [ 17 | seeds::POSITION.as_ref(), 18 | lb_pair.key().as_ref(), 19 | base.key().as_ref(), 20 | lower_bin_id.to_le_bytes().as_ref(), 21 | width.to_le_bytes().as_ref(), 22 | ], 23 | bump, 24 | payer = payer, 25 | space = 8 + PositionV2::INIT_SPACE, 26 | )] 27 | pub position: AccountLoader<'info, PositionV2>, 28 | 29 | pub lb_pair: AccountLoader<'info, LbPair>, 30 | 31 | /// owner 32 | pub owner: Signer<'info>, 33 | 34 | pub system_program: Program<'info, System>, 35 | 36 | pub rent: Sysvar<'info, Rent>, 37 | } 38 | 39 | pub fn handle(ctx: Context, lower_bin_id: i32, width: i32) -> Result<()> { 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/initialize_preset_parameters.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_admin; 2 | use crate::errors::LBError; 3 | use crate::state::preset_parameters::PresetParameter; 4 | use crate::utils::seeds::PRESET_PARAMETER; 5 | use anchor_lang::prelude::*; 6 | 7 | #[derive(AnchorSerialize, AnchorDeserialize)] 8 | pub struct InitPresetParametersIx { 9 | /// Bin step. Represent the price increment / decrement. 10 | pub bin_step: u16, 11 | /// Used for base fee calculation. base_fee_rate = base_factor * bin_step 12 | pub base_factor: u16, 13 | /// Filter period determine high frequency trading time window. 14 | pub filter_period: u16, 15 | /// Decay period determine when the volatile fee start decay / decrease. 16 | pub decay_period: u16, 17 | /// Reduction factor controls the volatile fee rate decrement rate. 18 | pub reduction_factor: u16, 19 | /// Used to scale the variable fee component depending on the dynamic of the market 20 | pub variable_fee_control: u32, 21 | /// Maximum number of bin crossed can be accumulated. Used to cap volatile fee rate. 22 | pub max_volatility_accumulator: u32, 23 | /// Min bin id supported by the pool based on the configured bin step. 24 | pub min_bin_id: i32, 25 | /// Max bin id supported by the pool based on the configured bin step. 26 | pub max_bin_id: i32, 27 | /// Portion of swap fees retained by the protocol by controlling protocol_share parameter. protocol_swap_fee = protocol_share * total_swap_fee 28 | pub protocol_share: u16, 29 | } 30 | 31 | #[derive(Accounts)] 32 | #[instruction(ix: InitPresetParametersIx)] 33 | pub struct InitializePresetParameter<'info> { 34 | #[account( 35 | init, 36 | seeds = [ 37 | PRESET_PARAMETER, 38 | &ix.bin_step.to_le_bytes(), 39 | &ix.base_factor.to_le_bytes() 40 | ], 41 | bump, 42 | payer = admin, 43 | space = 8 + PresetParameter::INIT_SPACE 44 | )] 45 | pub preset_parameter: Account<'info, PresetParameter>, 46 | 47 | #[account( 48 | mut, 49 | constraint = assert_eq_admin(admin.key()) @ LBError::InvalidAdmin 50 | )] 51 | pub admin: Signer<'info>, 52 | 53 | pub system_program: Program<'info, System>, 54 | pub rent: Sysvar<'info, Rent>, 55 | } 56 | 57 | pub fn handle(ctx: Context, ix: InitPresetParametersIx) -> Result<()> { 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/initialize_reward.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_admin; 2 | use crate::errors::LBError; 3 | use crate::state::lb_pair::LbPair; 4 | use anchor_lang::prelude::*; 5 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 6 | 7 | #[event_cpi] 8 | #[derive(Accounts)] 9 | #[instruction(reward_index: u64)] 10 | pub struct InitializeReward<'info> { 11 | #[account(mut)] 12 | pub lb_pair: AccountLoader<'info, LbPair>, 13 | 14 | #[account( 15 | init, 16 | seeds = [ 17 | lb_pair.key().as_ref(), 18 | reward_index.to_le_bytes().as_ref() 19 | ], 20 | bump, 21 | payer = admin, 22 | token::mint = reward_mint, 23 | token::authority = lb_pair, 24 | )] 25 | pub reward_vault: Box>, 26 | 27 | pub reward_mint: Box>, 28 | 29 | #[account( 30 | mut, 31 | constraint = assert_eq_admin(admin.key()) @ LBError::InvalidAdmin, 32 | )] 33 | pub admin: Signer<'info>, 34 | 35 | pub token_program: Interface<'info, TokenInterface>, 36 | pub system_program: Program<'info, System>, 37 | pub rent: Sysvar<'info, Rent>, 38 | } 39 | 40 | pub fn handle( 41 | ctx: Context, 42 | index: u64, 43 | reward_duration: u64, 44 | funder: Pubkey, 45 | ) -> Result<()> { 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/migrate_bin_array.rs: -------------------------------------------------------------------------------- 1 | use crate::state::lb_pair::LbPair; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct MigrateBinArray<'info> { 6 | pub lb_pair: AccountLoader<'info, LbPair>, 7 | } 8 | 9 | pub fn handle(ctx: Context) -> Result<()> { 10 | Ok(()) 11 | } 12 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/migrate_position.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::state::{ 4 | bin::BinArray, 5 | lb_pair::LbPair, 6 | position::{Position, PositionV2}, 7 | }; 8 | 9 | #[event_cpi] 10 | #[derive(Accounts)] 11 | pub struct MigratePosition<'info> { 12 | #[account( 13 | init, 14 | payer = owner, 15 | space = 8 + PositionV2::INIT_SPACE, 16 | )] 17 | pub position_v2: AccountLoader<'info, PositionV2>, 18 | 19 | // TODO do we need to check whether it is pda? 20 | #[account( 21 | mut, 22 | has_one = owner, 23 | has_one = lb_pair, 24 | close = rent_receiver 25 | )] 26 | pub position_v1: AccountLoader<'info, Position>, 27 | 28 | pub lb_pair: AccountLoader<'info, LbPair>, 29 | 30 | #[account( 31 | mut, 32 | has_one = lb_pair 33 | )] 34 | pub bin_array_lower: AccountLoader<'info, BinArray>, 35 | #[account( 36 | mut, 37 | has_one = lb_pair 38 | )] 39 | pub bin_array_upper: AccountLoader<'info, BinArray>, 40 | 41 | #[account(mut)] 42 | pub owner: Signer<'info>, 43 | 44 | pub system_program: Program<'info, System>, 45 | 46 | /// CHECK: Account to receive closed account rental SOL 47 | #[account(mut)] 48 | pub rent_receiver: UncheckedAccount<'info>, 49 | } 50 | 51 | pub fn handle(ctx: Context) -> Result<()> { 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_liquidity; 2 | pub mod add_liquidity_by_strategy; 3 | pub mod add_liquidity_by_strategy_one_side; 4 | pub mod add_liquidity_by_weight; 5 | pub mod add_liquidity_by_weight_one_side; 6 | pub mod add_liquidity_single_side_precise; 7 | pub mod claim_fee; 8 | pub mod claim_reward; 9 | pub mod close_position; 10 | pub mod close_preset_parameter; 11 | pub mod fund_reward; 12 | pub mod increase_oracle_length; 13 | pub mod initialize_bin_array; 14 | pub mod initialize_bin_array_bitmap_extension; 15 | pub mod initialize_lb_pair; 16 | pub mod initialize_permission_lb_pair; 17 | pub mod initialize_position; 18 | pub mod initialize_position_by_operator; 19 | pub mod initialize_position_pda; 20 | pub mod initialize_preset_parameters; 21 | pub mod initialize_reward; 22 | pub mod migrate_bin_array; 23 | pub mod migrate_position; 24 | pub mod position_authorize; 25 | pub mod remove_all_liquidity; 26 | pub mod remove_liquidity; 27 | pub mod set_activation_slot; 28 | pub mod set_lock_release_slot; 29 | pub mod set_pre_activation_slot_duration; 30 | pub mod set_pre_activation_swap_address; 31 | pub mod swap; 32 | pub mod toggle_pair_status; 33 | pub mod update_fee_owner; 34 | pub mod update_fee_parameters; 35 | pub mod update_fees_and_rewards; 36 | pub mod update_position_operator; 37 | pub mod update_reward_duration; 38 | pub mod update_reward_funder; 39 | pub mod update_whitelisted_wallet; 40 | pub mod withdraw_ineligible_reward; 41 | pub mod withdraw_protocol_fee; 42 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/position_authorize.rs: -------------------------------------------------------------------------------- 1 | use crate::{assert_eq_launch_pool_admin, state::position::PositionV2}; 2 | use anchor_lang::prelude::*; 3 | 4 | pub fn authorize_modify_position<'info>( 5 | position: &AccountLoader<'info, PositionV2>, 6 | sender: Pubkey, 7 | ) -> Result { 8 | let position = position.load()?; 9 | return Ok(position.owner == sender || position.operator == sender); 10 | } 11 | 12 | pub fn authorize_claim_fee_position<'info>( 13 | position: &AccountLoader<'info, PositionV2>, 14 | sender: Pubkey, 15 | ) -> Result { 16 | let position = position.load()?; 17 | 18 | if position.fee_owner == Pubkey::default() { 19 | Ok(position.owner == sender || position.operator == sender) 20 | } else { 21 | Ok(position.owner == sender 22 | || position.operator == sender 23 | || position.fee_owner == sender 24 | || assert_eq_launch_pool_admin(sender)) 25 | } 26 | } 27 | 28 | pub trait PositionLiquidityFlowValidator { 29 | fn validate_outflow_to_ata_of_position_owner(&self, owner: Pubkey) -> Result<()>; 30 | } 31 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/remove_all_liquidity.rs: -------------------------------------------------------------------------------- 1 | use super::add_liquidity::ModifyLiquidity; 2 | 3 | use anchor_lang::prelude::*; 4 | 5 | pub fn handle<'a, 'b, 'c, 'info>( 6 | ctx: Context<'a, 'b, 'c, 'info, ModifyLiquidity<'info>>, 7 | ) -> Result<()> { 8 | Ok(()) 9 | } 10 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/remove_liquidity.rs: -------------------------------------------------------------------------------- 1 | use super::add_liquidity::ModifyLiquidity; 2 | use crate::constants::BASIS_POINT_MAX; 3 | use crate::{errors::LBError, math::safe_math::SafeMath, state::position::PositionV2}; 4 | use anchor_lang::prelude::*; 5 | use ruint::aliases::U256; 6 | #[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone)] 7 | pub struct BinLiquidityReduction { 8 | pub bin_id: i32, 9 | pub bps_to_remove: u16, 10 | } 11 | 12 | pub fn calculate_shares_to_remove(bps: u16, bin_id: i32, position: &PositionV2) -> Result { 13 | let share_in_bin = U256::from(position.get_liquidity_share_in_bin(bin_id)?); 14 | 15 | let share_to_remove: u128 = U256::from(bps) 16 | .safe_mul(share_in_bin)? 17 | .safe_div(U256::from(BASIS_POINT_MAX))? 18 | .try_into() 19 | .map_err(|_| LBError::TypeCastFailed)?; 20 | Ok(share_to_remove) 21 | } 22 | 23 | pub fn handle<'a, 'b, 'c, 'info>( 24 | ctx: Context<'a, 'b, 'c, 'info, ModifyLiquidity<'info>>, 25 | bin_liquidity_reduction: Vec, 26 | ) -> Result<()> { 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/set_activation_slot.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_admin; 2 | use crate::errors::LBError; 3 | use crate::state::lb_pair::LbPair; 4 | use anchor_lang::prelude::*; 5 | 6 | #[derive(Accounts)] 7 | pub struct SetActivationSlot<'info> { 8 | #[account(mut)] 9 | pub lb_pair: AccountLoader<'info, LbPair>, 10 | 11 | #[account( 12 | mut, 13 | constraint = lb_pair.load()?.creator.eq(&admin.key()) @ LBError::UnauthorizedAccess, 14 | )] 15 | pub admin: Signer<'info>, 16 | } 17 | 18 | pub fn handle(ctx: Context, activation_slot: u64) -> Result<()> { 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/set_lock_release_slot.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_launch_pool_admin; 2 | use crate::errors::LBError; 3 | use crate::state::{lb_pair::LbPair, position::PositionV2}; 4 | use anchor_lang::prelude::*; 5 | 6 | #[event_cpi] 7 | #[derive(Accounts)] 8 | #[instruction(lock_release_slot: u64)] 9 | pub struct SetLockReleaseSlot<'info> { 10 | #[account( 11 | mut, 12 | has_one = lb_pair 13 | )] 14 | pub position: AccountLoader<'info, PositionV2>, 15 | 16 | pub lb_pair: AccountLoader<'info, LbPair>, 17 | 18 | #[account( 19 | constraint = assert_eq_launch_pool_admin(sender.key()) @ LBError::UnauthorizedAccess 20 | )] 21 | pub sender: Signer<'info>, 22 | } 23 | 24 | pub fn handle(ctx: Context, lock_release_slot: u64) -> Result<()> { 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/set_pre_activation_slot_duration.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::LBError; 2 | use crate::math::safe_math::SafeMath; 3 | use crate::state::lb_pair::LbPair; 4 | use anchor_lang::prelude::*; 5 | 6 | #[derive(Accounts)] 7 | pub struct SetPreActivationInfo<'info> { 8 | #[account( 9 | mut, 10 | has_one = creator 11 | )] 12 | pub lb_pair: AccountLoader<'info, LbPair>, 13 | 14 | pub creator: Signer<'info>, 15 | } 16 | 17 | pub fn handle( 18 | ctx: Context, 19 | pre_activation_slot_duration: u16, // Around 9 hours buffer 20 | ) -> Result<()> { 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/set_pre_activation_swap_address.rs: -------------------------------------------------------------------------------- 1 | use crate::{errors::LBError, SetPreActivationInfo}; 2 | use anchor_lang::prelude::*; 3 | 4 | pub fn handle( 5 | ctx: Context, 6 | pre_activation_swap_address: Pubkey, 7 | ) -> Result<()> { 8 | Ok(()) 9 | } 10 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/swap.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::LBError; 2 | use crate::state::bin_array_bitmap_extension::BinArrayBitmapExtension; 3 | use crate::state::lb_pair::*; 4 | use crate::state::oracle::Oracle; 5 | use anchor_lang::prelude::*; 6 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 7 | 8 | #[event_cpi] 9 | #[derive(Accounts)] 10 | pub struct Swap<'info> { 11 | #[account( 12 | mut, 13 | has_one = reserve_x, 14 | has_one = reserve_y, 15 | has_one = token_x_mint, 16 | has_one = token_y_mint, 17 | has_one = oracle, 18 | )] 19 | pub lb_pair: AccountLoader<'info, LbPair>, 20 | 21 | #[account( 22 | has_one = lb_pair, 23 | )] 24 | pub bin_array_bitmap_extension: Option>, 25 | 26 | #[account(mut)] 27 | pub reserve_x: Box>, 28 | #[account(mut)] 29 | pub reserve_y: Box>, 30 | 31 | #[account( 32 | mut, 33 | constraint = user_token_in.mint != user_token_out.mint @ LBError::InvalidTokenMint, 34 | constraint = user_token_in.mint == token_x_mint.key() || user_token_in.mint == token_y_mint.key() @ LBError::InvalidTokenMint, 35 | )] 36 | pub user_token_in: Box>, 37 | #[account( 38 | mut, 39 | constraint = user_token_out.mint == token_x_mint.key() || user_token_out.mint == token_y_mint.key() @ LBError::InvalidTokenMint, 40 | )] 41 | pub user_token_out: Box>, 42 | 43 | pub token_x_mint: Box>, 44 | pub token_y_mint: Box>, 45 | 46 | #[account(mut)] 47 | pub oracle: AccountLoader<'info, Oracle>, 48 | 49 | #[account(mut)] 50 | pub host_fee_in: Option>>, 51 | 52 | pub user: Signer<'info>, 53 | pub token_x_program: Interface<'info, TokenInterface>, 54 | pub token_y_program: Interface<'info, TokenInterface>, 55 | } 56 | 57 | pub fn handle<'a, 'b, 'c, 'info>( 58 | ctx: Context<'a, 'b, 'c, 'info, Swap<'info>>, 59 | amount_in: u64, 60 | min_amount_out: u64, 61 | ) -> Result<()> { 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/toggle_pair_status.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_admin; 2 | use crate::errors::LBError; 3 | use crate::state::lb_pair::LbPair; 4 | use anchor_lang::prelude::*; 5 | 6 | #[derive(Accounts)] 7 | pub struct TogglePairStatus<'info> { 8 | #[account(mut)] 9 | pub lb_pair: AccountLoader<'info, LbPair>, 10 | 11 | #[account(constraint = assert_eq_admin(admin.key()) @ LBError::InvalidAdmin)] 12 | pub admin: Signer<'info>, 13 | } 14 | 15 | pub fn handle(ctx: Context) -> Result<()> { 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/update_fee_owner.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_admin; 2 | use crate::errors::LBError; 3 | use crate::state::lb_pair::LbPair; 4 | use anchor_lang::prelude::*; 5 | 6 | #[derive(Accounts)] 7 | pub struct UpdateFeeOwner<'info> { 8 | #[account(mut)] 9 | pub lb_pair: AccountLoader<'info, LbPair>, 10 | 11 | /// CHECK: New fee owner doesn't need to be deserialized. 12 | #[account( 13 | constraint = lb_pair.load()?.fee_owner != new_fee_owner.key() @ LBError::IdenticalFeeOwner, 14 | )] 15 | pub new_fee_owner: UncheckedAccount<'info>, 16 | 17 | #[account( 18 | constraint = assert_eq_admin(admin.key()) @ LBError::InvalidAdmin, 19 | )] 20 | pub admin: Signer<'info>, 21 | } 22 | 23 | pub fn handle(ctx: Context) -> Result<()> { 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/update_fee_parameters.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_admin; 2 | use crate::errors::LBError; 3 | use crate::state::lb_pair::LbPair; 4 | use anchor_lang::prelude::*; 5 | 6 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)] 7 | pub struct FeeParameter { 8 | /// Portion of swap fees retained by the protocol by controlling protocol_share parameter. protocol_swap_fee = protocol_share * total_swap_fee 9 | pub protocol_share: u16, 10 | /// Base factor for base fee rate 11 | pub base_factor: u16, 12 | } 13 | 14 | #[event_cpi] 15 | #[derive(Accounts)] 16 | pub struct UpdateFeeParameters<'info> { 17 | #[account(mut)] 18 | pub lb_pair: AccountLoader<'info, LbPair>, 19 | 20 | #[account(constraint = assert_eq_admin(admin.key()) @ LBError::InvalidAdmin)] 21 | pub admin: Signer<'info>, 22 | } 23 | 24 | pub fn handle(ctx: Context, fee_parameter: FeeParameter) -> Result<()> { 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/update_fees_and_rewards.rs: -------------------------------------------------------------------------------- 1 | use crate::authorize_modify_position; 2 | use crate::state::{bin::BinArray, lb_pair::LbPair, position::PositionV2}; 3 | use anchor_lang::prelude::*; 4 | 5 | #[derive(Accounts)] 6 | pub struct UpdateFeesAndRewards<'info> { 7 | #[account( 8 | mut, 9 | has_one = lb_pair, 10 | constraint = authorize_modify_position(&position, owner.key())? 11 | )] 12 | pub position: AccountLoader<'info, PositionV2>, 13 | 14 | #[account(mut)] 15 | pub lb_pair: AccountLoader<'info, LbPair>, 16 | 17 | #[account( 18 | mut, 19 | has_one = lb_pair 20 | )] 21 | pub bin_array_lower: AccountLoader<'info, BinArray>, 22 | #[account( 23 | mut, 24 | has_one = lb_pair 25 | )] 26 | pub bin_array_upper: AccountLoader<'info, BinArray>, 27 | 28 | pub owner: Signer<'info>, 29 | } 30 | 31 | pub fn handle(ctx: Context) -> Result<()> { 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/update_position_operator.rs: -------------------------------------------------------------------------------- 1 | use crate::state::position::PositionV2; 2 | use anchor_lang::prelude::*; 3 | #[event_cpi] 4 | #[derive(Accounts)] 5 | pub struct UpdatePositionOperator<'info> { 6 | #[account(mut, has_one = owner)] 7 | pub position: AccountLoader<'info, PositionV2>, 8 | pub owner: Signer<'info>, 9 | } 10 | 11 | pub fn handle(ctx: Context, new_operator: Pubkey) -> Result<()> { 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/update_reward_duration.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_admin; 2 | use crate::errors::LBError; 3 | use crate::state::bin::BinArray; 4 | use crate::state::lb_pair::LbPair; 5 | use anchor_lang::prelude::*; 6 | 7 | #[event_cpi] 8 | #[derive(Accounts)] 9 | #[instruction(reward_index: u64)] 10 | pub struct UpdateRewardDuration<'info> { 11 | #[account(mut)] 12 | pub lb_pair: AccountLoader<'info, LbPair>, 13 | 14 | #[account( 15 | constraint = assert_eq_admin(admin.key()) @ LBError::InvalidAdmin, 16 | )] 17 | pub admin: Signer<'info>, 18 | 19 | #[account( 20 | mut, 21 | has_one = lb_pair 22 | )] 23 | pub bin_array: AccountLoader<'info, BinArray>, 24 | } 25 | 26 | pub fn handle( 27 | ctx: Context, 28 | index: u64, 29 | new_reward_duration: u64, 30 | ) -> Result<()> { 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/update_reward_funder.rs: -------------------------------------------------------------------------------- 1 | use crate::assert_eq_admin; 2 | use crate::constants::NUM_REWARDS; 3 | use crate::errors::LBError; 4 | use crate::state::lb_pair::LbPair; 5 | use anchor_lang::prelude::*; 6 | 7 | #[event_cpi] 8 | #[derive(Accounts)] 9 | #[instruction(reward_index: u64)] 10 | pub struct UpdateRewardFunder<'info> { 11 | #[account(mut)] 12 | pub lb_pair: AccountLoader<'info, LbPair>, 13 | 14 | #[account(constraint = assert_eq_admin(admin.key()) @ LBError::InvalidAdmin)] 15 | pub admin: Signer<'info>, 16 | } 17 | 18 | pub fn handle(ctx: Context, index: u64, new_funder: Pubkey) -> Result<()> { 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/update_whitelisted_wallet.rs: -------------------------------------------------------------------------------- 1 | use crate::{errors::LBError, state::lb_pair::LbPair}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct UpdateWhitelistWallet<'info> { 6 | #[account( 7 | mut, 8 | has_one = creator 9 | )] 10 | pub lb_pair: AccountLoader<'info, LbPair>, 11 | 12 | pub creator: Signer<'info>, 13 | } 14 | 15 | pub fn handle(ctx: Context, idx: u8, wallet: Pubkey) -> Result<()> { 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/withdraw_ineligible_reward.rs: -------------------------------------------------------------------------------- 1 | use crate::state::{bin::BinArray, lb_pair::LbPair}; 2 | use anchor_lang::prelude::*; 3 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 4 | 5 | #[event_cpi] 6 | #[derive(Accounts)] 7 | #[instruction(reward_index: u64)] 8 | pub struct WithdrawIneligibleReward<'info> { 9 | #[account(mut)] 10 | pub lb_pair: AccountLoader<'info, LbPair>, 11 | 12 | #[account(mut)] 13 | pub reward_vault: Box>, 14 | 15 | pub reward_mint: Box>, 16 | #[account(mut)] 17 | pub funder_token_account: Box>, 18 | 19 | pub funder: Signer<'info>, 20 | 21 | #[account( 22 | mut, 23 | has_one = lb_pair 24 | )] 25 | pub bin_array: AccountLoader<'info, BinArray>, 26 | 27 | pub token_program: Interface<'info, TokenInterface>, 28 | } 29 | 30 | pub fn handle(ctx: Context, index: u64) -> Result<()> { 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/instructions/withdraw_protocol_fee.rs: -------------------------------------------------------------------------------- 1 | use crate::state::lb_pair::LbPair; 2 | use anchor_lang::prelude::*; 3 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 4 | 5 | #[derive(Accounts)] 6 | pub struct WithdrawProtocolFee<'info> { 7 | #[account( 8 | mut, 9 | has_one = reserve_x, 10 | has_one = reserve_y, 11 | has_one = token_x_mint, 12 | has_one = token_y_mint, 13 | )] 14 | pub lb_pair: AccountLoader<'info, LbPair>, 15 | 16 | #[account(mut)] 17 | pub reserve_x: Box>, 18 | #[account(mut)] 19 | pub reserve_y: Box>, 20 | 21 | pub token_x_mint: Box>, 22 | pub token_y_mint: Box>, 23 | 24 | #[account(mut)] 25 | pub receiver_token_x: Box>, 26 | #[account(mut)] 27 | pub receiver_token_y: Box>, 28 | 29 | pub fee_owner: Signer<'info>, 30 | 31 | pub token_x_program: Interface<'info, TokenInterface>, 32 | pub token_y_program: Interface<'info, TokenInterface>, 33 | } 34 | 35 | pub fn handle(ctx: Context, amount_x: u64, amount_y: u64) -> Result<()> { 36 | Ok(()) 37 | } 38 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/manager/bin_array_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::MAX_BIN_PER_ARRAY; 2 | use crate::errors::LBError; 3 | use crate::state::bin::Bin; 4 | use crate::state::lb_pair::LbPair; 5 | use crate::{math::safe_math::SafeMath, state::bin::BinArray}; 6 | use anchor_lang::prelude::*; 7 | use std::cell::{Ref, RefMut}; 8 | 9 | /// A bin arrays container which make sure that the bin array are in continuous form. 10 | pub struct BinArrayManager<'a, 'info> { 11 | bin_arrays: &'a mut [RefMut<'info, BinArray>], 12 | } 13 | 14 | impl<'a, 'info> BinArrayManager<'a, 'info> { 15 | pub fn new(bin_arrays: &'a mut [RefMut<'info, BinArray>]) -> Result { 16 | Ok(BinArrayManager { bin_arrays }) 17 | } 18 | 19 | pub fn migrate_to_v2(&mut self) -> Result<()> { 20 | // do it every step 21 | for bin_array in self.bin_arrays.iter_mut() { 22 | bin_array.migrate_to_v2()?; 23 | } 24 | Ok(()) 25 | } 26 | 27 | pub fn get_zero_liquidity_flags(&self) -> Vec { 28 | let mut flags = vec![]; 29 | for bin_array in self.bin_arrays.iter() { 30 | flags.push(bin_array.is_zero_liquidity()); 31 | } 32 | flags 33 | } 34 | 35 | pub fn get_bin_array_index(&self, index: usize) -> Result { 36 | let index = 37 | i32::try_from(self.bin_arrays[index].index).map_err(|_| LBError::MathOverflow)?; 38 | Ok(index) 39 | } 40 | 41 | /// Validate whether the lower and upper bin array loaded aligned to lower bin id 42 | pub fn validate_bin_arrays(&self, lower_bin_id: i32) -> Result<()> { 43 | require!(self.bin_arrays.len() > 0, LBError::InvalidInput); 44 | 45 | let bin_array_0_index = BinArray::bin_id_to_bin_array_index(lower_bin_id)?; 46 | 47 | require!( 48 | bin_array_0_index as i64 == self.bin_arrays[0].index, 49 | LBError::InvalidInput 50 | ); 51 | 52 | for i in 0..self.bin_arrays.len().saturating_sub(1) { 53 | let current_bin_array = &self.bin_arrays[i]; 54 | let next_bin_array = &self.bin_arrays[i + 1]; 55 | 56 | require!( 57 | current_bin_array.index + 1 == next_bin_array.index, 58 | LBError::NonContinuousBinArrays 59 | ); 60 | } 61 | 62 | Ok(()) 63 | } 64 | 65 | pub fn get_lower_upper_bin_id(&self) -> Result<(i32, i32)> { 66 | let lower_bin_array_idx = self.bin_arrays[0].index as i32; 67 | let upper_bin_array_idx = self.bin_arrays[self.bin_arrays.len() - 1].index as i32; 68 | 69 | let lower_bin_id = lower_bin_array_idx.safe_mul(MAX_BIN_PER_ARRAY as i32)?; 70 | let upper_bin_id = upper_bin_array_idx 71 | .safe_mul(MAX_BIN_PER_ARRAY as i32)? 72 | .safe_add(MAX_BIN_PER_ARRAY as i32)? 73 | .safe_sub(1)?; 74 | 75 | Ok((lower_bin_id, upper_bin_id)) 76 | } 77 | 78 | pub fn is_bin_id_within_range(&self, bin_id: i32) -> Result<()> { 79 | let (lower_bin_id, upper_bin_id) = self.get_lower_upper_bin_id()?; 80 | 81 | require!( 82 | bin_id >= lower_bin_id && bin_id <= upper_bin_id, 83 | LBError::InvalidBinArray 84 | ); 85 | 86 | Ok(()) 87 | } 88 | 89 | // Update the rewards for active bin. If the active bin doesn't within the bin arrays, nothing will be updated. 90 | pub fn update_rewards<'b>(&mut self, lb_pair: &mut RefMut<'b, LbPair>) -> Result<()> { 91 | let current_timestamp = Clock::get()?.unix_timestamp; 92 | 93 | for bin_array in self.bin_arrays.iter_mut() { 94 | if bin_array.is_bin_id_within_range(lb_pair.active_id).is_ok() { 95 | bin_array.update_all_rewards(lb_pair, current_timestamp as u64)?; 96 | break; 97 | } 98 | } 99 | 100 | Ok(()) 101 | } 102 | 103 | pub fn get_continuous_bins(&'a self) -> impl Iterator { 104 | self.bin_arrays 105 | .iter() 106 | .map(|ba| ba.bins.iter()) 107 | .flat_map(|bins_iter| bins_iter) 108 | } 109 | 110 | pub fn get_bin_arrays(&'a mut self) -> &'a mut [RefMut<'info, BinArray>] { 111 | &mut self.bin_arrays 112 | } 113 | 114 | pub fn get_bin(&self, bin_id: i32) -> Result<&Bin> { 115 | let bin_array_idx = BinArray::bin_id_to_bin_array_index(bin_id)?; 116 | match self 117 | .bin_arrays 118 | .iter() 119 | .find(|ba| ba.index == bin_array_idx as i64) 120 | { 121 | Some(bin_array) => bin_array.get_bin(bin_id), 122 | None => Err(LBError::InvalidBinArray.into()), 123 | } 124 | } 125 | 126 | pub fn get_bin_mut(&mut self, bin_id: i32) -> Result<&mut Bin> { 127 | let bin_array_idx = BinArray::bin_id_to_bin_array_index(bin_id)?; 128 | match self 129 | .bin_arrays 130 | .iter_mut() 131 | .find(|ba| ba.index == bin_array_idx as i64) 132 | { 133 | Some(bin_array) => bin_array.get_bin_mut(bin_id), 134 | None => Err(LBError::InvalidBinArray.into()), 135 | } 136 | } 137 | } 138 | 139 | pub struct BinArrayManagerReadOnly<'a, 'info> { 140 | bin_arrays: &'a [Ref<'info, BinArray>], 141 | } 142 | 143 | impl<'a, 'info> BinArrayManagerReadOnly<'a, 'info> { 144 | pub fn new(bin_arrays: &'a [Ref<'info, BinArray>]) -> Result { 145 | Ok(BinArrayManagerReadOnly { bin_arrays }) 146 | } 147 | 148 | pub fn get_bin(&self, bin_id: i32) -> Result<&Bin> { 149 | let bin_array_idx = BinArray::bin_id_to_bin_array_index(bin_id)?; 150 | match self 151 | .bin_arrays 152 | .iter() 153 | .find(|ba| ba.index == bin_array_idx as i64) 154 | { 155 | Some(bin_array) => bin_array.get_bin(bin_id), 156 | None => Err(LBError::InvalidBinArray.into()), 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/manager/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bin_array_manager; 2 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/math/bin_math.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::LBError; 2 | 3 | use super::safe_math::SafeMath; 4 | use super::u64x64_math::SCALE_OFFSET; 5 | use anchor_lang::prelude::Result; 6 | use ruint::aliases::U256; 7 | 8 | /// Calculate the amount of liquidity following the constant sum formula `L = price * x + y` 9 | /// Price is in Q64x64 10 | pub fn get_liquidity(x: u64, y: u64, price: u128) -> Result { 11 | // Q64x0 12 | let x: U256 = U256::from(x); 13 | 14 | // Multiplication do not require same Q number format. px is in Q64x64 15 | let price = U256::from(price); 16 | let px = price.safe_mul(x)?; 17 | 18 | // When perform add, both must be same Q number format. Therefore << SCALE_OFFSET to make y and px Q64x64 19 | let y = u128::from(y).safe_shl(SCALE_OFFSET.into())?; 20 | let y = U256::from(y); 21 | // Liquidity represented with fractional part 22 | let liquidity = px.safe_add(U256::from(y))?; 23 | Ok(liquidity.try_into().map_err(|_| LBError::TypeCastFailed)?) 24 | } 25 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/math/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bin_math; 2 | pub mod price_math; 3 | pub mod safe_math; 4 | pub mod u128x128_math; 5 | pub mod u64x64_math; 6 | pub mod utils_math; 7 | pub mod weight_to_amounts; 8 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/math/price_math.rs: -------------------------------------------------------------------------------- 1 | use super::safe_math::SafeMath; 2 | use super::u64x64_math::{pow, ONE, SCALE_OFFSET}; 3 | use crate::constants::BASIS_POINT_MAX; 4 | use crate::errors::LBError; 5 | use anchor_lang::prelude::*; 6 | 7 | // In Trader Joe, the active_id need to be shifted by 2 ** 23 to get the actual ID. 8 | // The reason is because they mint LP for each bin based on active_id using ERC1155, which the ID do not support negative 9 | 10 | /// Calculate price based on the given bin id. Eg: 1.0001 ^ 5555. The returned value is in Q64.64 11 | pub fn get_price_from_id(active_id: i32, bin_step: u16) -> Result { 12 | // Make bin_step into Q64x64, and divided by BASIS_POINT_MAX. If bin_step = 1, we get 0.0001 in Q64x64 13 | let bps = u128::from(bin_step) 14 | .safe_shl(SCALE_OFFSET.into())? 15 | .safe_div(BASIS_POINT_MAX as u128)?; 16 | // Add 1 to bps, we get 1.0001 in Q64.64 17 | let base = ONE.safe_add(bps)?; 18 | pow(base, active_id).ok_or_else(|| LBError::MathOverflow.into()) 19 | } 20 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/math/safe_math.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::LBError; 2 | use anchor_lang::solana_program::msg; 3 | use ruint::aliases::U256; 4 | use std::panic::Location; 5 | 6 | pub trait SafeMath: Sized { 7 | fn safe_add(self, rhs: Self) -> Result; 8 | fn safe_mul(self, rhs: Self) -> Result; 9 | fn safe_div(self, rhs: Self) -> Result; 10 | fn safe_rem(self, rhs: Self) -> Result; 11 | fn safe_sub(self, rhs: Self) -> Result; 12 | fn safe_shl(self, offset: T) -> Result; 13 | fn safe_shr(self, offset: T) -> Result; 14 | } 15 | 16 | macro_rules! checked_impl { 17 | ($t:ty, $offset:ty) => { 18 | impl SafeMath<$offset> for $t { 19 | #[inline(always)] 20 | fn safe_add(self, v: $t) -> Result<$t, LBError> { 21 | match self.checked_add(v) { 22 | Some(result) => Ok(result), 23 | None => { 24 | let caller = Location::caller(); 25 | msg!("Math error thrown at {}:{}", caller.file(), caller.line()); 26 | Err(LBError::MathOverflow) 27 | } 28 | } 29 | } 30 | 31 | #[inline(always)] 32 | fn safe_sub(self, v: $t) -> Result<$t, LBError> { 33 | match self.checked_sub(v) { 34 | Some(result) => Ok(result), 35 | None => { 36 | let caller = Location::caller(); 37 | msg!("Math error thrown at {}:{}", caller.file(), caller.line()); 38 | Err(LBError::MathOverflow) 39 | } 40 | } 41 | } 42 | 43 | #[inline(always)] 44 | fn safe_mul(self, v: $t) -> Result<$t, LBError> { 45 | match self.checked_mul(v) { 46 | Some(result) => Ok(result), 47 | None => { 48 | let caller = Location::caller(); 49 | msg!("Math error thrown at {}:{}", caller.file(), caller.line()); 50 | Err(LBError::MathOverflow) 51 | } 52 | } 53 | } 54 | 55 | #[inline(always)] 56 | fn safe_div(self, v: $t) -> Result<$t, LBError> { 57 | match self.checked_div(v) { 58 | Some(result) => Ok(result), 59 | None => { 60 | let caller = Location::caller(); 61 | msg!("Math error thrown at {}:{}", caller.file(), caller.line()); 62 | Err(LBError::MathOverflow) 63 | } 64 | } 65 | } 66 | 67 | #[inline(always)] 68 | fn safe_rem(self, v: $t) -> Result<$t, LBError> { 69 | match self.checked_rem(v) { 70 | Some(result) => Ok(result), 71 | None => { 72 | let caller = Location::caller(); 73 | msg!("Math error thrown at {}:{}", caller.file(), caller.line()); 74 | Err(LBError::MathOverflow) 75 | } 76 | } 77 | } 78 | 79 | #[inline(always)] 80 | fn safe_shl(self, v: $offset) -> Result<$t, LBError> { 81 | match self.checked_shl(v) { 82 | Some(result) => Ok(result), 83 | None => { 84 | let caller = Location::caller(); 85 | msg!("Math error thrown at {}:{}", caller.file(), caller.line()); 86 | Err(LBError::MathOverflow) 87 | } 88 | } 89 | } 90 | 91 | #[inline(always)] 92 | fn safe_shr(self, v: $offset) -> Result<$t, LBError> { 93 | match self.checked_shr(v) { 94 | Some(result) => Ok(result), 95 | None => { 96 | let caller = Location::caller(); 97 | msg!("Math error thrown at {}:{}", caller.file(), caller.line()); 98 | Err(LBError::MathOverflow) 99 | } 100 | } 101 | } 102 | } 103 | }; 104 | } 105 | 106 | checked_impl!(u16, u32); 107 | checked_impl!(i32, u32); 108 | checked_impl!(u32, u32); 109 | checked_impl!(u64, u32); 110 | checked_impl!(i64, u32); 111 | checked_impl!(u128, u32); 112 | checked_impl!(i128, u32); 113 | checked_impl!(usize, u32); 114 | checked_impl!(U256, usize); 115 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/math/u128x128_math.rs: -------------------------------------------------------------------------------- 1 | use ruint::aliases::U256; 2 | 3 | // Round up, down 4 | #[derive(PartialEq)] 5 | pub enum Rounding { 6 | Up, 7 | Down, 8 | } 9 | 10 | /// (x * y) / denominator 11 | pub fn mul_div(x: u128, y: u128, denominator: u128, rounding: Rounding) -> Option { 12 | if denominator == 0 { 13 | return None; 14 | } 15 | 16 | let x = U256::from(x); 17 | let y = U256::from(y); 18 | let denominator = U256::from(denominator); 19 | 20 | let prod = x.checked_mul(y)?; 21 | 22 | match rounding { 23 | Rounding::Up => prod.div_ceil(denominator).try_into().ok(), 24 | Rounding::Down => { 25 | let (quotient, _) = prod.div_rem(denominator); 26 | quotient.try_into().ok() 27 | } 28 | } 29 | } 30 | 31 | /// (x * y) >> offset 32 | #[inline] 33 | pub fn mul_shr(x: u128, y: u128, offset: u8, rounding: Rounding) -> Option { 34 | let denominator = 1u128.checked_shl(offset.into())?; 35 | mul_div(x, y, denominator, rounding) 36 | } 37 | 38 | /// (x << offset) / y 39 | #[inline] 40 | pub fn shl_div(x: u128, y: u128, offset: u8, rounding: Rounding) -> Option { 41 | let scale = 1u128.checked_shl(offset.into())?; 42 | mul_div(x, scale, y, rounding) 43 | } 44 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/math/u64x64_math.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::BASIS_POINT_MAX; 2 | use ruint::aliases::U256; 3 | 4 | // Precision when converting from decimal to fixed point. Or the other way around. 10^12 5 | pub const PRECISION: u128 = 1_000_000_000_000; 6 | 7 | // Number of bits to scale. This will decide the position of the radix point. 8 | pub const SCALE_OFFSET: u8 = 64; 9 | 10 | // Where does this value come from ? 11 | // When smallest bin is used (1 bps), the maximum of bin limit is 887272 (Check: https://docs.traderjoexyz.com/concepts/bin-math). 12 | // But in solana, the token amount is represented in 64 bits, therefore, it will be (1 + 0.0001)^n < 2 ** 64, solve for n, n ~= 443636 13 | // Then we calculate bits needed to represent 443636 exponential, 2^n >= 443636, ~= 19 14 | // If we convert 443636 to binary form, it will be 1101100010011110100 (19 bits). 15 | // Which, the 19 bits are the bits the binary exponential will loop through. 16 | // The 20th bit will be 0x80000, which the exponential already > the maximum number of bin Q64.64 can support 17 | const MAX_EXPONENTIAL: u32 = 0x80000; // 1048576 18 | 19 | // 1.0000... representation of 64x64 20 | pub const ONE: u128 = 1u128 << SCALE_OFFSET; 21 | 22 | pub fn pow(base: u128, exp: i32) -> Option { 23 | // If exponent is negative. We will invert the result later by 1 / base^exp.abs() 24 | let mut invert = exp.is_negative(); 25 | 26 | // When exponential is 0, result will always be 1 27 | if exp == 0 { 28 | return Some(1u128 << 64); 29 | } 30 | 31 | // Make the exponential positive. Which will compute the result later by 1 / base^exp 32 | let exp: u32 = if invert { exp.abs() as u32 } else { exp as u32 }; 33 | 34 | // No point to continue the calculation as it will overflow the maximum value Q64.64 can support 35 | if exp >= MAX_EXPONENTIAL { 36 | return None; 37 | } 38 | 39 | let mut squared_base = base; 40 | let mut result = ONE; 41 | 42 | // When multiply the base twice, the number of bits double from 128 -> 256, which overflow. 43 | // The trick here is to inverse the calculation, which make the upper 64 bits (number bits) to be 0s. 44 | // For example: 45 | // let base = 1.001, exp = 5 46 | // let neg = 1 / (1.001 ^ 5) 47 | // Inverse the neg: 1 / neg 48 | // By using a calculator, you will find out that 1.001^5 == 1 / (1 / 1.001^5) 49 | if squared_base >= result { 50 | // This inverse the base: 1 / base 51 | squared_base = u128::MAX.checked_div(squared_base)?; 52 | // If exponent is negative, the above already inverted the result. Therefore, at the end of the function, we do not need to invert again. 53 | invert = !invert; 54 | } 55 | 56 | // The following code is equivalent to looping through each binary value of the exponential. 57 | // As explained in MAX_EXPONENTIAL, 19 exponential bits are enough to covert the full bin price. 58 | // Therefore, there will be 19 if statements, which similar to the following pseudo code. 59 | /* 60 | let mut result = 1; 61 | while exponential > 0 { 62 | if exponential & 1 > 0 { 63 | result *= base; 64 | } 65 | base *= base; 66 | exponential >>= 1; 67 | } 68 | */ 69 | 70 | // From right to left 71 | // squared_base = 1 * base^1 72 | // 1st bit is 1 73 | if exp & 0x1 > 0 { 74 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 75 | } 76 | 77 | // squared_base = base^2 78 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 79 | // 2nd bit is 1 80 | if exp & 0x2 > 0 { 81 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 82 | } 83 | 84 | // Example: 85 | // If the base is 1.001, exponential is 3. Binary form of 3 is ..0011. The last 2 1's bit fulfill the above 2 bitwise condition. 86 | // The result will be 1 * base^1 * base^2 == base^3. The process continues until reach the 20th bit 87 | 88 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 89 | if exp & 0x4 > 0 { 90 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 91 | } 92 | 93 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 94 | if exp & 0x8 > 0 { 95 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 96 | } 97 | 98 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 99 | if exp & 0x10 > 0 { 100 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 101 | } 102 | 103 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 104 | if exp & 0x20 > 0 { 105 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 106 | } 107 | 108 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 109 | if exp & 0x40 > 0 { 110 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 111 | } 112 | 113 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 114 | if exp & 0x80 > 0 { 115 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 116 | } 117 | 118 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 119 | if exp & 0x100 > 0 { 120 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 121 | } 122 | 123 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 124 | if exp & 0x200 > 0 { 125 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 126 | } 127 | 128 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 129 | if exp & 0x400 > 0 { 130 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 131 | } 132 | 133 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 134 | if exp & 0x800 > 0 { 135 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 136 | } 137 | 138 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 139 | if exp & 0x1000 > 0 { 140 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 141 | } 142 | 143 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 144 | if exp & 0x2000 > 0 { 145 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 146 | } 147 | 148 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 149 | if exp & 0x4000 > 0 { 150 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 151 | } 152 | 153 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 154 | if exp & 0x8000 > 0 { 155 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 156 | } 157 | 158 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 159 | if exp & 0x10000 > 0 { 160 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 161 | } 162 | 163 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 164 | if exp & 0x20000 > 0 { 165 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 166 | } 167 | 168 | squared_base = (squared_base.checked_mul(squared_base)?) >> SCALE_OFFSET; 169 | if exp & 0x40000 > 0 { 170 | result = (result.checked_mul(squared_base)?) >> SCALE_OFFSET 171 | } 172 | 173 | // Stop here as the next is 20th bit, which > MAX_EXPONENTIAL 174 | if result == 0 { 175 | return None; 176 | } 177 | 178 | if invert { 179 | result = u128::MAX.checked_div(result)?; 180 | } 181 | 182 | Some(result) 183 | } 184 | 185 | // Helper function to convert fixed point number to decimal with 10^12 precision. Decimal form is not being used in program, it's only for UI purpose. 186 | pub fn to_decimal(value: u128) -> Option { 187 | let value = U256::from(value); 188 | let precision = U256::from(PRECISION); 189 | let scaled_value = value.checked_mul(precision)?; 190 | // ruint checked math is different with the rust std u128. If there's bit with 1 value being shifted out, it will return None. Therefore, we use overflowing_shr 191 | let (scaled_down_value, _) = scaled_value.overflowing_shr(SCALE_OFFSET.into()); 192 | scaled_down_value.try_into().ok() 193 | } 194 | 195 | // Helper function to convert decimal with 10^12 precision to fixed point number 196 | pub fn from_decimal(value: u128) -> Option { 197 | let value = U256::from(value); 198 | let precision = U256::from(PRECISION); 199 | let (q_value, _) = value.overflowing_shl(SCALE_OFFSET.into()); 200 | let fp_value = q_value.checked_div(precision)?; 201 | fp_value.try_into().ok() 202 | } 203 | 204 | // Helper function to get the base for price calculation. Eg: 1.001 in 64x64 representation 205 | pub fn get_base(bin_step: u32) -> Option { 206 | let quotient = u128::from(bin_step).checked_shl(SCALE_OFFSET.into())?; 207 | let fraction = quotient.checked_div(BASIS_POINT_MAX as u128)?; 208 | ONE.checked_add(fraction) 209 | } 210 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/math/utils_math.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | safe_math::SafeMath, 3 | u128x128_math::{mul_div, mul_shr, shl_div, Rounding}, 4 | u64x64_math::pow, 5 | }; 6 | use crate::errors::LBError; 7 | use anchor_lang::prelude::Result; 8 | use num_traits::cast::FromPrimitive; 9 | use ruint::{aliases::U256, Uint}; 10 | 11 | #[inline] 12 | pub fn safe_pow_cast(base: u128, exp: i32) -> Result { 13 | T::from_u128(pow(base, exp).ok_or_else(|| LBError::MathOverflow)?) 14 | .ok_or_else(|| LBError::TypeCastFailed.into()) 15 | } 16 | 17 | #[inline] 18 | pub fn safe_mul_div_cast( 19 | x: u128, 20 | y: u128, 21 | denominator: u128, 22 | rounding: Rounding, 23 | ) -> Result { 24 | T::from_u128(mul_div(x, y, denominator, rounding).ok_or_else(|| LBError::MathOverflow)?) 25 | .ok_or_else(|| LBError::TypeCastFailed.into()) 26 | } 27 | 28 | #[inline] 29 | pub fn safe_mul_div_cast_from_u64_to_u64(x: u64, y: u64, denominator: u64) -> Result { 30 | let x = u128::from(x); 31 | let y = u128::from(y); 32 | let denominator = u128::from(denominator); 33 | let result = u64::try_from(x.safe_mul(y)?.safe_div(denominator)?) 34 | .map_err(|_| LBError::TypeCastFailed)?; 35 | Ok(result) 36 | } 37 | 38 | #[inline] 39 | pub fn safe_mul_div_cast_from_u256_to_u64(x: u64, y: U256, denominator: U256) -> Result { 40 | let x = U256::from(x); 41 | // let denominator = U256::from(denominator); 42 | let result = u64::try_from(x.safe_mul(y)?.safe_div(denominator)?) 43 | .map_err(|_| LBError::TypeCastFailed)?; 44 | Ok(result) 45 | } 46 | 47 | #[inline] 48 | pub fn safe_mul_shr_cast( 49 | x: u128, 50 | y: u128, 51 | offset: u8, 52 | rounding: Rounding, 53 | ) -> Result { 54 | T::from_u128(mul_shr(x, y, offset, rounding).ok_or_else(|| LBError::MathOverflow)?) 55 | .ok_or_else(|| LBError::TypeCastFailed.into()) 56 | } 57 | 58 | #[inline] 59 | pub fn safe_shl_div_cast( 60 | x: u128, 61 | y: u128, 62 | offset: u8, 63 | rounding: Rounding, 64 | ) -> Result { 65 | T::from_u128(shl_div(x, y, offset, rounding).ok_or_else(|| LBError::MathOverflow)?) 66 | .ok_or_else(|| LBError::TypeCastFailed.into()) 67 | } 68 | 69 | pub const fn one() -> Uint { 70 | let mut words = [0; LIMBS]; 71 | words[0] = 1; 72 | Uint::from_limbs(words) 73 | } 74 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/state/action_access.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use solana_program::pubkey::Pubkey; 3 | 4 | use super::lb_pair::{LbPair, PairStatus}; 5 | 6 | pub trait LbPairTypeActionAccess { 7 | fn validate_add_liquidity_access(&self, wallet: Pubkey) -> bool; 8 | fn validate_initialize_bin_array_access(&self, wallet: Pubkey) -> bool; 9 | fn validate_initialize_position_access(&self, wallet: Pubkey) -> bool; 10 | fn validate_swap_access(&self, sender: Pubkey) -> bool; 11 | } 12 | 13 | struct PermissionLbPairActionAccess { 14 | is_enabled: bool, 15 | activated: bool, 16 | pre_swap_activated: bool, 17 | whitelisted_wallet: Pubkey, 18 | pre_activation_swap_address: Pubkey, 19 | } 20 | 21 | impl PermissionLbPairActionAccess { 22 | pub fn new(lb_pair: &LbPair, current_slot: u64, pre_activation_swap_start_slot: u64) -> Self { 23 | Self { 24 | whitelisted_wallet: lb_pair.whitelisted_wallet, 25 | is_enabled: lb_pair.status == Into::::into(PairStatus::Enabled), 26 | activated: current_slot >= lb_pair.activation_slot, 27 | pre_activation_swap_address: lb_pair.pre_activation_swap_address, 28 | pre_swap_activated: current_slot >= pre_activation_swap_start_slot, 29 | } 30 | } 31 | } 32 | 33 | impl LbPairTypeActionAccess for PermissionLbPairActionAccess { 34 | fn validate_add_liquidity_access(&self, wallet: Pubkey) -> bool { 35 | // Pair disabled due to emergency mode. Nothing can be deposited. 36 | if !self.is_enabled { 37 | return false; 38 | } 39 | 40 | self.activated 41 | || !self.whitelisted_wallet.eq(&Pubkey::default()) 42 | && self.whitelisted_wallet.eq(&wallet) 43 | } 44 | 45 | fn validate_initialize_bin_array_access(&self, wallet: Pubkey) -> bool { 46 | self.validate_add_liquidity_access(wallet) 47 | } 48 | 49 | fn validate_initialize_position_access(&self, wallet: Pubkey) -> bool { 50 | self.validate_add_liquidity_access(wallet) 51 | } 52 | 53 | fn validate_swap_access(&self, sender: Pubkey) -> bool { 54 | let activated = if self.pre_activation_swap_address.eq(&sender) { 55 | self.pre_swap_activated 56 | } else { 57 | self.activated 58 | }; 59 | 60 | self.is_enabled && activated 61 | } 62 | } 63 | 64 | struct PermissionlessLbPairActionAccess { 65 | is_enabled: bool, 66 | } 67 | 68 | impl PermissionlessLbPairActionAccess { 69 | pub fn new(lb_pair: &LbPair) -> Self { 70 | Self { 71 | is_enabled: lb_pair.status == Into::::into(PairStatus::Enabled), 72 | } 73 | } 74 | } 75 | 76 | impl LbPairTypeActionAccess for PermissionlessLbPairActionAccess { 77 | fn validate_add_liquidity_access(&self, _wallet: Pubkey) -> bool { 78 | self.is_enabled 79 | } 80 | 81 | fn validate_initialize_bin_array_access(&self, _wallet: Pubkey) -> bool { 82 | self.is_enabled 83 | } 84 | 85 | fn validate_initialize_position_access(&self, _wallet: Pubkey) -> bool { 86 | self.is_enabled 87 | } 88 | 89 | fn validate_swap_access(&self, _sender: Pubkey) -> bool { 90 | self.is_enabled 91 | } 92 | } 93 | 94 | pub fn get_lb_pair_type_access_validator<'a>( 95 | lb_pair: &'a LbPair, 96 | current_slot: u64, 97 | ) -> Result> { 98 | if lb_pair.is_permission_pair()? { 99 | let pre_activation_start_slot = lb_pair.get_pre_activation_start_slot(); 100 | let permission_pair_access_validator = 101 | PermissionLbPairActionAccess::new(lb_pair, current_slot, pre_activation_start_slot); 102 | 103 | Ok(Box::new(permission_pair_access_validator)) 104 | } else { 105 | let permissionless_pair_access_validator = PermissionlessLbPairActionAccess::new(lb_pair); 106 | Ok(Box::new(permissionless_pair_access_validator)) 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod action_access; 2 | pub mod bin; 3 | pub mod bin_array_bitmap_extension; 4 | pub mod lb_pair; 5 | pub mod oracle; 6 | pub mod parameters; 7 | pub mod position; 8 | pub mod preset_parameters; 9 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/state/oracle.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "localnet"))] 2 | use crate::constants::SAMPLE_LIFETIME; 3 | use crate::errors::LBError; 4 | use crate::{constants::DEFAULT_OBSERVATION_LENGTH, math::safe_math::SafeMath}; 5 | use anchor_lang::prelude::*; 6 | use std::cell::RefMut; 7 | 8 | #[cfg(not(feature = "localnet"))] 9 | fn get_sample_lifetime() -> i64 { 10 | SAMPLE_LIFETIME as i64 11 | } 12 | 13 | #[cfg(feature = "localnet")] 14 | fn get_sample_lifetime() -> i64 { 15 | 5 16 | } 17 | 18 | /// Extension trait for loading dynamic-sized data in a zero-copy oracle account. 19 | pub trait OracleContentLoader<'info> { 20 | fn load_content_mut<'a>(&'a self) -> Result>; 21 | fn load_content_init<'a>(&'a self) -> Result>; 22 | fn load_content<'a>(&'a self) -> Result>; 23 | } 24 | 25 | #[zero_copy] 26 | #[derive(Default, Debug, PartialEq, Eq)] 27 | pub struct Observation { 28 | /// Cumulative active bin ID 29 | pub cumulative_active_bin_id: i128, 30 | /// Observation sample created timestamp 31 | pub created_at: i64, 32 | /// Observation sample last updated timestamp 33 | pub last_updated_at: i64, 34 | } 35 | 36 | impl Observation { 37 | pub fn initialized(&self) -> bool { 38 | self.created_at > 0 && self.last_updated_at > 0 39 | } 40 | 41 | pub fn reset(&mut self) { 42 | self.cumulative_active_bin_id = 0; 43 | self.created_at = 0; 44 | self.last_updated_at = 0; 45 | } 46 | 47 | /// Calculate cumulative_active_bin_id += active_id * delta_seconds 48 | pub fn accumulate_active_bin_id(&self, active_id: i32, current_timestamp: i64) -> Result { 49 | if self.initialized() { 50 | let delta = current_timestamp.safe_sub(self.last_updated_at)?; 51 | let cumulative_active_bin_id = Into::::into(active_id).safe_mul(delta.into())?; 52 | 53 | Ok(self 54 | .cumulative_active_bin_id 55 | .safe_add(cumulative_active_bin_id)?) 56 | } else { 57 | Ok(active_id.into()) 58 | } 59 | } 60 | 61 | /// Calculate the timestamp for the next observation sampling 62 | pub fn compute_next_sampling_timestamp(&self) -> Option { 63 | if self.initialized() { 64 | self.created_at.checked_add(get_sample_lifetime()) 65 | } else { 66 | None 67 | } 68 | } 69 | 70 | /// Update the observation sample 71 | pub fn update(&mut self, cumulative_active_bin_id: i128, current_timestamp: i64) { 72 | self.cumulative_active_bin_id = cumulative_active_bin_id; 73 | self.last_updated_at = current_timestamp; 74 | 75 | if !self.initialized() { 76 | self.created_at = current_timestamp; 77 | } 78 | } 79 | } 80 | 81 | #[account(zero_copy)] 82 | #[derive(Default, Debug)] 83 | pub struct Oracle { 84 | /// Index of latest observation slot 85 | pub idx: u64, 86 | /// Size of active sample. Active sample is initialized observation. 87 | pub active_size: u64, 88 | /// Number of observations 89 | pub length: u64, 90 | } 91 | 92 | impl Oracle { 93 | pub fn init(&mut self) { 94 | self.length = DEFAULT_OBSERVATION_LENGTH; 95 | } 96 | 97 | pub fn increase_length(&mut self, length_to_increase: u64) -> Result<()> { 98 | self.length = self.length.safe_add(length_to_increase)?; 99 | Ok(()) 100 | } 101 | 102 | pub fn space(observation_length: u64) -> usize { 103 | 8 + std::mem::size_of::() 104 | + observation_length as usize * std::mem::size_of::() 105 | } 106 | 107 | pub fn new_space( 108 | length_to_add: u64, 109 | account_loader: &AccountLoader<'_, Oracle>, 110 | ) -> Result { 111 | let oracle = account_loader.load()?; 112 | Ok(Oracle::space(oracle.length + length_to_add)) 113 | } 114 | 115 | pub fn metadata_len() -> usize { 116 | 8 + std::mem::size_of::() 117 | } 118 | } 119 | 120 | /// An oracle struct loaded with dynamic sized data type 121 | #[derive(Debug)] 122 | pub struct DynamicOracle<'a> { 123 | pub metadata: RefMut<'a, Oracle>, 124 | pub observations: RefMut<'a, [Observation]>, 125 | } 126 | 127 | impl<'a> DynamicOracle<'a> { 128 | pub fn new( 129 | metadata: RefMut<'a, Oracle>, 130 | observations: RefMut<'a, [Observation]>, 131 | ) -> DynamicOracle<'a> { 132 | Self { 133 | observations, 134 | metadata, 135 | } 136 | } 137 | 138 | /// Get wrapping next index 139 | fn next_idx(idx: usize, bound: usize) -> Option { 140 | idx.checked_add(1)?.checked_rem(bound) 141 | } 142 | 143 | /// Return indication whether the oracle have any observation samples 144 | fn is_initial_sampling(metadata: &Oracle) -> bool { 145 | metadata.active_size == 0 146 | } 147 | 148 | /// Return the latest observation sample 149 | pub fn get_latest_sample_mut<'dyo>(&'dyo mut self) -> Option<&'dyo mut Observation> { 150 | if Self::is_initial_sampling(&self.metadata) { 151 | return None; 152 | } 153 | Some(&mut self.observations[self.metadata.idx as usize]) 154 | } 155 | 156 | pub fn get_latest_sample<'dyo>(&'dyo self) -> Option<&'dyo Observation> { 157 | if Self::is_initial_sampling(&self.metadata) { 158 | return None; 159 | } 160 | Some(&self.observations[self.metadata.idx as usize]) 161 | } 162 | 163 | /// Return the earliest observation sample 164 | pub fn get_earliest_sample<'dyo>(&'dyo self) -> Option<&'dyo Observation> { 165 | if Self::is_initial_sampling(&self.metadata) { 166 | return None; 167 | } 168 | let next_idx = Self::next_idx( 169 | self.metadata.idx as usize, 170 | self.metadata.active_size as usize, 171 | )?; 172 | Some(&self.observations[next_idx]) 173 | } 174 | 175 | /// Get next observation and reset to empty value 176 | fn next_reset<'dyo>(&'dyo mut self) -> Option<&'dyo mut Observation> { 177 | let next_idx = Self::next_idx(self.metadata.idx as usize, self.metadata.length as usize)?; 178 | self.metadata.idx = next_idx as u64; 179 | 180 | let next_sample = &mut self.observations[next_idx]; 181 | 182 | if !next_sample.initialized() { 183 | self.metadata.active_size = std::cmp::min( 184 | self.metadata.active_size.checked_add(1)?, 185 | self.metadata.length, 186 | ); 187 | } 188 | 189 | next_sample.reset(); 190 | Some(next_sample) 191 | } 192 | 193 | /// Update existing observation sample / create a new observation sample based on sample lifetime expiration 194 | pub fn update(&mut self, active_id: i32, current_timestamp: i64) -> Result<()> { 195 | if Self::is_initial_sampling(&self.metadata) { 196 | self.metadata.active_size += 1; 197 | } 198 | 199 | let mut latest_sample = self 200 | .get_latest_sample_mut() 201 | .ok_or_else(|| LBError::InsufficientSample)?; // Unreachable ! 202 | 203 | let cumulative_active_bin_id = 204 | latest_sample.accumulate_active_bin_id(active_id, current_timestamp)?; 205 | 206 | if let Some(next_sampling_timestamp) = latest_sample.compute_next_sampling_timestamp() { 207 | if current_timestamp >= next_sampling_timestamp { 208 | latest_sample = self.next_reset().ok_or_else(|| LBError::MathOverflow)?; 209 | } 210 | } 211 | latest_sample.update(cumulative_active_bin_id, current_timestamp); 212 | 213 | Ok(()) 214 | } 215 | } 216 | 217 | fn oracle_account_split<'a, 'info>( 218 | oracle_al: &'a AccountLoader<'info, Oracle>, 219 | ) -> Result> { 220 | let data = oracle_al.as_ref().try_borrow_mut_data()?; 221 | 222 | let (oracle_metadata, observations) = RefMut::map_split(data, |data| { 223 | let (oracle_bytes, observations_bytes) = data.split_at_mut(Oracle::metadata_len()); 224 | let oracle = bytemuck::from_bytes_mut::(&mut oracle_bytes[8..]); 225 | let observations = bytemuck::cast_slice_mut::(observations_bytes); 226 | (oracle, observations) 227 | }); 228 | 229 | Ok(DynamicOracle::new(oracle_metadata, observations)) 230 | } 231 | 232 | impl<'info> OracleContentLoader<'info> for AccountLoader<'info, Oracle> { 233 | fn load_content_mut<'a>(&'a self) -> Result> { 234 | { 235 | // Re-use anchor internal validation such as discriminator check 236 | self.load_mut()?; 237 | } 238 | oracle_account_split(&self) 239 | } 240 | 241 | fn load_content_init<'a>(&'a self) -> Result> { 242 | { 243 | // Re-use anchor internal validation and initialization such as insert of discriminator for new zero copy account 244 | self.load_init()?; 245 | } 246 | oracle_account_split(&self) 247 | } 248 | 249 | fn load_content<'a>(&'a self) -> Result> { 250 | { 251 | // Re-use anchor internal validation and initialization such as insert of discriminator for new zero copy account 252 | self.load()?; 253 | } 254 | oracle_account_split(&self) 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/state/parameters.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::{BASIS_POINT_MAX, MAX_BASE_FACTOR_STEP, MAX_PROTOCOL_SHARE}; 2 | use crate::instructions::update_fee_parameters::FeeParameter; 3 | use crate::{errors::LBError, math::safe_math::SafeMath}; 4 | use anchor_lang::prelude::*; 5 | 6 | #[zero_copy] 7 | #[derive(InitSpace, Debug)] 8 | /// Parameter that set by the protocol 9 | pub struct StaticParameters { 10 | /// Used for base fee calculation. base_fee_rate = base_factor * bin_step 11 | pub base_factor: u16, 12 | /// Filter period determine high frequency trading time window. 13 | pub filter_period: u16, 14 | /// Decay period determine when the volatile fee start decay / decrease. 15 | pub decay_period: u16, 16 | /// Reduction factor controls the volatile fee rate decrement rate. 17 | pub reduction_factor: u16, 18 | /// Used to scale the variable fee component depending on the dynamic of the market 19 | pub variable_fee_control: u32, 20 | /// Maximum number of bin crossed can be accumulated. Used to cap volatile fee rate. 21 | pub max_volatility_accumulator: u32, 22 | /// Min bin id supported by the pool based on the configured bin step. 23 | pub min_bin_id: i32, 24 | /// Max bin id supported by the pool based on the configured bin step. 25 | pub max_bin_id: i32, 26 | /// Portion of swap fees retained by the protocol by controlling protocol_share parameter. protocol_swap_fee = protocol_share * total_swap_fee 27 | pub protocol_share: u16, 28 | /// Padding for bytemuck safe alignment 29 | pub _padding: [u8; 6], 30 | } 31 | 32 | impl StaticParameters { 33 | pub fn update(&mut self, parameter: &FeeParameter) -> Result<()> { 34 | let base_factor_delta = if parameter.base_factor > self.base_factor { 35 | parameter.base_factor.safe_sub(self.base_factor)? 36 | } else { 37 | self.base_factor.safe_sub(parameter.base_factor)? 38 | }; 39 | 40 | // Fee increment / decrement must <= 100% of the current fee rate 41 | require!( 42 | base_factor_delta <= self.base_factor, 43 | LBError::ExcessiveFeeUpdate 44 | ); 45 | 46 | // Fee increment / decrement must <= 100 bps, 1% 47 | require!( 48 | base_factor_delta <= MAX_BASE_FACTOR_STEP, 49 | LBError::ExcessiveFeeUpdate 50 | ); 51 | 52 | // During quote it already capped. Extra safety check. 53 | require!( 54 | parameter.protocol_share <= MAX_PROTOCOL_SHARE, 55 | LBError::ExcessiveFeeUpdate 56 | ); 57 | 58 | self.protocol_share = parameter.protocol_share; 59 | self.base_factor = parameter.base_factor; 60 | 61 | Ok(()) 62 | } 63 | 64 | #[inline(always)] 65 | #[cfg(not(feature = "localnet"))] 66 | pub fn get_filter_period(&self) -> u16 { 67 | self.filter_period 68 | } 69 | 70 | #[inline(always)] 71 | #[cfg(feature = "localnet")] 72 | pub fn get_filter_period(&self) -> u16 { 73 | 5 74 | } 75 | 76 | #[inline(always)] 77 | #[cfg(not(feature = "localnet"))] 78 | pub fn get_decay_period(&self) -> u16 { 79 | self.decay_period 80 | } 81 | 82 | #[inline(always)] 83 | #[cfg(feature = "localnet")] 84 | pub fn get_decay_period(&self) -> u16 { 85 | 10 86 | } 87 | } 88 | 89 | impl Default for StaticParameters { 90 | /// These value are references from Trader Joe 91 | fn default() -> Self { 92 | Self { 93 | base_factor: 10_000, 94 | filter_period: 30, 95 | decay_period: 600, 96 | reduction_factor: 500, 97 | variable_fee_control: 40_000, 98 | protocol_share: 1_000, 99 | max_volatility_accumulator: 350_000, // Capped at 35 bin crossed. 350_000 / 10_000 (bps unit) = 35 delta bin 100 | _padding: [0u8; 6], 101 | max_bin_id: i32::MAX, 102 | min_bin_id: i32::MIN, 103 | } 104 | } 105 | } 106 | 107 | #[zero_copy] 108 | #[derive(InitSpace, Default, Debug)] 109 | /// Parameters that changes based on dynamic of the market 110 | pub struct VariableParameters { 111 | /// Volatility accumulator measure the number of bin crossed since reference bin ID. Normally (without filter period taken into consideration), reference bin ID is the active bin of last swap. 112 | /// It affects the variable fee rate 113 | pub volatility_accumulator: u32, 114 | /// Volatility reference is decayed volatility accumulator. It is always <= volatility_accumulator 115 | pub volatility_reference: u32, 116 | /// Active bin id of last swap. 117 | pub index_reference: i32, 118 | /// Padding for bytemuck safe alignment 119 | pub _padding: [u8; 4], 120 | /// Last timestamp the variable parameters was updated 121 | pub last_update_timestamp: i64, 122 | /// Padding for bytemuck safe alignment 123 | pub _padding_1: [u8; 8], 124 | } 125 | 126 | impl VariableParameters { 127 | /// volatility_accumulator = min(volatility_reference + num_of_bin_crossed, max_volatility_accumulator) 128 | pub fn update_volatility_accumulator( 129 | &mut self, 130 | active_id: i32, 131 | static_params: &StaticParameters, 132 | ) -> Result<()> { 133 | // Upscale to prevent overflow caused by swapping from left most bin to right most bin. 134 | let delta_id = i64::from(self.index_reference) 135 | .safe_sub(active_id.into())? 136 | .unsigned_abs(); 137 | 138 | let volatility_accumulator = u64::from(self.volatility_reference) 139 | .safe_add(delta_id.safe_mul(BASIS_POINT_MAX as u64)?)?; 140 | 141 | self.volatility_accumulator = std::cmp::min( 142 | volatility_accumulator, 143 | static_params.max_volatility_accumulator.into(), 144 | ) 145 | .try_into() 146 | .map_err(|_| LBError::TypeCastFailed)?; 147 | 148 | Ok(()) 149 | } 150 | 151 | /// Update id, and volatility reference 152 | pub fn update_references( 153 | &mut self, 154 | active_id: i32, 155 | current_timestamp: i64, 156 | static_params: &StaticParameters, 157 | ) -> Result<()> { 158 | let elapsed = current_timestamp.safe_sub(self.last_update_timestamp)?; 159 | 160 | // Not high frequency trade 161 | if elapsed >= static_params.get_filter_period() as i64 { 162 | // Update active id of last transaction 163 | self.index_reference = active_id; 164 | // filter period < t < decay_period. Decay time window. 165 | if elapsed < static_params.get_decay_period() as i64 { 166 | let volatility_reference = self 167 | .volatility_accumulator 168 | .safe_mul(static_params.reduction_factor as u32)? 169 | .safe_div(BASIS_POINT_MAX as u32)?; 170 | 171 | self.volatility_reference = volatility_reference; 172 | } 173 | // Out of decay time window 174 | else { 175 | self.volatility_reference = 0; 176 | } 177 | } 178 | 179 | // self.last_update_timestamp = current_timestamp; 180 | 181 | Ok(()) 182 | } 183 | 184 | pub fn update_volatility_parameter( 185 | &mut self, 186 | active_id: i32, 187 | current_timestamp: i64, 188 | static_params: &StaticParameters, 189 | ) -> Result<()> { 190 | self.update_references(active_id, current_timestamp, static_params)?; 191 | self.update_volatility_accumulator(active_id, static_params) 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/state/preset_parameters.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::{BASIS_POINT_MAX, MAX_PROTOCOL_SHARE, U24_MAX}; 2 | use crate::errors::LBError; 3 | use crate::math::price_math::get_price_from_id; 4 | use anchor_lang::prelude::*; 5 | 6 | use super::parameters::StaticParameters; 7 | 8 | #[account] 9 | #[derive(InitSpace, Debug)] 10 | pub struct PresetParameter { 11 | /// Bin step. Represent the price increment / decrement. 12 | pub bin_step: u16, 13 | /// Used for base fee calculation. base_fee_rate = base_factor * bin_step 14 | pub base_factor: u16, 15 | /// Filter period determine high frequency trading time window. 16 | pub filter_period: u16, 17 | /// Decay period determine when the volatile fee start decay / decrease. 18 | pub decay_period: u16, 19 | /// Reduction factor controls the volatile fee rate decrement rate. 20 | pub reduction_factor: u16, 21 | /// Used to scale the variable fee component depending on the dynamic of the market 22 | pub variable_fee_control: u32, 23 | /// Maximum number of bin crossed can be accumulated. Used to cap volatile fee rate. 24 | pub max_volatility_accumulator: u32, 25 | /// Min bin id supported by the pool based on the configured bin step. 26 | pub min_bin_id: i32, 27 | /// Max bin id supported by the pool based on the configured bin step. 28 | pub max_bin_id: i32, 29 | /// Portion of swap fees retained by the protocol by controlling protocol_share parameter. protocol_swap_fee = protocol_share * total_swap_fee 30 | pub protocol_share: u16, 31 | } 32 | 33 | impl PresetParameter { 34 | pub fn init( 35 | &mut self, 36 | bin_step: u16, 37 | base_factor: u16, 38 | filter_period: u16, 39 | decay_period: u16, 40 | reduction_factor: u16, 41 | variable_fee_control: u32, 42 | max_volatility_accumulator: u32, 43 | min_bin_id: i32, 44 | max_bin_id: i32, 45 | protocol_share: u16, 46 | ) { 47 | self.bin_step = bin_step; 48 | self.base_factor = base_factor; 49 | self.filter_period = filter_period; 50 | self.decay_period = decay_period; 51 | self.reduction_factor = reduction_factor; 52 | self.variable_fee_control = variable_fee_control; 53 | self.max_volatility_accumulator = max_volatility_accumulator; 54 | self.min_bin_id = min_bin_id; 55 | self.max_bin_id = max_bin_id; 56 | self.protocol_share = protocol_share; 57 | } 58 | 59 | pub fn update( 60 | &mut self, 61 | base_factor: u16, 62 | filter_period: u16, 63 | decay_period: u16, 64 | reduction_factor: u16, 65 | variable_fee_control: u32, 66 | max_volatility_accumulator: u32, 67 | protocol_share: u16, 68 | ) { 69 | self.init( 70 | self.bin_step, 71 | base_factor, 72 | filter_period, 73 | decay_period, 74 | reduction_factor, 75 | variable_fee_control, 76 | max_volatility_accumulator, 77 | self.min_bin_id, 78 | self.max_bin_id, 79 | protocol_share, 80 | ); 81 | } 82 | 83 | pub fn validate(&self) -> Result<()> { 84 | require!( 85 | self.bin_step <= BASIS_POINT_MAX as u16, 86 | LBError::InvalidInput 87 | ); 88 | 89 | // we don't rug 90 | require!( 91 | self.protocol_share <= MAX_PROTOCOL_SHARE, 92 | LBError::InvalidInput 93 | ); 94 | 95 | // filter period < t < decay period 96 | require!( 97 | self.filter_period < self.decay_period, 98 | LBError::InvalidInput 99 | ); 100 | 101 | // reduction factor decide the decay rate of variable fee, max reduction_factor is BASIS_POINT_MAX = 100% reduction 102 | require!( 103 | self.reduction_factor <= BASIS_POINT_MAX as u16, 104 | LBError::InvalidInput 105 | ); 106 | 107 | // prevent program overflow 108 | require!(self.variable_fee_control <= U24_MAX, LBError::InvalidInput); 109 | require!( 110 | self.max_volatility_accumulator <= U24_MAX, 111 | LBError::InvalidInput 112 | ); 113 | 114 | let max_price = get_price_from_id(self.max_bin_id, self.bin_step); 115 | let min_price = get_price_from_id(self.min_bin_id, self.bin_step); 116 | 117 | require!(max_price.is_ok(), LBError::InvalidInput); 118 | require!(min_price.is_ok(), LBError::InvalidInput); 119 | 120 | // Bin is not swap-able when the price is u128::MAX, and 1. Make sure the min and max price bound is 2**127 - 1, 2 121 | if let Ok(max_price) = max_price { 122 | require!( 123 | max_price == 170141183460469231731687303715884105727, 124 | LBError::InvalidInput 125 | ); 126 | } 127 | 128 | if let Ok(min_price) = min_price { 129 | require!(min_price == 2, LBError::InvalidInput); 130 | } 131 | 132 | Ok(()) 133 | } 134 | 135 | pub fn to_static_parameters(&self) -> StaticParameters { 136 | StaticParameters { 137 | base_factor: self.base_factor, 138 | decay_period: self.decay_period, 139 | filter_period: self.filter_period, 140 | max_bin_id: self.max_bin_id, 141 | min_bin_id: self.min_bin_id, 142 | variable_fee_control: self.variable_fee_control, 143 | reduction_factor: self.reduction_factor, 144 | protocol_share: self.protocol_share, 145 | max_volatility_accumulator: self.max_volatility_accumulator, 146 | _padding: [0u8; 6], 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pda; 2 | pub mod seeds; 3 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/utils/pda.rs: -------------------------------------------------------------------------------- 1 | use super::seeds::{self, BIN_ARRAY, BIN_ARRAY_BITMAP_SEED, ORACLE, PRESET_PARAMETER}; 2 | use anchor_lang::prelude::Pubkey; 3 | use num_traits::ToBytes; 4 | use std::{cmp::max, cmp::min}; 5 | 6 | pub fn derive_lb_pair_pda2( 7 | token_x_mint: Pubkey, 8 | token_y_mint: Pubkey, 9 | bin_step: u16, 10 | base_factor: u16, 11 | ) -> (Pubkey, u8) { 12 | Pubkey::find_program_address( 13 | &[ 14 | min(token_x_mint, token_y_mint).as_ref(), 15 | max(token_x_mint, token_y_mint).as_ref(), 16 | &bin_step.to_le_bytes(), 17 | &base_factor.to_le_bytes(), 18 | ], 19 | &crate::ID, 20 | ) 21 | } 22 | 23 | pub fn derive_permission_lb_pair_pda( 24 | base: Pubkey, 25 | token_x_mint: Pubkey, 26 | token_y_mint: Pubkey, 27 | bin_step: u16, 28 | ) -> (Pubkey, u8) { 29 | Pubkey::find_program_address( 30 | &[ 31 | base.as_ref(), 32 | min(token_x_mint, token_y_mint).as_ref(), 33 | max(token_x_mint, token_y_mint).as_ref(), 34 | &bin_step.to_le_bytes(), 35 | ], 36 | &crate::ID, 37 | ) 38 | } 39 | 40 | #[deprecated] 41 | pub fn derive_lb_pair_pda( 42 | token_x_mint: Pubkey, 43 | token_y_mint: Pubkey, 44 | bin_step: u16, 45 | ) -> (Pubkey, u8) { 46 | Pubkey::find_program_address( 47 | &[ 48 | min(token_x_mint, token_y_mint).as_ref(), 49 | max(token_x_mint, token_y_mint).as_ref(), 50 | &bin_step.to_le_bytes(), 51 | ], 52 | &crate::ID, 53 | ) 54 | } 55 | 56 | pub fn derive_position_pda( 57 | lb_pair: Pubkey, 58 | base: Pubkey, 59 | lower_bin_id: i32, 60 | width: i32, 61 | ) -> (Pubkey, u8) { 62 | Pubkey::find_program_address( 63 | &[ 64 | seeds::POSITION.as_ref(), 65 | lb_pair.as_ref(), 66 | base.as_ref(), 67 | lower_bin_id.to_le_bytes().as_ref(), 68 | width.to_le_bytes().as_ref(), 69 | ], 70 | &crate::ID, 71 | ) 72 | } 73 | 74 | pub fn derive_oracle_pda(lb_pair: Pubkey) -> (Pubkey, u8) { 75 | Pubkey::find_program_address(&[ORACLE, lb_pair.as_ref()], &crate::ID) 76 | } 77 | 78 | pub fn derive_bin_array_pda(lb_pair: Pubkey, bin_array_index: i64) -> (Pubkey, u8) { 79 | Pubkey::find_program_address( 80 | &[BIN_ARRAY, lb_pair.as_ref(), &bin_array_index.to_le_bytes()], 81 | &crate::ID, 82 | ) 83 | } 84 | 85 | pub fn derive_bin_array_bitmap_extension(lb_pair: Pubkey) -> (Pubkey, u8) { 86 | Pubkey::find_program_address(&[BIN_ARRAY_BITMAP_SEED, lb_pair.as_ref()], &crate::ID) 87 | } 88 | 89 | pub fn derive_reserve_pda(token_mint: Pubkey, lb_pair: Pubkey) -> (Pubkey, u8) { 90 | Pubkey::find_program_address(&[lb_pair.as_ref(), token_mint.as_ref()], &crate::ID) 91 | } 92 | 93 | pub fn derive_reward_vault_pda(lb_pair: Pubkey, reward_index: u64) -> (Pubkey, u8) { 94 | Pubkey::find_program_address( 95 | &[lb_pair.as_ref(), reward_index.to_le_bytes().as_ref()], 96 | &crate::ID, 97 | ) 98 | } 99 | 100 | pub fn derive_event_authority_pda() -> (Pubkey, u8) { 101 | Pubkey::find_program_address(&[b"__event_authority"], &crate::ID) 102 | } 103 | 104 | #[deprecated] 105 | pub fn derive_preset_parameter_pda(bin_step: u16) -> (Pubkey, u8) { 106 | Pubkey::find_program_address(&[PRESET_PARAMETER, &bin_step.to_le_bytes()], &crate::ID) 107 | } 108 | 109 | pub fn derive_preset_parameter_pda2(bin_step: u16, base_factor: u16) -> (Pubkey, u8) { 110 | Pubkey::find_program_address( 111 | &[ 112 | PRESET_PARAMETER, 113 | &bin_step.to_le_bytes(), 114 | &base_factor.to_le_bytes(), 115 | ], 116 | &crate::ID, 117 | ) 118 | } 119 | -------------------------------------------------------------------------------- /programs/lb_clmm/src/utils/seeds.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[constant] 4 | pub const BIN_ARRAY: &[u8] = b"bin_array"; 5 | 6 | #[constant] 7 | pub const ORACLE: &[u8] = b"oracle"; 8 | 9 | #[constant] 10 | pub const BIN_ARRAY_BITMAP_SEED: &[u8] = b"bitmap"; 11 | 12 | #[constant] 13 | pub const PRESET_PARAMETER: &[u8] = b"preset_parameter"; 14 | 15 | #[constant] 16 | pub const POSITION: &[u8] = b"position"; 17 | -------------------------------------------------------------------------------- /programs/raydium_amm/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Note: This crate must be built using do.sh 2 | 3 | [package] 4 | name = "raydium_amm" 5 | version = "0.3.0" 6 | description = "Raydium AMM" 7 | authors = ["Raydium Maintainers "] 8 | repository = "https://github.com/raydium-io/raydium-amm" 9 | license = "Apache-2.0" 10 | edition = "2018" 11 | 12 | [features] 13 | no-entrypoint = ["serum_dex/no-entrypoint"] 14 | program = ["serum_dex/program", "serum_dex/no-entrypoint"] 15 | default = ["program"] 16 | client = ["serum_dex/client"] 17 | test = [] 18 | devnet = [] 19 | localnet = [] 20 | 21 | [dependencies] 22 | solana-program = "1.18.10" 23 | spl-token = { version = "4.0.0", features = ["no-entrypoint"] } 24 | spl-associated-token-account = { version = "2.2.0", features = ["no-entrypoint"]} 25 | serum_dex = { version = "0.5.10", git = "https://github.com/raydium-io/openbook-dex", features=["no-entrypoint", "program"] } 26 | serde_json = { version = "1.0.56" } 27 | serde = { version = "1.0", features = ["derive"] } 28 | bincode = { version = "1.3.3" } 29 | base64 = "0.13.0" 30 | arrayref = "0.3.6" 31 | arrform = { git = "https://github.com/raydium-io/arrform" } 32 | num-derive = "0.3" 33 | num-traits = "0.2.12" 34 | bytemuck = { version = "1.4.0" } 35 | safe-transmute = "0.11.0" 36 | thiserror = "1.0.20" 37 | uint = "0.9.5" 38 | 39 | [dev-dependencies] 40 | bumpalo = { version = "3.4.0", features = ["collections"] } 41 | 42 | [lib] 43 | name = "raydium_amm" 44 | crate-type = ["cdylib", "lib"] 45 | 46 | # [profile.release] 47 | # lto = "fat" 48 | # codegen-units = 1 49 | # panic = "abort" 50 | # overflow-checks = true 51 | # [profile.release.build-override] 52 | # opt-level = 3 53 | # incremental = false 54 | # codegen-units = 1 55 | -------------------------------------------------------------------------------- /programs/raydium_amm/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] -------------------------------------------------------------------------------- /programs/raydium_amm/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | //! Program entrypoint definitions 2 | 3 | #![cfg(all(target_arch = "bpf", not(feature = "no-entrypoint")))] 4 | 5 | use crate::{error::AmmError, processor::Processor}; 6 | use solana_program::{ 7 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, 8 | program_error::PrintProgramError, pubkey::Pubkey, 9 | }; 10 | 11 | entrypoint!(process_instruction); 12 | fn process_instruction<'a>( 13 | program_id: &Pubkey, 14 | accounts: &'a [AccountInfo<'a>], 15 | instruction_data: &[u8], 16 | ) -> ProgramResult { 17 | if let Err(error) = Processor::process(program_id, accounts, instruction_data) { 18 | // catch the error so we can print it 19 | error.print::(); 20 | return Err(error); 21 | } 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /programs/raydium_amm/src/lib.rs: -------------------------------------------------------------------------------- 1 | // #![deny(missing_docs)] 2 | 3 | //! An Uniswap-like program for the Solana blockchain. 4 | #[macro_use] 5 | pub mod log; 6 | 7 | mod entrypoint; 8 | pub mod error; 9 | pub mod instruction; 10 | pub mod invokers; 11 | pub mod math; 12 | pub mod processor; 13 | pub mod state; 14 | 15 | // Export current solana-sdk types for downstream users who may also be building with a different solana-sdk version 16 | pub use solana_program; 17 | 18 | // solana_program::declare_id!(""); 19 | -------------------------------------------------------------------------------- /programs/raydium_amm/src/log.rs: -------------------------------------------------------------------------------- 1 | use arrform::{arrform, ArrForm}; 2 | use serde::{Deserialize, Serialize}; 3 | use solana_program::{ 4 | msg, 5 | // entrypoint::ProgramResult, 6 | pubkey::Pubkey, 7 | }; 8 | 9 | pub const LOG_SIZE: usize = 256; 10 | 11 | #[macro_export] 12 | macro_rules! check_assert_eq { 13 | ($input:expr, $expected:expr, $msg:expr, $err:expr) => { 14 | if $input != $expected { 15 | log_keys_mismatch(concat!($msg, " mismatch:"), $input, $expected); 16 | return Err($err.into()); 17 | } 18 | }; 19 | } 20 | 21 | pub fn log_keys_mismatch(msg: &str, input: Pubkey, expected: Pubkey) { 22 | msg!(arrform!( 23 | LOG_SIZE, 24 | "ray_log: {} input:{}, expected:{}", 25 | msg, 26 | input, 27 | expected 28 | ) 29 | .as_str()); 30 | } 31 | 32 | /// LogType enum 33 | #[derive(Debug)] 34 | pub enum LogType { 35 | Init, 36 | Deposit, 37 | Withdraw, 38 | SwapBaseIn, 39 | SwapBaseOut, 40 | } 41 | 42 | impl LogType { 43 | pub fn from_u8(log_type: u8) -> Self { 44 | match log_type { 45 | 0 => LogType::Init, 46 | 1 => LogType::Deposit, 47 | 2 => LogType::Withdraw, 48 | 3 => LogType::SwapBaseIn, 49 | 4 => LogType::SwapBaseOut, 50 | _ => unreachable!(), 51 | } 52 | } 53 | 54 | pub fn into_u8(&self) -> u8 { 55 | match self { 56 | LogType::Init => 0u8, 57 | LogType::Deposit => 1u8, 58 | LogType::Withdraw => 2u8, 59 | LogType::SwapBaseIn => 3u8, 60 | LogType::SwapBaseOut => 4u8, 61 | } 62 | } 63 | } 64 | 65 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 66 | pub struct InitLog { 67 | pub log_type: u8, 68 | pub time: u64, 69 | pub pc_decimals: u8, 70 | pub coin_decimals: u8, 71 | pub pc_lot_size: u64, 72 | pub coin_lot_size: u64, 73 | pub pc_amount: u64, 74 | pub coin_amount: u64, 75 | pub market: Pubkey, 76 | } 77 | 78 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 79 | pub struct DepositLog { 80 | pub log_type: u8, 81 | // input 82 | pub max_coin: u64, 83 | pub max_pc: u64, 84 | pub base: u64, 85 | // pool info 86 | pub pool_coin: u64, 87 | pub pool_pc: u64, 88 | pub pool_lp: u64, 89 | pub calc_pnl_x: u128, 90 | pub calc_pnl_y: u128, 91 | // calc result 92 | pub deduct_coin: u64, 93 | pub deduct_pc: u64, 94 | pub mint_lp: u64, 95 | } 96 | 97 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 98 | pub struct WithdrawLog { 99 | pub log_type: u8, 100 | // input 101 | pub withdraw_lp: u64, 102 | // user info 103 | pub user_lp: u64, 104 | // pool info 105 | pub pool_coin: u64, 106 | pub pool_pc: u64, 107 | pub pool_lp: u64, 108 | pub calc_pnl_x: u128, 109 | pub calc_pnl_y: u128, 110 | // calc result 111 | pub out_coin: u64, 112 | pub out_pc: u64, 113 | } 114 | 115 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 116 | pub struct SwapBaseInLog { 117 | pub log_type: u8, 118 | // input 119 | pub amount_in: u64, 120 | pub minimum_out: u64, 121 | pub direction: u64, 122 | // user info 123 | pub user_source: u64, 124 | // pool info 125 | pub pool_coin: u64, 126 | pub pool_pc: u64, 127 | // calc result 128 | pub out_amount: u64, 129 | } 130 | 131 | #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 132 | pub struct SwapBaseOutLog { 133 | pub log_type: u8, 134 | // input 135 | pub max_in: u64, 136 | pub amount_out: u64, 137 | pub direction: u64, 138 | // user info 139 | pub user_source: u64, 140 | // pool info 141 | pub pool_coin: u64, 142 | pub pool_pc: u64, 143 | // calc result 144 | pub deduct_in: u64, 145 | } 146 | 147 | pub fn encode_ray_log(log: T) { 148 | // encode 149 | let bytes = bincode::serialize(&log).unwrap(); 150 | let mut out_buf = Vec::new(); 151 | out_buf.resize(bytes.len() * 4 / 3 + 4, 0); 152 | let bytes_written = base64::encode_config_slice(bytes, base64::STANDARD, &mut out_buf); 153 | out_buf.resize(bytes_written, 0); 154 | let msg_str = unsafe { std::str::from_utf8_unchecked(&out_buf) }; 155 | msg!(arrform!(LOG_SIZE, "ray_log: {}", msg_str).as_str()); 156 | } 157 | 158 | pub fn decode_ray_log(log: &str) { 159 | let bytes = base64::decode_config(log, base64::STANDARD).unwrap(); 160 | match LogType::from_u8(bytes[0]) { 161 | LogType::Init => { 162 | let log: InitLog = bincode::deserialize(&bytes).unwrap(); 163 | println!("{:?}", log); 164 | } 165 | LogType::Deposit => { 166 | let log: DepositLog = bincode::deserialize(&bytes).unwrap(); 167 | println!("{:?}", log); 168 | } 169 | LogType::Withdraw => { 170 | let log: WithdrawLog = bincode::deserialize(&bytes).unwrap(); 171 | println!("{:?}", log); 172 | } 173 | LogType::SwapBaseIn => { 174 | let log: SwapBaseInLog = bincode::deserialize(&bytes).unwrap(); 175 | println!("{:?}", log); 176 | } 177 | LogType::SwapBaseOut => { 178 | let log: SwapBaseOutLog = bincode::deserialize(&bytes).unwrap(); 179 | println!("{:?}", log); 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/arbitrage/calc_arb.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use log::{debug, info}; 3 | use crate::markets::types::{Dex, DexLabel, Market}; 4 | use crate::arbitrage::types::{TokenInArb, Route, SwapPath}; 5 | use crate::strategies::pools::get_fresh_pools; 6 | 7 | pub async fn get_markets_arb(get_fresh_pools_bool: bool, restrict_sol_usdc: bool, dexs: Vec, tokens: Vec) -> HashMap { 8 | 9 | let sol_addr = format!("So11111111111111111111111111111111111111112"); 10 | let usdc_addr = format!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 11 | let mut sol_usdc_count = 0; 12 | 13 | let mut markets_arb: HashMap = HashMap::new(); 14 | let token_addresses: HashSet = tokens.clone().into_iter().map(|token| token.address).collect(); 15 | 16 | for dex in dexs { 17 | for (pair, market) in dex.pairToMarkets { 18 | //The first token is the base token (SOL) 19 | for market_iter in market { 20 | if token_addresses.contains(&market_iter.tokenMintA) && token_addresses.contains(&market_iter.tokenMintB) { 21 | if restrict_sol_usdc { 22 | if (&market_iter.tokenMintA == &sol_addr || &market_iter.tokenMintA == &usdc_addr) && (&market_iter.tokenMintB == &sol_addr || &market_iter.tokenMintB == &usdc_addr) { 23 | if sol_usdc_count > 2 { 24 | continue; 25 | } else { 26 | let key = format!("{}", market_iter.clone().id); 27 | markets_arb.insert(key, market_iter.clone()); 28 | sol_usdc_count += 1; 29 | } 30 | } 31 | } 32 | let key = format!("{}", market_iter.clone().id); 33 | // key is the address of the pool 34 | markets_arb.insert(key, market_iter); 35 | } 36 | } 37 | } 38 | } 39 | if get_fresh_pools_bool { 40 | let new_markets_arb = get_fresh_pools(tokens).await; 41 | let mut count_new_pools = 0; 42 | 43 | for (key, market) in new_markets_arb { 44 | if token_addresses.contains(&market.tokenMintA) && token_addresses.contains(&market.tokenMintB) && !markets_arb.contains_key(&key) { 45 | if restrict_sol_usdc { 46 | if (&market.tokenMintA == &sol_addr || &market.tokenMintA == &usdc_addr) && (&market.tokenMintB == &sol_addr || &market.tokenMintB == &usdc_addr) { 47 | if sol_usdc_count > 2 { 48 | continue; 49 | } else { 50 | let key = format!("{}", market.clone().id); 51 | markets_arb.insert(key, market.clone()); 52 | sol_usdc_count += 1; 53 | } 54 | } 55 | } 56 | // key is the address of the pool 57 | markets_arb.insert(key, market); 58 | count_new_pools += 1; 59 | } 60 | } 61 | info!("👀 {} new markets founded !", count_new_pools); 62 | } 63 | 64 | return markets_arb; 65 | } 66 | 67 | pub fn calculate_arb(include_1hop: bool, include_2hop: bool, markets_arb: HashMap, tokens: Vec) -> (HashMap, Vec) { 68 | 69 | //Sort valuables markets: ex: Remove low liquidity markets 70 | let mut sorted_markets_arb: HashMap = HashMap::new(); 71 | let mut excluded_markets_arb: Vec = Vec::new(); 72 | 73 | // for (key, market) in markets_arb.clone() { 74 | // info!("Address: {}, DexLabel: {:?}, Liquidity: {:?}", market.id, market.dexLabel, market.liquidity); 75 | // } 76 | 77 | println!("⚠️⚠️ ORCA Pool not sorted"); 78 | println!("⚠️⚠️ RAYDIUM_CLMM Pool not sorted"); 79 | 80 | for (key, market) in markets_arb.clone() { 81 | match market.dexLabel { 82 | DexLabel::ORCA => { 83 | excluded_markets_arb.push(key); 84 | }, 85 | DexLabel::ORCA_WHIRLPOOLS => { 86 | if market.liquidity.unwrap() >= 2000000000 { // 2000$ with 6 decimals, not sure 87 | sorted_markets_arb.insert(key, market); 88 | } else { 89 | excluded_markets_arb.push(key); 90 | } 91 | }, 92 | DexLabel::RAYDIUM_CLMM => { 93 | excluded_markets_arb.push(key); 94 | }, 95 | DexLabel::RAYDIUM => { 96 | if market.liquidity.unwrap() >= 2000 { //If liquidity more than 2000$ 97 | sorted_markets_arb.insert(key, market); 98 | } else { 99 | excluded_markets_arb.push(key); 100 | } 101 | }, 102 | DexLabel::METEORA => { 103 | if market.liquidity.unwrap() >= 2000 { //If liquidity more than 2000$ 104 | sorted_markets_arb.insert(key, market); 105 | } else { 106 | excluded_markets_arb.push(key); 107 | } 108 | }, 109 | } 110 | } 111 | info!("👌 Included Markets: {}", sorted_markets_arb.len()); 112 | 113 | let mut counts: HashMap = HashMap::new(); 114 | counts.insert(DexLabel::ORCA, 0); 115 | counts.insert(DexLabel::ORCA_WHIRLPOOLS, 0); 116 | counts.insert(DexLabel::RAYDIUM, 0); 117 | counts.insert(DexLabel::RAYDIUM_CLMM, 0); 118 | counts.insert(DexLabel::METEORA, 0); 119 | 120 | for (key, market) in sorted_markets_arb.clone() { 121 | if let Some(count) = counts.get_mut(&market.dexLabel) { 122 | *count += 1; 123 | } 124 | } 125 | 126 | info!("Numbers of ORCA markets: {}", counts[&DexLabel::ORCA]); 127 | info!("Numbers of ORCA_WHIRLPOOLS markets: {}", counts[&DexLabel::ORCA_WHIRLPOOLS]); 128 | info!("Numbers of RAYDIUM markets: {}", counts[&DexLabel::RAYDIUM]); 129 | info!("Numbers of RAYDIUM_CLMM markets: {}", counts[&DexLabel::RAYDIUM_CLMM]); 130 | info!("Numbers of METEORA markets: {}", counts[&DexLabel::METEORA]); 131 | 132 | info!("🗑️ Excluded Markets: {}", excluded_markets_arb.len()); 133 | let all_routes: Vec = compute_routes(sorted_markets_arb.clone()); 134 | 135 | let all_paths: Vec = generate_swap_paths(include_1hop, include_2hop, all_routes, tokens.clone()); 136 | 137 | return (sorted_markets_arb, all_paths); 138 | } 139 | 140 | //Compute routes 141 | pub fn compute_routes(markets_arb: HashMap) -> Vec { 142 | let mut all_routes: Vec = Vec::new(); 143 | let mut counter: u32 = 0; 144 | for (key, market) in markets_arb { 145 | let route_0to1 = Route{id: counter, dex: market.clone().dexLabel, pool_address: market.clone().id, token_0to1: true, tokenIn: market.clone().tokenMintA, tokenOut: market.clone().tokenMintB, fee: market.clone().fee as u64}; 146 | counter += 1; 147 | let route_1to0 = Route{id: counter, dex: market.clone().dexLabel, pool_address: market.clone().id, token_0to1: false, tokenIn: market.clone().tokenMintB, tokenOut: market.clone().tokenMintA, fee: market.clone().fee as u64}; 148 | counter += 1; 149 | 150 | all_routes.push(route_0to1); 151 | all_routes.push(route_1to0); 152 | } 153 | 154 | // println!("All routes: {:?}", all_routes); 155 | return all_routes; 156 | } 157 | 158 | pub fn generate_swap_paths(include_1hop: bool, include_2hop: bool, all_routes: Vec, tokens: Vec) -> Vec { 159 | 160 | //Settings hop generations 161 | // let include_1hop = false; 162 | // let include_2hop = true; 163 | info!("Hops Settings | 1 Hop : {} | 2 Hops : {}", if include_1hop == true {"✅"} else {"❌"}, if include_2hop == true {"✅"} else {"❌"}); 164 | 165 | // On part du postulat que les pools de même jetons, du même Dex mais avec des fees différents peuvent avoir un prix différent, 166 | // donc on peut créer des routes 167 | let mut all_swap_paths: Vec = Vec::new(); 168 | let starting_routes: Vec<&Route> = all_routes.iter().filter(|route| route.tokenIn == tokens[0].address).collect(); 169 | 170 | //One hop 171 | // Sol -> token -> Sol 172 | 173 | if include_1hop == true { 174 | for route_x in starting_routes.clone() { 175 | for route_y in all_routes.clone() { 176 | if (route_y.tokenOut == tokens[0].address && route_x.tokenOut == route_y.tokenIn && route_x.pool_address != route_y.pool_address) { 177 | let paths = vec![route_x.clone(), route_y.clone()]; 178 | let id_paths = vec![route_x.clone().id, route_y.clone().id]; 179 | all_swap_paths.push(SwapPath{hops: 1, paths: paths.clone(), id_paths: id_paths}); 180 | } 181 | } 182 | } 183 | } 184 | 185 | let swap_paths_1hop_len = all_swap_paths.len(); 186 | info!("1 Hop swap_paths length: {}", swap_paths_1hop_len); 187 | 188 | //Two hops 189 | // Sol -> token1 -> token2 -> Sol 190 | if include_2hop == true { 191 | for route_1 in starting_routes { 192 | let all_routes_2: Vec<&Route> = all_routes.iter().filter(|route| route.tokenIn == route_1.tokenOut && route_1.pool_address != route.pool_address && route.tokenOut != tokens[0].address).collect(); 193 | for route_2 in all_routes_2 { 194 | let all_routes_3: Vec<&Route> = all_routes.iter().filter(|route| 195 | route.tokenIn == route_2.tokenOut 196 | && route_2.pool_address != route.pool_address 197 | && route_1.pool_address != route.pool_address 198 | && route.tokenOut == tokens[0].address 199 | ).collect(); 200 | if all_routes_3.len() > 0 { 201 | for route_3 in all_routes_3 { 202 | let paths = vec![route_1.clone(), route_2.clone(), route_3.clone()]; 203 | let id_paths = vec![route_1.clone().id, route_2.clone().id, route_3.clone().id]; 204 | all_swap_paths.push(SwapPath{hops: 2, paths: paths, id_paths: id_paths}); 205 | } 206 | } 207 | } 208 | } 209 | } 210 | info!("2 Hops swap_path length: {}", all_swap_paths.len() - swap_paths_1hop_len); 211 | 212 | // for path in all_swap_paths.clone() { 213 | // println!("Id_Paths: {:?}", path.id_paths); 214 | // } 215 | 216 | //Three hops 217 | // Sol -> token1 -> token2 -> token3 -> Sol 218 | 219 | // Code here... 220 | 221 | return all_swap_paths; 222 | } -------------------------------------------------------------------------------- /src/arbitrage/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod calc_arb; 2 | pub mod types; 3 | pub mod streams; 4 | pub mod strategies; 5 | pub mod simulate; -------------------------------------------------------------------------------- /src/arbitrage/streams.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::collections::HashMap; 3 | use anyhow::Result; 4 | use log::info; 5 | use solana_client::rpc_client::RpcClient; 6 | use solana_sdk::pubkey::Pubkey; 7 | use crate::{ 8 | common::{ 9 | constants::Env, 10 | utils::from_str, 11 | }, 12 | markets::types::{DexLabel, Market} 13 | }; 14 | 15 | //Get fresh data on all acounts with getMultipleAccounts 16 | pub async fn get_fresh_accounts_states(mut accounts: HashMap) -> HashMap { 17 | let env = Env::new(); 18 | let rpc_client = RpcClient::new(env.rpc_url); 19 | let mut counter_fresh_markets = 0; 20 | 21 | let mut markets_vec: Vec = Vec::new(); 22 | let mut key_vec: Vec = Vec::new(); 23 | let mut pubkeys_vec: Vec = Vec::new(); 24 | for (key, market) in accounts.clone().iter() { 25 | markets_vec.push(market.clone()); 26 | key_vec.push(key.clone()); 27 | pubkeys_vec.push(from_str(market.clone().id.as_str()).unwrap()); 28 | } 29 | 30 | for i in (0..pubkeys_vec.len()).step_by(100) { 31 | let maxLength = std::cmp::min(i + 100, pubkeys_vec.len()); 32 | let batch = &pubkeys_vec[(i..maxLength)]; 33 | 34 | let batch_results = rpc_client.get_multiple_accounts(&batch).unwrap(); 35 | // println!("BatchResult {:?}", batch_results); 36 | for (j, account) in batch_results.iter().enumerate() { 37 | let account = account.clone().unwrap(); 38 | // println!("WhirpoolAccount: {:?}", data); 39 | let account_data = account.data; 40 | 41 | markets_vec[j].account_data = Some(account_data); 42 | markets_vec[j].id = key_vec[j].clone(); 43 | counter_fresh_markets += 1; 44 | accounts.insert(key_vec[j].clone(), markets_vec[j].clone()); 45 | } 46 | } 47 | 48 | info!("💦💦 Fresh data for {:?} markets", counter_fresh_markets); 49 | return accounts; 50 | } 51 | 52 | -------------------------------------------------------------------------------- /src/arbitrage/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{de::DeserializeOwned, Deserialize, Serialize}; 2 | use mongodb::bson; 3 | 4 | use crate::markets::types::{DexLabel, Market}; 5 | 6 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 7 | pub struct TokenInArb { 8 | pub address: String, 9 | pub symbol: String, 10 | } 11 | 12 | #[derive(Debug, Clone, Serialize, Deserialize)] 13 | pub struct Route { 14 | pub id: u32, 15 | pub dex: DexLabel, 16 | pub pool_address: String, 17 | pub token_0to1: bool, 18 | pub tokenIn: String, 19 | pub tokenOut: String, 20 | pub fee: u64, 21 | } 22 | 23 | #[derive(Debug, Clone, Serialize, Deserialize)] 24 | pub struct SwapPath { 25 | pub hops: u8, 26 | pub paths: Vec, 27 | pub id_paths: Vec, 28 | } 29 | #[derive(Debug, Clone)] 30 | pub struct TokenInfos { 31 | pub address: String, 32 | pub decimals: u8, 33 | pub symbol: String, 34 | } 35 | 36 | #[derive(Debug, Clone, Serialize, Deserialize)] 37 | pub struct SwapRouteSimulation { 38 | pub id_route: u32, 39 | pub pool_address: String, 40 | pub dex_label: DexLabel, 41 | pub token_0to1: bool, 42 | pub token_in: String, 43 | pub token_out: String, 44 | pub amount_in: u64, 45 | pub estimated_amount_out: String, 46 | pub estimated_min_amount_out: String, 47 | 48 | } 49 | #[derive(Debug, Clone, Serialize, Deserialize)] 50 | pub struct SwapPathResult { 51 | pub path_id: u32, 52 | pub hops: u8, 53 | pub tokens_path: String, 54 | pub route_simulations: Vec, 55 | pub token_in: String, 56 | pub token_in_symbol: String, 57 | pub token_out: String, 58 | pub token_out_symbol: String, 59 | pub amount_in: u64, 60 | pub estimated_amount_out: String, 61 | pub estimated_min_amount_out: String, 62 | pub result: f64, 63 | } 64 | #[derive(Debug, Clone, Serialize)] 65 | pub struct VecSwapPathResult { 66 | pub result: Vec 67 | } 68 | #[derive(Debug, Clone, Serialize, Deserialize)] 69 | pub struct SwapPathSelected { 70 | pub result: f64, 71 | pub path: SwapPath, 72 | pub markets: Vec 73 | } 74 | #[derive(Debug, Clone, Serialize, Deserialize)] 75 | pub struct VecSwapPathSelected { 76 | pub value: Vec 77 | } 78 | -------------------------------------------------------------------------------- /src/common/config.rs: -------------------------------------------------------------------------------- 1 | // use std::collections::HashMap; 2 | 3 | // use crate::common::constants::Env; 4 | // use crate::addresses::ethereum::*; 5 | 6 | 7 | // pub const POOLS: Vec> = Env::new().[ 8 | // ("ethereum", POOLS_ETH), 9 | // ]; 10 | // pub const RPC_ENDPOINTS: HashMap<&str, &str> = [ 11 | // ("ethereum", env.ethereum_http_rpc_url), 12 | // ]; 13 | 14 | // pub const TOKENS: HashMap>> = [ 15 | // ("ethereum", TOKENS_ETH), 16 | // ]; 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | // import os 38 | // from dotenv import load_dotenv 39 | 40 | // from addresses import ( 41 | // ETHEREUM_TOKENS, 42 | // POLYGON_TOKENS, 43 | // ARBITRUM_TOKENS, 44 | 45 | // ETHEREUM_POOLS, 46 | // POLYGON_POOLS, 47 | // ARBITRUM_POOLS, 48 | 49 | // ETHEREUM_SIMULATION_HANDLERS, 50 | // ETHEREUM_EXECUTION_HANDLERS, 51 | // ) 52 | 53 | // load_dotenv(override=True) 54 | 55 | // RPC_ENDPOINTS = { 56 | // 'ethereum': os.getenv('ETHEREUM_HTTP_RPC_URL'), 57 | // 'polygon': os.getenv('POLYGON_HTTP_RPC_URL'), 58 | // 'arbitrum': os.getenv('ARBITRUM_HTTP_RPC_URL'), 59 | // } 60 | 61 | // WS_ENDPOINTS = { 62 | // 'ethereum': os.getenv('ETHEREUM_WS_RPC_URL'), 63 | // 'polygon': os.getenv('POLYGON_WS_RPC_URL'), 64 | // 'arbitrum': os.getenv('ARBITRUM_WS_RPC_URL'), 65 | // } 66 | 67 | // TOKENS = { 68 | // 'ethereum': ETHEREUM_TOKENS, 69 | // 'polygon': POLYGON_TOKENS, 70 | // 'arbitrum': ARBITRUM_TOKENS, 71 | // } 72 | 73 | // POOLS = ETHEREUM_POOLS + POLYGON_POOLS + ARBITRUM_POOLS 74 | 75 | // SIMULATION_HANDLERS = { 76 | // 'ethereum': ETHEREUM_SIMULATION_HANDLERS, 77 | // } 78 | 79 | // EXECUTION_HANDLERS = { 80 | // 'ethereum': ETHEREUM_EXECUTION_HANDLERS, 81 | // } 82 | -------------------------------------------------------------------------------- /src/common/constants.rs: -------------------------------------------------------------------------------- 1 | pub static PROJECT_NAME: &str = "MEV_Bot_Solana"; 2 | 3 | pub fn get_env(key: &str) -> String { 4 | std::env::var(key).unwrap_or(String::from("")) 5 | } 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct Env { 9 | pub block_engine_url: String, 10 | pub mainnet_rpc_url: String, 11 | pub rpc_url_tx: String, 12 | pub devnet_rpc_url: String, 13 | pub rpc_url: String, 14 | pub wss_rpc_url: String, 15 | pub geyser_url: String, 16 | pub geyser_access_token: String, 17 | pub simulator_url: String, 18 | pub ws_simulator_url: String, 19 | pub payer_keypair_path: String, 20 | pub database_name: String, 21 | 22 | } 23 | 24 | impl Env { 25 | pub fn new() -> Self { 26 | Env { 27 | block_engine_url: get_env("BLOCK_ENGINE_URL"), 28 | rpc_url: get_env("RPC_URL"), 29 | mainnet_rpc_url: get_env("MAINNET_RPC_URL"), 30 | rpc_url_tx: get_env("RPC_URL_TX"), 31 | devnet_rpc_url: get_env("DEVNET_RPC_URL"), 32 | wss_rpc_url: get_env("WSS_RPC_URL"), 33 | geyser_url: get_env("GEYSER_URL"), 34 | geyser_access_token: get_env("GEYSER_ACCESS_TOKEN"), 35 | simulator_url: get_env("SIMULATOR_URL"), 36 | ws_simulator_url: get_env("WS_SIMULATOR_URL"), 37 | payer_keypair_path: get_env("PAYER_KEYPAIR_PATH"), 38 | database_name: get_env("DATABASE_NAME") 39 | } 40 | } 41 | } 42 | 43 | pub static COINBASE: &str = "0xDAFEA492D9c6733ae3d56b7Ed1ADB60692c98Bc5"; // Flashbots Builder 44 | 45 | pub static WETH: &str = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 46 | pub static USDT: &str = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; 47 | pub static USDC: &str = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; 48 | 49 | /* 50 | Can figure out the balance slot of ERC-20 tokens using the: 51 | EvmSimulator::get_balance_slot method 52 | 53 | However, note that this does not work for all tokens. 54 | Especially tokens that are using proxy patterns. 55 | */ 56 | pub static WETH_BALANCE_SLOT: i32 = 3; 57 | pub static USDT_BALANCE_SLOT: i32 = 2; 58 | pub static USDC_BALANCE_SLOT: i32 = 9; 59 | 60 | pub static WETH_DECIMALS: u8 = 18; 61 | pub static USDT_DECIMALS: u8 = 6; 62 | pub static USDC_DECIMALS: u8 = 6; 63 | -------------------------------------------------------------------------------- /src/common/database.rs: -------------------------------------------------------------------------------- 1 | use log::info; 2 | use mongodb::bson::{doc, Document}; 3 | use mongodb::Collection; 4 | use mongodb::{Client as MongoDbCLient, options::ClientOptions}; 5 | use anyhow::Result; 6 | 7 | use crate::arbitrage::types::{SwapPathResult, VecSwapPathSelected}; 8 | 9 | pub async fn insert_swap_path_result_collection(collection_name: &str, sp_result: SwapPathResult) -> Result<()> { 10 | let db_name = "MEV_Bot"; 11 | let client_options = ClientOptions::parse("mongodb://localhost:27017").await.unwrap(); 12 | let client = MongoDbCLient::with_options(client_options).unwrap(); 13 | 14 | let db = client.database(db_name); 15 | let coll: Collection = db.collection::(collection_name); 16 | 17 | 18 | coll.insert_one(sp_result, None).await.unwrap(); 19 | info!("📊 {} writed in DB", collection_name); 20 | Ok(()) 21 | } 22 | pub async fn insert_vec_swap_path_selected_collection(collection_name: &str, best_paths_for_strat: VecSwapPathSelected) -> Result<()> { 23 | for bp in best_paths_for_strat.value.iter().enumerate() { 24 | 25 | } 26 | let db_name = "MEV_Bot"; 27 | 28 | let client_options = ClientOptions::parse("mongodb://localhost:27017").await.unwrap(); 29 | let client = MongoDbCLient::with_options(client_options).unwrap(); 30 | 31 | let db = client.database(db_name); 32 | let coll: Collection = db.collection::(collection_name); 33 | 34 | coll.insert_one(best_paths_for_strat, None).await.unwrap(); 35 | info!("📊 {} writed in DB", collection_name); 36 | 37 | Ok(()) 38 | } -------------------------------------------------------------------------------- /src/common/debug.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::{BufReader, Seek, SeekFrom, Read}; 3 | use serde_json::Value; 4 | 5 | pub fn print_json_segment(file_path: &str, start: u64, length: usize) -> Result<(), Box> { 6 | let file = File::open(file_path)?; 7 | let mut reader = BufReader::new(file); 8 | reader.seek(SeekFrom::Start(start))?; 9 | 10 | let mut buffer = vec![0; length]; 11 | reader.read_exact(&mut buffer)?; 12 | 13 | let segment = String::from_utf8_lossy(&buffer); 14 | println!("{}", segment); 15 | 16 | Ok(()) 17 | } -------------------------------------------------------------------------------- /src/common/maths.rs: -------------------------------------------------------------------------------- 1 | use rust_decimal::prelude::*; 2 | use rust_decimal::Decimal; 3 | use rust_decimal::MathematicalOps; 4 | use rust_decimal_macros::dec; 5 | 6 | pub fn from_x64_orca_wp(num: u128, decimals_0: f64, decimals_1: f64) -> Decimal { 7 | println!("numX64: {:?}", num); 8 | 9 | let num_dec: Decimal = Decimal::from_u128(num).unwrap(); 10 | let mul_x64: f64 = 2f64.powf(-64.0); 11 | let from_x64: Decimal = num_dec.checked_mul(Decimal::from_f64_retain(mul_x64).unwrap()).unwrap(); 12 | let price: Option = from_x64.checked_powd(dec!(2)).unwrap().checked_mul(Decimal::from_f64_retain(10f64.powf(decimals_0 - decimals_1)).unwrap()); 13 | return price.unwrap(); 14 | 15 | 16 | // public static fromX64(num: BN): Decimal { 17 | // return new Decimal(num.toString()).mul(Decimal.pow(2, -64)); 18 | // } 19 | 20 | // public static sqrtPriceX64ToPrice( 21 | // sqrtPriceX64: BN, 22 | // decimalsA: number, 23 | // decimalsB: number, 24 | // ): Decimal { 25 | // return MathUtil.fromX64(sqrtPriceX64) 26 | // .pow(2) 27 | // .mul(Decimal.pow(10, decimalsA - decimalsB)); 28 | // } 29 | 30 | } -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub mod constants; 3 | pub mod utils; 4 | pub mod maths; 5 | pub mod debug; 6 | pub mod types; 7 | pub mod database; -------------------------------------------------------------------------------- /src/common/types.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::arbitrage::types::TokenInArb; 4 | 5 | #[derive(Debug, Clone, Serialize, Deserialize)] 6 | pub struct InputVec { 7 | pub tokens_to_arb: Vec, 8 | pub include_1hop: bool, 9 | pub include_2hop: bool, 10 | pub numbers_of_best_paths: usize, 11 | pub get_fresh_pools_bool: bool, 12 | } -------------------------------------------------------------------------------- /src/common/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use fern::colors::{Color, ColoredLevelConfig}; 4 | use log::{info, LevelFilter}; 5 | use solana_program::pubkey::Pubkey; 6 | use solana_sdk::bs58; 7 | use core::mem; 8 | use std::{collections::HashMap, fs::{File, OpenOptions}}; 9 | use thiserror::Error; 10 | use reqwest::Error; 11 | use std::io::{BufWriter, Write}; 12 | 13 | use crate::{arbitrage::types::{SwapPathResult, TokenInArb, TokenInfos}, common::constants::{ 14 | Env, PROJECT_NAME 15 | }}; 16 | use solana_client::rpc_client::RpcClient; 17 | 18 | // Function to format our console logs 19 | pub fn setup_logger() -> Result<(), fern::InitError> { 20 | let colors = ColoredLevelConfig { 21 | trace: Color::Cyan, 22 | debug: Color::Magenta, 23 | info: Color::Green, 24 | warn: Color::Red, 25 | error: Color::BrightRed, 26 | ..ColoredLevelConfig::new() 27 | }; 28 | 29 | let mut base_config = fern::Dispatch::new(); 30 | 31 | //Console out 32 | let stdout_config = fern::Dispatch::new() 33 | .format(move |out, message, record| { 34 | out.finish(format_args!( 35 | "{}[{}] {}", 36 | chrono::Local::now().format("[%H:%M:%S]"), 37 | colors.color(record.level()), 38 | message 39 | )) 40 | }) 41 | .chain(std::io::stdout()) 42 | .level(log::LevelFilter::Error) 43 | .level_for(PROJECT_NAME, LevelFilter::Info); 44 | 45 | //File logs 46 | let file_config = fern::Dispatch::new() 47 | .format(move |out, message, record| { 48 | out.finish(format_args!( 49 | "{}{} [{}][{}] {}", 50 | chrono::Local::now().format("[%H:%M:%S]"), 51 | chrono::Local::now().format("[%d/%m/%Y]"), 52 | record.level(), 53 | record.target(), 54 | message 55 | )) 56 | }) 57 | .chain(fern::log_file("logs\\program.log")?) 58 | .level(log::LevelFilter::Error) 59 | .level_for(PROJECT_NAME, LevelFilter::Info); 60 | //Errors logs 61 | let errors_config = fern::Dispatch::new() 62 | .format(move |out, message, record| { 63 | out.finish(format_args!( 64 | "{}{} [{}][{}] {}", 65 | chrono::Local::now().format("[%H:%M:%S]"), 66 | chrono::Local::now().format("[%d/%m/%Y]"), 67 | record.level(), 68 | record.target(), 69 | message 70 | )) 71 | }) 72 | .chain(fern::log_file("logs\\errors.log")?) 73 | .level(log::LevelFilter::Error); 74 | 75 | base_config 76 | .chain(file_config) 77 | .chain(errors_config) 78 | .chain(stdout_config) 79 | .apply()?; 80 | Ok(()) 81 | } 82 | 83 | pub fn write_file_swap_path_result(path: String, content_raw: SwapPathResult) -> Result<()> { 84 | File::create(path.clone()); 85 | 86 | let file = OpenOptions::new().read(true).write(true).open(path.clone())?; 87 | let mut writer = BufWriter::new(&file); 88 | 89 | writer.write_all(serde_json::to_string(&content_raw)?.as_bytes())?; 90 | writer.flush()?; 91 | info!("Data written to '{}' successfully.", path); 92 | 93 | Ok(()) 94 | } 95 | 96 | #[derive(Error, Debug, Clone, PartialEq, Eq)] 97 | pub enum ParsePubkeyError { 98 | #[error("String is the wrong size")] 99 | WrongSize, 100 | #[error("Invalid Base58 string")] 101 | Invalid, 102 | } 103 | 104 | type Err = ParsePubkeyError; 105 | 106 | /// Maximum string length of a base58 encoded pubkey 107 | const MAX_BASE58_LEN: usize = 44; 108 | 109 | pub fn from_str(s: &str) -> Result { 110 | if s.len() > MAX_BASE58_LEN { 111 | return Err(ParsePubkeyError::WrongSize); 112 | } 113 | let pubkey_vec = bs58::decode(s) 114 | .into_vec() 115 | .map_err(|_| ParsePubkeyError::Invalid)?; 116 | if pubkey_vec.len() != mem::size_of::() { 117 | Err(ParsePubkeyError::WrongSize) 118 | } else { 119 | Pubkey::try_from(pubkey_vec).map_err(|_| ParsePubkeyError::Invalid) 120 | } 121 | } 122 | pub fn from_Pubkey(pubkey: Pubkey) -> String { 123 | let pubkey_vec = bs58::encode(pubkey) 124 | .into_string(); 125 | return pubkey_vec; 126 | } 127 | 128 | pub async fn get_tokens_infos(tokens: Vec) -> HashMap { 129 | let env = Env::new(); 130 | let rpc_client = RpcClient::new(env.rpc_url); 131 | 132 | let mut pubkeys_str: Vec = Vec::new(); 133 | let mut pubkeys: Vec = Vec::new(); 134 | for token in tokens.clone() { 135 | pubkeys_str.push(token.address.clone()); 136 | pubkeys.push(from_str(token.address.clone().as_str()).unwrap()); 137 | } 138 | let batch_results = rpc_client.get_multiple_accounts(&pubkeys).unwrap(); 139 | 140 | let mut tokens_infos: HashMap = HashMap::new(); 141 | 142 | for (j, account) in batch_results.iter().enumerate() { 143 | let account = account.clone().unwrap(); 144 | let mint_layout = MintLayout::try_from_slice(&account.data).unwrap(); 145 | 146 | let symbol = tokens.iter().find(|r| *r.address == pubkeys_str[j]).expect("Symbol token not found"); 147 | tokens_infos.insert(pubkeys_str[j].clone(), TokenInfos{ 148 | address: pubkeys_str[j].clone(), 149 | decimals: mint_layout.decimals, 150 | symbol: symbol.clone().symbol 151 | }); 152 | } 153 | return tokens_infos; 154 | 155 | } 156 | 157 | pub async fn make_request(req_url: String) -> Result { 158 | reqwest::get(req_url).await 159 | } 160 | 161 | #[derive(BorshDeserialize, Debug)] 162 | pub struct MintLayout { 163 | pub mint_authority_option: u32, 164 | pub mint_authority: Pubkey, 165 | pub supply: u64, 166 | pub decimals: u8, 167 | pub is_initialized: bool, 168 | pub freeze_authority_option: u32, 169 | pub freeze_authority: Pubkey, 170 | } 171 | -------------------------------------------------------------------------------- /src/data/graphs.rs: -------------------------------------------------------------------------------- 1 | // use piston_window::{EventLoop, PistonWindow, WindowSettings}; 2 | // use plotters::prelude::*; 3 | // use plotters_piston::{draw_piston_window}; 4 | // use systemstat::platform::common::Platform; 5 | // use systemstat::System; 6 | 7 | // use std::collections::vec_deque::VecDeque; 8 | 9 | // const FPS: u32 = 10; 10 | // const LENGTH: u32 = 20; 11 | // const N_DATA_POINTS: usize = (FPS * LENGTH) as usize; 12 | // fn execute_graph() { 13 | // let mut window: PistonWindow = WindowSettings::new("Real Time CPU Usage", [450, 300]) 14 | // .samples(4) 15 | // .build() 16 | // .unwrap(); 17 | // let sys = System::new(); 18 | // window.set_max_fps(FPS as u64); 19 | // let mut load_measurement: Vec<_> = (0..FPS).map(|_| sys.cpu_load().unwrap()).collect(); 20 | // let mut epoch = 0; 21 | // let mut data = vec![]; 22 | // while let Some(_) = draw_piston_window(&mut window, |b| { 23 | // let cpu_loads = load_measurement[epoch % FPS as usize].done()?; 24 | 25 | // let root = b.into_drawing_area(); 26 | // root.fill(&WHITE)?; 27 | 28 | // if data.len() < cpu_loads.len() { 29 | // for _ in data.len()..cpu_loads.len() { 30 | // data.push(VecDeque::from(vec![0f32; N_DATA_POINTS + 1])); 31 | // } 32 | // } 33 | 34 | // for (core_load, target) in cpu_loads.into_iter().zip(data.iter_mut()) { 35 | // if target.len() == N_DATA_POINTS + 1 { 36 | // target.pop_front(); 37 | // } 38 | // target.push_back(1.0 - core_load.idle); 39 | // } 40 | 41 | // let mut cc = ChartBuilder::on(&root) 42 | // .margin(10) 43 | // .caption("Real Time CPU Usage", ("sans-serif", 30)) 44 | // .x_label_area_size(40) 45 | // .y_label_area_size(50) 46 | // .build_cartesian_2d(0..N_DATA_POINTS as u32, 0f32..1f32)?; 47 | 48 | // cc.configure_mesh() 49 | // .x_label_formatter(&|x| format!("{}", -(LENGTH as f32) + (*x as f32 / FPS as f32))) 50 | // .y_label_formatter(&|y| format!("{}%", (*y * 100.0) as u32)) 51 | // .x_labels(15) 52 | // .y_labels(5) 53 | // .x_desc("Seconds") 54 | // .y_desc("% Busy") 55 | // .axis_desc_style(("sans-serif", 15)) 56 | // .draw()?; 57 | 58 | // for (idx, data) in (0..).zip(data.iter()) { 59 | // cc.draw_series(LineSeries::new( 60 | // (0..).zip(data.iter()).map(|(a, b)| (a, *b)), 61 | // &Palette99::pick(idx), 62 | // ))? 63 | // .label(format!("CPU {}", idx)) 64 | // .legend(move |(x, y)| { 65 | // Rectangle::new([(x - 5, y - 5), (x + 5, y + 5)], &Palette99::pick(idx)) 66 | // }); 67 | // } 68 | 69 | // cc.configure_series_labels() 70 | // .background_style(&WHITE.mix(0.8)) 71 | // .border_style(&BLACK) 72 | // .draw()?; 73 | 74 | // load_measurement[epoch % FPS as usize] = sys.cpu_load()?; 75 | // epoch += 1; 76 | // Ok(()) 77 | // }) {} 78 | // } -------------------------------------------------------------------------------- /src/data/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod graphs; -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod common; 2 | pub mod strategies; 3 | pub mod markets; 4 | pub mod arbitrage; 5 | pub mod transactions; 6 | pub mod data; 7 | 8 | use std::env; 9 | 10 | mod tests { 11 | use solana_sdk::pubkey::Pubkey; 12 | 13 | use crate::{arbitrage::types::{SwapPathResult, SwapRouteSimulation, TokenInArb}, common::utils::from_str, markets::types::DexLabel, transactions::create_transaction::{create_ata_extendlut_transaction, write_lut_for_market, ChainType, SendOrSimulate}}; 14 | 15 | #[test] 16 | fn write_in_write_lut_for_market() { 17 | let market: Pubkey = Pubkey::new_unique(); 18 | let lut_address: Pubkey = Pubkey::new_unique(); 19 | let _ = write_lut_for_market(market, lut_address, true); 20 | let market2: Pubkey = Pubkey::new_unique(); 21 | let lut_address2: Pubkey = Pubkey::new_unique(); 22 | let _ = write_lut_for_market(market2, lut_address2, true); 23 | } 24 | #[tokio::test] 25 | async fn test_devnet_create_ata_extendlut_transaction() { 26 | let tokens_to_arb: Vec = vec![ 27 | TokenInArb{address: String::from("So11111111111111111111111111111111111111112"), symbol: String::from("SOL")}, // Base token here 28 | TokenInArb{address: String::from("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"), symbol: String::from("USDC")}, 29 | // TokenInArb{address: String::from("9jaZhJM6nMHTo4hY9DGabQ1HNuUWhJtm7js1fmKMVpkN"), symbol: String::from("AMC")}, 30 | ]; 31 | 32 | let spr = SwapPathResult{ 33 | path_id: 1, 34 | hops: 2, 35 | tokens_path: "SOL-AMC-GME-SOL".to_string(), 36 | route_simulations: vec![ 37 | SwapRouteSimulation{ 38 | id_route: 17, 39 | pool_address: "HZZofxusqKaA9JqaeXW8PtUALRXUwSLLwnt4eBFiyEdC".to_string(), 40 | dex_label: DexLabel::RAYDIUM, 41 | token_0to1: false, 42 | token_in: "So11111111111111111111111111111111111111112".to_string(), 43 | token_out: "9jaZhJM6nMHTo4hY9DGabQ1HNuUWhJtm7js1fmKMVpkN".to_string(), 44 | amount_in: 300000000, 45 | estimated_amount_out: "8703355798604".to_string(), 46 | estimated_min_amount_out: "8617183959013".to_string() 47 | }, 48 | SwapRouteSimulation{ 49 | id_route: 26, 50 | pool_address: "9kbAydmdxuqrJGvaCmmnJaGnaC96zAkBHZ9dQn3cm9PZ".to_string(), 51 | dex_label: DexLabel::METEORA, 52 | token_0to1: true, 53 | token_in: "9jaZhJM6nMHTo4hY9DGabQ1HNuUWhJtm7js1fmKMVpkN".to_string(), 54 | token_out: "8wXtPeU6557ETkp9WHFY1n1EcU6NxDvbAggHGsMYiHsB".to_string(), 55 | amount_in: 8703355798604, // 0.001 SOL 56 | estimated_amount_out:"4002500590682".to_string(), 57 | estimated_min_amount_out: "3998498090091".to_string() 58 | }, 59 | SwapRouteSimulation{ 60 | id_route: 13, 61 | pool_address: "2qKjGUBdgLcGVt1JbjLfXtphPQNkq4ujd6PyrTBWkeJ5".to_string(), 62 | dex_label: DexLabel::ORCA_WHIRLPOOLS, 63 | token_0to1: false, 64 | token_in: "8wXtPeU6557ETkp9WHFY1n1EcU6NxDvbAggHGsMYiHsB".to_string(), 65 | token_out: "So11111111111111111111111111111111111111112".to_string(), 66 | amount_in: 4002500590682, // 0.001 SOL 67 | estimated_amount_out:"300776562".to_string(), 68 | estimated_min_amount_out: "297798576".to_string() 69 | } 70 | ], 71 | token_in: "So11111111111111111111111111111111111111112".to_string(), 72 | token_in_symbol: "SOL".to_string(), 73 | token_out: "So11111111111111111111111111111111111111112".to_string(), 74 | token_out_symbol: "SOL".to_string(), 75 | amount_in: 300000000, 76 | estimated_amount_out: "300776562".to_string(), 77 | estimated_min_amount_out: "297798576".to_string(), 78 | result: 776562.0 79 | }; 80 | 81 | let tokens: Vec = tokens_to_arb.into_iter().map(|tok| from_str(tok.address.as_str()).unwrap()).collect(); 82 | let _ = create_ata_extendlut_transaction( 83 | ChainType::Devnet, 84 | SendOrSimulate::Send, 85 | spr.clone(), 86 | from_str("6nGymM5X1djYERKZtoZ3Yz3thChMVF6jVRDzhhcmxuee").unwrap(), 87 | tokens 88 | ).await; 89 | } 90 | } -------------------------------------------------------------------------------- /src/markets/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod raydium_clmm; 2 | pub mod raydium; 3 | pub mod orca; 4 | pub mod orca_whirpools; 5 | pub mod meteora; 6 | pub mod types; 7 | pub mod utils; 8 | pub mod pools; 9 | -------------------------------------------------------------------------------- /src/markets/pools.rs: -------------------------------------------------------------------------------- 1 | use crate::common::constants::Env; 2 | use super::meteora::{fetch_data_meteora, MeteoraDEX}; 3 | use super::types::{Dex, DexLabel}; 4 | use super::raydium_clmm::{RaydiumClmmDEX, fetch_data_raydium_clmm}; 5 | use super::orca::{OrcaDex, fetch_data_orca}; 6 | use super::orca_whirpools::{OrcaDexWhirpools, fetch_data_orca_whirpools}; 7 | use super::raydium::{fetch_data_raydium, RaydiumDEX}; 8 | 9 | use strum::IntoEnumIterator; 10 | use std::fs::File; 11 | use std::io::Write; 12 | use serde::{Deserialize, Serialize}; 13 | use solana_sdk::pubkey::Pubkey; 14 | use reqwest::get; 15 | use log::info; 16 | use solana_client::rpc_client::RpcClient; 17 | 18 | 19 | pub async fn load_all_pools(refecth_api: bool) -> Vec { 20 | if refecth_api { 21 | fetch_data_raydium_clmm().await; 22 | fetch_data_orca().await; 23 | fetch_data_orca_whirpools().await; 24 | fetch_data_raydium().await; 25 | fetch_data_meteora().await; 26 | } 27 | 28 | let mut dex1 = Dex::new(DexLabel::RAYDIUM_CLMM); 29 | let dex_raydium_clmm = RaydiumClmmDEX::new(dex1); 30 | let mut dex2 = Dex::new(DexLabel::ORCA); 31 | let dex_orca = OrcaDex::new(dex2); 32 | let mut dex3 = Dex::new(DexLabel::ORCA_WHIRLPOOLS); 33 | let dex_orca_whirpools = OrcaDexWhirpools::new(dex3); 34 | let mut dex4 = Dex::new(DexLabel::RAYDIUM); 35 | let dex_raydium = RaydiumDEX::new(dex4); 36 | let mut dex5 = Dex::new(DexLabel::METEORA); 37 | let dex_meteora = MeteoraDEX::new(dex5); 38 | 39 | // println!("random_pair {:?}", dex_raydium.dex.pairToMarkets.get("So11111111111111111111111111111111111111112/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So")); 40 | 41 | let mut results: Vec = Vec::new(); 42 | results.push(dex_raydium_clmm.dex); 43 | results.push(dex_orca.dex); 44 | results.push(dex_orca_whirpools.dex); 45 | results.push(dex_raydium.dex); 46 | results.push(dex_meteora.dex); 47 | return results 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/markets/types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use crate::markets::utils::toPairString; 3 | use serde::{Deserialize, Serialize}; 4 | use strum_macros::EnumIter; 5 | 6 | #[derive(Debug, Clone, EnumIter, Serialize, Deserialize, Eq, PartialEq, Hash)] 7 | pub enum DexLabel { 8 | ORCA, 9 | ORCA_WHIRLPOOLS, 10 | RAYDIUM, 11 | RAYDIUM_CLMM, 12 | METEORA, 13 | } 14 | 15 | impl DexLabel { 16 | pub fn str(&self) -> String { 17 | match self { 18 | DexLabel::ORCA => String::from("Orca"), 19 | DexLabel::ORCA_WHIRLPOOLS => String::from("Orca (Whirlpools)"), 20 | DexLabel::RAYDIUM => String::from("Raydium"), 21 | DexLabel::RAYDIUM_CLMM => String::from("Raydium CLMM"), 22 | DexLabel::METEORA => String::from("Meteora"), 23 | } 24 | } 25 | pub fn api_url(&self) -> String { 26 | match self { 27 | DexLabel::ORCA => String::from("https://api.orca.so/allPools"), 28 | DexLabel::ORCA_WHIRLPOOLS => String::from("https://api.mainnet.orca.so/v1/whirlpool/list"), 29 | DexLabel::RAYDIUM => String::from("https://api.raydium.io/v2/main/pairs"), 30 | DexLabel::RAYDIUM_CLMM => String::from("https://api.raydium.io/v2/ammV3/ammPools"), 31 | DexLabel::METEORA => String::from("https://dlmm-api.meteora.ag/pair/all"), 32 | } 33 | } 34 | } 35 | 36 | #[derive(Debug, Clone, Serialize, Deserialize)] 37 | pub struct Market { 38 | pub tokenMintA: String, 39 | pub tokenVaultA: String, 40 | pub tokenMintB: String, 41 | pub tokenVaultB: String, 42 | pub dexLabel: DexLabel, 43 | pub fee: u64, 44 | pub id: String, 45 | pub account_data: Option>, 46 | pub liquidity: Option, 47 | } 48 | 49 | #[derive(Debug, Clone)] 50 | pub struct Dex { 51 | pub pairToMarkets: HashMap>, 52 | // ammCalcAddPoolMessages: AmmCalcWorkerParamMessage[]; 53 | pub label: DexLabel, 54 | } 55 | 56 | impl Dex { 57 | pub fn new(label: DexLabel) -> Self { 58 | let pairToMarkets = HashMap::new(); 59 | Dex { 60 | pairToMarkets: pairToMarkets, 61 | label: label, 62 | } 63 | } 64 | 65 | // getAmmCalcAddPoolMessages(): AmmCalcWorkerParamMessage[] { 66 | // return this.ammCalcAddPoolMessages; 67 | // } 68 | 69 | pub fn getMarketsForPair(&self, mintA: String, mintB: String) -> &Vec { 70 | let pair = toPairString(mintA, mintB); 71 | let markets = self.pairToMarkets.get(&pair).unwrap(); 72 | 73 | return markets; 74 | } 75 | 76 | pub fn getAllMarkets(&self) -> Vec<&Vec> { 77 | let mut all_markets = Vec::new(); 78 | 79 | for markets in self.pairToMarkets.values() { 80 | all_markets.push(markets); 81 | } 82 | return all_markets; 83 | } 84 | 85 | } 86 | 87 | #[derive(Debug)] 88 | pub struct PoolItem { 89 | pub mintA: String, 90 | pub mintB: String, 91 | pub vaultA: String, 92 | pub vaultB: String, 93 | pub tradeFeeRate: u128 94 | } 95 | 96 | #[derive(Debug, Deserialize, Serialize)] 97 | pub struct SimulationRes { 98 | pub amountIn: String, 99 | pub estimatedAmountOut: String, 100 | pub estimatedMinAmountOut: Option 101 | } 102 | #[derive(Debug, Deserialize, Serialize)] 103 | pub struct SimulationError { 104 | pub error: String, 105 | } -------------------------------------------------------------------------------- /src/markets/utils.rs: -------------------------------------------------------------------------------- 1 | pub fn toPairString(mintA: String, mintB: String) -> String { 2 | if (mintA < mintB) { 3 | return format!("{}/{}", mintA, mintB); 4 | } else { 5 | return format!("{}/{}", mintB, mintA); 6 | } 7 | } -------------------------------------------------------------------------------- /src/strategies/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod pools; -------------------------------------------------------------------------------- /src/strategies/pools.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, thread::sleep, time}; 2 | use log::info; 3 | use solana_client::rpc_client::RpcClient; 4 | use solana_sdk::commitment_config::CommitmentConfig; 5 | use crate::{ 6 | arbitrage::types::TokenInArb, common:: 7 | constants::Env 8 | , markets::{meteora::fetch_new_meteora_pools, orca_whirpools::fetch_new_orca_whirpools, raydium::fetch_new_raydium_pools, types::Market} 9 | }; 10 | 11 | pub async fn get_fresh_pools(tokens: Vec) -> HashMap { 12 | let env = Env::new(); 13 | let rpc_client = RpcClient::new_with_commitment(env.rpc_url.as_str(), CommitmentConfig::confirmed()); 14 | let mut new_markets: HashMap = HashMap::new(); 15 | let mut count_new_pools = 0; 16 | 17 | println!("Tokens: {:#?}", tokens); 18 | for (i, token) in tokens.iter().enumerate() { 19 | // Avoid fetch for the first token (often SOL) 20 | if i == 0 { 21 | continue; 22 | } 23 | //Orca Whirpools 24 | println!("1 GetProgramAccounts Orca"); 25 | let orca_res_tokena = fetch_new_orca_whirpools(&rpc_client, token.address.clone(), false).await; 26 | for orca_pool in orca_res_tokena { 27 | new_markets.insert(orca_pool.0.to_string(), orca_pool.1); 28 | count_new_pools += 1; 29 | } 30 | sleep(time::Duration::from_millis(2000)); 31 | println!("1 GetProgramAccounts Orca"); 32 | 33 | let orca_res_tokenb = fetch_new_orca_whirpools(&rpc_client, token.address.clone(), true).await; 34 | for orca_pool in orca_res_tokenb { 35 | new_markets.insert(orca_pool.0.to_string(), orca_pool.1); 36 | count_new_pools += 1; 37 | } 38 | sleep(time::Duration::from_millis(2000)); 39 | println!("1 GetProgramAccounts Raydium"); 40 | //Raydium Markets 41 | let raydium_res_tokena = fetch_new_raydium_pools(&rpc_client, token.address.clone(), false).await; 42 | for raydium_pool in raydium_res_tokena { 43 | new_markets.insert(raydium_pool.0.to_string(), raydium_pool.1); 44 | count_new_pools += 1; 45 | } 46 | sleep(time::Duration::from_millis(2000)); 47 | println!("1 GetProgramAccounts Raydium"); 48 | let raydium_res_tokenb = fetch_new_raydium_pools(&rpc_client, token.address.clone(), true).await; 49 | for raydium_pool in raydium_res_tokenb { 50 | new_markets.insert(raydium_pool.0.to_string(), raydium_pool.1); 51 | count_new_pools += 1; 52 | } 53 | sleep(time::Duration::from_millis(2000)); 54 | println!("1 GetProgramAccounts Meteora"); 55 | //Meteora Markets 56 | let meteora_res_tokena = fetch_new_meteora_pools(&rpc_client, token.address.clone(), false).await; 57 | for meteora_pool in meteora_res_tokena { 58 | new_markets.insert(meteora_pool.0.to_string(), meteora_pool.1); 59 | count_new_pools += 1; 60 | } 61 | sleep(time::Duration::from_millis(2000)); 62 | println!("1 GetProgramAccounts Meteora"); 63 | let meteora_res_tokenb = fetch_new_meteora_pools(&rpc_client, token.address.clone(), true).await; 64 | for meteora_pool in meteora_res_tokenb { 65 | new_markets.insert(meteora_pool.0.to_string(), meteora_pool.1); 66 | count_new_pools += 1; 67 | } 68 | sleep(time::Duration::from_millis(2000)); 69 | } 70 | info!("⚠️⚠️ NO RAYDIUM_CLMM fresh pools !"); 71 | info!("⚠️⚠️ NO ORCA fresh pools !"); 72 | return new_markets; 73 | } -------------------------------------------------------------------------------- /src/transactions/cache/lut_addresses.json: -------------------------------------------------------------------------------- 1 | {"value":[{"market":"1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM","lut_address":"1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh"},{"market":"HZZofxusqKaA9JqaeXW8PtUALRXUwSLLwnt4eBFiyEdC","lut_address":"6nGymM5X1djYERKZtoZ3Yz3thChMVF6jVRDzhhcmxuee"},{"market":"9kbAydmdxuqrJGvaCmmnJaGnaC96zAkBHZ9dQn3cm9PZ","lut_address":"6nGymM5X1djYERKZtoZ3Yz3thChMVF6jVRDzhhcmxuee"},{"market":"2qKjGUBdgLcGVt1JbjLfXtphPQNkq4ujd6PyrTBWkeJ5","lut_address":"6nGymM5X1djYERKZtoZ3Yz3thChMVF6jVRDzhhcmxuee"},{"market":"8qs2ye9viAD5QEmvq7MWBQvCXNJWEcmkodZg6CVtAnXP","lut_address":"6nGymM5X1djYERKZtoZ3Yz3thChMVF6jVRDzhhcmxuee"},{"market":"DoCdW2RPGc3NGdDDFtYbWHFSnJz5mWFV44u7ecwVV6FM","lut_address":"6nGymM5X1djYERKZtoZ3Yz3thChMVF6jVRDzhhcmxuee"}]} -------------------------------------------------------------------------------- /src/transactions/cache/lut_addresses_test.json: -------------------------------------------------------------------------------- 1 | {"value":[{"market":"1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM","lut_address":"1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh"},{"market":"11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3","lut_address":"11111112cMQwSC9qirWGjZM6gLGwW69X22mqwLLGP"},{"market":"1111111QLbz7JHiBTspS962RLKV8GndWFwiEaqKM","lut_address":"1111111ogCyDbaRMvkdsHB3qfdyFYaG1WtRUAfdh"},{"market":"11111112D1oxKts8YPdTJRG5FzxTNpMtWmq8hkVx3","lut_address":"11111112cMQwSC9qirWGjZM6gLGwW69X22mqwLLGP"}]} -------------------------------------------------------------------------------- /src/transactions/meteoradlmm_swap.rs: -------------------------------------------------------------------------------- 1 | // //Taken here: https://github.com/MeteoraAg/dlmm-sdk/blob/main/cli/src/instructions/swap.rs 2 | 3 | use anchor_client::solana_client::rpc_config::RpcSendTransactionConfig; 4 | use anchor_client::{Client, Cluster}; 5 | use anchor_client::{solana_sdk::pubkey::Pubkey, solana_sdk::signer::Signer}; 6 | use anchor_lang::error_code; 7 | use anchor_spl::associated_token::get_associated_token_address; 8 | use anchor_spl::token::spl_token; 9 | use anyhow::*; 10 | use anchor_lang::solana_program::msg; 11 | 12 | use borsh::{BorshDeserialize, BorshSerialize}; 13 | use num::Integer; 14 | use solana_client::rpc_client::RpcClient; 15 | use solana_program::hash; 16 | use solana_program::instruction::AccountMeta; 17 | use std::rc::Rc; 18 | use std::result::Result::Ok; 19 | use std::mem::size_of; 20 | 21 | use log::{info, error}; 22 | use solana_sdk::instruction::Instruction; 23 | use solana_sdk::signature::read_keypair_file; 24 | use spl_associated_token_account::instruction::create_associated_token_account; 25 | 26 | use crate::common::constants::Env; 27 | use crate::common::utils::from_str; 28 | use crate::markets::meteora::AccountData; 29 | use crate::markets::types::DexLabel; 30 | use crate::transactions::create_transaction::{InstructionDetails, MarketInfos}; 31 | 32 | 33 | #[derive(Debug, Clone)] 34 | pub struct SwapParametersMeteora { 35 | pub lb_pair: Pubkey, 36 | pub amount_in: u64, 37 | pub swap_for_y: bool, 38 | pub input_token: Pubkey, 39 | pub output_token: Pubkey, 40 | pub minimum_amount_out: u64, 41 | } 42 | // 43 | pub async fn construct_meteora_instructions(params: SwapParametersMeteora) -> Vec { 44 | let SwapParametersMeteora { 45 | amount_in, 46 | lb_pair, 47 | swap_for_y, 48 | input_token, 49 | output_token, 50 | minimum_amount_out, 51 | } = params; 52 | // info!("METEORA CRAFT SWAP INSTRUCTION !"); 53 | 54 | let mut swap_instructions: Vec = Vec::new(); 55 | let env = Env::new(); 56 | let payer = read_keypair_file(env.payer_keypair_path).expect("Wallet keypair file not found"); 57 | 58 | let amm_program = from_str("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo").unwrap(); 59 | 60 | let rpc_client: RpcClient = RpcClient::new(env.rpc_url); 61 | let pool_account: solana_sdk::account::Account = rpc_client.get_account(&lb_pair).unwrap(); 62 | let pool_state = AccountData::try_from_slice(&pool_account.data).unwrap(); 63 | 64 | // println!("Pool State: {:#?}", pool_state); 65 | 66 | //Get event authority 67 | let (event_authority, _bump) = Pubkey::find_program_address(&[b"__event_authority"], &amm_program); 68 | 69 | //Get PDA 70 | let pda_user_source = get_associated_token_address( 71 | &payer.pubkey(), 72 | &input_token, 73 | ); 74 | match rpc_client.get_account(&pda_user_source) { 75 | Ok(account) => {} 76 | Err(error) => { 77 | // error!("❌ PDA not exist for {}", input_token); 78 | } 79 | } 80 | 81 | let pda_user_destination = get_associated_token_address( 82 | &payer.pubkey(), 83 | &output_token, 84 | ); 85 | 86 | match rpc_client.get_account(&pda_user_destination) { 87 | Ok(account) => {} 88 | Err(error) => { 89 | // error!("❌ PDA not exist for {}", output_token); 90 | } 91 | } 92 | 93 | //Get bin arrays 94 | let active_bin_array_idx = bin_id_to_bin_array_index(pool_state.active_id).unwrap(); 95 | let (bin_array_0, _bump) = derive_bin_array_pda(lb_pair, active_bin_array_idx as i64, amm_program); 96 | 97 | let bin_array_bitmap_extension = derive_bin_array_bitmap_extension(lb_pair, amm_program); 98 | let bin_array_1 = derive_bin_array_pda(lb_pair, (active_bin_array_idx - 1) as i64, amm_program).0; 99 | let bin_array_2 = derive_bin_array_pda(lb_pair, (active_bin_array_idx - 2) as i64, amm_program).0; 100 | 101 | 102 | let accounts = vec![ 103 | // LbPair 104 | AccountMeta::new(lb_pair, false), 105 | AccountMeta::new_readonly(amm_program, false), 106 | AccountMeta::new(pool_state.reserve_x, false), 107 | AccountMeta::new(pool_state.reserve_y, false), 108 | //pda in 109 | AccountMeta::new(pda_user_source, false), 110 | //pda out 111 | AccountMeta::new(pda_user_destination, false), 112 | AccountMeta::new_readonly(pool_state.token_xmint, false), 113 | AccountMeta::new_readonly(pool_state.token_ymint, false), 114 | AccountMeta::new(pool_state.oracle, false), 115 | AccountMeta::new(amm_program, false), 116 | //user 117 | AccountMeta::new_readonly(payer.pubkey(), true), 118 | //token program 119 | AccountMeta::new_readonly(spl_token::id(), false), 120 | AccountMeta::new_readonly(spl_token::id(), false), 121 | //Event authority 122 | AccountMeta::new(event_authority, false), 123 | AccountMeta::new_readonly(amm_program, false), 124 | // 3 more accounts... 125 | AccountMeta::new(bin_array_0, false), 126 | AccountMeta::new(bin_array_1, false), 127 | AccountMeta::new(bin_array_2, false), 128 | // AccountMeta::new(from_str("EGcDqYxK3Ke7STeKwgaDH8uLSXqMEmwFC2hMnvWk7PwW").unwrap(), false), 129 | ]; 130 | 131 | //Data Instruction 132 | let mut sighash = [0u8; 8]; 133 | sighash.copy_from_slice(&hash::hash("global:swap".as_bytes()).to_bytes()[..8]); 134 | let mut data = [sighash].concat(); 135 | data.extend_from_slice(&amount_in.to_le_bytes()); 136 | data.extend_from_slice(&minimum_amount_out.to_le_bytes()); 137 | 138 | let instruction = Instruction{ 139 | program_id: amm_program, 140 | accounts, 141 | data, 142 | }; 143 | 144 | swap_instructions.push(InstructionDetails{ 145 | instruction: instruction, 146 | details: "Meteora Swap Instruction".to_string(), 147 | market: Some(MarketInfos{dex_label: DexLabel::METEORA, address: lb_pair }) 148 | }); 149 | return swap_instructions; 150 | 151 | } 152 | 153 | //////////////////////////////////////////////////////////////////////////////////////////////////////// 154 | ////////////////////////////////////////// UTILS /////////////////////////////////////////////////// 155 | //////////////////////////////////////////////////////////////////////////////////////////////////////// 156 | #[derive(BorshSerialize, BorshDeserialize)] 157 | pub struct SwapInstructionBaseIn { 158 | pub amount_in: u64, 159 | pub minimum_amount_out: u64, 160 | } 161 | 162 | pub const BIN_ARRAY_BITMAP_SEED: &[u8] = b"bitmap"; 163 | pub const BIN_ARRAY: &[u8] = b"bin_array"; 164 | pub const MAX_BIN_PER_ARRAY: usize = 70; 165 | 166 | pub fn derive_bin_array_bitmap_extension(lb_pair: Pubkey, amm_program: Pubkey) -> (Pubkey, u8) { 167 | let result = Pubkey::find_program_address(&[BIN_ARRAY_BITMAP_SEED, lb_pair.as_ref()], &amm_program); 168 | return result; 169 | } 170 | 171 | pub fn derive_bin_array_pda(lb_pair: Pubkey, bin_array_index: i64, amm_program: Pubkey) -> (Pubkey, u8) { 172 | let result = Pubkey::find_program_address( 173 | &[BIN_ARRAY, lb_pair.as_ref(), &bin_array_index.to_le_bytes()], 174 | &amm_program, 175 | ); 176 | return result 177 | } 178 | 179 | /// Get bin array index from bin id 180 | pub fn bin_id_to_bin_array_index(bin_id: i32) -> Result { 181 | let (idx, rem) = bin_id.div_rem(&(MAX_BIN_PER_ARRAY as i32)); 182 | 183 | if bin_id.is_negative() && rem != 0 { 184 | Ok(idx.checked_sub(1).unwrap()) 185 | } else { 186 | Ok(idx) 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/transactions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod create_transaction; 2 | pub mod meteoradlmm_swap; 3 | pub mod orca_whirpools_swap; 4 | pub mod raydium_swap; 5 | pub mod utils; -------------------------------------------------------------------------------- /src/transactions/raydium_swap.rs: -------------------------------------------------------------------------------- 1 | //Taken here: https://github.com/MeteoraAg/dlmm-sdk/blob/main/cli/src/instructions/swap.rs 2 | 3 | use anchor_client::solana_client::rpc_config::RpcSendTransactionConfig; 4 | use anchor_client::{solana_sdk::pubkey::Pubkey, solana_sdk::signer::Signer}; 5 | use anchor_spl::associated_token::get_associated_token_address; 6 | 7 | use anchor_spl::token::spl_token; 8 | use borsh::BorshDeserialize; 9 | // use anyhow::*; 10 | 11 | use log::{info, error}; 12 | use raydium_amm::instruction::swap_base_in; 13 | use solana_client::rpc_client::RpcClient; 14 | use solana_sdk::instruction::Instruction; 15 | use solana_sdk::signature::read_keypair_file; 16 | use spl_associated_token_account::instruction::create_associated_token_account; 17 | 18 | use crate::common::constants::Env; 19 | use crate::common::utils::from_str; 20 | use crate::markets::raydium::AmmInfo; 21 | use crate::markets::types::DexLabel; 22 | use crate::transactions::create_transaction::{InstructionDetails, MarketInfos}; 23 | 24 | use super::utils::get_keys_for_market; 25 | 26 | #[derive(Debug, Clone)] 27 | pub struct SwapParametersRaydium { 28 | pub pool: Pubkey, 29 | pub input_token_mint: Pubkey, 30 | pub output_token_mint: Pubkey, 31 | pub amount_in: u64, 32 | pub swap_for_y: bool, 33 | pub min_amount_out: u64 34 | } 35 | // Function are imported from Raydium library, you can see here: 36 | // https://github.com/raydium-io/raydium-library 37 | pub fn construct_raydium_instructions(params: SwapParametersRaydium) -> Vec { 38 | let SwapParametersRaydium { 39 | pool, 40 | input_token_mint, 41 | output_token_mint, 42 | amount_in, 43 | swap_for_y, 44 | min_amount_out 45 | } = params; 46 | // info!("RAYDIUM CRAFT SWAP INSTRUCTION !"); 47 | 48 | let mut swap_instructions: Vec = Vec::new(); 49 | let env = Env::new(); 50 | let payer = read_keypair_file(env.payer_keypair_path).expect("Wallet keypair file not found"); 51 | 52 | let amm_program = from_str("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8").unwrap(); 53 | //Devnet : HWy1jotHpo6UqeQxx49dpYYdQB8wj9Qk9MdxwjLvDHB8 54 | //Mainnet : 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 55 | 56 | let rpc_client: RpcClient = RpcClient::new(env.rpc_url); 57 | let pool_account: solana_sdk::account::Account = rpc_client.get_account(&pool).unwrap(); 58 | // println!("Params data length: {:?}", pool_account.data.len()); 59 | let pool_state = AmmInfo::try_from_slice(&pool_account.data).unwrap(); 60 | // println!("min_amount_out: {:?}", min_amount_out); 61 | // println!("Params: {:?}", params); 62 | // println!("Pool State: {:?}", pool_state); 63 | 64 | let authority = raydium_amm::processor::Processor::authority_id( 65 | &amm_program, 66 | raydium_amm::processor::AUTHORITY_AMM, 67 | pool_state.nonce as u8, 68 | ).unwrap(); 69 | 70 | // load market keys 71 | let market_keys = get_keys_for_market( 72 | &rpc_client, 73 | &pool_state.market_program, 74 | &pool_state.market, 75 | ).unwrap(); 76 | 77 | let pda_user_source = get_associated_token_address( 78 | &payer.pubkey(), 79 | &input_token_mint, 80 | ); 81 | match rpc_client.get_account(&pda_user_source) { 82 | Ok(account) => {} 83 | Err(error) => { 84 | // error!("❌ PDA not exist for {}", input_token_mint); 85 | } 86 | } 87 | 88 | let pda_user_destination = get_associated_token_address( 89 | &payer.pubkey(), 90 | &output_token_mint, 91 | ); 92 | 93 | match rpc_client.get_account(&pda_user_destination) { 94 | Ok(account) => {} 95 | Err(error) => { 96 | // error!("❌ PDA not exist for {}", output_token_mint); 97 | } 98 | } 99 | 100 | let amm_target_orders = from_str("9DCxsMizn3H1hprZ7xWe6LDzeUeZBksYFpBWBtSf1PQX").unwrap(); 101 | 102 | let swap_instruction = swap_base_in( 103 | &amm_program, 104 | &pool, 105 | &authority, 106 | &pool_state.open_orders, 107 | &amm_target_orders, 108 | &pool_state.coin_vault, 109 | &pool_state.pc_vault, 110 | &pool_state.market_program, 111 | &pool_state.market, 112 | &market_keys.bids, 113 | &market_keys.asks, 114 | &market_keys.event_q, 115 | &market_keys.coin_vault, 116 | &market_keys.pc_vault, 117 | &market_keys.vault_signer_key, 118 | &pda_user_source, 119 | &pda_user_destination, 120 | &payer.pubkey(), 121 | amount_in, 122 | min_amount_out, 123 | ).expect("Error in Raydium swap instruction construction"); 124 | 125 | // println!("DATA: {:?}", swap_instruction.data); 126 | swap_instructions.push(InstructionDetails{ 127 | instruction: swap_instruction, 128 | details: "Raydium Swap Instruction".to_string(), 129 | market: Some(MarketInfos{dex_label: DexLabel::RAYDIUM, address: pool }) 130 | }); 131 | 132 | return (swap_instructions); 133 | } 134 | -------------------------------------------------------------------------------- /src/transactions/utils.rs: -------------------------------------------------------------------------------- 1 | use log::{error, info}; 2 | use solana_client::rpc_client::RpcClient; 3 | use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Signature}; 4 | use anyhow::{format_err, Result}; 5 | use std::{ 6 | borrow::Cow, 7 | convert::{identity, TryFrom}, 8 | mem::size_of, 9 | thread::{self, sleep}, time::{self, Duration, Instant}, 10 | }; 11 | use safe_transmute::{ 12 | to_bytes::{transmute_one_to_bytes, transmute_to_bytes}, 13 | transmute_many_pedantic, transmute_one_pedantic, 14 | }; 15 | use serum_dex::state::{gen_vault_signer_key, AccountFlag, Market, MarketState, MarketStateV2}; 16 | 17 | use crate::common::constants::Env; 18 | 19 | use super::create_transaction::ChainType; 20 | 21 | 22 | pub async fn check_tx_status(commitment_config: CommitmentConfig, chain: ChainType ,signature: Signature) -> Result { 23 | let env = Env::new(); 24 | let rpc_url = if chain.clone() == ChainType::Mainnet { env.rpc_url } else { env.devnet_rpc_url }; 25 | let rpc_client: RpcClient = RpcClient::new(rpc_url); 26 | 27 | let start = Instant::now(); 28 | let mut counter = 0; 29 | loop { 30 | let confirmed = rpc_client.confirm_transaction(&signature)?; 31 | info!("Is confirmed? {:?}", confirmed); 32 | 33 | let status = rpc_client.get_signature_status_with_commitment_and_history(&signature, commitment_config, true)?; 34 | println!("Status: {:?}", status); 35 | 36 | let seventy_secs = Duration::from_secs(11); 37 | if confirmed { 38 | info!("✅ Transaction Confirmed with Confirmation"); 39 | info!("Status {:?}", status.clone().unwrap()); 40 | return Ok(true); 41 | } 42 | if status.is_some() { 43 | info!("✅ Transaction Confirmed with Status"); 44 | return Ok(true); 45 | } 46 | if start.elapsed() >= seventy_secs { 47 | error!("❌ Transaction not confirmed"); 48 | return Ok(false); 49 | } 50 | let ten_secs = Duration::from_secs(10); 51 | info!("⏳ {} seconds...", 10 * counter); 52 | sleep(ten_secs); 53 | counter += 1; 54 | } 55 | } 56 | 57 | #[cfg(target_endian = "little")] 58 | fn remove_dex_account_padding<'a>(data: &'a [u8]) -> Result> { 59 | use serum_dex::state::{ACCOUNT_HEAD_PADDING, ACCOUNT_TAIL_PADDING}; 60 | let head = &data[..ACCOUNT_HEAD_PADDING.len()]; 61 | if data.len() < ACCOUNT_HEAD_PADDING.len() + ACCOUNT_TAIL_PADDING.len() { 62 | return Err(format_err!( 63 | "dex account length {} is too small to contain valid padding", 64 | data.len() 65 | )); 66 | } 67 | if head != ACCOUNT_HEAD_PADDING { 68 | return Err(format_err!("dex account head padding mismatch")); 69 | } 70 | let tail = &data[data.len() - ACCOUNT_TAIL_PADDING.len()..]; 71 | if tail != ACCOUNT_TAIL_PADDING { 72 | return Err(format_err!("dex account tail padding mismatch")); 73 | } 74 | let inner_data_range = ACCOUNT_HEAD_PADDING.len()..(data.len() - ACCOUNT_TAIL_PADDING.len()); 75 | let inner: &'a [u8] = &data[inner_data_range]; 76 | let words: Cow<'a, [u64]> = match transmute_many_pedantic::(inner) { 77 | Ok(word_slice) => Cow::Borrowed(word_slice), 78 | Err(transmute_error) => { 79 | let word_vec = transmute_error.copy().map_err(|e| e.without_src())?; 80 | Cow::Owned(word_vec) 81 | } 82 | }; 83 | Ok(words) 84 | } 85 | 86 | #[cfg(target_endian = "little")] 87 | pub fn get_keys_for_market<'a>( 88 | client: &'a RpcClient, 89 | program_id: &'a Pubkey, 90 | market: &'a Pubkey, 91 | ) -> Result { 92 | 93 | let account_data: Vec = client.get_account_data(&market)?; 94 | let words: Cow<[u64]> = remove_dex_account_padding(&account_data)?; 95 | let market_state: MarketState = { 96 | let account_flags = Market::account_flags(&account_data)?; 97 | if account_flags.intersects(AccountFlag::Permissioned) { 98 | // println!("MarketStateV2"); 99 | let state = transmute_one_pedantic::(transmute_to_bytes(&words)) 100 | .map_err(|e| e.without_src())?; 101 | state.check_flags(true)?; 102 | state.inner 103 | } else { 104 | // println!("MarketStateV"); 105 | let state = transmute_one_pedantic::(transmute_to_bytes(&words)) 106 | .map_err(|e| e.without_src())?; 107 | state.check_flags(true)?; 108 | state 109 | } 110 | }; 111 | let vault_signer_key = 112 | gen_vault_signer_key(market_state.vault_signer_nonce, market, program_id)?; 113 | assert_eq!( 114 | transmute_to_bytes(&identity(market_state.own_address)), 115 | market.as_ref() 116 | ); 117 | Ok(MarketPubkeys { 118 | market: Box::new(*market), 119 | req_q: Box::new( 120 | Pubkey::try_from(transmute_one_to_bytes(&identity(market_state.req_q))).unwrap(), 121 | ), 122 | event_q: Box::new( 123 | Pubkey::try_from(transmute_one_to_bytes(&identity(market_state.event_q))).unwrap(), 124 | ), 125 | bids: Box::new( 126 | Pubkey::try_from(transmute_one_to_bytes(&identity(market_state.bids))).unwrap(), 127 | ), 128 | asks: Box::new( 129 | Pubkey::try_from(transmute_one_to_bytes(&identity(market_state.asks))).unwrap(), 130 | ), 131 | coin_vault: Box::new( 132 | Pubkey::try_from(transmute_one_to_bytes(&identity(market_state.coin_vault))).unwrap(), 133 | ), 134 | pc_vault: Box::new( 135 | Pubkey::try_from(transmute_one_to_bytes(&identity(market_state.pc_vault))).unwrap(), 136 | ), 137 | vault_signer_key: Box::new(vault_signer_key), 138 | coin_mint: Box::new( 139 | Pubkey::try_from(transmute_one_to_bytes(&identity(market_state.coin_mint))).unwrap(), 140 | ), 141 | pc_mint: Box::new( 142 | Pubkey::try_from(transmute_one_to_bytes(&identity(market_state.pc_mint))).unwrap(), 143 | ), 144 | coin_lot_size: market_state.coin_lot_size, 145 | pc_lot_size: market_state.pc_lot_size, 146 | }) 147 | } 148 | 149 | #[derive(Debug)] 150 | pub struct MarketPubkeys { 151 | pub market: Box, 152 | pub req_q: Box, 153 | pub event_q: Box, 154 | pub bids: Box, 155 | pub asks: Box, 156 | pub coin_vault: Box, 157 | pub pc_vault: Box, 158 | pub vault_signer_key: Box, 159 | pub coin_mint: Box, 160 | pub pc_mint: Box, 161 | pub coin_lot_size: u64, 162 | pub pc_lot_size: u64, 163 | } 164 | 165 | pub fn average(numbers: Vec) -> u64 { 166 | let sum: u64 = numbers.iter().sum(); 167 | let count = numbers.len() as u64; 168 | sum / count 169 | } --------------------------------------------------------------------------------