├── .env.sample ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── package.json └── programs └── dex-solana ├── Cargo.toml ├── Xargo.toml └── src ├── adapters ├── aldrin.rs ├── common.rs ├── fluxbeam.rs ├── lifinity.rs ├── meteora.rs ├── mod.rs ├── obric_v2.rs ├── openbookv2.rs ├── phoenix.rs ├── pumpfun.rs ├── raydium.rs ├── sanctum.rs ├── spl_token_swap.rs ├── stable_swap.rs └── whirlpool.rs ├── constants.rs ├── error.rs ├── instructions ├── commission_from_swap.rs ├── commission_proxy_swap.rs ├── commission_swap.rs ├── common.rs ├── from_swap.rs ├── mod.rs ├── proxy_swap.rs └── swap.rs ├── lib.rs └── utils ├── mod.rs └── token.rs /.env.sample: -------------------------------------------------------------------------------- 1 | FIGMENT_KEY= 2 | PRIVATE_KEY= 3 | RPC= 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | .yarn 9 | .env 10 | .vscode 11 | yarn.lock 12 | pk 13 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | anchor_version = "0.30.0" 3 | 4 | [features] 5 | resolution = true 6 | seeds = false 7 | skip-lint = false 8 | 9 | [programs.localnet] 10 | dex_solana = "6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma" 11 | 12 | [registry] 13 | url = "https://api.apr.dev" 14 | 15 | [provider] 16 | cluster = "Localnet" 17 | wallet = "PATH_TO_YOUR_WALLET" -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver="2" 3 | members = [ 4 | "programs/*" 5 | ] 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SOLANA SOR SmartContract 2 | 3 | ## Overview 4 | 5 | The SOLANA SOR (Smart Order Router) SmartContract repository implements a decentralized exchange (DEX) on the Solana blockchain. This project leverages the Solana Program Library (SPL) and Anchor framework to provide robust and efficient token swap functionality. 6 | 7 | ## Repository Structure 8 | 9 | - `programs/`: Contains the Solana smart contracts (on-chain programs) for the DEX. 10 | - `src/`: 11 | - **adapters/**: Provides abstraction layers for integrating various external protocols and utilities into the DEX, ensuring a modular and extensible architecture. 12 | - **instructions/**: Contains the core program logic, handling essential DEX functionalities such as token swaps, liquidity management, and fee calculations. 13 | - **utils/**: Includes utility functions and helper methods used across the program. 14 | - `Anchor.toml`: Configuration file for Anchor projects. 15 | - `Cargo.toml`: Rust project configuration file. 16 | - `package.json`: Contains dependencies and scripts for JavaScript-based tests or utilities. 17 | 18 | ## Key Features 19 | 20 | - **Split Trading**: The DexRouter allows for split trading, enabling users to execute trades across multiple liquidity sources. 21 | - **Security**: The contracts have been audited by okx inner audit team. 22 | - **Extensible Architecture**: Easily integrates with other Solana-based programs and tokens. 23 | - **Anchor Framework**: Utilizes Anchor for seamless program development and deployment. 24 | - **High Performance**: Built on Solana, ensuring high throughput and low latency. 25 | 26 | 27 | ## Prerequisites 28 | 29 | - **Rust**: Install Rust from [rustup.rs](https://rustup.rs/). 30 | - **Solana CLI**: Install the Solana command-line tools from [Solana CLI Documentation](https://docs.solana.com/cli/install-solana-cli-tools) (Recommended version: **1.18.26** for compatibility). 31 | - **Anchor Framework**: Install Anchor by running: 32 | 33 | ```bash 34 | cargo install --git https://github.com/coral-xyz/anchor avm --force 35 | ``` 36 | 37 | Alternatively, follow the installation guide from the [Solana Documentation](https://solana.com/docs/intro/installation) to ensure compatibility and proper setup.(Recommended version: **0.30.0** and **0.30.1** for compatibility) 38 | - **Node.js** and **npm** (or **yarn**) for JavaScript utilities. 39 | 40 | --- 41 | 42 | ## Installation and Usage 43 | 44 | 1. **Clone the Repository**: 45 | ```bash 46 | git clone https://github.com/okx/WEB3-DEX-SOLANA-OPENSOURCE.git 47 | cd WEB3-DEX-SOLANA-OPENSOURCE 48 | ``` 49 | 50 | 2. **Install Dependencies**: 51 | - With Yarn: 52 | ```bash 53 | yarn install 54 | ``` 55 | 56 | - With npm: 57 | ```bash 58 | npm install 59 | ``` 60 | 61 | 3. **Build the Project**: 62 | Use Anchor to build the smart contracts: 63 | ```bash 64 | anchor build 65 | ``` 66 | 67 | 4. **Deploy the Smart Contracts**: 68 | Configure your `Anchor.toml` with the appropriate cluster URL and deploy: 69 | ```bash 70 | anchor deploy 71 | ``` 72 | 73 | 5. **Run Tests**: 74 | Use Anchor's test suite to ensure functionality: 75 | ```bash 76 | anchor test 77 | ``` 78 | 79 | --- 80 | 81 | # Contributing 82 | 83 | There are several ways you can contribute to the SOR SmartContract project: 84 | 85 | ## Ways to Contribute 86 | 87 | ### Join Community Discussions 88 | Join our [Discord community](https://discord.gg/3N9PHeNn) to help other developers troubleshoot their integration issues and share your experience with the SOR SmartContract. Our Discord is the main hub for technical discussions, questions, and real-time support. 89 | 90 | ### Open an Issue 91 | - Open [issues](https://github.com/okx/WEB3-DEX-SOLANA-OPENSOURCE/issues) to suggest features or report minor bugs 92 | - Before opening a new issue, search existing issues to avoid duplicates 93 | - When requesting features, include details about use cases and potential impact 94 | 95 | ### Submit Pull Requests 96 | 1. Fork the repository 97 | 2. Create a feature branch 98 | 3. Make your changes 99 | 4. Run tests 100 | 5. Submit a pull request 101 | 102 | ### Pull Request Guidelines 103 | - Discuss non-trivial changes in an issue first 104 | - Include tests for new functionality 105 | - Update documentation as needed 106 | - Add a changelog entry describing your changes in the PR 107 | - PRs should be focused and preferably address a single concern 108 | 109 | ## First Time Contributors 110 | - Look for issues labeled "good first issue" 111 | - Read through our documentation 112 | - Set up your local development environment following the Installation guide 113 | 114 | ## Bug Bounty 115 | Help us enhance the security of the SOLANA SOR SmartContract by participating in our bug bounty program. Report vulnerabilities and earn rewards! Learn more and get started at [HackerOne](https://hackerone.com/okg/policy_scopes?type=team). 116 | 117 | ## Code Review Process 118 | 1. A maintainer will review your PR 119 | 2. Address any requested changes 120 | 3. Once approved, your PR will be merged 121 | 122 | ## Questions? 123 | - Open a discussion [issue](https://github.com/okx/WEB3-DEX-SOLANA-OPENSOURCE/issues) for general questions 124 | - Join our [community](https://discord.gg/okxdexapi) for real-time discussions 125 | - Review existing issues and discussions 126 | 127 | Thank you for contributing to the SOLANA SOR SmartContract repo! 128 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 4 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 5 | }, 6 | "dependencies": { 7 | "@coral-xyz/anchor": "^0.30.0", 8 | "@coral-xyz/borsh": "^0.30.1", 9 | "@meteora-ag/dlmm": "^1.0.45", 10 | "@raydium-io/raydium-sdk": "^1.3.1-beta.51", 11 | "@solana/buffer-layout": "^4.0.1", 12 | "@solana/spl-token": "^0.4.1", 13 | "anchor-bankrun": "^0.4.0", 14 | "base58": "^2.0.1", 15 | "decimal.js": "^10.4.3", 16 | "dotenv": "^16.4.5", 17 | "solana-bankrun": "^0.3.0", 18 | "stdio": "^2.1.3" 19 | }, 20 | "devDependencies": { 21 | "@types/bn.js": "^5.1.0", 22 | "@types/chai": "^4.3.0", 23 | "@types/mocha": "^9.0.0", 24 | "chai": "^4.3.4", 25 | "mocha": "^9.0.3", 26 | "prettier": "^2.6.2", 27 | "ts-mocha": "^10.0.0", 28 | "typescript": "^4.3.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /programs/dex-solana/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dex-solana" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "dex_solana" 10 | 11 | [features] 12 | no-entrypoint = [] 13 | no-idl = [] 14 | no-log-ix-name = [] 15 | cpi = ["no-entrypoint"] 16 | default = [] 17 | idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"] 18 | 19 | 20 | [dependencies] 21 | anchor-lang = { version = "0.30.0", features = ["init-if-needed"] } 22 | anchor-spl = "0.30.0" 23 | arrayref = "0.3.7" 24 | num_enum = "0.7.2" 25 | bytemuck = "^1" -------------------------------------------------------------------------------- /programs/dex-solana/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/aldrin.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::{aldrin_v1_program, aldrin_v2_program, HopAccounts, SWAP_SELECTOR}; 4 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 5 | use anchor_spl::token::Token; 6 | use anchor_spl::token_interface::{Mint, TokenAccount}; 7 | use arrayref::array_ref; 8 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 9 | 10 | use super::common::DexProcessor; 11 | 12 | #[derive( 13 | AnchorDeserialize, AnchorSerialize, Clone, Debug, PartialEq, TryFromPrimitive, IntoPrimitive, 14 | )] 15 | #[repr(u8)] 16 | pub enum Side { 17 | Bid = 0, 18 | Ask = 1, 19 | } 20 | 21 | const ARGS_LEN: usize = 25; 22 | 23 | pub struct AldrinProcessor; 24 | impl DexProcessor for AldrinProcessor {} 25 | 26 | pub struct AldrinSwapAccountsV1<'info> { 27 | pub dex_program_id: &'info AccountInfo<'info>, 28 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 29 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 30 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 31 | 32 | pub pool_info: &'info AccountInfo<'info>, 33 | pub pool_authority: &'info AccountInfo<'info>, 34 | pub pool_mint: InterfaceAccount<'info, Mint>, 35 | pub pool_coin_token_vault: InterfaceAccount<'info, TokenAccount>, 36 | pub pool_pc_token_vault: InterfaceAccount<'info, TokenAccount>, 37 | pub pool_fee_account: InterfaceAccount<'info, TokenAccount>, 38 | pub token_program: Program<'info, Token>, 39 | } 40 | const V1_ACCOUNTS_LEN: usize = 11; 41 | 42 | pub struct AldrinSwapAccountsV2<'info> { 43 | pub dex_program_id: &'info AccountInfo<'info>, 44 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 45 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 46 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 47 | 48 | pub pool_info: &'info AccountInfo<'info>, 49 | pub pool_authority: &'info AccountInfo<'info>, 50 | pub pool_mint: InterfaceAccount<'info, Mint>, 51 | pub pool_coin_token_vault: InterfaceAccount<'info, TokenAccount>, 52 | pub pool_pc_token_vault: InterfaceAccount<'info, TokenAccount>, 53 | pub pool_fee_account: InterfaceAccount<'info, TokenAccount>, 54 | pub pool_curve: &'info AccountInfo<'info>, 55 | pub token_program: Program<'info, Token>, 56 | } 57 | const V2_ACCOUNTS_LEN: usize = 12; 58 | 59 | impl<'info> AldrinSwapAccountsV1<'info> { 60 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 61 | let [ 62 | dex_program_id, 63 | swap_authority_pubkey, 64 | swap_source_token, 65 | swap_destination_token, 66 | pool_info, 67 | pool_authority, 68 | pool_mint, 69 | pool_coin_token_vault, 70 | pool_pc_token_vault, 71 | pool_fee_account, 72 | token_program, 73 | ]: & [AccountInfo<'info>; V1_ACCOUNTS_LEN] = array_ref![accounts, offset, V1_ACCOUNTS_LEN]; 74 | Ok(Self { 75 | dex_program_id, 76 | swap_authority_pubkey, 77 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 78 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 79 | pool_info, 80 | pool_authority, 81 | pool_mint: InterfaceAccount::try_from(pool_mint)?, 82 | pool_coin_token_vault: InterfaceAccount::try_from(pool_coin_token_vault)?, 83 | pool_pc_token_vault: InterfaceAccount::try_from(pool_pc_token_vault)?, 84 | pool_fee_account: InterfaceAccount::try_from(pool_fee_account)?, 85 | token_program: Program::try_from(token_program)?, 86 | }) 87 | } 88 | } 89 | 90 | impl<'info> AldrinSwapAccountsV2<'info> { 91 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 92 | let [ 93 | dex_program_id, 94 | swap_authority_pubkey, 95 | swap_source_token, 96 | swap_destination_token, 97 | pool_info, 98 | pool_authority, 99 | pool_mint, 100 | pool_coin_token_vault, 101 | pool_pc_token_vault, 102 | pool_fee_account, 103 | pool_curve, 104 | token_program, 105 | ]: & [AccountInfo<'info>; V2_ACCOUNTS_LEN] = array_ref![accounts, offset, V2_ACCOUNTS_LEN]; 106 | Ok(Self { 107 | dex_program_id, 108 | swap_authority_pubkey, 109 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 110 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 111 | pool_info, 112 | pool_authority, 113 | pool_mint: InterfaceAccount::try_from(pool_mint)?, 114 | pool_coin_token_vault: InterfaceAccount::try_from(pool_coin_token_vault)?, 115 | pool_pc_token_vault: InterfaceAccount::try_from(pool_pc_token_vault)?, 116 | pool_fee_account: InterfaceAccount::try_from(pool_fee_account)?, 117 | pool_curve, 118 | token_program: Program::try_from(token_program)?, 119 | }) 120 | } 121 | } 122 | 123 | pub fn swap_v1<'a>( 124 | remaining_accounts: &'a [AccountInfo<'a>], 125 | amount_in: u64, 126 | offset: &mut usize, 127 | hop_accounts: &mut HopAccounts, 128 | hop: usize, 129 | proxy_swap: bool, 130 | ) -> Result { 131 | msg!( 132 | "Dex::AldrinSwapV1 amount_in: {}, offset: {}", 133 | amount_in, 134 | offset 135 | ); 136 | require!( 137 | remaining_accounts.len() >= *offset + V1_ACCOUNTS_LEN, 138 | ErrorCode::InvalidAccountsLength 139 | ); 140 | let mut swap_accounts = AldrinSwapAccountsV1::parse_accounts(remaining_accounts, *offset)?; 141 | if swap_accounts.dex_program_id.key != &aldrin_v1_program::id() { 142 | return Err(ErrorCode::InvalidProgramId.into()); 143 | } 144 | 145 | // check hop accounts & swap authority 146 | let swap_source_token = swap_accounts.swap_source_token.key(); 147 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 148 | before_check( 149 | &swap_accounts.swap_authority_pubkey, 150 | swap_source_token, 151 | swap_destination_token, 152 | hop_accounts, 153 | hop, 154 | proxy_swap, 155 | )?; 156 | 157 | let side; 158 | if swap_accounts.swap_source_token.mint == swap_accounts.pool_coin_token_vault.mint { 159 | side = Side::Ask; 160 | } else { 161 | side = Side::Bid; 162 | } 163 | let mut data = Vec::with_capacity(ARGS_LEN); 164 | data.extend_from_slice(SWAP_SELECTOR); 165 | data.extend_from_slice(&amount_in.to_le_bytes()); 166 | data.extend_from_slice(&1u64.to_le_bytes()); 167 | data.push(Side::into(side)); 168 | 169 | let (user_coin_token_acc, user_pc_token_acc) = if swap_accounts.swap_source_token.mint 170 | == swap_accounts.pool_coin_token_vault.mint 171 | && swap_accounts.swap_destination_token.mint == swap_accounts.pool_pc_token_vault.mint 172 | { 173 | (swap_source_token, swap_destination_token) 174 | } else if swap_accounts.swap_source_token.mint == swap_accounts.pool_pc_token_vault.mint 175 | && swap_accounts.swap_destination_token.mint == swap_accounts.pool_coin_token_vault.mint 176 | { 177 | (swap_destination_token, swap_source_token) 178 | } else { 179 | return Err(ErrorCode::InvalidTokenMint.into()); 180 | }; 181 | 182 | let accounts = vec![ 183 | AccountMeta::new_readonly(swap_accounts.pool_info.key(), false), 184 | AccountMeta::new_readonly(swap_accounts.pool_authority.key(), false), 185 | AccountMeta::new(swap_accounts.pool_mint.key(), false), 186 | AccountMeta::new(swap_accounts.pool_coin_token_vault.key(), false), 187 | AccountMeta::new(swap_accounts.pool_pc_token_vault.key(), false), 188 | AccountMeta::new(swap_accounts.pool_fee_account.key(), false), 189 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 190 | AccountMeta::new(user_coin_token_acc, false), 191 | AccountMeta::new(user_pc_token_acc, false), 192 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 193 | ]; 194 | 195 | let account_infos = vec![ 196 | swap_accounts.pool_info.to_account_info(), 197 | swap_accounts.pool_authority.to_account_info(), 198 | swap_accounts.pool_mint.to_account_info(), 199 | swap_accounts.pool_coin_token_vault.to_account_info(), 200 | swap_accounts.pool_pc_token_vault.to_account_info(), 201 | swap_accounts.pool_fee_account.to_account_info(), 202 | swap_accounts.swap_authority_pubkey.to_account_info(), 203 | swap_accounts.swap_source_token.to_account_info(), 204 | swap_accounts.swap_destination_token.to_account_info(), 205 | swap_accounts.token_program.to_account_info(), 206 | ]; 207 | 208 | let instruction = Instruction { 209 | program_id: swap_accounts.dex_program_id.key(), 210 | accounts, 211 | data, 212 | }; 213 | 214 | let dex_processor = &AldrinProcessor; 215 | let amount_out = invoke_process( 216 | dex_processor, 217 | &account_infos, 218 | swap_source_token, 219 | &mut swap_accounts.swap_destination_token, 220 | hop_accounts, 221 | instruction, 222 | hop, 223 | offset, 224 | V1_ACCOUNTS_LEN, 225 | proxy_swap, 226 | )?; 227 | Ok(amount_out) 228 | } 229 | 230 | pub fn swap_v2<'a>( 231 | remaining_accounts: &'a [AccountInfo<'a>], 232 | amount_in: u64, 233 | offset: &mut usize, 234 | hop_accounts: &mut HopAccounts, 235 | hop: usize, 236 | proxy_swap: bool, 237 | ) -> Result { 238 | msg!( 239 | "Dex::AldrinSwapV2 amount_in: {}, offset: {}", 240 | amount_in, 241 | offset 242 | ); 243 | require!( 244 | remaining_accounts.len() >= *offset + V2_ACCOUNTS_LEN, 245 | ErrorCode::InvalidAccountsLength 246 | ); 247 | let mut swap_accounts = AldrinSwapAccountsV2::parse_accounts(remaining_accounts, *offset)?; 248 | if swap_accounts.dex_program_id.key != &aldrin_v2_program::id() { 249 | return Err(ErrorCode::InvalidProgramId.into()); 250 | } 251 | 252 | // check hop accounts & swap authority 253 | let swap_source_token = swap_accounts.swap_source_token.key(); 254 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 255 | before_check( 256 | &swap_accounts.swap_authority_pubkey, 257 | swap_source_token, 258 | swap_destination_token, 259 | hop_accounts, 260 | hop, 261 | proxy_swap, 262 | )?; 263 | 264 | let side; 265 | if swap_accounts.swap_source_token.mint == swap_accounts.pool_coin_token_vault.mint { 266 | side = Side::Ask; 267 | } else { 268 | side = Side::Bid; 269 | } 270 | let mut data = Vec::with_capacity(ARGS_LEN); 271 | data.extend_from_slice(SWAP_SELECTOR); 272 | data.extend_from_slice(&amount_in.to_le_bytes()); 273 | data.extend_from_slice(&1u64.to_le_bytes()); 274 | data.push(Side::into(side)); 275 | 276 | let (user_coin_token_acc, user_pc_token_acc) = if swap_accounts.swap_source_token.mint 277 | == swap_accounts.pool_coin_token_vault.mint 278 | && swap_accounts.swap_destination_token.mint == swap_accounts.pool_pc_token_vault.mint 279 | { 280 | (swap_source_token, swap_destination_token) 281 | } else if swap_accounts.swap_source_token.mint == swap_accounts.pool_pc_token_vault.mint 282 | && swap_accounts.swap_destination_token.mint == swap_accounts.pool_coin_token_vault.mint 283 | { 284 | (swap_destination_token, swap_source_token) 285 | } else { 286 | return Err(ErrorCode::InvalidTokenMint.into()); 287 | }; 288 | 289 | let accounts = vec![ 290 | AccountMeta::new_readonly(swap_accounts.pool_info.key(), false), 291 | AccountMeta::new_readonly(swap_accounts.pool_authority.key(), false), 292 | AccountMeta::new(swap_accounts.pool_mint.key(), false), 293 | AccountMeta::new(swap_accounts.pool_coin_token_vault.key(), false), 294 | AccountMeta::new(swap_accounts.pool_pc_token_vault.key(), false), 295 | AccountMeta::new(swap_accounts.pool_fee_account.key(), false), 296 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 297 | AccountMeta::new(user_coin_token_acc, false), 298 | AccountMeta::new(user_pc_token_acc, false), 299 | AccountMeta::new_readonly(swap_accounts.pool_curve.key(), false), 300 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 301 | ]; 302 | 303 | let account_infos = vec![ 304 | swap_accounts.pool_info.to_account_info(), 305 | swap_accounts.pool_authority.to_account_info(), 306 | swap_accounts.pool_mint.to_account_info(), 307 | swap_accounts.pool_coin_token_vault.to_account_info(), 308 | swap_accounts.pool_pc_token_vault.to_account_info(), 309 | swap_accounts.pool_fee_account.to_account_info(), 310 | swap_accounts.swap_authority_pubkey.to_account_info(), 311 | swap_accounts.swap_source_token.to_account_info(), 312 | swap_accounts.swap_destination_token.to_account_info(), 313 | swap_accounts.pool_curve.to_account_info(), 314 | swap_accounts.token_program.to_account_info(), 315 | ]; 316 | 317 | let instruction = Instruction { 318 | program_id: swap_accounts.dex_program_id.key(), 319 | accounts, 320 | data, 321 | }; 322 | 323 | let dex_processor = &AldrinProcessor; 324 | let amount_out = invoke_process( 325 | dex_processor, 326 | &account_infos, 327 | swap_source_token, 328 | &mut swap_accounts.swap_destination_token, 329 | hop_accounts, 330 | instruction, 331 | hop, 332 | offset, 333 | V2_ACCOUNTS_LEN, 334 | proxy_swap, 335 | )?; 336 | Ok(amount_out) 337 | } 338 | 339 | #[cfg(test)] 340 | mod tests { 341 | use super::*; 342 | 343 | #[test] 344 | pub fn test_pack_swap_instruction() { 345 | let amount_in = 100u64; 346 | let mut data = Vec::with_capacity(ARGS_LEN); 347 | data.extend_from_slice(SWAP_SELECTOR); 348 | data.extend_from_slice(&amount_in.to_le_bytes()); 349 | data.extend_from_slice(&99u64.to_le_bytes()); 350 | data.push(Side::Ask.into()); 351 | 352 | msg!("data.len: {}", data.len()); 353 | assert!(data.len() == ARGS_LEN); 354 | assert!( 355 | data == vec![ 356 | 248, 198, 158, 145, 225, 117, 135, 200, 100, 0, 0, 0, 0, 0, 0, 0, 99, 0, 0, 0, 0, 357 | 0, 0, 0, 1 358 | ] 359 | ); 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/common.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ErrorCode; 2 | use crate::{authority_pda, HopAccounts, BUMP_SA, SEED_SA, ZERO_ADDRESS}; 3 | use anchor_lang::prelude::*; 4 | use anchor_lang::solana_program::{ 5 | instruction::Instruction, 6 | program::{invoke, invoke_signed}, 7 | }; 8 | use anchor_spl::token_interface::TokenAccount; 9 | 10 | pub trait DexProcessor { 11 | fn before_invoke(&self, _account_infos: &[AccountInfo]) -> Result { 12 | Ok(0) 13 | } 14 | 15 | fn after_invoke(&self, _account_infos: &[AccountInfo]) -> Result { 16 | Ok(0) 17 | } 18 | } 19 | 20 | 21 | pub fn before_check( 22 | swap_authority_pubkey: &AccountInfo, 23 | swap_source_token: Pubkey, 24 | swap_destination_token: Pubkey, 25 | hop_accounts: &mut HopAccounts, 26 | hop: usize, 27 | proxy_swap: bool, 28 | ) -> Result<()> { 29 | // check hop accounts 30 | if hop_accounts.from_account != ZERO_ADDRESS { 31 | require_keys_eq!( 32 | swap_source_token, 33 | hop_accounts.from_account, 34 | ErrorCode::InvalidHopAccounts 35 | ); 36 | require_keys_eq!( 37 | swap_destination_token, 38 | hop_accounts.to_account, 39 | ErrorCode::InvalidHopAccounts 40 | ); 41 | } 42 | if hop_accounts.last_to_account != ZERO_ADDRESS { 43 | require_keys_eq!( 44 | swap_source_token, 45 | hop_accounts.last_to_account, 46 | ErrorCode::InvalidHopFromAccount 47 | ); 48 | } 49 | 50 | // check swap authority 51 | if !proxy_swap && hop == 0 { 52 | require!( 53 | swap_authority_pubkey.is_signer, 54 | ErrorCode::SwapAuthorityIsNotSigner 55 | ); 56 | } else { 57 | require_keys_eq!( 58 | swap_authority_pubkey.key(), 59 | authority_pda::id(), 60 | ErrorCode::InvalidAuthorityPda 61 | ); 62 | } 63 | Ok(()) 64 | } 65 | 66 | pub fn invoke_process<'info, T: DexProcessor>( 67 | dex_processor: &T, 68 | account_infos: &[AccountInfo], 69 | swap_source_token: Pubkey, 70 | swap_destination_account: &mut InterfaceAccount<'info, TokenAccount>, 71 | hop_accounts: &mut HopAccounts, 72 | instruction: Instruction, 73 | hop: usize, 74 | offset: &mut usize, 75 | accounts_len: usize, 76 | proxy_swap: bool, 77 | ) -> Result { 78 | 79 | 80 | // check if pumpfun swap 81 | let before_destination_balance = swap_destination_account.amount; 82 | dex_processor.before_invoke(account_infos)?; 83 | if !proxy_swap && hop == 0 { 84 | invoke(&instruction, account_infos)?; 85 | } else { 86 | invoke_signed(&instruction, account_infos, &[&[SEED_SA, &[BUMP_SA]]])?; 87 | } 88 | 89 | // check if pumpfun swap 90 | dex_processor.after_invoke(account_infos)?; 91 | swap_destination_account.reload()?; 92 | let after_destination_balance = swap_destination_account.amount; 93 | *offset += accounts_len; 94 | hop_accounts.from_account = swap_source_token; 95 | hop_accounts.to_account = swap_destination_account.key(); 96 | let amount_out = after_destination_balance 97 | .checked_sub(before_destination_balance) 98 | .ok_or(ErrorCode::CalculationError)?; 99 | Ok(amount_out) 100 | } 101 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/fluxbeam.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::{flux_beam_program, HopAccounts}; 4 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 5 | use anchor_spl::token_interface::{Mint, Token2022, TokenAccount}; 6 | use arrayref::array_ref; 7 | 8 | use super::common::DexProcessor; 9 | 10 | const ARGS_LEN: usize = 17; 11 | 12 | pub struct FluxBeamProcessor; 13 | 14 | impl DexProcessor for FluxBeamProcessor {} 15 | 16 | pub struct FluxBeamAccounts<'info> { 17 | pub dex_program_id: &'info AccountInfo<'info>, 18 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 19 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 20 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 21 | 22 | pub swap_info: &'info AccountInfo<'info>, 23 | pub authority_acc_info: &'info AccountInfo<'info>, 24 | pub token_a_account: InterfaceAccount<'info, TokenAccount>, 25 | pub token_b_account: InterfaceAccount<'info, TokenAccount>, 26 | pub pool_mint: &'info AccountInfo<'info>, 27 | pub pool_fee: InterfaceAccount<'info, TokenAccount>, 28 | pub source_mint: InterfaceAccount<'info, Mint>, 29 | pub destination_mint: InterfaceAccount<'info, Mint>, 30 | pub source_token_program: &'info AccountInfo<'info>, 31 | pub destination_token_program: &'info AccountInfo<'info>, 32 | pub token_program_2022: Program<'info, Token2022>, 33 | } 34 | const ACCOUNTS_LEN: usize = 15; 35 | 36 | impl<'info> FluxBeamAccounts<'info> { 37 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 38 | let [ 39 | dex_program_id, 40 | swap_authority_pubkey, 41 | swap_source_token, 42 | swap_destination_token, 43 | swap_info, 44 | authority_acc_info, 45 | token_a_account, 46 | token_b_account, 47 | pool_mint, 48 | pool_fee, 49 | source_mint, 50 | destination_mint, 51 | source_token_program, 52 | destination_token_program, 53 | token_program_2022, 54 | ]: & [AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 55 | Ok(Self { 56 | dex_program_id, 57 | swap_authority_pubkey, 58 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 59 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 60 | swap_info, 61 | authority_acc_info, 62 | token_a_account: InterfaceAccount::try_from(token_a_account)?, 63 | token_b_account: InterfaceAccount::try_from(token_b_account)?, 64 | pool_mint, 65 | pool_fee: InterfaceAccount::try_from(pool_fee)?, 66 | source_mint: InterfaceAccount::try_from(source_mint)?, 67 | destination_mint: InterfaceAccount::try_from(destination_mint)?, 68 | source_token_program, 69 | destination_token_program, 70 | token_program_2022: Program::try_from(token_program_2022)?, 71 | }) 72 | } 73 | } 74 | 75 | pub fn swap<'a>( 76 | remaining_accounts: &'a [AccountInfo<'a>], 77 | amount_in: u64, 78 | offset: &mut usize, 79 | hop_accounts: &mut HopAccounts, 80 | hop: usize, 81 | proxy_swap: bool, 82 | ) -> Result { 83 | msg!("Dex::FluxBeam amount_in: {}, offset: {}", amount_in, offset); 84 | require!( 85 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 86 | ErrorCode::InvalidAccountsLength 87 | ); 88 | let mut swap_accounts = FluxBeamAccounts::parse_accounts(remaining_accounts, *offset)?; 89 | if swap_accounts.dex_program_id.key != &flux_beam_program::id() { 90 | return Err(ErrorCode::InvalidProgramId.into()); 91 | } 92 | 93 | // check hop accounts & swap authority 94 | let swap_source_token = swap_accounts.swap_source_token.key(); 95 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 96 | before_check( 97 | &swap_accounts.swap_authority_pubkey, 98 | swap_source_token, 99 | swap_destination_token, 100 | hop_accounts, 101 | hop, 102 | proxy_swap, 103 | )?; 104 | 105 | let pool_source_pubkey; 106 | let pool_destination_pubkeyy; 107 | if (swap_accounts.swap_source_token.mint == swap_accounts.token_a_account.mint) 108 | && (swap_accounts.swap_destination_token.mint == swap_accounts.token_b_account.mint) 109 | { 110 | pool_source_pubkey = swap_accounts.token_a_account.key(); 111 | pool_destination_pubkeyy = swap_accounts.token_b_account.key(); 112 | } else if (swap_accounts.swap_source_token.mint == swap_accounts.token_b_account.mint) 113 | && (swap_accounts.swap_destination_token.mint == swap_accounts.token_a_account.mint) 114 | { 115 | pool_source_pubkey = swap_accounts.token_b_account.key(); 116 | pool_destination_pubkeyy = swap_accounts.token_a_account.key(); 117 | } else { 118 | return Err(ErrorCode::InvalidPool.into()); 119 | } 120 | 121 | let mut data = Vec::with_capacity(ARGS_LEN); 122 | data.push(1); 123 | data.extend_from_slice(&amount_in.to_le_bytes()); 124 | data.extend_from_slice(&1u64.to_le_bytes()); 125 | 126 | let accounts = vec![ 127 | AccountMeta::new_readonly(swap_accounts.swap_info.key(), false), 128 | AccountMeta::new_readonly(swap_accounts.authority_acc_info.key(), false), 129 | AccountMeta::new(swap_accounts.swap_authority_pubkey.key(), true), 130 | AccountMeta::new(swap_source_token, false), 131 | AccountMeta::new(pool_source_pubkey, false), 132 | AccountMeta::new(pool_destination_pubkeyy, false), 133 | AccountMeta::new(swap_destination_token, false), 134 | AccountMeta::new(swap_accounts.pool_mint.key(), false), 135 | AccountMeta::new(swap_accounts.pool_fee.key(), false), 136 | AccountMeta::new_readonly(swap_accounts.source_mint.key(), false), 137 | AccountMeta::new_readonly(swap_accounts.destination_mint.key(), false), 138 | AccountMeta::new_readonly(swap_accounts.source_token_program.key(), false), 139 | AccountMeta::new_readonly(swap_accounts.destination_token_program.key(), false), 140 | AccountMeta::new_readonly(swap_accounts.token_program_2022.key(), false), 141 | ]; 142 | 143 | let account_infos = vec![ 144 | swap_accounts.swap_info.to_account_info(), 145 | swap_accounts.authority_acc_info.to_account_info(), 146 | swap_accounts.swap_authority_pubkey.to_account_info(), 147 | swap_accounts.swap_source_token.to_account_info(), 148 | swap_accounts.token_a_account.to_account_info(), 149 | swap_accounts.token_b_account.to_account_info(), 150 | swap_accounts.swap_destination_token.to_account_info(), 151 | swap_accounts.pool_mint.to_account_info(), 152 | swap_accounts.pool_fee.to_account_info(), 153 | swap_accounts.source_mint.to_account_info(), 154 | swap_accounts.destination_mint.to_account_info(), 155 | swap_accounts.source_token_program.to_account_info(), 156 | swap_accounts.destination_token_program.to_account_info(), 157 | swap_accounts.token_program_2022.to_account_info(), 158 | ]; 159 | 160 | let instruction = Instruction { 161 | program_id: swap_accounts.dex_program_id.key(), 162 | accounts, 163 | data, 164 | }; 165 | 166 | let dex_processor = &FluxBeamProcessor; 167 | let amount_out = invoke_process( 168 | dex_processor, 169 | &account_infos, 170 | swap_source_token, 171 | &mut swap_accounts.swap_destination_token, 172 | hop_accounts, 173 | instruction, 174 | hop, 175 | offset, 176 | ACCOUNTS_LEN, 177 | proxy_swap, 178 | )?; 179 | Ok(amount_out) 180 | } 181 | 182 | #[cfg(test)] 183 | mod tests { 184 | use super::*; 185 | 186 | #[test] 187 | pub fn test_pack_swap_instruction() { 188 | let amount_in = 100u64; 189 | let mut data = Vec::with_capacity(ARGS_LEN); 190 | data.push(1); 191 | data.extend_from_slice(&amount_in.to_le_bytes()); 192 | data.extend_from_slice(&1u64.to_le_bytes()); 193 | 194 | msg!("data.len: {}", data.len()); 195 | assert!(data.len() == ARGS_LEN); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/lifinity.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::{lifinity_v1pool_program, lifinity_v2pool_program, HopAccounts, SWAP_SELECTOR}; 4 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 5 | use anchor_spl::token::Token; 6 | use anchor_spl::token_interface::{Mint, TokenAccount}; 7 | use arrayref::array_ref; 8 | 9 | use super::common::DexProcessor; 10 | 11 | const ARGS_LEN: usize = 24; 12 | 13 | pub struct LifinityProcessor; 14 | impl DexProcessor for LifinityProcessor {} 15 | pub struct LifinitySwapAccountsV1<'info> { 16 | pub dex_program_id: &'info AccountInfo<'info>, 17 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 18 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 19 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 20 | 21 | pub authority: &'info AccountInfo<'info>, 22 | pub amm_info: &'info AccountInfo<'info>, 23 | pub swap_source: InterfaceAccount<'info, TokenAccount>, 24 | pub swap_destination: InterfaceAccount<'info, TokenAccount>, 25 | pub pool_mint: InterfaceAccount<'info, Mint>, 26 | pub fee_account: &'info AccountInfo<'info>, 27 | pub pyth_account: &'info AccountInfo<'info>, 28 | pub pyth_pc_account: &'info AccountInfo<'info>, 29 | pub config_account: &'info AccountInfo<'info>, 30 | pub token_program: Program<'info, Token>, 31 | } 32 | 33 | pub struct LifinitySwapAccountsV2<'info> { 34 | pub dex_program_id: &'info AccountInfo<'info>, 35 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 36 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 37 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 38 | 39 | pub authority: &'info AccountInfo<'info>, 40 | pub amm_info: &'info AccountInfo<'info>, 41 | pub swap_source: InterfaceAccount<'info, TokenAccount>, 42 | pub swap_destination: InterfaceAccount<'info, TokenAccount>, 43 | pub pool_mint: InterfaceAccount<'info, Mint>, 44 | pub fee_account: &'info AccountInfo<'info>, 45 | pub oracle_main_account: &'info AccountInfo<'info>, 46 | pub oracle_sub_account: &'info AccountInfo<'info>, 47 | pub oracle_pc_account: &'info AccountInfo<'info>, 48 | pub token_program: Program<'info, Token>, 49 | } 50 | const ACCOUNTS_LEN: usize = 14; 51 | 52 | impl<'info> LifinitySwapAccountsV1<'info> { 53 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 54 | let [ 55 | dex_program_id, 56 | swap_authority_pubkey, 57 | swap_source_token, 58 | swap_destination_token, 59 | authority, 60 | amm_info, 61 | swap_source, 62 | swap_destination, 63 | pool_mint, 64 | fee_account, 65 | pyth_account, 66 | pyth_pc_account, 67 | config_account, 68 | token_program, 69 | ]: & [AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 70 | Ok(Self { 71 | dex_program_id, 72 | swap_authority_pubkey, 73 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 74 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 75 | authority, 76 | amm_info, 77 | swap_source: InterfaceAccount::try_from(swap_source)?, 78 | swap_destination: InterfaceAccount::try_from(swap_destination)?, 79 | pool_mint: InterfaceAccount::try_from(pool_mint)?, 80 | fee_account, 81 | pyth_account, 82 | pyth_pc_account, 83 | config_account, 84 | token_program: Program::try_from(token_program)?, 85 | }) 86 | } 87 | } 88 | 89 | impl<'info> LifinitySwapAccountsV2<'info> { 90 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 91 | let [ 92 | dex_program_id, 93 | swap_authority_pubkey, 94 | swap_source_token, 95 | swap_destination_token, 96 | authority, 97 | amm_info, 98 | swap_source, 99 | swap_destination, 100 | pool_mint, 101 | fee_account, 102 | oracle_main_account, 103 | oracle_sub_account, 104 | oracle_pc_account, 105 | token_program, 106 | ]: & [AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 107 | Ok(Self { 108 | dex_program_id, 109 | swap_authority_pubkey, 110 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 111 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 112 | authority, 113 | amm_info, 114 | swap_source: InterfaceAccount::try_from(swap_source)?, 115 | swap_destination: InterfaceAccount::try_from(swap_destination)?, 116 | pool_mint: InterfaceAccount::try_from(pool_mint)?, 117 | fee_account, 118 | oracle_main_account, 119 | oracle_sub_account, 120 | oracle_pc_account, 121 | token_program: Program::try_from(token_program)?, 122 | }) 123 | } 124 | } 125 | 126 | pub fn swap_v1<'a>( 127 | remaining_accounts: &'a [AccountInfo<'a>], 128 | amount_in: u64, 129 | offset: &mut usize, 130 | hop_accounts: &mut HopAccounts, 131 | hop: usize, 132 | proxy_swap: bool, 133 | ) -> Result { 134 | msg!( 135 | "Dex::LifinitySwapV1 amount_in: {}, offset: {}", 136 | amount_in, 137 | offset 138 | ); 139 | require!( 140 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 141 | ErrorCode::InvalidAccountsLength 142 | ); 143 | let mut swap_accounts = LifinitySwapAccountsV1::parse_accounts(remaining_accounts, *offset)?; 144 | if swap_accounts.dex_program_id.key != &lifinity_v1pool_program::id() { 145 | return Err(ErrorCode::InvalidProgramId.into()); 146 | } 147 | 148 | // check hop accounts & swap authority 149 | let swap_source_token = swap_accounts.swap_source_token.key(); 150 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 151 | before_check( 152 | &swap_accounts.swap_authority_pubkey, 153 | swap_source_token, 154 | swap_destination_token, 155 | hop_accounts, 156 | hop, 157 | proxy_swap, 158 | )?; 159 | 160 | let mut data = Vec::with_capacity(ARGS_LEN); 161 | data.extend_from_slice(SWAP_SELECTOR); 162 | data.extend_from_slice(&amount_in.to_le_bytes()); 163 | data.extend_from_slice(&1u64.to_le_bytes()); 164 | 165 | let accounts = vec![ 166 | AccountMeta::new_readonly(swap_accounts.authority.key(), false), 167 | AccountMeta::new_readonly(swap_accounts.amm_info.key(), false), 168 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 169 | AccountMeta::new(swap_source_token, false), 170 | AccountMeta::new(swap_destination_token, false), 171 | AccountMeta::new(swap_accounts.swap_source.key(), false), 172 | AccountMeta::new(swap_accounts.swap_destination.key(), false), 173 | AccountMeta::new(swap_accounts.pool_mint.key(), false), 174 | AccountMeta::new(swap_accounts.fee_account.key(), false), 175 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 176 | AccountMeta::new_readonly(swap_accounts.pyth_account.key(), false), 177 | AccountMeta::new_readonly(swap_accounts.pyth_pc_account.key(), false), 178 | AccountMeta::new(swap_accounts.config_account.key(), false), 179 | ]; 180 | 181 | let account_infos = vec![ 182 | swap_accounts.authority.to_account_info(), 183 | swap_accounts.amm_info.to_account_info(), 184 | swap_accounts.swap_authority_pubkey.to_account_info(), 185 | swap_accounts.swap_source_token.to_account_info(), 186 | swap_accounts.swap_destination_token.to_account_info(), 187 | swap_accounts.swap_source.to_account_info(), 188 | swap_accounts.swap_destination.to_account_info(), 189 | swap_accounts.pool_mint.to_account_info(), 190 | swap_accounts.fee_account.to_account_info(), 191 | swap_accounts.token_program.to_account_info(), 192 | swap_accounts.pyth_account.to_account_info(), 193 | swap_accounts.pyth_pc_account.to_account_info(), 194 | swap_accounts.config_account.to_account_info(), 195 | ]; 196 | 197 | let instruction = Instruction { 198 | program_id: swap_accounts.dex_program_id.key(), 199 | accounts, 200 | data, 201 | }; 202 | 203 | let dex_processor = &LifinityProcessor; 204 | let amount_out = invoke_process( 205 | dex_processor, 206 | &account_infos, 207 | swap_source_token, 208 | &mut swap_accounts.swap_destination_token, 209 | hop_accounts, 210 | instruction, 211 | hop, 212 | offset, 213 | ACCOUNTS_LEN, 214 | proxy_swap, 215 | )?; 216 | Ok(amount_out) 217 | } 218 | 219 | pub fn swap_v2<'a>( 220 | remaining_accounts: &'a [AccountInfo<'a>], 221 | amount_in: u64, 222 | offset: &mut usize, 223 | hop_accounts: &mut HopAccounts, 224 | hop: usize, 225 | proxy_swap: bool, 226 | ) -> Result { 227 | msg!( 228 | "Dex::LifinitySwapV2 amount_in: {}, offset: {}", 229 | amount_in, 230 | offset 231 | ); 232 | require!( 233 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 234 | ErrorCode::InvalidAccountsLength 235 | ); 236 | let mut swap_accounts = LifinitySwapAccountsV2::parse_accounts(remaining_accounts, *offset)?; 237 | if swap_accounts.dex_program_id.key != &lifinity_v2pool_program::id() { 238 | return Err(ErrorCode::InvalidProgramId.into()); 239 | } 240 | 241 | // check hop accounts & swap authority 242 | let swap_source_token = swap_accounts.swap_source_token.key(); 243 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 244 | before_check( 245 | &swap_accounts.swap_authority_pubkey, 246 | swap_source_token, 247 | swap_destination_token, 248 | hop_accounts, 249 | hop, 250 | proxy_swap, 251 | )?; 252 | 253 | let mut data = Vec::with_capacity(ARGS_LEN); 254 | data.extend_from_slice(SWAP_SELECTOR); 255 | data.extend_from_slice(&amount_in.to_le_bytes()); 256 | data.extend_from_slice(&1u64.to_le_bytes()); 257 | 258 | let accounts = vec![ 259 | AccountMeta::new_readonly(swap_accounts.authority.key(), false), 260 | AccountMeta::new(swap_accounts.amm_info.key(), false), 261 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 262 | AccountMeta::new(swap_source_token, false), 263 | AccountMeta::new(swap_destination_token, false), 264 | AccountMeta::new(swap_accounts.swap_source.key(), false), 265 | AccountMeta::new(swap_accounts.swap_destination.key(), false), 266 | AccountMeta::new(swap_accounts.pool_mint.key(), false), 267 | AccountMeta::new(swap_accounts.fee_account.key(), false), 268 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 269 | AccountMeta::new_readonly(swap_accounts.oracle_main_account.key(), false), 270 | AccountMeta::new_readonly(swap_accounts.oracle_sub_account.key(), false), 271 | AccountMeta::new_readonly(swap_accounts.oracle_pc_account.key(), false), 272 | ]; 273 | 274 | let account_infos = vec![ 275 | swap_accounts.authority.to_account_info(), 276 | swap_accounts.amm_info.to_account_info(), 277 | swap_accounts.swap_authority_pubkey.to_account_info(), 278 | swap_accounts.swap_source_token.to_account_info(), 279 | swap_accounts.swap_destination_token.to_account_info(), 280 | swap_accounts.swap_source.to_account_info(), 281 | swap_accounts.swap_destination.to_account_info(), 282 | swap_accounts.pool_mint.to_account_info(), 283 | swap_accounts.fee_account.to_account_info(), 284 | swap_accounts.token_program.to_account_info(), 285 | swap_accounts.oracle_main_account.to_account_info(), 286 | swap_accounts.oracle_sub_account.to_account_info(), 287 | swap_accounts.oracle_pc_account.to_account_info(), 288 | ]; 289 | 290 | let instruction = Instruction { 291 | program_id: swap_accounts.dex_program_id.key(), 292 | accounts, 293 | data, 294 | }; 295 | 296 | let dex_processor = &LifinityProcessor; 297 | let amount_out = invoke_process( 298 | dex_processor, 299 | &account_infos, 300 | swap_source_token, 301 | &mut swap_accounts.swap_destination_token, 302 | hop_accounts, 303 | instruction, 304 | hop, 305 | offset, 306 | ACCOUNTS_LEN, 307 | proxy_swap, 308 | )?; 309 | Ok(amount_out) 310 | } 311 | 312 | #[cfg(test)] 313 | mod tests { 314 | use super::*; 315 | 316 | #[test] 317 | pub fn test_pack_swap_instruction() { 318 | let amount_in = 100u64; 319 | let mut data = Vec::with_capacity(ARGS_LEN); 320 | data.extend_from_slice(SWAP_SELECTOR); 321 | data.extend_from_slice(&amount_in.to_le_bytes()); 322 | data.extend_from_slice(&1u64.to_le_bytes()); 323 | 324 | msg!("data.len: {}", data.len()); 325 | assert!(data.len() == ARGS_LEN); 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/meteora.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::{ 4 | meteora_dlmm_program, meteora_dynamicpool_program, HopAccounts, SWAP_SELECTOR, ZERO_ADDRESS, 5 | }; 6 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 7 | use anchor_spl::token::Token; 8 | use anchor_spl::token_interface::{Mint, TokenAccount}; 9 | use arrayref::array_ref; 10 | 11 | use super::common::DexProcessor; 12 | 13 | const ARGS_LEN: usize = 24; 14 | 15 | pub struct MeteoraDynamicPoolProcessor; 16 | impl DexProcessor for MeteoraDynamicPoolProcessor {} 17 | 18 | pub struct MeteoraDynamicPoolAccounts<'info> { 19 | pub dex_program_id: &'info AccountInfo<'info>, 20 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 21 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 22 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 23 | 24 | pub pool: &'info AccountInfo<'info>, 25 | pub a_vault: &'info AccountInfo<'info>, 26 | pub b_vault: &'info AccountInfo<'info>, 27 | pub a_token_vault: &'info AccountInfo<'info>, 28 | pub b_token_vault: &'info AccountInfo<'info>, 29 | pub a_vault_lp_mint: InterfaceAccount<'info, Mint>, 30 | pub b_vault_lp_mint: InterfaceAccount<'info, Mint>, 31 | pub a_vault_lp: InterfaceAccount<'info, TokenAccount>, 32 | pub b_vault_lp: InterfaceAccount<'info, TokenAccount>, 33 | pub admin_token_fee: &'info AccountInfo<'info>, 34 | pub vault_program: &'info AccountInfo<'info>, 35 | pub token_program: Program<'info, Token>, 36 | } 37 | const ACCOUNTS_LEN: usize = 16; 38 | 39 | pub struct MeteoraDlmmAccounts<'info> { 40 | pub dex_program_id: &'info AccountInfo<'info>, 41 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 42 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 43 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 44 | 45 | pub lb_pair: &'info AccountInfo<'info>, 46 | pub bin_array_bitmap_extension: &'info AccountInfo<'info>, 47 | pub reserve_x: InterfaceAccount<'info, TokenAccount>, 48 | pub reserve_y: InterfaceAccount<'info, TokenAccount>, 49 | pub token_x_mint: InterfaceAccount<'info, Mint>, 50 | pub token_y_mint: InterfaceAccount<'info, Mint>, 51 | pub oracle: &'info AccountInfo<'info>, 52 | pub host_fee_in: &'info AccountInfo<'info>, 53 | pub token_x_program: &'info AccountInfo<'info>, 54 | pub token_y_program: &'info AccountInfo<'info>, 55 | pub event_authority: &'info AccountInfo<'info>, 56 | pub bin_array0: &'info AccountInfo<'info>, 57 | pub bin_array1: &'info AccountInfo<'info>, 58 | pub bin_array2: &'info AccountInfo<'info>, 59 | } 60 | const DLMM_ACCOUNTS_LEN: usize = 18; 61 | 62 | impl<'info> MeteoraDynamicPoolAccounts<'info> { 63 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 64 | let [ 65 | dex_program_id, 66 | swap_authority_pubkey, 67 | swap_source_token, 68 | swap_destination_token, 69 | pool, 70 | a_vault, 71 | b_vault, 72 | a_token_vault, 73 | b_token_vault, 74 | a_vault_lp_mint, 75 | b_vault_lp_mint, 76 | a_vault_lp, 77 | b_vault_lp, 78 | admin_token_fee, 79 | vault_program, 80 | token_program, 81 | ]: & [AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 82 | Ok(Self { 83 | dex_program_id, 84 | swap_authority_pubkey, 85 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 86 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 87 | pool, 88 | a_vault, 89 | b_vault, 90 | a_token_vault, 91 | b_token_vault, 92 | a_vault_lp_mint: InterfaceAccount::try_from(a_vault_lp_mint)?, 93 | b_vault_lp_mint: InterfaceAccount::try_from(b_vault_lp_mint)?, 94 | a_vault_lp: InterfaceAccount::try_from(a_vault_lp)?, 95 | b_vault_lp: InterfaceAccount::try_from(b_vault_lp)?, 96 | admin_token_fee, 97 | vault_program, 98 | token_program: Program::try_from(token_program)?, 99 | }) 100 | } 101 | } 102 | 103 | impl<'info> MeteoraDlmmAccounts<'info> { 104 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 105 | let [ 106 | dex_program_id, 107 | swap_authority_pubkey, 108 | swap_source_token, 109 | swap_destination_token, 110 | lb_pair, 111 | bin_array_bitmap_extension, 112 | reserve_x, 113 | reserve_y, 114 | token_x_mint, 115 | token_y_mint, 116 | oracle, 117 | host_fee_in, 118 | token_x_program, 119 | token_y_program, 120 | event_authority, 121 | bin_array0, 122 | bin_array1, 123 | bin_array2, 124 | ]: & [AccountInfo<'info>; DLMM_ACCOUNTS_LEN] = array_ref![accounts, offset, DLMM_ACCOUNTS_LEN]; 125 | Ok(Self { 126 | dex_program_id, 127 | swap_authority_pubkey, 128 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 129 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 130 | lb_pair, 131 | bin_array_bitmap_extension, 132 | reserve_x: InterfaceAccount::try_from(reserve_x)?, 133 | reserve_y: InterfaceAccount::try_from(reserve_y)?, 134 | token_x_mint: InterfaceAccount::try_from(token_x_mint)?, 135 | token_y_mint: InterfaceAccount::try_from(token_y_mint)?, 136 | oracle, 137 | host_fee_in, 138 | token_x_program, 139 | token_y_program, 140 | event_authority, 141 | bin_array0, 142 | bin_array1, 143 | bin_array2, 144 | }) 145 | } 146 | } 147 | 148 | pub fn swap<'a>( 149 | remaining_accounts: &'a [AccountInfo<'a>], 150 | amount_in: u64, 151 | offset: &mut usize, 152 | hop_accounts: &mut HopAccounts, 153 | hop: usize, 154 | proxy_swap: bool, 155 | ) -> Result { 156 | msg!( 157 | "Dex::MeteoraSwap amount_in: {}, offset: {}", 158 | amount_in, 159 | offset 160 | ); 161 | require!( 162 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 163 | ErrorCode::InvalidAccountsLength 164 | ); 165 | let mut swap_accounts = 166 | MeteoraDynamicPoolAccounts::parse_accounts(remaining_accounts, *offset)?; 167 | if swap_accounts.dex_program_id.key != &meteora_dynamicpool_program::id() { 168 | return Err(ErrorCode::InvalidProgramId.into()); 169 | } 170 | 171 | // check hop accounts & swap authority 172 | let swap_source_token = swap_accounts.swap_source_token.key(); 173 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 174 | before_check( 175 | &swap_accounts.swap_authority_pubkey, 176 | swap_source_token, 177 | swap_destination_token, 178 | hop_accounts, 179 | hop, 180 | proxy_swap, 181 | )?; 182 | 183 | let mut data = Vec::with_capacity(ARGS_LEN); 184 | data.extend_from_slice(SWAP_SELECTOR); 185 | data.extend_from_slice(&amount_in.to_le_bytes()); 186 | data.extend_from_slice(&1u64.to_le_bytes()); 187 | 188 | let accounts = vec![ 189 | AccountMeta::new(swap_accounts.pool.key(), false), 190 | AccountMeta::new(swap_source_token, false), 191 | AccountMeta::new(swap_destination_token, false), 192 | AccountMeta::new(swap_accounts.a_vault.key(), false), 193 | AccountMeta::new(swap_accounts.b_vault.key(), false), 194 | AccountMeta::new(swap_accounts.a_token_vault.key(), false), 195 | AccountMeta::new(swap_accounts.b_token_vault.key(), false), 196 | AccountMeta::new(swap_accounts.a_vault_lp_mint.key(), false), 197 | AccountMeta::new(swap_accounts.b_vault_lp_mint.key(), false), 198 | AccountMeta::new(swap_accounts.a_vault_lp.key(), false), 199 | AccountMeta::new(swap_accounts.b_vault_lp.key(), false), 200 | AccountMeta::new(swap_accounts.admin_token_fee.key(), false), 201 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 202 | AccountMeta::new_readonly(swap_accounts.vault_program.key(), false), 203 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 204 | ]; 205 | 206 | let account_infos = vec![ 207 | swap_accounts.pool.to_account_info(), 208 | swap_accounts.swap_source_token.to_account_info(), 209 | swap_accounts.swap_destination_token.to_account_info(), 210 | swap_accounts.a_vault.to_account_info(), 211 | swap_accounts.b_vault.to_account_info(), 212 | swap_accounts.a_token_vault.to_account_info(), 213 | swap_accounts.b_token_vault.to_account_info(), 214 | swap_accounts.a_vault_lp_mint.to_account_info(), 215 | swap_accounts.b_vault_lp_mint.to_account_info(), 216 | swap_accounts.a_vault_lp.to_account_info(), 217 | swap_accounts.b_vault_lp.to_account_info(), 218 | swap_accounts.admin_token_fee.to_account_info(), 219 | swap_accounts.swap_authority_pubkey.to_account_info(), 220 | swap_accounts.vault_program.to_account_info(), 221 | swap_accounts.token_program.to_account_info(), 222 | ]; 223 | 224 | let instruction = Instruction { 225 | program_id: swap_accounts.dex_program_id.key(), 226 | accounts, 227 | data, 228 | }; 229 | 230 | let dex_processor = &MeteoraDynamicPoolProcessor; 231 | let amount_out = invoke_process( 232 | dex_processor, 233 | &account_infos, 234 | swap_source_token, 235 | &mut swap_accounts.swap_destination_token, 236 | hop_accounts, 237 | instruction, 238 | hop, 239 | offset, 240 | ACCOUNTS_LEN, 241 | proxy_swap, 242 | )?; 243 | Ok(amount_out) 244 | } 245 | 246 | pub fn swap_dlmm<'a>( 247 | remaining_accounts: &'a [AccountInfo<'a>], 248 | amount_in: u64, 249 | offset: &mut usize, 250 | hop_accounts: &mut HopAccounts, 251 | hop: usize, 252 | proxy_swap: bool, 253 | ) -> Result { 254 | msg!( 255 | "Dex::MeteoraDlmm amount_in: {}, offset: {}", 256 | amount_in, 257 | offset 258 | ); 259 | require!( 260 | remaining_accounts.len() >= *offset + DLMM_ACCOUNTS_LEN, 261 | ErrorCode::InvalidAccountsLength 262 | ); 263 | let mut swap_accounts = MeteoraDlmmAccounts::parse_accounts(remaining_accounts, *offset)?; 264 | if swap_accounts.dex_program_id.key != &meteora_dlmm_program::id() { 265 | return Err(ErrorCode::InvalidProgramId.into()); 266 | } 267 | 268 | // check hop accounts & swap authority 269 | let swap_source_token = swap_accounts.swap_source_token.key(); 270 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 271 | before_check( 272 | &swap_accounts.swap_authority_pubkey, 273 | swap_source_token, 274 | swap_destination_token, 275 | hop_accounts, 276 | hop, 277 | proxy_swap, 278 | )?; 279 | 280 | let mut data = Vec::with_capacity(ARGS_LEN); 281 | data.extend_from_slice(SWAP_SELECTOR); 282 | data.extend_from_slice(&amount_in.to_le_bytes()); 283 | data.extend_from_slice(&1u64.to_le_bytes()); 284 | 285 | let mut accounts = vec![ 286 | AccountMeta::new(swap_accounts.lb_pair.key(), false), 287 | AccountMeta::new_readonly(swap_accounts.bin_array_bitmap_extension.key(), false), 288 | AccountMeta::new(swap_accounts.reserve_x.key(), false), 289 | AccountMeta::new(swap_accounts.reserve_y.key(), false), 290 | AccountMeta::new(swap_source_token, false), 291 | AccountMeta::new(swap_destination_token, false), 292 | AccountMeta::new_readonly(swap_accounts.token_x_mint.key(), false), 293 | AccountMeta::new_readonly(swap_accounts.token_y_mint.key(), false), 294 | AccountMeta::new(swap_accounts.oracle.key(), false), 295 | AccountMeta::new(swap_accounts.host_fee_in.key(), false), 296 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 297 | AccountMeta::new_readonly(swap_accounts.token_x_program.key(), false), 298 | AccountMeta::new_readonly(swap_accounts.token_y_program.key(), false), 299 | AccountMeta::new_readonly(swap_accounts.event_authority.key(), false), 300 | AccountMeta::new_readonly(swap_accounts.dex_program_id.key(), false), 301 | AccountMeta::new(swap_accounts.bin_array0.key(), false), 302 | ]; 303 | 304 | let mut account_infos = vec![ 305 | swap_accounts.lb_pair.to_account_info(), 306 | swap_accounts.bin_array_bitmap_extension.to_account_info(), 307 | swap_accounts.reserve_x.to_account_info(), 308 | swap_accounts.reserve_y.to_account_info(), 309 | swap_accounts.swap_source_token.to_account_info(), 310 | swap_accounts.swap_destination_token.to_account_info(), 311 | swap_accounts.token_x_mint.to_account_info(), 312 | swap_accounts.token_y_mint.to_account_info(), 313 | swap_accounts.oracle.to_account_info(), 314 | swap_accounts.host_fee_in.to_account_info(), 315 | swap_accounts.swap_authority_pubkey.to_account_info(), 316 | swap_accounts.token_x_program.to_account_info(), 317 | swap_accounts.token_y_program.to_account_info(), 318 | swap_accounts.event_authority.to_account_info(), 319 | swap_accounts.dex_program_id.to_account_info(), 320 | swap_accounts.bin_array0.to_account_info(), 321 | ]; 322 | 323 | let bin_array1 = swap_accounts.bin_array1.key(); 324 | let bin_array2 = swap_accounts.bin_array2.key(); 325 | if bin_array1 != ZERO_ADDRESS { 326 | accounts.push(AccountMeta::new(bin_array1, false)); 327 | account_infos.push(swap_accounts.bin_array1.to_account_info()); 328 | } 329 | if bin_array2 != ZERO_ADDRESS { 330 | accounts.push(AccountMeta::new(bin_array2, false)); 331 | account_infos.push(swap_accounts.bin_array2.to_account_info()); 332 | } 333 | 334 | let instruction = Instruction { 335 | program_id: swap_accounts.dex_program_id.key(), 336 | accounts, 337 | data, 338 | }; 339 | 340 | let dex_processor = &MeteoraDynamicPoolProcessor; 341 | let amount_out = invoke_process( 342 | dex_processor, 343 | &account_infos, 344 | swap_source_token, 345 | &mut swap_accounts.swap_destination_token, 346 | hop_accounts, 347 | instruction, 348 | hop, 349 | offset, 350 | DLMM_ACCOUNTS_LEN, 351 | proxy_swap, 352 | )?; 353 | Ok(amount_out) 354 | } 355 | 356 | #[cfg(test)] 357 | mod tests { 358 | use super::*; 359 | 360 | #[test] 361 | pub fn test_pack_swap_instruction() { 362 | let amount_in = 100u64; 363 | let mut data = Vec::with_capacity(ARGS_LEN); 364 | data.extend_from_slice(SWAP_SELECTOR); 365 | data.extend_from_slice(&amount_in.to_le_bytes()); 366 | data.extend_from_slice(&1u64.to_le_bytes()); 367 | 368 | msg!("data.len: {}", data.len()); 369 | assert!(data.len() == ARGS_LEN); 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod aldrin; 2 | pub mod common; 3 | pub mod fluxbeam; 4 | pub mod lifinity; 5 | pub mod meteora; 6 | pub mod obric_v2; 7 | pub mod openbookv2; 8 | pub mod phoenix; 9 | pub mod raydium; 10 | pub mod sanctum; 11 | pub mod spl_token_swap; 12 | pub mod stable_swap; 13 | pub mod whirlpool; 14 | pub mod pumpfun; 15 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/obric_v2.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::{obric_v2_program, HopAccounts, SWAP_SELECTOR}; 4 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 5 | use anchor_spl::token::Token; 6 | use anchor_spl::token_interface::{Mint, TokenAccount}; 7 | use arrayref::array_ref; 8 | 9 | use super::common::DexProcessor; 10 | 11 | const ARGS_LEN: usize = 25; 12 | 13 | pub struct ObricV2Processor; 14 | impl DexProcessor for ObricV2Processor {} 15 | 16 | //this dex only supoort spltoken not support token_2022 17 | pub struct ObricV2Account<'info> { 18 | pub dex_program_id: &'info AccountInfo<'info>, 19 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 20 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 21 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 22 | 23 | pub trading_pair: &'info AccountInfo<'info>, 24 | pub token_mint_x: InterfaceAccount<'info, Mint>, 25 | pub token_mint_y: InterfaceAccount<'info, Mint>, 26 | pub reserve_x: InterfaceAccount<'info, TokenAccount>, 27 | pub reserve_y: InterfaceAccount<'info, TokenAccount>, 28 | pub protocol_fee: &'info AccountInfo<'info>, 29 | pub x_price_feed: &'info AccountInfo<'info>, 30 | pub y_price_feed: &'info AccountInfo<'info>, 31 | pub token_program: Program<'info, Token>, 32 | } 33 | const ACCOUNTS_LEN: usize = 13; 34 | 35 | impl<'info> ObricV2Account<'info> { 36 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 37 | let [ 38 | dex_program_id, 39 | swap_authority_pubkey, 40 | swap_source_token, 41 | swap_destination_token, 42 | trading_pair, 43 | token_mint_x, 44 | token_mint_y, 45 | reserve_x, 46 | reserve_y, 47 | protocol_fee, 48 | x_price_feed, 49 | y_price_feed, 50 | token_program, 51 | ]: & [AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 52 | Ok(Self { 53 | dex_program_id: dex_program_id, 54 | swap_authority_pubkey: swap_authority_pubkey, 55 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 56 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 57 | trading_pair: trading_pair, 58 | token_mint_x: InterfaceAccount::try_from(token_mint_x)?, 59 | token_mint_y: InterfaceAccount::try_from(token_mint_y)?, 60 | reserve_x: InterfaceAccount::try_from(reserve_x)?, 61 | reserve_y: InterfaceAccount::try_from(reserve_y)?, 62 | protocol_fee: protocol_fee, 63 | x_price_feed: x_price_feed, 64 | y_price_feed: y_price_feed, 65 | token_program: Program::try_from(token_program)?, 66 | }) 67 | } 68 | } 69 | 70 | pub fn swap<'a>( 71 | remaining_accounts: &'a [AccountInfo<'a>], 72 | amount_in: u64, 73 | offset: &mut usize, 74 | hop_accounts: &mut HopAccounts, 75 | hop: usize, 76 | proxy_swap: bool, 77 | ) -> Result { 78 | msg!("Dex::Obric v2 amount_in: {}, offset: {}", amount_in, offset); 79 | require!( 80 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 81 | ErrorCode::InvalidAccountsLength 82 | ); 83 | let mut swap_accounts = ObricV2Account::parse_accounts(remaining_accounts, *offset)?; 84 | if swap_accounts.dex_program_id.key != &obric_v2_program::id() { 85 | return Err(ErrorCode::InvalidProgramId.into()); 86 | } 87 | 88 | // check hop accounts & swap authority 89 | let swap_source_token = swap_accounts.swap_source_token.key(); 90 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 91 | before_check( 92 | &swap_accounts.swap_authority_pubkey, 93 | swap_source_token, 94 | swap_destination_token, 95 | hop_accounts, 96 | hop, 97 | proxy_swap, 98 | )?; 99 | 100 | let x_to_y: bool; 101 | // compare mint pubkey 102 | if swap_accounts.swap_destination_token.mint == swap_accounts.token_mint_x.key() { 103 | x_to_y = false; 104 | } else if swap_accounts.swap_destination_token.mint == swap_accounts.token_mint_y.key() { 105 | x_to_y = true; 106 | } else { 107 | return Err(ErrorCode::InvalidTokenMint.into()); 108 | } 109 | 110 | let (user_token_account_x, user_token_account_y) = if swap_accounts.swap_source_token.mint 111 | == swap_accounts.token_mint_x.key() 112 | && swap_accounts.swap_destination_token.mint == swap_accounts.token_mint_y.key() 113 | { 114 | ( 115 | swap_accounts.swap_source_token.key(), 116 | swap_accounts.swap_destination_token.key(), 117 | ) 118 | } else if swap_accounts.swap_source_token.mint == swap_accounts.token_mint_y.key() 119 | && swap_accounts.swap_destination_token.mint == swap_accounts.token_mint_x.key() 120 | { 121 | ( 122 | swap_accounts.swap_destination_token.key(), 123 | swap_accounts.swap_source_token.key(), 124 | ) 125 | } else { 126 | return Err(ErrorCode::InvalidTokenMint.into()); 127 | }; 128 | 129 | let mut data = Vec::with_capacity(ARGS_LEN); 130 | data.extend_from_slice(SWAP_SELECTOR); 131 | data.extend_from_slice(&(x_to_y as u8).to_le_bytes()); 132 | data.extend_from_slice(&amount_in.to_le_bytes()); 133 | data.extend_from_slice(&1u64.to_le_bytes()); 134 | 135 | let accounts = vec![ 136 | AccountMeta::new(swap_accounts.trading_pair.key(), false), 137 | AccountMeta::new_readonly(swap_accounts.token_mint_x.key(), false), 138 | AccountMeta::new_readonly(swap_accounts.token_mint_y.key(), false), 139 | AccountMeta::new(swap_accounts.reserve_x.key(), false), 140 | AccountMeta::new(swap_accounts.reserve_y.key(), false), 141 | AccountMeta::new(user_token_account_x.key(), false), 142 | AccountMeta::new(user_token_account_y.key(), false), 143 | AccountMeta::new(swap_accounts.protocol_fee.key(), false), 144 | AccountMeta::new_readonly(swap_accounts.x_price_feed.key(), false), 145 | AccountMeta::new_readonly(swap_accounts.y_price_feed.key(), false), 146 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 147 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 148 | ]; 149 | 150 | let account_infos = vec![ 151 | swap_accounts.trading_pair.to_account_info(), 152 | swap_accounts.token_mint_x.to_account_info(), 153 | swap_accounts.token_mint_y.to_account_info(), 154 | swap_accounts.reserve_x.to_account_info(), 155 | swap_accounts.reserve_y.to_account_info(), 156 | swap_accounts.swap_source_token.to_account_info(), 157 | swap_accounts.swap_destination_token.to_account_info(), 158 | swap_accounts.protocol_fee.to_account_info(), 159 | swap_accounts.x_price_feed.to_account_info(), 160 | swap_accounts.y_price_feed.to_account_info(), 161 | swap_accounts.swap_authority_pubkey.to_account_info(), 162 | swap_accounts.token_program.to_account_info(), 163 | ]; 164 | 165 | let instruction = Instruction { 166 | program_id: swap_accounts.dex_program_id.key(), 167 | accounts, 168 | data, 169 | }; 170 | 171 | let dex_processor = &ObricV2Processor; 172 | let amount_out = invoke_process( 173 | dex_processor, 174 | &account_infos, 175 | swap_source_token, 176 | &mut swap_accounts.swap_destination_token, 177 | hop_accounts, 178 | instruction, 179 | hop, 180 | offset, 181 | ACCOUNTS_LEN, 182 | proxy_swap, 183 | )?; 184 | Ok(amount_out) 185 | } 186 | 187 | #[cfg(test)] 188 | mod tests { 189 | use super::*; 190 | 191 | #[test] 192 | pub fn test_pack_swap_instruction() { 193 | let amount_in = 100u64; 194 | let x_to_y = true; 195 | let mut data = Vec::with_capacity(ARGS_LEN); 196 | data.extend_from_slice(SWAP_SELECTOR); 197 | data.extend_from_slice(&(x_to_y as u8).to_le_bytes()); 198 | data.extend_from_slice(&amount_in.to_le_bytes()); 199 | data.extend_from_slice(&1u64.to_le_bytes()); 200 | 201 | msg!("data.len: {}", data.len()); 202 | assert!(data.len() == ARGS_LEN); 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/openbookv2.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::{openbookv2_program, HopAccounts, PLACE_TAKE_ORDER_SELECTOR, ZERO_ADDRESS}; 4 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 5 | use anchor_spl::token::Token; 6 | use anchor_spl::token_interface::TokenAccount; 7 | use arrayref::array_ref; 8 | use num_enum::{IntoPrimitive, TryFromPrimitive}; 9 | 10 | use super::common::DexProcessor; 11 | 12 | const ARGS_LEN: usize = 35; 13 | 14 | #[derive( 15 | AnchorDeserialize, AnchorSerialize, Clone, Debug, PartialEq, TryFromPrimitive, IntoPrimitive, 16 | )] 17 | #[repr(u8)] 18 | pub enum Side { 19 | Bid = 0, 20 | Ask = 1, 21 | } 22 | 23 | pub struct OpenbookV2Processor; 24 | impl DexProcessor for OpenbookV2Processor {} 25 | 26 | pub struct PlaceTakeOrderAccounts<'info> { 27 | pub dex_program_id: &'info AccountInfo<'info>, 28 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 29 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 30 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 31 | 32 | pub market: &'info AccountInfo<'info>, 33 | pub market_authority: &'info AccountInfo<'info>, 34 | pub bids: &'info AccountInfo<'info>, 35 | pub asks: &'info AccountInfo<'info>, 36 | pub market_base_vault: InterfaceAccount<'info, TokenAccount>, 37 | pub market_quote_vault: InterfaceAccount<'info, TokenAccount>, 38 | pub event_heap: &'info AccountInfo<'info>, 39 | pub oracle_a: &'info AccountInfo<'info>, 40 | pub oracle_b: &'info AccountInfo<'info>, 41 | pub token_program: Program<'info, Token>, 42 | pub system_program: Program<'info, System>, 43 | pub open_orders_admin: &'info AccountInfo<'info>, 44 | pub open_orders_account0: &'info AccountInfo<'info>, 45 | pub open_orders_account1: &'info AccountInfo<'info>, 46 | pub open_orders_account2: &'info AccountInfo<'info>, 47 | } 48 | const ACCOUNTS_LEN: usize = 19; 49 | 50 | impl<'info> PlaceTakeOrderAccounts<'info> { 51 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 52 | let [ 53 | dex_program_id, 54 | swap_authority_pubkey, 55 | swap_source_token, 56 | swap_destination_token, 57 | market, 58 | market_authority, 59 | bids, 60 | asks, 61 | market_base_vault, 62 | market_quote_vault, 63 | event_heap, 64 | oracle_a, 65 | oracle_b, 66 | token_program, 67 | system_program, 68 | open_orders_admin, 69 | open_orders_account0, 70 | open_orders_account1, 71 | open_orders_account2, 72 | ]: & [AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 73 | 74 | Ok(Self { 75 | dex_program_id, 76 | swap_authority_pubkey, 77 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 78 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 79 | market, 80 | market_authority, 81 | bids, 82 | asks, 83 | market_base_vault: InterfaceAccount::try_from(market_base_vault)?, 84 | market_quote_vault: InterfaceAccount::try_from(market_quote_vault)?, 85 | event_heap, 86 | oracle_a, 87 | oracle_b, 88 | token_program: Program::try_from(token_program)?, 89 | system_program: Program::try_from(system_program)?, 90 | open_orders_admin, 91 | open_orders_account0, 92 | open_orders_account1, 93 | open_orders_account2, 94 | }) 95 | } 96 | 97 | fn get_lot_size(&self) -> Result<(i64, i64)> { 98 | let data = &self.market.try_borrow_data()?; 99 | let quote_lots_size = i64::from_le_bytes(*array_ref![data, 448, 8]); 100 | let base_lots_size = i64::from_le_bytes(*array_ref![data, 456, 8]); 101 | Ok((base_lots_size, quote_lots_size)) 102 | } 103 | } 104 | 105 | pub fn place_take_order<'a>( 106 | remaining_accounts: &'a [AccountInfo<'a>], 107 | amount_in: u64, 108 | offset: &mut usize, 109 | hop_accounts: &mut HopAccounts, 110 | hop: usize, 111 | proxy_swap: bool, 112 | ) -> Result { 113 | msg!( 114 | "Dex::OpenBookV2 amount_in: {}, offset: {}", 115 | amount_in, 116 | offset 117 | ); 118 | require!( 119 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 120 | ErrorCode::InvalidAccountsLength 121 | ); 122 | 123 | let mut swap_accounts = PlaceTakeOrderAccounts::parse_accounts(remaining_accounts, *offset)?; 124 | if swap_accounts.dex_program_id.key != &openbookv2_program::id() { 125 | return Err(ErrorCode::InvalidProgramId.into()); 126 | } 127 | 128 | // check hop accounts & swap authority 129 | let swap_source_token = swap_accounts.swap_source_token.key(); 130 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 131 | before_check( 132 | &swap_accounts.swap_authority_pubkey, 133 | swap_source_token, 134 | swap_destination_token, 135 | hop_accounts, 136 | hop, 137 | proxy_swap, 138 | )?; 139 | 140 | let (base_lot_size, quote_lot_size) = swap_accounts.get_lot_size()?; 141 | let side; 142 | let price_lots: i64; 143 | let max_base_lots; 144 | let max_quote_lots_including_fees; 145 | if swap_accounts.swap_source_token.mint == swap_accounts.market_base_vault.mint { 146 | side = Side::Ask; 147 | price_lots = 1; 148 | max_base_lots = i64::try_from(amount_in) 149 | .unwrap() 150 | .checked_div(base_lot_size) 151 | .ok_or(ErrorCode::CalculationError)?; 152 | max_quote_lots_including_fees = i64::MAX 153 | .checked_div(quote_lot_size) 154 | .ok_or(ErrorCode::CalculationError)?; 155 | } else { 156 | side = Side::Bid; 157 | price_lots = i64::MAX; 158 | max_base_lots = i64::MAX 159 | .checked_div(base_lot_size) 160 | .ok_or(ErrorCode::CalculationError)?; 161 | max_quote_lots_including_fees = i64::try_from(amount_in) 162 | .unwrap() 163 | .checked_div(quote_lot_size) 164 | .ok_or(ErrorCode::CalculationError)?; 165 | } 166 | let order_type = 3u8; 167 | let limit = 50u8; 168 | 169 | let mut data = Vec::with_capacity(ARGS_LEN); 170 | data.extend_from_slice(PLACE_TAKE_ORDER_SELECTOR); 171 | data.push(Side::into(side)); 172 | data.extend_from_slice(&price_lots.to_le_bytes()); 173 | data.extend_from_slice(&max_base_lots.to_le_bytes()); 174 | data.extend_from_slice(&max_quote_lots_including_fees.to_le_bytes()); 175 | data.extend_from_slice(&order_type.to_le_bytes()); 176 | data.extend_from_slice(&limit.to_le_bytes()); 177 | 178 | let (user_base_account, user_quote_account) = if swap_accounts.swap_source_token.mint 179 | == swap_accounts.market_base_vault.mint 180 | && swap_accounts.swap_destination_token.mint == swap_accounts.market_quote_vault.mint 181 | { 182 | (swap_source_token, swap_destination_token) 183 | } else if swap_accounts.swap_source_token.mint == swap_accounts.market_quote_vault.mint 184 | && swap_accounts.swap_destination_token.mint == swap_accounts.market_base_vault.mint 185 | { 186 | (swap_destination_token, swap_source_token) 187 | } else { 188 | return Err(ErrorCode::InvalidTokenMint.into()); 189 | }; 190 | 191 | let mut accounts = vec![ 192 | AccountMeta::new(swap_accounts.swap_authority_pubkey.key(), true), 193 | AccountMeta::new(swap_accounts.swap_authority_pubkey.key(), true), 194 | AccountMeta::new(swap_accounts.market.key(), false), 195 | AccountMeta::new_readonly(swap_accounts.market_authority.key(), false), 196 | AccountMeta::new(swap_accounts.bids.key(), false), 197 | AccountMeta::new(swap_accounts.asks.key(), false), 198 | AccountMeta::new(swap_accounts.market_base_vault.key(), false), 199 | AccountMeta::new(swap_accounts.market_quote_vault.key(), false), 200 | AccountMeta::new(swap_accounts.event_heap.key(), false), 201 | AccountMeta::new(user_base_account.key(), false), 202 | AccountMeta::new(user_quote_account.key(), false), 203 | AccountMeta::new_readonly(swap_accounts.oracle_a.key(), false), 204 | AccountMeta::new_readonly(swap_accounts.oracle_b.key(), false), 205 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 206 | AccountMeta::new_readonly(swap_accounts.system_program.key(), false), 207 | AccountMeta::new_readonly(swap_accounts.open_orders_admin.key(), false), 208 | ]; 209 | 210 | let mut account_infos = vec![ 211 | swap_accounts.swap_authority_pubkey.to_account_info(), 212 | swap_accounts.market.to_account_info(), 213 | swap_accounts.market_authority.to_account_info(), 214 | swap_accounts.bids.to_account_info(), 215 | swap_accounts.asks.to_account_info(), 216 | swap_accounts.market_base_vault.to_account_info(), 217 | swap_accounts.market_quote_vault.to_account_info(), 218 | swap_accounts.event_heap.to_account_info(), 219 | swap_accounts.swap_source_token.to_account_info(), 220 | swap_accounts.swap_destination_token.to_account_info(), 221 | swap_accounts.oracle_a.to_account_info(), 222 | swap_accounts.oracle_b.to_account_info(), 223 | swap_accounts.token_program.to_account_info(), 224 | swap_accounts.system_program.to_account_info(), 225 | swap_accounts.open_orders_admin.to_account_info(), 226 | ]; 227 | 228 | let open_orders_account0 = swap_accounts.open_orders_account0.key(); 229 | let open_orders_account1 = swap_accounts.open_orders_account1.key(); 230 | let open_orders_account2 = swap_accounts.open_orders_account2.key(); 231 | if open_orders_account0 != ZERO_ADDRESS { 232 | accounts.push(AccountMeta::new(open_orders_account0, false)); 233 | account_infos.push(swap_accounts.open_orders_account0.to_account_info()); 234 | } 235 | if open_orders_account1 != ZERO_ADDRESS { 236 | accounts.push(AccountMeta::new(open_orders_account1, false)); 237 | account_infos.push(swap_accounts.open_orders_account1.to_account_info()); 238 | } 239 | if open_orders_account2 != ZERO_ADDRESS { 240 | accounts.push(AccountMeta::new(open_orders_account2, false)); 241 | account_infos.push(swap_accounts.open_orders_account2.to_account_info()); 242 | } 243 | 244 | let instruction = Instruction { 245 | program_id: swap_accounts.dex_program_id.key(), 246 | accounts, 247 | data, 248 | }; 249 | 250 | let dex_processor = &OpenbookV2Processor; 251 | let amount_out = invoke_process( 252 | dex_processor, 253 | &account_infos, 254 | swap_source_token, 255 | &mut swap_accounts.swap_destination_token, 256 | hop_accounts, 257 | instruction, 258 | hop, 259 | offset, 260 | ACCOUNTS_LEN, 261 | proxy_swap, 262 | )?; 263 | Ok(amount_out) 264 | } 265 | 266 | #[cfg(test)] 267 | mod tests { 268 | use super::*; 269 | 270 | #[test] 271 | pub fn test_pack_instruction() { 272 | let amount_in = 39624782u64; 273 | 274 | let side = Side::Bid; 275 | let price_lots = i64::MAX; 276 | // let quote_lot_size = 1i64; 277 | let base_lot_size = 100000i64; 278 | let max_base_lots = i64::MAX / base_lot_size; 279 | let max_quote_lots_including_fees = i64::try_from(amount_in).unwrap(); 280 | let order_type = 3u8; 281 | let limit = 50u8; 282 | 283 | let mut data = Vec::with_capacity(ARGS_LEN); 284 | data.extend_from_slice(PLACE_TAKE_ORDER_SELECTOR); 285 | data.push(Side::into(side)); 286 | data.extend_from_slice(&price_lots.to_le_bytes()); 287 | data.extend_from_slice(&max_base_lots.to_le_bytes()); 288 | data.extend_from_slice(&max_quote_lots_including_fees.to_le_bytes()); 289 | data.extend_from_slice(&order_type.to_le_bytes()); 290 | data.extend_from_slice(&limit.to_le_bytes()); 291 | msg!("data.len: {}", data.len()); 292 | assert!(data.len() == ARGS_LEN); 293 | println!("constructed_data {:?}", data); 294 | //032c47031ac7cb5500ffffffffffffff7fa38d23d6e25300004ea05c02000000000332 295 | } 296 | 297 | #[test] 298 | pub fn test_div() { 299 | let amount_in: u64 = 113934176; 300 | let base_lot_size = i64::try_from(1000000).unwrap(); 301 | let res = i64::try_from(amount_in).unwrap() / base_lot_size; 302 | msg!("res: {}", res); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/phoenix.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::{phoenix_program, HopAccounts}; 4 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 5 | use anchor_spl::token::Token; 6 | use anchor_spl::token_interface::TokenAccount; 7 | use arrayref::array_ref; 8 | use std::u64; 9 | 10 | use super::common::DexProcessor; 11 | 12 | const ARGS_LEN: usize = 55; 13 | 14 | pub struct PhoenixProcessor; 15 | impl DexProcessor for PhoenixProcessor {} 16 | 17 | pub struct SwapAccounts<'info> { 18 | pub dex_program_id: &'info AccountInfo<'info>, 19 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 20 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 21 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 22 | 23 | pub log_authority: &'info AccountInfo<'info>, 24 | pub market: &'info AccountInfo<'info>, 25 | pub base_vault: InterfaceAccount<'info, TokenAccount>, 26 | pub quote_vault: InterfaceAccount<'info, TokenAccount>, 27 | pub token_program: Program<'info, Token>, 28 | } 29 | const ACCOUNTS_LEN: usize = 9; 30 | 31 | impl<'info> SwapAccounts<'info> { 32 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 33 | let [ 34 | dex_program_id, 35 | swap_authority_pubkey, 36 | swap_source_token, 37 | swap_destination_token, 38 | log_authority, 39 | market, 40 | base_vault, 41 | quote_vault, 42 | token_program, 43 | ]: & [AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 44 | 45 | Ok(Self { 46 | dex_program_id, 47 | swap_authority_pubkey, 48 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 49 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 50 | log_authority, 51 | market, 52 | base_vault: InterfaceAccount::try_from(base_vault)?, 53 | quote_vault: InterfaceAccount::try_from(quote_vault)?, 54 | token_program: Program::try_from(token_program)?, 55 | }) 56 | } 57 | 58 | fn get_lot_size(&self) -> Result<(u64, u64)> { 59 | let data = &self.market.try_borrow_data()?; 60 | let base_lots_size = u64::from_le_bytes(*array_ref![data, 112, 8]); 61 | let quote_lots_size = u64::from_le_bytes(*array_ref![data, 192, 8]); 62 | Ok((base_lots_size, quote_lots_size)) 63 | } 64 | } 65 | 66 | pub fn swap<'a>( 67 | remaining_accounts: &'a [AccountInfo<'a>], 68 | amount_in: u64, 69 | offset: &mut usize, 70 | hop_accounts: &mut HopAccounts, 71 | hop: usize, 72 | proxy_swap: bool, 73 | ) -> Result { 74 | msg!("Dex::Phoenix amount_in: {}, offset: {}", amount_in, offset); 75 | require!( 76 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 77 | ErrorCode::InvalidAccountsLength 78 | ); 79 | 80 | let mut swap_accounts = SwapAccounts::parse_accounts(remaining_accounts, *offset)?; 81 | if swap_accounts.dex_program_id.key != &phoenix_program::id() { 82 | return Err(ErrorCode::InvalidProgramId.into()); 83 | } 84 | 85 | // check hop accounts & swap authority 86 | let swap_source_token = swap_accounts.swap_source_token.key(); 87 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 88 | before_check( 89 | &swap_accounts.swap_authority_pubkey, 90 | swap_source_token, 91 | swap_destination_token, 92 | hop_accounts, 93 | hop, 94 | proxy_swap, 95 | )?; 96 | 97 | let mut data = Vec::with_capacity(ARGS_LEN); 98 | let (base_lot_size, quote_lot_size) = swap_accounts.get_lot_size()?; 99 | let (side, num_base_lots, num_quote_lots) = 100 | if swap_accounts.swap_source_token.mint == swap_accounts.base_vault.mint { 101 | ( 102 | 1u8, 103 | amount_in 104 | .checked_div(base_lot_size) 105 | .ok_or(ErrorCode::CalculationError)?, 106 | 0u64, 107 | ) // 'ask' side 108 | } else { 109 | ( 110 | 0u8, 111 | 0u64, 112 | amount_in 113 | .checked_div(quote_lot_size) 114 | .ok_or(ErrorCode::CalculationError)?, 115 | ) // 'bid' side 116 | }; 117 | 118 | let order_type = 2u8; // 'immediateOrCancel' 119 | let self_trade_behavior = 1u8; // 'cancelProvide' 120 | 121 | data.push(0); //discriminator 122 | data.push(order_type); 123 | data.push(side); 124 | data.push(0); // Indicates absence of price_in_ticks (market order) 125 | data.extend_from_slice(&num_base_lots.to_le_bytes()); 126 | data.extend_from_slice(&num_quote_lots.to_le_bytes()); 127 | data.extend_from_slice(&0u64.to_le_bytes()); // min_base_lots_to_fill 128 | data.extend_from_slice(&0u64.to_le_bytes()); // min_quote_lots_to_fill 129 | data.push(self_trade_behavior); 130 | data.push(0); // Indicates absence of match_limit 131 | data.extend_from_slice(&0u128.to_le_bytes()); // client_order_id 132 | data.push(0u8); // use_only_deposited_funds as false 133 | 134 | let (user_base_account, user_quote_account) = if swap_accounts.swap_source_token.mint 135 | == swap_accounts.base_vault.mint 136 | && swap_accounts.swap_destination_token.mint == swap_accounts.quote_vault.mint 137 | { 138 | (swap_source_token, swap_destination_token) 139 | } else if swap_accounts.swap_source_token.mint == swap_accounts.quote_vault.mint 140 | && swap_accounts.swap_destination_token.mint == swap_accounts.base_vault.mint 141 | { 142 | (swap_destination_token, swap_source_token) 143 | } else { 144 | return Err(ErrorCode::InvalidTokenMint.into()); 145 | }; 146 | 147 | let accounts = vec![ 148 | AccountMeta::new_readonly(swap_accounts.dex_program_id.key(), false), 149 | AccountMeta::new(swap_accounts.log_authority.key(), false), 150 | AccountMeta::new(swap_accounts.market.key(), false), 151 | AccountMeta::new(swap_accounts.swap_authority_pubkey.key(), true), 152 | AccountMeta::new(user_base_account.key(), false), 153 | AccountMeta::new(user_quote_account.key(), false), 154 | AccountMeta::new(swap_accounts.base_vault.key(), false), 155 | AccountMeta::new(swap_accounts.quote_vault.key(), false), 156 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 157 | ]; 158 | 159 | let account_infos = vec![ 160 | swap_accounts.dex_program_id.to_account_info(), 161 | swap_accounts.log_authority.to_account_info(), 162 | swap_accounts.market.to_account_info(), 163 | swap_accounts.swap_authority_pubkey.to_account_info(), 164 | swap_accounts.swap_source_token.to_account_info(), 165 | swap_accounts.swap_destination_token.to_account_info(), 166 | swap_accounts.base_vault.to_account_info(), 167 | swap_accounts.quote_vault.to_account_info(), 168 | swap_accounts.token_program.to_account_info(), 169 | ]; 170 | 171 | let instruction = Instruction { 172 | program_id: swap_accounts.dex_program_id.key(), 173 | accounts, 174 | data, 175 | }; 176 | 177 | let dex_processor = &PhoenixProcessor; 178 | let amount_out = invoke_process( 179 | dex_processor, 180 | &account_infos, 181 | swap_source_token, 182 | &mut swap_accounts.swap_destination_token, 183 | hop_accounts, 184 | instruction, 185 | hop, 186 | offset, 187 | ACCOUNTS_LEN, 188 | proxy_swap, 189 | )?; 190 | Ok(amount_out) 191 | } 192 | 193 | #[cfg(test)] 194 | mod tests { 195 | use super::*; 196 | 197 | #[test] 198 | pub fn test_pack_instruction() { 199 | let side = 1u8; // 'ask' side 200 | let num_base_lots = 68378u64; 201 | let num_quote_lots = 0u64; 202 | let order_type = 2u8; // 'immediateOrCancel' 203 | let self_trade_behavior = 1u8; // 'cancelProvide' 204 | 205 | let mut data = Vec::with_capacity(ARGS_LEN); 206 | data.push(0); // discriminator 207 | data.push(order_type); 208 | data.push(side); 209 | data.push(0); // Indicates absence of price_in_ticks (market order) 210 | data.extend_from_slice(&num_base_lots.to_le_bytes()); 211 | data.extend_from_slice(&num_quote_lots.to_le_bytes()); 212 | data.extend_from_slice(&0u64.to_le_bytes()); // min_base_lots_to_fill 213 | data.extend_from_slice(&0u64.to_le_bytes()); // min_quote_lots_to_fill 214 | data.push(self_trade_behavior); 215 | data.push(0); // Indicates absence of match_limit 216 | data.extend_from_slice(&0u128.to_le_bytes()); // client_order_id 217 | data.push(0u8); // use_only_deposited_funds as false 218 | 219 | msg!("data.len: {}", data.len()); 220 | assert!(data.len() == ARGS_LEN); 221 | println!("constructed_data {:?}", data); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/pumpfun.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::solana_program::instruction::Instruction; 2 | use anchor_lang::prelude::*; 3 | use anchor_lang::solana_program::program::invoke; 4 | use anchor_spl::associated_token::AssociatedToken; 5 | use anchor_spl::token::spl_token::instruction::{close_account, sync_native}; 6 | use anchor_spl::token::Token; 7 | use anchor_spl::token_interface::{Mint, TokenAccount}; 8 | use arrayref::array_ref; 9 | use crate::adapters::common::{before_check, invoke_process}; 10 | use crate::error::ErrorCode; 11 | use crate::utils::transfer_sol_from_user; 12 | use crate::{pumpfun_program, HopAccounts, PUMPFUN_BUY_SELECTOR, PUMPFUN_SELL_SELECTOR, ZERO_ADDRESS}; 13 | 14 | use super::common::DexProcessor; 15 | 16 | const ARGS_LEN: usize = 24; 17 | 18 | pub fn pumpfun_before_check( 19 | swap_authority_pubkey: &AccountInfo, 20 | swap_source_token: Pubkey, 21 | swap_source_token_account: TokenAccount, 22 | swap_destination_token: Pubkey, 23 | hop_accounts: &mut HopAccounts, 24 | hop: usize, 25 | proxy_swap: bool, 26 | ) -> Result<()> { 27 | // check hop accounts 28 | if hop_accounts.from_account != ZERO_ADDRESS { 29 | require_keys_eq!( 30 | swap_source_token, 31 | hop_accounts.from_account, 32 | ErrorCode::InvalidHopAccounts 33 | ); 34 | require_keys_eq!( 35 | swap_destination_token, 36 | hop_accounts.to_account, 37 | ErrorCode::InvalidHopAccounts 38 | ); 39 | } 40 | if hop_accounts.last_to_account != ZERO_ADDRESS { 41 | require_keys_eq!( 42 | swap_source_token, 43 | hop_accounts.last_to_account, 44 | ErrorCode::InvalidHopFromAccount 45 | ); 46 | } 47 | 48 | // check swap authority 49 | if !proxy_swap && hop == 0 { 50 | require!( 51 | swap_authority_pubkey.is_signer, 52 | ErrorCode::SwapAuthorityIsNotSigner 53 | ); 54 | } else { 55 | require_keys_eq!( 56 | swap_authority_pubkey.key(), 57 | swap_source_token_account.owner, 58 | ErrorCode::InvalidAuthorityPda 59 | ); 60 | } 61 | Ok(()) 62 | } 63 | 64 | 65 | 66 | pub struct PumpfunBuyAccounts<'info> { 67 | pub dex_program_id: &'info AccountInfo<'info>, 68 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 69 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 70 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 71 | pub global: &'info AccountInfo<'info>, 72 | pub fee_recipient: &'info AccountInfo<'info>, 73 | pub mint: InterfaceAccount<'info, Mint>, 74 | pub bonding_curve: &'info AccountInfo<'info>, 75 | pub associated_bonding_curve: &'info AccountInfo<'info>, 76 | pub system_program: Program<'info, System>, 77 | pub token_program: Program<'info, Token>, 78 | pub rent: &'info AccountInfo<'info>, 79 | pub event_authority: &'info AccountInfo<'info>, 80 | } 81 | 82 | const BUY_ACCOUNTS_LEN: usize = 13; 83 | 84 | 85 | pub struct PumpfunBuyProcessor; 86 | impl DexProcessor for PumpfunBuyProcessor { 87 | fn before_invoke(&self, account_infos: &[AccountInfo]) -> Result { 88 | let source_token_account = account_infos.get(12).unwrap(); 89 | let token_program = account_infos.get(8).unwrap(); 90 | let user = account_infos.get(6).unwrap(); 91 | 92 | let close_account_ix = close_account( 93 | &token_program.key(), 94 | &source_token_account.key(), 95 | &user.key(), 96 | &user.key(), 97 | &[&user.key()], 98 | )?; 99 | 100 | invoke(&close_account_ix, &[source_token_account.to_account_info(), token_program.to_account_info(), user.to_account_info()])?; 101 | 102 | Ok(0) 103 | } 104 | } 105 | 106 | impl<'info> PumpfunBuyAccounts<'info> { 107 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 108 | let [ 109 | dex_program_id, 110 | swap_authority_pubkey, 111 | swap_source_token, 112 | swap_destination_token, 113 | global, 114 | fee_recipient, 115 | mint, 116 | bonding_curve, 117 | associated_bonding_curve, 118 | system_program, 119 | token_program, 120 | rent, 121 | event_authority, 122 | 123 | ]: &[AccountInfo<'info>; BUY_ACCOUNTS_LEN] = array_ref![accounts, offset, BUY_ACCOUNTS_LEN]; 124 | 125 | Ok(Self { 126 | dex_program_id, 127 | swap_authority_pubkey, 128 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 129 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 130 | global, 131 | fee_recipient, 132 | mint: InterfaceAccount::try_from(mint)?, 133 | bonding_curve, 134 | associated_bonding_curve, 135 | system_program: Program::try_from(system_program)?, 136 | token_program: Program::try_from(token_program)?, 137 | rent, 138 | event_authority, 139 | }) 140 | } 141 | 142 | fn cal_token_amount_out(&self, amount_in: u64) -> Result { 143 | let data = &self.bonding_curve.try_borrow_data()?; 144 | let virtual_token_reserves = u64::from_le_bytes(*array_ref![data, 8, 8]); 145 | let virtual_sol_reserves = u64::from_le_bytes(*array_ref![data, 16, 8]); 146 | let numerator = virtual_token_reserves as u128 * virtual_sol_reserves as u128; 147 | let denominator = virtual_sol_reserves as u128 + amount_in as u128; 148 | let amount_out = (virtual_token_reserves as u128) 149 | .checked_sub(numerator.checked_div(denominator).unwrap()).unwrap(); 150 | Ok(amount_out as u128) 151 | } 152 | } 153 | 154 | 155 | pub fn buy<'a>( 156 | remaining_accounts: &'a [AccountInfo<'a>], 157 | amount_in: u64, 158 | offset: &mut usize, 159 | hop_accounts: &mut HopAccounts, 160 | hop: usize, 161 | proxy_swap: bool, 162 | ) -> Result { 163 | msg!( 164 | "Dex::Pumpfun amount_in: {}, offset: {}", 165 | amount_in, 166 | offset 167 | ); 168 | require!( 169 | remaining_accounts.len() >= *offset + BUY_ACCOUNTS_LEN, 170 | ErrorCode::InvalidAccountsLength 171 | ); 172 | 173 | let mut swap_accounts = PumpfunBuyAccounts::parse_accounts(remaining_accounts, *offset)?; 174 | if swap_accounts.dex_program_id.key != &pumpfun_program::id() { 175 | return Err(ErrorCode::InvalidProgramId.into()); 176 | } 177 | 178 | pumpfun_before_check( 179 | swap_accounts.swap_authority_pubkey, 180 | swap_accounts.swap_source_token.key(), 181 | *swap_accounts.swap_source_token, 182 | swap_accounts.swap_destination_token.key(), 183 | hop_accounts, 184 | hop, 185 | proxy_swap, 186 | )?; 187 | 188 | let real_amount_in = amount_in.checked_mul(980009).unwrap().checked_div(1000000).unwrap(); 189 | let amount_out = swap_accounts.cal_token_amount_out(real_amount_in)? as u64; 190 | 191 | let mut data = Vec::with_capacity(ARGS_LEN); 192 | data.extend_from_slice(PUMPFUN_BUY_SELECTOR); 193 | data.extend_from_slice(&amount_out.to_le_bytes()); // token_amount_out 194 | data.extend_from_slice(&amount_in.to_le_bytes()); // max_amount_cost 195 | 196 | let accounts = vec![ 197 | AccountMeta::new_readonly(swap_accounts.global.key(), false), 198 | AccountMeta::new(swap_accounts.fee_recipient.key(), false), 199 | AccountMeta::new_readonly(swap_accounts.mint.key(), false), 200 | AccountMeta::new(swap_accounts.bonding_curve.key(), false), 201 | AccountMeta::new(swap_accounts.associated_bonding_curve.key(), false), 202 | AccountMeta::new(swap_accounts.swap_destination_token.key(), false), 203 | AccountMeta::new(swap_accounts.swap_authority_pubkey.key(), true), 204 | AccountMeta::new_readonly(swap_accounts.system_program.key(), false), 205 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 206 | AccountMeta::new_readonly(swap_accounts.rent.key(), false), 207 | AccountMeta::new_readonly(swap_accounts.event_authority.key(), false), 208 | AccountMeta::new_readonly(swap_accounts.dex_program_id.key(), false) 209 | ]; 210 | 211 | let account_infos = vec![ 212 | swap_accounts.global.to_account_info(), 213 | swap_accounts.fee_recipient.to_account_info(), 214 | swap_accounts.mint.to_account_info(), 215 | swap_accounts.bonding_curve.to_account_info(), 216 | swap_accounts.associated_bonding_curve.to_account_info(), 217 | swap_accounts.swap_destination_token.to_account_info(), 218 | swap_accounts.swap_authority_pubkey.to_account_info(), 219 | swap_accounts.system_program.to_account_info(), 220 | swap_accounts.token_program.to_account_info(), 221 | swap_accounts.rent.to_account_info(), 222 | swap_accounts.event_authority.to_account_info(), 223 | swap_accounts.dex_program_id.to_account_info(), 224 | swap_accounts.swap_source_token.to_account_info(), 225 | ]; 226 | 227 | let instruction = Instruction{ 228 | program_id: swap_accounts.dex_program_id.key(), 229 | accounts, 230 | data, 231 | }; 232 | 233 | let dex_processor = &PumpfunBuyProcessor; 234 | let amount_out = invoke_process( 235 | dex_processor, 236 | &account_infos, 237 | swap_accounts.swap_source_token.key(), 238 | &mut swap_accounts.swap_destination_token, 239 | hop_accounts, 240 | instruction, 241 | hop, 242 | offset, 243 | BUY_ACCOUNTS_LEN, 244 | false, 245 | )?; 246 | Ok(amount_out) 247 | } 248 | 249 | 250 | 251 | pub struct PumpfunSellProcessor { 252 | pub amount: u64, 253 | } 254 | 255 | impl DexProcessor for PumpfunSellProcessor { 256 | 257 | fn before_invoke(&self, account_infos: &[AccountInfo]) -> Result { 258 | let destination_token_account = account_infos.last().unwrap(); 259 | let balance = destination_token_account.get_lamports(); 260 | Ok(balance) 261 | } 262 | 263 | fn after_invoke(&self, account_infos: &[AccountInfo]) -> Result { 264 | let destination_token_account = account_infos.last().unwrap(); 265 | let user = account_infos.get(6).unwrap(); 266 | let token_program = account_infos.get(9).unwrap(); 267 | transfer_sol_from_user( 268 | user.to_account_info(), 269 | destination_token_account.to_account_info(), 270 | self.amount, 271 | )?; 272 | 273 | let sync_native_ix = sync_native( 274 | &token_program.key(), 275 | &destination_token_account.key(), 276 | )?; 277 | invoke(&sync_native_ix, &[destination_token_account.to_account_info(), token_program.to_account_info()])?; 278 | Ok(self.amount) 279 | } 280 | } 281 | 282 | 283 | pub struct PumpfunSellAccounts<'info> { 284 | pub dex_program_id: &'info AccountInfo<'info>, 285 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 286 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 287 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 288 | pub global: &'info AccountInfo<'info>, 289 | pub fee_recipient: &'info AccountInfo<'info>, 290 | pub mint: InterfaceAccount<'info, Mint>, 291 | pub bonding_curve: &'info AccountInfo<'info>, 292 | pub associated_bonding_curve: &'info AccountInfo<'info>, 293 | pub system_program: Program<'info, System>, 294 | pub associated_token_program: Program<'info, AssociatedToken>, 295 | pub token_program: Program<'info, Token>, 296 | pub event_authority: &'info AccountInfo<'info>, 297 | 298 | } 299 | 300 | const SELL_ACCOUNTS_LEN: usize = 13; 301 | 302 | impl<'info> PumpfunSellAccounts<'info> { 303 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 304 | let [ 305 | dex_program_id, 306 | swap_authority_pubkey, 307 | swap_source_token, 308 | swap_destination_token, 309 | global, 310 | fee_recipient, 311 | mint, 312 | bonding_curve, 313 | associated_bonding_curve, 314 | system_program, 315 | associated_token_program, 316 | token_program, 317 | event_authority, 318 | 319 | ]: &[AccountInfo<'info>; SELL_ACCOUNTS_LEN] = array_ref![accounts, offset, SELL_ACCOUNTS_LEN]; 320 | 321 | Ok(Self { 322 | dex_program_id, 323 | swap_authority_pubkey, 324 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 325 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 326 | global, 327 | fee_recipient, 328 | mint: InterfaceAccount::try_from(mint)?, 329 | bonding_curve, 330 | associated_bonding_curve, 331 | system_program: Program::try_from(system_program)?, 332 | associated_token_program: Program::try_from(associated_token_program)?, 333 | token_program: Program::try_from(token_program)?, 334 | event_authority, 335 | 336 | }) 337 | } 338 | 339 | fn cal_sol_amount_out(&self, token_amount_in: u64) -> Result { 340 | let data = &self.bonding_curve.try_borrow_data()?; 341 | let virtual_token_reserves = u64::from_le_bytes(*array_ref![data, 8, 8]); 342 | let virtual_sol_reserves = u64::from_le_bytes(*array_ref![data, 16, 8]); 343 | let numerator = virtual_token_reserves as u128 * virtual_sol_reserves as u128; 344 | 345 | let sol_amount_out = numerator.checked_div(virtual_token_reserves as u128 - token_amount_in as u128).unwrap() - virtual_sol_reserves as u128; 346 | Ok(sol_amount_out) 347 | } 348 | } 349 | 350 | 351 | 352 | pub fn sell<'a>( 353 | remaining_accounts: &'a [AccountInfo<'a>], 354 | amount_in: u64, 355 | offset: &mut usize, 356 | hop_accounts: &mut HopAccounts, 357 | hop: usize, 358 | proxy_swap: bool, 359 | ) -> Result { 360 | msg!( 361 | "Dex::Pumpfun amount_in: {}, offset: {}", 362 | amount_in, 363 | offset 364 | ); 365 | require!( 366 | remaining_accounts.len() >= *offset + SELL_ACCOUNTS_LEN, 367 | ErrorCode::InvalidAccountsLength 368 | ); 369 | 370 | let mut swap_accounts = PumpfunSellAccounts::parse_accounts(remaining_accounts, *offset)?; 371 | if swap_accounts.dex_program_id.key != &pumpfun_program::id() { 372 | return Err(ErrorCode::InvalidProgramId.into()); 373 | } 374 | 375 | before_check( 376 | swap_accounts.swap_authority_pubkey, 377 | swap_accounts.swap_source_token.key(), 378 | swap_accounts.swap_destination_token.key(), 379 | hop_accounts, 380 | hop, 381 | proxy_swap 382 | )?; 383 | 384 | let sol_amount_out = swap_accounts.cal_sol_amount_out(amount_in)? as u64; 385 | let min_sol_amount_out = sol_amount_out.checked_mul(980009).unwrap().checked_div(1000000).unwrap(); 386 | 387 | let mut data = Vec::with_capacity(ARGS_LEN); 388 | data.extend_from_slice(PUMPFUN_SELL_SELECTOR); 389 | data.extend_from_slice(&amount_in.to_le_bytes()); // token_amount_in 390 | data.extend_from_slice(&min_sol_amount_out.to_le_bytes()); // min_sol_amount_out 391 | 392 | 393 | let accounts = vec![ 394 | AccountMeta::new_readonly(swap_accounts.global.key(), false), 395 | AccountMeta::new(swap_accounts.fee_recipient.key(), false), 396 | AccountMeta::new_readonly(swap_accounts.mint.key(), false), 397 | AccountMeta::new(swap_accounts.bonding_curve.key(), false), 398 | AccountMeta::new(swap_accounts.associated_bonding_curve.key(), false), 399 | AccountMeta::new(swap_accounts.swap_source_token.key(), false), 400 | AccountMeta::new(swap_accounts.swap_authority_pubkey.key(), true), 401 | AccountMeta::new_readonly(swap_accounts.system_program.key(), false), 402 | AccountMeta::new_readonly(swap_accounts.associated_token_program.key(), false), 403 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 404 | AccountMeta::new_readonly(swap_accounts.event_authority.key(), false), 405 | AccountMeta::new_readonly(swap_accounts.dex_program_id.key(), false), 406 | ]; 407 | 408 | let account_infos = vec![ 409 | swap_accounts.global.to_account_info(), 410 | swap_accounts.fee_recipient.to_account_info(), 411 | swap_accounts.mint.to_account_info(), 412 | swap_accounts.bonding_curve.to_account_info(), 413 | swap_accounts.associated_bonding_curve.to_account_info(), 414 | swap_accounts.swap_source_token.to_account_info(), 415 | swap_accounts.swap_authority_pubkey.to_account_info(), 416 | swap_accounts.system_program.to_account_info(), 417 | swap_accounts.associated_token_program.to_account_info(), 418 | swap_accounts.token_program.to_account_info(), 419 | swap_accounts.event_authority.to_account_info(), 420 | swap_accounts.dex_program_id.to_account_info(), 421 | swap_accounts.swap_destination_token.to_account_info(), 422 | ]; 423 | 424 | let instruction = Instruction{ 425 | program_id: swap_accounts.dex_program_id.key(), 426 | accounts, 427 | data, 428 | }; 429 | let dex_processor = &PumpfunSellProcessor{amount: min_sol_amount_out}; 430 | 431 | let amount_out = invoke_process( 432 | dex_processor, 433 | &account_infos, 434 | swap_accounts.swap_source_token.key(), 435 | &mut swap_accounts.swap_destination_token, 436 | hop_accounts, 437 | instruction, 438 | hop, 439 | offset, 440 | SELL_ACCOUNTS_LEN, 441 | false, 442 | )?; 443 | Ok(amount_out) 444 | } -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/spl_token_swap.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::HopAccounts; 4 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 5 | use anchor_spl::token::Token; 6 | use anchor_spl::token_interface::TokenAccount; 7 | use arrayref::array_ref; 8 | 9 | use super::common::DexProcessor; 10 | 11 | const ARGS_LEN: usize = 17; 12 | 13 | pub struct SplTokenSwapProcessor; 14 | impl DexProcessor for SplTokenSwapProcessor {} 15 | 16 | pub struct SplTokenSwapAccounts<'info> { 17 | pub dex_program_id: &'info AccountInfo<'info>, 18 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 19 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 20 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 21 | 22 | pub swap_info: &'info AccountInfo<'info>, 23 | pub authority_acc_info: &'info AccountInfo<'info>, 24 | pub token_a_account: InterfaceAccount<'info, TokenAccount>, 25 | pub token_b_account: InterfaceAccount<'info, TokenAccount>, 26 | pub pool_mint: &'info AccountInfo<'info>, 27 | pub pool_fee: InterfaceAccount<'info, TokenAccount>, 28 | pub token_program: Program<'info, Token>, 29 | } 30 | const ACCOUNTS_LEN: usize = 11; 31 | 32 | impl<'info> SplTokenSwapAccounts<'info> { 33 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 34 | let [ 35 | dex_program_id, 36 | swap_authority_pubkey, 37 | swap_source_token, 38 | swap_destination_token, 39 | swap_info, 40 | authority_acc_info, 41 | token_a_account, 42 | token_b_account, 43 | pool_mint, 44 | pool_fee, 45 | token_program, 46 | ]: &[AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 47 | Ok(Self { 48 | dex_program_id, 49 | swap_authority_pubkey, 50 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 51 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 52 | swap_info, 53 | authority_acc_info, 54 | token_a_account: InterfaceAccount::try_from(token_a_account)?, 55 | token_b_account: InterfaceAccount::try_from(token_b_account)?, 56 | pool_mint, 57 | pool_fee: InterfaceAccount::try_from(pool_fee)?, 58 | token_program: Program::try_from(token_program)?, 59 | }) 60 | } 61 | } 62 | 63 | pub fn swap<'a>( 64 | remaining_accounts: &'a [AccountInfo<'a>], 65 | amount_in: u64, 66 | offset: &mut usize, 67 | hop_accounts: &mut HopAccounts, 68 | hop: usize, 69 | proxy_swap: bool, 70 | ) -> Result { 71 | msg!( 72 | "Dex::SplTokenSwap amount_in: {}, offset: {}", 73 | amount_in, 74 | offset 75 | ); 76 | require!( 77 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 78 | ErrorCode::InvalidAccountsLength 79 | ); 80 | let mut swap_accounts = SplTokenSwapAccounts::parse_accounts(remaining_accounts, *offset)?; 81 | 82 | // check hop accounts & swap authority 83 | let swap_source_token = swap_accounts.swap_source_token.key(); 84 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 85 | before_check( 86 | &swap_accounts.swap_authority_pubkey, 87 | swap_source_token, 88 | swap_destination_token, 89 | hop_accounts, 90 | hop, 91 | proxy_swap, 92 | )?; 93 | 94 | let pool_source_pubkey; 95 | let pool_destination_pubkey; 96 | if (swap_accounts.swap_source_token.mint == swap_accounts.token_a_account.mint) 97 | && (swap_accounts.swap_destination_token.mint == swap_accounts.token_b_account.mint) 98 | { 99 | pool_source_pubkey = swap_accounts.token_a_account.key(); 100 | pool_destination_pubkey = swap_accounts.token_b_account.key(); 101 | } else if (swap_accounts.swap_source_token.mint == swap_accounts.token_b_account.mint) 102 | && (swap_accounts.swap_destination_token.mint == swap_accounts.token_a_account.mint) 103 | { 104 | pool_source_pubkey = swap_accounts.token_b_account.key(); 105 | pool_destination_pubkey = swap_accounts.token_a_account.key(); 106 | } else { 107 | return Err(ErrorCode::InvalidPool.into()); 108 | } 109 | 110 | let mut data = Vec::with_capacity(ARGS_LEN); 111 | data.push(1); 112 | data.extend_from_slice(&amount_in.to_le_bytes()); 113 | data.extend_from_slice(&1u64.to_le_bytes()); 114 | 115 | let accounts = vec![ 116 | AccountMeta::new_readonly(swap_accounts.swap_info.key(), false), 117 | AccountMeta::new_readonly(swap_accounts.authority_acc_info.key(), false), 118 | AccountMeta::new(swap_accounts.swap_authority_pubkey.key(), true), 119 | AccountMeta::new(swap_source_token, false), 120 | AccountMeta::new(pool_source_pubkey, false), 121 | AccountMeta::new(pool_destination_pubkey, false), 122 | AccountMeta::new(swap_destination_token, false), 123 | AccountMeta::new(swap_accounts.pool_mint.key(), false), 124 | AccountMeta::new(swap_accounts.pool_fee.key(), false), 125 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 126 | ]; 127 | 128 | let account_infos = vec![ 129 | swap_accounts.swap_info.to_account_info(), 130 | swap_accounts.authority_acc_info.to_account_info(), 131 | swap_accounts.swap_authority_pubkey.to_account_info(), 132 | swap_accounts.swap_source_token.to_account_info(), 133 | swap_accounts.token_a_account.to_account_info(), 134 | swap_accounts.token_b_account.to_account_info(), 135 | swap_accounts.swap_destination_token.to_account_info(), 136 | swap_accounts.pool_mint.to_account_info(), 137 | swap_accounts.pool_fee.to_account_info(), 138 | swap_accounts.token_program.to_account_info(), 139 | ]; 140 | 141 | let instruction = Instruction { 142 | program_id: swap_accounts.dex_program_id.key(), 143 | accounts, 144 | data, 145 | }; 146 | 147 | let dex_processor = &SplTokenSwapProcessor; 148 | let amount_out = invoke_process( 149 | dex_processor, 150 | &account_infos, 151 | swap_source_token, 152 | &mut swap_accounts.swap_destination_token, 153 | hop_accounts, 154 | instruction, 155 | hop, 156 | offset, 157 | ACCOUNTS_LEN, 158 | proxy_swap, 159 | )?; 160 | Ok(amount_out) 161 | } 162 | 163 | #[cfg(test)] 164 | mod tests { 165 | use super::*; 166 | 167 | #[test] 168 | pub fn test_pack_swap_instruction() { 169 | let amount_in = 100u64; 170 | let mut data = Vec::with_capacity(ARGS_LEN); 171 | data.push(1); 172 | data.extend_from_slice(&amount_in.to_le_bytes()); 173 | data.extend_from_slice(&1u64.to_le_bytes()); 174 | 175 | msg!("data.len: {}", data.len()); 176 | assert!(data.len() == ARGS_LEN); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/stable_swap.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::HopAccounts; 4 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 5 | use anchor_spl::token::Token; 6 | use anchor_spl::token_interface::TokenAccount; 7 | use arrayref::array_ref; 8 | 9 | use super::common::DexProcessor; 10 | 11 | const ARGS_LEN: usize = 17; 12 | 13 | pub struct StableSwapProcessor; 14 | impl DexProcessor for StableSwapProcessor {} 15 | 16 | pub struct StableSwapAccounts<'info> { 17 | pub dex_program_id: &'info AccountInfo<'info>, 18 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 19 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 20 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 21 | 22 | pub swap_info: &'info AccountInfo<'info>, 23 | pub swap_authority: &'info AccountInfo<'info>, 24 | pub token_a_account: InterfaceAccount<'info, TokenAccount>, 25 | pub token_b_account: InterfaceAccount<'info, TokenAccount>, 26 | pub swap_admin_fee: InterfaceAccount<'info, TokenAccount>, 27 | pub token_program: Program<'info, Token>, 28 | } 29 | const ACCOUNTS_LEN: usize = 10; 30 | 31 | impl<'info> StableSwapAccounts<'info> { 32 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 33 | let [ 34 | dex_program_id, 35 | swap_authority_pubkey, 36 | swap_source_token, 37 | swap_destination_token, 38 | swap_info, 39 | swap_authority, 40 | token_a_account, 41 | token_b_account, 42 | swap_admin_fee, 43 | token_program, 44 | ]: & [AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 45 | Ok(Self { 46 | dex_program_id, 47 | swap_authority_pubkey, 48 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 49 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 50 | swap_info, 51 | swap_authority, 52 | token_a_account: InterfaceAccount::try_from(token_a_account)?, 53 | token_b_account: InterfaceAccount::try_from(token_b_account)?, 54 | swap_admin_fee: InterfaceAccount::try_from(swap_admin_fee)?, 55 | token_program: Program::try_from(token_program)?, 56 | }) 57 | } 58 | } 59 | 60 | pub fn swap<'a>( 61 | remaining_accounts: &'a [AccountInfo<'a>], 62 | amount_in: u64, 63 | offset: &mut usize, 64 | hop_accounts: &mut HopAccounts, 65 | hop: usize, 66 | proxy_swap: bool, 67 | ) -> Result { 68 | msg!( 69 | "Dex::StableSwap amount_in: {}, offset: {}", 70 | amount_in, 71 | offset 72 | ); 73 | require!( 74 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 75 | ErrorCode::InvalidAccountsLength 76 | ); 77 | let mut swap_accounts = StableSwapAccounts::parse_accounts(remaining_accounts, *offset)?; 78 | 79 | // check hop accounts & swap authority 80 | let swap_source_token = swap_accounts.swap_source_token.key(); 81 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 82 | before_check( 83 | &swap_accounts.swap_authority_pubkey, 84 | swap_source_token, 85 | swap_destination_token, 86 | hop_accounts, 87 | hop, 88 | proxy_swap, 89 | )?; 90 | 91 | let pool_source_pubkey; 92 | let pool_destination_pubkeyy; 93 | if (swap_accounts.swap_source_token.mint == swap_accounts.token_a_account.mint) 94 | && (swap_accounts.swap_destination_token.mint == swap_accounts.token_b_account.mint) 95 | { 96 | pool_source_pubkey = swap_accounts.token_a_account.key(); 97 | pool_destination_pubkeyy = swap_accounts.token_b_account.key(); 98 | } else if (swap_accounts.swap_source_token.mint == swap_accounts.token_b_account.mint) 99 | && (swap_accounts.swap_destination_token.mint == swap_accounts.token_a_account.mint) 100 | { 101 | pool_source_pubkey = swap_accounts.token_b_account.key(); 102 | pool_destination_pubkeyy = swap_accounts.token_a_account.key(); 103 | } else { 104 | return Err(ErrorCode::InvalidPool.into()); 105 | } 106 | 107 | let mut data = Vec::with_capacity(ARGS_LEN); 108 | data.push(1); 109 | data.extend_from_slice(&amount_in.to_le_bytes()); 110 | data.extend_from_slice(&1u64.to_le_bytes()); 111 | 112 | let accounts = vec![ 113 | AccountMeta::new_readonly(swap_accounts.swap_info.key(), false), 114 | AccountMeta::new_readonly(swap_accounts.swap_authority.key(), false), 115 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 116 | AccountMeta::new(swap_source_token, false), 117 | AccountMeta::new(pool_source_pubkey, false), 118 | AccountMeta::new(pool_destination_pubkeyy, false), 119 | AccountMeta::new(swap_destination_token, false), 120 | AccountMeta::new(swap_accounts.swap_admin_fee.key(), false), 121 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 122 | ]; 123 | 124 | let account_infos = vec![ 125 | swap_accounts.swap_info.to_account_info(), 126 | swap_accounts.swap_authority.to_account_info(), 127 | swap_accounts.swap_authority_pubkey.to_account_info(), 128 | swap_accounts.swap_source_token.to_account_info(), 129 | swap_accounts.token_a_account.to_account_info(), 130 | swap_accounts.token_b_account.to_account_info(), 131 | swap_accounts.swap_destination_token.to_account_info(), 132 | swap_accounts.swap_admin_fee.to_account_info(), 133 | swap_accounts.token_program.to_account_info(), 134 | ]; 135 | 136 | let instruction = Instruction { 137 | program_id: swap_accounts.dex_program_id.key(), 138 | accounts, 139 | data, 140 | }; 141 | 142 | let dex_processor = &StableSwapProcessor; 143 | let amount_out = invoke_process( 144 | dex_processor, 145 | &account_infos, 146 | swap_source_token, 147 | &mut swap_accounts.swap_destination_token, 148 | hop_accounts, 149 | instruction, 150 | hop, 151 | offset, 152 | ACCOUNTS_LEN, 153 | proxy_swap, 154 | )?; 155 | Ok(amount_out) 156 | } 157 | 158 | #[cfg(test)] 159 | mod tests { 160 | use super::*; 161 | 162 | #[test] 163 | pub fn test_pack_swap_instruction() { 164 | let amount_in = 100u64; 165 | let mut data = Vec::with_capacity(ARGS_LEN); 166 | data.push(1); 167 | data.extend_from_slice(&amount_in.to_le_bytes()); 168 | data.extend_from_slice(&1u64.to_le_bytes()); 169 | 170 | msg!("data.len: {}", data.len()); 171 | assert!(data.len() == ARGS_LEN); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /programs/dex-solana/src/adapters/whirlpool.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::common::{before_check, invoke_process}; 2 | use crate::error::ErrorCode; 3 | use crate::{whirlpool_program, HopAccounts, SWAP_SELECTOR, SWAP_V2_SELECTOR}; 4 | use anchor_lang::{prelude::*, solana_program::instruction::Instruction}; 5 | use anchor_spl::token::Token; 6 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 7 | use arrayref::array_ref; 8 | 9 | use super::common::DexProcessor; 10 | 11 | const ARGS_LEN: usize = 42; 12 | const ARGS_V2_LEN: usize = 43; 13 | 14 | pub struct WhirlpoolProcessor; 15 | impl DexProcessor for WhirlpoolProcessor {} 16 | 17 | pub struct WhirlpoolAccounts<'info> { 18 | pub dex_program_id: &'info AccountInfo<'info>, 19 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 20 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 21 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 22 | 23 | pub token_program: Program<'info, Token>, 24 | pub whirlpool: &'info AccountInfo<'info>, 25 | pub token_vault_a: InterfaceAccount<'info, TokenAccount>, 26 | pub token_vault_b: InterfaceAccount<'info, TokenAccount>, 27 | pub tick_array0: &'info AccountInfo<'info>, 28 | pub tick_array1: &'info AccountInfo<'info>, 29 | pub tick_array2: &'info AccountInfo<'info>, 30 | pub oracle: &'info AccountInfo<'info>, 31 | } 32 | const ACCOUNTS_LEN: usize = 12; 33 | 34 | pub struct WhirlpoolV2Accounts<'info> { 35 | pub dex_program_id: &'info AccountInfo<'info>, 36 | pub swap_authority_pubkey: &'info AccountInfo<'info>, 37 | pub swap_source_token: InterfaceAccount<'info, TokenAccount>, 38 | pub swap_destination_token: InterfaceAccount<'info, TokenAccount>, 39 | 40 | pub token_program_a: Interface<'info, TokenInterface>, 41 | pub token_program_b: Interface<'info, TokenInterface>, 42 | pub memo_program: &'info AccountInfo<'info>, 43 | pub whirlpool: &'info AccountInfo<'info>, 44 | pub token_mint_a: InterfaceAccount<'info, Mint>, 45 | pub token_mint_b: InterfaceAccount<'info, Mint>, 46 | pub token_vault_a: InterfaceAccount<'info, TokenAccount>, 47 | pub token_vault_b: InterfaceAccount<'info, TokenAccount>, 48 | pub tick_array0: &'info AccountInfo<'info>, 49 | pub tick_array1: &'info AccountInfo<'info>, 50 | pub tick_array2: &'info AccountInfo<'info>, 51 | pub oracle: &'info AccountInfo<'info>, 52 | } 53 | const ACCOUNTS_V2_LEN: usize = 16; 54 | 55 | impl<'info> WhirlpoolAccounts<'info> { 56 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 57 | let [ 58 | dex_program_id, 59 | swap_authority_pubkey, 60 | swap_source_token, 61 | swap_destination_token, 62 | token_program, 63 | whirlpool, 64 | token_vault_a, 65 | token_vault_b, 66 | tick_array0, 67 | tick_array1, 68 | tick_array2, 69 | oracle, 70 | ]: & [AccountInfo<'info>; ACCOUNTS_LEN] = array_ref![accounts, offset, ACCOUNTS_LEN]; 71 | Ok(Self { 72 | dex_program_id, 73 | swap_authority_pubkey, 74 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 75 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 76 | token_program: Program::try_from(token_program)?, 77 | whirlpool, 78 | token_vault_a: InterfaceAccount::try_from(token_vault_a)?, 79 | token_vault_b: InterfaceAccount::try_from(token_vault_b)?, 80 | tick_array0, 81 | tick_array1, 82 | tick_array2, 83 | oracle, 84 | }) 85 | } 86 | } 87 | 88 | impl<'info> WhirlpoolV2Accounts<'info> { 89 | fn parse_accounts(accounts: &'info [AccountInfo<'info>], offset: usize) -> Result { 90 | let [ 91 | dex_program_id, 92 | swap_authority_pubkey, 93 | swap_source_token, 94 | swap_destination_token, 95 | token_program_a, 96 | token_program_b, 97 | memo_program, 98 | whirlpool, 99 | token_mint_a, 100 | token_mint_b, 101 | token_vault_a, 102 | token_vault_b, 103 | tick_array0, 104 | tick_array1, 105 | tick_array2, 106 | oracle, 107 | ]: & [AccountInfo<'info>; ACCOUNTS_V2_LEN] = array_ref![accounts, offset, ACCOUNTS_V2_LEN]; 108 | Ok(Self { 109 | dex_program_id, 110 | swap_authority_pubkey, 111 | swap_source_token: InterfaceAccount::try_from(swap_source_token)?, 112 | swap_destination_token: InterfaceAccount::try_from(swap_destination_token)?, 113 | token_program_a: Interface::try_from(token_program_a)?, 114 | token_program_b: Interface::try_from(token_program_b)?, 115 | memo_program, 116 | whirlpool, 117 | token_mint_a: InterfaceAccount::try_from(token_mint_a)?, 118 | token_mint_b: InterfaceAccount::try_from(token_mint_b)?, 119 | token_vault_a: InterfaceAccount::try_from(token_vault_a)?, 120 | token_vault_b: InterfaceAccount::try_from(token_vault_b)?, 121 | tick_array0, 122 | tick_array1, 123 | tick_array2, 124 | oracle, 125 | }) 126 | } 127 | } 128 | 129 | pub fn swap<'a>( 130 | remaining_accounts: &'a [AccountInfo<'a>], 131 | amount_in: u64, 132 | offset: &mut usize, 133 | hop_accounts: &mut HopAccounts, 134 | hop: usize, 135 | proxy_swap: bool, 136 | ) -> Result { 137 | msg!( 138 | "Dex::Whirlpool amount_in: {}, offset: {}", 139 | amount_in, 140 | offset 141 | ); 142 | require!( 143 | remaining_accounts.len() >= *offset + ACCOUNTS_LEN, 144 | ErrorCode::InvalidAccountsLength 145 | ); 146 | let mut swap_accounts = WhirlpoolAccounts::parse_accounts(remaining_accounts, *offset)?; 147 | if swap_accounts.dex_program_id.key != &whirlpool_program::id() { 148 | return Err(ErrorCode::InvalidProgramId.into()); 149 | } 150 | 151 | // check hop accounts & swap authority 152 | let swap_source_token = swap_accounts.swap_source_token.key(); 153 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 154 | before_check( 155 | &swap_accounts.swap_authority_pubkey, 156 | swap_source_token, 157 | swap_destination_token, 158 | hop_accounts, 159 | hop, 160 | proxy_swap, 161 | )?; 162 | 163 | let amount_specified_is_input = true; 164 | let other_amount_threshold = 1u64; 165 | let a_to_b: bool; 166 | let sqrt_price_limit: i128; 167 | if swap_accounts.swap_source_token.mint == swap_accounts.token_vault_a.mint 168 | && swap_accounts.swap_destination_token.mint == swap_accounts.token_vault_b.mint 169 | { 170 | a_to_b = true; 171 | sqrt_price_limit = 4295048016; //The minimum sqrt-price supported by the Whirlpool program. 172 | } else if swap_accounts.swap_source_token.mint == swap_accounts.token_vault_b.mint 173 | && swap_accounts.swap_destination_token.mint == swap_accounts.token_vault_a.mint 174 | { 175 | a_to_b = false; 176 | sqrt_price_limit = 79226673515401279992447579055; //The maximum sqrt-price supported by the Whirlpool program. 177 | } else { 178 | return Err(ErrorCode::InvalidTokenMint.into()); 179 | } 180 | let (token_owner_account_a, token_owner_account_b) = if a_to_b { 181 | ( 182 | swap_accounts.swap_source_token, 183 | swap_accounts.swap_destination_token.clone(), 184 | ) 185 | } else { 186 | ( 187 | swap_accounts.swap_destination_token.clone(), 188 | swap_accounts.swap_source_token, 189 | ) 190 | }; 191 | 192 | let mut data = Vec::with_capacity(ARGS_LEN); 193 | data.extend_from_slice(SWAP_SELECTOR); 194 | data.extend_from_slice(&amount_in.to_le_bytes()); 195 | data.extend_from_slice(&other_amount_threshold.to_le_bytes()); 196 | data.extend_from_slice(&sqrt_price_limit.to_le_bytes()); 197 | data.extend_from_slice(&(amount_specified_is_input as u8).to_le_bytes()); 198 | data.extend_from_slice(&(a_to_b as u8).to_le_bytes()); 199 | 200 | let accounts = vec![ 201 | AccountMeta::new_readonly(swap_accounts.token_program.key(), false), 202 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 203 | AccountMeta::new(swap_accounts.whirlpool.key(), false), 204 | AccountMeta::new(token_owner_account_a.key(), false), 205 | AccountMeta::new(swap_accounts.token_vault_a.key(), false), 206 | AccountMeta::new(token_owner_account_b.key(), false), 207 | AccountMeta::new(swap_accounts.token_vault_b.key(), false), 208 | AccountMeta::new(swap_accounts.tick_array0.key(), false), 209 | AccountMeta::new(swap_accounts.tick_array1.key(), false), 210 | AccountMeta::new(swap_accounts.tick_array2.key(), false), 211 | AccountMeta::new_readonly(swap_accounts.oracle.key(), false), 212 | ]; 213 | 214 | let account_infos = vec![ 215 | swap_accounts.token_program.to_account_info(), 216 | swap_accounts.swap_authority_pubkey.to_account_info(), 217 | swap_accounts.whirlpool.to_account_info(), 218 | token_owner_account_a.to_account_info(), 219 | swap_accounts.token_vault_a.to_account_info(), 220 | token_owner_account_b.to_account_info(), 221 | swap_accounts.token_vault_b.to_account_info(), 222 | swap_accounts.tick_array0.to_account_info(), 223 | swap_accounts.tick_array1.to_account_info(), 224 | swap_accounts.tick_array2.to_account_info(), 225 | swap_accounts.oracle.to_account_info(), 226 | ]; 227 | 228 | let instruction = Instruction { 229 | program_id: swap_accounts.dex_program_id.key(), 230 | accounts, 231 | data, 232 | }; 233 | 234 | let dex_processor = &WhirlpoolProcessor; 235 | let amount_out = invoke_process( 236 | dex_processor, 237 | &account_infos, 238 | swap_source_token, 239 | &mut swap_accounts.swap_destination_token, 240 | hop_accounts, 241 | instruction, 242 | hop, 243 | offset, 244 | ACCOUNTS_LEN, 245 | proxy_swap, 246 | )?; 247 | Ok(amount_out) 248 | } 249 | 250 | pub fn swap_v2<'a>( 251 | remaining_accounts: &'a [AccountInfo<'a>], 252 | amount_in: u64, 253 | offset: &mut usize, 254 | hop_accounts: &mut HopAccounts, 255 | hop: usize, 256 | proxy_swap: bool, 257 | ) -> Result { 258 | msg!( 259 | "Dex::WhirlpoolV2 amount_in: {}, offset: {}", 260 | amount_in, 261 | offset 262 | ); 263 | require!( 264 | remaining_accounts.len() >= *offset + ACCOUNTS_V2_LEN, 265 | ErrorCode::InvalidAccountsLength 266 | ); 267 | let mut swap_accounts = WhirlpoolV2Accounts::parse_accounts(remaining_accounts, *offset)?; 268 | if swap_accounts.dex_program_id.key != &whirlpool_program::id() { 269 | return Err(ErrorCode::InvalidProgramId.into()); 270 | } 271 | 272 | // check hop accounts & swap authority 273 | let swap_source_token = swap_accounts.swap_source_token.key(); 274 | let swap_destination_token = swap_accounts.swap_destination_token.key(); 275 | before_check( 276 | &swap_accounts.swap_authority_pubkey, 277 | swap_source_token, 278 | swap_destination_token, 279 | hop_accounts, 280 | hop, 281 | proxy_swap, 282 | )?; 283 | 284 | let amount_specified_is_input = true; 285 | let other_amount_threshold = 1u64; 286 | let a_to_b: bool; 287 | let sqrt_price_limit: i128; 288 | if swap_accounts.swap_source_token.mint == swap_accounts.token_vault_a.mint 289 | && swap_accounts.swap_destination_token.mint == swap_accounts.token_vault_b.mint 290 | { 291 | a_to_b = true; 292 | sqrt_price_limit = 4295048016; //The minimum sqrt-price supported by the Whirlpool program. 293 | } else if swap_accounts.swap_source_token.mint == swap_accounts.token_vault_b.mint 294 | && swap_accounts.swap_destination_token.mint == swap_accounts.token_vault_a.mint 295 | { 296 | a_to_b = false; 297 | sqrt_price_limit = 79226673515401279992447579055; //The maximum sqrt-price supported by the Whirlpool program. 298 | } else { 299 | return Err(ErrorCode::InvalidTokenMint.into()); 300 | } 301 | let (token_owner_account_a, token_owner_account_b) = if a_to_b { 302 | ( 303 | swap_accounts.swap_source_token, 304 | swap_accounts.swap_destination_token.clone(), 305 | ) 306 | } else { 307 | ( 308 | swap_accounts.swap_destination_token.clone(), 309 | swap_accounts.swap_source_token, 310 | ) 311 | }; 312 | 313 | let mut data = Vec::with_capacity(ARGS_V2_LEN); 314 | data.extend_from_slice(SWAP_V2_SELECTOR); 315 | data.extend_from_slice(&amount_in.to_le_bytes()); 316 | data.extend_from_slice(&other_amount_threshold.to_le_bytes()); 317 | data.extend_from_slice(&sqrt_price_limit.to_le_bytes()); 318 | data.extend_from_slice(&(amount_specified_is_input as u8).to_le_bytes()); 319 | data.extend_from_slice(&(a_to_b as u8).to_le_bytes()); 320 | data.extend_from_slice(&(0u8).to_le_bytes()); 321 | 322 | let accounts = vec![ 323 | AccountMeta::new_readonly(swap_accounts.token_program_a.key(), false), 324 | AccountMeta::new_readonly(swap_accounts.token_program_b.key(), false), 325 | AccountMeta::new_readonly(swap_accounts.memo_program.key(), false), 326 | AccountMeta::new_readonly(swap_accounts.swap_authority_pubkey.key(), true), 327 | AccountMeta::new(swap_accounts.whirlpool.key(), false), 328 | AccountMeta::new_readonly(swap_accounts.token_mint_a.key(), false), 329 | AccountMeta::new_readonly(swap_accounts.token_mint_b.key(), false), 330 | AccountMeta::new(token_owner_account_a.key(), false), 331 | AccountMeta::new(swap_accounts.token_vault_a.key(), false), 332 | AccountMeta::new(token_owner_account_b.key(), false), 333 | AccountMeta::new(swap_accounts.token_vault_b.key(), false), 334 | AccountMeta::new(swap_accounts.tick_array0.key(), false), 335 | AccountMeta::new(swap_accounts.tick_array1.key(), false), 336 | AccountMeta::new(swap_accounts.tick_array2.key(), false), 337 | AccountMeta::new(swap_accounts.oracle.key(), false), 338 | ]; 339 | 340 | let account_infos = vec![ 341 | swap_accounts.token_program_a.to_account_info(), 342 | swap_accounts.token_program_b.to_account_info(), 343 | swap_accounts.memo_program.to_account_info(), 344 | swap_accounts.swap_authority_pubkey.to_account_info(), 345 | swap_accounts.whirlpool.to_account_info(), 346 | swap_accounts.token_mint_a.to_account_info(), 347 | swap_accounts.token_mint_b.to_account_info(), 348 | token_owner_account_a.to_account_info(), 349 | swap_accounts.token_vault_a.to_account_info(), 350 | token_owner_account_b.to_account_info(), 351 | swap_accounts.token_vault_b.to_account_info(), 352 | swap_accounts.tick_array0.to_account_info(), 353 | swap_accounts.tick_array1.to_account_info(), 354 | swap_accounts.tick_array2.to_account_info(), 355 | swap_accounts.oracle.to_account_info(), 356 | ]; 357 | 358 | let instruction = Instruction { 359 | program_id: swap_accounts.dex_program_id.key(), 360 | accounts, 361 | data, 362 | }; 363 | 364 | let dex_processor = &WhirlpoolProcessor; 365 | let amount_out = invoke_process( 366 | dex_processor, 367 | &account_infos, 368 | swap_source_token, 369 | &mut swap_accounts.swap_destination_token, 370 | hop_accounts, 371 | instruction, 372 | hop, 373 | offset, 374 | ACCOUNTS_V2_LEN, 375 | proxy_swap, 376 | )?; 377 | Ok(amount_out) 378 | } 379 | 380 | #[cfg(test)] 381 | mod tests { 382 | use super::*; 383 | 384 | #[test] 385 | pub fn test_pack_swap_instruction() { 386 | let amount_in = 100u64; 387 | let amount_specified_is_input = true; 388 | let other_amount_threshold = 1u64; 389 | let a_to_b = true; 390 | let sqrt_price_limit = 0u128; 391 | 392 | let mut data = Vec::with_capacity(ARGS_LEN); 393 | data.extend_from_slice(SWAP_SELECTOR); 394 | data.extend_from_slice(&amount_in.to_le_bytes()); 395 | data.extend_from_slice(&other_amount_threshold.to_le_bytes()); 396 | data.extend_from_slice(&sqrt_price_limit.to_le_bytes()); 397 | data.extend_from_slice(&(amount_specified_is_input as u8).to_le_bytes()); 398 | data.extend_from_slice(&(a_to_b as u8).to_le_bytes()); 399 | 400 | msg!("data.len: {}", data.len()); 401 | assert!(data.len() == ARGS_LEN); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /programs/dex-solana/src/constants.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[constant] 4 | pub const SEED_SA: &[u8] = b"okx_sa"; 5 | pub const BUMP_SA: u8 = 251; 6 | pub const COMMISSION_RATE_LIMIT: u16 = 300; 7 | pub const COMMISSION_DENOMINATOR: u64 = 10000; 8 | pub const MAX_HOPS: usize = 3; 9 | pub const TOTAL_WEIGHT: u8 = 100; 10 | 11 | pub const SWAP_SELECTOR: &[u8; 8] = &[248, 198, 158, 145, 225, 117, 135, 200]; 12 | pub const CPSWAP_SELECTOR: &[u8; 8] = &[143, 190, 90, 218, 196, 30, 51, 222]; 13 | pub const SWAP_V2_SELECTOR: &[u8; 8] = &[43, 4, 237, 11, 26, 201, 30, 98]; 14 | pub const PLACE_TAKE_ORDER_SELECTOR: &[u8; 8] = &[3, 44, 71, 3, 26, 199, 203, 85]; 15 | pub const BRIDGE_TO_LOG_SELECTOR: &[u8; 8] = &[212, 189, 176, 218, 196, 135, 64, 122]; 16 | pub const ZERO_ADDRESS: Pubkey = Pubkey::new_from_array([0u8; 32]); 17 | 18 | pub const PUMPFUN_BUY_SELECTOR: &[u8; 8] = &[102, 6, 61, 18, 1, 218, 235, 234]; 19 | pub const PUMPFUN_SELL_SELECTOR: &[u8; 8] = &[51, 230, 133, 164, 1, 127, 131, 173]; 20 | 21 | pub mod authority_pda { 22 | use anchor_lang::declare_id; 23 | declare_id!("HV1KXxWFaSeriyFvXyx48FqG9BoFbfinB8njCJonqP7K"); 24 | // declare_id!("4DwLmWvMyWPPKa8jhmW6AZKGctUMe7GxAWrb2Wcw8ZUa"); //pre_deploy 25 | } 26 | 27 | pub mod okx_bridge_program { 28 | use anchor_lang::declare_id; 29 | declare_id!("okxBd18urPbBi2vsExxUDArzQNcju2DugV9Mt46BxYE"); 30 | } 31 | 32 | pub mod token_program { 33 | use anchor_lang::declare_id; 34 | declare_id!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); 35 | } 36 | 37 | pub mod wsol_program { 38 | use anchor_lang::declare_id; 39 | declare_id!("So11111111111111111111111111111111111111112"); 40 | } 41 | 42 | pub mod raydium_swap_program { 43 | use anchor_lang::declare_id; 44 | declare_id!("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"); 45 | } 46 | 47 | pub mod raydium_stable_program { 48 | use anchor_lang::declare_id; 49 | declare_id!("5quBtoiQqxF9Jv6KYKctB59NT3gtJD2Y65kdnB1Uev3h"); 50 | } 51 | 52 | pub mod raydium_clmm_program { 53 | use anchor_lang::declare_id; 54 | declare_id!("CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK"); 55 | } 56 | 57 | pub mod raydium_cpmm_program { 58 | use anchor_lang::declare_id; 59 | declare_id!("CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C"); 60 | } 61 | 62 | pub mod aldrin_v1_program { 63 | use anchor_lang::declare_id; 64 | declare_id!("AMM55ShdkoGRB5jVYPjWziwk8m5MpwyDgsMWHaMSQWH6"); 65 | } 66 | 67 | pub mod aldrin_v2_program { 68 | use anchor_lang::declare_id; 69 | declare_id!("CURVGoZn8zycx6FXwwevgBTB2gVvdbGTEpvMJDbgs2t4"); 70 | } 71 | 72 | pub mod whirlpool_program { 73 | use anchor_lang::declare_id; 74 | declare_id!("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"); 75 | } 76 | 77 | pub mod meteora_dynamicpool_program { 78 | use anchor_lang::declare_id; 79 | declare_id!("Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB"); 80 | } 81 | 82 | pub mod meteora_dlmm_program { 83 | use anchor_lang::declare_id; 84 | declare_id!("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"); 85 | } 86 | 87 | pub mod lifinity_v1pool_program { 88 | use anchor_lang::declare_id; 89 | declare_id!("EewxydAPCCVuNEyrVN68PuSYdQ7wKn27V9Gjeoi8dy3S"); 90 | } 91 | 92 | pub mod lifinity_v2pool_program { 93 | use anchor_lang::declare_id; 94 | declare_id!("2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c"); 95 | } 96 | 97 | pub mod flux_beam_program { 98 | use anchor_lang::declare_id; 99 | declare_id!("FLUXubRmkEi2q6K3Y9kBPg9248ggaZVsoSFhtJHSrm1X"); 100 | } 101 | 102 | pub mod openbookv2_program { 103 | use anchor_lang::declare_id; 104 | declare_id!("opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb"); 105 | } 106 | 107 | pub mod phoenix_program { 108 | use anchor_lang::declare_id; 109 | declare_id!("PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY"); 110 | } 111 | 112 | pub mod obric_v2_program { 113 | use anchor_lang::declare_id; 114 | declare_id!("obriQD1zbpyLz95G5n7nJe6a4DPjpFwa5XYPoNm113y"); 115 | } 116 | 117 | pub mod sanctum_program { 118 | use anchor_lang::declare_id; 119 | declare_id!("5ocnV1qiCgaQR8Jb8xWnVbApfaygJ8tNoZfgPwsgx9kx"); 120 | } 121 | 122 | pub mod pumpfun_program { 123 | use anchor_lang::declare_id; 124 | declare_id!("6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P"); 125 | } 126 | -------------------------------------------------------------------------------- /programs/dex-solana/src/error.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[error_code] 4 | pub enum ErrorCode { 5 | #[msg("Too many hops")] 6 | TooManyHops, 7 | 8 | #[msg("Min return not reached")] 9 | MinReturnNotReached, 10 | 11 | #[msg("amount_in must be greater than 0")] 12 | AmountInMustBeGreaterThanZero, 13 | 14 | #[msg("min_return must be greater than 0")] 15 | MinReturnMustBeGreaterThanZero, 16 | 17 | #[msg("invalid expect amount out")] 18 | InvalidExpectAmountOut, 19 | 20 | #[msg("amounts and routes must have the same length")] 21 | AmountsAndRoutesMustHaveTheSameLength, 22 | 23 | #[msg("total_amounts must be equal to amount_in")] 24 | TotalAmountsMustBeEqualToAmountIn, 25 | 26 | #[msg("dexes and weights must have the same length")] 27 | DexesAndWeightsMustHaveTheSameLength, 28 | 29 | #[msg("weights must sum to 100")] 30 | WeightsMustSumTo100, 31 | 32 | #[msg("Invalid source token account")] 33 | InvalidSourceTokenAccount, 34 | 35 | #[msg("Invalid destination token account")] 36 | InvalidDestinationTokenAccount, 37 | 38 | #[msg("Invalid commission rate")] 39 | InvalidCommissionRate, 40 | 41 | #[msg("Invalid commission token account")] 42 | InvalidCommissionTokenAccount, 43 | 44 | #[msg("Invalid accounts length")] 45 | InvalidAccountsLength, 46 | 47 | #[msg("Invalid hop accounts")] 48 | InvalidHopAccounts, 49 | 50 | #[msg("Invalid hop from account")] 51 | InvalidHopFromAccount, 52 | 53 | #[msg("Swap authority is not signer")] 54 | SwapAuthorityIsNotSigner, 55 | 56 | #[msg("Invalid authority pda")] 57 | InvalidAuthorityPda, 58 | 59 | #[msg("Invalid program id")] 60 | InvalidProgramId, 61 | 62 | #[msg("Invalid pool")] 63 | InvalidPool, 64 | 65 | #[msg("Invalid token mint")] 66 | InvalidTokenMint, 67 | 68 | #[msg("Calculation error")] 69 | CalculationError, 70 | 71 | #[msg("Authority pda creation failed")] 72 | AuthorityPDACreationFailed, 73 | 74 | #[msg("Transfer sol failed")] 75 | TransferSolFailed, 76 | 77 | #[msg("Transfer token failed")] 78 | TransferTokenFailed, 79 | 80 | #[msg("Invalid sanctum lst state list data")] 81 | InvalidSanctumLstStateListData, 82 | 83 | #[msg("Invalid sanctum lst state list index")] 84 | InvalidSanctumLstStateListIndex, 85 | 86 | #[msg("Invalid sanctum swap accounts")] 87 | InvalidSanctumSwapAccounts, 88 | } 89 | -------------------------------------------------------------------------------- /programs/dex-solana/src/instructions/commission_from_swap.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ErrorCode; 2 | use crate::instructions::from_swap::cpi_bridge_to_log; 3 | use crate::utils::token::{transfer_sol_from_user, transfer_token_from_user}; 4 | use crate::{ 5 | swap_process, wsol_program, BridgeToArgs, SwapArgs, COMMISSION_DENOMINATOR, 6 | COMMISSION_RATE_LIMIT, 7 | }; 8 | use anchor_lang::prelude::*; 9 | use anchor_spl::{ 10 | associated_token::AssociatedToken, 11 | token::Token, 12 | token_2022::Token2022, 13 | token_interface::{Mint, TokenAccount}, 14 | }; 15 | 16 | #[derive(Accounts)] 17 | pub struct CommissionSOLFromSwapAccounts<'info> { 18 | #[account(mut)] 19 | pub payer: Signer<'info>, 20 | 21 | #[account( 22 | mut, 23 | token::mint = source_mint, 24 | token::authority = payer, 25 | )] 26 | pub source_token_account: InterfaceAccount<'info, TokenAccount>, 27 | 28 | #[account( 29 | mut, 30 | token::mint = destination_mint, 31 | )] 32 | pub destination_token_account: InterfaceAccount<'info, TokenAccount>, 33 | 34 | pub source_mint: InterfaceAccount<'info, Mint>, 35 | 36 | #[account(mut)] 37 | pub destination_mint: InterfaceAccount<'info, Mint>, 38 | 39 | /// CHECK: bridge_program 40 | #[account(address = crate::okx_bridge_program::id())] 41 | pub bridge_program: AccountInfo<'info>, 42 | 43 | pub associated_token_program: Program<'info, AssociatedToken>, 44 | pub token_program: Program<'info, Token>, 45 | pub token_2022_program: Program<'info, Token2022>, 46 | pub system_program: Program<'info, System>, 47 | 48 | #[account(mut)] 49 | pub commission_account: SystemAccount<'info>, 50 | } 51 | 52 | pub fn commission_sol_from_swap_handler<'a>( 53 | ctx: Context<'_, '_, 'a, 'a, CommissionSOLFromSwapAccounts<'a>>, 54 | args: SwapArgs, 55 | commission_rate: u16, 56 | bridge_to_args: BridgeToArgs, 57 | offset: u8, 58 | len: u8, 59 | ) -> Result<()> { 60 | require!( 61 | commission_rate > 0 && commission_rate <= COMMISSION_RATE_LIMIT, 62 | ErrorCode::InvalidCommissionRate 63 | ); 64 | require!( 65 | ctx.accounts.source_mint.key() == wsol_program::id(), 66 | ErrorCode::InvalidCommissionTokenAccount 67 | ); 68 | 69 | let commission_amount = args 70 | .amount_in 71 | .checked_mul(commission_rate as u64) 72 | .ok_or(ErrorCode::CalculationError)? 73 | .checked_div(COMMISSION_DENOMINATOR - commission_rate as u64) 74 | .ok_or(ErrorCode::CalculationError)?; 75 | 76 | let amount_out = swap_process( 77 | &mut ctx.accounts.source_token_account, 78 | &mut ctx.accounts.destination_token_account, 79 | &ctx.accounts.source_mint, 80 | &ctx.accounts.destination_mint, 81 | ctx.remaining_accounts, 82 | args, 83 | bridge_to_args.order_id, 84 | false, 85 | )?; 86 | 87 | // Transfer commission_amount 88 | transfer_sol_from_user( 89 | ctx.accounts.payer.to_account_info(), 90 | ctx.accounts.commission_account.to_account_info(), 91 | commission_amount, 92 | )?; 93 | msg!( 94 | "commission_direction: {:?}, commission_amount: {:?}", 95 | true, 96 | commission_amount 97 | ); 98 | 99 | // CPI bridge_to_log 100 | cpi_bridge_to_log( 101 | bridge_to_args, 102 | amount_out, 103 | offset, 104 | len, 105 | &ctx.accounts.bridge_program, 106 | &ctx.accounts.payer, 107 | &ctx.accounts.destination_token_account, 108 | &ctx.accounts.destination_mint, 109 | &ctx.accounts.associated_token_program, 110 | &ctx.accounts.token_program, 111 | &ctx.accounts.token_2022_program, 112 | &ctx.accounts.system_program, 113 | &ctx.remaining_accounts, 114 | )?; 115 | 116 | Ok(()) 117 | } 118 | 119 | #[derive(Accounts)] 120 | pub struct CommissionSPLFromSwapAccounts<'info> { 121 | #[account(mut)] 122 | pub payer: Signer<'info>, 123 | 124 | #[account( 125 | mut, 126 | token::mint = source_mint, 127 | token::authority = payer, 128 | )] 129 | pub source_token_account: InterfaceAccount<'info, TokenAccount>, 130 | 131 | #[account( 132 | mut, 133 | token::mint = destination_mint, 134 | )] 135 | pub destination_token_account: InterfaceAccount<'info, TokenAccount>, 136 | 137 | pub source_mint: InterfaceAccount<'info, Mint>, 138 | 139 | #[account(mut)] 140 | pub destination_mint: InterfaceAccount<'info, Mint>, 141 | 142 | /// CHECK: bridge_program 143 | #[account(address = crate::okx_bridge_program::id())] 144 | pub bridge_program: AccountInfo<'info>, 145 | 146 | pub associated_token_program: Program<'info, AssociatedToken>, 147 | pub token_program: Program<'info, Token>, 148 | pub token_2022_program: Program<'info, Token2022>, 149 | pub system_program: Program<'info, System>, 150 | 151 | #[account( 152 | mut, 153 | token::token_program = token_program, 154 | )] 155 | pub commission_token_account: InterfaceAccount<'info, TokenAccount>, 156 | } 157 | 158 | pub fn commission_spl_from_swap_handler<'a>( 159 | ctx: Context<'_, '_, 'a, 'a, CommissionSPLFromSwapAccounts<'a>>, 160 | args: SwapArgs, 161 | commission_rate: u16, 162 | bridge_to_args: BridgeToArgs, 163 | offset: u8, 164 | len: u8, 165 | ) -> Result<()> { 166 | require!( 167 | commission_rate > 0 && commission_rate <= COMMISSION_RATE_LIMIT, 168 | ErrorCode::InvalidCommissionRate 169 | ); 170 | require!( 171 | ctx.accounts.commission_token_account.mint == ctx.accounts.source_mint.key(), 172 | ErrorCode::InvalidCommissionTokenAccount 173 | ); 174 | 175 | let commission_amount = args 176 | .amount_in 177 | .checked_mul(commission_rate as u64) 178 | .ok_or(ErrorCode::CalculationError)? 179 | .checked_div(COMMISSION_DENOMINATOR - commission_rate as u64) 180 | .ok_or(ErrorCode::CalculationError)?; 181 | 182 | let amount_out = swap_process( 183 | &mut ctx.accounts.source_token_account, 184 | &mut ctx.accounts.destination_token_account, 185 | &ctx.accounts.source_mint, 186 | &ctx.accounts.destination_mint, 187 | ctx.remaining_accounts, 188 | args, 189 | bridge_to_args.order_id, 190 | false, 191 | )?; 192 | 193 | // Transfer commission_amount 194 | let commission_token_program = 195 | if *ctx.accounts.source_mint.to_account_info().owner == Token2022::id() { 196 | ctx.accounts.token_2022_program.to_account_info() 197 | } else { 198 | ctx.accounts.token_program.to_account_info() 199 | }; 200 | transfer_token_from_user( 201 | ctx.accounts.payer.to_account_info(), 202 | ctx.accounts.source_token_account.to_account_info(), 203 | ctx.accounts.commission_token_account.to_account_info(), 204 | ctx.accounts.source_mint.to_account_info(), 205 | commission_token_program, 206 | commission_amount, 207 | ctx.accounts.source_mint.decimals, 208 | )?; 209 | msg!( 210 | "commission_direction: {:?}, commission_amount: {:?}", 211 | true, 212 | commission_amount 213 | ); 214 | 215 | // CPI bridge_to_log 216 | cpi_bridge_to_log( 217 | bridge_to_args, 218 | amount_out, 219 | offset, 220 | len, 221 | &ctx.accounts.bridge_program, 222 | &ctx.accounts.payer, 223 | &ctx.accounts.destination_token_account, 224 | &ctx.accounts.destination_mint, 225 | &ctx.accounts.associated_token_program, 226 | &ctx.accounts.token_program, 227 | &ctx.accounts.token_2022_program, 228 | &ctx.accounts.system_program, 229 | &ctx.remaining_accounts, 230 | )?; 231 | 232 | Ok(()) 233 | } 234 | -------------------------------------------------------------------------------- /programs/dex-solana/src/instructions/commission_proxy_swap.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::*; 2 | use crate::error::ErrorCode; 3 | use crate::utils::token::{transfer_sol_from_user, transfer_token_from_user}; 4 | use crate::{proxy_swap_process, SwapArgs, COMMISSION_DENOMINATOR, COMMISSION_RATE_LIMIT}; 5 | use anchor_lang::prelude::*; 6 | use anchor_spl::associated_token::AssociatedToken; 7 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 8 | 9 | #[derive(Accounts)] 10 | pub struct CommissionSOLProxySwapAccounts<'info> { 11 | #[account(mut)] 12 | pub payer: Signer<'info>, 13 | 14 | #[account( 15 | mut, 16 | token::mint = source_mint, 17 | token::authority = payer, 18 | )] 19 | pub source_token_account: Box>, 20 | 21 | #[account( 22 | mut, 23 | token::mint = destination_mint, 24 | )] 25 | pub destination_token_account: Box>, 26 | 27 | pub source_mint: Box>, 28 | 29 | pub destination_mint: Box>, 30 | 31 | #[account(mut)] 32 | pub commission_account: SystemAccount<'info>, 33 | 34 | /// CHECK: sa_authority 35 | #[account( 36 | seeds = [ 37 | SEED_SA, 38 | ], 39 | bump = BUMP_SA, 40 | )] 41 | pub sa_authority: UncheckedAccount<'info>, 42 | 43 | #[account( 44 | init_if_needed, 45 | payer = payer, 46 | associated_token::mint = source_mint, 47 | associated_token::authority = sa_authority, 48 | associated_token::token_program = source_token_program, 49 | )] 50 | pub source_token_sa: Option>, 51 | 52 | #[account( 53 | init_if_needed, 54 | payer = payer, 55 | associated_token::mint = destination_mint, 56 | associated_token::authority = sa_authority, 57 | associated_token::token_program = destination_token_program, 58 | )] 59 | pub destination_token_sa: Option>, 60 | 61 | pub source_token_program: Interface<'info, TokenInterface>, 62 | 63 | pub destination_token_program: Interface<'info, TokenInterface>, 64 | 65 | pub associated_token_program: Program<'info, AssociatedToken>, 66 | 67 | pub system_program: Program<'info, System>, 68 | } 69 | 70 | pub fn commission_sol_proxy_swap_handler<'a>( 71 | ctx: Context<'_, '_, 'a, 'a, CommissionSOLProxySwapAccounts<'a>>, 72 | args: SwapArgs, 73 | commission_rate: u16, 74 | commission_direction: bool, 75 | order_id: u64, 76 | ) -> Result { 77 | // Check commission_rate 78 | require!( 79 | commission_rate > 0 && commission_rate <= COMMISSION_RATE_LIMIT, 80 | ErrorCode::InvalidCommissionRate 81 | ); 82 | 83 | let mut commission_amount: u64 = 0; 84 | if commission_direction { 85 | // Commission direction: true-fromToken 86 | require!( 87 | ctx.accounts.source_mint.key() == wsol_program::id(), 88 | ErrorCode::InvalidCommissionTokenAccount 89 | ); 90 | 91 | // Commission for fromToken 92 | commission_amount = args 93 | .amount_in 94 | .checked_mul(commission_rate as u64) 95 | .ok_or(ErrorCode::CalculationError)? 96 | .checked_div(COMMISSION_DENOMINATOR - commission_rate as u64) 97 | .ok_or(ErrorCode::CalculationError)?; 98 | } else { 99 | // Commission direction: false-toToken 100 | require!( 101 | ctx.accounts.destination_mint.key() == wsol_program::id(), 102 | ErrorCode::InvalidCommissionTokenAccount 103 | ); 104 | } 105 | 106 | // Proxy Swap 107 | let amount_out = proxy_swap_process( 108 | &ctx.accounts.payer, 109 | &ctx.accounts.sa_authority, 110 | &mut ctx.accounts.source_token_account, 111 | &mut ctx.accounts.destination_token_account, 112 | &mut ctx.accounts.source_token_sa, 113 | &mut ctx.accounts.destination_token_sa, 114 | &ctx.accounts.source_mint, 115 | &ctx.accounts.destination_mint, 116 | &ctx.accounts.source_token_program, 117 | &ctx.accounts.destination_token_program, 118 | ctx.remaining_accounts, 119 | args, 120 | order_id, 121 | )?; 122 | 123 | // Commission for toToken 124 | if !commission_direction { 125 | commission_amount = amount_out 126 | .checked_mul(commission_rate as u64) 127 | .ok_or(ErrorCode::CalculationError)? 128 | .checked_div(COMMISSION_DENOMINATOR) 129 | .ok_or(ErrorCode::CalculationError)?; 130 | } 131 | 132 | // Transfer commission_amount 133 | transfer_sol_from_user( 134 | ctx.accounts.payer.to_account_info(), 135 | ctx.accounts.commission_account.to_account_info(), 136 | commission_amount, 137 | )?; 138 | msg!( 139 | "commission_direction: {:?}, commission_amount: {:?}", 140 | commission_direction, 141 | commission_amount 142 | ); 143 | Ok(amount_out) 144 | } 145 | 146 | #[derive(Accounts)] 147 | pub struct CommissionSPLProxySwapAccounts<'info> { 148 | #[account(mut)] 149 | pub payer: Signer<'info>, 150 | 151 | #[account( 152 | mut, 153 | token::mint = source_mint, 154 | token::authority = payer, 155 | )] 156 | pub source_token_account: Box>, 157 | 158 | #[account( 159 | mut, 160 | token::mint = destination_mint, 161 | )] 162 | pub destination_token_account: Box>, 163 | 164 | pub source_mint: Box>, 165 | 166 | pub destination_mint: Box>, 167 | 168 | #[account(mut)] 169 | pub commission_token_account: Box>, 170 | 171 | /// CHECK: sa_authority 172 | #[account( 173 | seeds = [ 174 | SEED_SA, 175 | ], 176 | bump = BUMP_SA, 177 | )] 178 | pub sa_authority: UncheckedAccount<'info>, 179 | 180 | #[account( 181 | init_if_needed, 182 | payer = payer, 183 | associated_token::mint = source_mint, 184 | associated_token::authority = sa_authority, 185 | associated_token::token_program = source_token_program, 186 | )] 187 | pub source_token_sa: Option>, 188 | 189 | #[account( 190 | init_if_needed, 191 | payer = payer, 192 | associated_token::mint = destination_mint, 193 | associated_token::authority = sa_authority, 194 | associated_token::token_program = destination_token_program, 195 | )] 196 | pub destination_token_sa: Option>, 197 | 198 | pub source_token_program: Interface<'info, TokenInterface>, 199 | 200 | pub destination_token_program: Interface<'info, TokenInterface>, 201 | 202 | pub associated_token_program: Program<'info, AssociatedToken>, 203 | 204 | pub system_program: Program<'info, System>, 205 | } 206 | 207 | pub fn commission_spl_proxy_swap_handler<'a>( 208 | ctx: Context<'_, '_, 'a, 'a, CommissionSPLProxySwapAccounts<'a>>, 209 | args: SwapArgs, 210 | commission_rate: u16, 211 | commission_direction: bool, 212 | order_id: u64, 213 | ) -> Result { 214 | // Check commission_rate 215 | require!( 216 | commission_rate > 0 && commission_rate <= COMMISSION_RATE_LIMIT, 217 | ErrorCode::InvalidCommissionRate 218 | ); 219 | 220 | let mut commission_amount: u64 = 0; 221 | if commission_direction { 222 | // Commission direction: true-fromToken 223 | require!( 224 | ctx.accounts.commission_token_account.mint == ctx.accounts.source_mint.key(), 225 | ErrorCode::InvalidCommissionTokenAccount 226 | ); 227 | 228 | // Commission for fromToken 229 | commission_amount = args 230 | .amount_in 231 | .checked_mul(commission_rate as u64) 232 | .ok_or(ErrorCode::CalculationError)? 233 | .checked_div(COMMISSION_DENOMINATOR - commission_rate as u64) 234 | .ok_or(ErrorCode::CalculationError)?; 235 | } else { 236 | // Commission direction: false-toToken 237 | require!( 238 | ctx.accounts.commission_token_account.mint == ctx.accounts.destination_mint.key(), 239 | ErrorCode::InvalidCommissionTokenAccount 240 | ); 241 | } 242 | 243 | // Proxy Swap 244 | let amount_out = proxy_swap_process( 245 | &ctx.accounts.payer, 246 | &ctx.accounts.sa_authority, 247 | &mut ctx.accounts.source_token_account, 248 | &mut ctx.accounts.destination_token_account, 249 | &mut ctx.accounts.source_token_sa, 250 | &mut ctx.accounts.destination_token_sa, 251 | &ctx.accounts.source_mint, 252 | &ctx.accounts.destination_mint, 253 | &ctx.accounts.source_token_program, 254 | &ctx.accounts.destination_token_program, 255 | ctx.remaining_accounts, 256 | args, 257 | order_id, 258 | )?; 259 | 260 | // Transfer commission_amount 261 | if commission_direction { 262 | // Commission for fromToken 263 | transfer_token_from_user( 264 | ctx.accounts.payer.to_account_info(), 265 | ctx.accounts.source_token_account.to_account_info(), 266 | ctx.accounts.commission_token_account.to_account_info(), 267 | ctx.accounts.source_mint.to_account_info(), 268 | ctx.accounts.source_token_program.to_account_info(), 269 | commission_amount, 270 | ctx.accounts.source_mint.decimals, 271 | )?; 272 | } else { 273 | // Commission for toToken 274 | commission_amount = amount_out 275 | .checked_mul(commission_rate as u64) 276 | .ok_or(ErrorCode::CalculationError)? 277 | .checked_div(COMMISSION_DENOMINATOR) 278 | .ok_or(ErrorCode::CalculationError)?; 279 | 280 | transfer_token_from_user( 281 | ctx.accounts.payer.to_account_info(), 282 | ctx.accounts.destination_token_account.to_account_info(), 283 | ctx.accounts.commission_token_account.to_account_info(), 284 | ctx.accounts.destination_mint.to_account_info(), 285 | ctx.accounts.destination_token_program.to_account_info(), 286 | commission_amount, 287 | ctx.accounts.destination_mint.decimals, 288 | )?; 289 | } 290 | msg!( 291 | "commission_direction: {:?}, commission_amount: {:?}", 292 | commission_direction, 293 | commission_amount 294 | ); 295 | Ok(amount_out) 296 | } 297 | -------------------------------------------------------------------------------- /programs/dex-solana/src/instructions/commission_swap.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ErrorCode; 2 | use crate::utils::token::{transfer_sol_from_user, transfer_token_from_user}; 3 | use crate::{ 4 | swap_process, wsol_program, CommissionSwapArgs, SwapArgs, COMMISSION_DENOMINATOR, 5 | COMMISSION_RATE_LIMIT, 6 | }; 7 | use anchor_lang::prelude::*; 8 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 9 | 10 | #[derive(Accounts)] 11 | pub struct CommissionSOLAccounts<'info> { 12 | pub payer: Signer<'info>, 13 | 14 | #[account( 15 | mut, 16 | token::mint = source_mint, 17 | token::authority = payer, 18 | )] 19 | pub source_token_account: InterfaceAccount<'info, TokenAccount>, 20 | 21 | #[account( 22 | mut, 23 | token::mint = destination_mint, 24 | )] 25 | pub destination_token_account: InterfaceAccount<'info, TokenAccount>, 26 | 27 | pub source_mint: InterfaceAccount<'info, Mint>, 28 | 29 | pub destination_mint: InterfaceAccount<'info, Mint>, 30 | 31 | #[account(mut)] 32 | pub commission_account: SystemAccount<'info>, 33 | 34 | pub system_program: Program<'info, System>, 35 | } 36 | 37 | pub fn commission_sol_swap_handler<'a>( 38 | ctx: Context<'_, '_, 'a, 'a, CommissionSOLAccounts<'a>>, 39 | args: CommissionSwapArgs, 40 | order_id: u64, 41 | ) -> Result { 42 | // CHECK: CommissionSwapArgs 43 | require!( 44 | args.commission_rate > 0 && args.commission_rate <= COMMISSION_RATE_LIMIT, 45 | ErrorCode::InvalidCommissionRate 46 | ); 47 | 48 | let mut commission_amount: u64 = 0; 49 | if args.commission_direction { 50 | // Commission direction: true-fromToken 51 | require!( 52 | ctx.accounts.source_mint.key() == wsol_program::id(), 53 | ErrorCode::InvalidCommissionTokenAccount 54 | ); 55 | 56 | // Commission for fromToken 57 | commission_amount = args 58 | .amount_in 59 | .checked_mul(args.commission_rate as u64) 60 | .ok_or(ErrorCode::CalculationError)? 61 | .checked_div(COMMISSION_DENOMINATOR - args.commission_rate as u64) 62 | .ok_or(ErrorCode::CalculationError)?; 63 | } else { 64 | // Commission direction: false-toToken 65 | require!( 66 | ctx.accounts.destination_mint.key() == wsol_program::id(), 67 | ErrorCode::InvalidCommissionTokenAccount 68 | ); 69 | } 70 | 71 | let swap_args = SwapArgs { 72 | amount_in: args.amount_in, 73 | expect_amount_out: args.expect_amount_out, 74 | min_return: args.min_return, 75 | amounts: args.amounts, 76 | routes: args.routes, 77 | }; 78 | let amount_out = swap_process( 79 | &mut ctx.accounts.source_token_account, 80 | &mut ctx.accounts.destination_token_account, 81 | &ctx.accounts.source_mint, 82 | &ctx.accounts.destination_mint, 83 | ctx.remaining_accounts, 84 | swap_args, 85 | order_id, 86 | false, 87 | )?; 88 | 89 | // Commission for toToken 90 | if !args.commission_direction { 91 | commission_amount = amount_out 92 | .checked_mul(args.commission_rate as u64) 93 | .ok_or(ErrorCode::CalculationError)? 94 | .checked_div(COMMISSION_DENOMINATOR) 95 | .ok_or(ErrorCode::CalculationError)?; 96 | } 97 | 98 | // Transfer commission_amount 99 | transfer_sol_from_user( 100 | ctx.accounts.payer.to_account_info(), 101 | ctx.accounts.commission_account.to_account_info(), 102 | commission_amount, 103 | )?; 104 | msg!( 105 | "commission_direction: {:?}, commission_amount: {:?}", 106 | args.commission_direction, 107 | commission_amount 108 | ); 109 | Ok(amount_out) 110 | } 111 | 112 | #[derive(Accounts)] 113 | pub struct CommissionSPLAccounts<'info> { 114 | pub payer: Signer<'info>, 115 | 116 | #[account( 117 | mut, 118 | token::mint = source_mint, 119 | token::authority = payer, 120 | )] 121 | pub source_token_account: InterfaceAccount<'info, TokenAccount>, 122 | 123 | #[account( 124 | mut, 125 | token::mint = destination_mint, 126 | )] 127 | pub destination_token_account: InterfaceAccount<'info, TokenAccount>, 128 | 129 | pub source_mint: InterfaceAccount<'info, Mint>, 130 | 131 | pub destination_mint: InterfaceAccount<'info, Mint>, 132 | 133 | #[account( 134 | mut, 135 | token::token_program = token_program, 136 | )] 137 | pub commission_token_account: InterfaceAccount<'info, TokenAccount>, 138 | 139 | pub token_program: Interface<'info, TokenInterface>, 140 | } 141 | 142 | pub fn commission_spl_swap_handler<'a>( 143 | ctx: Context<'_, '_, 'a, 'a, CommissionSPLAccounts<'a>>, 144 | args: CommissionSwapArgs, 145 | order_id: u64, 146 | ) -> Result { 147 | // CHECK: CommissionSwapArgs 148 | require!( 149 | args.commission_rate > 0 && args.commission_rate <= COMMISSION_RATE_LIMIT, 150 | ErrorCode::InvalidCommissionRate 151 | ); 152 | 153 | let mut commission_amount: u64 = 0; 154 | if args.commission_direction { 155 | // Commission direction: true-fromToken 156 | require!( 157 | ctx.accounts.commission_token_account.mint == ctx.accounts.source_mint.key(), 158 | ErrorCode::InvalidCommissionTokenAccount 159 | ); 160 | 161 | // Commission for fromToken 162 | commission_amount = args 163 | .amount_in 164 | .checked_mul(args.commission_rate as u64) 165 | .ok_or(ErrorCode::CalculationError)? 166 | .checked_div(COMMISSION_DENOMINATOR - args.commission_rate as u64) 167 | .ok_or(ErrorCode::CalculationError)?; 168 | } else { 169 | // Commission direction: false-toToken 170 | require!( 171 | ctx.accounts.commission_token_account.mint == ctx.accounts.destination_mint.key(), 172 | ErrorCode::InvalidCommissionTokenAccount 173 | ); 174 | } 175 | 176 | let swap_args = SwapArgs { 177 | amount_in: args.amount_in, 178 | expect_amount_out: args.expect_amount_out, 179 | min_return: args.min_return, 180 | amounts: args.amounts, 181 | routes: args.routes, 182 | }; 183 | let amount_out = swap_process( 184 | &mut ctx.accounts.source_token_account, 185 | &mut ctx.accounts.destination_token_account, 186 | &ctx.accounts.source_mint, 187 | &ctx.accounts.destination_mint, 188 | ctx.remaining_accounts, 189 | swap_args, 190 | order_id, 191 | false, 192 | )?; 193 | 194 | // Transfer commission_amount 195 | if args.commission_direction { 196 | // Commission for fromToken 197 | transfer_token_from_user( 198 | ctx.accounts.payer.to_account_info(), 199 | ctx.accounts.source_token_account.to_account_info(), 200 | ctx.accounts.commission_token_account.to_account_info(), 201 | ctx.accounts.source_mint.to_account_info(), 202 | ctx.accounts.token_program.to_account_info(), 203 | commission_amount, 204 | ctx.accounts.source_mint.decimals, 205 | )?; 206 | } else { 207 | // Commission for toToken 208 | commission_amount = amount_out 209 | .checked_mul(args.commission_rate as u64) 210 | .ok_or(ErrorCode::CalculationError)? 211 | .checked_div(COMMISSION_DENOMINATOR) 212 | .ok_or(ErrorCode::CalculationError)?; 213 | 214 | transfer_token_from_user( 215 | ctx.accounts.payer.to_account_info(), 216 | ctx.accounts.destination_token_account.to_account_info(), 217 | ctx.accounts.commission_token_account.to_account_info(), 218 | ctx.accounts.destination_mint.to_account_info(), 219 | ctx.accounts.token_program.to_account_info(), 220 | commission_amount, 221 | ctx.accounts.destination_mint.decimals, 222 | )?; 223 | } 224 | msg!( 225 | "commission_direction: {:?}, commission_amount: {:?}", 226 | args.commission_direction, 227 | commission_amount 228 | ); 229 | Ok(amount_out) 230 | } 231 | -------------------------------------------------------------------------------- /programs/dex-solana/src/instructions/common.rs: -------------------------------------------------------------------------------- 1 | use crate::adapters::{ 2 | aldrin, fluxbeam, lifinity, meteora, obric_v2, openbookv2, phoenix, pumpfun, raydium, sanctum, spl_token_swap, stable_swap, whirlpool 3 | }; 4 | use crate::error::ErrorCode; 5 | use crate::utils::token::{transfer_token_from_sa_pda, transfer_token_from_user}; 6 | use crate::{MAX_HOPS, TOTAL_WEIGHT, ZERO_ADDRESS}; 7 | use anchor_lang::prelude::*; 8 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 9 | 10 | #[derive(AnchorSerialize, AnchorDeserialize, Copy, Clone, PartialEq, Eq, Debug)] 11 | pub enum Dex { 12 | SplTokenSwap, 13 | StableSwap, 14 | Whirlpool, 15 | MeteoraDynamicpool, 16 | RaydiumSwap, 17 | RaydiumStableSwap, 18 | RaydiumClmmSwap, 19 | AldrinExchangeV1, 20 | AldrinExchangeV2, 21 | LifinityV1, 22 | LifinityV2, 23 | RaydiumClmmSwapV2, 24 | FluxBeam, 25 | MeteoraDlmm, 26 | RaydiumCpmmSwap, 27 | OpenBookV2, 28 | WhirlpoolV2, 29 | Phoenix, 30 | ObricV2, 31 | SanctumAddLiq, 32 | SanctumRemoveLiq, 33 | SanctumNonWsolSwap, 34 | SanctumWsolSwap, 35 | PumpfunBuy, 36 | PumpfunSell, 37 | } 38 | #[derive(Debug)] 39 | pub struct HopAccounts { 40 | pub last_to_account: Pubkey, 41 | pub from_account: Pubkey, 42 | pub to_account: Pubkey, 43 | } 44 | 45 | #[derive(AnchorSerialize, AnchorDeserialize, Clone)] 46 | pub struct Route { 47 | pub dexes: Vec, 48 | pub weights: Vec, 49 | } 50 | 51 | #[derive(AnchorDeserialize, AnchorSerialize, Clone)] 52 | pub struct SwapArgs { 53 | pub amount_in: u64, 54 | pub expect_amount_out: u64, 55 | pub min_return: u64, 56 | pub amounts: Vec, // 1st level split amount 57 | pub routes: Vec>, // 2nd level split route 58 | } 59 | 60 | #[derive(AnchorDeserialize, AnchorSerialize, Clone)] 61 | pub struct CommissionSwapArgs { 62 | pub amount_in: u64, 63 | pub expect_amount_out: u64, 64 | pub min_return: u64, 65 | pub amounts: Vec, // 1st level split amount 66 | pub routes: Vec>, // 2nd level split route 67 | 68 | pub commission_rate: u16, // Commission rate 69 | pub commission_direction: bool, // Commission direction: true-fromToken, false-toToken 70 | } 71 | 72 | #[event] 73 | #[derive(Debug)] 74 | pub struct SwapEvent { 75 | pub dex: Dex, 76 | pub amount_in: u64, 77 | pub amount_out: u64, 78 | } 79 | 80 | pub fn proxy_swap_process<'info>( 81 | payer: &Signer<'info>, 82 | sa_authority: &UncheckedAccount<'info>, 83 | source_token_account: &mut InterfaceAccount<'info, TokenAccount>, 84 | destination_token_account: &mut InterfaceAccount<'info, TokenAccount>, 85 | source_token_sa: &mut Option>, 86 | destination_token_sa: &mut Option>, 87 | source_mint: &InterfaceAccount<'info, Mint>, 88 | destination_mint: &InterfaceAccount<'info, Mint>, 89 | source_token_program: &Interface<'info, TokenInterface>, 90 | destination_token_program: &Interface<'info, TokenInterface>, 91 | remaining_accounts: &'info [AccountInfo<'info>], 92 | args: SwapArgs, 93 | order_id: u64, 94 | ) -> Result { 95 | let before_source_balance = source_token_account.amount; 96 | let before_destination_balance = destination_token_account.amount; 97 | let min_return = args.min_return; 98 | msg!( 99 | "before_source_balance: {}, before_destination_balance: {}, amount_in: {}, expect_amount_out: {}, min_return: {}", 100 | before_source_balance, 101 | before_destination_balance, 102 | args.amount_in, 103 | args.expect_amount_out, 104 | min_return 105 | ); 106 | 107 | // 1.Transfer source token to source_token_sa 108 | let mut source_account = if let Some(source_token_sa) = source_token_sa { 109 | transfer_token_from_user( 110 | payer.to_account_info(), 111 | source_token_account.to_account_info(), 112 | source_token_sa.to_account_info(), 113 | source_mint.to_account_info(), 114 | source_token_program.to_account_info(), 115 | args.amount_in, 116 | source_mint.decimals, 117 | )?; 118 | source_token_sa.clone() 119 | } else { 120 | source_token_account.clone() 121 | }; 122 | 123 | // 2.Smart swap 124 | let mut destination_account = if let Some(destination_token_sa) = destination_token_sa { 125 | destination_token_sa.clone() 126 | } else { 127 | destination_token_account.clone() 128 | }; 129 | let amount_out = swap_process( 130 | &mut source_account, 131 | &mut destination_account, 132 | &source_mint, 133 | &destination_mint, 134 | remaining_accounts, 135 | args, 136 | order_id, 137 | source_token_sa.is_some(), 138 | )?; 139 | msg!("Swap amount_out: {}", amount_out); 140 | 141 | // 3. Transfer destination token to destination_token_account 142 | if let Some(ref destination_token_sa) = destination_token_sa { 143 | transfer_token_from_sa_pda( 144 | sa_authority.to_account_info(), 145 | destination_token_sa.to_account_info(), 146 | destination_token_account.to_account_info(), 147 | destination_mint.to_account_info(), 148 | destination_token_program.to_account_info(), 149 | amount_out, 150 | destination_mint.decimals, 151 | )?; 152 | } 153 | 154 | source_token_account.reload()?; 155 | destination_token_account.reload()?; 156 | let after_source_balance = source_token_account.amount; 157 | let after_destination_balance = destination_token_account.amount; 158 | let source_token_change = before_source_balance 159 | .checked_sub(after_source_balance) 160 | .ok_or(ErrorCode::CalculationError)?; 161 | let destination_token_change = after_destination_balance 162 | .checked_sub(before_destination_balance) 163 | .ok_or(ErrorCode::CalculationError)?; 164 | msg!( 165 | "after_source_balance: {}, after_destination_balance: {}, source_token_change: {}, destination_token_change: {}", 166 | after_source_balance, 167 | after_destination_balance, 168 | source_token_change, 169 | destination_token_change 170 | ); 171 | 172 | // CHECK: min_return 173 | require!( 174 | destination_token_change >= min_return, 175 | ErrorCode::MinReturnNotReached 176 | ); 177 | Ok(amount_out) 178 | } 179 | 180 | pub fn swap_process<'info>( 181 | source_token_account: &mut InterfaceAccount<'info, TokenAccount>, 182 | destination_token_account: &mut InterfaceAccount<'info, TokenAccount>, 183 | source_mint: &InterfaceAccount<'info, Mint>, 184 | destination_mint: &InterfaceAccount<'info, Mint>, 185 | remaining_accounts: &'info [AccountInfo<'info>], 186 | args: SwapArgs, 187 | order_id: u64, 188 | proxy_swap: bool, 189 | ) -> Result { 190 | if order_id > 0 { 191 | msg!("order_id: {}", order_id); 192 | } 193 | // Check SwapArgs 194 | let SwapArgs { 195 | amount_in, 196 | min_return, 197 | expect_amount_out, 198 | amounts, 199 | routes, 200 | } = &args; 201 | require!(*amount_in > 0, ErrorCode::AmountInMustBeGreaterThanZero); 202 | require!(*min_return > 0, ErrorCode::MinReturnMustBeGreaterThanZero); 203 | require!( 204 | *expect_amount_out >= *min_return, 205 | ErrorCode::InvalidExpectAmountOut 206 | ); 207 | require!( 208 | amounts.len() == routes.len(), 209 | ErrorCode::AmountsAndRoutesMustHaveTheSameLength 210 | ); 211 | 212 | let total_amounts: u64 = amounts.iter().try_fold(0u64, |acc, &x| { 213 | acc.checked_add(x).ok_or(ErrorCode::CalculationError) 214 | })?; 215 | require!( 216 | total_amounts == *amount_in, 217 | ErrorCode::TotalAmountsMustBeEqualToAmountIn 218 | ); 219 | 220 | // log source_mint and destination_mint 221 | source_mint.key().log(); 222 | destination_mint.key().log(); 223 | 224 | if proxy_swap { 225 | source_token_account.reload()?; 226 | destination_token_account.reload()?; 227 | } 228 | let before_source_balance = source_token_account.amount; 229 | let before_destination_balance = destination_token_account.amount; 230 | 231 | if !proxy_swap { 232 | msg!( 233 | "before_source_balance: {}, before_destination_balance: {}, amount_in: {}, expect_amount_out: {}, min_return: {}", 234 | before_source_balance, 235 | before_destination_balance, 236 | amount_in, 237 | expect_amount_out, 238 | min_return 239 | ); 240 | } 241 | 242 | // Swap by Routes 243 | let mut offset: usize = 0; 244 | // Level 1 split handling 245 | for (i, hops) in routes.iter().enumerate() { 246 | require!(hops.len() <= MAX_HOPS, ErrorCode::TooManyHops); 247 | let mut amount_in = amounts[i]; 248 | 249 | // Multi-hop handling 250 | let mut last_to_account = ZERO_ADDRESS; 251 | for (hop, route) in hops.iter().enumerate() { 252 | let dexes = &route.dexes; 253 | let weights = &route.weights; 254 | require!( 255 | dexes.len() == weights.len(), 256 | ErrorCode::DexesAndWeightsMustHaveTheSameLength 257 | ); 258 | let total_weight: u8 = weights.iter().try_fold(0u8, |acc, &x| { 259 | acc.checked_add(x).ok_or(ErrorCode::CalculationError) 260 | })?; 261 | require!(total_weight == TOTAL_WEIGHT, ErrorCode::WeightsMustSumTo100); 262 | 263 | // Level 2 split handling 264 | let mut hop_accounts = HopAccounts { 265 | last_to_account, 266 | from_account: ZERO_ADDRESS, 267 | to_account: ZERO_ADDRESS, 268 | }; 269 | let mut amount_out: u64 = 0; 270 | let mut acc_fork_in: u64 = 0; 271 | for (index, dex) in dexes.iter().enumerate() { 272 | // Calculate 2 level split amount 273 | let fork_amount_in = if index == dexes.len() - 1 { 274 | // The last dex, use the remaining amount_in for trading to prevent accumulation 275 | amount_in 276 | .checked_sub(acc_fork_in) 277 | .ok_or(ErrorCode::CalculationError)? 278 | } else { 279 | let temp_amount = amount_in 280 | .checked_mul(weights[index] as u64) 281 | .ok_or(ErrorCode::CalculationError)? 282 | .checked_div(TOTAL_WEIGHT as u64) 283 | .ok_or(ErrorCode::CalculationError)?; 284 | acc_fork_in = acc_fork_in 285 | .checked_add(temp_amount) 286 | .ok_or(ErrorCode::CalculationError)?; 287 | temp_amount 288 | }; 289 | 290 | // Execute swap 291 | let fork_amount_out = excute_swap( 292 | dex, 293 | remaining_accounts, 294 | fork_amount_in, 295 | &mut offset, 296 | &mut hop_accounts, 297 | hop, 298 | proxy_swap, 299 | )?; 300 | 301 | // Emit SwapEvent 302 | let event = SwapEvent { 303 | dex: *dex, 304 | amount_in: fork_amount_in, 305 | amount_out: fork_amount_out, 306 | }; 307 | emit!(event); 308 | msg!("{:?}", event); 309 | hop_accounts.from_account.log(); 310 | hop_accounts.to_account.log(); 311 | 312 | amount_out = amount_out 313 | .checked_add(fork_amount_out) 314 | .ok_or(ErrorCode::CalculationError)?; 315 | } 316 | 317 | if hop == 0 { 318 | // CHECK: Verify the first hop's from_token must be consistent with ctx.accounts.source_token_account 319 | require!( 320 | source_token_account.key() == hop_accounts.from_account, 321 | ErrorCode::InvalidSourceTokenAccount 322 | ); 323 | } 324 | if hop == hops.len() - 1 { 325 | // CHECK: Verify the last hop's to_account must be consistent with ctx.accounts.destination_token_account 326 | require!( 327 | destination_token_account.key() == hop_accounts.to_account, 328 | ErrorCode::InvalidDestinationTokenAccount 329 | ); 330 | } 331 | amount_in = amount_out; 332 | last_to_account = hop_accounts.to_account; 333 | } 334 | } 335 | 336 | //source_token_account.reload()?; 337 | 338 | // source token account has been closed in pumpfun buy 339 | if source_token_account.get_lamports() != 0 { 340 | source_token_account.reload()?; 341 | } 342 | 343 | destination_token_account.reload()?; 344 | let after_source_balance = source_token_account.amount; 345 | let after_destination_balance = destination_token_account.amount; 346 | 347 | let source_token_change = before_source_balance 348 | .checked_sub(after_source_balance) 349 | .ok_or(ErrorCode::CalculationError)?; 350 | let destination_token_change = after_destination_balance 351 | .checked_sub(before_destination_balance) 352 | .ok_or(ErrorCode::CalculationError)?; 353 | if !proxy_swap { 354 | msg!( 355 | "after_source_balance: {}, after_destination_balance: {}, source_token_change: {}, destination_token_change: {}", 356 | after_source_balance, 357 | after_destination_balance, 358 | source_token_change, 359 | destination_token_change 360 | ); 361 | } 362 | 363 | // CHECK: min_return 364 | require!( 365 | destination_token_change >= *min_return, 366 | ErrorCode::MinReturnNotReached 367 | ); 368 | 369 | Ok(destination_token_change) 370 | } 371 | 372 | fn excute_swap<'a>( 373 | dex: &Dex, 374 | remaining_accounts: &'a [AccountInfo<'a>], 375 | amount_in: u64, 376 | offset: &mut usize, 377 | hop_accounts: &mut HopAccounts, 378 | hop: usize, 379 | proxy_swap: bool, 380 | ) -> Result { 381 | let swap_function = match dex { 382 | Dex::SplTokenSwap => spl_token_swap::swap, 383 | Dex::StableSwap => stable_swap::swap, 384 | Dex::Whirlpool => whirlpool::swap, 385 | Dex::MeteoraDynamicpool => meteora::swap, 386 | Dex::RaydiumSwap => raydium::swap, 387 | Dex::RaydiumStableSwap => raydium::swap_stable, 388 | Dex::RaydiumClmmSwap => raydium::swap_clmm, 389 | Dex::RaydiumClmmSwapV2 => raydium::swap_clmm_v2, 390 | Dex::AldrinExchangeV1 => aldrin::swap_v1, 391 | Dex::AldrinExchangeV2 => aldrin::swap_v2, 392 | Dex::LifinityV1 => lifinity::swap_v1, 393 | Dex::LifinityV2 => lifinity::swap_v2, 394 | Dex::FluxBeam => fluxbeam::swap, 395 | Dex::MeteoraDlmm => meteora::swap_dlmm, 396 | Dex::RaydiumCpmmSwap => raydium::swap_cpmm, 397 | Dex::OpenBookV2 => openbookv2::place_take_order, 398 | Dex::WhirlpoolV2 => whirlpool::swap_v2, 399 | Dex::Phoenix => phoenix::swap, 400 | Dex::ObricV2 => obric_v2::swap, 401 | Dex::SanctumAddLiq => sanctum::add_liquidity_handler, 402 | Dex::SanctumRemoveLiq => sanctum::remove_liquidity_handler, 403 | Dex::SanctumNonWsolSwap => sanctum::swap_without_wsol_handler, 404 | Dex::SanctumWsolSwap => sanctum::swap_with_wsol_handler, 405 | Dex::PumpfunBuy => pumpfun::buy, 406 | Dex::PumpfunSell => pumpfun::sell, 407 | }; 408 | swap_function( 409 | remaining_accounts, 410 | amount_in, 411 | offset, 412 | hop_accounts, 413 | hop, 414 | proxy_swap, 415 | ) 416 | } 417 | -------------------------------------------------------------------------------- /programs/dex-solana/src/instructions/from_swap.rs: -------------------------------------------------------------------------------- 1 | use crate::BRIDGE_TO_LOG_SELECTOR; 2 | use crate::{error::ErrorCode, swap_process, SwapArgs}; 3 | use anchor_lang::{ 4 | prelude::*, 5 | solana_program::{instruction::Instruction, program::invoke}, 6 | }; 7 | use anchor_spl::{ 8 | associated_token::AssociatedToken, 9 | token::Token, 10 | token_2022::Token2022, 11 | token_interface::{Mint, TokenAccount}, 12 | }; 13 | 14 | #[derive(Accounts)] 15 | pub struct FromSwapAccounts<'info> { 16 | #[account(mut)] 17 | pub payer: Signer<'info>, 18 | 19 | #[account( 20 | mut, 21 | token::mint = source_mint, 22 | token::authority = payer, 23 | )] 24 | pub source_token_account: InterfaceAccount<'info, TokenAccount>, 25 | 26 | #[account( 27 | mut, 28 | token::mint = destination_mint, 29 | )] 30 | pub destination_token_account: InterfaceAccount<'info, TokenAccount>, 31 | 32 | pub source_mint: InterfaceAccount<'info, Mint>, 33 | 34 | #[account(mut)] 35 | pub destination_mint: InterfaceAccount<'info, Mint>, 36 | 37 | /// CHECK: bridge_program 38 | #[account(address = crate::okx_bridge_program::id())] 39 | pub bridge_program: AccountInfo<'info>, 40 | 41 | pub associated_token_program: Program<'info, AssociatedToken>, 42 | pub token_program: Program<'info, Token>, 43 | pub token_2022_program: Program<'info, Token2022>, 44 | pub system_program: Program<'info, System>, 45 | } 46 | 47 | #[derive(AnchorDeserialize, AnchorSerialize)] 48 | pub struct BridgeToArgs { 49 | pub adaptor_id: AdaptorID, // bridge adaptor id 50 | pub to: Vec, // recipient address on target chain 51 | pub order_id: u64, // order id for okx 52 | pub to_chain_id: u64, // target chain id 53 | pub amount: u64, // amount to bridge 54 | pub swap_type: SwapType, // swap type 55 | pub data: Vec, // data for bridge 56 | pub ext_data: Vec, // ext data for extension feature 57 | } 58 | 59 | #[derive(AnchorDeserialize, AnchorSerialize)] 60 | pub enum SwapType { 61 | BRIDGE, 62 | SWAPANDBRIDGE, 63 | } 64 | 65 | #[derive(AnchorDeserialize, AnchorSerialize)] 66 | pub enum AdaptorID { 67 | /* 00 */ Bridge0, 68 | /* 01 */ Bridge1, 69 | /* 02 */ Bridge2, 70 | /* 03 */ Bridge3, 71 | /* 04 */ Bridge4, 72 | /* 05 */ Bridge5, 73 | /* 06 */ Bridge6, 74 | /* 07 */ Bridge7, 75 | /* 08 */ Bridge8, 76 | /* 09 */ Bridge9, 77 | /* 10 */ Bridge10, 78 | /* 11 */ Bridge11, 79 | /* 12 */ Bridge12, 80 | /* 13 */ Bridge13, 81 | /* 14 */ Bridge14, 82 | /* 15 */ Bridge15, 83 | /* 16 */ Bridge16, 84 | /* 17 */ Bridge17, 85 | /* 18 */ Cctp, 86 | /* 19 */ Bridge19, 87 | /* 20 */ Bridge20, 88 | /* 21 */ Wormhole, 89 | /* 22 */ Meson, 90 | /* 23 */ Bridge23, 91 | /* 24 */ Bridge24, 92 | /* 25 */ Bridge25, 93 | /* 26 */ Bridge26, 94 | /* 27 */ Bridge27, 95 | /* 28 */ Bridge28, 96 | /* 29 */ Bridge29, 97 | /* 30 */ Bridge30, 98 | /* 31 */ Bridge31, 99 | /* 32 */ Bridge32, 100 | /* 33 */ Bridge33, 101 | /* 34 */ Debridgedln, 102 | /* 35 */ Bridge35, 103 | /* 36 */ Bridge36, 104 | /* 37 */ Bridge37, 105 | /* 38 */ Bridge38, 106 | /* 39 */ Bridge39, 107 | /* 40 */ Bridge40, 108 | /* 41 */ Allbridge, 109 | } 110 | 111 | pub fn from_swap_log_handler<'a>( 112 | ctx: Context<'_, '_, 'a, 'a, FromSwapAccounts<'a>>, 113 | args: SwapArgs, 114 | bridge_to_args: BridgeToArgs, 115 | offset: u8, 116 | len: u8, 117 | ) -> Result<()> { 118 | // 1.Smart swap 119 | let amount_out = swap_process( 120 | &mut ctx.accounts.source_token_account, 121 | &mut ctx.accounts.destination_token_account, 122 | &ctx.accounts.source_mint, 123 | &ctx.accounts.destination_mint, 124 | ctx.remaining_accounts, 125 | args, 126 | 0, 127 | false, 128 | )?; 129 | msg!("Swap amount_out: {}", amount_out); 130 | 131 | // 2. CPI bridge_to_log 132 | cpi_bridge_to_log( 133 | bridge_to_args, 134 | amount_out, 135 | offset, 136 | len, 137 | &ctx.accounts.bridge_program, 138 | &ctx.accounts.payer, 139 | &ctx.accounts.destination_token_account, 140 | &ctx.accounts.destination_mint, 141 | &ctx.accounts.associated_token_program, 142 | &ctx.accounts.token_program, 143 | &ctx.accounts.token_2022_program, 144 | &ctx.accounts.system_program, 145 | &ctx.remaining_accounts, 146 | )?; 147 | 148 | Ok(()) 149 | } 150 | 151 | pub fn cpi_bridge_to_log<'info>( 152 | bridge_to_args: BridgeToArgs, 153 | amount_out: u64, 154 | offset: u8, 155 | len: u8, 156 | bridge_program: &AccountInfo<'info>, 157 | payer: &Signer<'info>, 158 | destination_token_account: &InterfaceAccount<'info, TokenAccount>, 159 | destination_mint: &InterfaceAccount<'info, Mint>, 160 | associated_token_program: &Program<'info, AssociatedToken>, 161 | token_program: &AccountInfo<'info>, 162 | token_2022_program: &AccountInfo<'info>, 163 | system_program: &AccountInfo<'info>, 164 | remaining_accounts: &[AccountInfo<'info>], 165 | ) -> Result<()> { 166 | let offset = offset as usize; 167 | let len = len as usize; 168 | require!( 169 | remaining_accounts.len() >= offset + len, 170 | ErrorCode::InvalidAccountsLength 171 | ); 172 | // get bridgeTo remaining accounts 173 | let bridge_remaining_accounts = Vec::from(&remaining_accounts[offset..offset + len]); 174 | 175 | // reset amount 176 | let mut bridge_to_args = bridge_to_args; 177 | bridge_to_args.amount = amount_out; 178 | 179 | let serialized_args = bridge_to_args.try_to_vec()?; 180 | let mut data = Vec::with_capacity(BRIDGE_TO_LOG_SELECTOR.len() + serialized_args.len()); 181 | data.extend_from_slice(BRIDGE_TO_LOG_SELECTOR); 182 | data.extend_from_slice(&serialized_args); 183 | 184 | let mut accounts = vec![ 185 | AccountMeta::new(payer.key(), true), 186 | AccountMeta::new(destination_token_account.key(), false), 187 | AccountMeta::new(destination_mint.key(), false), 188 | AccountMeta::new_readonly(associated_token_program.key(), false), 189 | AccountMeta::new_readonly(token_program.key(), false), 190 | AccountMeta::new_readonly(token_2022_program.key(), false), 191 | AccountMeta::new_readonly(system_program.key(), false), 192 | ]; 193 | accounts.extend(bridge_remaining_accounts.to_account_metas(None)); 194 | 195 | let mut accounts_infos = vec![ 196 | payer.to_account_info(), 197 | destination_token_account.to_account_info(), 198 | destination_mint.to_account_info(), 199 | associated_token_program.to_account_info(), 200 | token_program.to_account_info(), 201 | token_2022_program.to_account_info(), 202 | system_program.to_account_info(), 203 | ]; 204 | accounts_infos.extend(bridge_remaining_accounts.to_account_infos()); 205 | 206 | let ix = Instruction { 207 | program_id: bridge_program.key(), 208 | accounts: accounts, 209 | data: data, 210 | }; 211 | invoke(&ix, &accounts_infos)?; 212 | 213 | Ok(()) 214 | } 215 | -------------------------------------------------------------------------------- /programs/dex-solana/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod commission_from_swap; 2 | pub mod commission_proxy_swap; 3 | pub mod commission_swap; 4 | pub mod common; 5 | pub mod from_swap; 6 | pub mod proxy_swap; 7 | pub mod swap; 8 | 9 | pub use commission_from_swap::*; 10 | pub use commission_proxy_swap::*; 11 | pub use commission_swap::*; 12 | pub use common::*; 13 | pub use from_swap::*; 14 | pub use proxy_swap::*; 15 | pub use swap::*; 16 | -------------------------------------------------------------------------------- /programs/dex-solana/src/instructions/proxy_swap.rs: -------------------------------------------------------------------------------- 1 | use crate::{constants::*, proxy_swap_process, SwapArgs}; 2 | use anchor_lang::prelude::*; 3 | use anchor_spl::associated_token::AssociatedToken; 4 | use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface}; 5 | 6 | #[derive(Accounts)] 7 | pub struct ProxySwapAccounts<'info> { 8 | #[account(mut)] 9 | pub payer: Signer<'info>, 10 | 11 | #[account( 12 | mut, 13 | token::mint = source_mint, 14 | token::authority = payer, 15 | token::token_program = source_token_program, 16 | )] 17 | pub source_token_account: Box>, 18 | 19 | #[account( 20 | mut, 21 | token::mint = destination_mint, 22 | token::token_program = destination_token_program, 23 | )] 24 | pub destination_token_account: Box>, 25 | 26 | pub source_mint: Box>, 27 | 28 | pub destination_mint: Box>, 29 | 30 | /// CHECK: sa_authority 31 | #[account( 32 | seeds = [ 33 | SEED_SA, 34 | ], 35 | bump = BUMP_SA, 36 | )] 37 | pub sa_authority: UncheckedAccount<'info>, 38 | 39 | #[account( 40 | init_if_needed, 41 | payer = payer, 42 | associated_token::mint = source_mint, 43 | associated_token::authority = sa_authority, 44 | associated_token::token_program = source_token_program, 45 | )] 46 | pub source_token_sa: Option>, 47 | 48 | #[account( 49 | init_if_needed, 50 | payer = payer, 51 | associated_token::mint = destination_mint, 52 | associated_token::authority = sa_authority, 53 | associated_token::token_program = destination_token_program, 54 | )] 55 | pub destination_token_sa: Option>, 56 | 57 | pub source_token_program: Interface<'info, TokenInterface>, 58 | pub destination_token_program: Interface<'info, TokenInterface>, 59 | pub associated_token_program: Program<'info, AssociatedToken>, 60 | pub system_program: Program<'info, System>, 61 | } 62 | 63 | pub fn proxy_swap_handler<'a>( 64 | ctx: Context<'_, '_, 'a, 'a, ProxySwapAccounts<'a>>, 65 | args: SwapArgs, 66 | order_id: u64, 67 | ) -> Result { 68 | proxy_swap_process( 69 | &ctx.accounts.payer, 70 | &ctx.accounts.sa_authority, 71 | &mut ctx.accounts.source_token_account, 72 | &mut ctx.accounts.destination_token_account, 73 | &mut ctx.accounts.source_token_sa, 74 | &mut ctx.accounts.destination_token_sa, 75 | &ctx.accounts.source_mint, 76 | &ctx.accounts.destination_mint, 77 | &ctx.accounts.source_token_program, 78 | &ctx.accounts.destination_token_program, 79 | ctx.remaining_accounts, 80 | args, 81 | order_id, 82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /programs/dex-solana/src/instructions/swap.rs: -------------------------------------------------------------------------------- 1 | use crate::swap_process; 2 | use crate::SwapArgs; 3 | use anchor_lang::prelude::*; 4 | use anchor_spl::token_interface::{Mint, TokenAccount}; 5 | 6 | #[derive(Accounts)] 7 | pub struct SwapAccounts<'info> { 8 | pub payer: Signer<'info>, 9 | 10 | #[account( 11 | mut, 12 | token::mint = source_mint, 13 | token::authority = payer, 14 | )] 15 | pub source_token_account: InterfaceAccount<'info, TokenAccount>, 16 | 17 | #[account( 18 | mut, 19 | token::mint = destination_mint, 20 | )] 21 | pub destination_token_account: InterfaceAccount<'info, TokenAccount>, 22 | 23 | pub source_mint: InterfaceAccount<'info, Mint>, 24 | 25 | pub destination_mint: InterfaceAccount<'info, Mint>, 26 | } 27 | 28 | pub fn swap_handler<'a>( 29 | ctx: Context<'_, '_, 'a, 'a, SwapAccounts<'a>>, 30 | args: SwapArgs, 31 | order_id: u64, 32 | ) -> Result { 33 | swap_process( 34 | &mut ctx.accounts.source_token_account, 35 | &mut ctx.accounts.destination_token_account, 36 | &ctx.accounts.source_mint, 37 | &ctx.accounts.destination_mint, 38 | ctx.remaining_accounts, 39 | args, 40 | order_id, 41 | false, 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /programs/dex-solana/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | pub mod adapters; 3 | pub mod constants; 4 | pub mod error; 5 | pub mod instructions; 6 | pub mod utils; 7 | 8 | pub use constants::*; 9 | pub use instructions::*; 10 | 11 | declare_id!("6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma"); 12 | 13 | #[program] 14 | pub mod dex_solana { 15 | use super::*; 16 | 17 | pub fn swap<'a>(ctx: Context<'_, '_, 'a, 'a, SwapAccounts<'a>>, data: SwapArgs) -> Result { 18 | instructions::swap_handler(ctx, data, 0) 19 | } 20 | 21 | pub fn swap2<'a>( 22 | ctx: Context<'_, '_, 'a, 'a, SwapAccounts<'a>>, 23 | data: SwapArgs, 24 | order_id: u64, 25 | ) -> Result { 26 | instructions::swap_handler(ctx, data, order_id) 27 | } 28 | 29 | pub fn commission_spl_swap<'a>( 30 | ctx: Context<'_, '_, 'a, 'a, CommissionSPLAccounts<'a>>, 31 | data: CommissionSwapArgs, 32 | ) -> Result { 33 | instructions::commission_spl_swap_handler(ctx, data, 0) 34 | } 35 | 36 | pub fn commission_spl_swap2<'a>( 37 | ctx: Context<'_, '_, 'a, 'a, CommissionSPLAccounts<'a>>, 38 | data: CommissionSwapArgs, 39 | order_id: u64, 40 | ) -> Result { 41 | instructions::commission_spl_swap_handler(ctx, data, order_id) 42 | } 43 | 44 | pub fn commission_sol_swap<'a>( 45 | ctx: Context<'_, '_, 'a, 'a, CommissionSOLAccounts<'a>>, 46 | data: CommissionSwapArgs, 47 | ) -> Result { 48 | instructions::commission_sol_swap_handler(ctx, data, 0) 49 | } 50 | 51 | pub fn commission_sol_swap2<'a>( 52 | ctx: Context<'_, '_, 'a, 'a, CommissionSOLAccounts<'a>>, 53 | data: CommissionSwapArgs, 54 | order_id: u64, 55 | ) -> Result { 56 | instructions::commission_sol_swap_handler(ctx, data, order_id) 57 | } 58 | 59 | pub fn from_swap_log<'a>( 60 | ctx: Context<'_, '_, 'a, 'a, FromSwapAccounts<'a>>, 61 | args: SwapArgs, 62 | bridge_to_args: BridgeToArgs, 63 | offset: u8, 64 | len: u8, 65 | ) -> Result<()> { 66 | instructions::from_swap_log_handler(ctx, args, bridge_to_args, offset, len) 67 | } 68 | 69 | // proxy swap 70 | pub fn proxy_swap<'a>( 71 | ctx: Context<'_, '_, 'a, 'a, ProxySwapAccounts<'a>>, 72 | data: SwapArgs, 73 | order_id: u64, 74 | ) -> Result { 75 | instructions::proxy_swap_handler(ctx, data, order_id) 76 | } 77 | 78 | pub fn commission_sol_proxy_swap<'a>( 79 | ctx: Context<'_, '_, 'a, 'a, CommissionSOLProxySwapAccounts<'a>>, 80 | data: SwapArgs, 81 | commission_rate: u16, 82 | commission_direction: bool, 83 | order_id: u64, 84 | ) -> Result { 85 | instructions::commission_sol_proxy_swap_handler( 86 | ctx, 87 | data, 88 | commission_rate, 89 | commission_direction, 90 | order_id, 91 | ) 92 | } 93 | 94 | pub fn commission_spl_proxy_swap<'a>( 95 | ctx: Context<'_, '_, 'a, 'a, CommissionSPLProxySwapAccounts<'a>>, 96 | data: SwapArgs, 97 | commission_rate: u16, 98 | commission_direction: bool, 99 | order_id: u64, 100 | ) -> Result { 101 | instructions::commission_spl_proxy_swap_handler( 102 | ctx, 103 | data, 104 | commission_rate, 105 | commission_direction, 106 | order_id, 107 | ) 108 | } 109 | 110 | pub fn commission_sol_from_swap<'a>( 111 | ctx: Context<'_, '_, 'a, 'a, CommissionSOLFromSwapAccounts<'a>>, 112 | args: SwapArgs, 113 | commission_rate: u16, 114 | bridge_to_args: BridgeToArgs, 115 | offset: u8, 116 | len: u8, 117 | ) -> Result<()> { 118 | instructions::commission_sol_from_swap_handler( 119 | ctx, 120 | args, 121 | commission_rate, 122 | bridge_to_args, 123 | offset, 124 | len, 125 | ) 126 | } 127 | 128 | pub fn commission_spl_from_swap<'a>( 129 | ctx: Context<'_, '_, 'a, 'a, CommissionSPLFromSwapAccounts<'a>>, 130 | args: SwapArgs, 131 | commission_rate: u16, 132 | bridge_to_args: BridgeToArgs, 133 | offset: u8, 134 | len: u8, 135 | ) -> Result<()> { 136 | instructions::commission_spl_from_swap_handler( 137 | ctx, 138 | args, 139 | commission_rate, 140 | bridge_to_args, 141 | offset, 142 | len, 143 | ) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /programs/dex-solana/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod token; 2 | 3 | pub use token::*; 4 | -------------------------------------------------------------------------------- /programs/dex-solana/src/utils/token.rs: -------------------------------------------------------------------------------- 1 | use crate::error::ErrorCode; 2 | use crate::{BUMP_SA, SEED_SA}; 3 | use anchor_lang::prelude::*; 4 | use anchor_lang::solana_program::{program::invoke, system_instruction::transfer}; 5 | use anchor_spl::token_2022::{self}; 6 | 7 | pub fn transfer_sol_from_user<'a>( 8 | from: AccountInfo<'a>, 9 | to: AccountInfo<'a>, 10 | lamports: u64, 11 | ) -> Result<()> { 12 | if lamports == 0 { 13 | return Ok(()); 14 | } 15 | let ix = transfer(from.key, to.key, lamports); 16 | let res = invoke(&ix, &[from, to]); 17 | require!(res.is_ok(), ErrorCode::TransferSolFailed); 18 | Ok(()) 19 | } 20 | 21 | pub fn transfer_token_from_user<'a>( 22 | authority: AccountInfo<'a>, 23 | from: AccountInfo<'a>, 24 | to: AccountInfo<'a>, 25 | mint: AccountInfo<'a>, 26 | token_program: AccountInfo<'a>, 27 | amount: u64, 28 | mint_decimals: u8, 29 | ) -> Result<()> { 30 | if amount == 0 { 31 | return Ok(()); 32 | } 33 | let res = token_2022::transfer_checked( 34 | CpiContext::new( 35 | token_program.to_account_info(), 36 | token_2022::TransferChecked { 37 | from, 38 | to, 39 | authority, 40 | mint, 41 | }, 42 | ), 43 | amount, 44 | mint_decimals, 45 | ); 46 | require!(res.is_ok(), ErrorCode::TransferTokenFailed); 47 | Ok(()) 48 | } 49 | 50 | pub fn transfer_token_from_sa_pda<'a>( 51 | authority: AccountInfo<'a>, 52 | from: AccountInfo<'a>, 53 | to: AccountInfo<'a>, 54 | mint: AccountInfo<'a>, 55 | token_program: AccountInfo<'a>, 56 | amount: u64, 57 | mint_decimals: u8, 58 | ) -> Result<()> { 59 | if amount == 0 { 60 | return Ok(()); 61 | } 62 | let res = token_2022::transfer_checked( 63 | CpiContext::new_with_signer( 64 | token_program.to_account_info(), 65 | token_2022::TransferChecked { 66 | from, 67 | to, 68 | authority, 69 | mint, 70 | }, 71 | &[&[SEED_SA, &[BUMP_SA]]], 72 | ), 73 | amount, 74 | mint_decimals, 75 | ); 76 | require!(res.is_ok(), ErrorCode::TransferTokenFailed); 77 | Ok(()) 78 | } 79 | --------------------------------------------------------------------------------