├── python-client └── dlmm │ ├── tests │ ├── __init__.py │ ├── test_util_methods.py │ └── test_lp_flow.py │ ├── .gitignore │ ├── dlmm │ ├── __init__.py │ └── utils.py │ ├── dist │ ├── dlmm-0.1.0.tar.gz │ └── dlmm-0.1.0-py3-none-any.whl │ ├── pyproject.toml │ └── README.md ├── rust-toolchain.toml ├── commons ├── tests │ ├── integration │ │ ├── helpers │ │ │ ├── mod.rs │ │ │ └── utils.rs │ │ └── main.rs │ ├── artifacts │ │ ├── token_2022.so │ │ └── lb_clmm_prod.so │ └── fixtures │ │ ├── B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc │ │ ├── lb_pair.bin │ │ ├── oracle.bin │ │ ├── reserve_x.bin │ │ ├── reserve_y.bin │ │ ├── bin_array_1.bin │ │ ├── bin_array_2.bin │ │ └── token_x_mint.bin │ │ └── EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig │ │ ├── lb_pair.bin │ │ ├── oracle.bin │ │ ├── reserve_x.bin │ │ ├── reserve_y.bin │ │ ├── bin_array_1.bin │ │ ├── bin_array_2.bin │ │ └── token_x_mint.bin ├── src │ ├── math │ │ ├── mod.rs │ │ ├── price_math.rs │ │ ├── utils.rs │ │ └── u128x128_math.rs │ ├── conversions │ │ ├── mod.rs │ │ ├── activation_type.rs │ │ ├── status.rs │ │ ├── token_program_flag.rs │ │ └── pair_type.rs │ ├── extensions │ │ ├── mod.rs │ │ ├── position.rs │ │ └── bin_array.rs │ ├── typedefs.rs │ ├── account_filters.rs │ ├── seeds.rs │ ├── lib.rs │ ├── rpc_client_extension.rs │ └── constants.rs └── Cargo.toml ├── package.json ├── artifacts ├── lb_clmm.so ├── token_2022.so ├── transfer_hook_counter.so └── FJbEo74c2W4QLBBVUfUvi8VBWXtMdJVPuFpq2f6UV1iB │ ├── FQuMNJ6UcrkA3xMLsmmLL8sk47RkZYod57mX3a8w1brg.json │ ├── So11111111111111111111111111111111111111112.json │ └── FJbEo74c2W4QLBBVUfUvi8VBWXtMdJVPuFpq2f6UV1iB.json ├── .prettierignore ├── ts-client ├── src │ ├── dlmm │ │ ├── helpers │ │ │ ├── rebalance │ │ │ │ ├── index.ts │ │ │ │ └── strategy │ │ │ │ │ ├── index.ts │ │ │ │ │ └── balanced.ts │ │ │ ├── lbPair.ts │ │ │ ├── accountFilters.ts │ │ │ ├── computeUnit.ts │ │ │ └── u64xu64_math.ts │ │ ├── error.ts │ │ └── constants │ │ │ └── index.ts │ ├── index.ts │ ├── examples │ │ ├── fetch_lb_pair_lock_info.ts │ │ ├── swap_quote.ts │ │ └── initialize_bin_arrays.ts │ ├── test │ │ ├── decode.test.ts │ │ ├── external │ │ │ ├── program.ts │ │ │ ├── helper.ts │ │ │ ├── transfer_hook_counter.json │ │ │ └── transfer_hook_counter.ts │ │ ├── esm_import.test.ts │ │ └── esm_module.test.ts │ └── server │ │ └── utils.ts ├── jest.config.js ├── tsconfig.json ├── tsup.config.ts └── package.json ├── Dockerfile ├── README.md ├── .github ├── actions │ ├── setup-dep │ │ └── action.yml │ ├── setup-anchor │ │ └── action.yml │ └── setup-solana │ │ └── action.yml └── workflows │ ├── ci-pr-main-program.yml │ ├── ci-pr-main-cli.yml │ ├── ci-pr-main-market-making.yml │ └── ci-pr-main-sdk.yml ├── keys └── localnet │ ├── admin-bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1.json │ └── program-LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ.json ├── .gitignore ├── cli ├── src │ ├── instructions │ │ ├── ilm │ │ │ └── mod.rs │ │ ├── show_position.rs │ │ ├── show_preset_parameters.rs │ │ ├── admin │ │ │ ├── mod.rs │ │ │ ├── toggle_pair_status.rs │ │ │ ├── set_activation_point.rs │ │ │ ├── initialize_token_badge.rs │ │ │ ├── close_claim_protocol_fee_operator.rs │ │ │ ├── create_claim_protocol_fee_operator.rs │ │ │ ├── set_pre_activation_duration.rs │ │ │ ├── update_reward_funder.rs │ │ │ ├── set_pre_activation_swap_address.rs │ │ │ ├── update_base_fee.rs │ │ │ ├── update_reward_duration.rs │ │ │ ├── initialize_reward.rs │ │ │ ├── close_preset_parameter.rs │ │ │ ├── withdraw_protocol_fee.rs │ │ │ └── initialize_preset_parameter.rs │ │ ├── set_pair_status_permissionless.rs │ │ ├── get_all_positions.rs │ │ ├── initialize_bin_array_with_bin_range.rs │ │ ├── initialize_position_with_price_range.rs │ │ ├── increase_oracle_length.rs │ │ ├── set_pair_status.rs │ │ ├── initialize_bin_array.rs │ │ ├── initialize_bin_array_with_price_range.rs │ │ ├── close_position.rs │ │ ├── initialize_position.rs │ │ ├── mod.rs │ │ ├── simulate_swap_demand.rs │ │ ├── fund_reward.rs │ │ ├── list_all_binstep.rs │ │ ├── claim_reward.rs │ │ ├── sync_price.rs │ │ ├── initialize_lb_pair.rs │ │ ├── show_pair.rs │ │ └── initialize_lb_pair2.rs │ └── math.rs ├── README.md └── Cargo.toml ├── market_making ├── src │ ├── config.json │ ├── router.rs │ ├── pair_config.rs │ ├── bin_array_manager.rs │ └── utils.rs ├── README.MD └── Cargo.toml ├── command_list ├── claim_fee_from_operator.sh ├── set_bootstrapping_pair_status.sh ├── ilm_single_bin_by_operator.sh └── ilm_curve_by_operator.sh ├── Anchor.toml └── Cargo.toml /python-client/dlmm/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /python-client/dlmm/.gitignore: -------------------------------------------------------------------------------- 1 | */__pycache__ -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.85.0" 3 | -------------------------------------------------------------------------------- /commons/tests/integration/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "pako": "^2.1.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /artifacts/lb_clmm.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/artifacts/lb_clmm.so -------------------------------------------------------------------------------- /python-client/dlmm/dlmm/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | 3 | from .dlmm import DLMM_CLIENT -------------------------------------------------------------------------------- /artifacts/token_2022.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/artifacts/token_2022.so -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /artifacts/transfer_hook_counter.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/artifacts/transfer_hook_counter.so -------------------------------------------------------------------------------- /commons/tests/artifacts/token_2022.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/artifacts/token_2022.so -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/rebalance/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./rebalancePosition"; 2 | export * from "./liquidity_strategy"; 3 | -------------------------------------------------------------------------------- /commons/tests/artifacts/lb_clmm_prod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/artifacts/lb_clmm_prod.so -------------------------------------------------------------------------------- /python-client/dlmm/dist/dlmm-0.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/python-client/dlmm/dist/dlmm-0.1.0.tar.gz -------------------------------------------------------------------------------- /python-client/dlmm/dist/dlmm-0.1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/python-client/dlmm/dist/dlmm-0.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/lb_pair.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/lb_pair.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/oracle.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/oracle.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/lb_pair.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/lb_pair.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/oracle.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/oracle.bin -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | RUN mkdir dlmm-server 3 | WORKDIR /dlmm-server 4 | COPY package*.json ./ 5 | RUN npm install 6 | COPY . . 7 | RUN npm run build 8 | EXPOSE 3000 9 | CMD ["npm", "start-server"] 10 | -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/reserve_x.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/reserve_x.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/reserve_y.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/reserve_y.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/reserve_x.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/reserve_x.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/reserve_y.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/reserve_y.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/bin_array_1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/bin_array_1.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/bin_array_2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/bin_array_2.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/token_x_mint.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/token_x_mint.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/bin_array_1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/bin_array_1.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/bin_array_2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/bin_array_2.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/token_x_mint.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/HEAD/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/token_x_mint.bin -------------------------------------------------------------------------------- /commons/src/math/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod price_math; 2 | pub use price_math::*; 3 | 4 | pub mod u64x64_math; 5 | pub use u64x64_math::*; 6 | 7 | pub mod u128x128_math; 8 | pub use u128x128_math::*; 9 | 10 | pub mod utils; 11 | pub use utils::*; 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LB CLMM SDK 2 | 3 | #### Quote Testing 4 | 5 | ``` 6 | cargo t -p commons --test '*' 7 | ``` 8 | 9 | #### SDK Testing 10 | 11 | ``` 12 | 1. cd ts-client 13 | 2. anchor localnet -- --features localnet 14 | 3. pnpm run test 15 | ``` 16 | -------------------------------------------------------------------------------- /.github/actions/setup-dep/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup" 2 | description: "Setup program dependencies" 3 | runs: 4 | using: "composite" 5 | steps: 6 | - run: sudo apt-get update && sudo apt-get install -y pkg-config build-essential libudev-dev 7 | shell: bash 8 | -------------------------------------------------------------------------------- /keys/localnet/admin-bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1.json: -------------------------------------------------------------------------------- 1 | [230,207,238,109,95,154,47,93,183,250,147,189,87,15,117,184,44,91,94,231,126,140,238,134,29,58,8,182,88,22,113,234,8,234,192,109,87,125,190,55,129,173,227,8,104,201,104,13,31,178,74,80,54,14,77,78,226,57,47,122,166,165,57,144] -------------------------------------------------------------------------------- /keys/localnet/program-LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ.json: -------------------------------------------------------------------------------- 1 | [237,14,0,252,204,70,136,161,168,214,209,214,165,86,118,17,167,67,226,89,141,50,93,57,21,217,228,215,232,31,23,19,5,5,8,150,192,245,85,119,65,35,231,38,247,167,119,108,169,108,10,152,101,233,92,168,216,177,25,12,113,154,69,75] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | **/*.rs.bk 5 | node_modules 6 | test-ledger 7 | .yarn 8 | yarn.lock 9 | .yarnrc.yml 10 | 11 | target/* 12 | !target/types 13 | !target/idl 14 | !target/debug/cli 15 | deployment 16 | 17 | ts-client/dist 18 | ts-client/.env 19 | 20 | .pnpm-debug.log 21 | -------------------------------------------------------------------------------- /commons/src/conversions/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | pub mod status; 3 | pub use status::*; 4 | 5 | pub mod pair_type; 6 | pub use pair_type::*; 7 | 8 | pub mod activation_type; 9 | pub use activation_type::*; 10 | 11 | pub mod token_program_flag; 12 | pub use token_program_flag::*; 13 | -------------------------------------------------------------------------------- /commons/src/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lb_pair; 2 | pub use lb_pair::*; 3 | 4 | pub mod bin_array; 5 | pub use bin_array::*; 6 | 7 | pub mod bin; 8 | pub use bin::*; 9 | 10 | pub mod bin_array_bitmap; 11 | pub use bin_array_bitmap::*; 12 | 13 | pub mod position; 14 | pub use position::*; 15 | -------------------------------------------------------------------------------- /ts-client/jest.config.js: -------------------------------------------------------------------------------- 1 | const TIMEOUT_SEC = 1000; 2 | 3 | module.exports = { 4 | preset: "ts-jest", 5 | testEnvironment: "node", 6 | transform: { 7 | "^.+\\.ts?$": "ts-jest", 8 | }, 9 | transformIgnorePatterns: ["/node_modules/"], 10 | testTimeout: TIMEOUT_SEC * 60 * 5, 11 | }; 12 | -------------------------------------------------------------------------------- /cli/src/instructions/ilm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod remove_liquidity_by_price_range; 2 | pub use remove_liquidity_by_price_range::*; 3 | 4 | pub mod seed_liquidity_from_operator; 5 | pub use seed_liquidity_from_operator::*; 6 | 7 | pub mod seed_liquidity_single_bin_by_operator; 8 | pub use seed_liquidity_single_bin_by_operator::*; 9 | -------------------------------------------------------------------------------- /ts-client/src/index.ts: -------------------------------------------------------------------------------- 1 | import { DLMM } from "./dlmm"; 2 | 3 | export default DLMM; 4 | export * from "./dlmm/helpers"; 5 | export * from "./dlmm/types"; 6 | export * from "./dlmm/error"; 7 | export * from "./dlmm/constants"; 8 | export * from "./dlmm/idl"; 9 | export { default as IDL } from "./dlmm/dlmm.json"; 10 | export * from "./dlmm/helpers/accountFilters"; 11 | -------------------------------------------------------------------------------- /.github/actions/setup-anchor/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup anchor-cli" 2 | description: "Setup anchor cli" 3 | runs: 4 | using: "composite" 5 | steps: 6 | - uses: taiki-e/cache-cargo-install-action@v2 7 | with: 8 | tool: avm 9 | tag: v${{ env.ANCHOR_CLI_VERSION }} 10 | git: https://github.com/coral-xyz/anchor 11 | - run: avm install ${{ env.ANCHOR_CLI_VERSION }} 12 | shell: bash 13 | -------------------------------------------------------------------------------- /cli/README.md: -------------------------------------------------------------------------------- 1 | # DLMM Cli 2 | 3 | Command line utility for managing DLMM program. 4 | 5 | ### Toolchain 6 | 7 | ``` 8 | channel = 1.76.0 9 | ``` 10 | 11 | If you're using M1 chip 12 | 13 | ``` 14 | channel = 1.76.0 15 | target triple = x86_64-apple-darwin 16 | # Eg: 1.76.0-x86_64-apple-darwin 17 | ``` 18 | 19 | ### Build 20 | 21 | ``` 22 | cargo build 23 | ``` 24 | 25 | ### Run 26 | 27 | target/debug/cli --help 28 | -------------------------------------------------------------------------------- /artifacts/FJbEo74c2W4QLBBVUfUvi8VBWXtMdJVPuFpq2f6UV1iB/FQuMNJ6UcrkA3xMLsmmLL8sk47RkZYod57mX3a8w1brg.json: -------------------------------------------------------------------------------- 1 | {"account":{"data":["AQAAANe+8tVwdizbqse7oVmqHK+2vLnma1/yqcz5ATnQwE7PABAvMmFGYwEJAQEAAADXvvLVcHYs26rHu6FZqhyvtry55mtf8qnM+QE50MBOzw==","base64"],"executable":false,"lamports":1461600,"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","rentEpoch":18446744073709551615,"space":82},"pubkey":"FQuMNJ6UcrkA3xMLsmmLL8sk47RkZYod57mX3a8w1brg"} -------------------------------------------------------------------------------- /market_making/src/config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pair_address": "jKzkEPEnoGkrR7QQqzsTDQ1MuGDSyHM3yCgYpNKwREm", 4 | "x_amount": 15000000000, 5 | "y_amount": 2000000, 6 | "mode": "ModeBoth" 7 | }, 8 | { 9 | "pair_address": "FoSDw2L5DmTuQTFe55gWPDXf88euaxAEKFre74CnvQbX", 10 | "x_amount": 17000000, 11 | "y_amount": 2000000, 12 | "mode": "ModeBoth" 13 | } 14 | ] -------------------------------------------------------------------------------- /artifacts/FJbEo74c2W4QLBBVUfUvi8VBWXtMdJVPuFpq2f6UV1iB/So11111111111111111111111111111111111111112.json: -------------------------------------------------------------------------------- 1 | {"account":{"data":["AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","base64"],"executable":false,"lamports":1384775953167,"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","rentEpoch":18446744073709551615,"space":82},"pubkey":"So11111111111111111111111111111111111111112"} -------------------------------------------------------------------------------- /commons/src/conversions/activation_type.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | impl TryFrom for ActivationType { 4 | type Error = anyhow::Error; 5 | 6 | fn try_from(value: u8) -> Result { 7 | match value { 8 | 0 => Ok(ActivationType::Slot), 9 | 1 => Ok(ActivationType::Timestamp), 10 | _ => Err(anyhow::anyhow!("Invalid ActivationType value: {}", value)), 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /commons/src/math/price_math.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | pub fn get_price_from_id(active_id: i32, bin_step: u16) -> Result { 4 | let bps = u128::from(bin_step) 5 | .checked_shl(SCALE_OFFSET.into()) 6 | .context("overflow")? 7 | .checked_div(BASIS_POINT_MAX as u128) 8 | .context("overflow")?; 9 | 10 | let base = ONE.checked_add(bps).context("overflow")?; 11 | 12 | pow(base, active_id).context("overflow") 13 | } 14 | -------------------------------------------------------------------------------- /market_making/README.MD: -------------------------------------------------------------------------------- 1 | An example for market making with DLMM 2 | 3 | ### Toolchain 4 | 5 | ``` 6 | channel = 1.76.0 7 | ``` 8 | 9 | If you're using M1 chip 10 | 11 | ``` 12 | channel = 1.76.0 13 | target triple = x86_64-apple-darwin 14 | # Eg: 1.76.0-x86_64-apple-darwin 15 | ``` 16 | 17 | ### Build 18 | 19 | ``` 20 | cargo build 21 | ``` 22 | 23 | ### Run 24 | 25 | target/debug/market_making --help 26 | 27 | ### Check positions: 28 | 29 | `http://localhost:8080/check_positions` 30 | -------------------------------------------------------------------------------- /python-client/dlmm/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "dlmm" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Tanishq Parkar "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.12" 10 | solders = "^0.21.0" 11 | solana = "^0.34.3" 12 | requests = "^2.32.3" 13 | 14 | 15 | [tool.poetry.group.dev.dependencies] 16 | pytest = "^8.3.2" 17 | 18 | [build-system] 19 | requires = ["poetry-core"] 20 | build-backend = "poetry.core.masonry.api" 21 | -------------------------------------------------------------------------------- /ts-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "types": ["jest"], 5 | "typeRoots": ["./node_modules/@types"], 6 | "module": "commonjs", 7 | "target": "ESNext", 8 | "moduleResolution": "node", 9 | "esModuleInterop": true, 10 | "resolveJsonModule": true, 11 | "noImplicitAny": false, 12 | "skipLibCheck": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "strict": true, 15 | "strictNullChecks": false, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /command_list/claim_fee_from_operator.sh: -------------------------------------------------------------------------------- 1 | cluster=https://api.mainnet-beta.solana.com 2 | operator_kp=~/.config/solana/id.json 3 | priority_fee=1000 4 | 5 | position=[Position pk] 6 | ./target/debug/cli --provider.cluster $cluster --provider.wallet $operator_kp --priority-fee $priority_fee claim-fee $position 7 | 8 | # get all positions 9 | # lb_pair=[Pool pk] 10 | # owner=[Owner pk] 11 | # ./target/debug/cli --provider.cluster $cluster get-all-positions-for-an-owner --lb-pair $lb_pair --owner $owner 12 | 13 | -------------------------------------------------------------------------------- /commons/src/typedefs.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct SwapResult { 3 | /// Amount of token swap into the bin 4 | pub amount_in_with_fees: u64, 5 | /// Amount of token swap out from the bin 6 | pub amount_out: u64, 7 | /// Swap fee, includes protocol fee 8 | pub fee: u64, 9 | /// Part of fee 10 | pub protocol_fee_after_host_fee: u64, 11 | /// Part of protocol fee 12 | pub host_fee: u64, 13 | /// Indicate whether we reach exact out amount 14 | pub is_exact_out_amount: bool, 15 | } 16 | -------------------------------------------------------------------------------- /command_list/set_bootstrapping_pair_status.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cluster="https://api.devnet.solana.com" 4 | 5 | pair="[LB pair public key]" 6 | pool_creator_path="[Pool creator keypair path]" 7 | 8 | # enable / disable pool 9 | enable=false 10 | 11 | if $enable 12 | then 13 | ./target/debug/cli set-pair-status-permissionless --lb-pair $pair --enable --provider.cluster $cluster --provider.wallet $pool_creator_path 14 | else 15 | ./target/debug/cli set-pair-status-permissionless --lb-pair $pair --provider.cluster $cluster --provider.wallet $pool_creator_path 16 | fi 17 | -------------------------------------------------------------------------------- /commons/src/account_filters.rs: -------------------------------------------------------------------------------- 1 | use anchor_client::solana_client::rpc_filter::{Memcmp, RpcFilterType}; 2 | use solana_sdk::pubkey::Pubkey; 3 | 4 | pub fn position_filter_by_wallet_and_pair(wallet: Pubkey, pair: Pubkey) -> Vec { 5 | let position_pair_filter = 6 | RpcFilterType::Memcmp(Memcmp::new_base58_encoded(8, &pair.to_bytes())); 7 | 8 | let position_owner_filter = RpcFilterType::Memcmp(Memcmp::new_base58_encoded( 9 | 8 + std::mem::size_of::(), 10 | &wallet.to_bytes(), 11 | )); 12 | 13 | vec![position_pair_filter, position_owner_filter] 14 | } 15 | -------------------------------------------------------------------------------- /commons/tests/integration/main.rs: -------------------------------------------------------------------------------- 1 | mod helpers; 2 | mod test_swap; 3 | mod test_swap_token2022; 4 | 5 | use anchor_lang::*; 6 | use anchor_spl::token::spl_token; 7 | use anchor_spl::token_2022::spl_token_2022; 8 | use anchor_spl::token_interface::*; 9 | use commons::dlmm::accounts::*; 10 | use commons::dlmm::types::*; 11 | use commons::*; 12 | use helpers::utils::*; 13 | use solana_program_test::*; 14 | use solana_sdk::instruction::{AccountMeta, Instruction}; 15 | use solana_sdk::native_token::LAMPORTS_PER_SOL; 16 | use solana_sdk::pubkey::Pubkey; 17 | use solana_sdk::signature::Signer; 18 | use std::collections::HashMap; 19 | -------------------------------------------------------------------------------- /commons/src/seeds.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::pubkey; 2 | use solana_sdk::pubkey::Pubkey; 3 | 4 | pub const BIN_ARRAY: &[u8] = b"bin_array"; 5 | 6 | pub const ORACLE: &[u8] = b"oracle"; 7 | 8 | pub const BIN_ARRAY_BITMAP_SEED: &[u8] = b"bitmap"; 9 | 10 | pub const PRESET_PARAMETER: &[u8] = b"preset_parameter"; 11 | 12 | pub const PRESET_PARAMETER2: &[u8] = b"preset_parameter2"; 13 | 14 | pub const POSITION: &[u8] = b"position"; 15 | 16 | pub const ILM_BASE_KEY: Pubkey = pubkey!("MFGQxwAmB91SwuYX36okv2Qmdc9aMuHTwWGUrp4AtB1"); 17 | 18 | pub const TOKEN_BADGE: &[u8] = b"token_badge"; 19 | 20 | pub const CLAIM_PROTOCOL_FEE_OPERATOR: &[u8] = b"cf_operator"; 21 | -------------------------------------------------------------------------------- /commons/src/conversions/status.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | impl TryFrom for PairStatus { 4 | type Error = anyhow::Error; 5 | 6 | fn try_from(value: u8) -> Result { 7 | match value { 8 | 0 => Ok(PairStatus::Enabled), 9 | 1 => Ok(PairStatus::Disabled), 10 | _ => Err(anyhow::anyhow!("Invalid PairStatus value: {}", value)), 11 | } 12 | } 13 | } 14 | 15 | impl PartialEq for PairStatus { 16 | fn eq(&self, other: &Self) -> bool { 17 | matches!( 18 | (self, other), 19 | (PairStatus::Enabled, PairStatus::Enabled) 20 | | (PairStatus::Disabled, PairStatus::Disabled) 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ts-client/src/examples/fetch_lb_pair_lock_info.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js"; 2 | import { DLMM } from "../dlmm"; 3 | 4 | async function fetchLbPairLockInfoExample() { 5 | const poolAddress = new PublicKey( 6 | "9DiruRpjnAnzhn6ts5HGLouHtJrT1JGsPbXNYCrFz2ad" 7 | ); 8 | 9 | let rpc = process.env.RPC || "https://api.mainnet-beta.solana.com"; 10 | const connection = new Connection(rpc, "finalized"); 11 | const dlmmPool = await DLMM.create(connection, poolAddress, { 12 | cluster: "mainnet-beta", 13 | }); 14 | 15 | const lbPairLockInfo = await dlmmPool.getLbPairLockInfo(); 16 | console.log(lbPairLockInfo); 17 | } 18 | 19 | fetchLbPairLockInfoExample(); 20 | -------------------------------------------------------------------------------- /commons/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::declare_program; 2 | use anyhow::*; 3 | 4 | declare_program!(dlmm); 5 | 6 | use dlmm::accounts::*; 7 | use dlmm::types::*; 8 | 9 | pub mod constants; 10 | pub use constants::*; 11 | 12 | pub mod conversions; 13 | pub use conversions::*; 14 | 15 | pub mod extensions; 16 | pub use extensions::*; 17 | 18 | pub mod pda; 19 | pub use pda::*; 20 | 21 | pub mod quote; 22 | pub use quote::*; 23 | 24 | pub mod seeds; 25 | pub use seeds::*; 26 | 27 | pub mod math; 28 | pub use math::*; 29 | 30 | pub mod typedefs; 31 | pub use typedefs::*; 32 | 33 | pub mod rpc_client_extension; 34 | 35 | pub mod account_filters; 36 | pub use account_filters::*; 37 | 38 | pub mod token_2022; 39 | pub use token_2022::*; 40 | -------------------------------------------------------------------------------- /.github/actions/setup-solana/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup Solana" 2 | description: "Setup Solana" 3 | runs: 4 | using: "composite" 5 | steps: 6 | - uses: actions/cache@v4 7 | name: Cache Solana Tool Suite 8 | id: cache-solana 9 | with: 10 | path: | 11 | ~/.cache/solana/ 12 | ~/.local/share/solana/ 13 | key: solana-${{ runner.os }}-v0000-${{ env.SOLANA_CLI_VERSION }} 14 | - run: sh -c "$(curl -sSfL https://release.anza.xyz/v${{ env.SOLANA_CLI_VERSION }}/install)" 15 | shell: bash 16 | - run: echo "$HOME/.local/share/solana/install/active_release/bin/" >> $GITHUB_PATH 17 | shell: bash 18 | - run: solana-keygen new --no-bip39-passphrase 19 | shell: bash 20 | - run: solana config set --url localhost 21 | shell: bash 22 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/rebalance/strategy/index.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import { BASIS_POINT_MAX } from "../../../constants"; 3 | import { 4 | RebalanceWithDeposit, 5 | RebalanceWithWithdraw, 6 | } from "../rebalancePosition"; 7 | 8 | export interface RebalanceDepositWithdrawParameters { 9 | shouldClaimFee: boolean; 10 | shouldClaimReward: boolean; 11 | deposits: RebalanceWithDeposit[]; 12 | withdraws: RebalanceWithWithdraw[]; 13 | } 14 | 15 | export interface RebalanceStrategyBuilder { 16 | buildRebalanceStrategyParameters(): RebalanceDepositWithdrawParameters; 17 | } 18 | 19 | export const MAX_BPS = new BN(BASIS_POINT_MAX); 20 | 21 | export function capBps(bps: BN) { 22 | return bps.lt(new BN(0)) 23 | ? new BN(0) 24 | : bps.gt(MAX_BPS) 25 | ? new BN(MAX_BPS) 26 | : bps; 27 | } 28 | -------------------------------------------------------------------------------- /Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [registry] 6 | url = "https://api.apr.dev" 7 | 8 | [provider] 9 | cluster = "Localnet" 10 | wallet = "keys/localnet/admin-bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1.json" 11 | 12 | [[test.genesis]] 13 | address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" 14 | program = "./artifacts/token_2022.so" 15 | 16 | [[test.genesis]] 17 | address = "LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ" 18 | program = "./artifacts/lb_clmm.so" 19 | 20 | [[test.genesis]] 21 | address = "abcSyangMHdGzUGKhBhKoQzSFdJKUdkPGf5cbXVHpEw" 22 | program = "./artifacts/transfer_hook_counter.so" 23 | 24 | [scripts] 25 | test = "yarn run ts-mocha --sort --type-check --bail -p ./tsconfig.json -t 1000000 tests/*.ts" 26 | 27 | [toolchain] 28 | solana_version = "2.1.0" 29 | anchor_version = "0.31.0" 30 | -------------------------------------------------------------------------------- /commons/src/rpc_client_extension.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_client::solana_client::nonblocking::rpc_client::RpcClient; 3 | use async_trait::async_trait; 4 | use solana_sdk::{account::Account, pubkey::Pubkey}; 5 | 6 | #[async_trait] 7 | pub trait RpcClientExtension { 8 | async fn get_account_and_deserialize( 9 | &self, 10 | pubkey: &Pubkey, 11 | deserialize_fn: fn(Account) -> Result, 12 | ) -> Result; 13 | } 14 | 15 | #[async_trait] 16 | impl RpcClientExtension for RpcClient { 17 | async fn get_account_and_deserialize( 18 | &self, 19 | pubkey: &Pubkey, 20 | deserialize_fn: fn(Account) -> Result, 21 | ) -> Result { 22 | let account = self.get_account(pubkey).await?; 23 | let data = deserialize_fn(account)?; 24 | Ok(data) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /commons/src/conversions/token_program_flag.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use std::ops::Deref; 3 | 4 | pub struct TokenProgramFlagWrapper(TokenProgramFlags); 5 | 6 | impl Deref for TokenProgramFlagWrapper { 7 | type Target = TokenProgramFlags; 8 | 9 | fn deref(&self) -> &Self::Target { 10 | &self.0 11 | } 12 | } 13 | 14 | impl TryFrom for TokenProgramFlagWrapper { 15 | type Error = anyhow::Error; 16 | 17 | fn try_from(value: u8) -> Result { 18 | match value { 19 | 0 => Ok(TokenProgramFlagWrapper(TokenProgramFlags::TokenProgram)), 20 | 1 => Ok(TokenProgramFlagWrapper(TokenProgramFlags::TokenProgram2022)), 21 | _ => Err(anyhow::anyhow!( 22 | "Invalid TokenProgramFlags value: {}", 23 | value 24 | )), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ts-client/src/test/decode.test.ts: -------------------------------------------------------------------------------- 1 | import { Connection, SYSVAR_CLOCK_PUBKEY } from "@solana/web3.js"; 2 | import { Clock, ClockLayout } from "../dlmm/types"; 3 | 4 | describe("Decode", () => { 5 | const connection = new Connection("http://127.0.0.1:8899", "processed"); 6 | 7 | test("Decode sysvar clock", async () => { 8 | const currentTime = Math.floor(Date.now() / 1000); 9 | 10 | const clockAccount = await connection.getAccountInfo(SYSVAR_CLOCK_PUBKEY); 11 | const clock = ClockLayout.decode(clockAccount!.data) as Clock; 12 | 13 | console.log(clock.slot.toString()); 14 | console.log(clock.unixTimestamp.toString()); 15 | 16 | const secondDiff = Math.abs(currentTime - clock.unixTimestamp.toNumber()); 17 | 18 | expect(clock.slot.toNumber()).toBeGreaterThan(0); 19 | expect(secondDiff).toBeLessThan(30); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /commons/src/math/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use num_traits::FromPrimitive; 3 | 4 | #[inline] 5 | pub fn safe_mul_shr_cast( 6 | x: u128, 7 | y: u128, 8 | offset: u8, 9 | rounding: Rounding, 10 | ) -> Result { 11 | T::from_u128(mul_shr(x, y, offset, rounding).context("overflow")?).context("overflow") 12 | } 13 | 14 | #[inline] 15 | pub fn safe_shl_div_cast( 16 | x: u128, 17 | y: u128, 18 | offset: u8, 19 | rounding: Rounding, 20 | ) -> Result { 21 | T::from_u128(shl_div(x, y, offset, rounding).context("overflow")?).context("overflow") 22 | } 23 | 24 | pub fn safe_mul_div_cast( 25 | x: u128, 26 | y: u128, 27 | denominator: u128, 28 | rounding: Rounding, 29 | ) -> Result { 30 | T::from_u128(mul_div(x, y, denominator, rounding).context("overflow")?).context("overflow") 31 | } 32 | -------------------------------------------------------------------------------- /ts-client/src/test/external/program.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, Program, Wallet, web3 } from "@coral-xyz/anchor"; 2 | import { TransferHookCounter } from "./transfer_hook_counter"; 3 | import TransferHookCounterIDL from "./transfer_hook_counter.json"; 4 | import { Connection } from "@solana/web3.js"; 5 | 6 | export const TRANSFER_HOOK_COUNTER_PROGRAM_ID = new web3.PublicKey( 7 | "abcSyangMHdGzUGKhBhKoQzSFdJKUdkPGf5cbXVHpEw" 8 | ); 9 | 10 | export function createTransferHookCounterProgram( 11 | wallet: Wallet, 12 | programId: web3.PublicKey, 13 | connection: Connection 14 | ): Program { 15 | const provider = new AnchorProvider(connection, wallet, { 16 | maxRetries: 3, 17 | }); 18 | 19 | const program = new Program( 20 | { ...TransferHookCounterIDL, address: programId }, 21 | provider 22 | ); 23 | 24 | return program; 25 | } 26 | -------------------------------------------------------------------------------- /ts-client/src/server/utils.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { LbPosition } from "../dlmm/types"; 3 | import BN from "bn.js"; 4 | 5 | export const convertToPosition = (rawPosition: any): LbPosition => { 6 | return { 7 | ...rawPosition, 8 | publicKey: new PublicKey(rawPosition.publicKey), 9 | positionData: { 10 | ...rawPosition.positionData, 11 | lastUpdatedAt: new BN(rawPosition.positionData.lastUpdatedAt, 16), 12 | feeX: new BN(rawPosition.positionData.feeX, 16), 13 | feeY: new BN(rawPosition.positionData.feeY, 16), 14 | rewardOne: new BN(rawPosition.positionData.rewardOne, 16), 15 | rewardTwo: new BN(rawPosition.positionData.rewardTwo, 16), 16 | feeOwner: new PublicKey(rawPosition.positionData.feeOwner), 17 | totalClaimedFeeXAmount: new BN(rawPosition.positionData.totalClaimedFeeXAmount, 16), 18 | totalClaimedFeeYAmount: new BN(rawPosition.positionData.totalClaimedFeeYAmount, 16), 19 | }, 20 | } 21 | } -------------------------------------------------------------------------------- /commons/src/conversions/pair_type.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | impl TryFrom for PairType { 4 | type Error = anyhow::Error; 5 | 6 | fn try_from(value: u8) -> Result { 7 | match value { 8 | 0 => Ok(PairType::Permissionless), 9 | 1 => Ok(PairType::Permission), 10 | 2 => Ok(PairType::CustomizablePermissionless), 11 | 3 => Ok(PairType::PermissionlessV2), 12 | _ => Err(anyhow::anyhow!("Invalid PairType value: {}", value)), 13 | } 14 | } 15 | } 16 | 17 | impl PartialEq for PairType { 18 | fn eq(&self, other: &Self) -> bool { 19 | match (self, other) { 20 | (&PairType::Permissionless, &PairType::Permissionless) => true, 21 | (&PairType::Permission, &PairType::Permission) => true, 22 | (&PairType::CustomizablePermissionless, &PairType::CustomizablePermissionless) => true, 23 | (&PairType::PermissionlessV2, &PairType::PermissionlessV2) => true, 24 | _ => false, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cli/src/instructions/show_position.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::Discriminator; 2 | 3 | use crate::*; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct ShowPositionParams { 7 | pub position: Pubkey, 8 | } 9 | 10 | pub async fn execute_show_position + Clone>( 11 | params: ShowPositionParams, 12 | program: &Program, 13 | ) -> Result<()> { 14 | let ShowPositionParams { position } = params; 15 | 16 | let rpc_client = program.rpc(); 17 | let position_account = rpc_client.get_account(&position).await?; 18 | 19 | let mut disc = [0u8; 8]; 20 | disc.copy_from_slice(&position_account.data[..8]); 21 | 22 | if disc == Position::DISCRIMINATOR { 23 | let position_state: Position = bytemuck::pod_read_unaligned(&position_account.data[8..]); 24 | println!("{:#?}", position_state); 25 | } else if disc == PositionV2::DISCRIMINATOR { 26 | let position_state: PositionV2 = bytemuck::pod_read_unaligned(&position_account.data[8..]); 27 | println!("{:#?}", position_state); 28 | } else { 29 | bail!("Not a valid position account"); 30 | }; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/ci-pr-main-program.yml: -------------------------------------------------------------------------------- 1 | name: DLMM Commons 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | common_changed_files: 10 | runs-on: ubuntu-latest 11 | outputs: 12 | program: ${{steps.changed-files-specific.outputs.any_changed}} 13 | steps: 14 | - uses: actions/checkout@v2 15 | with: 16 | fetch-depth: 0 17 | - name: Get specific changed files 18 | id: changed-files-specific 19 | uses: tj-actions/changed-files@v18.6 20 | with: 21 | files: | 22 | commons 23 | artifacts 24 | 25 | common_test: 26 | runs-on: ubuntu-latest 27 | needs: common_changed_files 28 | if: needs.common_changed_files.outputs.program == 'true' 29 | steps: 30 | - uses: actions/checkout@v2 31 | # Install rust + toolchain 32 | - uses: actions-rs/toolchain@v1 33 | with: 34 | toolchain: 1.85.0 35 | override: true 36 | components: clippy 37 | # Cache rust, cargo 38 | - uses: Swatinem/rust-cache@v1 39 | - run: cargo t -p commons --test '*' 40 | shell: bash 41 | -------------------------------------------------------------------------------- /commons/src/math/u128x128_math.rs: -------------------------------------------------------------------------------- 1 | use crate::Rounding; 2 | use ruint::aliases::U256; 3 | 4 | /// (x * y) / denominator 5 | pub fn mul_div(x: u128, y: u128, denominator: u128, rounding: Rounding) -> Option { 6 | if denominator == 0 { 7 | return None; 8 | } 9 | 10 | let x = U256::from(x); 11 | let y = U256::from(y); 12 | let denominator = U256::from(denominator); 13 | 14 | let prod = x.checked_mul(y)?; 15 | 16 | match rounding { 17 | Rounding::Up => prod.div_ceil(denominator).try_into().ok(), 18 | Rounding::Down => { 19 | let (quotient, _) = prod.div_rem(denominator); 20 | quotient.try_into().ok() 21 | } 22 | } 23 | } 24 | 25 | /// (x * y) >> offset 26 | #[inline] 27 | pub fn mul_shr(x: u128, y: u128, offset: u8, rounding: Rounding) -> Option { 28 | let denominator = 1u128.checked_shl(offset.into())?; 29 | mul_div(x, y, denominator, rounding) 30 | } 31 | 32 | /// (x << offset) / y 33 | #[inline] 34 | pub fn shl_div(x: u128, y: u128, offset: u8, rounding: Rounding) -> Option { 35 | let scale = 1u128.checked_shl(offset.into())?; 36 | mul_div(x, scale, y, rounding) 37 | } 38 | -------------------------------------------------------------------------------- /.github/workflows/ci-pr-main-cli.yml: -------------------------------------------------------------------------------- 1 | name: DLMM Cli 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | env: 9 | SOLANA_CLI_VERSION: 2.1.0 10 | NODE_VERSION: 20.11.0 11 | ANCHOR_CLI_VERSION: 0.31.0 12 | 13 | jobs: 14 | cli_changed_files: 15 | runs-on: ubuntu-latest 16 | outputs: 17 | cli: ${{steps.changed-files-specific.outputs.any_changed}} 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - name: Get specific changed files 23 | id: changed-files-specific 24 | uses: tj-actions/changed-files@v18.6 25 | with: 26 | files: | 27 | cli 28 | 29 | cli_build: 30 | runs-on: ubuntu-latest 31 | needs: cli_changed_files 32 | if: needs.cli_changed_files.outputs.cli == 'true' 33 | steps: 34 | - uses: actions/checkout@v2 35 | # Install rust + toolchain 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: 1.85.0 39 | override: true 40 | components: clippy 41 | # Cache rust, cargo 42 | - uses: Swatinem/rust-cache@v1 43 | - run: cargo build -p cli 44 | shell: bash 45 | -------------------------------------------------------------------------------- /cli/src/instructions/show_preset_parameters.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::Discriminator; 2 | 3 | use crate::*; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct ShowPresetAccountParams { 7 | pub preset_parameter: Pubkey, 8 | } 9 | 10 | pub async fn execute_show_preset_parameters + Clone>( 11 | params: ShowPresetAccountParams, 12 | program: &Program, 13 | ) -> Result<()> { 14 | let ShowPresetAccountParams { preset_parameter } = params; 15 | 16 | let rpc_client = program.rpc(); 17 | let account = rpc_client.get_account(&preset_parameter).await?; 18 | 19 | let mut disc = [0u8; 8]; 20 | disc.copy_from_slice(&account.data[..8]); 21 | 22 | if disc == PresetParameter::DISCRIMINATOR { 23 | let preset_param_state = PresetParameter::try_deserialize(&mut account.data.as_ref())?; 24 | println!("{:#?}", preset_param_state); 25 | } else if disc == PresetParameter2::DISCRIMINATOR { 26 | let preset_param_state: PresetParameter2 = bytemuck::pod_read_unaligned(&account.data[8..]); 27 | println!("{:#?}", preset_param_state); 28 | } else { 29 | bail!("Not a valid preset parameter account"); 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /ts-client/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from "tsup"; 2 | 3 | const config: Options = { 4 | entry: ["src/index.ts"], 5 | format: ["esm", "cjs"], 6 | splitting: true, 7 | sourcemap: true, 8 | minify: false, 9 | clean: true, 10 | skipNodeModulesBundle: true, 11 | dts: true, 12 | external: ["node_modules"], 13 | // post-process CJS build to fix default export for require() users 14 | onSuccess: async () => { 15 | const fs = await import("fs"); 16 | const path = await import("path"); 17 | const cjsPath = path.join(process.cwd(), "dist", "index.js"); 18 | let content = fs.readFileSync(cjsPath, "utf8"); 19 | 20 | content += 21 | "\n\n// CJS interop: Make default export primary for require() compatibility\n"; 22 | content += "if (exports.default) {\n"; 23 | content += " module.exports = exports.default;\n"; 24 | content += " for (const key in exports) {\n"; 25 | content += ' if (key !== "default") {\n'; 26 | content += " module.exports[key] = exports[key];\n"; 27 | content += " }\n"; 28 | content += " }\n"; 29 | content += "}\n"; 30 | 31 | fs.writeFileSync(cjsPath, content); 32 | }, 33 | }; 34 | 35 | export default config; 36 | -------------------------------------------------------------------------------- /commons/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "commons" 3 | version = "0.3.2" 4 | edition = "2021" 5 | description = "Common libraries for DLMM" 6 | authors = ["tian "] 7 | 8 | [features] 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | anchor-lang = { workspace = true } 14 | anchor-client = { workspace = true, features = ["async"] } 15 | anchor-spl = { workspace = true } 16 | anyhow = { workspace = true } 17 | tokio = { workspace = true, features = ["full", "parking_lot"] } 18 | bincode = { workspace = true } 19 | solana-sdk = { workspace = true } 20 | ruint = { workspace = true } 21 | num-traits = { workspace = true } 22 | num-integer = { workspace = true } 23 | bytemuck = { workspace = true, features = ["derive", "min_const_generics"] } 24 | async-trait = { workspace = true } 25 | spl-transfer-hook-interface = { workspace = true } 26 | 27 | [dev-dependencies] 28 | spl-associated-token-account = { workspace = true } 29 | solana-program-test = "2.1.0" 30 | assert_matches = "1.5.0" 31 | solana-program = "2.1.0" 32 | spl-memo = { workspace = true, features = ["no-entrypoint"] } 33 | litesvm = "0.6.0" 34 | serde_json = { workspace = true } 35 | -------------------------------------------------------------------------------- /python-client/dlmm/dlmm/utils.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from solders.hash import Hash 3 | from solders.pubkey import Pubkey 4 | from solders.keypair import Keypair 5 | from solana.transaction import Transaction 6 | from solders.instruction import Instruction, AccountMeta 7 | 8 | def convert_to_transaction(response: dict) -> Transaction: 9 | recent_blockhash = Hash.from_string(response["recentBlockhash"]) 10 | fee_payer = Pubkey.from_string(response["feePayer"]) 11 | 12 | instructions: List[Instruction] = [] 13 | for instruction in response["instructions"]: 14 | keys = [AccountMeta(Pubkey.from_string(key["pubkey"]), key["isSigner"], key["isWritable"]) for key in instruction["keys"]] 15 | data = bytes(instruction['data']) 16 | program_id = Pubkey.from_string(instruction['programId']) 17 | compiled_instruction = Instruction( 18 | program_id=program_id, 19 | data=data, 20 | accounts=keys 21 | ) 22 | instructions.append(compiled_instruction) 23 | 24 | transaction = Transaction( 25 | recent_blockhash=recent_blockhash, 26 | instructions=instructions, 27 | fee_payer=fee_payer, 28 | ) 29 | 30 | return transaction 31 | 32 | -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "0.5.2" 4 | edition = "2021" 5 | description = "cli" 6 | authors = ["tian "] 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [features] 11 | 12 | [dependencies] 13 | commons = { workspace = true } 14 | anchor-lang = { workspace = true } 15 | anchor-spl = { workspace = true } 16 | anchor-client = { workspace = true, features = ["async"] } 17 | clap = { workspace = true, features = ["derive"] } 18 | anyhow = { workspace = true } 19 | shellexpand = { workspace = true } 20 | rust_decimal = { workspace = true, features = ["maths"] } 21 | spl-associated-token-account = { workspace = true } 22 | rand = { workspace = true } 23 | tokio = { workspace = true, features = ["full", "parking_lot"] } 24 | bincode = { workspace = true } 25 | spl-memo = { workspace = true, features = ["no-entrypoint"] } 26 | spl-transfer-hook-interface = { workspace = true } 27 | solana-account-decoder = { workspace = true } 28 | num-integer = { workspace = true } 29 | bytemuck = { workspace = true } 30 | futures-util = { workspace = true } 31 | 32 | bigdecimal = "0.4.2" 33 | serde = "1.0.167" 34 | serde_json = "1.0.100" 35 | serde_json_any_key = "2.0.0" 36 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["cli", "commons", "market_making"] 3 | resolver = "2" 4 | 5 | [workspace.dependencies] 6 | anchor-lang = "0.31.0" 7 | anchor-spl = "0.31.0" 8 | anchor-client = "0.31.0" 9 | 10 | solana-sdk = "2.1.0" 11 | spl-associated-token-account = "6.0.0" 12 | solana-transaction-status = "2.1.0" 13 | solana-account-decoder = "2.1.0" 14 | spl-memo = "6.0.0" 15 | spl-transfer-hook-interface = "0.9.0" 16 | 17 | serde_json = "1.0.140" 18 | serde = "1.0.219" 19 | bincode = "1.3.3" 20 | bs58 = "0.5.0" 21 | bytemuck = "1.13.1" 22 | 23 | clap = "4.3.3" 24 | shellexpand = "3.1.0" 25 | 26 | env_logger = "0.9.0" 27 | log = "0.4.17" 28 | 29 | rust_decimal = "1.31.0" 30 | ruint = "1.3.0" 31 | num-integer = "0.1.45" 32 | num-traits = "0.2.16" 33 | 34 | hyper = "0.14.17" 35 | routerify = "3" 36 | 37 | tokio = "^1.0" 38 | futures-util = "0.3.0" 39 | async-trait = "0.1.0" 40 | 41 | anyhow = "1.0.71" 42 | 43 | rand = "0.8.5" 44 | 45 | chrono = "0.4.31" 46 | 47 | ureq = "2.0.0" 48 | 49 | itertools = "0.10.0" 50 | 51 | commons = { path = "./commons" } 52 | 53 | [profile.release] 54 | overflow-checks = true 55 | lto = "fat" 56 | codegen-units = 1 57 | 58 | [profile.release.build-override] 59 | opt-level = 3 60 | incremental = false 61 | codegen-units = 1 62 | -------------------------------------------------------------------------------- /ts-client/src/examples/swap_quote.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from "@solana/web3.js"; 2 | import { DLMM } from "../dlmm"; 3 | import BN from "bn.js"; 4 | 5 | async function swapQuote( 6 | poolAddress: PublicKey, 7 | swapAmount: BN, 8 | swapYtoX: boolean, 9 | isPartialFill: boolean, 10 | maxExtraBinArrays: number = 0 11 | ) { 12 | let rpc = "https://api.mainnet-beta.solana.com"; 13 | const connection = new Connection(rpc, "finalized"); 14 | const dlmmPool = await DLMM.create(connection, poolAddress, { 15 | cluster: "mainnet-beta", 16 | }); 17 | 18 | const binArrays = await dlmmPool.getBinArrayForSwap(swapYtoX); 19 | 20 | const swapQuote = dlmmPool.swapQuote( 21 | swapAmount, 22 | swapYtoX, 23 | new BN(10), 24 | binArrays, 25 | isPartialFill, 26 | maxExtraBinArrays 27 | ); 28 | console.log("🚀 ~ swapQuote:", swapQuote); 29 | console.log( 30 | "consumedInAmount: %s, outAmount: %s", 31 | swapQuote.consumedInAmount.toString(), 32 | swapQuote.outAmount.toString() 33 | ); 34 | } 35 | 36 | async function main() { 37 | await swapQuote( 38 | new PublicKey("5BKxfWMbmYBAEWvyPZS9esPducUba9GqyMjtLCfbaqyF"), 39 | new BN(5_000 * 10 ** 6), 40 | true, 41 | false, 42 | 3 43 | ); 44 | } 45 | 46 | main(); 47 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod close_preset_parameter; 2 | pub use close_preset_parameter::*; 3 | 4 | pub mod initialize_permission_lb_pair; 5 | pub use initialize_permission_lb_pair::*; 6 | 7 | pub mod initialize_preset_parameter; 8 | pub use initialize_preset_parameter::*; 9 | 10 | pub mod initialize_reward; 11 | pub use initialize_reward::*; 12 | 13 | pub mod set_activation_point; 14 | pub use set_activation_point::*; 15 | 16 | pub mod set_pre_activation_duration; 17 | pub use set_pre_activation_duration::*; 18 | 19 | pub mod set_pre_activation_swap_address; 20 | pub use set_pre_activation_swap_address::*; 21 | 22 | pub mod toggle_pair_status; 23 | pub use toggle_pair_status::*; 24 | 25 | pub mod update_reward_duration; 26 | pub use update_reward_duration::*; 27 | 28 | pub mod update_reward_funder; 29 | pub use update_reward_funder::*; 30 | 31 | pub mod withdraw_protocol_fee; 32 | pub use withdraw_protocol_fee::*; 33 | 34 | pub mod initialize_token_badge; 35 | pub use initialize_token_badge::*; 36 | 37 | pub mod create_claim_protocol_fee_operator; 38 | pub use create_claim_protocol_fee_operator::*; 39 | 40 | pub mod close_claim_protocol_fee_operator; 41 | pub use close_claim_protocol_fee_operator::*; 42 | 43 | pub mod update_base_fee; 44 | pub use update_base_fee::*; 45 | -------------------------------------------------------------------------------- /market_making/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "market_making" 3 | version = "0.0.1" 4 | description = "Market making bot" 5 | edition = "2021" 6 | authors = ["andrew "] 7 | 8 | [dependencies] 9 | tokio = { workspace = true, features = ["full"] } 10 | hyper = { workspace = true, features = ["full"] } 11 | routerify = { workspace = true } 12 | ureq = { workspace = true, features = ["json"] } 13 | anchor-client = { workspace = true, features = ["async"] } 14 | anchor-spl = { workspace = true } 15 | anchor-lang = { workspace = true } 16 | env_logger = { workspace = true } 17 | log = { workspace = true } 18 | clap = { workspace = true, features = ["derive"] } 19 | shellexpand = { workspace = true } 20 | anyhow = { workspace = true } 21 | serde_json = { workspace = true } 22 | serde = { workspace = true, features = ["derive"] } 23 | spl-associated-token-account = { workspace = true } 24 | solana-transaction-status = { workspace = true } 25 | bs58 = { workspace = true } 26 | chrono = { workspace = true } 27 | commons = { workspace = true } 28 | solana-account-decoder = { workspace = true } 29 | itertools = { workspace = true } 30 | rust_decimal = { workspace = true, features = ["maths"] } 31 | spl-memo = { workspace = true, features = ["no-entrypoint"] } 32 | bytemuck = { workspace = true } 33 | -------------------------------------------------------------------------------- /.github/workflows/ci-pr-main-market-making.yml: -------------------------------------------------------------------------------- 1 | name: DLMM Market Making Example 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | env: 9 | SOLANA_CLI_VERSION: 2.1.0 10 | NODE_VERSION: 20.11.0 11 | ANCHOR_CLI_VERSION: 0.31.0 12 | 13 | jobs: 14 | market_making_changed_files: 15 | runs-on: ubuntu-latest 16 | outputs: 17 | market_making: ${{steps.changed-files-specific.outputs.any_changed}} 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - name: Get specific changed files 23 | id: changed-files-specific 24 | uses: tj-actions/changed-files@v18.6 25 | with: 26 | files: | 27 | market_making 28 | 29 | market_making_build: 30 | runs-on: ubuntu-latest 31 | needs: market_making_changed_files 32 | if: needs.market_making_changed_files.outputs.market_making == 'true' 33 | steps: 34 | - uses: actions/checkout@v2 35 | # Install rust + toolchain 36 | - uses: actions-rs/toolchain@v1 37 | with: 38 | toolchain: 1.85.0 39 | override: true 40 | components: clippy 41 | # Cache rust, cargo 42 | - uses: Swatinem/rust-cache@v1 43 | - run: cargo build -p market_making 44 | shell: bash 45 | -------------------------------------------------------------------------------- /command_list/ilm_single_bin_by_operator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cluster="https://api.devnet.solana.com" 3 | # Get from initialize pair 4 | pair="[LB pair public key]" 5 | 6 | # Liquidity for seeding. UI amount. 7 | amount=1000000 8 | # Price 9 | price=0.25 10 | # Pool start price rounding if the price cannot be exact. "up", "down", "none". None will terminate the script if the price cannot be exact. 11 | price_rounding="up" 12 | 13 | # Keypair paths 14 | # Position owner keypair path 15 | position_owner="[Position owner public key]" 16 | fee_owner="[Fee owner public key]" 17 | lock_release_point=0 18 | 19 | # Base position keypair path 20 | base_position_path="deployer.json" 21 | operator_kp_path="deployer.json" 22 | # Get base_position_path pubkey by solana-keygen pubkey 23 | base_position_key="[Address of deployer.json]" 24 | priority_fee_microlamport=100000 25 | 26 | # Seed liquidity 27 | ./target/debug/cli seed-liquidity-single-bin-by-operator --lb-pair $pair --base-position-path $base_position_path --base-pubkey $base_position_key --amount $amount \ 28 | --price $price --position-owner $position_owner --fee-owner $fee_owner --lock-release-point $lock_release_point --selective-rounding $price_rounding\ 29 | --provider.cluster $cluster --provider.wallet $operator_kp_path --priority-fee $priority_fee_microlamport -------------------------------------------------------------------------------- /cli/src/instructions/admin/toggle_pair_status.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct SetPairStatusParams { 5 | /// Address of the pair 6 | pub lb_pair: Pubkey, 7 | /// Pair status. 0 is enabled, 1 is disabled 8 | pub pair_status: u8, 9 | } 10 | 11 | pub async fn execute_set_pair_status + Clone>( 12 | params: SetPairStatusParams, 13 | program: &Program, 14 | transaction_config: RpcSendTransactionConfig, 15 | ) -> Result<()> { 16 | let SetPairStatusParams { 17 | lb_pair, 18 | pair_status, 19 | } = params; 20 | 21 | let accounts = dlmm::client::accounts::SetPairStatus { 22 | admin: program.payer(), 23 | lb_pair, 24 | } 25 | .to_account_metas(None); 26 | 27 | let data = dlmm::client::args::SetPairStatus { 28 | status: pair_status, 29 | } 30 | .data(); 31 | 32 | let instruction = Instruction { 33 | program_id: dlmm::ID, 34 | accounts, 35 | data, 36 | }; 37 | 38 | let request_builder = program.request(); 39 | let signature = request_builder 40 | .instruction(instruction) 41 | .send_with_spinner_and_config(transaction_config) 42 | .await; 43 | 44 | println!("Set pair status. Signature: {:#?}", signature); 45 | 46 | signature?; 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/set_activation_point.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct SetActivationPointParam { 5 | /// Address of the pair 6 | pub lb_pair: Pubkey, 7 | /// Activation point 8 | pub activation_point: u64, 9 | } 10 | 11 | pub async fn execute_set_activation_point + Clone>( 12 | params: SetActivationPointParam, 13 | program: &Program, 14 | transaction_config: RpcSendTransactionConfig, 15 | ) -> Result<()> { 16 | let SetActivationPointParam { 17 | lb_pair, 18 | activation_point, 19 | } = params; 20 | 21 | let accounts = dlmm::client::accounts::SetActivationPoint { 22 | admin: program.payer(), 23 | lb_pair, 24 | } 25 | .to_account_metas(None); 26 | 27 | let data = dlmm::client::args::SetActivationPoint { activation_point }.data(); 28 | 29 | let set_activation_point_ix = Instruction { 30 | accounts, 31 | data, 32 | program_id: dlmm::ID, 33 | }; 34 | 35 | let request_builder = program.request(); 36 | let signature = request_builder 37 | .instruction(set_activation_point_ix) 38 | .send_with_spinner_and_config(transaction_config) 39 | .await; 40 | 41 | println!("Set activation point. Signature: {:#?}", signature); 42 | 43 | signature?; 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /python-client/dlmm/README.md: -------------------------------------------------------------------------------- 1 | # DLMM Python SDK 2 | 3 | ## Using the SDK 4 | 1. Install the SDK and other necessary libraries 5 | ```bash 6 | pip install dlmm solders 7 | ``` 8 | 2. Initialize DLMM instance 9 | ```python 10 | from dlmm import DLMM_CLIENT 11 | from solders.pubkey import Pubkey 12 | 13 | RPC = "https://api.devnet.solana.com" 14 | pool_address = Pubkey.from_string("3W2HKgUa96Z69zzG3LK1g8KdcRAWzAttiLiHfYnKuPw5") # You can get your desired pool address from the API https://dlmm-api.meteora.ag/pair/all 15 | dlmm = DLMM_CLIENT.create(pool_address, RPC) # Returns DLMM object instance 16 | ``` 17 | Now you can use the `dlmm` object to interact with different methods of the [DLMM](https://docs.meteora.ag/dlmm/dlmm-integration/dlmm-sdk). 18 | 19 | ## Setup and Run (Development) 20 | 1. Install [poetry](https://python-poetry.org/docs/#installing-with-the-official-installer/). 21 | 2. CD to `python-client/dlmm` and Run `poetry install` to install the dependencies. 22 | 3. Open another terminal, CD to `ts-client`. 23 | 4. Install the dependencies using `npm install` and run the server using `npm start-server`. 24 | 5. In the `dlmm.py`, if the API_URL is not already set to `localhost:3000`. 25 | 6. Add new dependencies using `poetry add package_name` 26 | 7. Now you can add and modify the python code and add tests as required under `dlmm/tests` directory and test them using `poetry run pytest`. 27 | 28 | -------------------------------------------------------------------------------- /command_list/ilm_curve_by_operator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cluster="https://api.devnet.solana.com" 3 | 4 | # Get from initialize pair 5 | pair="[LB pair public key]" 6 | 7 | # ILM parameters 8 | min_price=0.003 9 | max_price=0.03 10 | curvature=0.8 11 | # Liquidity for seeding. UI amount. 12 | amount=150000000 13 | # Keypair paths 14 | # Position owner public key 15 | position_owner="[Position owner public key]" 16 | # Fee owner public key 17 | fee_owner="[Fee owner public key]" 18 | # Lock release point, the point when position can be withdraw 19 | lock_release_point=0 20 | # Base position keypair path 21 | base_position_path="[Base position keypair path (can be the same with operator kp path)]" 22 | # Deployer keypair path 23 | operator_kp_path="[Deployer keypair path]" 24 | # Get base_position_path pubkey by solana-keygen pubkey 25 | base_position_key="[Base public key]" 26 | priority_fee_microlamport=100000 27 | max_retries=1000 28 | 29 | # Seed liquidity 30 | ./target/debug/cli seed-liquidity-by-operator --lb-pair $pair --base-position-path $base_position_path --base-pubkey $base_position_key --amount $amount \ 31 | --min-price $min_price --max-price $max_price --curvature $curvature --position-owner $position_owner --fee-owner $fee_owner --lock-release-point $lock_release_point\ 32 | --max-retries $max_retries --provider.cluster $cluster --provider.wallet $operator_kp_path --priority-fee $priority_fee_microlamport -------------------------------------------------------------------------------- /ts-client/src/test/external/helper.ts: -------------------------------------------------------------------------------- 1 | import { Program, web3 } from "@coral-xyz/anchor"; 2 | import { TransferHookCounter } from "./transfer_hook_counter"; 3 | import { 4 | ASSOCIATED_TOKEN_PROGRAM_ID, 5 | getExtraAccountMetaAddress, 6 | TOKEN_2022_PROGRAM_ID, 7 | } from "@solana/spl-token"; 8 | import { SystemProgram } from "@solana/web3.js"; 9 | 10 | export async function createExtraAccountMetaListAndCounter( 11 | program: Program, 12 | mint: web3.PublicKey 13 | ) { 14 | const extraAccountMetaList = getExtraAccountMetaAddress( 15 | mint, 16 | program.programId 17 | ); 18 | const counterAccount = deriveCounter(mint, program.programId); 19 | 20 | await program.methods 21 | .initializeExtraAccountMetaList() 22 | .accountsStrict({ 23 | mint, 24 | counterAccount, 25 | extraAccountMetaList, 26 | tokenProgram: TOKEN_2022_PROGRAM_ID, 27 | payer: program.provider.wallet.publicKey, 28 | systemProgram: SystemProgram.programId, 29 | associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 30 | }) 31 | .rpc(); 32 | 33 | return [extraAccountMetaList, counterAccount]; 34 | } 35 | 36 | export function deriveCounter(mint: web3.PublicKey, programId: web3.PublicKey) { 37 | const [counter] = web3.PublicKey.findProgramAddressSync( 38 | [Buffer.from("counter"), mint.toBuffer()], 39 | programId 40 | ); 41 | 42 | return counter; 43 | } 44 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/initialize_token_badge.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use solana_sdk::system_program; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct InitializeTokenBadgeParams { 6 | /// Token mint address 7 | pub mint: Pubkey, 8 | } 9 | 10 | pub async fn execute_initialize_token_badge + Clone>( 11 | params: InitializeTokenBadgeParams, 12 | program: &Program, 13 | transaction_config: RpcSendTransactionConfig, 14 | ) -> Result<()> { 15 | let InitializeTokenBadgeParams { mint } = params; 16 | 17 | let (token_badge, _bump) = derive_token_badge_pda(mint); 18 | 19 | let accounts = dlmm::client::accounts::InitializeTokenBadge { 20 | admin: program.payer(), 21 | token_mint: mint, 22 | system_program: system_program::ID, 23 | token_badge, 24 | } 25 | .to_account_metas(None); 26 | 27 | let data = dlmm::client::args::InitializeTokenBadge {}.data(); 28 | 29 | let instruction = Instruction { 30 | program_id: dlmm::ID, 31 | accounts, 32 | data, 33 | }; 34 | 35 | let request_builder = program.request(); 36 | let signature = request_builder 37 | .instruction(instruction) 38 | .send_with_spinner_and_config(transaction_config) 39 | .await; 40 | 41 | println!("Initialize token badge {}. Signature: {signature:#?}", mint); 42 | 43 | signature?; 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/close_claim_protocol_fee_operator.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct CloseClaimFeeOperatorParams { 5 | #[clap(long)] 6 | pub operator: Pubkey, 7 | } 8 | 9 | pub async fn execute_close_claim_protocol_fee_operator + Clone>( 10 | params: CloseClaimFeeOperatorParams, 11 | program: &Program, 12 | transaction_config: RpcSendTransactionConfig, 13 | ) -> Result<()> { 14 | let CloseClaimFeeOperatorParams { operator } = params; 15 | 16 | let (claim_fee_operator, _bump) = derive_claim_protocol_fee_operator_pda(operator); 17 | 18 | let accounts = dlmm::client::accounts::CloseClaimProtocolFeeOperator { 19 | claim_fee_operator, 20 | admin: program.payer(), 21 | rent_receiver: program.payer(), 22 | } 23 | .to_account_metas(None); 24 | 25 | let data = dlmm::client::args::CloseClaimProtocolFeeOperator {}.data(); 26 | 27 | let instruction = Instruction { 28 | program_id: dlmm::ID, 29 | accounts, 30 | data, 31 | }; 32 | 33 | let request_builder = program.request(); 34 | let signature = request_builder 35 | .instruction(instruction) 36 | .send_with_spinner_and_config(transaction_config) 37 | .await; 38 | 39 | println!("Close claim protocol fee operator. Signature: {signature:#?}"); 40 | 41 | signature?; 42 | 43 | Ok(()) 44 | } 45 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/create_claim_protocol_fee_operator.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct CreateClaimFeeOperatorParams { 5 | #[clap(long)] 6 | pub operator: Pubkey, 7 | } 8 | 9 | pub async fn execute_create_claim_protocol_fee_operator + Clone>( 10 | params: CreateClaimFeeOperatorParams, 11 | program: &Program, 12 | transaction_config: RpcSendTransactionConfig, 13 | ) -> Result<()> { 14 | let CreateClaimFeeOperatorParams { operator } = params; 15 | 16 | let (claim_fee_operator, _bump) = derive_claim_protocol_fee_operator_pda(operator); 17 | 18 | let accounts = dlmm::client::accounts::CreateClaimProtocolFeeOperator { 19 | claim_fee_operator, 20 | operator, 21 | admin: program.payer(), 22 | system_program: anchor_lang::system_program::ID, 23 | } 24 | .to_account_metas(None); 25 | 26 | let data = dlmm::client::args::CreateClaimProtocolFeeOperator {}.data(); 27 | 28 | let instruction = Instruction { 29 | program_id: dlmm::ID, 30 | accounts, 31 | data, 32 | }; 33 | 34 | let request_builder = program.request(); 35 | let signature = request_builder 36 | .instruction(instruction) 37 | .send_with_spinner_and_config(transaction_config) 38 | .await; 39 | 40 | println!("Create claim protocol fee operator. Signature: {signature:#?}"); 41 | 42 | signature?; 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/set_pre_activation_duration.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct SetPreactivationDurationParam { 5 | pub lb_pair: Pubkey, 6 | pub pre_activation_duration: u16, 7 | } 8 | 9 | pub async fn execute_set_pre_activation_duration + Clone>( 10 | params: SetPreactivationDurationParam, 11 | program: &Program, 12 | transaction_config: RpcSendTransactionConfig, 13 | ) -> Result<()> { 14 | let SetPreactivationDurationParam { 15 | lb_pair, 16 | pre_activation_duration, 17 | } = params; 18 | 19 | let accounts = dlmm::client::accounts::SetPreActivationDuration { 20 | creator: program.payer(), 21 | lb_pair, 22 | } 23 | .to_account_metas(None); 24 | 25 | let data = dlmm::client::args::SetPreActivationDuration { 26 | pre_activation_duration: pre_activation_duration as u64, 27 | } 28 | .data(); 29 | 30 | let set_pre_activation_slot_duration_ix = Instruction { 31 | accounts, 32 | data, 33 | program_id: dlmm::ID, 34 | }; 35 | 36 | let request_builder = program.request(); 37 | 38 | let signature = request_builder 39 | .instruction(set_pre_activation_slot_duration_ix) 40 | .send_with_spinner_and_config(transaction_config) 41 | .await; 42 | 43 | println!("Set pre activation duration. Signature: {:#?}", signature); 44 | 45 | signature?; 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /cli/src/instructions/set_pair_status_permissionless.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct SetPairStatusPermissionlessParams { 5 | #[clap(long)] 6 | pub lb_pair: Pubkey, 7 | #[clap(long)] 8 | pub enable: bool, 9 | } 10 | 11 | pub async fn execute_set_pair_status_permissionless + Clone>( 12 | params: SetPairStatusPermissionlessParams, 13 | program: &Program, 14 | transaction_config: RpcSendTransactionConfig, 15 | ) -> Result<()> { 16 | let SetPairStatusPermissionlessParams { lb_pair, enable } = params; 17 | 18 | let accounts = dlmm::client::accounts::SetPairStatusPermissionless { 19 | creator: program.payer(), 20 | lb_pair, 21 | } 22 | .to_account_metas(None); 23 | 24 | let status = if enable { 1 } else { 0 }; 25 | 26 | let data = dlmm::client::args::SetPairStatusPermissionless { status }.data(); 27 | 28 | let set_pair_status_permissionless_ix = Instruction { 29 | accounts, 30 | data, 31 | program_id: dlmm::ID, 32 | }; 33 | 34 | let request_builder = program.request(); 35 | let signature = request_builder 36 | .instruction(set_pair_status_permissionless_ix) 37 | .send_with_spinner_and_config(transaction_config) 38 | .await; 39 | 40 | println!( 41 | "Set pair status permissionless. Signature: {:#?}", 42 | signature 43 | ); 44 | 45 | signature?; 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /artifacts/FJbEo74c2W4QLBBVUfUvi8VBWXtMdJVPuFpq2f6UV1iB/FJbEo74c2W4QLBBVUfUvi8VBWXtMdJVPuFpq2f6UV1iB.json: -------------------------------------------------------------------------------- 1 | {"account":{"data":["IQsxYrVlsQ0QJx4AWAKIE0CcAAAwVwUAalX//5aqAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/woAAwAAAAAKAAAAECcAANYjK1IINMala6PQnLIrNY+ueDyVA1jhf0pZXPi99RQJBpuIV/6rgYT7aH9jRhjANdrEOdwa6ztVmKDwAAAAAAFnFuSSu++GgmnDsghSLvWYwChGzA+Xtw+GiGhD24q7TDHD5EuDMLrU9QzNBex+RQhysQLe3Wwgk3qe6dYJTIy2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhzz3TkqQbRDAtSIURflxYm+t9S0J8HtJm6TM3mhnJwUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgBQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJcmd/lEKbh/6BL/3yM1WQGzw0ub30RbKoBToEq06bMHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADXvvLVcHYs26rHu6FZqhyvtry55mtf8qnM+QE50MBOzwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==","base64"],"executable":false,"lamports":7182720,"owner":"LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo","rentEpoch":18446744073709551615,"space":904},"pubkey":"FJbEo74c2W4QLBBVUfUvi8VBWXtMdJVPuFpq2f6UV1iB"} -------------------------------------------------------------------------------- /cli/src/instructions/get_all_positions.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use solana_client::rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct GetAllPositionsParams { 6 | /// Address of the pair 7 | #[clap(long)] 8 | lb_pair: Pubkey, 9 | /// Owner of position 10 | #[clap(long)] 11 | owner: Pubkey, 12 | } 13 | 14 | pub async fn execute_get_all_positions + Clone>( 15 | program: &Program, 16 | params: GetAllPositionsParams, 17 | ) -> Result<()> { 18 | let GetAllPositionsParams { lb_pair, owner } = params; 19 | 20 | let rpc_client = program.rpc(); 21 | 22 | let account_config = RpcAccountInfoConfig { 23 | encoding: Some(UiAccountEncoding::Base64), 24 | ..Default::default() 25 | }; 26 | let config = RpcProgramAccountsConfig { 27 | filters: Some(position_filter_by_wallet_and_pair(owner, lb_pair)), 28 | account_config, 29 | ..Default::default() 30 | }; 31 | 32 | let accounts = rpc_client 33 | .get_program_accounts_with_config(&dlmm::ID, config) 34 | .await?; 35 | 36 | for (position_key, position_raw_account) in accounts { 37 | let position_state: PositionV2 = 38 | bytemuck::pod_read_unaligned(&position_raw_account.data[8..]); 39 | println!( 40 | "Position {} fee owner {}", 41 | position_key, position_state.fee_owner 42 | ); 43 | } 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/update_reward_funder.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct UpdateRewardFunderParams { 5 | pub lb_pair: Pubkey, 6 | pub reward_index: u64, 7 | pub funder: Pubkey, 8 | } 9 | 10 | pub async fn execute_update_reward_funder + Clone>( 11 | params: UpdateRewardFunderParams, 12 | program: &Program, 13 | transaction_config: RpcSendTransactionConfig, 14 | ) -> Result<()> { 15 | let UpdateRewardFunderParams { 16 | lb_pair, 17 | reward_index, 18 | funder, 19 | } = params; 20 | 21 | let (event_authority, _bump) = derive_event_authority_pda(); 22 | 23 | let accounts = dlmm::client::accounts::UpdateRewardFunder { 24 | lb_pair, 25 | admin: program.payer(), 26 | event_authority, 27 | program: dlmm::ID, 28 | } 29 | .to_account_metas(None); 30 | 31 | let data = dlmm::client::args::UpdateRewardFunder { 32 | reward_index, 33 | new_funder: funder, 34 | } 35 | .data(); 36 | 37 | let ix = Instruction { 38 | program_id: dlmm::ID, 39 | accounts, 40 | data, 41 | }; 42 | 43 | let request_builder = program.request(); 44 | let signature = request_builder 45 | .instruction(ix) 46 | .send_with_spinner_and_config(transaction_config) 47 | .await; 48 | 49 | println!("Fund reward. Signature: {:#?}", signature); 50 | 51 | signature?; 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/set_pre_activation_swap_address.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct SetPreactivationSwapAddressParam { 5 | pub lb_pair: Pubkey, 6 | pub pre_activation_swap_address: Pubkey, 7 | } 8 | 9 | pub async fn execute_set_pre_activation_swap_address + Clone>( 10 | params: SetPreactivationSwapAddressParam, 11 | program: &Program, 12 | transaction_config: RpcSendTransactionConfig, 13 | ) -> Result<()> { 14 | let SetPreactivationSwapAddressParam { 15 | lb_pair, 16 | pre_activation_swap_address, 17 | } = params; 18 | 19 | let accounts = dlmm::client::accounts::SetPreActivationSwapAddress { 20 | creator: program.payer(), 21 | lb_pair, 22 | } 23 | .to_account_metas(None); 24 | 25 | let data = dlmm::client::args::SetPreActivationSwapAddress { 26 | pre_activation_swap_address, 27 | } 28 | .data(); 29 | 30 | let set_pre_activation_swap_address_ix = Instruction { 31 | accounts, 32 | data, 33 | program_id: dlmm::ID, 34 | }; 35 | 36 | let request_builder = program.request(); 37 | 38 | let signature = request_builder 39 | .instruction(set_pre_activation_swap_address_ix) 40 | .send_with_spinner_and_config(transaction_config) 41 | .await; 42 | 43 | println!( 44 | "Set pre activation swap address. Signature: {:#?}", 45 | signature 46 | ); 47 | 48 | signature?; 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /cli/src/instructions/initialize_bin_array_with_bin_range.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use instructions::*; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct InitBinArrayWithBinRangeParams { 6 | /// Address of the liquidity pair. 7 | pub lb_pair: Pubkey, 8 | /// Lower bound of the bin range. 9 | #[clap(long, allow_negative_numbers = true)] 10 | pub lower_bin_id: i32, 11 | /// Upper bound of the bin range. 12 | #[clap(long, allow_negative_numbers = true)] 13 | pub upper_bin_id: i32, 14 | } 15 | 16 | pub async fn execute_initialize_bin_array_with_bin_range + Clone>( 17 | params: InitBinArrayWithBinRangeParams, 18 | program: &Program, 19 | transaction_config: RpcSendTransactionConfig, 20 | ) -> Result> { 21 | let InitBinArrayWithBinRangeParams { 22 | lb_pair, 23 | lower_bin_id, 24 | upper_bin_id, 25 | } = params; 26 | 27 | let mut bin_arrays_pubkey = vec![]; 28 | 29 | let lower_bin_array_idx = BinArray::bin_id_to_bin_array_index(lower_bin_id)?; 30 | let upper_bin_array_idx = BinArray::bin_id_to_bin_array_index(upper_bin_id)?; 31 | 32 | for idx in lower_bin_array_idx..=upper_bin_array_idx { 33 | let params = InitBinArrayParams { 34 | bin_array_index: idx.into(), 35 | lb_pair, 36 | }; 37 | let bin_array_pubkey = 38 | execute_initialize_bin_array(params, program, transaction_config).await?; 39 | bin_arrays_pubkey.push(bin_array_pubkey); 40 | } 41 | 42 | Ok(bin_arrays_pubkey) 43 | } 44 | -------------------------------------------------------------------------------- /cli/src/instructions/initialize_position_with_price_range.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use instructions::*; 3 | use rust_decimal::Decimal; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct InitPositionWithPriceRangeParams { 7 | /// Address of the liquidity pair. 8 | pub lb_pair: Pubkey, 9 | /// Lower bound of the price. 10 | pub lower_price: f64, 11 | /// Width of the position. Start with 1 until 70. 12 | pub width: i32, 13 | } 14 | 15 | pub async fn execute_initialize_position_with_price_range< 16 | C: Deref + Clone, 17 | >( 18 | params: InitPositionWithPriceRangeParams, 19 | program: &Program, 20 | transaction_config: RpcSendTransactionConfig, 21 | ) -> Result { 22 | let InitPositionWithPriceRangeParams { 23 | lb_pair, 24 | lower_price, 25 | width, 26 | } = params; 27 | 28 | let rpc_client = program.rpc(); 29 | let lb_pair_state: LbPair = rpc_client 30 | .get_account_and_deserialize(&lb_pair, |account| { 31 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 32 | }) 33 | .await?; 34 | 35 | let lower_bin_id = get_id_from_price( 36 | lb_pair_state.bin_step, 37 | &Decimal::from_f64_retain(lower_price).context("lower price overflow")?, 38 | Rounding::Down, 39 | ) 40 | .context("get_id_from_price overflow")?; 41 | 42 | let params = InitPositionParams { 43 | lb_pair, 44 | lower_bin_id, 45 | width, 46 | }; 47 | 48 | execute_initialize_position(params, program, transaction_config).await 49 | } 50 | -------------------------------------------------------------------------------- /cli/src/instructions/increase_oracle_length.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_client::solana_sdk; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct IncreaseOracleLengthParams { 6 | pub lb_pair: Pubkey, 7 | pub length_to_add: u64, 8 | } 9 | 10 | pub async fn execute_increase_oracle_length + Clone>( 11 | params: IncreaseOracleLengthParams, 12 | program: &Program, 13 | transaction_config: RpcSendTransactionConfig, 14 | ) -> Result<()> { 15 | let IncreaseOracleLengthParams { 16 | lb_pair, 17 | length_to_add, 18 | } = params; 19 | 20 | let (oracle, _) = derive_oracle_pda(lb_pair); 21 | let (event_authority, _bump) = derive_event_authority_pda(); 22 | 23 | let accounts = dlmm::client::accounts::IncreaseOracleLength { 24 | funder: program.payer(), 25 | oracle, 26 | system_program: solana_sdk::system_program::ID, 27 | event_authority, 28 | program: dlmm::ID, 29 | } 30 | .to_account_metas(None); 31 | 32 | let data = dlmm::client::args::IncreaseOracleLength { length_to_add }.data(); 33 | 34 | let increase_length_ix = Instruction { 35 | program_id: dlmm::ID, 36 | accounts, 37 | data, 38 | }; 39 | 40 | let request_builder = program.request(); 41 | let signature = request_builder 42 | .instruction(increase_length_ix) 43 | .send_with_spinner_and_config(transaction_config) 44 | .await; 45 | 46 | println!("Increase oracle {oracle} length. Signature: {signature:#?}"); 47 | 48 | signature?; 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /cli/src/instructions/set_pair_status.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use anchor_client::solana_client::rpc_config::RpcSendTransactionConfig; 4 | use anchor_client::{solana_sdk::pubkey::Pubkey, solana_sdk::signer::Signer, Program}; 5 | 6 | use anchor_lang::solana_program::instruction::Instruction; 7 | use anchor_lang::{InstructionData, ToAccountMetas}; 8 | use anyhow::*; 9 | 10 | #[derive(Debug)] 11 | pub struct SetPairStatusParam { 12 | pub lb_pair: Pubkey, 13 | pub pair_status: u8, 14 | } 15 | 16 | pub async fn set_pair_status + Clone>( 17 | params: SetPairStatusParam, 18 | program: &Program, 19 | transaction_config: RpcSendTransactionConfig, 20 | ) -> Result<()> { 21 | let SetPairStatusParam { 22 | lb_pair, 23 | pair_status, 24 | } = params; 25 | 26 | let accounts = lb_clmm::accounts::SetPairStatus { 27 | admin: program.payer(), 28 | lb_pair, 29 | } 30 | .to_account_metas(None); 31 | 32 | let ix_data = lb_clmm::instruction::SetPairStatus { 33 | status: pair_status, 34 | } 35 | .data(); 36 | 37 | let set_pair_status_ix = Instruction { 38 | accounts, 39 | data: ix_data, 40 | program_id: lb_clmm::ID, 41 | }; 42 | 43 | let request_builder = program.request(); 44 | let signature = request_builder 45 | .instruction(set_pair_status_ix) 46 | .send_with_spinner_and_config(transaction_config) 47 | .await; 48 | 49 | println!("Set pair status successfully. Signature: {:#?}", signature); 50 | 51 | signature?; 52 | 53 | Ok(()) 54 | } 55 | -------------------------------------------------------------------------------- /cli/src/instructions/initialize_bin_array.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct InitBinArrayParams { 5 | /// Index of the bin array. 6 | #[clap(long, allow_negative_numbers = true)] 7 | pub bin_array_index: i64, 8 | /// Address of the liquidity pair. 9 | pub lb_pair: Pubkey, 10 | } 11 | 12 | pub async fn execute_initialize_bin_array + Clone>( 13 | params: InitBinArrayParams, 14 | program: &Program, 15 | transaction_config: RpcSendTransactionConfig, 16 | ) -> Result { 17 | let InitBinArrayParams { 18 | lb_pair, 19 | bin_array_index, 20 | } = params; 21 | 22 | let (bin_array, _bump) = derive_bin_array_pda(lb_pair, bin_array_index); 23 | 24 | let accounts = dlmm::client::accounts::InitializeBinArray { 25 | bin_array, 26 | funder: program.payer(), 27 | lb_pair, 28 | system_program: solana_sdk::system_program::ID, 29 | } 30 | .to_account_metas(None); 31 | 32 | let data = dlmm::client::args::InitializeBinArray { 33 | index: bin_array_index, 34 | } 35 | .data(); 36 | 37 | let init_bin_array_ix = Instruction { 38 | program_id: dlmm::ID, 39 | accounts, 40 | data, 41 | }; 42 | 43 | let request_builder = program.request(); 44 | let signature = request_builder 45 | .instruction(init_bin_array_ix) 46 | .send_with_spinner_and_config(transaction_config) 47 | .await; 48 | 49 | println!("Initialize Bin Array {bin_array}. Signature: {signature:#?}"); 50 | 51 | signature?; 52 | 53 | Ok(bin_array) 54 | } 55 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/lbPair.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, Program } from "@coral-xyz/anchor"; 2 | import { Cluster, Connection, PublicKey } from "@solana/web3.js"; 3 | import { LBCLMM_PROGRAM_IDS } from "../constants"; 4 | import { LbPair } from "../types"; 5 | import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token"; 6 | import { createProgram } from "."; 7 | 8 | /** 9 | * It fetches the pool account from the AMM program, and returns the mint addresses for the two tokens 10 | * @param {Connection} connection - Connection - The connection to the Solana cluster 11 | * @param {string} poolAddress - The address of the pool account. 12 | * @returns The tokenAMint and tokenBMint addresses for the pool. 13 | */ 14 | export async function getTokensMintFromPoolAddress( 15 | connection: Connection, 16 | poolAddress: string, 17 | opt?: { 18 | cluster?: Cluster; 19 | programId?: PublicKey; 20 | } 21 | ) { 22 | const program = createProgram(connection, opt); 23 | 24 | const poolAccount = await program.account.lbPair.fetchNullable( 25 | new PublicKey(poolAddress) 26 | ); 27 | 28 | if (!poolAccount) throw new Error("Pool account not found"); 29 | 30 | return { 31 | tokenXMint: poolAccount.tokenXMint, 32 | tokenYMint: poolAccount.tokenYMint, 33 | }; 34 | } 35 | 36 | export function getTokenProgramId(lbPairState: LbPair) { 37 | const getTokenProgramIdByFlag = (flag: number) => { 38 | return flag == 0 ? TOKEN_PROGRAM_ID : TOKEN_2022_PROGRAM_ID; 39 | }; 40 | return { 41 | tokenXProgram: getTokenProgramIdByFlag(lbPairState.tokenMintXProgramFlag), 42 | tokenYProgram: getTokenProgramIdByFlag(lbPairState.tokenMintYProgramFlag), 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /market_making/src/router.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::Core; 4 | use hyper::{Body, Request, Response, StatusCode}; 5 | use log::debug; 6 | use routerify::prelude::*; 7 | use routerify::{Middleware, RequestInfo, Router}; 8 | use std::convert::Infallible; 9 | 10 | pub fn router(core: Arc) -> Router { 11 | Router::builder() 12 | .data(core) 13 | .middleware(Middleware::pre(logger)) 14 | .get("/check_positions", check_positions) 15 | .err_handler_with_info(error_handler) 16 | .build() 17 | .unwrap() 18 | } 19 | 20 | async fn check_positions(req: Request) -> Result, Infallible> { 21 | // Access the app state. 22 | let core = req.data::>().unwrap(); 23 | match core.get_positions() { 24 | Ok(positions) => match serde_json::to_string(&positions) { 25 | Ok(res) => Ok(Response::new(Body::from(res))), 26 | Err(_) => Ok(Response::new(Body::from("Cannot encode positions"))), 27 | }, 28 | Err(err) => { 29 | println!("{err}"); 30 | Ok(Response::new(Body::from("Cannot get positions"))) 31 | } 32 | } 33 | } 34 | 35 | async fn error_handler(err: routerify::RouteError, _: RequestInfo) -> Response { 36 | debug!("{}", err); 37 | Response::builder() 38 | .status(StatusCode::INTERNAL_SERVER_ERROR) 39 | .body(Body::from(format!("Something went wrong: {}", err))) 40 | .unwrap() 41 | } 42 | 43 | async fn logger(req: Request) -> Result, Infallible> { 44 | debug!( 45 | "{} {} {}", 46 | req.remote_addr(), 47 | req.method(), 48 | req.uri().path() 49 | ); 50 | Ok(req) 51 | } 52 | -------------------------------------------------------------------------------- /ts-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meteora-ag/dlmm", 3 | "version": "1.9.0", 4 | "description": "", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "source": "./src/index.ts", 8 | "types": "./dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "types": "./dist/index.d.ts", 12 | "import": "./dist/index.mjs", 13 | "require": "./dist/index.js", 14 | "default": "./dist/index.mjs" 15 | } 16 | }, 17 | "files": [ 18 | "dist/**" 19 | ], 20 | "scripts": { 21 | "build": "tsup", 22 | "start": "npm run build -- --watch", 23 | "test": "jest 'src/test/sdk_token2022.test.ts'", 24 | "unit-test": "jest src/test/calculate_distribution.test.ts", 25 | "example": "dotenv -e .env npx ts-node src/examples/example.ts", 26 | "start-server": "npx tsc && node dist/src/server/index.js" 27 | }, 28 | "devDependencies": { 29 | "@babel/preset-env": "^7.22.5", 30 | "@types/babar": "^0.2.1", 31 | "@types/bn.js": "^5.1.5", 32 | "@types/express": "^4.17.21", 33 | "@types/gaussian": "^1.2.0", 34 | "@types/jest": "^29.5.2", 35 | "babar": "^0.2.3", 36 | "babel-jest": "^29.5.0", 37 | "dotenv-cli": "^7.2.1", 38 | "jest": "^29.5.0", 39 | "ts-jest": "^29.1.1", 40 | "tsup": "^6.7.0", 41 | "typescript": "^5.0.4" 42 | }, 43 | "dependencies": { 44 | "@coral-xyz/anchor": "0.31.0", 45 | "@coral-xyz/borsh": "0.31.0", 46 | "@solana/buffer-layout": "^4.0.1", 47 | "@solana/spl-token": "^0.4.6", 48 | "@solana/web3.js": "^1.91.6", 49 | "bn.js": "^5.2.1", 50 | "decimal.js": "^10.4.2", 51 | "express": "^4.19.2", 52 | "gaussian": "^1.3.0" 53 | }, 54 | "keywords": [], 55 | "author": "McSam", 56 | "license": "ISC" 57 | } -------------------------------------------------------------------------------- /.github/workflows/ci-pr-main-sdk.yml: -------------------------------------------------------------------------------- 1 | name: DLMM SDK 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | env: 9 | SOLANA_CLI_VERSION: 2.1.0 10 | NODE_VERSION: 20.11.0 11 | ANCHOR_CLI_VERSION: 0.31.0 12 | 13 | jobs: 14 | sdk_changed_files: 15 | runs-on: ubuntu-latest 16 | outputs: 17 | sdk: ${{steps.changed-files-specific.outputs.any_changed}} 18 | steps: 19 | - uses: actions/checkout@v2 20 | with: 21 | fetch-depth: 0 22 | - name: Get specific changed files 23 | id: changed-files-specific 24 | uses: tj-actions/changed-files@v18.6 25 | with: 26 | files: | 27 | ts-client 28 | artifacts 29 | 30 | sdk_test: 31 | runs-on: ubuntu-latest 32 | needs: sdk_changed_files 33 | if: needs.sdk_changed_files.outputs.sdk == 'true' 34 | env: 35 | RPC: ${{ secrets.RPC }} 36 | steps: 37 | - uses: actions/checkout@v2 38 | - uses: ./.github/actions/setup-solana 39 | - uses: ./.github/actions/setup-dep 40 | - uses: ./.github/actions/setup-anchor 41 | # Install rust + toolchain 42 | - uses: actions-rs/toolchain@v1 43 | with: 44 | toolchain: stable 45 | components: clippy 46 | - uses: pnpm/action-setup@v4 47 | with: 48 | version: 10 49 | # Cache node_modules 50 | - uses: actions/cache@v4 51 | id: cache-node-modules 52 | with: 53 | path: ./ts-client/node_modules 54 | key: ${{ runner.os }}-${{ hashFiles('./pnpm-lock.yaml') }} 55 | - run: anchor localnet -- --features localnet & sleep 2 56 | shell: bash 57 | - run: cd ts-client && pnpm install && pnpm run test 58 | shell: bash 59 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/error.ts: -------------------------------------------------------------------------------- 1 | import IDL from "./dlmm.json"; 2 | import { AnchorError } from "@coral-xyz/anchor"; 3 | import { LBCLMM_PROGRAM_IDS } from "./constants"; 4 | 5 | type Codes = (typeof IDL.errors)[number]["code"]; 6 | 7 | // ProgramError error parser 8 | export class DLMMError extends Error { 9 | public errorCode: number; 10 | public errorName: string; 11 | public errorMessage: string; 12 | 13 | constructor(error: object | Codes) { 14 | let _errorCode = 0; 15 | let _errorName = "Something went wrong"; 16 | let _errorMessage = "Something went wrong"; 17 | 18 | if (error instanceof Error) { 19 | const anchorError = AnchorError.parse( 20 | JSON.parse(JSON.stringify(error)).logs as string[] 21 | ); 22 | 23 | if ( 24 | anchorError?.program.toBase58() === LBCLMM_PROGRAM_IDS["mainnet-beta"] 25 | ) { 26 | _errorCode = anchorError.error.errorCode.number; 27 | _errorName = anchorError.error.errorCode.code; 28 | _errorMessage = anchorError.error.errorMessage; 29 | } 30 | } else { 31 | const idlError = IDL.errors.find((err) => err.code === error); 32 | 33 | if (idlError) { 34 | _errorCode = idlError.code; 35 | _errorName = idlError.name; 36 | _errorMessage = idlError.msg; 37 | } 38 | } 39 | 40 | super(_errorMessage); 41 | 42 | this.errorCode = _errorCode; 43 | this.errorName = _errorName; 44 | this.errorMessage = _errorMessage; 45 | } 46 | } 47 | 48 | // SDK error 49 | type ErrorName = 50 | | "SWAP_QUOTE_INSUFFICIENT_LIQUIDITY" 51 | | "INVALID_MAX_EXTRA_BIN_ARRAYS"; 52 | 53 | export class DlmmSdkError extends Error { 54 | name: ErrorName; 55 | message: string; 56 | 57 | constructor(name: ErrorName, message: string) { 58 | super(); 59 | this.name = name; 60 | this.message = message; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cli/src/instructions/initialize_bin_array_with_price_range.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use instructions::*; 3 | use rust_decimal::Decimal; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct InitBinArrayWithPriceRangeParams { 7 | /// Address of the liquidity pair. 8 | pub lb_pair: Pubkey, 9 | /// Lower bound of the price. 10 | pub lower_price: f64, 11 | /// Upper bound of the price. 12 | pub upper_price: f64, 13 | } 14 | 15 | pub async fn execute_initialize_bin_array_with_price_range< 16 | C: Deref + Clone, 17 | >( 18 | params: InitBinArrayWithPriceRangeParams, 19 | program: &Program, 20 | transaction_config: RpcSendTransactionConfig, 21 | ) -> Result> { 22 | let InitBinArrayWithPriceRangeParams { 23 | lb_pair, 24 | lower_price, 25 | upper_price, 26 | } = params; 27 | 28 | let rpc_client = program.rpc(); 29 | let lb_pair_state: LbPair = rpc_client 30 | .get_account_and_deserialize(&lb_pair, |account| { 31 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 32 | }) 33 | .await?; 34 | 35 | let lower_bin_id = get_id_from_price( 36 | lb_pair_state.bin_step, 37 | &Decimal::from_f64_retain(lower_price).context("lower price overflow")?, 38 | Rounding::Down, 39 | ) 40 | .context("get_id_from_price overflow")?; 41 | 42 | let upper_bin_id = get_id_from_price( 43 | lb_pair_state.bin_step, 44 | &Decimal::from_f64_retain(upper_price).context("upper price overflow")?, 45 | Rounding::Up, 46 | ) 47 | .context("get_id_from_price overflow")?; 48 | 49 | let params = InitBinArrayWithBinRangeParams { 50 | lb_pair, 51 | lower_bin_id, 52 | upper_bin_id, 53 | }; 54 | 55 | execute_initialize_bin_array_with_bin_range(params, program, transaction_config).await 56 | } 57 | -------------------------------------------------------------------------------- /cli/src/instructions/close_position.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct ClosePositionParams { 5 | pub position: Pubkey, 6 | } 7 | 8 | pub async fn execute_close_position + Clone>( 9 | params: ClosePositionParams, 10 | program: &Program, 11 | transaction_config: RpcSendTransactionConfig, 12 | ) -> Result<()> { 13 | let ClosePositionParams { position } = params; 14 | 15 | let rpc_client = program.rpc(); 16 | let position_state: PositionV2 = rpc_client 17 | .get_account_and_deserialize(&position, |account| { 18 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 19 | }) 20 | .await?; 21 | 22 | let bin_arrays_account_meta = position_state.get_bin_array_accounts_meta_coverage()?; 23 | 24 | let (event_authority, _bump) = derive_event_authority_pda(); 25 | 26 | let main_accounts = dlmm::client::accounts::ClosePosition2 { 27 | sender: position_state.owner, 28 | rent_receiver: position_state.owner, 29 | position, 30 | event_authority, 31 | program: dlmm::ID, 32 | } 33 | .to_account_metas(None); 34 | 35 | let data = dlmm::client::args::ClosePosition2 {}.data(); 36 | let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(1_400_000); 37 | 38 | let accounts = [main_accounts.to_vec(), bin_arrays_account_meta].concat(); 39 | 40 | let close_position_ix = Instruction { 41 | program_id: dlmm::ID, 42 | accounts, 43 | data, 44 | }; 45 | 46 | let request_builder = program.request(); 47 | let signature = request_builder 48 | .instruction(compute_budget_ix) 49 | .instruction(close_position_ix) 50 | .send_with_spinner_and_config(transaction_config) 51 | .await; 52 | 53 | println!("Close position. Signature: {:#?}", signature); 54 | 55 | signature?; 56 | 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /market_making/src/pair_config.rs: -------------------------------------------------------------------------------- 1 | use crate::MarketMakingMode; 2 | use anchor_lang::prelude::Pubkey; 3 | use anyhow::*; 4 | use serde::Deserialize; 5 | use std::fs::File; 6 | use std::io::Read; 7 | 8 | #[derive(Debug, Deserialize, Clone, Default)] 9 | #[serde(rename_all = "snake_case")] 10 | pub struct PairConfig { 11 | pub pair_address: String, 12 | pub x_amount: u64, 13 | pub y_amount: u64, 14 | pub mode: MarketMakingMode, 15 | } 16 | 17 | pub fn should_market_making(config: &Vec) -> bool { 18 | for pair in config.iter() { 19 | if pair.mode != MarketMakingMode::ModeView { 20 | return true; 21 | } 22 | } 23 | return false; 24 | } 25 | 26 | pub fn get_pair_config(config: &Vec, pair_addr: Pubkey) -> PairConfig { 27 | for pair_config in config.iter() { 28 | if pair_config.pair_address == pair_addr.to_string() { 29 | return pair_config.clone(); 30 | } 31 | } 32 | return PairConfig::default(); 33 | } 34 | 35 | pub fn get_config_from_file(path: &str) -> Result> { 36 | // println!("config file {}", env::var("KEEPER_CONFIG_FILE").unwrap()); 37 | let mut file = File::open(path)?; 38 | let mut data = String::new(); 39 | file.read_to_string(&mut data)?; 40 | 41 | let config: Vec = serde_json::from_str(&data)?; 42 | Ok(config) 43 | } 44 | 45 | #[cfg(test)] 46 | mod config_test { 47 | use super::*; 48 | use std::env; 49 | #[test] 50 | fn test_get_get_config_from_file() { 51 | let mut owned_string: String = env::current_dir() 52 | .unwrap() 53 | .into_os_string() 54 | .into_string() 55 | .unwrap(); 56 | let borrowed_string: &str = "/src/pair_config.json"; 57 | owned_string.push_str(borrowed_string); 58 | 59 | let config = get_config_from_file(&owned_string).unwrap(); 60 | println!("{:?}", config); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/update_base_fee.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct UpdateBaseFeeParams { 5 | pub lb_pair: Pubkey, 6 | pub base_fee_bps: u16, 7 | } 8 | 9 | pub async fn execute_update_base_fee + Clone>( 10 | params: UpdateBaseFeeParams, 11 | program: &Program, 12 | transaction_config: RpcSendTransactionConfig, 13 | ) -> Result<()> { 14 | let UpdateBaseFeeParams { 15 | lb_pair, 16 | base_fee_bps, 17 | } = params; 18 | 19 | let rpc_client = program.rpc(); 20 | 21 | let pair_account = rpc_client.get_account(&lb_pair).await?; 22 | 23 | let lb_pair_state = LbPair::try_deserialize(&mut pair_account.data.as_ref())?; 24 | 25 | let (base_factor, base_fee_power_factor) = 26 | compute_base_factor_from_fee_bps(lb_pair_state.bin_step, base_fee_bps)?; 27 | 28 | let ix_data = dlmm::client::args::UpdateBaseFeeParameters { 29 | fee_parameter: BaseFeeParameter { 30 | protocol_share: lb_pair_state.parameters.protocol_share, 31 | base_factor, 32 | base_fee_power_factor, 33 | }, 34 | } 35 | .data(); 36 | 37 | let event_authority = derive_event_authority_pda().0; 38 | 39 | let accounts = dlmm::client::accounts::UpdateBaseFeeParameters { 40 | lb_pair, 41 | admin: program.payer(), 42 | event_authority, 43 | program: dlmm::ID, 44 | } 45 | .to_account_metas(None); 46 | 47 | let ix = Instruction { 48 | program_id: program.id(), 49 | data: ix_data, 50 | accounts: accounts.to_vec(), 51 | }; 52 | 53 | let request_builder = program.request(); 54 | let signature = request_builder 55 | .instruction(ix) 56 | .send_with_spinner_and_config(transaction_config) 57 | .await; 58 | 59 | println!("Update base fee. Signature: {:#?}", signature); 60 | 61 | signature?; 62 | 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/accountFilters.ts: -------------------------------------------------------------------------------- 1 | import { GetProgramAccountsFilter, PublicKey } from "@solana/web3.js"; 2 | import { bs58 } from "@coral-xyz/anchor/dist/cjs/utils/bytes"; 3 | import BN from "bn.js"; 4 | import { getAccountDiscriminator } from "."; 5 | 6 | export const presetParameter2BinStepFilter = ( 7 | binStep: BN 8 | ): GetProgramAccountsFilter => { 9 | return { 10 | memcmp: { 11 | bytes: bs58.encode(binStep.toArrayLike(Buffer, "le", 2)), 12 | offset: 8, 13 | }, 14 | }; 15 | }; 16 | 17 | export const presetParameter2BaseFactorFilter = ( 18 | baseFactor: BN 19 | ): GetProgramAccountsFilter => { 20 | return { 21 | memcmp: { 22 | bytes: bs58.encode(baseFactor.toArrayLike(Buffer, "le", 2)), 23 | offset: 8 + 2, 24 | }, 25 | }; 26 | }; 27 | 28 | export const presetParameter2BaseFeePowerFactor = ( 29 | baseFeePowerFactor: BN 30 | ): GetProgramAccountsFilter => { 31 | return { 32 | memcmp: { 33 | bytes: bs58.encode(baseFeePowerFactor.toArrayLike(Buffer, "le", 1)), 34 | offset: 8 + 22, 35 | }, 36 | }; 37 | }; 38 | 39 | export const binArrayLbPairFilter = ( 40 | lbPair: PublicKey 41 | ): GetProgramAccountsFilter => { 42 | return { 43 | memcmp: { 44 | bytes: lbPair.toBase58(), 45 | offset: 8 + 16, 46 | }, 47 | }; 48 | }; 49 | 50 | export const positionOwnerFilter = ( 51 | owner: PublicKey 52 | ): GetProgramAccountsFilter => { 53 | return { 54 | memcmp: { 55 | bytes: owner.toBase58(), 56 | offset: 8 + 32, 57 | }, 58 | }; 59 | }; 60 | 61 | export const positionLbPairFilter = ( 62 | lbPair: PublicKey 63 | ): GetProgramAccountsFilter => { 64 | return { 65 | memcmp: { 66 | bytes: bs58.encode(lbPair.toBuffer()), 67 | offset: 8, 68 | }, 69 | }; 70 | }; 71 | 72 | export const positionV2Filter = (): GetProgramAccountsFilter => { 73 | return { 74 | memcmp: { 75 | bytes: bs58.encode(Buffer.from(getAccountDiscriminator("positionV2"))), 76 | offset: 0, 77 | }, 78 | }; 79 | }; 80 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/update_reward_duration.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use commons::dlmm::accounts::{BinArray, LbPair}; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct UpdateRewardDurationParams { 6 | pub lb_pair: Pubkey, 7 | pub reward_index: u64, 8 | pub reward_duration: u64, 9 | } 10 | 11 | pub async fn execute_update_reward_duration + Clone>( 12 | params: UpdateRewardDurationParams, 13 | program: &Program, 14 | transaction_config: RpcSendTransactionConfig, 15 | ) -> Result<()> { 16 | let UpdateRewardDurationParams { 17 | lb_pair, 18 | reward_index, 19 | reward_duration, 20 | } = params; 21 | 22 | let rpc_client = program.rpc(); 23 | let lb_pair_state: LbPair = rpc_client 24 | .get_account_and_deserialize(&lb_pair, |account| { 25 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 26 | }) 27 | .await?; 28 | 29 | let active_bin_array_idx = BinArray::bin_id_to_bin_array_index(lb_pair_state.active_id)?; 30 | let (bin_array, _bump) = derive_bin_array_pda(lb_pair, active_bin_array_idx as i64); 31 | 32 | let (event_authority, _bump) = derive_event_authority_pda(); 33 | 34 | let accounts = dlmm::client::accounts::UpdateRewardDuration { 35 | lb_pair, 36 | admin: program.payer(), 37 | bin_array, 38 | event_authority, 39 | program: dlmm::ID, 40 | } 41 | .to_account_metas(None); 42 | 43 | let data = dlmm::client::args::UpdateRewardDuration { 44 | reward_index, 45 | new_duration: reward_duration, 46 | } 47 | .data(); 48 | 49 | let ix = Instruction { 50 | program_id: dlmm::ID, 51 | accounts, 52 | data, 53 | }; 54 | 55 | let request_builder = program.request(); 56 | let signature = request_builder 57 | .instruction(ix) 58 | .send_with_spinner_and_config(transaction_config) 59 | .await; 60 | 61 | println!("Fund reward. Signature: {:#?}", signature); 62 | 63 | signature?; 64 | 65 | Ok(()) 66 | } 67 | -------------------------------------------------------------------------------- /cli/src/instructions/initialize_position.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::*; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct InitPositionParams { 7 | /// Address of the liquidity pair. 8 | pub lb_pair: Pubkey, 9 | /// Lower bound of the bin range. 10 | #[clap(long, allow_negative_numbers = true)] 11 | pub lower_bin_id: i32, 12 | /// Width of the position. Start with 1 until 70. 13 | pub width: i32, 14 | } 15 | 16 | pub async fn execute_initialize_position + Clone>( 17 | params: InitPositionParams, 18 | program: &Program, 19 | transaction_config: RpcSendTransactionConfig, 20 | ) -> Result { 21 | let InitPositionParams { 22 | lb_pair, 23 | lower_bin_id, 24 | width, 25 | } = params; 26 | 27 | let position_keypair = Arc::new(Keypair::new()); 28 | 29 | let (event_authority, _bump) = derive_event_authority_pda(); 30 | 31 | let accounts = dlmm::client::accounts::InitializePosition { 32 | lb_pair, 33 | payer: program.payer(), 34 | position: position_keypair.pubkey(), 35 | owner: program.payer(), 36 | rent: solana_sdk::sysvar::rent::ID, 37 | system_program: solana_sdk::system_program::ID, 38 | event_authority, 39 | program: dlmm::ID, 40 | } 41 | .to_account_metas(None); 42 | 43 | let data = dlmm::client::args::InitializePosition { 44 | lower_bin_id, 45 | width, 46 | } 47 | .data(); 48 | 49 | let init_position_ix = Instruction { 50 | program_id: dlmm::ID, 51 | data, 52 | accounts, 53 | }; 54 | 55 | let request_builder = program.request(); 56 | let signature = request_builder 57 | .instruction(init_position_ix) 58 | .signer(position_keypair.clone()) 59 | .send_with_spinner_and_config(transaction_config) 60 | .await; 61 | 62 | println!( 63 | "Initialize position {}. Signature: {signature:#?}", 64 | position_keypair.pubkey() 65 | ); 66 | 67 | signature?; 68 | 69 | Ok(position_keypair.pubkey()) 70 | } 71 | -------------------------------------------------------------------------------- /cli/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod add_liquidity; 2 | pub use add_liquidity::*; 3 | 4 | pub mod claim_fee; 5 | pub use claim_fee::*; 6 | 7 | pub mod claim_reward; 8 | pub use claim_reward::*; 9 | 10 | pub mod close_position; 11 | pub use close_position::*; 12 | 13 | pub mod fund_reward; 14 | pub use fund_reward::*; 15 | 16 | pub mod get_all_positions; 17 | pub use get_all_positions::*; 18 | 19 | pub mod increase_oracle_length; 20 | pub use increase_oracle_length::*; 21 | 22 | pub mod initialize_bin_array; 23 | pub use initialize_bin_array::*; 24 | 25 | pub mod initialize_bin_array_with_bin_range; 26 | pub use initialize_bin_array_with_bin_range::*; 27 | 28 | pub mod initialize_bin_array_with_price_range; 29 | pub use initialize_bin_array_with_price_range::*; 30 | 31 | pub mod initialize_customizable_permissionless_lb_pair2; 32 | pub use initialize_customizable_permissionless_lb_pair2::*; 33 | 34 | pub mod initialize_lb_pair2; 35 | pub use initialize_lb_pair2::*; 36 | 37 | pub mod initialize_position; 38 | pub use initialize_position::*; 39 | 40 | pub mod initialize_position_with_price_range; 41 | pub use initialize_position_with_price_range::*; 42 | 43 | pub mod list_all_binstep; 44 | pub use list_all_binstep::*; 45 | 46 | pub mod remove_liquidity; 47 | pub use remove_liquidity::*; 48 | 49 | pub mod show_pair; 50 | pub use show_pair::*; 51 | 52 | pub mod swap_exact_in; 53 | pub use swap_exact_in::*; 54 | 55 | pub mod swap_exact_out; 56 | pub use swap_exact_out::*; 57 | 58 | pub mod swap_with_price_impact; 59 | pub use swap_with_price_impact::*; 60 | 61 | mod utils; 62 | pub use utils::*; 63 | 64 | pub mod show_position; 65 | pub use show_position::*; 66 | 67 | pub mod show_preset_parameters; 68 | pub use show_preset_parameters::*; 69 | 70 | pub mod set_pair_status_permissionless; 71 | 72 | pub mod admin; 73 | pub use admin::*; 74 | 75 | pub mod ilm; 76 | pub use ilm::*; 77 | 78 | pub mod initialize_customizable_permissionless_lb_pair; 79 | pub use initialize_customizable_permissionless_lb_pair::*; 80 | 81 | pub mod initialize_lb_pair; 82 | pub use initialize_lb_pair::*; 83 | 84 | pub mod sync_price; 85 | pub use sync_price::*; 86 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/initialize_reward.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct InitializeRewardParams { 5 | pub lb_pair: Pubkey, 6 | pub reward_mint: Pubkey, 7 | pub reward_index: u64, 8 | pub reward_duration: u64, 9 | pub funder: Pubkey, 10 | } 11 | 12 | pub async fn execute_initialize_reward + Clone>( 13 | params: InitializeRewardParams, 14 | program: &Program, 15 | transaction_config: RpcSendTransactionConfig, 16 | ) -> Result<()> { 17 | let InitializeRewardParams { 18 | lb_pair, 19 | reward_mint, 20 | reward_index, 21 | reward_duration, 22 | funder, 23 | } = params; 24 | 25 | let (reward_vault, _bump) = derive_reward_vault_pda(lb_pair, reward_index); 26 | let (event_authority, _bump) = derive_event_authority_pda(); 27 | 28 | let rpc_client = program.rpc(); 29 | let reward_mint_account = rpc_client.get_account(&reward_mint).await?; 30 | 31 | let (token_badge, _bump) = derive_token_badge_pda(reward_mint); 32 | let token_badge = rpc_client 33 | .get_account(&token_badge) 34 | .await 35 | .ok() 36 | .map(|_| token_badge) 37 | .or(Some(dlmm::ID)); 38 | 39 | let accounts = dlmm::client::accounts::InitializeReward { 40 | lb_pair, 41 | reward_vault, 42 | reward_mint, 43 | admin: program.payer(), 44 | token_program: reward_mint_account.owner, 45 | token_badge, 46 | rent: solana_sdk::sysvar::rent::ID, 47 | system_program: solana_sdk::system_program::ID, 48 | event_authority, 49 | program: dlmm::ID, 50 | } 51 | .to_account_metas(None); 52 | 53 | let data = dlmm::client::args::InitializeReward { 54 | reward_index, 55 | reward_duration, 56 | funder, 57 | } 58 | .data(); 59 | 60 | let instruction = Instruction { 61 | program_id: dlmm::ID, 62 | accounts, 63 | data, 64 | }; 65 | 66 | let request_builder = program.request(); 67 | let signature = request_builder 68 | .instruction(instruction) 69 | .send_with_spinner_and_config(transaction_config) 70 | .await; 71 | 72 | println!("Initialize reward. Signature: {signature:#?}"); 73 | 74 | signature?; 75 | 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/close_preset_parameter.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_lang::Discriminator; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct ClosePresetAccountParams { 6 | /// Preset parameter pubkey. Get from ListAllBinStep 7 | pub preset_parameter: Pubkey, 8 | } 9 | 10 | pub async fn execute_close_preset_parameter + Clone>( 11 | params: ClosePresetAccountParams, 12 | program: &Program, 13 | transaction_config: RpcSendTransactionConfig, 14 | ) -> Result { 15 | let ClosePresetAccountParams { preset_parameter } = params; 16 | 17 | let rpc_client = program.rpc(); 18 | let preset_parameter_account = rpc_client.get_account(&preset_parameter).await?; 19 | 20 | let disc = &preset_parameter_account.data[..8]; 21 | 22 | let instruction = if disc == dlmm::accounts::PresetParameter::DISCRIMINATOR { 23 | let accounts = dlmm::client::accounts::ClosePresetParameter { 24 | admin: program.payer(), 25 | rent_receiver: program.payer(), 26 | preset_parameter, 27 | } 28 | .to_account_metas(None); 29 | 30 | let data = dlmm::client::args::ClosePresetParameter {}.data(); 31 | 32 | Instruction { 33 | program_id: dlmm::ID, 34 | accounts, 35 | data, 36 | } 37 | } else if disc == dlmm::accounts::PresetParameter2::DISCRIMINATOR { 38 | let accounts = dlmm::client::accounts::ClosePresetParameter2 { 39 | admin: program.payer(), 40 | rent_receiver: program.payer(), 41 | preset_parameter, 42 | } 43 | .to_account_metas(None); 44 | 45 | let data = dlmm::client::args::ClosePresetParameter2 {}.data(); 46 | 47 | Instruction { 48 | program_id: dlmm::ID, 49 | accounts, 50 | data, 51 | } 52 | } else { 53 | bail!("Not a valid preset parameter account"); 54 | }; 55 | 56 | let request_builder = program.request(); 57 | let signature = request_builder 58 | .instruction(instruction) 59 | .send_with_spinner_and_config(transaction_config) 60 | .await; 61 | 62 | println!( 63 | "Close preset parameter {}. Signature: {signature:#?}", 64 | preset_parameter 65 | ); 66 | 67 | signature?; 68 | 69 | Ok(preset_parameter) 70 | } 71 | -------------------------------------------------------------------------------- /commons/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const BASIS_POINT_MAX: i32 = 10000; 2 | 3 | /// Maximum number of bin a bin array able to contains. 4 | pub const MAX_BIN_PER_ARRAY: usize = 70; 5 | 6 | /// Default number of bin per position contains. 7 | pub const DEFAULT_BIN_PER_POSITION: usize = 70; 8 | 9 | /// Max resize length allowed 10 | pub const MAX_RESIZE_LENGTH: usize = 70; 11 | 12 | /// Maximum number of bin per position contains. 13 | pub const POSITION_MAX_LENGTH: usize = 1400; 14 | 15 | /// Minimum bin ID supported. Computed based on 1 bps. 16 | pub const MIN_BIN_ID: i32 = -443636; 17 | 18 | /// Maximum bin ID supported. Computed based on 1 bps. 19 | pub const MAX_BIN_ID: i32 = 443636; 20 | 21 | /// Maximum fee rate. 10% 22 | pub const MAX_FEE_RATE: u64 = 100_000_000; 23 | 24 | pub const FEE_PRECISION: u64 = 1_000_000_000; 25 | 26 | /// Maximum protocol share of the fee. 25% 27 | pub const MAX_PROTOCOL_SHARE: u16 = 2_500; 28 | 29 | /// Host fee. 20% 30 | pub const HOST_FEE_BPS: u16 = 2_000; 31 | 32 | // Number of rewards supported by pool 33 | pub const NUM_REWARDS: usize = 2; 34 | 35 | // Minimum reward duration 36 | pub const MIN_REWARD_DURATION: u64 = 1; 37 | 38 | pub const MAX_REWARD_DURATION: u64 = 31536000; // 1 year = 365 * 24 * 3600 39 | 40 | pub const DEFAULT_OBSERVATION_LENGTH: u64 = 100; 41 | 42 | pub const SAMPLE_LIFETIME: u64 = 120; // 2 43 | 44 | pub const EXTENSION_BINARRAY_BITMAP_SIZE: usize = 12; 45 | 46 | pub const BIN_ARRAY_BITMAP_SIZE: i32 = 512; 47 | 48 | pub const MAX_BASE_FACTOR_STEP: u16 = 100; // 100 bps, 1% 49 | 50 | pub const MAX_FEE_UPDATE_WINDOW: i64 = 0; 51 | 52 | pub const MAX_REWARD_BIN_SPLIT: usize = 15; 53 | 54 | pub const SLOT_BUFFER: u64 = 9000; 55 | 56 | pub const TIME_BUFFER: u64 = 3600; 57 | 58 | pub const MAX_ACTIVATION_SLOT_DURATION: u64 = SLOT_BUFFER * 24 * 31; // 31 days 59 | 60 | pub const MAX_ACTIVATION_TIME_DURATION: u64 = TIME_BUFFER * 24 * 31; // 31 days 61 | 62 | pub const FIVE_MINUTES_SLOT_BUFFER: u64 = SLOT_BUFFER / 12; // 5 minutes 63 | 64 | pub const FIVE_MINUTES_TIME_BUFFER: u64 = TIME_BUFFER / 12; // 5 minutes 65 | 66 | // ILM token launch protocol fee 67 | pub const ILM_PROTOCOL_SHARE: u16 = 2000; // 20% 68 | 69 | /// Maximum bin step 70 | pub const MAX_BIN_STEP: u16 = 400; 71 | 72 | /// Maximum base fee, base_fee / 10^9 = fee_in_percentage 73 | pub const MAX_BASE_FEE: u128 = 100_000_000; // 10% (10^9 * 10 / 100) 74 | 75 | /// Minimum base fee 76 | pub const MIN_BASE_FEE: u128 = 100_000; // 0.01% (10^9 * 0.01 / 100) 77 | -------------------------------------------------------------------------------- /ts-client/src/examples/initialize_bin_arrays.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js"; 2 | import { DLMM } from "../dlmm"; 3 | import BN from "bn.js"; 4 | import Decimal from "decimal.js"; 5 | import { getBinArraysRequiredByPositionRange } from "../dlmm/helpers"; 6 | import { simulateTransaction } from "@coral-xyz/anchor/dist/cjs/utils/rpc"; 7 | 8 | async function initializeBinArrayExample() { 9 | const funder = Keypair.fromSecretKey( 10 | new Uint8Array(JSON.parse(process.env.WALLET)) 11 | ); 12 | 13 | console.log("Connected wallet", funder.publicKey.toBase58()); 14 | 15 | const poolAddress = new PublicKey( 16 | "BfxJcifavkCgznhvAtLsBHQpyNwaTMs2cR986qbH4fPh" 17 | ); 18 | 19 | let rpc = "https://api.mainnet-beta.solana.com"; 20 | const connection = new Connection(rpc, "finalized"); 21 | const dlmmPool = await DLMM.create(connection, poolAddress, { 22 | cluster: "mainnet-beta", 23 | }); 24 | 25 | const fromUIPrice = 1.0; 26 | const toUIPrice = 4.0; 27 | 28 | const toLamportMultiplier = new Decimal( 29 | 10 ** (dlmmPool.tokenX.mint.decimals - dlmmPool.tokenX.mint.decimals) 30 | ); 31 | 32 | const minPricePerLamport = new Decimal(fromUIPrice).mul(toLamportMultiplier); 33 | const maxPricePerLamport = new Decimal(toUIPrice).mul(toLamportMultiplier); 34 | 35 | const minBinId = new BN( 36 | DLMM.getBinIdFromPrice(minPricePerLamport, dlmmPool.lbPair.binStep, false) 37 | ); 38 | 39 | const maxBinId = new BN( 40 | DLMM.getBinIdFromPrice(maxPricePerLamport, dlmmPool.lbPair.binStep, false) 41 | ); 42 | 43 | const binArraysRequired = getBinArraysRequiredByPositionRange( 44 | poolAddress, 45 | minBinId, 46 | maxBinId, 47 | dlmmPool.program.programId 48 | ); 49 | 50 | console.log(binArraysRequired); 51 | 52 | const initializeBinArrayIxs = await dlmmPool.initializeBinArrays( 53 | binArraysRequired.map((b) => b.index), 54 | funder.publicKey 55 | ); 56 | 57 | const { blockhash, lastValidBlockHeight } = 58 | await connection.getLatestBlockhash(); 59 | 60 | const transaction = new Transaction({ 61 | blockhash, 62 | lastValidBlockHeight, 63 | feePayer: funder.publicKey, 64 | }).add(...initializeBinArrayIxs); 65 | 66 | transaction.sign(funder); 67 | 68 | const simulationResult = await simulateTransaction(connection, transaction, [ 69 | funder, 70 | ]); 71 | 72 | console.log(simulationResult); 73 | } 74 | 75 | initializeBinArrayExample(); 76 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/computeUnit.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AddressLookupTableAccount, 3 | Commitment, 4 | ComputeBudgetProgram, 5 | Connection, 6 | PublicKey, 7 | TransactionInstruction, 8 | TransactionMessage, 9 | VersionedTransaction, 10 | } from "@solana/web3.js"; 11 | import { ResizeSide } from "../types"; 12 | 13 | // https://solscan.io/tx/4ryJKTB1vYmGU6YnUWwbLps18FaJjiTwgRozcgdP8RFcwp7zUZi85vgWE7rARNx2NvzDJiM9CUWArqzY7LHv38WL 14 | export const DEFAULT_ADD_LIQUIDITY_CU = 1_000_000; 15 | export const DEFAULT_EXTEND_POSITION_HIGH_CU = 1_000_000; 16 | export const DEFAULT_EXTEND_POSITION_LOW_CU = 30_000; 17 | export const DEFAULT_INIT_POSITION_CU = 30_000; 18 | export const DEFAULT_INIT_BIN_ARRAY_CU = 350_000; 19 | 20 | export const MIN_CU_BUFFER = 50_000; 21 | export const MAX_CU_BUFFER = 200_000; 22 | export const MAX_CU = 1_400_000; 23 | 24 | // CU estimate is difficult due to the CU estimated is based on current position state. We use hardcoded value ... 25 | export const getDefaultExtendPositionCU = (side: ResizeSide) => { 26 | switch (side) { 27 | case ResizeSide.Lower: 28 | return DEFAULT_EXTEND_POSITION_HIGH_CU; 29 | case ResizeSide.Upper: 30 | return DEFAULT_EXTEND_POSITION_LOW_CU; 31 | } 32 | }; 33 | 34 | export const getSimulationComputeUnits = async ( 35 | connection: Connection, 36 | instructions: Array, 37 | payer: PublicKey, 38 | lookupTables: Array | [], 39 | commitment: Commitment = "confirmed" 40 | ): Promise => { 41 | const testInstructions = [ 42 | // Set an arbitrarily high number in simulation 43 | // so we can be sure the transaction will succeed 44 | // and get the real compute units used 45 | ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }), 46 | ...instructions, 47 | ]; 48 | 49 | const testTransaction = new VersionedTransaction( 50 | new TransactionMessage({ 51 | instructions: testInstructions, 52 | payerKey: payer, 53 | // RecentBlockhash can by any public key during simulation 54 | // since 'replaceRecentBlockhash' is set to 'true' below 55 | recentBlockhash: PublicKey.default.toString(), 56 | }).compileToV0Message(lookupTables) 57 | ); 58 | 59 | const rpcResponse = await connection.simulateTransaction(testTransaction, { 60 | replaceRecentBlockhash: true, 61 | sigVerify: false, 62 | commitment, 63 | }); 64 | 65 | if (rpcResponse?.value?.err) { 66 | const logs = rpcResponse.value.logs?.join("\n • ") || "No logs available"; 67 | throw new Error( 68 | `Transaction simulation failed:\n •${logs}` + 69 | JSON.stringify(rpcResponse?.value?.err) 70 | ); 71 | } 72 | 73 | return rpcResponse.value.unitsConsumed || null; 74 | }; 75 | -------------------------------------------------------------------------------- /ts-client/src/test/external/transfer_hook_counter.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "abcSyangMHdGzUGKhBhKoQzSFdJKUdkPGf5cbXVHpEw", 3 | "metadata": { 4 | "name": "transfer_hook_counter", 5 | "version": "0.1.0", 6 | "spec": "0.1.0" 7 | }, 8 | "instructions": [ 9 | { 10 | "name": "initialize_extra_account_meta_list", 11 | "discriminator": [ 12 | 92, 13 | 197, 14 | 174, 15 | 197, 16 | 41, 17 | 124, 18 | 19, 19 | 3 20 | ], 21 | "accounts": [ 22 | { 23 | "name": "payer", 24 | "writable": true, 25 | "signer": true 26 | }, 27 | { 28 | "name": "extra_account_meta_list", 29 | "writable": true 30 | }, 31 | { 32 | "name": "mint" 33 | }, 34 | { 35 | "name": "counter_account", 36 | "writable": true 37 | }, 38 | { 39 | "name": "token_program" 40 | }, 41 | { 42 | "name": "associated_token_program" 43 | }, 44 | { 45 | "name": "system_program" 46 | } 47 | ], 48 | "args": [] 49 | }, 50 | { 51 | "name": "transfer_hook", 52 | "discriminator": [ 53 | 220, 54 | 57, 55 | 220, 56 | 152, 57 | 126, 58 | 125, 59 | 97, 60 | 168 61 | ], 62 | "accounts": [ 63 | { 64 | "name": "source_token" 65 | }, 66 | { 67 | "name": "mint" 68 | }, 69 | { 70 | "name": "destination_token" 71 | }, 72 | { 73 | "name": "owner" 74 | }, 75 | { 76 | "name": "extra_account_meta_list" 77 | }, 78 | { 79 | "name": "counter_account", 80 | "writable": true 81 | } 82 | ], 83 | "args": [ 84 | { 85 | "name": "amount", 86 | "type": "u64" 87 | } 88 | ] 89 | } 90 | ], 91 | "accounts": [ 92 | { 93 | "name": "CounterAccount", 94 | "discriminator": [ 95 | 164, 96 | 8, 97 | 153, 98 | 71, 99 | 8, 100 | 44, 101 | 93, 102 | 22 103 | ] 104 | } 105 | ], 106 | "errors": [ 107 | { 108 | "code": 6000, 109 | "name": "AmountTooBig", 110 | "msg": "The amount is too big" 111 | } 112 | ], 113 | "types": [ 114 | { 115 | "name": "CounterAccount", 116 | "type": { 117 | "kind": "struct", 118 | "fields": [ 119 | { 120 | "name": "counter", 121 | "type": "u32" 122 | } 123 | ] 124 | } 125 | } 126 | ] 127 | } -------------------------------------------------------------------------------- /python-client/dlmm/tests/test_util_methods.py: -------------------------------------------------------------------------------- 1 | from dlmm.dlmm import DLMM, DLMM_CLIENT 2 | from solana.rpc.api import Client 3 | from solders.pubkey import Pubkey 4 | from dlmm.types import FeeInfo, GetBins, Position 5 | from solders.keypair import Keypair 6 | from solana.transaction import Transaction 7 | 8 | def test_util_methods(): 9 | RPC = "https://api.devnet.solana.com" 10 | pool_address = Pubkey.from_string("3W2HKgUa96Z69zzG3LK1g8KdcRAWzAttiLiHfYnKuPw5") 11 | # client = Client(RPC) 12 | dlmm = DLMM_CLIENT.create(pool_address, RPC) 13 | assert isinstance(dlmm, DLMM) 14 | 15 | user = Keypair.from_bytes([3, 65, 174, 194, 140, 162, 138, 46, 167, 188, 153, 227, 110, 110, 82, 167, 238, 92, 174, 250, 66, 104, 188, 196, 164, 72, 222, 202, 150, 52, 38, 249, 205, 59, 43, 173, 101, 40, 208, 183, 241, 9, 237, 75, 52, 240, 165, 65, 91, 247, 233, 207, 170, 155, 162, 181, 215, 135, 103, 2, 132, 32, 196, 16]) 16 | 17 | new_balance_position = Keypair() 18 | #new_balance_position = Keypair.from_bytes([32, 144, 75, 246, 203, 27, 190, 52, 136, 171, 135, 250, 125, 246, 242, 26, 67, 40, 71, 23, 206, 192, 101, 86, 155, 59, 121, 96, 14, 59, 50, 215, 212, 236, 210, 249, 79, 133, 198, 35, 7, 150, 118, 47, 206, 4, 220, 255, 79, 208, 248, 233, 179, 231, 209, 204, 139, 232, 20, 116, 66, 48, 2, 49]) 19 | 20 | bin_arrays = dlmm.get_bin_arrays() 21 | assert type(bin_arrays) == list 22 | 23 | fee_info = dlmm.get_fee_info() 24 | assert isinstance(fee_info, FeeInfo) 25 | 26 | dynamic_fee = dlmm.get_dynamic_fee() 27 | assert type(dynamic_fee) == float 28 | 29 | check_bin = bin_arrays[0].account.bins[0] 30 | 31 | bin_id = dlmm.get_bin_id_from_price(float(check_bin.price), True) 32 | assert isinstance(bin_id, int) or bin_id is None 33 | 34 | bins_around_active = dlmm.get_bins_around_active_bin(0, 0) 35 | assert isinstance(bins_around_active, GetBins) 36 | 37 | bins_between_upper_lower = dlmm.get_bins_between_lower_and_upper_bound(0, 2) 38 | assert isinstance(bins_between_upper_lower, GetBins) 39 | 40 | bins_between_min_max = dlmm.get_bins_between_min_and_max_price(0.1, 50.0) 41 | assert isinstance(bins_between_min_max, GetBins) 42 | 43 | positions = dlmm.get_positions_by_user_and_lb_pair(user.pubkey()) 44 | user_positions = next(filter(lambda x: x.public_key == new_balance_position.pubkey() ,positions.user_positions), None) 45 | if user_positions: 46 | claim_lm = dlmm.claim_LM_reward(new_balance_position.pubkey(), user_positions) 47 | assert isinstance(claim_lm, Transaction) 48 | 49 | claim_swap_fee = dlmm.claim_swap_fee(new_balance_position.pubkey(), user_positions) 50 | assert isinstance(claim_swap_fee, Transaction) 51 | 52 | all_positions = DLMM_CLIENT.get_all_lb_pair_positions_by_user(user.pubkey(), RPC) 53 | 54 | assert isinstance(all_positions, dict) and len(all_positions) > 0 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /ts-client/src/test/external/transfer_hook_counter.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Program IDL in camelCase format in order to be used in JS/TS. 3 | * 4 | * Note that this is only a type helper and is not the actual IDL. The original 5 | * IDL can be found at `target/idl/transfer_hook_counter.json`. 6 | */ 7 | export type TransferHookCounter = { 8 | "address": "abcSyangMHdGzUGKhBhKoQzSFdJKUdkPGf5cbXVHpEw", 9 | "metadata": { 10 | "name": "transferHookCounter", 11 | "version": "0.1.0", 12 | "spec": "0.1.0" 13 | }, 14 | "instructions": [ 15 | { 16 | "name": "initializeExtraAccountMetaList", 17 | "discriminator": [ 18 | 92, 19 | 197, 20 | 174, 21 | 197, 22 | 41, 23 | 124, 24 | 19, 25 | 3 26 | ], 27 | "accounts": [ 28 | { 29 | "name": "payer", 30 | "writable": true, 31 | "signer": true 32 | }, 33 | { 34 | "name": "extraAccountMetaList", 35 | "writable": true 36 | }, 37 | { 38 | "name": "mint" 39 | }, 40 | { 41 | "name": "counterAccount", 42 | "writable": true 43 | }, 44 | { 45 | "name": "tokenProgram" 46 | }, 47 | { 48 | "name": "associatedTokenProgram" 49 | }, 50 | { 51 | "name": "systemProgram" 52 | } 53 | ], 54 | "args": [] 55 | }, 56 | { 57 | "name": "transferHook", 58 | "discriminator": [ 59 | 220, 60 | 57, 61 | 220, 62 | 152, 63 | 126, 64 | 125, 65 | 97, 66 | 168 67 | ], 68 | "accounts": [ 69 | { 70 | "name": "sourceToken" 71 | }, 72 | { 73 | "name": "mint" 74 | }, 75 | { 76 | "name": "destinationToken" 77 | }, 78 | { 79 | "name": "owner" 80 | }, 81 | { 82 | "name": "extraAccountMetaList" 83 | }, 84 | { 85 | "name": "counterAccount", 86 | "writable": true 87 | } 88 | ], 89 | "args": [ 90 | { 91 | "name": "amount", 92 | "type": "u64" 93 | } 94 | ] 95 | } 96 | ], 97 | "accounts": [ 98 | { 99 | "name": "counterAccount", 100 | "discriminator": [ 101 | 164, 102 | 8, 103 | 153, 104 | 71, 105 | 8, 106 | 44, 107 | 93, 108 | 22 109 | ] 110 | } 111 | ], 112 | "errors": [ 113 | { 114 | "code": 6000, 115 | "name": "amountTooBig", 116 | "msg": "The amount is too big" 117 | } 118 | ], 119 | "types": [ 120 | { 121 | "name": "counterAccount", 122 | "type": { 123 | "kind": "struct", 124 | "fields": [ 125 | { 126 | "name": "counter", 127 | "type": "u32" 128 | } 129 | ] 130 | } 131 | } 132 | ] 133 | }; 134 | -------------------------------------------------------------------------------- /cli/src/instructions/simulate_swap_demand.rs: -------------------------------------------------------------------------------- 1 | use crate::instructions::utils::get_or_create_ata; 2 | use crate::swap; 3 | use crate::SwapExactInParameters; 4 | use anchor_client::solana_client::rpc_config::RpcSendTransactionConfig; 5 | use anchor_client::solana_sdk::instruction::Instruction; 6 | use anchor_client::{solana_sdk::pubkey::Pubkey, solana_sdk::signer::Signer, Program}; 7 | use anchor_spl::token::Mint; 8 | use anyhow::*; 9 | use lb_clmm::state::lb_pair::LbPair; 10 | use rand::Rng; 11 | use std::ops::Deref; 12 | use std::result::Result::Ok; 13 | #[derive(Debug)] 14 | pub struct SimulateSwapDemandParameters { 15 | pub lb_pair: Pubkey, 16 | pub x_amount: f64, // ex: 10 jup 17 | pub y_amount: f64, // ex: 1k jup 18 | pub side_ratio: u64, 19 | } 20 | 21 | pub async fn simulate_swap_demand + Clone>( 22 | params: SimulateSwapDemandParameters, 23 | program: &Program, 24 | transaction_config: RpcSendTransactionConfig, 25 | compute_unit_price: Option, 26 | ) -> Result<()> { 27 | let SimulateSwapDemandParameters { 28 | lb_pair, 29 | x_amount, 30 | y_amount, 31 | side_ratio, 32 | } = params; 33 | 34 | let lb_pair_state: LbPair = program.account(lb_pair).await?; 35 | let token_mint_base: Mint = program.account(lb_pair_state.token_x_mint).await?; 36 | let token_mint_quote: Mint = program.account(lb_pair_state.token_y_mint).await?; 37 | 38 | get_or_create_ata( 39 | program, 40 | transaction_config, 41 | lb_pair_state.token_x_mint, 42 | program.payer(), 43 | compute_unit_price.clone(), 44 | ) 45 | .await?; 46 | get_or_create_ata( 47 | program, 48 | transaction_config, 49 | lb_pair_state.token_y_mint, 50 | program.payer(), 51 | compute_unit_price.clone(), 52 | ) 53 | .await?; 54 | 55 | // random amount 56 | let mut rng = rand::thread_rng(); 57 | loop { 58 | let side = rng.gen_range(0..side_ratio); 59 | if side == 0 { 60 | // sell side 61 | println!("try to sell {x_amount} jup"); 62 | let amount_x = x_amount * (10u64.pow(token_mint_base.decimals as u32) as f64); 63 | let params = SwapExactInParameters { 64 | amount_in: amount_x.round() as u64, 65 | lb_pair, 66 | swap_for_y: true, 67 | }; 68 | match swap(params, program, transaction_config).await { 69 | Ok(_) => {} 70 | Err(err) => { 71 | println!("{err}"); 72 | } 73 | } 74 | } else { 75 | // buy side 76 | println!("try to buy with {y_amount} usd"); 77 | let amount_y = y_amount * (10u64.pow(token_mint_quote.decimals as u32) as f64); 78 | 79 | let params = SwapExactInParameters { 80 | amount_in: amount_y.round() as u64, 81 | lb_pair, 82 | swap_for_y: false, 83 | }; 84 | match swap(params, program, transaction_config).await { 85 | Ok(_) => {} 86 | Err(err) => { 87 | println!("{err}"); 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /cli/src/instructions/fund_reward.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use instructions::*; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct FundRewardParams { 6 | pub lb_pair: Pubkey, 7 | pub reward_index: u64, 8 | pub funding_amount: u64, 9 | } 10 | 11 | pub async fn execute_fund_reward + Clone>( 12 | params: FundRewardParams, 13 | program: &Program, 14 | transaction_config: RpcSendTransactionConfig, 15 | compute_unit_price: Option, 16 | ) -> Result<()> { 17 | let FundRewardParams { 18 | lb_pair, 19 | reward_index, 20 | funding_amount, 21 | } = params; 22 | 23 | let rpc_client = program.rpc(); 24 | 25 | let (reward_vault, _bump) = derive_reward_vault_pda(lb_pair, reward_index); 26 | 27 | let lb_pair_state: LbPair = rpc_client 28 | .get_account_and_deserialize(&lb_pair, |account| { 29 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 30 | }) 31 | .await?; 32 | 33 | let reward_info = lb_pair_state.reward_infos[reward_index as usize]; 34 | let reward_mint = reward_info.mint; 35 | 36 | let reward_mint_program = rpc_client.get_account(&reward_mint).await?.owner; 37 | 38 | let funder_token_account = get_or_create_ata( 39 | program, 40 | transaction_config, 41 | reward_mint, 42 | program.payer(), 43 | compute_unit_price.clone(), 44 | ) 45 | .await?; 46 | 47 | let active_bin_array_idx = BinArray::bin_id_to_bin_array_index(lb_pair_state.active_id)?; 48 | let (bin_array, _bump) = derive_bin_array_pda(lb_pair, active_bin_array_idx as i64); 49 | 50 | let (event_authority, _bump) = derive_event_authority_pda(); 51 | 52 | let reward_transfer_hook_accounts = 53 | get_extra_account_metas_for_transfer_hook(reward_mint, program.rpc()).await?; 54 | 55 | let remaining_accounts_info = RemainingAccountsInfo { 56 | slices: vec![RemainingAccountsSlice { 57 | accounts_type: AccountsType::TransferHookReward, 58 | length: reward_transfer_hook_accounts.len() as u8, 59 | }], 60 | }; 61 | 62 | let main_accounts = dlmm::client::accounts::FundReward { 63 | lb_pair, 64 | reward_vault, 65 | reward_mint, 66 | funder: program.payer(), 67 | funder_token_account, 68 | bin_array, 69 | token_program: reward_mint_program, 70 | event_authority, 71 | program: dlmm::ID, 72 | } 73 | .to_account_metas(None); 74 | 75 | let data = dlmm::client::args::FundReward { 76 | reward_index, 77 | amount: funding_amount, 78 | carry_forward: true, 79 | remaining_accounts_info, 80 | } 81 | .data(); 82 | 83 | let accounts = [main_accounts.to_vec(), reward_transfer_hook_accounts].concat(); 84 | 85 | let fund_reward_ix = Instruction { 86 | program_id: dlmm::ID, 87 | accounts, 88 | data, 89 | }; 90 | 91 | let request_builder = program.request(); 92 | let signature = request_builder 93 | .instruction(fund_reward_ix) 94 | .send_with_spinner_and_config(transaction_config) 95 | .await; 96 | 97 | println!("Fund reward. Signature: {:#?}", signature); 98 | 99 | signature?; 100 | 101 | Ok(()) 102 | } 103 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/withdraw_protocol_fee.rs: -------------------------------------------------------------------------------- 1 | use anchor_spl::associated_token::get_associated_token_address_with_program_id; 2 | use commons::dlmm::accounts::LbPair; 3 | 4 | use crate::*; 5 | #[derive(Debug, Parser)] 6 | pub struct WithdrawProtocolFeeParams { 7 | pub lb_pair: Pubkey, 8 | } 9 | 10 | pub async fn execute_withdraw_protocol_fee + Clone>( 11 | params: WithdrawProtocolFeeParams, 12 | program: &Program, 13 | transaction_config: RpcSendTransactionConfig, 14 | ) -> Result<()> { 15 | let WithdrawProtocolFeeParams { lb_pair } = params; 16 | 17 | let rpc_client = program.rpc(); 18 | 19 | let lb_pair_state: LbPair = rpc_client 20 | .get_account_and_deserialize(&lb_pair, |account| { 21 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 22 | }) 23 | .await?; 24 | 25 | let [token_x_program, token_y_program] = lb_pair_state.get_token_programs()?; 26 | 27 | let receiver_token_x = get_associated_token_address_with_program_id( 28 | &program.payer(), 29 | &lb_pair_state.token_x_mint, 30 | &token_x_program, 31 | ); 32 | 33 | let receiver_token_y = get_associated_token_address_with_program_id( 34 | &program.payer(), 35 | &lb_pair_state.token_y_mint, 36 | &token_y_program, 37 | ); 38 | 39 | let (claim_fee_operator, _) = derive_claim_protocol_fee_operator_pda(program.payer()); 40 | 41 | let main_accounts = dlmm::client::accounts::WithdrawProtocolFee { 42 | lb_pair, 43 | reserve_x: lb_pair_state.reserve_x, 44 | reserve_y: lb_pair_state.reserve_y, 45 | token_x_mint: lb_pair_state.token_x_mint, 46 | token_y_mint: lb_pair_state.token_y_mint, 47 | token_x_program, 48 | token_y_program, 49 | receiver_token_x, 50 | receiver_token_y, 51 | claim_fee_operator, 52 | operator: program.payer(), 53 | memo_program: spl_memo::ID, 54 | } 55 | .to_account_metas(None); 56 | 57 | let mut remaining_accounts_info = RemainingAccountsInfo { slices: vec![] }; 58 | let mut remaining_accounts = vec![]; 59 | 60 | if let Some((slices, transfer_hook_remaining_accounts)) = 61 | get_potential_token_2022_related_ix_data_and_accounts( 62 | &lb_pair_state, 63 | program.rpc(), 64 | ActionType::Liquidity, 65 | ) 66 | .await? 67 | { 68 | remaining_accounts_info.slices = slices; 69 | remaining_accounts.extend(transfer_hook_remaining_accounts); 70 | }; 71 | 72 | let data = dlmm::client::args::WithdrawProtocolFee { 73 | max_amount_x: u64::MAX, 74 | max_amount_y: u64::MAX, 75 | remaining_accounts_info, 76 | } 77 | .data(); 78 | 79 | let accounts = [main_accounts.to_vec(), remaining_accounts].concat(); 80 | 81 | let withdraw_ix = Instruction { 82 | program_id: dlmm::ID, 83 | accounts, 84 | data, 85 | }; 86 | 87 | let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(200_000); 88 | 89 | let request_builder = program.request(); 90 | let signature = request_builder 91 | .instruction(compute_budget_ix) 92 | .instruction(withdraw_ix) 93 | .send_with_spinner_and_config(transaction_config) 94 | .await; 95 | 96 | println!("WithdrawProtocolFee. Signature: {:#?}", signature); 97 | 98 | signature?; 99 | 100 | Ok(()) 101 | } 102 | -------------------------------------------------------------------------------- /cli/src/instructions/list_all_binstep.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::Discriminator; 2 | use solana_client::{ 3 | rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, 4 | rpc_filter::{Memcmp, RpcFilterType}, 5 | }; 6 | 7 | use crate::*; 8 | 9 | pub async fn execute_list_all_bin_step + Clone>( 10 | program: &Program, 11 | ) -> Result<()> { 12 | let rpc_client = program.rpc(); 13 | 14 | let account_config = RpcAccountInfoConfig { 15 | encoding: Some(UiAccountEncoding::Base64), 16 | data_slice: Some(UiDataSliceConfig { 17 | offset: 0, 18 | length: 0, 19 | }), 20 | ..Default::default() 21 | }; 22 | 23 | let preset_parameter_keys = rpc_client 24 | .get_program_accounts_with_config( 25 | &dlmm::ID, 26 | RpcProgramAccountsConfig { 27 | filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( 28 | 0, 29 | &PresetParameter::DISCRIMINATOR, 30 | ))]), 31 | account_config: account_config.clone(), 32 | ..Default::default() 33 | }, 34 | ) 35 | .await? 36 | .into_iter() 37 | .map(|(key, _)| key) 38 | .collect::>(); 39 | 40 | let preset_parameter_v2_keys = rpc_client 41 | .get_program_accounts_with_config( 42 | &dlmm::ID, 43 | RpcProgramAccountsConfig { 44 | filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( 45 | 0, 46 | &PresetParameter2::DISCRIMINATOR, 47 | ))]), 48 | account_config, 49 | ..Default::default() 50 | }, 51 | ) 52 | .await? 53 | .into_iter() 54 | .map(|(key, _)| key) 55 | .collect::>(); 56 | 57 | let all_versioned_keys = [preset_parameter_keys, preset_parameter_v2_keys].concat(); 58 | 59 | for keys in all_versioned_keys.chunks(100) { 60 | let accounts = rpc_client.get_multiple_accounts(keys).await?; 61 | for (key, account) in keys.iter().zip(accounts) { 62 | if let Some(account) = account { 63 | let mut disc = [0u8; 8]; 64 | disc.copy_from_slice(&account.data[..8]); 65 | 66 | let (bin_step, base_factor, base_fee_power_factor) = if disc 67 | == PresetParameter::DISCRIMINATOR 68 | { 69 | let state = PresetParameter::try_deserialize(&mut account.data.as_ref())?; 70 | (state.bin_step, state.base_factor, 0) 71 | } else if disc == PresetParameter2::DISCRIMINATOR { 72 | let state: PresetParameter2 = bytemuck::pod_read_unaligned(&account.data[8..]); 73 | ( 74 | state.bin_step, 75 | state.base_factor, 76 | state.base_fee_power_factor, 77 | ) 78 | } else { 79 | continue; 80 | }; 81 | 82 | let base_fee = (u128::from(bin_step) 83 | * u128::from(base_factor).pow(base_fee_power_factor.into()) 84 | * 1000) as f64 85 | / FEE_PRECISION as f64; 86 | 87 | println!( 88 | "Preset Pubkey: {}. Bin step {}. Base fee: {}%", 89 | key, bin_step, base_fee 90 | ); 91 | } 92 | } 93 | } 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/rebalance/strategy/balanced.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import { 3 | capBps, 4 | MAX_BPS, 5 | RebalanceDepositWithdrawParameters, 6 | RebalanceStrategyBuilder, 7 | } from "."; 8 | import { PositionData, StrategyType } from "../../../types"; 9 | import { 10 | buildLiquidityStrategyParameters, 11 | getLiquidityStrategyParameterBuilder, 12 | } from "../liquidity_strategy"; 13 | import { 14 | RebalanceWithDeposit, 15 | RebalanceWithWithdraw, 16 | } from "../rebalancePosition"; 17 | 18 | export class BalancedStrategyBuilder implements RebalanceStrategyBuilder { 19 | constructor( 20 | public activeId: BN, 21 | public binStep: BN, 22 | public positionData: PositionData, 23 | public topUpAmountX: BN, 24 | public topUpAmountY: BN, 25 | public xWithdrawBps: BN, 26 | public yWithdrawBps: BN, 27 | public strategy: StrategyType, 28 | public favorXIfImbalance: boolean = false, 29 | public favorXInActiveBin: boolean = false 30 | ) {} 31 | 32 | // Rebalance to active bin by withdrawing all liquidities and redeposit portion of withdrawn liquidity, together with topup amount 33 | buildRebalanceStrategyParameters(): RebalanceDepositWithdrawParameters { 34 | const xWithdrawBps = capBps(this.xWithdrawBps); 35 | const yWithdrawBps = capBps(this.yWithdrawBps); 36 | 37 | let totalXAmountOut = new BN(this.positionData.totalXAmount); 38 | let totalYAmountOut = new BN(this.positionData.totalYAmount); 39 | 40 | totalXAmountOut = totalXAmountOut.add(new BN(this.positionData.feeX)); 41 | totalYAmountOut = totalYAmountOut.add(new BN(this.positionData.feeY)); 42 | 43 | const redepositAmountX = totalXAmountOut 44 | .mul(MAX_BPS.sub(xWithdrawBps)) 45 | .div(MAX_BPS); 46 | const redepositAmountY = totalYAmountOut 47 | .mul(MAX_BPS.sub(yWithdrawBps)) 48 | .div(MAX_BPS); 49 | 50 | const depositAmountX = this.topUpAmountX.add(redepositAmountX); 51 | const depositAmountY = this.topUpAmountY.add(redepositAmountY); 52 | 53 | const width = 54 | this.positionData.upperBinId - this.positionData.lowerBinId + 1; 55 | const binPerSide = Math.floor(width / 2); 56 | const rem = width % 2; 57 | 58 | let binPerAsk = binPerSide; 59 | let binPerBid = binPerSide; 60 | 61 | if (rem == 0) { 62 | if (this.favorXIfImbalance) { 63 | binPerAsk += 1; 64 | binPerBid -= 1; 65 | } else { 66 | binPerAsk -= 1; 67 | binPerBid += 1; 68 | } 69 | } 70 | 71 | const minDeltaId = new BN(binPerBid).neg(); 72 | const maxDeltaId = new BN(binPerAsk); 73 | 74 | const strategyParameters = buildLiquidityStrategyParameters( 75 | depositAmountX, 76 | depositAmountY, 77 | minDeltaId, 78 | maxDeltaId, 79 | this.binStep, 80 | this.favorXInActiveBin, 81 | this.activeId, 82 | getLiquidityStrategyParameterBuilder(this.strategy) 83 | ); 84 | 85 | const depositParam: RebalanceWithDeposit = { 86 | minDeltaId, 87 | maxDeltaId, 88 | x0: strategyParameters.x0, 89 | y0: strategyParameters.y0, 90 | deltaX: strategyParameters.deltaX, 91 | deltaY: strategyParameters.deltaY, 92 | favorXInActiveBin: this.favorXInActiveBin, 93 | }; 94 | 95 | const withdrawParam: RebalanceWithWithdraw = { 96 | minBinId: new BN(this.positionData.lowerBinId), 97 | maxBinId: new BN(this.positionData.upperBinId), 98 | bps: MAX_BPS, 99 | }; 100 | 101 | return { 102 | shouldClaimFee: true, 103 | shouldClaimReward: true, 104 | deposits: [depositParam], 105 | withdraws: [withdrawParam], 106 | }; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /commons/src/extensions/position.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey}; 3 | 4 | pub trait PositionExtension { 5 | fn get_bin_array_indexes_bound(&self) -> Result<(i32, i32)>; 6 | fn get_bin_array_keys_coverage(&self) -> Result>; 7 | fn get_bin_array_accounts_meta_coverage(&self) -> Result>; 8 | 9 | fn get_bin_array_indexes_bound_by_chunk( 10 | &self, 11 | lower_bin_id: i32, 12 | upper_bin_id: i32, 13 | ) -> Result<(i32, i32)>; 14 | 15 | fn get_bin_array_keys_coverage_by_chunk( 16 | &self, 17 | lower_bin_id: i32, 18 | upper_bin_id: i32, 19 | ) -> Result>; 20 | 21 | fn get_bin_array_accounts_meta_coverage_by_chunk( 22 | &self, 23 | lower_bin_id: i32, 24 | upper_bin_id: i32, 25 | ) -> Result>; 26 | 27 | fn is_empty(&self) -> bool; 28 | } 29 | 30 | impl PositionExtension for PositionV2 { 31 | fn get_bin_array_indexes_bound(&self) -> Result<(i32, i32)> { 32 | self.get_bin_array_indexes_bound_by_chunk(self.lower_bin_id, self.upper_bin_id) 33 | } 34 | 35 | fn get_bin_array_indexes_bound_by_chunk( 36 | &self, 37 | lower_bin_id: i32, 38 | upper_bin_id: i32, 39 | ) -> Result<(i32, i32)> { 40 | ensure!(lower_bin_id >= self.lower_bin_id && upper_bin_id <= self.upper_bin_id); 41 | let lower_bin_array_index = BinArray::bin_id_to_bin_array_index(lower_bin_id)?; 42 | let upper_bin_array_index = lower_bin_array_index + 1; 43 | Ok((lower_bin_array_index, upper_bin_array_index)) 44 | } 45 | 46 | fn get_bin_array_keys_coverage(&self) -> Result> { 47 | self.get_bin_array_keys_coverage_by_chunk(self.lower_bin_id, self.upper_bin_id) 48 | } 49 | 50 | fn get_bin_array_keys_coverage_by_chunk( 51 | &self, 52 | lower_bin_id: i32, 53 | upper_bin_id: i32, 54 | ) -> Result> { 55 | let (lower_bin_array_index, upper_bin_array_index) = 56 | self.get_bin_array_indexes_bound_by_chunk(lower_bin_id, upper_bin_id)?; 57 | let mut bin_array_keys = Vec::new(); 58 | for bin_array_index in lower_bin_array_index..=upper_bin_array_index { 59 | bin_array_keys.push(derive_bin_array_pda(self.lb_pair, bin_array_index.into()).0); 60 | } 61 | Ok(bin_array_keys) 62 | } 63 | 64 | fn get_bin_array_accounts_meta_coverage(&self) -> Result> { 65 | self.get_bin_array_accounts_meta_coverage_by_chunk(self.lower_bin_id, self.upper_bin_id) 66 | } 67 | 68 | fn get_bin_array_accounts_meta_coverage_by_chunk( 69 | &self, 70 | lower_bin_id: i32, 71 | upper_bin_id: i32, 72 | ) -> Result> { 73 | let bin_array_keys = 74 | self.get_bin_array_keys_coverage_by_chunk(lower_bin_id, upper_bin_id)?; 75 | Ok(bin_array_keys 76 | .into_iter() 77 | .map(|key| AccountMeta::new(key, false)) 78 | .collect()) 79 | } 80 | 81 | fn is_empty(&self) -> bool { 82 | for i in 0..self.liquidity_shares.len() { 83 | if self.liquidity_shares[i] != 0 { 84 | return false; 85 | } 86 | 87 | if self.fee_infos[i].fee_x_pending != 0 || self.fee_infos[i].fee_y_pending != 0 { 88 | return false; 89 | } 90 | 91 | for pending_reward in self.reward_infos[i].reward_pendings { 92 | if pending_reward != 0 { 93 | return false; 94 | } 95 | } 96 | } 97 | 98 | true 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /python-client/dlmm/tests/test_lp_flow.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from dlmm import DLMM_CLIENT 3 | from dlmm.dlmm import DLMM 4 | from dlmm.types import GetPositionByUser, StrategyType, SwapQuote 5 | from solders.keypair import Keypair 6 | from solders.pubkey import Pubkey 7 | from solana.rpc.api import Client 8 | from solana.transaction import Transaction 9 | 10 | def test_flow(): 11 | RPC = "https://api.devnet.solana.com" 12 | pool_address = Pubkey.from_string("3W2HKgUa96Z69zzG3LK1g8KdcRAWzAttiLiHfYnKuPw5") 13 | client = Client(RPC) 14 | dlmm = DLMM_CLIENT.create(pool_address, RPC) 15 | assert isinstance(dlmm, DLMM) 16 | 17 | active_bin = dlmm.get_active_bin() 18 | active_bin_price_per_token = dlmm.from_price_per_lamport(active_bin.price) 19 | assert type(active_bin_price_per_token) == float 20 | 21 | user = Keypair.from_bytes([3, 65, 174, 194, 140, 162, 138, 46, 167, 188, 153, 227, 110, 110, 82, 167, 238, 92, 174, 250, 66, 104, 188, 196, 164, 72, 222, 202, 150, 52, 38, 249, 205, 59, 43, 173, 101, 40, 208, 183, 241, 9, 237, 75, 52, 240, 165, 65, 91, 247, 233, 207, 170, 155, 162, 181, 215, 135, 103, 2, 132, 32, 196, 16]) 22 | 23 | new_balance_position = Keypair() 24 | 25 | total_interval_range = 10 26 | max_bin_id = active_bin.bin_id + total_interval_range 27 | min_bin_id = active_bin.bin_id - total_interval_range 28 | total_x_amount = 100 29 | total_y_amount = total_x_amount * int(active_bin_price_per_token) 30 | 31 | position_tx = dlmm.initialize_position_and_add_liquidity_by_strategy( 32 | new_balance_position.pubkey(), 33 | user.pubkey(), 34 | total_x_amount, 35 | total_y_amount, 36 | { 37 | "max_bin_id": max_bin_id, 38 | "min_bin_id": min_bin_id, 39 | "strategy_type": 0 40 | }) 41 | 42 | assert isinstance(position_tx, Transaction) 43 | 44 | client.send_transaction(position_tx, user, new_balance_position) 45 | print("Transaction sent") 46 | 47 | positions = dlmm.get_positions_by_user_and_lb_pair(user.pubkey()) 48 | assert isinstance(positions, GetPositionByUser) 49 | 50 | add_liquidity_tx = dlmm.add_liquidity_by_strategy( 51 | new_balance_position.pubkey(), 52 | user.pubkey(), 53 | total_x_amount, 54 | total_y_amount, 55 | { 56 | "max_bin_id": max_bin_id, 57 | "min_bin_id": min_bin_id, 58 | "strategy_type": StrategyType.SpotBalanced 59 | }) 60 | assert isinstance(add_liquidity_tx, Transaction) 61 | 62 | client.send_transaction(add_liquidity_tx, user) 63 | print("Transaction sent") 64 | 65 | user_positions = next(filter(lambda x: x.public_key == new_balance_position.pubkey() ,positions.user_positions), None) 66 | 67 | if user_positions: 68 | bin_ids_to_remove = list(map(lambda x: x.bin_id, user_positions.position_data.position_bin_data)) 69 | remove_liquidity = dlmm.remove_liqidity( 70 | new_balance_position.pubkey(), 71 | user.pubkey(), 72 | bin_ids_to_remove, 73 | 100*100, 74 | True 75 | ) 76 | assert isinstance(remove_liquidity, list) 77 | client.send_transaction(remove_liquidity, user) 78 | 79 | swap_amount = 100 80 | swap_y_to_x = True 81 | bin_arrays = dlmm.get_bin_array_for_swap(swap_y_to_x) 82 | swap_quote = dlmm.swap_quote(swap_amount, swap_y_to_x, 10, bin_arrays) 83 | assert isinstance(swap_quote, SwapQuote) 84 | 85 | swap_tx = dlmm.swap( 86 | dlmm.token_X.public_key, 87 | dlmm.token_Y.public_key, 88 | swap_amount, 89 | swap_quote.min_out_amount, 90 | dlmm.pool_address, 91 | user.pubkey(), 92 | swap_quote.bin_arrays_pubkey 93 | ) 94 | assert isinstance(swap_tx, Transaction) 95 | 96 | client.send_transaction(swap_tx, user) 97 | 98 | 99 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js"; 2 | import { BN } from "@coral-xyz/anchor"; 3 | import IDL from "../dlmm.json"; 4 | import Decimal from "decimal.js"; 5 | 6 | export const LBCLMM_PROGRAM_IDS = { 7 | devnet: "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo", 8 | localhost: "LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ", 9 | "mainnet-beta": "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo", 10 | }; 11 | 12 | export const ALT_ADDRESS = { 13 | "mainnet-beta": "JA5F83HUK9L78Y12TRLCsJZbu3Tv8pCK1GfK8mVNp1sz", 14 | devnet: "BD6E8oYV52P829MxDq3sEtqWKJrHh9pDSUHeaWR1jRBs", 15 | }; 16 | 17 | export const ADMIN = { 18 | devnet: "6WaLrrRfReGKBYUSkmx2K6AuT21ida4j8at2SUiZdXu8", 19 | localhost: "bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1", 20 | }; 21 | 22 | export enum Network { 23 | MAINNET = "mainnet-beta", 24 | TESTNET = "testnet", 25 | DEVNET = "devnet", 26 | LOCAL = "localhost", 27 | } 28 | 29 | export const BASIS_POINT_MAX = 10000; 30 | export const SCALE_OFFSET = 64; 31 | export const SCALE = new BN(1).shln(SCALE_OFFSET); 32 | 33 | export const FEE_PRECISION = new BN(1_000_000_000); 34 | export const MAX_FEE_RATE = new BN(100_000_000); 35 | 36 | // https://solscan.io/tx/5JgHgEiVoqV61p3SASYzP4gnedvYFLhewPchBdFgPQZjHEiitjZCqs8u4rXyDYnGJ9zqAscknv9NoBiodsfDE1qR 37 | export const BIN_ARRAY_FEE = 0.07143744; 38 | // https://solscan.io/tx/37yEmHsTU6tKjUc6iGG8GPiEuPHxiyBezwexsnnsqXQQKuDgwsNciEzkQZFWJShcdLpfug5xqNBPJkzit7eWvkDD 39 | export const POSITION_FEE = 0.05740608; 40 | export const TOKEN_ACCOUNT_FEE = 0.00203928; 41 | // https://solscan.io/tx/4QkTyVZbZgS3Go7ksEWzmHef7SBVgoJ8Fjjxk3eL9LZBBmrXHJarVM4TPy5Nq3XcjwdhWALeCCbL7xonExBGpNry 42 | export const POOL_FEE = 0.00718272; 43 | export const BIN_ARRAY_BITMAP_FEE = 0.01180416; 44 | 45 | export const BIN_ARRAY_FEE_BN = new BN( 46 | new Decimal(BIN_ARRAY_FEE).mul(LAMPORTS_PER_SOL).toString() 47 | ); 48 | export const POSITION_FEE_BN = new BN( 49 | new Decimal(POSITION_FEE).mul(LAMPORTS_PER_SOL).toString() 50 | ); 51 | export const TOKEN_ACCOUNT_FEE_BN = new BN( 52 | new Decimal(TOKEN_ACCOUNT_FEE).mul(LAMPORTS_PER_SOL).toString() 53 | ); 54 | export const POOL_FEE_BN = new BN( 55 | new Decimal(POOL_FEE).mul(LAMPORTS_PER_SOL).toString() 56 | ); 57 | export const BIN_ARRAY_BITMAP_FEE_BN = new BN( 58 | new Decimal(BIN_ARRAY_BITMAP_FEE).mul(LAMPORTS_PER_SOL).toString() 59 | ); 60 | 61 | const CONSTANTS = Object.entries(IDL.constants); 62 | 63 | export const MAX_BIN_ARRAY_SIZE = new BN( 64 | CONSTANTS.find(([k, v]) => v.name == "MAX_BIN_PER_ARRAY")?.[1].value ?? 0 65 | ); 66 | export const DEFAULT_BIN_PER_POSITION = new BN( 67 | CONSTANTS.find(([k, v]) => v.name == "DEFAULT_BIN_PER_POSITION")?.[1].value ?? 68 | 0 69 | ); 70 | export const BIN_ARRAY_BITMAP_SIZE = new BN( 71 | CONSTANTS.find(([k, v]) => v.name == "BIN_ARRAY_BITMAP_SIZE")?.[1].value ?? 0 72 | ); 73 | export const EXTENSION_BINARRAY_BITMAP_SIZE = new BN( 74 | CONSTANTS.find(([k, v]) => v.name == "EXTENSION_BINARRAY_BITMAP_SIZE")?.[1] 75 | .value ?? 0 76 | ); 77 | 78 | export const POSITION_MAX_LENGTH = new BN( 79 | CONSTANTS.find(([k, v]) => v.name == "POSITION_MAX_LENGTH")?.[1].value ?? 0 80 | ); 81 | 82 | export const MAX_RESIZE_LENGTH = new BN( 83 | CONSTANTS.find(([k, v]) => v.name == "MAX_RESIZE_LENGTH")?.[1].value ?? 0 84 | ); 85 | 86 | export const SIMULATION_USER = new PublicKey( 87 | "HrY9qR5TiB2xPzzvbBu5KrBorMfYGQXh9osXydz4jy9s" 88 | ); 89 | 90 | export const PRECISION = 18446744073709551616; 91 | 92 | export const MAX_CLAIM_ALL_ALLOWED = 2; 93 | 94 | export const MAX_BIN_LENGTH_ALLOWED_IN_ONE_TX = 26; 95 | 96 | export const MAX_ACTIVE_BIN_SLIPPAGE = 3; 97 | 98 | export const ILM_BASE = new PublicKey( 99 | "MFGQxwAmB91SwuYX36okv2Qmdc9aMuHTwWGUrp4AtB1" 100 | ); 101 | 102 | export const MAX_EXTRA_BIN_ARRAYS = 3; 103 | export const U64_MAX = new BN("18446744073709551615"); 104 | 105 | export const MAX_BINS_PER_POSITION = new BN( 106 | CONSTANTS.find(([k, v]) => v.name == "POSITION_MAX_LENGTH")?.[1].value ?? 0 107 | ); 108 | -------------------------------------------------------------------------------- /commons/tests/integration/helpers/utils.rs: -------------------------------------------------------------------------------- 1 | use anchor_spl::associated_token::*; 2 | use anchor_spl::token::spl_token; 3 | use assert_matches::assert_matches; 4 | use solana_program_test::BanksClient; 5 | use solana_sdk::{ 6 | instruction::Instruction, 7 | pubkey::Pubkey, 8 | signature::{Keypair, Signer}, 9 | transaction::Transaction, 10 | }; 11 | pub async fn process_and_assert_ok( 12 | instructions: &[Instruction], 13 | payer: &Keypair, 14 | signers: &[&Keypair], 15 | banks_client: &mut BanksClient, 16 | ) { 17 | let recent_blockhash = banks_client.get_latest_blockhash().await.unwrap(); 18 | 19 | let mut all_signers = vec![payer]; 20 | all_signers.extend_from_slice(signers); 21 | 22 | let tx = Transaction::new_signed_with_payer( 23 | instructions, 24 | Some(&payer.pubkey()), 25 | &all_signers, 26 | recent_blockhash, 27 | ); 28 | 29 | let result = banks_client.process_transaction(tx).await.inspect_err(|e| { 30 | println!("Transaction error: {}", e); 31 | }); 32 | 33 | assert_matches!(result, Ok(())); 34 | } 35 | 36 | pub async fn get_or_create_ata( 37 | payer: &Keypair, 38 | token_mint: &Pubkey, 39 | authority: &Pubkey, 40 | banks_client: &mut BanksClient, 41 | ) -> Pubkey { 42 | let token_mint_owner = banks_client 43 | .get_account(*token_mint) 44 | .await 45 | .ok() 46 | .flatten() 47 | .unwrap() 48 | .owner; 49 | let ata_address = 50 | get_associated_token_address_with_program_id(authority, token_mint, &token_mint_owner); 51 | let ata_account = banks_client.get_account(ata_address).await.unwrap(); 52 | if ata_account.is_none() { 53 | create_associated_token_account( 54 | payer, 55 | token_mint, 56 | authority, 57 | &token_mint_owner, 58 | banks_client, 59 | ) 60 | .await; 61 | } 62 | ata_address 63 | } 64 | 65 | pub async fn create_associated_token_account( 66 | payer: &Keypair, 67 | token_mint: &Pubkey, 68 | authority: &Pubkey, 69 | program_id: &Pubkey, 70 | banks_client: &mut BanksClient, 71 | ) { 72 | println!("{}", program_id); 73 | let ins = vec![ 74 | spl_associated_token_account::instruction::create_associated_token_account( 75 | &payer.pubkey(), 76 | authority, 77 | token_mint, 78 | program_id, 79 | ), 80 | ]; 81 | 82 | process_and_assert_ok(&ins, payer, &[payer], banks_client).await; 83 | } 84 | 85 | pub async fn warp_sol( 86 | payer: &Keypair, 87 | wallet: Pubkey, 88 | amount: u64, 89 | banks_client: &mut BanksClient, 90 | ) { 91 | let wsol_ata = spl_associated_token_account::get_associated_token_address( 92 | &wallet, 93 | &spl_token::native_mint::id(), 94 | ); 95 | 96 | let create_wsol_ata_ix = 97 | spl_associated_token_account::instruction::create_associated_token_account( 98 | &payer.pubkey(), 99 | &payer.pubkey(), 100 | &spl_token::native_mint::id(), 101 | &spl_token::id(), 102 | ); 103 | 104 | let transfer_sol_ix = 105 | solana_program::system_instruction::transfer(&payer.pubkey(), &wsol_ata, amount); 106 | 107 | let sync_native_ix = spl_token::instruction::sync_native(&spl_token::id(), &wsol_ata).unwrap(); 108 | 109 | process_and_assert_ok( 110 | &[create_wsol_ata_ix, transfer_sol_ix, sync_native_ix], 111 | &payer, 112 | &[&payer], 113 | banks_client, 114 | ) 115 | .await; 116 | } 117 | 118 | pub async fn get_clock(banks_client: &mut BanksClient) -> solana_program::clock::Clock { 119 | let clock_account = banks_client 120 | .get_account(solana_program::sysvar::clock::id()) 121 | .await 122 | .unwrap() 123 | .unwrap(); 124 | 125 | let clock_state = 126 | bincode::deserialize::(clock_account.data.as_ref()).unwrap(); 127 | 128 | clock_state 129 | } 130 | -------------------------------------------------------------------------------- /cli/src/instructions/claim_reward.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use instructions::*; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct ClaimRewardParams { 6 | pub lb_pair: Pubkey, 7 | pub reward_index: u64, 8 | pub position: Pubkey, 9 | } 10 | 11 | pub async fn execute_claim_reward + Clone>( 12 | params: ClaimRewardParams, 13 | program: &Program, 14 | transaction_config: RpcSendTransactionConfig, 15 | compute_unit_price: Option, 16 | ) -> Result<()> { 17 | let ClaimRewardParams { 18 | lb_pair, 19 | reward_index, 20 | position, 21 | } = params; 22 | 23 | let rpc_client = program.rpc(); 24 | let (reward_vault, _bump) = derive_reward_vault_pda(lb_pair, reward_index); 25 | 26 | let lb_pair_state: LbPair = rpc_client 27 | .get_account_and_deserialize(&lb_pair, |account| { 28 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 29 | }) 30 | .await?; 31 | 32 | let position_state: PositionV2 = rpc_client 33 | .get_account_and_deserialize(&position, |account| { 34 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 35 | }) 36 | .await?; 37 | 38 | let reward_info = lb_pair_state.reward_infos[reward_index as usize]; 39 | let reward_mint = reward_info.mint; 40 | 41 | let reward_mint_program = rpc_client.get_account(&reward_mint).await?.owner; 42 | 43 | let user_token_account = get_or_create_ata( 44 | program, 45 | transaction_config, 46 | reward_mint, 47 | program.payer(), 48 | compute_unit_price.clone(), 49 | ) 50 | .await?; 51 | 52 | let (event_authority, _bump) = derive_event_authority_pda(); 53 | 54 | let main_accounts = dlmm::client::accounts::ClaimReward2 { 55 | lb_pair, 56 | reward_vault, 57 | reward_mint, 58 | memo_program: spl_memo::ID, 59 | token_program: reward_mint_program, 60 | position, 61 | user_token_account, 62 | sender: program.payer(), 63 | event_authority, 64 | program: dlmm::ID, 65 | } 66 | .to_account_metas(None); 67 | 68 | let mut remaining_accounts_info = RemainingAccountsInfo { slices: vec![] }; 69 | let mut token_2022_remaining_accounts = vec![]; 70 | 71 | if let Some((slices, transfer_hook_remaining_accounts)) = 72 | get_potential_token_2022_related_ix_data_and_accounts( 73 | &lb_pair_state, 74 | program.rpc(), 75 | ActionType::Reward(reward_index as usize), 76 | ) 77 | .await? 78 | { 79 | remaining_accounts_info.slices = slices; 80 | token_2022_remaining_accounts.extend(transfer_hook_remaining_accounts); 81 | }; 82 | 83 | for (min_bin_id, max_bin_id) in 84 | position_bin_range_chunks(position_state.lower_bin_id, position_state.upper_bin_id) 85 | { 86 | let data = dlmm::client::args::ClaimReward2 { 87 | reward_index, 88 | min_bin_id, 89 | max_bin_id, 90 | remaining_accounts_info: remaining_accounts_info.clone(), 91 | } 92 | .data(); 93 | 94 | let bin_arrays_account_meta = 95 | position_state.get_bin_array_accounts_meta_coverage_by_chunk(min_bin_id, max_bin_id)?; 96 | 97 | let accounts = [ 98 | main_accounts.to_vec(), 99 | token_2022_remaining_accounts.clone(), 100 | bin_arrays_account_meta, 101 | ] 102 | .concat(); 103 | 104 | let claim_reward_ix = Instruction { 105 | program_id: dlmm::ID, 106 | accounts, 107 | data, 108 | }; 109 | 110 | let request_builder = program.request(); 111 | let signature = request_builder 112 | .instruction(claim_reward_ix) 113 | .send_with_spinner_and_config(transaction_config) 114 | .await; 115 | 116 | println!("Claim reward. Signature: {:#?}", signature); 117 | 118 | signature?; 119 | } 120 | 121 | Ok(()) 122 | } 123 | -------------------------------------------------------------------------------- /cli/src/instructions/sync_price.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_spl::token_interface::Mint; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct SyncPriceParams { 6 | pub lb_pair: Pubkey, 7 | pub price: f64, 8 | } 9 | 10 | pub async fn execute_sync_price + Clone>( 11 | params: SyncPriceParams, 12 | program: &Program, 13 | transaction_config: RpcSendTransactionConfig, 14 | compute_unit_price: Option, 15 | ) -> Result<()> { 16 | let SyncPriceParams { lb_pair, price } = params; 17 | 18 | let rpc_client = program.rpc(); 19 | 20 | let (bin_array_bitmap_extension, _bump) = derive_bin_array_bitmap_extension(lb_pair); 21 | 22 | let lb_pair_state: LbPair = rpc_client 23 | .get_account_and_deserialize(&lb_pair, |account| { 24 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 25 | }) 26 | .await?; 27 | 28 | let mut accounts = rpc_client 29 | .get_multiple_accounts(&[ 30 | lb_pair_state.token_x_mint, 31 | lb_pair_state.token_y_mint, 32 | bin_array_bitmap_extension, 33 | ]) 34 | .await?; 35 | 36 | let token_mint_base_account = accounts[0].take().context("token_mint_base not found")?; 37 | let token_mint_quote_account = accounts[1].take().context("token_mint_quote not found")?; 38 | let bin_array_bitmap_extension_account = accounts[2].take(); 39 | 40 | let token_mint_base = Mint::try_deserialize(&mut token_mint_base_account.data.as_ref())?; 41 | let token_mint_quote = Mint::try_deserialize(&mut token_mint_quote_account.data.as_ref())?; 42 | 43 | let price_per_lamport = 44 | price_per_token_to_per_lamport(price, token_mint_base.decimals, token_mint_quote.decimals) 45 | .context("price_per_token_to_per_lamport overflow")?; 46 | 47 | let computed_active_id = 48 | get_id_from_price(lb_pair_state.bin_step, &price_per_lamport, Rounding::Up) 49 | .context("get_id_from_price overflow")?; 50 | 51 | let ix_data = dlmm::client::args::GoToABin { 52 | bin_id: computed_active_id, 53 | } 54 | .data(); 55 | 56 | let from_bin_array_idx = BinArray::bin_id_to_bin_array_index(lb_pair_state.active_id)?; 57 | let to_bin_array_idx = BinArray::bin_id_to_bin_array_index(computed_active_id)?; 58 | 59 | let (from_bin_array, _bump) = derive_bin_array_pda(lb_pair, from_bin_array_idx.into()); 60 | let (to_bin_array, _bump) = derive_bin_array_pda(lb_pair, to_bin_array_idx.into()); 61 | 62 | accounts = rpc_client 63 | .get_multiple_accounts(&[from_bin_array, to_bin_array]) 64 | .await?; 65 | 66 | let from_bin_array_account = accounts[0].take(); 67 | let to_bin_array_account = accounts[1].take(); 68 | 69 | let (event_authority, _bump) = derive_event_authority_pda(); 70 | 71 | let accounts = dlmm::client::accounts::GoToABin { 72 | lb_pair, 73 | bin_array_bitmap_extension: bin_array_bitmap_extension_account 74 | .map(|_| bin_array_bitmap_extension) 75 | .or(Some(dlmm::ID)), 76 | from_bin_array: from_bin_array_account 77 | .map(|_| from_bin_array) 78 | .or(Some(dlmm::ID)), 79 | to_bin_array: to_bin_array_account 80 | .map(|_| to_bin_array) 81 | .or(Some(dlmm::ID)), 82 | event_authority, 83 | program: dlmm::ID, 84 | } 85 | .to_account_metas(None); 86 | 87 | let ix = Instruction { 88 | program_id: dlmm::ID, 89 | accounts, 90 | data: ix_data, 91 | }; 92 | 93 | let mut ixs = vec![]; 94 | 95 | if let Some(compute_unit_price_ix) = compute_unit_price { 96 | ixs.push(compute_unit_price_ix); 97 | } 98 | 99 | ixs.push(ix); 100 | 101 | let builder = program.request(); 102 | let builder = ixs 103 | .into_iter() 104 | .fold(builder, |builder, ix| builder.instruction(ix)); 105 | 106 | let signature = builder 107 | .send_with_spinner_and_config(transaction_config) 108 | .await; 109 | println!("{:#?}", signature); 110 | 111 | signature?; 112 | 113 | Ok(()) 114 | } 115 | -------------------------------------------------------------------------------- /commons/src/extensions/bin_array.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use num_integer::Integer; 3 | use solana_sdk::{instruction::AccountMeta, pubkey::Pubkey}; 4 | 5 | pub trait BinArrayExtension { 6 | fn is_bin_id_within_range(&self, bin_id: i32) -> Result; 7 | fn get_bin_index_in_array(&self, bin_id: i32) -> Result; 8 | 9 | fn get_bin_array_lower_upper_bin_id(index: i32) -> Result<(i32, i32)>; 10 | fn bin_id_to_bin_array_index(bin_id: i32) -> Result; 11 | fn bin_id_to_bin_array_key(lb_pair: Pubkey, bin_id: i32) -> Result; 12 | 13 | fn get_bin_mut<'a>(&'a mut self, bin_id: i32) -> Result<&'a mut Bin>; 14 | fn get_bin<'a>(&'a self, bin_id: i32) -> Result<&'a Bin>; 15 | 16 | fn get_bin_array_account_metas_coverage( 17 | lower_bin_id: i32, 18 | upper_bin_id: i32, 19 | lb_pair: Pubkey, 20 | ) -> Result>; 21 | 22 | fn get_bin_array_indexes_coverage(lower_bin_id: i32, upper_bin_id: i32) -> Result>; 23 | } 24 | 25 | impl BinArrayExtension for BinArray { 26 | fn get_bin_array_lower_upper_bin_id(index: i32) -> Result<(i32, i32)> { 27 | let lower_bin_id = index 28 | .checked_mul(MAX_BIN_PER_ARRAY as i32) 29 | .context("overflow")?; 30 | 31 | let upper_bin_id = lower_bin_id 32 | .checked_add(MAX_BIN_PER_ARRAY as i32) 33 | .context("overflow")? 34 | .checked_sub(1) 35 | .context("overflow")?; 36 | 37 | Ok((lower_bin_id, upper_bin_id)) 38 | } 39 | 40 | fn is_bin_id_within_range(&self, bin_id: i32) -> Result { 41 | let (lower_bin_id, upper_bin_id) = 42 | BinArray::get_bin_array_lower_upper_bin_id(self.index as i32)?; 43 | 44 | Ok(bin_id >= lower_bin_id && bin_id <= upper_bin_id) 45 | } 46 | 47 | fn get_bin_mut<'a>(&'a mut self, bin_id: i32) -> Result<&'a mut Bin> { 48 | Ok(&mut self.bins[self.get_bin_index_in_array(bin_id)?]) 49 | } 50 | 51 | fn get_bin<'a>(&'a self, bin_id: i32) -> Result<&'a Bin> { 52 | Ok(&self.bins[self.get_bin_index_in_array(bin_id)?]) 53 | } 54 | 55 | fn get_bin_index_in_array(&self, bin_id: i32) -> Result { 56 | ensure!(self.is_bin_id_within_range(bin_id)?, "Bin id out of range"); 57 | let (lower_bin_id, _) = BinArray::get_bin_array_lower_upper_bin_id(self.index as i32)?; 58 | let index = bin_id.checked_sub(lower_bin_id).context("overflow")?; 59 | Ok(index as usize) 60 | } 61 | 62 | fn bin_id_to_bin_array_index(bin_id: i32) -> Result { 63 | let (idx, rem) = bin_id.div_rem(&(MAX_BIN_PER_ARRAY as i32)); 64 | 65 | if bin_id.is_negative() && rem != 0 { 66 | Ok(idx.checked_sub(1).context("overflow")?) 67 | } else { 68 | Ok(idx) 69 | } 70 | } 71 | 72 | fn bin_id_to_bin_array_key(lb_pair: Pubkey, bin_id: i32) -> Result { 73 | let bin_array_index = Self::bin_id_to_bin_array_index(bin_id)?; 74 | Ok(derive_bin_array_pda(lb_pair, bin_array_index.into()).0) 75 | } 76 | 77 | fn get_bin_array_indexes_coverage(lower_bin_id: i32, upper_bin_id: i32) -> Result> { 78 | let lower_idx = BinArray::bin_id_to_bin_array_index(lower_bin_id)?; 79 | let upper_idx = BinArray::bin_id_to_bin_array_index(upper_bin_id)?; 80 | 81 | let mut indexes = vec![]; 82 | 83 | for i in lower_idx..=upper_idx { 84 | indexes.push(i); 85 | } 86 | 87 | Ok(indexes) 88 | } 89 | 90 | fn get_bin_array_account_metas_coverage( 91 | lower_bin_id: i32, 92 | upper_bin_id: i32, 93 | lb_pair: Pubkey, 94 | ) -> Result> { 95 | let bin_array_indexes = 96 | BinArray::get_bin_array_indexes_coverage(lower_bin_id, upper_bin_id)?; 97 | 98 | Ok(bin_array_indexes 99 | .into_iter() 100 | .map(|index| AccountMeta { 101 | pubkey: derive_bin_array_pda(lb_pair, index.into()).0, 102 | is_signer: false, 103 | is_writable: true, 104 | }) 105 | .collect()) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /cli/src/instructions/initialize_lb_pair.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_lang::AccountDeserialize; 3 | use anchor_spl::token_interface::Mint; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct InitLbPairParams { 7 | /// Preset parameter pubkey. Get the pubkey from list_all_binstep command. 8 | pub preset_parameter: Pubkey, 9 | /// Token X mint of the liquidity pair. Eg: BTC. This should be the base token. 10 | pub token_mint_x: Pubkey, 11 | /// Token Y mint of the liquidity pair. Eg: USDC. This should be the quote token. 12 | pub token_mint_y: Pubkey, 13 | /// The initial price of the liquidity pair. Eg: 24123.12312412 USDC per 1 BTC. 14 | pub initial_price: f64, 15 | } 16 | 17 | pub async fn execute_initialize_lb_pair + Clone>( 18 | params: InitLbPairParams, 19 | program: &Program, 20 | transaction_config: RpcSendTransactionConfig, 21 | ) -> Result { 22 | let InitLbPairParams { 23 | preset_parameter, 24 | token_mint_x, 25 | token_mint_y, 26 | initial_price, 27 | } = params; 28 | 29 | let rpc_client = program.rpc(); 30 | 31 | let mut accounts = rpc_client 32 | .get_multiple_accounts(&[token_mint_x, token_mint_y]) 33 | .await?; 34 | 35 | let token_mint_base_account = accounts[0].take().context("token_mint_base not found")?; 36 | let token_mint_quote_account = accounts[1].take().context("token_mint_quote not found")?; 37 | 38 | let token_mint_base = Mint::try_deserialize(&mut token_mint_base_account.data.as_ref())?; 39 | let token_mint_quote = Mint::try_deserialize(&mut token_mint_quote_account.data.as_ref())?; 40 | 41 | let price_per_lamport = price_per_token_to_per_lamport( 42 | initial_price, 43 | token_mint_base.decimals, 44 | token_mint_quote.decimals, 45 | ) 46 | .context("price_per_token_to_per_lamport overflow")?; 47 | 48 | let preset_parameter_state = rpc_client 49 | .get_account_and_deserialize(&preset_parameter, |account| { 50 | Ok(PresetParameter::try_deserialize( 51 | &mut account.data.as_ref(), 52 | )?) 53 | }) 54 | .await?; 55 | 56 | let bin_step = preset_parameter_state.bin_step; 57 | 58 | let computed_active_id = get_id_from_price(bin_step, &price_per_lamport, Rounding::Up) 59 | .context("get_id_from_price overflow")?; 60 | 61 | let (lb_pair, _bump) = derive_lb_pair_pda2( 62 | token_mint_x, 63 | token_mint_y, 64 | preset_parameter_state.bin_step, 65 | preset_parameter_state.base_factor, 66 | ); 67 | 68 | if program.rpc().get_account_data(&lb_pair).await.is_ok() { 69 | return Ok(lb_pair); 70 | } 71 | 72 | let (reserve_x, _bump) = derive_reserve_pda(token_mint_x, lb_pair); 73 | let (reserve_y, _bump) = derive_reserve_pda(token_mint_y, lb_pair); 74 | let (oracle, _bump) = derive_oracle_pda(lb_pair); 75 | 76 | let (event_authority, _bump) = derive_event_authority_pda(); 77 | 78 | let accounts = dlmm::client::accounts::InitializeLbPair { 79 | lb_pair, 80 | bin_array_bitmap_extension: Some(dlmm::ID), 81 | reserve_x, 82 | reserve_y, 83 | token_mint_x, 84 | token_mint_y, 85 | oracle, 86 | funder: program.payer(), 87 | token_program: token_mint_base_account.owner, 88 | preset_parameter, 89 | system_program: solana_sdk::system_program::ID, 90 | event_authority, 91 | program: dlmm::ID, 92 | rent: solana_sdk::sysvar::rent::ID, 93 | } 94 | .to_account_metas(None); 95 | 96 | let data = dlmm::client::args::InitializeLbPair { 97 | active_id: computed_active_id, 98 | bin_step, 99 | } 100 | .data(); 101 | 102 | let init_pair_ix = Instruction { 103 | program_id: dlmm::ID, 104 | data, 105 | accounts, 106 | }; 107 | 108 | let request_builder = program.request(); 109 | 110 | let signature = request_builder 111 | .instruction(init_pair_ix) 112 | .send_with_spinner_and_config(transaction_config) 113 | .await; 114 | 115 | println!("Initialize LB pair {lb_pair}. Signature: {signature:#?}"); 116 | 117 | signature?; 118 | 119 | Ok(lb_pair) 120 | } 121 | -------------------------------------------------------------------------------- /cli/src/instructions/show_pair.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_lang::AccountDeserialize; 3 | use anchor_spl::token_interface::Mint; 4 | use rust_decimal::prelude::*; 5 | use rust_decimal::Decimal; 6 | use solana_client::{ 7 | rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, 8 | rpc_filter::{Memcmp, RpcFilterType}, 9 | }; 10 | 11 | fn fee_rate_to_fee_pct(fee_rate: u128) -> Option { 12 | let fee_rate = Decimal::from_u128(fee_rate)?.checked_div(Decimal::from(FEE_PRECISION))?; 13 | fee_rate.checked_mul(Decimal::ONE_HUNDRED) 14 | } 15 | 16 | #[derive(Debug, Parser)] 17 | pub struct ShowPairParams { 18 | pub lb_pair: Pubkey, 19 | } 20 | 21 | pub async fn execute_show_pair + Clone>( 22 | params: ShowPairParams, 23 | program: &Program, 24 | ) -> Result<()> { 25 | let ShowPairParams { lb_pair } = params; 26 | let rpc_client = program.rpc(); 27 | 28 | let lb_pair_state: LbPair = rpc_client 29 | .get_account_and_deserialize(&lb_pair, |account| { 30 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 31 | }) 32 | .await?; 33 | 34 | let lb_pair_filter = RpcFilterType::Memcmp(Memcmp::new_base58_encoded(16, &lb_pair.to_bytes())); 35 | let account_config = RpcAccountInfoConfig { 36 | encoding: Some(UiAccountEncoding::Base64), 37 | ..Default::default() 38 | }; 39 | let config = RpcProgramAccountsConfig { 40 | filters: Some(vec![lb_pair_filter]), 41 | account_config, 42 | ..Default::default() 43 | }; 44 | 45 | let mut bin_arrays: Vec<(Pubkey, BinArray)> = rpc_client 46 | .get_program_accounts_with_config(&dlmm::ID, config) 47 | .await? 48 | .into_iter() 49 | .filter_map(|(key, account)| { 50 | let bin_array = bytemuck::pod_read_unaligned(&account.data[8..]); 51 | Some((key, bin_array)) 52 | }) 53 | .collect(); 54 | 55 | bin_arrays.sort_by(|a, b| a.1.index.cmp(&b.1.index)); 56 | 57 | println!("{:#?}", lb_pair_state); 58 | 59 | for (_, bin_array) in bin_arrays { 60 | let (mut lower_bin_id, _) = 61 | BinArray::get_bin_array_lower_upper_bin_id(bin_array.index as i32)?; 62 | for bin in bin_array.bins.iter() { 63 | let total_amount = bin.amount_x + bin.amount_y; 64 | if total_amount > 0 { 65 | println!( 66 | "Bin: {}, X: {}, Y: {}", 67 | lower_bin_id, bin.amount_x, bin.amount_y 68 | ); 69 | } 70 | lower_bin_id += 1; 71 | } 72 | } 73 | 74 | let mut accounts = rpc_client 75 | .get_multiple_accounts(&[lb_pair_state.token_x_mint, lb_pair_state.token_y_mint]) 76 | .await?; 77 | 78 | let token_x_account = accounts[0].take().context("token_mint_base not found")?; 79 | let token_y_account = accounts[1].take().context("token_mint_quote not found")?; 80 | 81 | let x_mint = Mint::try_deserialize(&mut token_x_account.data.as_ref())?; 82 | let y_mint = Mint::try_deserialize(&mut token_y_account.data.as_ref())?; 83 | 84 | let q64x64_price = get_price_from_id(lb_pair_state.active_id, lb_pair_state.bin_step)?; 85 | let decimal_price_per_lamport = 86 | q64x64_price_to_decimal(q64x64_price).context("q64x64 price to decimal overflow")?; 87 | 88 | let token_price = price_per_lamport_to_price_per_token( 89 | decimal_price_per_lamport 90 | .to_f64() 91 | .context("Decimal conversion to f64 fail")?, 92 | x_mint.decimals, 93 | y_mint.decimals, 94 | ) 95 | .context("price_per_lamport_to_price_per_token overflow")?; 96 | 97 | let base_fee_rate = fee_rate_to_fee_pct(lb_pair_state.get_total_fee()?) 98 | .context("get_total_fee convert to percentage overflow")?; 99 | let variable_fee_rate = fee_rate_to_fee_pct(lb_pair_state.get_variable_fee()?) 100 | .context("get_total_fee convert to percentage overflow")?; 101 | let current_fee_rate = fee_rate_to_fee_pct(lb_pair_state.get_total_fee()?) 102 | .context("get_total_fee convert to percentage overflow")?; 103 | 104 | println!("Current price {}", token_price); 105 | println!("Base fee rate {}%", base_fee_rate); 106 | println!("Volatile fee rate {}%", variable_fee_rate); 107 | println!("Current fee rate {}%", current_fee_rate); 108 | 109 | Ok(()) 110 | } 111 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/initialize_preset_parameter.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::Discriminator; 2 | use commons::dlmm::{accounts::PresetParameter2, types::InitPresetParameters2Ix}; 3 | use solana_client::{ 4 | rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, 5 | rpc_filter::{Memcmp, RpcFilterType}, 6 | }; 7 | 8 | use crate::*; 9 | 10 | #[derive(Debug, Parser)] 11 | pub struct InitPresetParameters { 12 | /// Bin step. Represent the price increment / decrement. 13 | pub bin_step: u16, 14 | /// Used for base fee calculation. base_fee_rate = base_factor * bin_step 15 | pub base_factor: u16, 16 | /// Filter period determine high frequency trading time window. 17 | pub filter_period: u16, 18 | /// Decay period determine when the volatile fee start decay / decrease. 19 | pub decay_period: u16, 20 | /// Reduction factor controls the volatile fee rate decrement rate. 21 | pub reduction_factor: u16, 22 | /// Used to scale the variable fee component depending on the dynamic of the market 23 | pub variable_fee_control: u32, 24 | /// Maximum number of bin crossed can be accumulated. Used to cap volatile fee rate. 25 | pub max_volatility_accumulator: u32, 26 | /// Portion of swap fees retained by the protocol by controlling protocol_share parameter. protocol_swap_fee = protocol_share * total_swap_fee 27 | pub protocol_share: u16, 28 | /// Base fee power factor 29 | pub base_fee_power_factor: u8, 30 | } 31 | 32 | pub async fn execute_initialize_preset_parameter + Clone>( 33 | params: InitPresetParameters, 34 | program: &Program, 35 | transaction_config: RpcSendTransactionConfig, 36 | ) -> Result { 37 | let InitPresetParameters { 38 | base_factor, 39 | bin_step, 40 | decay_period, 41 | filter_period, 42 | max_volatility_accumulator, 43 | protocol_share, 44 | reduction_factor, 45 | variable_fee_control, 46 | base_fee_power_factor, 47 | } = params; 48 | 49 | let rpc_client = program.rpc(); 50 | 51 | let preset_parameter_v2_count = rpc_client 52 | .get_program_accounts_with_config( 53 | &dlmm::ID, 54 | RpcProgramAccountsConfig { 55 | filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( 56 | 0, 57 | &PresetParameter2::DISCRIMINATOR, 58 | ))]), 59 | account_config: RpcAccountInfoConfig { 60 | encoding: Some(UiAccountEncoding::Base64), 61 | data_slice: Some(UiDataSliceConfig { 62 | offset: 0, 63 | length: 0, 64 | }), 65 | ..Default::default() 66 | }, 67 | ..Default::default() 68 | }, 69 | ) 70 | .await? 71 | .len(); 72 | 73 | let index = preset_parameter_v2_count as u16; 74 | 75 | let (preset_parameter, _bump) = 76 | derive_preset_parameter_pda_v2(preset_parameter_v2_count as u16); 77 | 78 | let accounts = dlmm::client::accounts::InitializePresetParameter2 { 79 | preset_parameter, 80 | admin: program.payer(), 81 | system_program: solana_sdk::system_program::ID, 82 | } 83 | .to_account_metas(None); 84 | 85 | let data = dlmm::client::args::InitializePresetParameter2 { 86 | ix: InitPresetParameters2Ix { 87 | index, 88 | bin_step, 89 | base_factor, 90 | filter_period, 91 | decay_period, 92 | reduction_factor, 93 | variable_fee_control, 94 | max_volatility_accumulator, 95 | protocol_share, 96 | base_fee_power_factor, 97 | }, 98 | } 99 | .data(); 100 | 101 | let init_preset_param_ix = Instruction { 102 | program_id: dlmm::ID, 103 | accounts, 104 | data, 105 | }; 106 | 107 | let request_builder = program.request(); 108 | let signature = request_builder 109 | .instruction(init_preset_param_ix) 110 | .send_with_spinner_and_config(transaction_config) 111 | .await; 112 | 113 | println!( 114 | "Initialize preset parameter {}. Signature: {signature:#?}", 115 | preset_parameter 116 | ); 117 | 118 | signature?; 119 | 120 | Ok(preset_parameter) 121 | } 122 | -------------------------------------------------------------------------------- /cli/src/math.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use commons::dlmm::types::Rounding; 3 | use commons::{BASIS_POINT_MAX, SCALE_OFFSET}; 4 | use rust_decimal::MathematicalOps; 5 | use rust_decimal::{ 6 | prelude::{FromPrimitive, ToPrimitive}, 7 | Decimal, 8 | }; 9 | 10 | pub fn compute_base_factor_from_fee_bps(bin_step: u16, fee_bps: u16) -> Result<(u16, u8)> { 11 | let computed_base_factor = fee_bps as f64 * 10_000.0f64 / bin_step as f64; 12 | 13 | if computed_base_factor > u16::MAX as f64 { 14 | let mut truncated_base_factor = computed_base_factor; 15 | let mut base_power_factor = 0u8; 16 | loop { 17 | if truncated_base_factor < u16::MAX as f64 { 18 | break; 19 | } 20 | 21 | let remainder = truncated_base_factor % 10.0; 22 | if remainder == 0.0 { 23 | base_power_factor += 1; 24 | truncated_base_factor /= 10.0; 25 | } else { 26 | return Err(anyhow!("have decimals")); 27 | } 28 | } 29 | 30 | Ok((truncated_base_factor as u16, base_power_factor)) 31 | } else { 32 | // Sanity check 33 | let casted_base_factor = computed_base_factor as u16 as f64; 34 | if casted_base_factor != computed_base_factor { 35 | if casted_base_factor == u16::MAX as f64 { 36 | return Err(anyhow!("overflow")); 37 | } 38 | 39 | if casted_base_factor == 0.0f64 { 40 | return Err(anyhow!("underflow")); 41 | } 42 | 43 | if computed_base_factor.fract() != 0.0 { 44 | return Err(anyhow!("have decimals")); 45 | } 46 | 47 | return Err(anyhow!("unknown error")); 48 | } 49 | 50 | Ok((computed_base_factor as u16, 0u8)) 51 | } 52 | } 53 | 54 | pub fn get_precise_id_from_price(bin_step: u16, price: &Decimal) -> Option { 55 | let bps = Decimal::from_u16(bin_step)?.checked_div(Decimal::from_i32(BASIS_POINT_MAX)?)?; 56 | let base = Decimal::ONE.checked_add(bps)?; 57 | 58 | let id = price.log10().checked_div(base.log10())?.to_f64()?; 59 | let trimmed_id = id as i32; 60 | let trimmed_id_f64 = trimmed_id as f64; 61 | 62 | if trimmed_id_f64 == id { 63 | id.to_i32() 64 | } else { 65 | None 66 | } 67 | } 68 | 69 | /// Calculate the bin id based on price. If the bin id is in between 2 bins, it will round up. 70 | pub fn get_id_from_price(bin_step: u16, price: &Decimal, rounding: Rounding) -> Option { 71 | let bps = Decimal::from_u16(bin_step)?.checked_div(Decimal::from_i32(BASIS_POINT_MAX)?)?; 72 | let base = Decimal::ONE.checked_add(bps)?; 73 | 74 | let id = match rounding { 75 | Rounding::Down => price.log10().checked_div(base.log10())?.floor(), 76 | Rounding::Up => price.log10().checked_div(base.log10())?.ceil(), 77 | }; 78 | 79 | id.to_i32() 80 | } 81 | 82 | /// Convert Q64xQ64 price to human readable decimal. This is price per lamport. 83 | pub fn q64x64_price_to_decimal(q64x64_price: u128) -> Option { 84 | let q_price = Decimal::from_u128(q64x64_price)?; 85 | let scale_off = Decimal::TWO.powu(SCALE_OFFSET.into()); 86 | q_price.checked_div(scale_off) 87 | } 88 | 89 | /// price_per_lamport = price_per_token * 10 ** quote_token_decimal / 10 ** base_token_decimal 90 | pub fn price_per_token_to_per_lamport( 91 | price_per_token: f64, 92 | base_token_decimal: u8, 93 | quote_token_decimal: u8, 94 | ) -> Option { 95 | let price_per_token = Decimal::from_f64(price_per_token)?; 96 | price_per_token 97 | .checked_mul(Decimal::TEN.powu(quote_token_decimal.into()))? 98 | .checked_div(Decimal::TEN.powu(base_token_decimal.into())) 99 | } 100 | 101 | /// price_per_token = price_per_lamport * 10 ** base_token_decimal / 10 ** quote_token_decimal, Solve for price_per_lamport 102 | pub fn price_per_lamport_to_price_per_token( 103 | price_per_lamport: f64, 104 | base_token_decimal: u8, 105 | quote_token_decimal: u8, 106 | ) -> Option { 107 | let one_ui_base_token_amount = Decimal::TEN.powu(base_token_decimal.into()); 108 | let one_ui_quote_token_amount = Decimal::TEN.powu(quote_token_decimal.into()); 109 | let price_per_lamport = Decimal::from_f64(price_per_lamport)?; 110 | 111 | one_ui_base_token_amount 112 | .checked_mul(price_per_lamport)? 113 | .checked_div(one_ui_quote_token_amount) 114 | } 115 | -------------------------------------------------------------------------------- /market_making/src/bin_array_manager.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | pub struct BinArrayManager<'a> { 4 | pub bin_arrays: &'a [BinArray], 5 | } 6 | 7 | impl<'a> BinArrayManager<'a> { 8 | pub fn get_bin(&self, bin_id: i32) -> Result<&Bin> { 9 | let bin_array_idx = BinArray::bin_id_to_bin_array_index(bin_id)?; 10 | match self 11 | .bin_arrays 12 | .iter() 13 | .find(|ba| ba.index == bin_array_idx as i64) 14 | { 15 | Some(bin_array) => Ok(bin_array.get_bin(bin_id)?), 16 | None => Err(anyhow::Error::msg("Cannot get bin")), 17 | } 18 | } 19 | 20 | pub fn get_lower_upper_bin_id(&self) -> Result<(i32, i32)> { 21 | let lower_bin_array_idx = self.bin_arrays[0].index as i32; 22 | let upper_bin_array_idx = self.bin_arrays[self.bin_arrays.len() - 1].index as i32; 23 | 24 | let lower_bin_id = lower_bin_array_idx 25 | .checked_mul(MAX_BIN_PER_ARRAY as i32) 26 | .context("math is overflow")?; 27 | 28 | let upper_bin_id = upper_bin_array_idx 29 | .checked_mul(MAX_BIN_PER_ARRAY as i32) 30 | .context("math is overflow")? 31 | .checked_add(MAX_BIN_PER_ARRAY as i32) 32 | .context("math is overflow")? 33 | .checked_sub(1) 34 | .context("math is overflow")?; 35 | 36 | Ok((lower_bin_id, upper_bin_id)) 37 | } 38 | 39 | /// Update reward + fee earning 40 | pub fn get_total_fee_pending(&self, position: &PositionV2) -> Result<(u64, u64)> { 41 | let (bin_arrays_lower_bin_id, bin_arrays_upper_bin_id) = self.get_lower_upper_bin_id()?; 42 | 43 | if position.lower_bin_id < bin_arrays_lower_bin_id 44 | && position.upper_bin_id > bin_arrays_upper_bin_id 45 | { 46 | return Err(anyhow::Error::msg("Bin array is not correct")); 47 | } 48 | 49 | let mut total_fee_x = 0u64; 50 | let mut total_fee_y = 0u64; 51 | for bin_id in position.lower_bin_id..=position.upper_bin_id { 52 | let bin = self.get_bin(bin_id)?; 53 | let (fee_x_pending, fee_y_pending) = 54 | BinArrayManager::get_fee_pending_for_a_bin(position, bin_id, &bin)?; 55 | total_fee_x = fee_x_pending 56 | .checked_add(total_fee_x) 57 | .context("math is overflow")?; 58 | total_fee_y = fee_y_pending 59 | .checked_add(total_fee_y) 60 | .context("math is overflow")?; 61 | } 62 | 63 | Ok((total_fee_x, total_fee_y)) 64 | } 65 | 66 | fn get_fee_pending_for_a_bin( 67 | position: &PositionV2, 68 | bin_id: i32, 69 | bin: &Bin, 70 | ) -> Result<(u64, u64)> { 71 | ensure!( 72 | bin_id >= position.lower_bin_id && bin_id <= position.upper_bin_id, 73 | "Bin is not within the position" 74 | ); 75 | 76 | let idx = bin_id - position.lower_bin_id; 77 | 78 | let fee_infos = position.fee_infos[idx as usize]; 79 | let liquidity_share_in_bin = position.liquidity_shares[idx as usize]; 80 | 81 | let fee_x_per_token_stored = bin.fee_amount_x_per_token_stored; 82 | 83 | let liquidity_share_in_bin_downscaled = liquidity_share_in_bin 84 | .checked_shr(SCALE_OFFSET.into()) 85 | .context("math is overflow")?; 86 | 87 | let new_fee_x: u64 = safe_mul_shr_cast( 88 | liquidity_share_in_bin_downscaled, 89 | fee_x_per_token_stored 90 | .checked_sub(fee_infos.fee_x_per_token_complete) 91 | .context("math is overflow")?, 92 | SCALE_OFFSET, 93 | Rounding::Down, 94 | )?; 95 | 96 | let fee_x_pending = new_fee_x 97 | .checked_add(fee_infos.fee_x_pending) 98 | .context("math is overflow")?; 99 | 100 | let fee_y_per_token_stored = bin.fee_amount_y_per_token_stored; 101 | 102 | let new_fee_y: u64 = safe_mul_shr_cast( 103 | liquidity_share_in_bin_downscaled, 104 | fee_y_per_token_stored 105 | .checked_sub(fee_infos.fee_y_per_token_complete) 106 | .context("math is overflow")?, 107 | SCALE_OFFSET, 108 | Rounding::Down, 109 | )?; 110 | 111 | let fee_y_pending = new_fee_y 112 | .checked_add(fee_infos.fee_y_pending) 113 | .context("math is overflow")?; 114 | 115 | Ok((fee_x_pending, fee_y_pending)) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/u64xu64_math.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import { SCALE_OFFSET } from "../constants"; 3 | 4 | const MAX_EXPONENTIAL = new BN(0x80000); 5 | 6 | export const ONE = new BN(1).shln(SCALE_OFFSET); 7 | const MAX = new BN(2).pow(new BN(128)).sub(new BN(1)); 8 | 9 | export function pow(base: BN, exp: BN): BN { 10 | let invert = exp.isNeg(); 11 | 12 | if (exp.isZero()) { 13 | return ONE; 14 | } 15 | 16 | exp = invert ? exp.abs() : exp; 17 | 18 | if (exp.gt(MAX_EXPONENTIAL)) { 19 | return new BN(0); 20 | } 21 | 22 | let squaredBase = base; 23 | let result = ONE; 24 | 25 | if (squaredBase.gte(result)) { 26 | squaredBase = MAX.div(squaredBase); 27 | invert = !invert; 28 | } 29 | 30 | if (!exp.and(new BN(0x1)).isZero()) { 31 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 32 | } 33 | 34 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 35 | 36 | if (!exp.and(new BN(0x2)).isZero()) { 37 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 38 | } 39 | 40 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 41 | 42 | if (!exp.and(new BN(0x4)).isZero()) { 43 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 44 | } 45 | 46 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 47 | 48 | if (!exp.and(new BN(0x8)).isZero()) { 49 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 50 | } 51 | 52 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 53 | 54 | if (!exp.and(new BN(0x10)).isZero()) { 55 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 56 | } 57 | 58 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 59 | 60 | if (!exp.and(new BN(0x20)).isZero()) { 61 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 62 | } 63 | 64 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 65 | 66 | if (!exp.and(new BN(0x40)).isZero()) { 67 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 68 | } 69 | 70 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 71 | 72 | if (!exp.and(new BN(0x80)).isZero()) { 73 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 74 | } 75 | 76 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 77 | 78 | if (!exp.and(new BN(0x100)).isZero()) { 79 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 80 | } 81 | 82 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 83 | 84 | if (!exp.and(new BN(0x200)).isZero()) { 85 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 86 | } 87 | 88 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 89 | 90 | if (!exp.and(new BN(0x400)).isZero()) { 91 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 92 | } 93 | 94 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 95 | 96 | if (!exp.and(new BN(0x800)).isZero()) { 97 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 98 | } 99 | 100 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 101 | 102 | if (!exp.and(new BN(0x1000)).isZero()) { 103 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 104 | } 105 | 106 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 107 | 108 | if (!exp.and(new BN(0x2000)).isZero()) { 109 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 110 | } 111 | 112 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 113 | 114 | if (!exp.and(new BN(0x4000)).isZero()) { 115 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 116 | } 117 | 118 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 119 | 120 | if (!exp.and(new BN(0x8000)).isZero()) { 121 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 122 | } 123 | 124 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 125 | 126 | if (!exp.and(new BN(0x10000)).isZero()) { 127 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 128 | } 129 | 130 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 131 | 132 | if (!exp.and(new BN(0x20000)).isZero()) { 133 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 134 | } 135 | 136 | squaredBase = squaredBase.mul(squaredBase).shrn(SCALE_OFFSET); 137 | 138 | if (!exp.and(new BN(0x40000)).isZero()) { 139 | result = result.mul(squaredBase).shrn(SCALE_OFFSET); 140 | } 141 | 142 | if (result.isZero()) { 143 | return new BN(0); 144 | } 145 | 146 | if (invert) { 147 | result = MAX.div(result); 148 | } 149 | 150 | return result; 151 | } 152 | -------------------------------------------------------------------------------- /market_making/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_spl::associated_token::get_associated_token_address_with_program_id; 3 | use commitment_config::CommitmentConfig; 4 | use dlmm::events::Swap as SwapEvent; 5 | use solana_client::rpc_response::{Response, RpcSimulateTransactionResult}; 6 | use solana_sdk::instruction::Instruction; 7 | use solana_transaction_status::option_serializer::OptionSerializer; 8 | use solana_transaction_status::{UiInstruction, UiTransactionEncoding}; 9 | use spl_associated_token_account::instruction::create_associated_token_account; 10 | use std::time::*; 11 | use transaction::Transaction; 12 | 13 | pub fn get_epoch_sec() -> u64 { 14 | SystemTime::now() 15 | .duration_since(UNIX_EPOCH) 16 | .unwrap() 17 | .as_secs() 18 | } 19 | 20 | pub async fn get_or_create_ata( 21 | rpc_client: &RpcClient, 22 | token_mint: Pubkey, 23 | program_id: Pubkey, 24 | wallet_address: Pubkey, 25 | payer: &Keypair, 26 | ) -> Result { 27 | let user_ata = 28 | get_associated_token_address_with_program_id(&wallet_address, &token_mint, &program_id); 29 | 30 | let user_ata_exists = rpc_client.get_account(&user_ata).await.is_ok(); 31 | 32 | if !user_ata_exists { 33 | let create_ata_ix = create_associated_token_account( 34 | &payer.pubkey(), 35 | &wallet_address, 36 | &token_mint, 37 | &program_id, 38 | ); 39 | 40 | let signature = send_tx(&[create_ata_ix], rpc_client, &[], payer).await?; 41 | println!("Create ata {token_mint} {wallet_address} {signature}"); 42 | } 43 | 44 | Ok(user_ata) 45 | } 46 | 47 | pub fn get_transaction_config() -> RpcSendTransactionConfig { 48 | let commitment_config = CommitmentConfig::confirmed(); 49 | 50 | RpcSendTransactionConfig { 51 | skip_preflight: false, 52 | preflight_commitment: Some(commitment_config.commitment), 53 | encoding: None, 54 | max_retries: None, 55 | min_context_slot: None, 56 | } 57 | } 58 | 59 | pub async fn send_tx( 60 | instructions: &[Instruction], 61 | rpc_client: &RpcClient, 62 | keypairs: &[&Keypair], 63 | payer: &Keypair, 64 | ) -> Result { 65 | let latest_blockhash = rpc_client.get_latest_blockhash().await?; 66 | 67 | let mut tx = Transaction::new_signed_with_payer( 68 | instructions, 69 | Some(&payer.pubkey()), 70 | keypairs, 71 | latest_blockhash, 72 | ); 73 | tx.sign(&[payer], latest_blockhash); 74 | 75 | let signature = rpc_client.send_and_confirm_transaction(&tx).await?; 76 | 77 | Ok(signature) 78 | } 79 | 80 | pub async fn simulate_transaction( 81 | instructions: &[Instruction], 82 | rpc_client: &RpcClient, 83 | keypairs: &[&Keypair], 84 | payer: Pubkey, 85 | ) -> Result> { 86 | let latest_blockhash = rpc_client.get_latest_blockhash().await?; 87 | 88 | let tx = 89 | Transaction::new_signed_with_payer(&instructions, Some(&payer), keypairs, latest_blockhash); 90 | let simulation = rpc_client.simulate_transaction(&tx).await?; 91 | 92 | Ok(simulation) 93 | } 94 | 95 | pub async fn parse_swap_event(rpc_client: &RpcClient, signature: Signature) -> Result { 96 | let tx = rpc_client 97 | .get_transaction_with_config( 98 | &signature, 99 | RpcTransactionConfig { 100 | encoding: Some(UiTransactionEncoding::Base64), 101 | commitment: Some(CommitmentConfig::finalized()), 102 | max_supported_transaction_version: Some(0), 103 | }, 104 | ) 105 | .await?; 106 | 107 | if let Some(meta) = &tx.transaction.meta { 108 | if let OptionSerializer::Some(inner_instructions) = meta.inner_instructions.as_ref() { 109 | let inner_ixs = inner_instructions 110 | .iter() 111 | .flat_map(|ix| ix.instructions.as_slice()); 112 | 113 | for ix in inner_ixs { 114 | match ix { 115 | UiInstruction::Compiled(compiled_ix) => { 116 | if let std::result::Result::Ok(ix_data) = 117 | bs58::decode(compiled_ix.data.as_str()).into_vec() 118 | { 119 | return Ok(SwapEvent::deserialize(&mut ix_data.as_ref())?); 120 | }; 121 | } 122 | _ => {} 123 | } 124 | } 125 | } 126 | } 127 | Err(Error::msg("Cannot find swap event")) 128 | } 129 | -------------------------------------------------------------------------------- /ts-client/src/test/esm_import.test.ts: -------------------------------------------------------------------------------- 1 | import DLMM from "../../dist"; 2 | 3 | describe("ESM default import tests", () => { 4 | describe("default import structure tests", () => { 5 | test("should import DLMM as a direct reference, not a namespace", () => { 6 | expect(DLMM).toBeDefined(); 7 | expect(typeof DLMM).toBe("function"); 8 | }); 9 | 10 | test("should be a class constructor", () => { 11 | expect(DLMM.name).toBe("DLMM"); 12 | expect(DLMM.prototype).toBeDefined(); 13 | expect(DLMM.prototype.constructor).toBe(DLMM); 14 | }); 15 | 16 | test("should NOT require .default to access the class", () => { 17 | expect((DLMM as any).default).toBeUndefined(); 18 | }); 19 | }); 20 | 21 | describe("static methods tests", () => { 22 | test("should have getAllLbPairPositionsByUser static method directly accessible", () => { 23 | expect(DLMM.getAllLbPairPositionsByUser).toBeDefined(); 24 | expect(typeof DLMM.getAllLbPairPositionsByUser).toBe("function"); 25 | }); 26 | 27 | test("should have createLbPair static method directly accessible", () => { 28 | expect(DLMM.createLbPair).toBeDefined(); 29 | expect(typeof DLMM.createLbPair).toBe("function"); 30 | }); 31 | 32 | test("should have createCustomizablePermissionlessLbPair static method directly accessible", () => { 33 | expect(DLMM.createCustomizablePermissionlessLbPair).toBeDefined(); 34 | expect(typeof DLMM.createCustomizablePermissionlessLbPair).toBe( 35 | "function" 36 | ); 37 | }); 38 | 39 | test("should have getLbPairs static method directly accessible", () => { 40 | expect(DLMM.getLbPairs).toBeDefined(); 41 | expect(typeof DLMM.getLbPairs).toBe("function"); 42 | }); 43 | 44 | test("should have getAllLbPairPositionsByUser static method directly accessible", () => { 45 | expect(DLMM.getAllLbPairPositionsByUser).toBeDefined(); 46 | expect(typeof DLMM.getAllLbPairPositionsByUser).toBe("function"); 47 | }); 48 | }); 49 | 50 | describe("typescript type tests", () => { 51 | test("should be compatible with TypeScript strict mode", () => { 52 | const DLMMClass: typeof DLMM = DLMM; 53 | expect(DLMMClass).toBe(DLMM); 54 | }); 55 | 56 | test("should support ESM import syntax in type definitions", () => { 57 | const importedDLMM: typeof DLMM = DLMM; 58 | expect(importedDLMM.name).toBe("DLMM"); 59 | }); 60 | }); 61 | 62 | describe("usage tests", () => { 63 | test("should allow direct usage without .default workaround", () => { 64 | expect(() => { 65 | const method = DLMM.getAllLbPairPositionsByUser; 66 | expect(method).toBeDefined(); 67 | }).not.toThrow(); 68 | }); 69 | 70 | test("should support TypeScript 5+ bundler module resolution", () => { 71 | expect(DLMM).toBeDefined(); 72 | expect(typeof DLMM.create).toBe("function"); 73 | }); 74 | }); 75 | 76 | describe("backward compatibility tests", () => { 77 | test("should work with traditional module resolution", () => { 78 | expect(DLMM).toBeDefined(); 79 | expect(DLMM.name).toBe("DLMM"); 80 | }); 81 | 82 | test("should have consistent behavior across import styles", () => { 83 | const hasStaticMethods = 84 | typeof DLMM.create === "function" && 85 | typeof DLMM.getAllLbPairPositionsByUser === "function"; 86 | expect(hasStaticMethods).toBe(true); 87 | }); 88 | }); 89 | 90 | describe("export validation tests", () => { 91 | test("should export DLMM as the default export", () => { 92 | expect(DLMM).toBeDefined(); 93 | expect(DLMM.constructor.name).toBe("Function"); 94 | }); 95 | 96 | test("should not wrap the class in an additional object", () => { 97 | expect(DLMM.name).toBe("DLMM"); 98 | }); 99 | }); 100 | 101 | describe("error prevention tests", () => { 102 | test("should prevent 'DLMM.getAllLbPairPositionsByUser is not a function' error", () => { 103 | expect(typeof DLMM.getAllLbPairPositionsByUser).toBe("function"); 104 | 105 | expect( 106 | (DLMM as any).default?.getAllLbPairPositionsByUser 107 | ).toBeUndefined(); 108 | }); 109 | 110 | test("should not require DLMM.default.createLbPair() workaround", () => { 111 | expect(typeof DLMM.createLbPair).toBe("function"); 112 | 113 | expect((DLMM as any).default).toBeUndefined(); 114 | }); 115 | 116 | test("should work with modern ESM tooling (tsx, esbuild, vite)", () => { 117 | expect(DLMM).toBeDefined(); 118 | expect(typeof DLMM).toBe("function"); 119 | expect(DLMM.name).toBe("DLMM"); 120 | }); 121 | }); 122 | }); 123 | -------------------------------------------------------------------------------- /cli/src/instructions/initialize_lb_pair2.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_lang::AccountDeserialize; 3 | use anchor_spl::token_interface::Mint; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct InitLbPair2Params { 7 | /// Preset parameter pubkey. Get the pubkey from list_all_binstep command. 8 | pub preset_parameter: Pubkey, 9 | /// Token X mint of the liquidity pair. Eg: BTC. This should be the base token. 10 | pub token_mint_x: Pubkey, 11 | /// Token Y mint of the liquidity pair. Eg: USDC. This should be the quote token. 12 | pub token_mint_y: Pubkey, 13 | /// The initial price of the liquidity pair. Eg: 24123.12312412 USDC per 1 BTC. 14 | pub initial_price: f64, 15 | } 16 | 17 | pub async fn execute_initialize_lb_pair2 + Clone>( 18 | params: InitLbPair2Params, 19 | program: &Program, 20 | transaction_config: RpcSendTransactionConfig, 21 | ) -> Result { 22 | let InitLbPair2Params { 23 | preset_parameter, 24 | token_mint_x, 25 | token_mint_y, 26 | initial_price, 27 | } = params; 28 | 29 | let rpc_client = program.rpc(); 30 | 31 | let mut accounts = rpc_client 32 | .get_multiple_accounts(&[token_mint_x, token_mint_y]) 33 | .await?; 34 | 35 | let token_mint_base_account = accounts[0].take().context("token_mint_base not found")?; 36 | let token_mint_quote_account = accounts[1].take().context("token_mint_quote not found")?; 37 | 38 | let token_mint_base = Mint::try_deserialize(&mut token_mint_base_account.data.as_ref())?; 39 | let token_mint_quote = Mint::try_deserialize(&mut token_mint_quote_account.data.as_ref())?; 40 | 41 | let price_per_lamport = price_per_token_to_per_lamport( 42 | initial_price, 43 | token_mint_base.decimals, 44 | token_mint_quote.decimals, 45 | ) 46 | .context("price_per_token_to_per_lamport overflow")?; 47 | 48 | let preset_parameter_state: PresetParameter2 = rpc_client 49 | .get_account_and_deserialize(&preset_parameter, |account| { 50 | Ok(bytemuck::pod_read_unaligned(&account.data[8..])) 51 | }) 52 | .await?; 53 | 54 | let bin_step = preset_parameter_state.bin_step; 55 | 56 | let computed_active_id = get_id_from_price(bin_step, &price_per_lamport, Rounding::Up) 57 | .context("get_id_from_price overflow")?; 58 | 59 | let (lb_pair, _bump) = 60 | derive_lb_pair_with_preset_parameter_key(preset_parameter, token_mint_x, token_mint_y); 61 | 62 | if program.rpc().get_account_data(&lb_pair).await.is_ok() { 63 | return Ok(lb_pair); 64 | } 65 | 66 | let (reserve_x, _bump) = derive_reserve_pda(token_mint_x, lb_pair); 67 | let (reserve_y, _bump) = derive_reserve_pda(token_mint_y, lb_pair); 68 | let (oracle, _bump) = derive_oracle_pda(lb_pair); 69 | 70 | let (event_authority, _bump) = derive_event_authority_pda(); 71 | let (token_badge_x, _bump) = derive_token_badge_pda(token_mint_x); 72 | let (token_badge_y, _bump) = derive_token_badge_pda(token_mint_y); 73 | 74 | let accounts = rpc_client 75 | .get_multiple_accounts(&[token_badge_x, token_badge_y]) 76 | .await?; 77 | 78 | let token_badge_x = accounts[0] 79 | .as_ref() 80 | .map(|_| token_badge_x) 81 | .or(Some(dlmm::ID)); 82 | 83 | let token_badge_y = accounts[1] 84 | .as_ref() 85 | .map(|_| token_badge_y) 86 | .or(Some(dlmm::ID)); 87 | 88 | let accounts = dlmm::client::accounts::InitializeLbPair2 { 89 | lb_pair, 90 | bin_array_bitmap_extension: Some(dlmm::ID), 91 | reserve_x, 92 | reserve_y, 93 | token_mint_x, 94 | token_mint_y, 95 | oracle, 96 | funder: program.payer(), 97 | token_badge_x, 98 | token_badge_y, 99 | token_program_x: token_mint_base_account.owner, 100 | token_program_y: token_mint_quote_account.owner, 101 | preset_parameter, 102 | system_program: solana_sdk::system_program::ID, 103 | event_authority, 104 | program: dlmm::ID, 105 | } 106 | .to_account_metas(None); 107 | 108 | let data = dlmm::client::args::InitializeLbPair2 { 109 | params: InitializeLbPair2Params { 110 | active_id: computed_active_id, 111 | padding: [0u8; 96], 112 | }, 113 | } 114 | .data(); 115 | 116 | let init_pair_ix = Instruction { 117 | program_id: dlmm::ID, 118 | data, 119 | accounts, 120 | }; 121 | 122 | let request_builder = program.request(); 123 | 124 | let signature = request_builder 125 | .instruction(init_pair_ix) 126 | .send_with_spinner_and_config(transaction_config) 127 | .await; 128 | 129 | println!("Initialize LB pair2 {lb_pair}. Signature: {signature:#?}"); 130 | 131 | signature?; 132 | 133 | Ok(lb_pair) 134 | } 135 | -------------------------------------------------------------------------------- /ts-client/src/test/esm_module.test.ts: -------------------------------------------------------------------------------- 1 | import DLMM from "../../dist"; 2 | 3 | describe("ESM module compatibility tests", () => { 4 | describe("esm module resolution tests", () => { 5 | test("should support ES6 default import", () => { 6 | expect(DLMM).toBeDefined(); 7 | expect(typeof DLMM).toBe("function"); 8 | expect(DLMM.name).toBe("DLMM"); 9 | }); 10 | 11 | test("should work with TypeScript moduleResolution: bundler", () => { 12 | const dlmmClass = DLMM; 13 | expect(dlmmClass.getAllLbPairPositionsByUser).toBeDefined(); 14 | }); 15 | 16 | test("should work with package.json type: module", () => { 17 | expect(DLMM).toBeDefined(); 18 | expect(DLMM.prototype).toBeDefined(); 19 | }); 20 | }); 21 | 22 | describe("esm api surface tests", () => { 23 | test("should not have double-wrapping in ESM import", () => { 24 | expect(DLMM).toBeDefined(); 25 | expect(typeof DLMM).toBe("function"); // not double-wrapped 26 | }); 27 | 28 | test("should provide consistent API for ESM consumers", () => { 29 | const requiredMethods = [ 30 | "createLbPair", 31 | "createCustomizablePermissionlessLbPair", 32 | "getAllLbPairPositionsByUser", 33 | "getLbPairs", 34 | ]; 35 | 36 | requiredMethods.forEach((method) => { 37 | expect(DLMM).toHaveProperty(method); 38 | expect(typeof (DLMM as any)[method]).toBe("function"); 39 | }); 40 | }); 41 | }); 42 | 43 | describe("modern module tests", () => { 44 | test("should work with tsx", () => { 45 | expect(DLMM).toBeDefined(); 46 | expect(DLMM.name).toBe("DLMM"); 47 | }); 48 | 49 | test("should work with esbuild", () => { 50 | expect(typeof DLMM).toBe("function"); 51 | expect(DLMM.prototype.constructor).toBe(DLMM); 52 | }); 53 | 54 | test("should work with modern bundlers (webpack, vite, rollup)", () => { 55 | expect(DLMM).toBeDefined(); 56 | expect(typeof DLMM.createLbPair).toBe("function"); 57 | }); 58 | }); 59 | 60 | describe("Package.json Exports Field", () => { 61 | test("should have correct export structure for dual module support", () => { 62 | // The package.json should have: 63 | // "exports": { 64 | // ".": { 65 | // "import": "./dist/index.mjs", 66 | // "require": "./dist/index.js" 67 | // } 68 | // } 69 | 70 | expect(DLMM).toBeDefined(); 71 | expect(typeof DLMM).toBe("function"); 72 | }); 73 | }); 74 | 75 | describe("TypeScript Version Compatibility", () => { 76 | test("should work with TypeScript 5.9.3 (from issue)", () => { 77 | const dlmm: typeof DLMM = DLMM; 78 | expect(dlmm.name).toBe("DLMM"); 79 | }); 80 | 81 | test("should work with TypeScript 5+ strict mode", () => { 82 | const staticMethod: typeof DLMM.getAllLbPairPositionsByUser = 83 | DLMM.getAllLbPairPositionsByUser; 84 | expect(typeof staticMethod).toBe("function"); 85 | }); 86 | }); 87 | 88 | describe("Node.js Compatibility", () => { 89 | test("should work with Node.js 20.x (from issue)", () => { 90 | expect(DLMM).toBeDefined(); 91 | expect(DLMM.name).toBe("DLMM"); 92 | }); 93 | 94 | test("should support Node.js ESM (package.json type: module)", () => { 95 | expect(typeof DLMM).toBe("function"); 96 | expect(DLMM.getAllLbPairPositionsByUser).toBeDefined(); 97 | }); 98 | }); 99 | 100 | describe("Regression Prevention", () => { 101 | test("should never require double .default access", () => { 102 | // DLMM.default.default.method() 103 | 104 | expect(typeof DLMM).toBe("function"); 105 | 106 | expect((DLMM as any).default).toBeUndefined(); 107 | }); 108 | 109 | test("should not expose internal export structures", () => { 110 | expect((DLMM as any).src_default).toBeUndefined(); 111 | 112 | expect((DLMM as any).__esModule).toBeUndefined(); 113 | }); 114 | 115 | test("should maintain consistent namespace across builds", () => { 116 | const publicAPI = Object.getOwnPropertyNames(DLMM); 117 | 118 | expect(publicAPI.length).toBeGreaterThan(0); 119 | 120 | expect(publicAPI).not.toContain("default"); 121 | expect(publicAPI).not.toContain("__esModule"); 122 | }); 123 | }); 124 | 125 | describe("Documentation Examples Validation", () => { 126 | test("should allow usage exactly as shown in docs", () => { 127 | expect(DLMM.getAllLbPairPositionsByUser).toBeDefined(); 128 | expect(typeof DLMM.getAllLbPairPositionsByUser).toBe("function"); 129 | 130 | expect( 131 | (DLMM as any).default?.getAllLbPairPositionsByUser 132 | ).toBeUndefined(); 133 | }); 134 | 135 | test("should match expected console.log output", () => { 136 | const dlmmString = DLMM.toString(); 137 | expect(dlmmString).toContain("class"); 138 | expect(DLMM.name).toBe("DLMM"); 139 | expect(typeof DLMM).not.toBe("object"); 140 | expect(typeof DLMM).toBe("function"); 141 | }); 142 | }); 143 | }); 144 | --------------------------------------------------------------------------------