├── .github ├── actions │ ├── setup-anchor │ │ └── action.yml │ ├── setup-dep │ │ └── action.yml │ └── setup-solana │ │ └── action.yml └── workflows │ ├── ci-pr-main-cli.yml │ ├── ci-pr-main-market-making.yml │ ├── ci-pr-main-program.yml │ └── ci-pr-main-sdk.yml ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── README.md ├── artifacts ├── lb_clmm.so ├── token_2022.so └── transfer_hook_counter.so ├── cli ├── Cargo.toml ├── README.md └── src │ ├── args.rs │ ├── instructions │ ├── add_liquidity.rs │ ├── admin │ │ ├── close_claim_protocol_fee_operator.rs │ │ ├── close_preset_parameter.rs │ │ ├── create_claim_protocol_fee_operator.rs │ │ ├── initialize_permission_lb_pair.rs │ │ ├── initialize_preset_parameter.rs │ │ ├── initialize_reward.rs │ │ ├── initialize_token_badge.rs │ │ ├── mod.rs │ │ ├── set_activation_point.rs │ │ ├── set_pre_activation_duration.rs │ │ ├── set_pre_activation_swap_address.rs │ │ ├── toggle_pair_status.rs │ │ ├── update_base_fee.rs │ │ ├── update_reward_duration.rs │ │ ├── update_reward_funder.rs │ │ └── withdraw_protocol_fee.rs │ ├── claim_fee.rs │ ├── claim_reward.rs │ ├── close_position.rs │ ├── fund_reward.rs │ ├── get_all_positions.rs │ ├── ilm │ │ ├── mod.rs │ │ ├── remove_liquidity_by_price_range.rs │ │ ├── seed_liquidity_from_operator.rs │ │ └── seed_liquidity_single_bin_by_operator.rs │ ├── increase_oracle_length.rs │ ├── initialize_bin_array.rs │ ├── initialize_bin_array_with_bin_range.rs │ ├── initialize_bin_array_with_price_range.rs │ ├── initialize_customizable_permissionless_lb_pair.rs │ ├── initialize_customizable_permissionless_lb_pair2.rs │ ├── initialize_lb_pair.rs │ ├── initialize_lb_pair2.rs │ ├── initialize_position.rs │ ├── initialize_position_with_price_range.rs │ ├── list_all_binstep.rs │ ├── mod.rs │ ├── remove_liquidity.rs │ ├── set_pair_status.rs │ ├── set_pair_status_permissionless.rs │ ├── show_pair.rs │ ├── show_position.rs │ ├── show_preset_parameters.rs │ ├── simulate_swap_demand.rs │ ├── swap_exact_in.rs │ ├── swap_exact_out.rs │ ├── swap_with_price_impact.rs │ └── utils.rs │ ├── main.rs │ └── math.rs ├── command_list ├── claim_fee_from_operator.sh ├── ilm_curve_by_operator.sh ├── ilm_single_bin_by_operator.sh └── set_bootstrapping_pair_status.sh ├── commons ├── Cargo.toml ├── src │ ├── account_filters.rs │ ├── constants.rs │ ├── conversions │ │ ├── activation_type.rs │ │ ├── mod.rs │ │ ├── pair_type.rs │ │ ├── status.rs │ │ └── token_program_flag.rs │ ├── extensions │ │ ├── bin.rs │ │ ├── bin_array.rs │ │ ├── bin_array_bitmap.rs │ │ ├── lb_pair.rs │ │ ├── mod.rs │ │ └── position.rs │ ├── lib.rs │ ├── math │ │ ├── mod.rs │ │ ├── price_math.rs │ │ ├── u128x128_math.rs │ │ ├── u64x64_math.rs │ │ └── utils.rs │ ├── pda.rs │ ├── quote.rs │ ├── rpc_client_extension.rs │ ├── seeds.rs │ ├── token_2022.rs │ └── typedefs.rs └── tests │ ├── artifacts │ ├── lb_clmm_prod.so │ └── token_2022.so │ ├── fixtures │ ├── B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc │ │ ├── bin_array_1.bin │ │ ├── bin_array_2.bin │ │ ├── lb_pair.bin │ │ ├── oracle.bin │ │ ├── reserve_x.bin │ │ ├── reserve_y.bin │ │ └── token_x_mint.bin │ └── EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig │ │ ├── bin_array_1.bin │ │ ├── bin_array_2.bin │ │ ├── lb_pair.bin │ │ ├── oracle.bin │ │ ├── reserve_x.bin │ │ ├── reserve_y.bin │ │ └── token_x_mint.bin │ ├── helpers │ ├── mod.rs │ └── utils.rs │ ├── test_swap.rs │ └── test_swap_token2022.rs ├── dlmm_interface ├── .gitignore ├── Cargo.toml └── src │ ├── accounts.rs │ ├── errors.rs │ ├── events.rs │ ├── instructions.rs │ ├── lib.rs │ └── typedefs.rs ├── id.json ├── keys └── localnet │ ├── admin-bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1.json │ └── program-LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ.json ├── market_making ├── Cargo.toml ├── README.MD └── src │ ├── bin_array_manager.rs │ ├── config.json │ ├── core.rs │ ├── main.rs │ ├── pair_config.rs │ ├── router.rs │ ├── state.rs │ └── utils.rs ├── package-lock.json ├── package.json ├── python-client └── dlmm │ ├── .gitignore │ ├── README.md │ ├── dist │ ├── dlmm-0.1.0-py3-none-any.whl │ └── dlmm-0.1.0.tar.gz │ ├── dlmm │ ├── __init__.py │ ├── dlmm.py │ ├── types.py │ └── utils.py │ ├── poetry.lock │ ├── pyproject.toml │ └── tests │ ├── __init__.py │ ├── test_lp_flow.py │ └── test_util_methods.py └── ts-client ├── README.md ├── jest.config.js ├── package.json ├── pnpm-lock.yaml ├── src ├── dlmm │ ├── constants │ │ └── index.ts │ ├── error.ts │ ├── helpers │ │ ├── accountFilters.ts │ │ ├── binArray.ts │ │ ├── computeUnit.ts │ │ ├── derive.ts │ │ ├── fee.ts │ │ ├── index.ts │ │ ├── lbPair.ts │ │ ├── math.ts │ │ ├── positions │ │ │ ├── index.ts │ │ │ └── wrapper.ts │ │ ├── strategy.ts │ │ ├── token_2022.ts │ │ ├── u64xu64_math.ts │ │ ├── weight.ts │ │ └── weightToAmounts.ts │ ├── idl.ts │ ├── index.ts │ └── types │ │ └── index.ts ├── examples │ ├── example.ts │ ├── fetch_lb_pair_lock_info.ts │ ├── initialize_bin_arrays.ts │ └── swap_quote.ts ├── index.ts ├── server │ ├── index.ts │ └── utils.ts └── test │ ├── calculate_distribution.test.ts │ ├── decode.test.ts │ ├── external │ ├── helper.ts │ ├── program.ts │ └── transfer_hook_counter.ts │ ├── helper.ts │ ├── ilm.test.ts │ ├── sdk.test.ts │ ├── sdk_token2022.test.ts │ ├── single_bin.test.ts │ └── token_2022.test.ts ├── tsconfig.json └── tsup.config.ts /.github/actions/setup-anchor/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup anchor-cli" 2 | description: "Setup node js and anchor cli" 3 | runs: 4 | using: "composite" 5 | steps: 6 | - uses: actions/setup-node@v2 7 | with: 8 | node-version: ${{ env.NODE_VERSION }} 9 | - run: npm install -g @coral-xyz/anchor-cli@${{ env.ANCHOR_CLI_VERSION }} yarn 10 | shell: bash 11 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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: 1.18.13 10 | NODE_VERSION: 18.14.2 11 | ANCHOR_CLI_VERSION: 0.28.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.76.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 | -------------------------------------------------------------------------------- /.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: 1.18.13 10 | NODE_VERSION: 18.14.2 11 | ANCHOR_CLI_VERSION: 0.28.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.76.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 | -------------------------------------------------------------------------------- /.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.76.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 | -------------------------------------------------------------------------------- /.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: 1.18.13 10 | NODE_VERSION: 18.14.2 11 | ANCHOR_CLI_VERSION: 0.28.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | node_modules 6 | dist 7 | build 8 | test-ledger 9 | -------------------------------------------------------------------------------- /Anchor.toml: -------------------------------------------------------------------------------- 1 | [features] 2 | seeds = false 3 | skip-lint = false 4 | 5 | [programs.localnet] 6 | lb_clmm = { address = "GrAkKfEpTKQuVHG2Y97Y2FF4i7y7Q5AHLK94JBy7Y5yv", idl = "target/idl/id.json" } 7 | 8 | [registry] 9 | url = "https://api.apr.dev" 10 | 11 | [provider] 12 | cluster = "Localnet" 13 | wallet = "keys/localnet/admin-bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1.json" 14 | 15 | [[test.genesis]] 16 | address = "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb" 17 | program = "./artifacts/token_2022.so" 18 | 19 | [[test.genesis]] 20 | address = "LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ" 21 | program = "./artifacts/lb_clmm.so" 22 | 23 | [[test.genesis]] 24 | address = "abcSyangMHdGzUGKhBhKoQzSFdJKUdkPGf5cbXVHpEw" 25 | program = "./artifacts/transfer_hook_counter.so" 26 | 27 | [scripts] 28 | test = "yarn run ts-mocha --sort --type-check --bail -p ./tsconfig.json -t 1000000 tests/*.ts" 29 | build-local = "anchor build" 30 | deploy-local = "solana program deploy target/deploy/lb_clmm.so --keypair keys/localnet/admin-bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1.json --program-id keys/localnet/program-LbVRzDTvBDEcrthxfZ4RL6yiq3uZw8bS6MwtdY6UhFQ.json" 31 | 32 | [test] 33 | startup_wait = 50000 34 | 35 | [toolchain] 36 | anchor_version = "0.29.0" 37 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["cli", "market_making", "commons", "dlmm_interface"] 3 | resolver = "2" 4 | 5 | [workspace.dependencies] 6 | anchor-lang = "0.29.0" 7 | anchor-spl = "0.29.0" 8 | anchor-client = "0.29.0" 9 | 10 | solana-sdk = "1.17.0" 11 | spl-associated-token-account = "1" 12 | solana-transaction-status = "1.17.0" 13 | solana-account-decoder = "1.17.0" 14 | spl-memo = "3.0.0" 15 | spl-transfer-hook-interface = "0.5.0" 16 | 17 | serde_json = "1.0.48" 18 | serde = "1.0.104" 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /artifacts/lb_clmm.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/artifacts/lb_clmm.so -------------------------------------------------------------------------------- /artifacts/token_2022.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/artifacts/token_2022.so -------------------------------------------------------------------------------- /artifacts/transfer_hook_counter.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/artifacts/transfer_hook_counter.so -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "0.5.1" 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 | staging = ["dlmm_interface/staging"] 12 | 13 | [dependencies] 14 | commons = { workspace = true } 15 | anchor-lang = { workspace = true } 16 | anchor-spl = { workspace = true } 17 | anchor-client = { workspace = true, features = ["async"] } 18 | clap = { workspace = true, features = ["derive"] } 19 | anyhow = { workspace = true } 20 | shellexpand = { workspace = true } 21 | rust_decimal = { workspace = true, features = ["maths"] } 22 | dlmm_interface = { path = "../dlmm_interface" } 23 | spl-associated-token-account = { workspace = true } 24 | rand = { workspace = true } 25 | tokio = { workspace = true, features = ["full", "parking_lot"] } 26 | bincode = { workspace = true } 27 | spl-memo = { workspace = true, features = ["no-entrypoint"] } 28 | spl-transfer-hook-interface = { workspace = true } 29 | solana-account-decoder = { workspace = true } 30 | num-integer = { workspace = true } 31 | bytemuck = { workspace = true } 32 | futures-util = { workspace = true } 33 | 34 | bigdecimal = "0.4.2" 35 | serde = "1.0.167" 36 | serde_json = "1.0.100" 37 | serde_json_any_key = "2.0.0" 38 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: [AccountMeta; CLOSE_CLAIM_PROTOCOL_FEE_OPERATOR_IX_ACCOUNTS_LEN] = 19 | CloseClaimProtocolFeeOperatorKeys { 20 | claim_fee_operator, 21 | admin: program.payer(), 22 | rent_receiver: program.payer(), 23 | } 24 | .into(); 25 | 26 | let data = CloseClaimProtocolFeeOperatorIxData; 27 | 28 | let instruction = Instruction { 29 | program_id: dlmm_interface::ID, 30 | accounts: accounts.to_vec(), 31 | data: data.try_to_vec()?, 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!("Close claim protocol fee operator. Signature: {signature:#?}"); 41 | 42 | signature?; 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/close_preset_parameter.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct ClosePresetAccountParams { 5 | /// Preset parameter pubkey. Get from ListAllBinStep 6 | pub preset_parameter: Pubkey, 7 | } 8 | 9 | pub async fn execute_close_preset_parameter + Clone>( 10 | params: ClosePresetAccountParams, 11 | program: &Program, 12 | transaction_config: RpcSendTransactionConfig, 13 | ) -> Result { 14 | let ClosePresetAccountParams { preset_parameter } = params; 15 | 16 | let accounts: [AccountMeta; CLOSE_PRESET_PARAMETER_IX_ACCOUNTS_LEN] = 17 | ClosePresetParameterKeys { 18 | admin: program.payer(), 19 | rent_receiver: program.payer(), 20 | preset_parameter, 21 | } 22 | .into(); 23 | 24 | let data = ClosePresetParameter2IxData; 25 | 26 | let instruction = Instruction { 27 | program_id: dlmm_interface::ID, 28 | accounts: accounts.to_vec(), 29 | data: data.try_to_vec()?, 30 | }; 31 | 32 | let request_builder = program.request(); 33 | let signature = request_builder 34 | .instruction(instruction) 35 | .send_with_spinner_and_config(transaction_config) 36 | .await; 37 | 38 | println!( 39 | "Close preset parameter {}. Signature: {signature:#?}", 40 | preset_parameter 41 | ); 42 | 43 | signature?; 44 | 45 | Ok(preset_parameter) 46 | } 47 | -------------------------------------------------------------------------------- /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: [AccountMeta; CREATE_CLAIM_PROTOCOL_FEE_OPERATOR_IX_ACCOUNTS_LEN] = 19 | CreateClaimProtocolFeeOperatorKeys { 20 | claim_fee_operator, 21 | operator, 22 | admin: program.payer(), 23 | system_program: anchor_lang::system_program::ID, 24 | } 25 | .into(); 26 | 27 | let data = CreateClaimProtocolFeeOperatorIxData; 28 | 29 | let instruction = Instruction { 30 | program_id: dlmm_interface::ID, 31 | accounts: accounts.to_vec(), 32 | data: data.try_to_vec()?, 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!("Create claim protocol fee operator. Signature: {signature:#?}"); 42 | 43 | signature?; 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/initialize_preset_parameter.rs: -------------------------------------------------------------------------------- 1 | use solana_client::{ 2 | rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, 3 | rpc_filter::{Memcmp, RpcFilterType}, 4 | }; 5 | 6 | use crate::*; 7 | 8 | #[derive(Debug, Parser)] 9 | pub struct InitPresetParameters { 10 | /// Bin step. Represent the price increment / decrement. 11 | pub bin_step: u16, 12 | /// Used for base fee calculation. base_fee_rate = base_factor * bin_step 13 | pub base_factor: u16, 14 | /// Filter period determine high frequency trading time window. 15 | pub filter_period: u16, 16 | /// Decay period determine when the volatile fee start decay / decrease. 17 | pub decay_period: u16, 18 | /// Reduction factor controls the volatile fee rate decrement rate. 19 | pub reduction_factor: u16, 20 | /// Used to scale the variable fee component depending on the dynamic of the market 21 | pub variable_fee_control: u32, 22 | /// Maximum number of bin crossed can be accumulated. Used to cap volatile fee rate. 23 | pub max_volatility_accumulator: u32, 24 | /// Portion of swap fees retained by the protocol by controlling protocol_share parameter. protocol_swap_fee = protocol_share * total_swap_fee 25 | pub protocol_share: u16, 26 | /// Base fee power factor 27 | pub base_fee_power_factor: u8, 28 | } 29 | 30 | pub async fn execute_initialize_preset_parameter + Clone>( 31 | params: InitPresetParameters, 32 | program: &Program, 33 | transaction_config: RpcSendTransactionConfig, 34 | ) -> Result { 35 | let InitPresetParameters { 36 | base_factor, 37 | bin_step, 38 | decay_period, 39 | filter_period, 40 | max_volatility_accumulator, 41 | protocol_share, 42 | reduction_factor, 43 | variable_fee_control, 44 | base_fee_power_factor, 45 | } = params; 46 | 47 | let rpc_client = program.async_rpc(); 48 | 49 | let preset_parameter_v2_count = rpc_client 50 | .get_program_accounts_with_config( 51 | &dlmm_interface::ID, 52 | RpcProgramAccountsConfig { 53 | filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( 54 | 0, 55 | &PRESET_PARAMETER2_ACCOUNT_DISCM, 56 | ))]), 57 | account_config: RpcAccountInfoConfig { 58 | encoding: Some(UiAccountEncoding::Base64), 59 | data_slice: Some(UiDataSliceConfig { 60 | offset: 0, 61 | length: 0, 62 | }), 63 | ..Default::default() 64 | }, 65 | ..Default::default() 66 | }, 67 | ) 68 | .await? 69 | .len(); 70 | 71 | let index = preset_parameter_v2_count as u16; 72 | 73 | let (preset_parameter, _bump) = 74 | derive_preset_parameter_pda_v2(preset_parameter_v2_count as u16); 75 | 76 | let accounts: [AccountMeta; INITIALIZE_PRESET_PARAMETER2_IX_ACCOUNTS_LEN] = 77 | InitializePresetParameter2Keys { 78 | preset_parameter, 79 | admin: program.payer(), 80 | system_program: solana_sdk::system_program::ID, 81 | } 82 | .into(); 83 | 84 | let data = InitializePresetParameter2IxData(InitializePresetParameter2IxArgs { 85 | ix: InitPresetParameters2Ix { 86 | index, 87 | bin_step, 88 | base_factor, 89 | filter_period, 90 | decay_period, 91 | reduction_factor, 92 | variable_fee_control, 93 | max_volatility_accumulator, 94 | protocol_share, 95 | base_fee_power_factor, 96 | }, 97 | }) 98 | .try_to_vec()?; 99 | 100 | let init_preset_param_ix = Instruction { 101 | program_id: dlmm_interface::ID, 102 | accounts: accounts.to_vec(), 103 | data, 104 | }; 105 | 106 | let request_builder = program.request(); 107 | let signature = request_builder 108 | .instruction(init_preset_param_ix) 109 | .send_with_spinner_and_config(transaction_config) 110 | .await; 111 | 112 | println!( 113 | "Initialize preset parameter {}. Signature: {signature:#?}", 114 | preset_parameter 115 | ); 116 | 117 | signature?; 118 | 119 | Ok(preset_parameter) 120 | } 121 | -------------------------------------------------------------------------------- /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.async_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 | .unwrap_or(dlmm_interface::ID); 38 | 39 | let accounts: [AccountMeta; INITIALIZE_REWARD_IX_ACCOUNTS_LEN] = InitializeRewardKeys { 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_interface::ID, 50 | } 51 | .into(); 52 | 53 | let data = InitializeRewardIxData(InitializeRewardIxArgs { 54 | reward_index, 55 | reward_duration, 56 | funder, 57 | }) 58 | .try_to_vec()?; 59 | 60 | let instruction = Instruction { 61 | program_id: dlmm_interface::ID, 62 | accounts: accounts.to_vec(), 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/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: [AccountMeta; INITIALIZE_TOKEN_BADGE_IX_ACCOUNTS_LEN] = 20 | InitializeTokenBadgeKeys { 21 | admin: program.payer(), 22 | token_mint: mint, 23 | system_program: system_program::ID, 24 | token_badge, 25 | } 26 | .into(); 27 | 28 | let data = InitializeTokenBadgeIxData; 29 | 30 | let instruction = Instruction { 31 | program_id: dlmm_interface::ID, 32 | accounts: accounts.to_vec(), 33 | data: data.try_to_vec()?, 34 | }; 35 | 36 | let request_builder = program.request(); 37 | let signature = request_builder 38 | .instruction(instruction) 39 | .send_with_spinner_and_config(transaction_config) 40 | .await; 41 | 42 | println!("Initialize token badge {}. Signature: {signature:#?}", mint); 43 | 44 | signature?; 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: [AccountMeta; SET_ACTIVATION_POINT_IX_ACCOUNTS_LEN] = SetActivationPointKeys { 22 | admin: program.payer(), 23 | lb_pair, 24 | } 25 | .into(); 26 | 27 | let data = 28 | SetActivationPointIxData(SetActivationPointIxArgs { activation_point }).try_to_vec()?; 29 | 30 | let set_activation_point_ix = Instruction { 31 | accounts: accounts.to_vec(), 32 | data, 33 | program_id: dlmm_interface::ID, 34 | }; 35 | 36 | let request_builder = program.request(); 37 | let signature = request_builder 38 | .instruction(set_activation_point_ix) 39 | .send_with_spinner_and_config(transaction_config) 40 | .await; 41 | 42 | println!("Set activation point. Signature: {:#?}", signature); 43 | 44 | signature?; 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /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: [AccountMeta; SET_PRE_ACTIVATION_DURATION_IX_ACCOUNTS_LEN] = 20 | SetPreActivationSwapAddressKeys { 21 | creator: program.payer(), 22 | lb_pair, 23 | } 24 | .into(); 25 | 26 | let data = SetPreActivationDurationIxData(SetPreActivationDurationIxArgs { 27 | pre_activation_duration: pre_activation_duration as u64, 28 | }) 29 | .try_to_vec()?; 30 | 31 | let set_pre_activation_slot_duration_ix = Instruction { 32 | accounts: accounts.to_vec(), 33 | data, 34 | program_id: dlmm_interface::ID, 35 | }; 36 | 37 | let request_builder = program.request(); 38 | 39 | let signature = request_builder 40 | .instruction(set_pre_activation_slot_duration_ix) 41 | .send_with_spinner_and_config(transaction_config) 42 | .await; 43 | 44 | println!("Set pre activation duration. Signature: {:#?}", signature); 45 | 46 | signature?; 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /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: [AccountMeta; SET_PRE_ACTIVATION_SWAP_ADDRESS_IX_ACCOUNTS_LEN] = 20 | SetPreActivationSwapAddressKeys { 21 | creator: program.payer(), 22 | lb_pair, 23 | } 24 | .into(); 25 | 26 | let data = SetPreActivationSwapAddressIxData(SetPreActivationSwapAddressIxArgs { 27 | pre_activation_swap_address, 28 | }) 29 | .try_to_vec()?; 30 | 31 | let set_pre_activation_swap_address_ix = Instruction { 32 | accounts: accounts.to_vec(), 33 | data, 34 | program_id: dlmm_interface::ID, 35 | }; 36 | 37 | let request_builder = program.request(); 38 | 39 | let signature = request_builder 40 | .instruction(set_pre_activation_swap_address_ix) 41 | .send_with_spinner_and_config(transaction_config) 42 | .await; 43 | 44 | println!( 45 | "Set pre activation swap address. Signature: {:#?}", 46 | signature 47 | ); 48 | 49 | signature?; 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /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: [AccountMeta; SET_PAIR_STATUS_IX_ACCOUNTS_LEN] = SetPairStatusKeys { 22 | admin: program.payer(), 23 | lb_pair, 24 | } 25 | .into(); 26 | 27 | let data = SetPairStatusIxData(SetPairStatusIxArgs { 28 | status: pair_status, 29 | }) 30 | .try_to_vec()?; 31 | 32 | let instruction = Instruction { 33 | program_id: dlmm_interface::ID, 34 | accounts: accounts.to_vec(), 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/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.async_rpc(); 20 | 21 | let pair_account = rpc_client.get_account(&lb_pair).await?; 22 | 23 | let lb_pair_state = LbPairAccount::deserialize(pair_account.data.as_ref())?.0; 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 = UpdateBaseFeeParametersIxData(UpdateBaseFeeParametersIxArgs { 29 | fee_parameter: BaseFeeParameter { 30 | protocol_share: lb_pair_state.parameters.protocol_share, 31 | base_factor, 32 | base_fee_power_factor, 33 | }, 34 | }) 35 | .try_to_vec()?; 36 | 37 | let event_authority = derive_event_authority_pda().0; 38 | 39 | let accounts: [AccountMeta; UPDATE_BASE_FEE_PARAMETERS_IX_ACCOUNTS_LEN] = 40 | UpdateBaseFeeParametersKeys { 41 | lb_pair, 42 | admin: program.payer(), 43 | event_authority, 44 | program: dlmm_interface::ID, 45 | } 46 | .into(); 47 | 48 | let ix = Instruction { 49 | program_id: program.id(), 50 | data: ix_data, 51 | accounts: accounts.to_vec(), 52 | }; 53 | 54 | let request_builder = program.request(); 55 | let signature = request_builder 56 | .instruction(ix) 57 | .send_with_spinner_and_config(transaction_config) 58 | .await; 59 | 60 | println!("Update base fee. Signature: {:#?}", signature); 61 | 62 | signature?; 63 | 64 | Ok(()) 65 | } 66 | -------------------------------------------------------------------------------- /cli/src/instructions/admin/update_reward_duration.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct UpdateRewardDurationParams { 5 | pub lb_pair: Pubkey, 6 | pub reward_index: u64, 7 | pub reward_duration: u64, 8 | } 9 | 10 | pub async fn execute_update_reward_duration + Clone>( 11 | params: UpdateRewardDurationParams, 12 | program: &Program, 13 | transaction_config: RpcSendTransactionConfig, 14 | ) -> Result<()> { 15 | let UpdateRewardDurationParams { 16 | lb_pair, 17 | reward_index, 18 | reward_duration, 19 | } = params; 20 | 21 | let rpc_client = program.async_rpc(); 22 | let lb_pair_state = rpc_client 23 | .get_account_and_deserialize(&lb_pair, |account| { 24 | Ok(LbPairAccount::deserialize(&account.data)?.0) 25 | }) 26 | .await?; 27 | 28 | let active_bin_array_idx = BinArray::bin_id_to_bin_array_index(lb_pair_state.active_id)?; 29 | let (bin_array, _bump) = derive_bin_array_pda(lb_pair, active_bin_array_idx as i64); 30 | 31 | let (event_authority, _bump) = derive_event_authority_pda(); 32 | 33 | let accounts: [AccountMeta; UPDATE_REWARD_DURATION_IX_ACCOUNTS_LEN] = 34 | UpdateRewardDurationKeys { 35 | lb_pair, 36 | admin: program.payer(), 37 | bin_array, 38 | event_authority, 39 | program: dlmm_interface::ID, 40 | } 41 | .into(); 42 | 43 | let data = UpdateRewardDurationIxData(UpdateRewardDurationIxArgs { 44 | reward_index, 45 | new_duration: reward_duration, 46 | }) 47 | .try_to_vec()?; 48 | 49 | let ix = Instruction { 50 | program_id: dlmm_interface::ID, 51 | accounts: accounts.to_vec(), 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/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: [AccountMeta; UPDATE_REWARD_FUNDER_IX_ACCOUNTS_LEN] = UpdateRewardFunderKeys { 24 | lb_pair, 25 | admin: program.payer(), 26 | event_authority, 27 | program: dlmm_interface::ID, 28 | } 29 | .into(); 30 | 31 | let data = UpdateRewardFunderIxData(UpdateRewardFunderIxArgs { 32 | reward_index, 33 | new_funder: funder, 34 | }) 35 | .try_to_vec()?; 36 | 37 | let ix = Instruction { 38 | program_id: dlmm_interface::ID, 39 | accounts: accounts.to_vec(), 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/withdraw_protocol_fee.rs: -------------------------------------------------------------------------------- 1 | use anchor_spl::associated_token::get_associated_token_address_with_program_id; 2 | 3 | use crate::*; 4 | #[derive(Debug, Parser)] 5 | pub struct WithdrawProtocolFeeParams { 6 | pub lb_pair: Pubkey, 7 | pub amount_x: u64, 8 | pub amount_y: u64, 9 | } 10 | 11 | pub async fn execute_withdraw_protocol_fee + Clone>( 12 | params: WithdrawProtocolFeeParams, 13 | program: &Program, 14 | transaction_config: RpcSendTransactionConfig, 15 | ) -> Result<()> { 16 | let WithdrawProtocolFeeParams { 17 | lb_pair, 18 | amount_x, 19 | amount_y, 20 | } = params; 21 | 22 | let rpc_client = program.async_rpc(); 23 | 24 | let lb_pair_state = rpc_client 25 | .get_account_and_deserialize(&lb_pair, |account| { 26 | Ok(LbPairAccount::deserialize(&account.data)?.0) 27 | }) 28 | .await?; 29 | 30 | let [token_x_program, token_y_program] = lb_pair_state.get_token_programs()?; 31 | 32 | let receiver_token_x = get_associated_token_address_with_program_id( 33 | &program.payer(), 34 | &lb_pair_state.token_x_mint, 35 | &token_x_program, 36 | ); 37 | 38 | let receiver_token_y = get_associated_token_address_with_program_id( 39 | &program.payer(), 40 | &lb_pair_state.token_y_mint, 41 | &token_y_program, 42 | ); 43 | 44 | let (claim_fee_operator, _) = derive_claim_protocol_fee_operator_pda(program.payer()); 45 | 46 | let main_accounts: [AccountMeta; WITHDRAW_PROTOCOL_FEE_IX_ACCOUNTS_LEN] = 47 | WithdrawProtocolFeeKeys { 48 | lb_pair, 49 | reserve_x: lb_pair_state.reserve_x, 50 | reserve_y: lb_pair_state.reserve_y, 51 | token_x_mint: lb_pair_state.token_x_mint, 52 | token_y_mint: lb_pair_state.token_y_mint, 53 | token_x_program, 54 | token_y_program, 55 | receiver_token_x, 56 | receiver_token_y, 57 | claim_fee_operator, 58 | operator: program.payer(), 59 | memo_program: spl_memo::ID, 60 | } 61 | .into(); 62 | 63 | let mut remaining_accounts_info = RemainingAccountsInfo { slices: vec![] }; 64 | let mut remaining_accounts = vec![]; 65 | 66 | if let Some((slices, transfer_hook_remaining_accounts)) = 67 | get_potential_token_2022_related_ix_data_and_accounts( 68 | &lb_pair_state, 69 | program.async_rpc(), 70 | ActionType::Liquidity, 71 | ) 72 | .await? 73 | { 74 | remaining_accounts_info.slices = slices; 75 | remaining_accounts.extend(transfer_hook_remaining_accounts); 76 | }; 77 | 78 | let data = WithdrawProtocolFeeIxData(WithdrawProtocolFeeIxArgs { 79 | amount_x, 80 | amount_y, 81 | remaining_accounts_info, 82 | }) 83 | .try_to_vec()?; 84 | 85 | let accounts = [main_accounts.to_vec(), remaining_accounts].concat(); 86 | 87 | let withdraw_ix = Instruction { 88 | program_id: dlmm_interface::ID, 89 | accounts, 90 | data, 91 | }; 92 | 93 | let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(200_000); 94 | 95 | let request_builder = program.request(); 96 | let signature = request_builder 97 | .instruction(compute_budget_ix) 98 | .instruction(withdraw_ix) 99 | .send_with_spinner_and_config(transaction_config) 100 | .await; 101 | 102 | println!("WithdrawProtocolFee. Signature: {:#?}", signature); 103 | 104 | signature?; 105 | 106 | Ok(()) 107 | } 108 | -------------------------------------------------------------------------------- /cli/src/instructions/claim_fee.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use instructions::*; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct ClaimFeeParams { 6 | /// Position address 7 | pub position: Pubkey, 8 | } 9 | 10 | pub async fn execute_claim_fee + Clone>( 11 | params: ClaimFeeParams, 12 | program: &Program, 13 | transaction_config: RpcSendTransactionConfig, 14 | compute_unit_price: Option, 15 | ) -> Result<()> { 16 | let ClaimFeeParams { position } = params; 17 | 18 | let rpc_client = program.async_rpc(); 19 | let position_state = rpc_client 20 | .get_account_and_deserialize(&position, |account| { 21 | Ok(PositionV2Account::deserialize(&account.data)?.0) 22 | }) 23 | .await?; 24 | 25 | let lb_pair_state = rpc_client 26 | .get_account_and_deserialize(&position_state.lb_pair, |account| { 27 | Ok(LbPairAccount::deserialize(&account.data)?.0) 28 | }) 29 | .await?; 30 | 31 | let (user_token_x, user_token_y) = if position_state.fee_owner.eq(&Pubkey::default()) { 32 | let user_token_x = get_or_create_ata( 33 | program, 34 | transaction_config, 35 | lb_pair_state.token_x_mint, 36 | program.payer(), 37 | compute_unit_price.clone(), 38 | ) 39 | .await?; 40 | 41 | let user_token_y = get_or_create_ata( 42 | program, 43 | transaction_config, 44 | lb_pair_state.token_y_mint, 45 | program.payer(), 46 | compute_unit_price.clone(), 47 | ) 48 | .await?; 49 | 50 | (user_token_x, user_token_y) 51 | } else { 52 | let user_token_x = get_or_create_ata( 53 | program, 54 | transaction_config, 55 | lb_pair_state.token_x_mint, 56 | position_state.fee_owner, 57 | compute_unit_price.clone(), 58 | ) 59 | .await?; 60 | 61 | let user_token_y = get_or_create_ata( 62 | program, 63 | transaction_config, 64 | lb_pair_state.token_y_mint, 65 | position_state.fee_owner, 66 | compute_unit_price.clone(), 67 | ) 68 | .await?; 69 | 70 | (user_token_x, user_token_y) 71 | }; 72 | 73 | let [token_program_x, token_program_y] = lb_pair_state.get_token_programs()?; 74 | 75 | let (event_authority, _bump) = derive_event_authority_pda(); 76 | 77 | let main_accounts: [AccountMeta; CLAIM_FEE2_IX_ACCOUNTS_LEN] = dlmm_interface::ClaimFee2Keys { 78 | lb_pair: position_state.lb_pair, 79 | sender: program.payer(), 80 | position, 81 | reserve_x: lb_pair_state.reserve_x, 82 | reserve_y: lb_pair_state.reserve_y, 83 | token_program_x, 84 | token_program_y, 85 | token_x_mint: lb_pair_state.token_x_mint, 86 | token_y_mint: lb_pair_state.token_y_mint, 87 | user_token_x, 88 | user_token_y, 89 | event_authority, 90 | program: dlmm_interface::ID, 91 | memo_program: spl_memo::id(), 92 | } 93 | .into(); 94 | 95 | let mut remaining_accounts_info = RemainingAccountsInfo { slices: vec![] }; 96 | let mut token_2022_remaining_accounts = vec![]; 97 | 98 | if let Some((slices, transfer_hook_remaining_accounts)) = 99 | get_potential_token_2022_related_ix_data_and_accounts( 100 | &lb_pair_state, 101 | program.async_rpc(), 102 | ActionType::Liquidity, 103 | ) 104 | .await? 105 | { 106 | remaining_accounts_info.slices = slices; 107 | token_2022_remaining_accounts.extend(transfer_hook_remaining_accounts); 108 | }; 109 | 110 | for (min_bin_id, max_bin_id) in 111 | position_bin_range_chunks(position_state.lower_bin_id, position_state.upper_bin_id) 112 | { 113 | let data = ClaimFee2IxData(ClaimFee2IxArgs { 114 | min_bin_id, 115 | max_bin_id, 116 | remaining_accounts_info: remaining_accounts_info.clone(), 117 | }) 118 | .try_to_vec()?; 119 | 120 | let bin_arrays_account_meta = 121 | position_state.get_bin_array_accounts_meta_coverage_by_chunk(min_bin_id, max_bin_id)?; 122 | 123 | let accounts = [ 124 | main_accounts.to_vec(), 125 | token_2022_remaining_accounts.clone(), 126 | bin_arrays_account_meta, 127 | ] 128 | .concat(); 129 | 130 | let claim_fee_ix = Instruction { 131 | program_id: dlmm_interface::ID, 132 | accounts, 133 | data, 134 | }; 135 | 136 | let mut request_builder = program.request(); 137 | 138 | if let Some(compute_unit_price_ix) = compute_unit_price.clone() { 139 | request_builder = request_builder.instruction(compute_unit_price_ix); 140 | } 141 | 142 | let signature = request_builder 143 | .instruction(claim_fee_ix) 144 | .send_with_spinner_and_config(transaction_config) 145 | .await; 146 | 147 | println!("Claim fee. Signature: {:#?}", signature); 148 | 149 | signature?; 150 | } 151 | 152 | Ok(()) 153 | } 154 | -------------------------------------------------------------------------------- /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.async_rpc(); 24 | let (reward_vault, _bump) = derive_reward_vault_pda(lb_pair, reward_index); 25 | 26 | let lb_pair_state = rpc_client 27 | .get_account_and_deserialize(&lb_pair, |account| { 28 | Ok(LbPairAccount::deserialize(&account.data)?.0) 29 | }) 30 | .await?; 31 | 32 | let position_state = rpc_client 33 | .get_account_and_deserialize(&position, |account| { 34 | Ok(PositionV2Account::deserialize(&account.data)?.0) 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: [AccountMeta; CLAIM_REWARD2_IX_ACCOUNTS_LEN] = ClaimReward2Keys { 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_interface::ID, 65 | } 66 | .into(); 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.async_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 = ClaimReward2IxData(ClaimReward2IxArgs { 87 | reward_index, 88 | min_bin_id, 89 | max_bin_id, 90 | remaining_accounts_info: remaining_accounts_info.clone(), 91 | }) 92 | .try_to_vec()?; 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_interface::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/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.async_rpc(); 16 | let position_state = rpc_client 17 | .get_account_and_deserialize(&position, |account| { 18 | Ok(PositionV2Account::deserialize(&account.data)?.0) 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: [AccountMeta; CLOSE_POSITION2_IX_ACCOUNTS_LEN] = ClosePosition2Keys { 27 | sender: position_state.owner, 28 | rent_receiver: position_state.owner, 29 | position, 30 | event_authority, 31 | program: dlmm_interface::ID, 32 | } 33 | .into(); 34 | 35 | let data = ClosePosition2IxData.try_to_vec()?; 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_interface::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 | -------------------------------------------------------------------------------- /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.async_rpc(); 24 | 25 | let (reward_vault, _bump) = derive_reward_vault_pda(lb_pair, reward_index); 26 | 27 | let lb_pair_state = rpc_client 28 | .get_account_and_deserialize(&lb_pair, |account| { 29 | Ok(LbPairAccount::deserialize(&account.data)?.0) 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.async_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: [AccountMeta; FUND_REWARD_IX_ACCOUNTS_LEN] = FundRewardKeys { 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_interface::ID, 72 | } 73 | .into(); 74 | 75 | let data = FundRewardIxData(FundRewardIxArgs { 76 | reward_index, 77 | amount: funding_amount, 78 | carry_forward: true, 79 | remaining_accounts_info, 80 | }) 81 | .try_to_vec()?; 82 | 83 | let accounts = [main_accounts.to_vec(), reward_transfer_hook_accounts].concat(); 84 | 85 | let fund_reward_ix = Instruction { 86 | program_id: dlmm_interface::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/get_all_positions.rs: -------------------------------------------------------------------------------- 1 | use solana_client::rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}; 2 | 3 | use crate::*; 4 | 5 | #[derive(Debug, Parser)] 6 | pub struct GetAllPositionsParams { 7 | /// Address of the pair 8 | #[clap(long)] 9 | lb_pair: Pubkey, 10 | /// Owner of position 11 | #[clap(long)] 12 | owner: Pubkey, 13 | } 14 | 15 | pub async fn execute_get_all_positions + Clone>( 16 | program: &Program, 17 | params: GetAllPositionsParams, 18 | ) -> Result<()> { 19 | let GetAllPositionsParams { lb_pair, owner } = params; 20 | 21 | let rpc_client = program.async_rpc(); 22 | 23 | let account_config = RpcAccountInfoConfig { 24 | encoding: Some(UiAccountEncoding::Base64), 25 | ..Default::default() 26 | }; 27 | let config = RpcProgramAccountsConfig { 28 | filters: Some(position_filter_by_wallet_and_pair(owner, lb_pair)), 29 | account_config, 30 | ..Default::default() 31 | }; 32 | 33 | let accounts = rpc_client 34 | .get_program_accounts_with_config(&dlmm_interface::ID, config) 35 | .await?; 36 | 37 | for (position_key, position_raw_account) in accounts { 38 | let position_state = PositionV2Account::deserialize(&position_raw_account.data)?.0; 39 | println!( 40 | "Position {} fee owner {}", 41 | position_key, position_state.fee_owner 42 | ); 43 | } 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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: [AccountMeta; INCREASE_ORACLE_LENGTH_IX_ACCOUNTS_LEN] = 24 | IncreaseOracleLengthKeys { 25 | funder: program.payer(), 26 | oracle, 27 | system_program: solana_sdk::system_program::ID, 28 | event_authority, 29 | program: dlmm_interface::ID, 30 | } 31 | .into(); 32 | 33 | let data = 34 | IncreaseOracleLengthIxData(IncreaseOracleLengthIxArgs { length_to_add }).try_to_vec()?; 35 | 36 | let increase_length_ix = Instruction { 37 | program_id: dlmm_interface::ID, 38 | accounts: accounts.to_vec(), 39 | data, 40 | }; 41 | 42 | let request_builder = program.request(); 43 | let signature = request_builder 44 | .instruction(increase_length_ix) 45 | .send_with_spinner_and_config(transaction_config) 46 | .await; 47 | 48 | println!("Increase oracle {oracle} length. Signature: {signature:#?}"); 49 | 50 | signature?; 51 | 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /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: [AccountMeta; INITIALIZE_BIN_ARRAY_IX_ACCOUNTS_LEN] = InitializeBinArrayKeys { 25 | bin_array, 26 | funder: program.payer(), 27 | lb_pair, 28 | system_program: solana_sdk::system_program::ID, 29 | } 30 | .into(); 31 | 32 | let data = InitializeBinArrayIxData(InitializeBinArrayIxArgs { 33 | index: bin_array_index, 34 | }) 35 | .try_to_vec()?; 36 | 37 | let init_bin_array_ix = Instruction { 38 | program_id: dlmm_interface::ID, 39 | accounts: accounts.to_vec(), 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 | -------------------------------------------------------------------------------- /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_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.async_rpc(); 29 | let lb_pair_state = rpc_client 30 | .get_account_and_deserialize(&lb_pair, |account| { 31 | Ok(LbPairAccount::deserialize(&account.data)?.0) 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/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.async_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(PresetParameterAccount::deserialize(&account.data)?.0) 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) = derive_lb_pair_pda2( 60 | token_mint_x, 61 | token_mint_y, 62 | preset_parameter_state.bin_step, 63 | preset_parameter_state.base_factor, 64 | ); 65 | 66 | if program.rpc().get_account_data(&lb_pair).is_ok() { 67 | return Ok(lb_pair); 68 | } 69 | 70 | let (reserve_x, _bump) = derive_reserve_pda(token_mint_x, lb_pair); 71 | let (reserve_y, _bump) = derive_reserve_pda(token_mint_y, lb_pair); 72 | let (oracle, _bump) = derive_oracle_pda(lb_pair); 73 | 74 | let (event_authority, _bump) = derive_event_authority_pda(); 75 | 76 | let accounts: [AccountMeta; INITIALIZE_LB_PAIR_IX_ACCOUNTS_LEN] = InitializeLbPairKeys { 77 | lb_pair, 78 | bin_array_bitmap_extension: dlmm_interface::ID, 79 | reserve_x, 80 | reserve_y, 81 | token_mint_x, 82 | token_mint_y, 83 | oracle, 84 | funder: program.payer(), 85 | token_program: token_mint_base_account.owner, 86 | preset_parameter, 87 | system_program: solana_sdk::system_program::ID, 88 | event_authority, 89 | program: dlmm_interface::ID, 90 | rent: solana_sdk::sysvar::rent::ID, 91 | } 92 | .into(); 93 | 94 | let data = InitializeLbPairIxData(InitializeLbPairIxArgs { 95 | active_id: computed_active_id, 96 | bin_step, 97 | }) 98 | .try_to_vec()?; 99 | 100 | let init_pair_ix = Instruction { 101 | program_id: dlmm_interface::ID, 102 | data, 103 | accounts: accounts.to_vec(), 104 | }; 105 | 106 | let request_builder = program.request(); 107 | 108 | let signature = request_builder 109 | .instruction(init_pair_ix) 110 | .send_with_spinner_and_config(transaction_config) 111 | .await; 112 | 113 | println!("Initialize LB pair {lb_pair}. Signature: {signature:#?}"); 114 | 115 | signature?; 116 | 117 | Ok(lb_pair) 118 | } 119 | -------------------------------------------------------------------------------- /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.async_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(PresetParameter2Account::deserialize(&account.data)?.0) 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).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 | .unwrap_or(dlmm_interface::ID); 82 | 83 | let token_badge_y = accounts[1] 84 | .as_ref() 85 | .map(|_| token_badge_y) 86 | .unwrap_or(dlmm_interface::ID); 87 | 88 | let accounts: [AccountMeta; INITIALIZE_LB_PAIR2_IX_ACCOUNTS_LEN] = InitializeLbPair2Keys { 89 | lb_pair, 90 | bin_array_bitmap_extension: dlmm_interface::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_interface::ID, 105 | } 106 | .into(); 107 | 108 | let data = InitializeLbPair2IxData(InitializeLbPair2IxArgs { 109 | params: InitializeLbPair2Params { 110 | active_id: computed_active_id, 111 | padding: [0u8; 96], 112 | }, 113 | }) 114 | .try_to_vec()?; 115 | 116 | let init_pair_ix = Instruction { 117 | program_id: dlmm_interface::ID, 118 | data, 119 | accounts: accounts.to_vec(), 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 | -------------------------------------------------------------------------------- /cli/src/instructions/initialize_position.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct InitPositionParams { 5 | /// Address of the liquidity pair. 6 | pub lb_pair: Pubkey, 7 | /// Lower bound of the bin range. 8 | #[clap(long, allow_negative_numbers = true)] 9 | pub lower_bin_id: i32, 10 | /// Width of the position. Start with 1 until 70. 11 | pub width: i32, 12 | } 13 | 14 | pub async fn execute_initialize_position + Clone>( 15 | params: InitPositionParams, 16 | program: &Program, 17 | transaction_config: RpcSendTransactionConfig, 18 | ) -> Result { 19 | let InitPositionParams { 20 | lb_pair, 21 | lower_bin_id, 22 | width, 23 | } = params; 24 | 25 | let position_keypair = Keypair::new(); 26 | 27 | let (event_authority, _bump) = derive_event_authority_pda(); 28 | 29 | let accounts: [AccountMeta; INITIALIZE_POSITION_IX_ACCOUNTS_LEN] = InitializePositionKeys { 30 | lb_pair, 31 | payer: program.payer(), 32 | position: position_keypair.pubkey(), 33 | owner: program.payer(), 34 | rent: solana_sdk::sysvar::rent::ID, 35 | system_program: solana_sdk::system_program::ID, 36 | event_authority, 37 | program: dlmm_interface::ID, 38 | } 39 | .into(); 40 | 41 | let data = InitializePositionIxData(InitializePositionIxArgs { 42 | lower_bin_id, 43 | width, 44 | }) 45 | .try_to_vec()?; 46 | 47 | let init_position_ix = Instruction { 48 | program_id: dlmm_interface::ID, 49 | data, 50 | accounts: accounts.to_vec(), 51 | }; 52 | 53 | let request_builder = program.request(); 54 | let signature = request_builder 55 | .instruction(init_position_ix) 56 | .signer(&position_keypair) 57 | .send_with_spinner_and_config(transaction_config) 58 | .await; 59 | 60 | println!( 61 | "Initialize position {}. Signature: {signature:#?}", 62 | position_keypair.pubkey() 63 | ); 64 | 65 | signature?; 66 | 67 | Ok(position_keypair.pubkey()) 68 | } 69 | -------------------------------------------------------------------------------- /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.async_rpc(); 29 | let lb_pair_state = rpc_client 30 | .get_account_and_deserialize(&lb_pair, |account| { 31 | Ok(LbPairAccount::deserialize(&account.data)?.0) 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/list_all_binstep.rs: -------------------------------------------------------------------------------- 1 | use solana_client::{ 2 | rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig}, 3 | rpc_filter::{Memcmp, RpcFilterType}, 4 | }; 5 | 6 | use crate::*; 7 | 8 | pub async fn execute_list_all_bin_step + Clone>( 9 | program: &Program, 10 | ) -> Result<()> { 11 | let rpc_client = program.async_rpc(); 12 | 13 | let account_config = RpcAccountInfoConfig { 14 | encoding: Some(UiAccountEncoding::Base64), 15 | data_slice: Some(UiDataSliceConfig { 16 | offset: 0, 17 | length: 0, 18 | }), 19 | ..Default::default() 20 | }; 21 | 22 | let preset_parameter_keys = rpc_client 23 | .get_program_accounts_with_config( 24 | &dlmm_interface::ID, 25 | RpcProgramAccountsConfig { 26 | filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( 27 | 0, 28 | &PRESET_PARAMETER_ACCOUNT_DISCM, 29 | ))]), 30 | account_config: account_config.clone(), 31 | ..Default::default() 32 | }, 33 | ) 34 | .await? 35 | .into_iter() 36 | .map(|(key, _)| key) 37 | .collect::>(); 38 | 39 | let preset_parameter_v2_keys = rpc_client 40 | .get_program_accounts_with_config( 41 | &dlmm_interface::ID, 42 | RpcProgramAccountsConfig { 43 | filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded( 44 | 0, 45 | &PRESET_PARAMETER2_ACCOUNT_DISCM, 46 | ))]), 47 | account_config, 48 | ..Default::default() 49 | }, 50 | ) 51 | .await? 52 | .into_iter() 53 | .map(|(key, _)| key) 54 | .collect::>(); 55 | 56 | let all_versioned_keys = [preset_parameter_keys, preset_parameter_v2_keys].concat(); 57 | 58 | for keys in all_versioned_keys.chunks(100) { 59 | let accounts = rpc_client.get_multiple_accounts(keys).await?; 60 | for (key, account) in keys.iter().zip(accounts) { 61 | if let Some(account) = account { 62 | let mut disc = [0u8; 8]; 63 | disc.copy_from_slice(&account.data[..8]); 64 | 65 | let (bin_step, base_factor, base_fee_power_factor) = match disc { 66 | PRESET_PARAMETER_ACCOUNT_DISCM => { 67 | let state = PresetParameterAccount::deserialize(&account.data)?.0; 68 | (state.bin_step, state.base_factor, 0) 69 | } 70 | PRESET_PARAMETER2_ACCOUNT_DISCM => { 71 | let state = PresetParameter2Account::deserialize(&account.data)?.0; 72 | ( 73 | state.bin_step, 74 | state.base_factor, 75 | state.base_fee_power_factor, 76 | ) 77 | } 78 | _ => continue, 79 | }; 80 | 81 | let base_fee = (u128::from(bin_step) 82 | * u128::from(base_factor).pow(base_fee_power_factor.into()) 83 | * 1000) as f64 84 | / FEE_PRECISION as f64; 85 | 86 | println!( 87 | "Preset Pubkey: {}. Bin step {}. Base fee: {}%", 88 | key, bin_step, base_fee 89 | ); 90 | } 91 | } 92 | } 93 | 94 | Ok(()) 95 | } 96 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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: [AccountMeta; SET_PAIR_STATUS_PERMISSIONLESS_IX_ACCOUNTS_LEN] = 19 | SetPairStatusPermissionlessKeys { 20 | creator: program.payer(), 21 | lb_pair, 22 | } 23 | .into(); 24 | 25 | let status = if enable { 1 } else { 0 }; 26 | 27 | let data = SetPairStatusIxData(SetPairStatusIxArgs { status }).try_to_vec()?; 28 | 29 | let set_pair_status_permissionless_ix = Instruction { 30 | accounts: accounts.to_vec(), 31 | data, 32 | program_id: dlmm_interface::ID, 33 | }; 34 | 35 | let request_builder = program.request(); 36 | let signature = request_builder 37 | .instruction(set_pair_status_permissionless_ix) 38 | .send_with_spinner_and_config(transaction_config) 39 | .await; 40 | 41 | println!( 42 | "Set pair status permissionless. Signature: {:#?}", 43 | signature 44 | ); 45 | 46 | signature?; 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /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.async_rpc(); 27 | 28 | let lb_pair_state = rpc_client 29 | .get_account_and_deserialize(&lb_pair, |account| { 30 | Ok(LbPairAccount::deserialize(&account.data)?.0) 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_interface::ID, config) 47 | .await? 48 | .into_iter() 49 | .filter_map(|(key, account)| { 50 | let bin_array = BinArrayAccount::deserialize(&account.data).ok()?; 51 | Some((key, bin_array.0)) 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/show_position.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct ShowPositionParams { 5 | pub position: Pubkey, 6 | } 7 | 8 | pub async fn execute_show_position + Clone>( 9 | params: ShowPositionParams, 10 | program: &Program, 11 | ) -> Result<()> { 12 | let ShowPositionParams { position } = params; 13 | 14 | let rpc_client = program.async_rpc(); 15 | let position_account = rpc_client.get_account(&position).await?; 16 | 17 | let mut disc = [0u8; 8]; 18 | disc.copy_from_slice(&position_account.data[..8]); 19 | 20 | match disc { 21 | POSITION_ACCOUNT_DISCM => { 22 | let position_state = PositionAccount::deserialize(&position_account.data)?.0; 23 | println!("{:#?}", position_state); 24 | } 25 | POSITION_V2_ACCOUNT_DISCM => { 26 | let position_state = PositionV2Account::deserialize(&position_account.data)?.0; 27 | println!("{:#?}", position_state); 28 | } 29 | _ => { 30 | bail!("Not a valid position account"); 31 | } 32 | }; 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /cli/src/instructions/show_preset_parameters.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | 3 | #[derive(Debug, Parser)] 4 | pub struct ShowPresetAccountParams { 5 | pub preset_parameter: Pubkey, 6 | } 7 | 8 | pub async fn execute_show_preset_parameters + Clone>( 9 | params: ShowPresetAccountParams, 10 | program: &Program, 11 | ) -> Result<()> { 12 | let ShowPresetAccountParams { preset_parameter } = params; 13 | 14 | let rpc_client = program.async_rpc(); 15 | let account = rpc_client.get_account(&preset_parameter).await?; 16 | 17 | let mut disc = [0u8; 8]; 18 | disc.copy_from_slice(&account.data[..8]); 19 | 20 | match disc { 21 | PRESET_PARAMETER_ACCOUNT_DISCM => { 22 | let preset_param_state = PresetParameterAccount::deserialize(&account.data)?.0; 23 | println!("{:#?}", preset_param_state); 24 | } 25 | PRESET_PARAMETER2_ACCOUNT_DISCM => { 26 | let preset_param_state = PresetParameter2Account::deserialize(&account.data)?.0; 27 | println!("{:#?}", preset_param_state); 28 | } 29 | _ => bail!("Not a valid preset parameter account"), 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /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/swap_exact_out.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_spl::associated_token::get_associated_token_address; 3 | 4 | #[derive(Debug, Parser)] 5 | pub struct SwapExactOutParams { 6 | /// Address of the liquidity pair. 7 | pub lb_pair: Pubkey, 8 | /// Amount of token to be buy. 9 | pub amount_out: u64, 10 | /// Buy direction. true = buy token Y, false = buy token X. 11 | #[clap(long)] 12 | pub swap_for_y: bool, 13 | } 14 | 15 | pub async fn execute_swap_exact_out + Clone>( 16 | params: SwapExactOutParams, 17 | program: &Program, 18 | transaction_config: RpcSendTransactionConfig, 19 | ) -> Result<()> { 20 | let SwapExactOutParams { 21 | amount_out, 22 | lb_pair, 23 | swap_for_y, 24 | } = params; 25 | 26 | let rpc_client = program.async_rpc(); 27 | let lb_pair_state = rpc_client 28 | .get_account_and_deserialize(&lb_pair, |account| { 29 | Ok(LbPairAccount::deserialize(&account.data)?.0) 30 | }) 31 | .await?; 32 | 33 | let (user_token_in, user_token_out) = if swap_for_y { 34 | ( 35 | get_associated_token_address(&program.payer(), &lb_pair_state.token_x_mint), 36 | get_associated_token_address(&program.payer(), &lb_pair_state.token_y_mint), 37 | ) 38 | } else { 39 | ( 40 | get_associated_token_address(&program.payer(), &lb_pair_state.token_y_mint), 41 | get_associated_token_address(&program.payer(), &lb_pair_state.token_x_mint), 42 | ) 43 | }; 44 | 45 | let (bitmap_extension_key, _bump) = derive_bin_array_bitmap_extension(lb_pair); 46 | 47 | let bitmap_extension = rpc_client 48 | .get_account_and_deserialize(&bitmap_extension_key, |account| { 49 | Ok(BinArrayBitmapExtensionAccount::deserialize(&account.data)?.0) 50 | }) 51 | .await 52 | .ok(); 53 | 54 | let bin_arrays_for_swap = get_bin_array_pubkeys_for_swap( 55 | lb_pair, 56 | &lb_pair_state, 57 | bitmap_extension.as_ref(), 58 | swap_for_y, 59 | 3, 60 | )?; 61 | 62 | let SwapQuoteAccounts { 63 | lb_pair_state, 64 | clock, 65 | mint_x_account, 66 | mint_y_account, 67 | bin_arrays, 68 | bin_array_keys, 69 | } = fetch_quote_required_accounts(&rpc_client, lb_pair, &lb_pair_state, bin_arrays_for_swap) 70 | .await?; 71 | 72 | let quote = quote_exact_out( 73 | lb_pair, 74 | &lb_pair_state, 75 | amount_out, 76 | swap_for_y, 77 | bin_arrays, 78 | bitmap_extension.as_ref(), 79 | &clock, 80 | &mint_x_account, 81 | &mint_y_account, 82 | )?; 83 | 84 | let (event_authority, _bump) = derive_event_authority_pda(); 85 | 86 | let main_accounts: [AccountMeta; SWAP_EXACT_OUT2_IX_ACCOUNTS_LEN] = SwapExactOut2Keys { 87 | lb_pair, 88 | bin_array_bitmap_extension: bitmap_extension 89 | .map(|_| bitmap_extension_key) 90 | .unwrap_or(dlmm_interface::ID), 91 | reserve_x: lb_pair_state.reserve_x, 92 | reserve_y: lb_pair_state.reserve_y, 93 | token_x_mint: lb_pair_state.token_x_mint, 94 | token_y_mint: lb_pair_state.token_y_mint, 95 | token_x_program: anchor_spl::token::ID, 96 | token_y_program: anchor_spl::token::ID, 97 | user: program.payer(), 98 | user_token_in, 99 | user_token_out, 100 | oracle: lb_pair_state.oracle, 101 | host_fee_in: dlmm_interface::ID, 102 | event_authority, 103 | program: dlmm_interface::ID, 104 | memo_program: spl_memo::ID, 105 | } 106 | .into(); 107 | 108 | let in_amount = quote.amount_in + quote.fee; 109 | // 100 bps slippage 110 | let max_in_amount = in_amount * 10100 / BASIS_POINT_MAX as u64; 111 | 112 | let data = SwapExactOutIxData(SwapExactOutIxArgs { 113 | out_amount: amount_out, 114 | max_in_amount, 115 | }) 116 | .try_to_vec()?; 117 | 118 | let remaining_accounts = bin_array_keys 119 | .into_iter() 120 | .map(|key| AccountMeta::new(key, false)) 121 | .collect::>(); 122 | 123 | let accounts = [main_accounts.to_vec(), remaining_accounts].concat(); 124 | 125 | let swap_ix = Instruction { 126 | program_id: dlmm_interface::ID, 127 | accounts, 128 | data, 129 | }; 130 | 131 | let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(1_400_000); 132 | 133 | let request_builder = program.request(); 134 | let signature = request_builder 135 | .instruction(compute_budget_ix) 136 | .instruction(swap_ix) 137 | .send_with_spinner_and_config(transaction_config) 138 | .await; 139 | 140 | println!("Swap. Signature: {:#?}", signature); 141 | 142 | signature?; 143 | 144 | Ok(()) 145 | } 146 | -------------------------------------------------------------------------------- /cli/src/math.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use commons::{BASIS_POINT_MAX, SCALE_OFFSET}; 3 | use dlmm_interface::Rounding; 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "commons" 3 | version = "0.3.1" 4 | edition = "2021" 5 | description = "Common libraries for DLMM" 6 | authors = ["tian "] 7 | 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anchor-client = { workspace = true, features = ["async"] } 13 | anchor-spl = { workspace = true } 14 | anyhow = { workspace = true } 15 | dlmm_interface = { path = "../dlmm_interface" } 16 | tokio = { workspace = true, features = ["full", "parking_lot"] } 17 | bincode = { workspace = true } 18 | solana-sdk = { workspace = true } 19 | ruint = { workspace = true } 20 | num-traits = { workspace = true } 21 | num-integer = { workspace = true } 22 | bytemuck = { workspace = true } 23 | async-trait = { workspace = true } 24 | spl-transfer-hook-interface = { workspace = true } 25 | 26 | [dev-dependencies] 27 | spl-associated-token-account = { workspace = true } 28 | solana-program-test = "1.17.0" 29 | assert_matches = "1.5.0" 30 | solana-program = "1.17.0" 31 | spl-memo = { workspace = true, features = ["no-entrypoint"] } 32 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /commons/src/conversions/activation_type.rs: -------------------------------------------------------------------------------- 1 | use dlmm_interface::ActivationType; 2 | use std::ops::Deref; 3 | 4 | pub struct ActivationTypeWrapper(ActivationType); 5 | 6 | impl Deref for ActivationTypeWrapper { 7 | type Target = ActivationType; 8 | 9 | fn deref(&self) -> &Self::Target { 10 | &self.0 11 | } 12 | } 13 | 14 | impl TryFrom for ActivationTypeWrapper { 15 | type Error = anyhow::Error; 16 | 17 | fn try_from(value: u8) -> Result { 18 | match value { 19 | 0 => Ok(ActivationTypeWrapper(ActivationType::Slot)), 20 | 1 => Ok(ActivationTypeWrapper(ActivationType::Timestamp)), 21 | _ => Err(anyhow::anyhow!("Invalid ActivationType value: {}", value)), 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /commons/src/conversions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod status; 2 | pub use status::*; 3 | 4 | pub mod pair_type; 5 | pub use pair_type::*; 6 | 7 | pub mod activation_type; 8 | pub use activation_type::*; 9 | 10 | pub mod token_program_flag; 11 | pub use token_program_flag::*; 12 | -------------------------------------------------------------------------------- /commons/src/conversions/pair_type.rs: -------------------------------------------------------------------------------- 1 | use dlmm_interface::PairType; 2 | use std::ops::Deref; 3 | 4 | pub struct PairTypeWrapper(PairType); 5 | 6 | impl Deref for PairTypeWrapper { 7 | type Target = PairType; 8 | 9 | fn deref(&self) -> &Self::Target { 10 | &self.0 11 | } 12 | } 13 | 14 | impl TryFrom for PairTypeWrapper { 15 | type Error = anyhow::Error; 16 | 17 | fn try_from(value: u8) -> Result { 18 | match value { 19 | 0 => Ok(PairTypeWrapper(PairType::Permissionless)), 20 | 1 => Ok(PairTypeWrapper(PairType::Permission)), 21 | 2 => Ok(PairTypeWrapper(PairType::CustomizablePermissionless)), 22 | 3 => Ok(PairTypeWrapper(PairType::PermissionlessV2)), 23 | _ => Err(anyhow::anyhow!("Invalid PairType value: {}", value)), 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /commons/src/conversions/status.rs: -------------------------------------------------------------------------------- 1 | use dlmm_interface::PairStatus; 2 | use std::ops::Deref; 3 | 4 | pub struct PairStatusWrapper(PairStatus); 5 | 6 | impl Deref for PairStatusWrapper { 7 | type Target = PairStatus; 8 | 9 | fn deref(&self) -> &Self::Target { 10 | &self.0 11 | } 12 | } 13 | 14 | impl TryFrom for PairStatusWrapper { 15 | type Error = anyhow::Error; 16 | 17 | fn try_from(value: u8) -> Result { 18 | match value { 19 | 0 => Ok(PairStatusWrapper(PairStatus::Enabled)), 20 | 1 => Ok(PairStatusWrapper(PairStatus::Disabled)), 21 | _ => Err(anyhow::anyhow!("Invalid PairStatus value: {}", value)), 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /commons/src/conversions/token_program_flag.rs: -------------------------------------------------------------------------------- 1 | use dlmm_interface::TokenProgramFlags; 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /commons/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::*; 2 | use dlmm_interface::*; 3 | 4 | pub mod constants; 5 | pub use constants::*; 6 | 7 | pub mod conversions; 8 | pub use conversions::*; 9 | 10 | pub mod extensions; 11 | pub use extensions::*; 12 | 13 | pub mod pda; 14 | pub use pda::*; 15 | 16 | pub mod quote; 17 | pub use quote::*; 18 | 19 | pub mod seeds; 20 | pub use seeds::*; 21 | 22 | pub mod math; 23 | pub use math::*; 24 | 25 | pub mod typedefs; 26 | pub use typedefs::*; 27 | 28 | pub mod rpc_client_extension; 29 | 30 | pub mod account_filters; 31 | pub use account_filters::*; 32 | 33 | pub mod token_2022; 34 | pub use token_2022::*; 35 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /commons/src/math/u128x128_math.rs: -------------------------------------------------------------------------------- 1 | use dlmm_interface::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 | -------------------------------------------------------------------------------- /commons/src/math/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use dlmm_interface::Rounding; 3 | use num_traits::FromPrimitive; 4 | 5 | #[inline] 6 | pub fn safe_mul_shr_cast( 7 | x: u128, 8 | y: u128, 9 | offset: u8, 10 | rounding: Rounding, 11 | ) -> Result { 12 | T::from_u128(mul_shr(x, y, offset, rounding).context("overflow")?).context("overflow") 13 | } 14 | 15 | #[inline] 16 | pub fn safe_shl_div_cast( 17 | x: u128, 18 | y: u128, 19 | offset: u8, 20 | rounding: Rounding, 21 | ) -> Result { 22 | T::from_u128(shl_div(x, y, offset, rounding).context("overflow")?).context("overflow") 23 | } 24 | 25 | pub fn safe_mul_div_cast( 26 | x: u128, 27 | y: u128, 28 | denominator: u128, 29 | rounding: Rounding, 30 | ) -> Result { 31 | T::from_u128(mul_div(x, y, denominator, rounding).context("overflow")?).context("overflow") 32 | } 33 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /commons/tests/artifacts/lb_clmm_prod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/artifacts/lb_clmm_prod.so -------------------------------------------------------------------------------- /commons/tests/artifacts/token_2022.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/artifacts/token_2022.so -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/bin_array_1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/bin_array_1.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/bin_array_2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/bin_array_2.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/lb_pair.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/lb_pair.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/oracle.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/oracle.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/reserve_x.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/reserve_x.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/reserve_y.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/reserve_y.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/token_x_mint.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/B5Eia4cE71tKuEDaqPHucJLG2fxySKyKzLMewd2nUvoc/token_x_mint.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/bin_array_1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/bin_array_1.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/bin_array_2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/bin_array_2.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/lb_pair.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/lb_pair.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/oracle.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/oracle.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/reserve_x.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/reserve_x.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/reserve_y.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/reserve_y.bin -------------------------------------------------------------------------------- /commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/token_x_mint.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/commons/tests/fixtures/EtAdVRLFH22rjWh3mcUasKFF27WtHhsaCvK27tPFFWig/token_x_mint.bin -------------------------------------------------------------------------------- /commons/tests/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | -------------------------------------------------------------------------------- /commons/tests/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 | -------------------------------------------------------------------------------- /dlmm_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /dlmm_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dlmm_interface" 3 | version = "0.9.0" 4 | edition = "2021" 5 | 6 | [features] 7 | staging = [] 8 | 9 | [dependencies.borsh] 10 | version = "^1.5" 11 | 12 | [dependencies.bytemuck] 13 | features = ["derive", "min_const_generics"] 14 | version = "^1.16" 15 | 16 | [dependencies.num-derive] 17 | version = "0.4.2" 18 | 19 | [dependencies.num-traits] 20 | version = "^0.2" 21 | 22 | [dependencies.serde] 23 | optional = true 24 | version = "^1.0" 25 | 26 | [dependencies.solana-program] 27 | version = "^1.16" 28 | 29 | [dependencies.thiserror] 30 | version = "^1.0" 31 | -------------------------------------------------------------------------------- /dlmm_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "staging"))] 2 | solana_program::declare_id!("LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"); 3 | 4 | #[cfg(feature = "staging")] 5 | solana_program::declare_id!("tLBro6JJuZNnpoad3p8pXKohE9f7f7tBZJpaeh6pXt1"); 6 | 7 | pub mod accounts; 8 | pub use accounts::*; 9 | pub mod typedefs; 10 | pub use typedefs::*; 11 | pub mod instructions; 12 | pub use instructions::*; 13 | pub mod errors; 14 | pub use errors::*; 15 | pub mod events; 16 | pub use events::*; 17 | -------------------------------------------------------------------------------- /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] -------------------------------------------------------------------------------- /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 | dlmm_interface = { path = "../dlmm_interface" } 22 | serde_json = { workspace = true } 23 | serde = { workspace = true, features = ["derive"] } 24 | spl-associated-token-account = { workspace = true } 25 | solana-transaction-status = { workspace = true } 26 | bs58 = { workspace = true } 27 | chrono = { workspace = true } 28 | commons = { workspace = true } 29 | solana-account-decoder = { workspace = true } 30 | itertools = { workspace = true } 31 | rust_decimal = { workspace = true, features = ["maths"] } 32 | spl-memo = { workspace = true, features = ["no-entrypoint"] } 33 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | ] -------------------------------------------------------------------------------- /market_making/src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod bin_array_manager; 2 | pub mod core; 3 | pub mod pair_config; 4 | pub mod router; 5 | pub mod state; 6 | pub mod utils; 7 | 8 | use crate::pair_config::*; 9 | use crate::state::*; 10 | use crate::utils::*; 11 | 12 | use anchor_client::solana_client::nonblocking::rpc_client::*; 13 | use anchor_client::solana_sdk::pubkey::*; 14 | use anchor_client::solana_sdk::signature::*; 15 | use anchor_client::solana_sdk::*; 16 | use anchor_client::*; 17 | use solana_account_decoder::*; 18 | 19 | use anyhow::*; 20 | use commons::extensions::*; 21 | use commons::pda::*; 22 | use commons::rpc_client_extension::*; 23 | use commons::*; 24 | use dlmm_interface::*; 25 | 26 | use serde::*; 27 | use solana_client::rpc_config::*; 28 | 29 | use clap::Parser; 30 | use core::Core; 31 | use hyper::Server; 32 | use router::router; 33 | use routerify::RouterService; 34 | use std::convert::Into; 35 | use std::fmt::Debug; 36 | use std::str::FromStr; 37 | use std::sync::Arc; 38 | use std::sync::Mutex; 39 | use std::time::Duration; 40 | 41 | #[macro_use] 42 | extern crate log; 43 | 44 | use tokio::time::interval; 45 | 46 | #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 47 | pub enum MarketMakingMode { 48 | ModeRight, 49 | ModeLeft, 50 | ModeBoth, 51 | ModeView, 52 | } 53 | 54 | impl Default for MarketMakingMode { 55 | fn default() -> Self { 56 | MarketMakingMode::ModeView 57 | } 58 | } 59 | 60 | impl FromStr for MarketMakingMode { 61 | type Err = anyhow::Error; 62 | 63 | fn from_str(s: &str) -> Result { 64 | match s.to_ascii_lowercase().as_str() { 65 | "moderight" => Ok(MarketMakingMode::ModeRight), 66 | "modeleft" => Ok(MarketMakingMode::ModeLeft), 67 | "modeboth" => Ok(MarketMakingMode::ModeBoth), 68 | "modeview" => Ok(MarketMakingMode::ModeView), 69 | _ => Err(anyhow::Error::msg("cannot get mode")), 70 | } 71 | } 72 | } 73 | 74 | #[derive(Parser, Debug)] 75 | pub struct Args { 76 | /// Solana RPC provider. For example: https://api.mainnet-beta.solana.com 77 | #[clap(long, default_value_t = Cluster::Localnet)] 78 | provider: Cluster, 79 | /// Wallet of owner 80 | #[clap(long)] 81 | wallet: Option, 82 | /// Address of owner, only user_public_key or wallet is set, other wise it is panic immediately 83 | #[clap(long)] 84 | user_public_key: Option, 85 | /// config path 86 | #[clap(long)] 87 | config_file: String, 88 | } 89 | 90 | #[tokio::main] 91 | async fn main() -> Result<()> { 92 | env_logger::init(); 93 | 94 | let Args { 95 | provider, 96 | wallet, 97 | user_public_key, 98 | config_file, 99 | } = Args::parse(); 100 | 101 | let config = get_config_from_file(&config_file)?; 102 | let wallet = wallet.and_then(|path| read_keypair_file(path).ok()); 103 | 104 | let user_wallet = if should_market_making(&config) { 105 | wallet.as_ref().context("Require keypair")?.pubkey() 106 | } else { 107 | user_public_key.unwrap() 108 | }; 109 | 110 | let core = Core { 111 | provider, 112 | wallet: wallet.map(Arc::new), 113 | owner: user_wallet, 114 | config: config.clone(), 115 | state: Arc::new(Mutex::new(AllPosition::new(&config))), 116 | }; 117 | 118 | // init some state 119 | core.refresh_state().await.unwrap(); 120 | core.fetch_token_info().await.unwrap(); 121 | 122 | let core = Arc::new(core); 123 | 124 | let mut handles = vec![]; 125 | { 126 | // crawl epoch down 127 | let core = core.clone(); 128 | let handle = tokio::spawn(async move { 129 | let duration = 60; // 1 min 130 | let mut interval = interval(Duration::from_secs(duration)); 131 | loop { 132 | interval.tick().await; 133 | info!("refresh state"); 134 | if let Err(err) = core.refresh_state().await { 135 | error!("refresh_state err {}", err) 136 | }; 137 | } 138 | }); 139 | handles.push(handle); 140 | } 141 | 142 | if should_market_making(&config) { 143 | { 144 | // crawl epoch down 145 | let core = core.clone(); 146 | 147 | // init user ata 148 | core.init_user_ata().await.unwrap(); 149 | 150 | let handle = tokio::spawn(async move { 151 | let duration = 60; // 1 min 152 | let mut interval = interval(Duration::from_secs(duration)); 153 | loop { 154 | interval.tick().await; 155 | info!("check shift price range"); 156 | if let Err(err) = core.check_shift_price_range().await { 157 | error!("check shift price err {}", err) 158 | } 159 | } 160 | }); 161 | handles.push(handle); 162 | } 163 | } 164 | 165 | let router = router(core); 166 | 167 | let service = RouterService::new(router).unwrap(); 168 | 169 | let addr = ([0, 0, 0, 0], 8080).into(); 170 | 171 | let server = Server::bind(&addr).serve(service); 172 | 173 | server.await.unwrap(); 174 | 175 | for handle in handles { 176 | handle.await.unwrap(); 177 | } 178 | 179 | Ok(()) 180 | } 181 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_interface::events::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 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dlmm-sdk", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "dependencies": { 8 | "pako": "^2.1.0" 9 | } 10 | }, 11 | "node_modules/pako": { 12 | "version": "2.1.0", 13 | "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", 14 | "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==" 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "pako": "^2.1.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /python-client/dlmm/.gitignore: -------------------------------------------------------------------------------- 1 | */__pycache__ -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /python-client/dlmm/dist/dlmm-0.1.0-py3-none-any.whl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/python-client/dlmm/dist/dlmm-0.1.0-py3-none-any.whl -------------------------------------------------------------------------------- /python-client/dlmm/dist/dlmm-0.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/python-client/dlmm/dist/dlmm-0.1.0.tar.gz -------------------------------------------------------------------------------- /python-client/dlmm/dlmm/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.1.0" 2 | 3 | from .dlmm import DLMM_CLIENT -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /python-client/dlmm/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dlmm-sdk/4b6f4ea39db7d41e5d430cc74887df61e2806a1b/python-client/dlmm/tests/__init__.py -------------------------------------------------------------------------------- /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 | 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]) 23 | total_interval_range = 10 24 | max_bin_id = active_bin.bin_id + total_interval_range 25 | min_bin_id = active_bin.bin_id - total_interval_range 26 | total_x_amount = 100 27 | total_y_amount = total_x_amount * int(active_bin_price_per_token) 28 | 29 | position_tx = dlmm.initialize_position_and_add_liquidity_by_strategy( 30 | new_balance_position.pubkey(), 31 | user.pubkey(), 32 | total_x_amount, 33 | total_y_amount, 34 | { 35 | "max_bin_id": max_bin_id, 36 | "min_bin_id": min_bin_id, 37 | "strategy_type": StrategyType.SpotBalanced 38 | }) 39 | 40 | assert isinstance(position_tx, Transaction) 41 | 42 | client.send_transaction(position_tx, user, new_balance_position) 43 | print("Transaction sent") 44 | 45 | positions = dlmm.get_positions_by_user_and_lb_pair(user.pubkey()) 46 | assert isinstance(positions, GetPositionByUser) 47 | 48 | add_liquidity_tx = dlmm.add_liquidity_by_strategy( 49 | new_balance_position.pubkey(), 50 | user.pubkey(), 51 | total_x_amount, 52 | total_y_amount, 53 | { 54 | "max_bin_id": max_bin_id, 55 | "min_bin_id": min_bin_id, 56 | "strategy_type": StrategyType.SpotBalanced 57 | }) 58 | assert isinstance(add_liquidity_tx, Transaction) 59 | 60 | client.send_transaction(add_liquidity_tx, user) 61 | print("Transaction sent") 62 | 63 | user_positions = next(filter(lambda x: x.public_key == new_balance_position.pubkey() ,positions.user_positions), None) 64 | 65 | if user_positions: 66 | bin_ids_to_remove = list(map(lambda x: x.bin_id, user_positions.position_data.position_bin_data)) 67 | remove_liquidity = dlmm.remove_liqidity( 68 | new_balance_position.pubkey(), 69 | user.pubkey(), 70 | bin_ids_to_remove, 71 | 100*100, 72 | True 73 | ) 74 | assert isinstance(remove_liquidity, list) 75 | client.send_transaction(remove_liquidity, user) 76 | 77 | swap_amount = 100 78 | swap_y_to_x = True 79 | bin_arrays = dlmm.get_bin_array_for_swap(swap_y_to_x) 80 | swap_quote = dlmm.swap_quote(swap_amount, swap_y_to_x, 10, bin_arrays) 81 | assert isinstance(swap_quote, SwapQuote) 82 | 83 | swap_tx = dlmm.swap( 84 | dlmm.token_X.public_key, 85 | dlmm.token_Y.public_key, 86 | swap_amount, 87 | swap_quote.min_out_amount, 88 | dlmm.pool_address, 89 | user.pubkey(), 90 | swap_quote.bin_arrays_pubkey 91 | ) 92 | assert isinstance(swap_tx, Transaction) 93 | 94 | client.send_transaction(swap_tx, user) 95 | 96 | 97 | -------------------------------------------------------------------------------- /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 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 | 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]) 17 | 18 | bin_arrays = dlmm.get_bin_arrays() 19 | assert type(bin_arrays) == list 20 | 21 | fee_info = dlmm.get_fee_info() 22 | assert isinstance(fee_info, FeeInfo) 23 | 24 | dynamic_fee = dlmm.get_dynamic_fee() 25 | assert type(dynamic_fee) == float 26 | 27 | check_bin = bin_arrays[0]["account"]["bins"][0] 28 | bin_id = dlmm.get_bin_id_from_price(float(check_bin["price"]), True) 29 | assert isinstance(bin_id, int) or bin_id is None 30 | 31 | bins_around_active = dlmm.get_bins_around_active_bin(0, 0) 32 | assert isinstance(bins_around_active, GetBins) 33 | 34 | bins_between_upper_lower = dlmm.get_bins_between_lower_and_upper_bound(0, 2) 35 | assert isinstance(bins_between_upper_lower, GetBins) 36 | 37 | bins_between_min_max = dlmm.get_bins_between_min_and_max_price(0.0, 50.0) 38 | assert isinstance(bins_between_min_max, GetBins) 39 | 40 | positions = dlmm.get_positions_by_user_and_lb_pair(user.pubkey()) 41 | user_positions = next(filter(lambda x: x.public_key == new_balance_position.pubkey() ,positions.user_positions), None) 42 | if user_positions: 43 | claim_lm = dlmm.claim_LM_reward(new_balance_position.pubkey(), user_positions) 44 | assert isinstance(claim_lm, Transaction) 45 | 46 | claim_swap_fee = dlmm.claim_swap_fee(new_balance_position.pubkey(), user_positions) 47 | assert isinstance(claim_swap_fee, Transaction) 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /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 * 90, 11 | }; 12 | -------------------------------------------------------------------------------- /ts-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meteora-ag/dlmm", 3 | "version": "1.5.3", 4 | "description": "", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "source": "./src/index.ts", 8 | "types": "./dist/index.d.ts", 9 | "files": [ 10 | "dist/**" 11 | ], 12 | "scripts": { 13 | "build": "tsup", 14 | "start": "npm run build -- --watch", 15 | "test": "jest 'src/test/(ilm|sdk_token2022|sdk|single_bin|token_2022).test.ts'", 16 | "unit-test": "jest src/test/calculate_distribution.test.ts", 17 | "example": "dotenv -e .env npx ts-node src/examples/example.ts", 18 | "start-server": "npx tsc && node dist/src/server/index.js" 19 | }, 20 | "devDependencies": { 21 | "@babel/preset-env": "^7.22.5", 22 | "@types/babar": "^0.2.1", 23 | "@types/bn.js": "^5.1.5", 24 | "@types/express": "^4.17.21", 25 | "@types/gaussian": "^1.2.0", 26 | "@types/jest": "^29.5.2", 27 | "babar": "^0.2.3", 28 | "babel-jest": "^29.5.0", 29 | "dotenv-cli": "^7.2.1", 30 | "jest": "^29.5.0", 31 | "ts-jest": "^29.1.1", 32 | "tsup": "^6.7.0", 33 | "typescript": "^5.0.4" 34 | }, 35 | "dependencies": { 36 | "@coral-xyz/anchor": "^0.28.0", 37 | "@coral-xyz/borsh": "^0.28.0", 38 | "@solana/buffer-layout": "^4.0.1", 39 | "@solana/spl-token": "^0.4.6", 40 | "@solana/web3.js": "^1.91.6", 41 | "bn.js": "^5.2.1", 42 | "decimal.js": "^10.4.2", 43 | "express": "^4.19.2", 44 | "gaussian": "^1.3.0" 45 | }, 46 | "keywords": [], 47 | "author": "McSam", 48 | "license": "ISC" 49 | } 50 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/constants/index.ts: -------------------------------------------------------------------------------- 1 | import { LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js"; 2 | import { IDL } from "../idl"; 3 | import { BN } from "@coral-xyz/anchor"; 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 ADMIN = { 13 | devnet: "6WaLrrRfReGKBYUSkmx2K6AuT21ida4j8at2SUiZdXu8", 14 | localhost: "bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1", 15 | }; 16 | 17 | export enum Network { 18 | MAINNET = "mainnet-beta", 19 | TESTNET = "testnet", 20 | DEVNET = "devnet", 21 | LOCAL = "localhost", 22 | } 23 | 24 | export const BASIS_POINT_MAX = 10000; 25 | export const SCALE_OFFSET = 64; 26 | export const SCALE = new BN(1).shln(SCALE_OFFSET); 27 | 28 | export const FEE_PRECISION = new BN(1_000_000_000); 29 | export const MAX_FEE_RATE = new BN(100_000_000); 30 | // https://solscan.io/tx/5JgHgEiVoqV61p3SASYzP4gnedvYFLhewPchBdFgPQZjHEiitjZCqs8u4rXyDYnGJ9zqAscknv9NoBiodsfDE1qR 31 | export const BIN_ARRAY_FEE = 0.07143744; 32 | // https://solscan.io/tx/37yEmHsTU6tKjUc6iGG8GPiEuPHxiyBezwexsnnsqXQQKuDgwsNciEzkQZFWJShcdLpfug5xqNBPJkzit7eWvkDD 33 | export const POSITION_FEE = 0.05740608; 34 | export const TOKEN_ACCOUNT_FEE = 0.00203928; 35 | // https://solscan.io/tx/4QkTyVZbZgS3Go7ksEWzmHef7SBVgoJ8Fjjxk3eL9LZBBmrXHJarVM4TPy5Nq3XcjwdhWALeCCbL7xonExBGpNry 36 | export const POOL_FEE = 0.00718272; 37 | export const BIN_ARRAY_BITMAP_FEE = 0.01180416; 38 | 39 | export const BIN_ARRAY_FEE_BN = new BN( 40 | new Decimal(BIN_ARRAY_FEE).mul(LAMPORTS_PER_SOL).toString() 41 | ); 42 | export const POSITION_FEE_BN = new BN( 43 | new Decimal(POSITION_FEE).mul(LAMPORTS_PER_SOL).toString() 44 | ); 45 | export const TOKEN_ACCOUNT_FEE_BN = new BN( 46 | new Decimal(TOKEN_ACCOUNT_FEE).mul(LAMPORTS_PER_SOL).toString() 47 | ); 48 | export const POOL_FEE_BN = new BN( 49 | new Decimal(POOL_FEE).mul(LAMPORTS_PER_SOL).toString() 50 | ); 51 | export const BIN_ARRAY_BITMAP_FEE_BN = new BN( 52 | new Decimal(BIN_ARRAY_BITMAP_FEE).mul(LAMPORTS_PER_SOL).toString() 53 | ); 54 | 55 | const CONSTANTS = Object.entries(IDL.constants); 56 | 57 | export const MAX_BIN_ARRAY_SIZE = new BN( 58 | CONSTANTS.find(([k, v]) => v.name == "MAX_BIN_PER_ARRAY")?.[1].value ?? 0 59 | ); 60 | export const MAX_BIN_PER_POSITION = new BN( 61 | CONSTANTS.find(([k, v]) => v.name == "MAX_BIN_PER_POSITION")?.[1].value ?? 0 62 | ); 63 | export const BIN_ARRAY_BITMAP_SIZE = new BN( 64 | CONSTANTS.find(([k, v]) => v.name == "BIN_ARRAY_BITMAP_SIZE")?.[1].value ?? 0 65 | ); 66 | export const EXTENSION_BINARRAY_BITMAP_SIZE = new BN( 67 | CONSTANTS.find(([k, v]) => v.name == "EXTENSION_BINARRAY_BITMAP_SIZE")?.[1] 68 | .value ?? 0 69 | ); 70 | 71 | export const SIMULATION_USER = new PublicKey( 72 | "HrY9qR5TiB2xPzzvbBu5KrBorMfYGQXh9osXydz4jy9s" 73 | ); 74 | 75 | export const PRECISION = 18446744073709551616; 76 | 77 | export const MAX_CLAIM_ALL_ALLOWED = 3; 78 | 79 | export const MAX_BIN_LENGTH_ALLOWED_IN_ONE_TX = 26; 80 | export const MAX_BIN_PER_TX = 69; 81 | 82 | export const MAX_ACTIVE_BIN_SLIPPAGE = 3; 83 | 84 | export const ILM_BASE = new PublicKey( 85 | "MFGQxwAmB91SwuYX36okv2Qmdc9aMuHTwWGUrp4AtB1" 86 | ); 87 | 88 | export const MAX_EXTRA_BIN_ARRAYS = 3; 89 | export const U64_MAX = new BN("18446744073709551615"); 90 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/error.ts: -------------------------------------------------------------------------------- 1 | import { IDL } from "./idl"; 2 | import { AnchorError, ProgramError } 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 = "SWAP_QUOTE_INSUFFICIENT_LIQUIDITY" | "INVALID_MAX_EXTRA_BIN_ARRAYS"; 50 | 51 | export class DlmmSdkError extends Error { 52 | name: ErrorName; 53 | message: string; 54 | 55 | constructor(name: ErrorName, message: string) { 56 | super(); 57 | this.name = name; 58 | this.message = message; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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 | 5 | export const presetParameter2BinStepFilter = ( 6 | binStep: BN 7 | ): GetProgramAccountsFilter => { 8 | return { 9 | memcmp: { 10 | bytes: bs58.encode(binStep.toArrayLike(Buffer, "le", 2)), 11 | offset: 8, 12 | }, 13 | }; 14 | }; 15 | 16 | export const presetParameter2BaseFactorFilter = ( 17 | baseFactor: BN 18 | ): GetProgramAccountsFilter => { 19 | return { 20 | memcmp: { 21 | bytes: bs58.encode(baseFactor.toArrayLike(Buffer, "le", 2)), 22 | offset: 8 + 2, 23 | }, 24 | }; 25 | }; 26 | 27 | export const presetParameter2BaseFeePowerFactor = ( 28 | baseFeePowerFactor: BN 29 | ): GetProgramAccountsFilter => { 30 | return { 31 | memcmp: { 32 | bytes: bs58.encode(baseFeePowerFactor.toArrayLike(Buffer, "le", 1)), 33 | offset: 8 + 22, 34 | }, 35 | }; 36 | }; 37 | 38 | export const binArrayLbPairFilter = ( 39 | lbPair: PublicKey 40 | ): GetProgramAccountsFilter => { 41 | return { 42 | memcmp: { 43 | bytes: lbPair.toBase58(), 44 | offset: 8 + 16, 45 | }, 46 | }; 47 | }; 48 | 49 | export const positionOwnerFilter = ( 50 | owner: PublicKey 51 | ): GetProgramAccountsFilter => { 52 | return { 53 | memcmp: { 54 | bytes: owner.toBase58(), 55 | offset: 8 + 32, 56 | }, 57 | }; 58 | }; 59 | 60 | export const positionLbPairFilter = ( 61 | lbPair: PublicKey 62 | ): GetProgramAccountsFilter => { 63 | return { 64 | memcmp: { 65 | bytes: bs58.encode(lbPair.toBuffer()), 66 | offset: 8, 67 | }, 68 | }; 69 | }; 70 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/computeUnit.ts: -------------------------------------------------------------------------------- 1 | import { AddressLookupTableAccount, Commitment, ComputeBudgetProgram, Connection, PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction } from "@solana/web3.js"; 2 | 3 | // https://solscan.io/tx/4ryJKTB1vYmGU6YnUWwbLps18FaJjiTwgRozcgdP8RFcwp7zUZi85vgWE7rARNx2NvzDJiM9CUWArqzY7LHv38WL 4 | export const DEFAULT_ADD_LIQUIDITY_CU = 800_000; 5 | 6 | export const MIN_CU_BUFFER = 50_000; 7 | export const MAX_CU_BUFFER = 200_000; 8 | 9 | export const getSimulationComputeUnits = async ( 10 | connection: Connection, 11 | instructions: Array, 12 | payer: PublicKey, 13 | lookupTables: Array | [], 14 | commitment: Commitment = "confirmed", 15 | ): Promise => { 16 | const testInstructions = [ 17 | // Set an arbitrarily high number in simulation 18 | // so we can be sure the transaction will succeed 19 | // and get the real compute units used 20 | ComputeBudgetProgram.setComputeUnitLimit({ units: 1_400_000 }), 21 | ...instructions, 22 | ]; 23 | 24 | const testTransaction = new VersionedTransaction( 25 | new TransactionMessage({ 26 | instructions: testInstructions, 27 | payerKey: payer, 28 | // RecentBlockhash can by any public key during simulation 29 | // since 'replaceRecentBlockhash' is set to 'true' below 30 | recentBlockhash: PublicKey.default.toString(), 31 | }).compileToV0Message(lookupTables), 32 | ); 33 | 34 | const rpcResponse = await connection.simulateTransaction(testTransaction, { 35 | replaceRecentBlockhash: true, 36 | sigVerify: false, 37 | commitment, 38 | }); 39 | 40 | if (rpcResponse?.value?.err) { 41 | const logs = rpcResponse.value.logs?.join("\n • ") || "No logs available"; 42 | throw new Error( 43 | `Transaction simulation failed:\n •${logs}` + 44 | JSON.stringify(rpcResponse?.value?.err), 45 | ); 46 | } 47 | 48 | return rpcResponse.value.unitsConsumed || null; 49 | }; 50 | -------------------------------------------------------------------------------- /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 { IDL } from "../idl"; 4 | import { LBCLMM_PROGRAM_IDS } from "../constants"; 5 | import { LbPair } from "../types"; 6 | import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from "@solana/spl-token"; 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 provider = new AnchorProvider( 23 | connection, 24 | {} as any, 25 | AnchorProvider.defaultOptions() 26 | ); 27 | const program = new Program( 28 | IDL, 29 | opt.programId ?? LBCLMM_PROGRAM_IDS[opt?.cluster ?? "mainnet-beta"], 30 | provider 31 | ); 32 | 33 | const poolAccount = await program.account.lbPair.fetchNullable( 34 | new PublicKey(poolAddress) 35 | ); 36 | 37 | if (!poolAccount) throw new Error("Pool account not found"); 38 | 39 | return { 40 | tokenXMint: poolAccount.tokenXMint, 41 | tokenYMint: poolAccount.tokenYMint, 42 | }; 43 | } 44 | 45 | export function getTokenProgramId(lbPairState: LbPair) { 46 | const getTokenProgramIdByFlag = (flag: number) => { 47 | return flag == 0 ? TOKEN_PROGRAM_ID : TOKEN_2022_PROGRAM_ID; 48 | }; 49 | return { 50 | tokenXProgram: getTokenProgramIdByFlag(lbPairState.tokenMintXProgramFlag), 51 | tokenYProgram: getTokenProgramIdByFlag(lbPairState.tokenMintYProgramFlag), 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/positions/index.ts: -------------------------------------------------------------------------------- 1 | import { AccountMeta, PublicKey } from "@solana/web3.js"; 2 | import BN from "bn.js"; 3 | import { binIdToBinArrayIndex } from "../binArray"; 4 | import { deriveBinArray } from "../derive"; 5 | import { PositionData } from "../../types"; 6 | 7 | export * from "./wrapper"; 8 | 9 | export function getBinArrayIndexesCoverage(lowerBinId: BN, upperBinId: BN) { 10 | const lowerBinArrayIndex = binIdToBinArrayIndex(lowerBinId); 11 | const upperBinArrayIndex = binIdToBinArrayIndex(upperBinId); 12 | 13 | const binArrayIndexes: BN[] = []; 14 | 15 | for ( 16 | let i = lowerBinArrayIndex.toNumber(); 17 | i <= upperBinArrayIndex.toNumber(); 18 | i++ 19 | ) { 20 | binArrayIndexes.push(new BN(i)); 21 | } 22 | 23 | return binArrayIndexes; 24 | } 25 | 26 | export function getBinArrayKeysCoverage( 27 | lowerBinId: BN, 28 | upperBinId: BN, 29 | lbPair: PublicKey, 30 | programId: PublicKey 31 | ) { 32 | const binArrayIndexes = getBinArrayIndexesCoverage(lowerBinId, upperBinId); 33 | 34 | return binArrayIndexes.map((index) => { 35 | return deriveBinArray(lbPair, index, programId)[0]; 36 | }); 37 | } 38 | 39 | export function getBinArrayAccountMetasCoverage( 40 | lowerBinId: BN, 41 | upperBinId: BN, 42 | lbPair: PublicKey, 43 | programId: PublicKey 44 | ): AccountMeta[] { 45 | return getBinArrayKeysCoverage(lowerBinId, upperBinId, lbPair, programId).map( 46 | (key) => { 47 | return { 48 | pubkey: key, 49 | isSigner: false, 50 | isWritable: true, 51 | }; 52 | } 53 | ); 54 | } 55 | 56 | export function getPositionLowerUpperBinIdWithLiquidity( 57 | position: PositionData 58 | ): { lowerBinId: BN; upperBinId: BN } | null { 59 | const binWithLiquidity = position.positionBinData.filter( 60 | (b) => !new BN(b.binLiquidity).isZero() 61 | ); 62 | 63 | return binWithLiquidity.length > 0 64 | ? { 65 | lowerBinId: new BN(binWithLiquidity[0].binId), 66 | upperBinId: new BN(binWithLiquidity[binWithLiquidity.length - 1].binId), 67 | } 68 | : null; 69 | } 70 | 71 | export function isPositionNoFee( 72 | position: PositionData 73 | ): boolean { 74 | return ( 75 | position.feeX.isZero() && 76 | position.feeY.isZero() 77 | ); 78 | } 79 | 80 | export function isPositionNoReward( 81 | position: PositionData 82 | ): boolean { 83 | return ( 84 | position.rewardOne.isZero() && 85 | position.rewardTwo.isZero() 86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /ts-client/src/dlmm/helpers/positions/wrapper.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import { 3 | POSITION_V2_DISC, 4 | PositionV2, 5 | PositionVersion, 6 | UserFeeInfo, 7 | UserRewardInfo, 8 | } from "../../types"; 9 | import { AccountInfo, PublicKey } from "@solana/web3.js"; 10 | import { Program } from "@coral-xyz/anchor"; 11 | import { LbClmm } from "../../idl"; 12 | import { getBinArrayKeysCoverage } from "."; 13 | import { binIdToBinArrayIndex } from "../binArray"; 14 | import { deriveBinArray } from "../derive"; 15 | 16 | export interface IPosition { 17 | address(): PublicKey; 18 | lowerBinId(): BN; 19 | upperBinId(): BN; 20 | liquidityShares(): BN[]; 21 | rewardInfos(): UserRewardInfo[]; 22 | feeInfos(): UserFeeInfo[]; 23 | lastUpdatedAt(): BN; 24 | lbPair(): PublicKey; 25 | totalClaimedFeeXAmount(): BN; 26 | totalClaimedFeeYAmount(): BN; 27 | totalClaimedRewards(): BN[]; 28 | operator(): PublicKey; 29 | lockReleasePoint(): BN; 30 | feeOwner(): PublicKey; 31 | owner(): PublicKey; 32 | getBinArrayIndexesCoverage(): BN[]; 33 | getBinArrayKeysCoverage(programId: PublicKey): PublicKey[]; 34 | version(): PositionVersion; 35 | } 36 | 37 | export function wrapPosition( 38 | program: Program, 39 | key: PublicKey, 40 | account: AccountInfo 41 | ): IPosition { 42 | const disc = account.data.subarray(0, 8); 43 | if (disc.equals(POSITION_V2_DISC)) { 44 | const state = program.coder.accounts.decode( 45 | program.account.positionV2.idlAccount.name, 46 | account.data 47 | ); 48 | return new PositionV2Wrapper(key, state); 49 | } else { 50 | throw new Error("Unknown position account"); 51 | } 52 | } 53 | 54 | export class PositionV2Wrapper implements IPosition { 55 | constructor( 56 | public positionAddress: PublicKey, 57 | public inner: PositionV2 58 | ) {} 59 | 60 | address(): PublicKey { 61 | return this.positionAddress; 62 | } 63 | 64 | totalClaimedRewards(): BN[] { 65 | return this.inner.totalClaimedRewards; 66 | } 67 | 68 | feeOwner(): PublicKey { 69 | return this.inner.feeOwner; 70 | } 71 | 72 | lockReleasePoint(): BN { 73 | return this.inner.lockReleasePoint; 74 | } 75 | 76 | operator(): PublicKey { 77 | return this.inner.operator; 78 | } 79 | 80 | totalClaimedFeeYAmount(): BN { 81 | return this.inner.totalClaimedFeeYAmount; 82 | } 83 | 84 | totalClaimedFeeXAmount(): BN { 85 | return this.inner.totalClaimedFeeXAmount; 86 | } 87 | 88 | lbPair(): PublicKey { 89 | return this.inner.lbPair; 90 | } 91 | 92 | lowerBinId(): BN { 93 | return new BN(this.inner.lowerBinId); 94 | } 95 | 96 | upperBinId(): BN { 97 | return new BN(this.inner.upperBinId); 98 | } 99 | 100 | liquidityShares(): BN[] { 101 | return this.inner.liquidityShares; 102 | } 103 | 104 | rewardInfos(): UserRewardInfo[] { 105 | return this.inner.rewardInfos; 106 | } 107 | 108 | feeInfos(): UserFeeInfo[] { 109 | return this.inner.feeInfos; 110 | } 111 | 112 | lastUpdatedAt(): BN { 113 | return this.inner.lastUpdatedAt; 114 | } 115 | 116 | getBinArrayIndexesCoverage(): BN[] { 117 | const lowerBinArrayIndex = binIdToBinArrayIndex(this.lowerBinId()); 118 | const upperBinArrayIndex = lowerBinArrayIndex.add(new BN(1)); 119 | 120 | return [lowerBinArrayIndex, upperBinArrayIndex]; 121 | } 122 | 123 | getBinArrayKeysCoverage(programId: PublicKey): PublicKey[] { 124 | return this.getBinArrayIndexesCoverage().map( 125 | (index) => deriveBinArray(this.lbPair(), index, programId)[0] 126 | ); 127 | } 128 | 129 | version(): PositionVersion { 130 | return PositionVersion.V2; 131 | } 132 | 133 | owner(): PublicKey { 134 | return this.inner.owner; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ts-client/src/test/external/helper.ts: -------------------------------------------------------------------------------- 1 | import { Program, web3 } from "@coral-xyz/anchor"; 2 | import { TransferHookCounter } from "./transfer_hook_counter"; 3 | import { getExtraAccountMetaAddress } from "@solana/spl-token"; 4 | 5 | export async function createExtraAccountMetaListAndCounter( 6 | program: Program, 7 | mint: web3.PublicKey 8 | ) { 9 | const extraAccountMetaList = getExtraAccountMetaAddress( 10 | mint, 11 | program.programId 12 | ); 13 | const counterAccount = deriveCounter(mint, program.programId); 14 | 15 | await program.methods 16 | .initializeExtraAccountMetaList() 17 | .accounts({ 18 | mint, 19 | counterAccount, 20 | extraAccountMetaList, 21 | }) 22 | .rpc(); 23 | 24 | return [extraAccountMetaList, counterAccount]; 25 | } 26 | 27 | export function deriveCounter(mint: web3.PublicKey, programId: web3.PublicKey) { 28 | const [counter] = web3.PublicKey.findProgramAddressSync( 29 | [Buffer.from("counter"), mint.toBuffer()], 30 | programId 31 | ); 32 | 33 | return counter; 34 | } 35 | -------------------------------------------------------------------------------- /ts-client/src/test/external/program.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, Program, Wallet, web3 } from "@coral-xyz/anchor"; 2 | import { 3 | TransferHookCounter, 4 | IDL as TransferHookCounterIDL, 5 | } from "./transfer_hook_counter"; 6 | import { Connection } from "@solana/web3.js"; 7 | 8 | export const TRANSFER_HOOK_COUNTER_PROGRAM_ID = new web3.PublicKey( 9 | "abcSyangMHdGzUGKhBhKoQzSFdJKUdkPGf5cbXVHpEw" 10 | ); 11 | 12 | export function createTransferHookCounterProgram( 13 | wallet: Wallet, 14 | programId: web3.PublicKey, 15 | connection: Connection 16 | ): Program { 17 | const provider = new AnchorProvider(connection, wallet, { 18 | maxRetries: 3, 19 | }); 20 | 21 | const program = new Program( 22 | TransferHookCounterIDL, 23 | programId, 24 | provider 25 | ); 26 | 27 | return program; 28 | } 29 | -------------------------------------------------------------------------------- /ts-client/src/test/external/transfer_hook_counter.ts: -------------------------------------------------------------------------------- 1 | export type TransferHookCounter = { 2 | "version": "0.1.0", 3 | "name": "transfer_hook_counter", 4 | "instructions": [ 5 | { 6 | "name": "initializeExtraAccountMetaList", 7 | "accounts": [ 8 | { 9 | "name": "payer", 10 | "isMut": true, 11 | "isSigner": true 12 | }, 13 | { 14 | "name": "extraAccountMetaList", 15 | "isMut": true, 16 | "isSigner": false 17 | }, 18 | { 19 | "name": "mint", 20 | "isMut": false, 21 | "isSigner": false 22 | }, 23 | { 24 | "name": "counterAccount", 25 | "isMut": true, 26 | "isSigner": false 27 | }, 28 | { 29 | "name": "tokenProgram", 30 | "isMut": false, 31 | "isSigner": false 32 | }, 33 | { 34 | "name": "associatedTokenProgram", 35 | "isMut": false, 36 | "isSigner": false 37 | }, 38 | { 39 | "name": "systemProgram", 40 | "isMut": false, 41 | "isSigner": false 42 | } 43 | ], 44 | "args": [] 45 | }, 46 | { 47 | "name": "transferHook", 48 | "accounts": [ 49 | { 50 | "name": "sourceToken", 51 | "isMut": false, 52 | "isSigner": false 53 | }, 54 | { 55 | "name": "mint", 56 | "isMut": false, 57 | "isSigner": false 58 | }, 59 | { 60 | "name": "destinationToken", 61 | "isMut": false, 62 | "isSigner": false 63 | }, 64 | { 65 | "name": "owner", 66 | "isMut": false, 67 | "isSigner": false 68 | }, 69 | { 70 | "name": "extraAccountMetaList", 71 | "isMut": false, 72 | "isSigner": false 73 | }, 74 | { 75 | "name": "counterAccount", 76 | "isMut": true, 77 | "isSigner": false 78 | } 79 | ], 80 | "args": [ 81 | { 82 | "name": "amount", 83 | "type": "u64" 84 | } 85 | ] 86 | } 87 | ], 88 | "accounts": [ 89 | { 90 | "name": "counterAccount", 91 | "type": { 92 | "kind": "struct", 93 | "fields": [ 94 | { 95 | "name": "counter", 96 | "type": "u32" 97 | } 98 | ] 99 | } 100 | } 101 | ], 102 | "errors": [ 103 | { 104 | "code": 6000, 105 | "name": "AmountTooBig", 106 | "msg": "The amount is too big" 107 | } 108 | ] 109 | }; 110 | 111 | export const IDL: TransferHookCounter = { 112 | "version": "0.1.0", 113 | "name": "transfer_hook_counter", 114 | "instructions": [ 115 | { 116 | "name": "initializeExtraAccountMetaList", 117 | "accounts": [ 118 | { 119 | "name": "payer", 120 | "isMut": true, 121 | "isSigner": true 122 | }, 123 | { 124 | "name": "extraAccountMetaList", 125 | "isMut": true, 126 | "isSigner": false 127 | }, 128 | { 129 | "name": "mint", 130 | "isMut": false, 131 | "isSigner": false 132 | }, 133 | { 134 | "name": "counterAccount", 135 | "isMut": true, 136 | "isSigner": false 137 | }, 138 | { 139 | "name": "tokenProgram", 140 | "isMut": false, 141 | "isSigner": false 142 | }, 143 | { 144 | "name": "associatedTokenProgram", 145 | "isMut": false, 146 | "isSigner": false 147 | }, 148 | { 149 | "name": "systemProgram", 150 | "isMut": false, 151 | "isSigner": false 152 | } 153 | ], 154 | "args": [] 155 | }, 156 | { 157 | "name": "transferHook", 158 | "accounts": [ 159 | { 160 | "name": "sourceToken", 161 | "isMut": false, 162 | "isSigner": false 163 | }, 164 | { 165 | "name": "mint", 166 | "isMut": false, 167 | "isSigner": false 168 | }, 169 | { 170 | "name": "destinationToken", 171 | "isMut": false, 172 | "isSigner": false 173 | }, 174 | { 175 | "name": "owner", 176 | "isMut": false, 177 | "isSigner": false 178 | }, 179 | { 180 | "name": "extraAccountMetaList", 181 | "isMut": false, 182 | "isSigner": false 183 | }, 184 | { 185 | "name": "counterAccount", 186 | "isMut": true, 187 | "isSigner": false 188 | } 189 | ], 190 | "args": [ 191 | { 192 | "name": "amount", 193 | "type": "u64" 194 | } 195 | ] 196 | } 197 | ], 198 | "accounts": [ 199 | { 200 | "name": "counterAccount", 201 | "type": { 202 | "kind": "struct", 203 | "fields": [ 204 | { 205 | "name": "counter", 206 | "type": "u32" 207 | } 208 | ] 209 | } 210 | } 211 | ], 212 | "errors": [ 213 | { 214 | "code": 6000, 215 | "name": "AmountTooBig", 216 | "msg": "The amount is too big" 217 | } 218 | ] 219 | }; 220 | -------------------------------------------------------------------------------- /ts-client/src/test/helper.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { Position } from "../dlmm/types"; 3 | import { DLMM } from "../dlmm"; 4 | import BN from "bn.js"; 5 | 6 | export function assertAmountWithPrecision( 7 | actualAmount: number, 8 | expectedAmount: number, 9 | precisionPercent: number 10 | ) { 11 | if (expectedAmount == 0 && actualAmount == 0) { 12 | return; 13 | } 14 | let maxAmount, minAmount; 15 | if (expectedAmount > actualAmount) { 16 | maxAmount = expectedAmount; 17 | minAmount = actualAmount; 18 | } else { 19 | maxAmount = actualAmount; 20 | minAmount = expectedAmount; 21 | } 22 | let diff = ((maxAmount - minAmount) * 100) / maxAmount; 23 | expect(diff).toBeLessThan(precisionPercent); 24 | } 25 | 26 | export async function assertPosition({ 27 | lbClmm, 28 | positionPubkey, 29 | userPublicKey, 30 | xAmount, 31 | yAmount, 32 | }: { 33 | lbClmm: DLMM; 34 | positionPubkey: PublicKey; 35 | userPublicKey: PublicKey; 36 | xAmount: BN; 37 | yAmount: BN; 38 | }) { 39 | const positionState: Position = await lbClmm.program.account.positionV2.fetch( 40 | positionPubkey 41 | ); 42 | 43 | const { userPositions } = await lbClmm.getPositionsByUserAndLbPair( 44 | userPublicKey 45 | ); 46 | 47 | expect(userPositions.length).toBeGreaterThan(0); 48 | const position = userPositions.find((ps) => 49 | ps.publicKey.equals(positionPubkey) 50 | ); 51 | expect(position).not.toBeUndefined(); 52 | expect(position.positionData.positionBinData.length).toBe( 53 | positionState.upperBinId - positionState.lowerBinId + 1 54 | ); 55 | expect(position.positionData.positionBinData[0].binId).toBe( 56 | positionState.lowerBinId 57 | ); 58 | expect( 59 | position.positionData.positionBinData[ 60 | position.positionData.positionBinData.length - 1 61 | ].binId 62 | ).toBe(positionState.upperBinId); 63 | expect(+position.positionData.totalXAmount).toBeLessThan(xAmount.toNumber()); 64 | assertAmountWithPrecision( 65 | +position.positionData.totalXAmount, 66 | xAmount.toNumber(), 67 | 5 68 | ); 69 | expect(+position.positionData.totalYAmount).toBeLessThan(yAmount.toNumber()); 70 | assertAmountWithPrecision( 71 | +position.positionData.totalYAmount, 72 | yAmount.toNumber(), 73 | 5 74 | ); 75 | 76 | return { bins: position.positionData.positionBinData }; 77 | } 78 | -------------------------------------------------------------------------------- /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 | } 14 | } 15 | -------------------------------------------------------------------------------- /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 | }; 14 | 15 | export default config; 16 | --------------------------------------------------------------------------------