├── .github ├── actions │ ├── setup-anchor │ │ └── action.yml │ ├── setup-dep │ │ └── action.yml │ └── setup-solana │ │ └── action.yml └── workflows │ ├── ci_rust.yml │ └── ci_ts.yml ├── .gitignore ├── .prettierignore ├── Anchor.toml ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── common ├── Cargo.toml └── src │ ├── dynamic_amm │ ├── aux_lp_mint.rs │ ├── ix_account_builder.rs │ ├── mod.rs │ └── pda.rs │ ├── dynamic_vault │ ├── aux_lp_mint.rs │ ├── mod.rs │ └── pda.rs │ └── lib.rs ├── dynamic-amm-quote ├── Cargo.toml ├── src │ ├── curve │ │ ├── curve_type.rs │ │ └── mod.rs │ ├── depeg │ │ ├── marinade.rs │ │ ├── mod.rs │ │ ├── solido.rs │ │ └── spl_stake.rs │ ├── lib.rs │ └── math │ │ ├── constant_product.rs │ │ ├── mod.rs │ │ └── stable_swap.rs └── tests │ ├── fixtures │ ├── accounts │ │ ├── 21bR3D4QR4GzopVco44PVMBXwHFpSYrbrdeNwdKk7umb.json │ │ ├── 3ifhD4Ywaa8aBZAaQSqYgN4Q1kaFArioLU8uumJMaqkE.json │ │ ├── 8p1VKP45hhqq5iZG5fNGoi7ucme8nFLeChoDWNy7rWFm.json │ │ ├── 8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC.json │ │ ├── AYX6kFvQRacdbC1eBaK5kgEPGjzjYYd1fyduHg55Z1ai.json │ │ ├── B2uEs9zjnz222hfUaUuRgesryUEYwy3JGuWe31sE9gsG.json │ │ ├── FERjPVNEa7Udq8CEv68h6tPL46Tq7ieE49HrE2wea3XT.json │ │ ├── FZN7QZ8ZUUAxMPfxYEYkH3cXUASzH8EqA6B4tyCL8f1j.json │ │ ├── GGG4DxkYa86g2v4KwtvR8Xu2tXEp1xd4BRC3yNnpve3g.json │ │ ├── GYuyKhedWVsndqG5tyXwRiRSTxFKrLJysgedccqU7aH8.json │ │ ├── HWnWnLvBzvSmXH5hnHJCFmuQbDTsX3Ba2w9CPE5zf4YD.json │ │ ├── HZeLxbZ9uHtSpwZC3LBr4Nubd14iHwz7bRSghRZf5VCG.json │ │ ├── HcjZvfeSNJbNkfLD4eEcRBr96AD3w1GpmMppaeRZf7ur.json │ │ └── mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So.json │ ├── dynamic_amm.so │ ├── dynamic_vault.so │ └── metaplex.so │ └── test_quote.rs ├── keys └── localnet │ └── admin-bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1.json ├── programs ├── dynamic-amm │ ├── Cargo.toml │ └── src │ │ ├── constants.rs │ │ ├── error.rs │ │ ├── event.rs │ │ ├── instructions │ │ ├── add_balance_liquidity.rs │ │ ├── bootstrap_liquidity.rs │ │ ├── claim_fee.rs │ │ ├── close_config.rs │ │ ├── create_config.rs │ │ ├── create_lock_escrow.rs │ │ ├── create_mint_metadata.rs │ │ ├── enable_pool.rs │ │ ├── get_pool_info.rs │ │ ├── initialize_customizable_permissionless_constant_product_pool.rs │ │ ├── initialize_permissioned_pool.rs │ │ ├── initialize_permissionless_pool.rs │ │ ├── initialize_permissionless_pool_with_config.rs │ │ ├── lock.rs │ │ ├── mod.rs │ │ ├── move_locked_lp.rs │ │ ├── override_curve_param.rs │ │ ├── partner_claim_fees.rs │ │ ├── remove_liquidity_single_side.rs │ │ ├── set_pool_fee.rs │ │ ├── swap.rs │ │ └── update_activation_point.rs │ │ ├── lib.rs │ │ ├── seed.rs │ │ └── state.rs └── dynamic-vault │ ├── Cargo.toml │ └── src │ ├── lib.rs │ ├── seed.rs │ └── state.rs ├── rust-client ├── Cargo.toml ├── README.md └── src │ ├── fee_estimation.rs │ ├── file.rs │ ├── instructions │ ├── dynamic_amm │ │ ├── create_pool.rs │ │ ├── deposit.rs │ │ ├── get_pool_info.rs │ │ ├── mod.rs │ │ ├── quote.rs │ │ ├── swap.rs │ │ └── withdraw.rs │ └── mod.rs │ ├── main.rs │ ├── rpc.rs │ └── transaction_utils.rs └── ts-client ├── .prettierrc ├── README.md ├── index.ts ├── jest.config.js ├── jest └── setup.js ├── package.json ├── pnpm-lock.yaml ├── src ├── amm │ ├── constants.ts │ ├── curve │ │ ├── constant-product.ts │ │ ├── index.ts │ │ ├── stable-swap-math │ │ │ ├── calculator.ts │ │ │ └── index.ts │ │ └── stable-swap.ts │ ├── error.ts │ ├── idl.ts │ ├── index.ts │ ├── marinade-finance.json │ ├── tests │ │ ├── bundlePoolQuote.test.ts │ │ ├── constantProduct.test.ts │ │ ├── decode.test.ts │ │ ├── error.test.ts │ │ ├── initializeCustomizablePermissionlessConstantProductPool.test.ts │ │ ├── stableSwap.test.ts │ │ └── utils │ │ │ └── index.ts │ ├── types │ │ └── index.ts │ └── utils.ts └── examples │ ├── create_pool_and_lock_liquidity.ts │ ├── create_pool_with_authorized_config.ts │ ├── get_claimable_fee.ts │ ├── get_pool_info.ts │ ├── swap.ts │ └── swap_quote.ts ├── tsconfig.build.json ├── tsconfig.esm.json └── tsconfig.json /.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_rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust SDK CI 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "docs/**" 7 | - "README.md" 8 | - "LICENSE" 9 | - ".editorconfig" 10 | branches: 11 | - main 12 | - develop 13 | - staging 14 | - feat/protocol-fee-part-of-lp-fees 15 | 16 | env: 17 | SOLANA_CLI_VERSION: 1.16.3 18 | RUST_TOOLCHAIN_VERSION: 1.76.0 19 | 20 | jobs: 21 | changed_files_rust: 22 | runs-on: ubuntu-latest 23 | outputs: 24 | program: ${{steps.changed-files-specific.outputs.any_changed}} 25 | steps: 26 | - uses: actions/checkout@v2 27 | with: 28 | fetch-depth: 0 29 | - name: Get specific changed files 30 | id: changed-files-specific 31 | uses: tj-actions/changed-files@v18.6 32 | with: 33 | files: | 34 | rust-client 35 | programs 36 | dynamic-amm-quote 37 | 38 | rust-test: 39 | needs: changed_files_rust 40 | runs-on: ubuntu-latest 41 | if: needs.changed_files_rust.outputs.program == 'true' 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: ./.github/actions/setup-dep 45 | - uses: ./.github/actions/setup-solana 46 | # Install rust + toolchain 47 | - uses: actions-rs/toolchain@v1 48 | with: 49 | toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} 50 | components: clippy 51 | # Cargo build cache 52 | - name: Cargo Cache 53 | uses: actions/cache@v4 54 | with: 55 | path: | 56 | ~/.cargo/ 57 | target 58 | key: ${{ runner.os }}-cargo-${{ steps.extract_branch.outputs.branch }} 59 | restore-keys: | 60 | ${{ runner.os }}-cargo-${{ steps.extract_branch.outputs.branch }} 61 | ${{ runner.os }}-cargo 62 | - name: Cargo fmt check 63 | run: cargo fmt -- --check 64 | shell: bash 65 | - name: Cargo test 66 | run: cargo test -- --nocapture 67 | shell: bash 68 | -------------------------------------------------------------------------------- /.github/workflows/ci_ts.yml: -------------------------------------------------------------------------------- 1 | name: Typescript SDK CI 2 | 3 | on: 4 | pull_request: 5 | paths-ignore: 6 | - "docs/**" 7 | - "README.md" 8 | - "LICENSE" 9 | - ".editorconfig" 10 | branches: 11 | - main 12 | - develop 13 | - staging 14 | - feat/protocol-fee-part-of-lp-fees 15 | 16 | env: 17 | SOLANA_CLI_VERSION: 2.1.0 18 | NODE_VERSION: 18.14.2 19 | ANCHOR_CLI_VERSION: 0.28.0 20 | 21 | jobs: 22 | sdk_changed_files: 23 | runs-on: ubuntu-latest 24 | outputs: 25 | sdk: ${{steps.changed-files-specific.outputs.any_changed}} 26 | steps: 27 | - uses: actions/checkout@v2 28 | with: 29 | fetch-depth: 0 30 | - name: Get specific changed files 31 | id: changed-files-specific 32 | uses: tj-actions/changed-files@v18.6 33 | with: 34 | files: | 35 | ts-client 36 | 37 | sdk_test: 38 | runs-on: ubuntu-latest 39 | needs: sdk_changed_files 40 | if: needs.sdk_changed_files.outputs.sdk == 'true' 41 | steps: 42 | - uses: actions/checkout@v2 43 | - uses: ./.github/actions/setup-solana 44 | - uses: ./.github/actions/setup-dep 45 | - uses: ./.github/actions/setup-anchor 46 | # This much more faster than anchor localnet 47 | - run: solana-test-validator --bpf-program 24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi ./dynamic-amm-quote/tests/fixtures/dynamic_vault.so --bpf-program Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB ./dynamic-amm-quote/tests/fixtures/dynamic_amm.so --bpf-program metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s ./dynamic-amm-quote/tests/fixtures/metaplex.so --account-dir ./dynamic-amm-quote/tests/fixtures/accounts --reset & sleep 2 48 | shell: bash 49 | - run: cd ts-client && npm install && npm run test 50 | shell: bash 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .anchor 3 | .DS_Store 4 | target 5 | **/*.rs.bk 6 | node_modules 7 | test-ledger 8 | .pnpm-debug.log 9 | .idea 10 | bun.lockb 11 | 12 | ts-client/dist/ 13 | ts-client/package-lock.json 14 | 15 | ts-client/.env 16 | 17 | commands -------------------------------------------------------------------------------- /.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 | 4 | [programs.localnet] 5 | mercurial_dynamic_amm_sdk = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" 6 | 7 | [registry] 8 | url = "https://anchor.projectserum.com" 9 | 10 | [provider] 11 | cluster = "localnet" 12 | wallet = "./keys/localnet/admin-bossj3JvwiNK7pvjr149DqdtJxf2gdygbcmEPTkb2F1.json" 13 | 14 | [scripts] 15 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 16 | 17 | [[test.genesis]] 18 | address = "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB" 19 | program = "./dynamic-amm-quote/tests/fixtures/dynamic_amm.so" 20 | 21 | [[test.genesis]] 22 | address = "24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi" 23 | program = "./dynamic-amm-quote/tests/fixtures/dynamic_vault.so" 24 | 25 | [[test.genesis]] 26 | address = "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" 27 | program = "./dynamic-amm-quote/tests/fixtures/metaplex.so" 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["programs/*", "rust-client", "dynamic-amm-quote", "common"] 3 | resolver = "2" 4 | 5 | [workspace.package] 6 | version = "0.0.5" 7 | authors = ["Andrew"] 8 | edition = "2021" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mercurial Dynamic AMM SDK 2 | 3 | States and instructions for interacting with mercurial dynamic AMM program. 4 | 5 | # Program ID 6 | 7 | Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB 8 | 9 | # Test 10 | ### Rust quote 11 | ``` 12 | cargo t -p dynamic-amm-quote test_quote 13 | ``` -------------------------------------------------------------------------------- /common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "common" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | 6 | [dependencies] 7 | anchor-lang = { version = "0.28.0" } 8 | anchor-spl = "0.28.0" 9 | solana-sdk = { version = "1.16.0" } 10 | spl-associated-token-account = "2.2.0" 11 | mpl-token-metadata = "3.2.3" 12 | dynamic-amm = { path = "../programs/dynamic-amm" } 13 | dynamic-vault = { path = "../programs/dynamic-vault" } 14 | lazy_static = "1.4.0" 15 | 16 | [features] 17 | devnet = [] 18 | -------------------------------------------------------------------------------- /common/src/dynamic_amm/aux_lp_mint.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use lazy_static::lazy_static; 4 | use solana_sdk::pubkey; 5 | use solana_sdk::pubkey::Pubkey; 6 | 7 | lazy_static! { 8 | pub static ref POOL_WITH_NON_PDA_BASED_LP_MINT: HashMap = HashMap::from_iter([ 9 | ( 10 | pubkey!("H49XALURUKbXRysVXYvHKsd5h4TRbZA2iq9kChVi1JvF"), 11 | pubkey!("3GgCMTyddNhZd29rKLLfQ86wQcer1CcksEgvYpraF2UH") 12 | ), 13 | ( 14 | pubkey!("HcjZvfeSNJbNkfLD4eEcRBr96AD3w1GpmMppaeRZf7ur"), 15 | pubkey!("B2uEs9zjnz222hfUaUuRgesryUEYwy3JGuWe31sE9gsG") 16 | ), 17 | ( 18 | pubkey!("7EJSgV2pthhDfb4UiER9vzTqe2eojei9GEQAQnkqJ96e"), 19 | pubkey!("9Gpvqcua3hLps1AhtTSVEFSgKG2Dni6yLyXtEeZGLR3y") 20 | ), 21 | ( 22 | pubkey!("486t886mRYmo29uubn59PND69ApSbn9ukeLeL2aemLAS"), 23 | pubkey!("BNqkJsPE1GraPokMMQXoVaZSoAjL1RTNwDcdmsfcjqf4") 24 | ), 25 | ( 26 | pubkey!("Aw1MJ31SHEEYmzg9vE4bXa5ZURS5HDvMZjhXmt7Tu8yJ"), 27 | pubkey!("CVLS1XdDzNBVnQGwhLG5fz3mUur4Zb1KvUbAUa3SJEZN") 28 | ), 29 | ( 30 | pubkey!("pvgGDMNpxapbVM8YNao5f6i6mioMzZBHjzDeEcYf9iZ"), 31 | pubkey!("FdpRVskpa3uR8CR7ggLs7RjpVH1iF5kog6maKTHcTqK1") 32 | ), 33 | ( 34 | pubkey!("5fMmLVAtAtMjaXtAWtHPQT83trtDx7kxmgcgJkVPny1K"), 35 | pubkey!("Bpa1RdNV7ScuaPdVVosQ2UCcHbpr4b9KWQvXJGGKCJ3F") 36 | ), 37 | ( 38 | pubkey!("6ZLKLjMd2KzH7PPHCXUPgbMAtdTT37VgTtdeXWLoJppr"), 39 | pubkey!("3mtMyBrCf48tJ1XmMnoYZgQqqn6VNEYAfKHzGZnfAZPt") 40 | ), 41 | ( 42 | pubkey!("Gyv8znLzPb44XatDar8ebx1zG6VvvuPHtaJP8MdCjNoQ"), 43 | pubkey!("FmQSveFkR6Z2hbkA5WDNwLdo4xdsS1C8gR5bCu8Zpdsu") 44 | ), 45 | ( 46 | pubkey!("2X2my7iZEKE7BgqfRVC2a5c5VxekZa2HxGctHyoXxe2m"), 47 | pubkey!("GX9SZ81En5uSDB5PTL9AAfKfKuqVHiBYJMY5wsex4Zu4") 48 | ), 49 | ( 50 | pubkey!("5NLrRmfjaBHj2DpGNihVis6yVv236P8m5AdR838vGMWK"), 51 | pubkey!("mXZy4YFSKWZ3BdnHjbG9NJknL2PcmAXxF2neUDoFcNq") 52 | ), 53 | ( 54 | pubkey!("5kYoik2SHAQxtK82jeHfkwTb1UrStJJUqBteXcU5csP8"), 55 | pubkey!("B1J8bDBxoUXWk6hpdbcLDw1WQFeBUuiFMyJFwpnxujmp") 56 | ), 57 | ( 58 | pubkey!("HCuxikDxMaSvJHoWVy2WaJyvZ5SPnFYZQhRDmtonHrv2"), 59 | pubkey!("2jWz3vZzCTxLsPjGsuQLLY9hB7RKtA8KiwtBJziqJz9v") 60 | ), 61 | ( 62 | pubkey!("4xqyRGWMRkfVo7GH74aryKjSLcpQiVHGAZY4u1n6wAbZ"), 63 | pubkey!("36F4LM4tK5xSteQnv7DGjkgoyeb7iWjzwGwmxiEUixPC") 64 | ), 65 | ( 66 | pubkey!("EdCfPu6685kto9w9xJnwyAsrB1xPVcAzLzyVNW9EwdQS"), 67 | pubkey!("9i6M6o2NCqUG8jinGrqKb6w172YfX5KBVPFSsSnv1ZXi") 68 | ), 69 | ( 70 | pubkey!("5yuefgbJJpmFNK2iiYbLSpv1aZXq7F9AUKkZKErTYCvs"), 71 | pubkey!("4x76pkvNJYy9YRZM6Y6RZXJckRpsHQEWoD6sM9HEpmB") 72 | ), 73 | ( 74 | pubkey!("5NQTw1WqVEt6wP1LmohsrYDyJp2NDipdv6eULVNByXMb"), 75 | pubkey!("472wjciN9cdAdMAWA3aQXBqTeoX6UV1ahTVELdrcncD2") 76 | ), 77 | ( 78 | pubkey!("32D4zRxNc1EssbJieVHfPhZM3rH6CzfUPrWUuWxD9prG"), 79 | pubkey!("xLebAypjbaQ9tmxUKHV6DZU4mY8ATAAP2sfkNNQLXjf") 80 | ), 81 | ( 82 | pubkey!("9CopBY6iQBaZKAhhQANfy7g4VXZkx9zKm8AisPd5Ufay"), 83 | pubkey!("48w8Bdsz15PzFLuow9bvo3HQWZW4bxdivvpRoTRc3prg") 84 | ), 85 | ( 86 | pubkey!("RBtHAB7TS7EDaGVRQHDG6AzK5whz1pQtMiXtrMa5Srn"), 87 | pubkey!("DkcS27SzJ4sN94eS3Y84u6QPLJ3LM3RfmfDM7NVnwee1") 88 | ), 89 | ( 90 | pubkey!("3y6k8aeJxeRX7i3mYtd8G12oqzko1wdjxLJ91roJRnsP"), 91 | pubkey!("8TEL6fscLwefNbCEKdCNBuVUSSD9JX6aZ6hSP1oqqiY6") 92 | ), 93 | ]); 94 | } 95 | 96 | #[cfg(feature = "devnet")] 97 | lazy_static! { 98 | static ref POOL_WITH_NON_PDA_BASED_LP_MINT: HashMap = HashMap::from_iter([ 99 | ( 100 | pubkey!("GXy2cEDWFodXuXpEZZizVzcyiqF2QZCiMqfZX9BGx1vz"), 101 | pubkey!("2nqgDcgfTzXJSckrVdqZGFpSfAAUY7NJKhCeikioaP5m") 102 | ), 103 | ( 104 | pubkey!("2GPECnGQbXgBBPmmLdw8daxu6A1VJUyuHGWPyJH6U56h"), 105 | pubkey!("FMnK5dTHUDR9iLvcshaaxnnEtYN1Ly5Gv8e3vccQ8k9K") 106 | ), 107 | ( 108 | pubkey!("3PSrJVm8CYJ9R1eJUT9iMikjWD6b8xHWp8VGUnMdr5yF"), 109 | pubkey!("cu45VVBLEjpwufw4A4FRLmfyckJNwwxmNrf86burdvy") 110 | ), 111 | ( 112 | pubkey!("FZgdEqq6rwsWnsZ83Ez2pyJqPdPGfDvzYhrbvcboTPtf"), 113 | pubkey!("2qWv8R6EBibqTsBCfyiqKaDh3HU8TxwD26cr5zGkFMJs") 114 | ), 115 | ( 116 | pubkey!("AyRTAzaXPamTMTRG8dny9jqG3EGWte8FrY5g9Ds3Gtr2"), 117 | pubkey!("8J65QEAV5c6CREDBpNBYhrKn7zGdoodMj2Nk1mSeUpPz") 118 | ), 119 | ( 120 | pubkey!("HK7b7P3goViFkSLQ7rTKRemsGa8LKNKt2t9D4buzbxq2"), 121 | pubkey!("BJUsjgYod77LrvTpTATwPQwjuk7hHPyNGKYUSVRJmgvN") 122 | ), 123 | ( 124 | pubkey!("BAHscmu1NncGS7t4rc5gSBPv1UFEMkvLaon1Ahdd5rHi"), 125 | pubkey!("3A2DuLdNFyeVFVsumFEVWKFoLaeTryZ4PQSJowD38Le7") 126 | ), 127 | ( 128 | pubkey!("2rkn2yM4wJcHPV57T8fPWeBksrfSpiNZoEjRgjtxNDEQ"), 129 | pubkey!("ENoFQvqrk6LnxRUmPX1cJzHyrsicJCrbZ2WEtGMY9y6N") 130 | ), 131 | ( 132 | pubkey!("Bgf1Sy5kfeDgib4go4NgzHuZwek8wE8NZus56z6uizzi"), 133 | pubkey!("2xSpdNRwDjkx2BJAtdu2zArWybzeEHqPqP1m63tFakNU") 134 | ), 135 | ]); 136 | } 137 | -------------------------------------------------------------------------------- /common/src/dynamic_amm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod aux_lp_mint; 2 | pub mod ix_account_builder; 3 | pub mod pda; 4 | 5 | pub use aux_lp_mint::*; 6 | -------------------------------------------------------------------------------- /common/src/dynamic_amm/pda.rs: -------------------------------------------------------------------------------- 1 | use dynamic_amm::{ 2 | constants::fee::MAX_BASIS_POINT, 3 | state::{CurveType, PoolFees}, 4 | }; 5 | use solana_sdk::pubkey::Pubkey; 6 | 7 | use super::POOL_WITH_NON_PDA_BASED_LP_MINT; 8 | 9 | pub const METAPLEX_PROGRAM_ID: Pubkey = 10 | solana_sdk::pubkey!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"); 11 | 12 | /// get first key, this is same as max(key1, key2) 13 | fn get_first_key(key1: Pubkey, key2: Pubkey) -> Pubkey { 14 | if key1 > key2 { 15 | return key1; 16 | } 17 | key2 18 | } 19 | /// get second key, this is same as min(key1, key2) 20 | fn get_second_key(key1: Pubkey, key2: Pubkey) -> Pubkey { 21 | if key1 > key2 { 22 | return key2; 23 | } 24 | key1 25 | } 26 | 27 | fn get_curve_type(curve_type: CurveType) -> u8 { 28 | match curve_type { 29 | CurveType::ConstantProduct {} => 0, 30 | _ => 1, 31 | } 32 | } 33 | 34 | fn get_trade_fee_bps_bytes(trade_fee_bps: u64) -> Vec { 35 | let default_fees = PoolFees { 36 | trade_fee_numerator: 250, 37 | trade_fee_denominator: 100000, 38 | protocol_trade_fee_numerator: 0, 39 | protocol_trade_fee_denominator: 100000, 40 | }; 41 | 42 | // Unwrap on default configured fee is safe 43 | let default_trade_fee_bps = to_bps( 44 | default_fees.trade_fee_numerator.into(), 45 | default_fees.trade_fee_denominator.into(), 46 | ) 47 | .unwrap(); 48 | 49 | if default_trade_fee_bps == trade_fee_bps { 50 | return vec![]; 51 | } 52 | 53 | trade_fee_bps.to_le_bytes().to_vec() 54 | } 55 | 56 | fn to_bps(numerator: u128, denominator: u128) -> Option { 57 | let bps = numerator 58 | .checked_mul(MAX_BASIS_POINT.into())? 59 | .checked_div(denominator)?; 60 | bps.try_into().ok() 61 | } 62 | 63 | pub fn derive_permissionless_pool_key( 64 | curve_type: CurveType, 65 | token_a_mint: Pubkey, 66 | token_b_mint: Pubkey, 67 | ) -> Pubkey { 68 | let (pool, _bump) = Pubkey::find_program_address( 69 | &[ 70 | &get_curve_type(curve_type).to_le_bytes(), 71 | get_first_key(token_a_mint, token_b_mint).as_ref(), 72 | get_second_key(token_a_mint, token_b_mint).as_ref(), 73 | ], 74 | &dynamic_amm::id(), 75 | ); 76 | pool 77 | } 78 | 79 | pub fn derive_customizable_permissionless_constant_product_pool_key( 80 | mint_a: Pubkey, 81 | mint_b: Pubkey, 82 | ) -> Pubkey { 83 | Pubkey::find_program_address( 84 | &[ 85 | b"pool", 86 | get_first_key(mint_a, mint_b).as_ref(), 87 | get_second_key(mint_a, mint_b).as_ref(), 88 | ], 89 | &dynamic_amm::ID, 90 | ) 91 | .0 92 | } 93 | 94 | pub fn derive_protocol_fee_key(mint_key: Pubkey, pool_key: Pubkey) -> Pubkey { 95 | Pubkey::find_program_address( 96 | &[b"fee", mint_key.as_ref(), pool_key.as_ref()], 97 | &dynamic_amm::ID, 98 | ) 99 | .0 100 | } 101 | 102 | pub fn derive_metadata_key(lp_mint: Pubkey) -> Pubkey { 103 | Pubkey::find_program_address( 104 | &[b"metadata", METAPLEX_PROGRAM_ID.as_ref(), lp_mint.as_ref()], 105 | &METAPLEX_PROGRAM_ID, 106 | ) 107 | .0 108 | } 109 | 110 | pub fn derive_vault_lp_key(vault_key: Pubkey, pool_key: Pubkey) -> Pubkey { 111 | Pubkey::find_program_address(&[vault_key.as_ref(), pool_key.as_ref()], &dynamic_amm::ID).0 112 | } 113 | 114 | pub fn derive_lp_mint_key(pool_key: Pubkey) -> Pubkey { 115 | if let Some(lp_mint) = POOL_WITH_NON_PDA_BASED_LP_MINT.get(&pool_key) { 116 | *lp_mint 117 | } else { 118 | Pubkey::find_program_address(&[b"lp_mint", pool_key.as_ref()], &dynamic_amm::ID).0 119 | } 120 | } 121 | 122 | pub fn derive_lock_escrow_key(pool_key: Pubkey, owner_key: Pubkey) -> Pubkey { 123 | Pubkey::find_program_address( 124 | &[b"lock_escrow", pool_key.as_ref(), owner_key.as_ref()], 125 | &dynamic_amm::ID, 126 | ) 127 | .0 128 | } 129 | 130 | pub fn derive_permissionless_constant_product_pool_with_config_key( 131 | mint_a: Pubkey, 132 | mint_b: Pubkey, 133 | config: Pubkey, 134 | ) -> Pubkey { 135 | Pubkey::find_program_address( 136 | &[ 137 | get_first_key(mint_a, mint_b).as_ref(), 138 | get_second_key(mint_a, mint_b).as_ref(), 139 | config.as_ref(), 140 | ], 141 | &dynamic_amm::ID, 142 | ) 143 | .0 144 | } 145 | 146 | pub fn derive_permissionless_pool_key_with_fee_tier( 147 | curve_type: CurveType, 148 | token_a_mint: Pubkey, 149 | token_b_mint: Pubkey, 150 | trade_fee_bps: u64, 151 | ) -> Pubkey { 152 | let (pool, _bump) = Pubkey::find_program_address( 153 | &[ 154 | &get_curve_type(curve_type).to_le_bytes(), 155 | get_first_key(token_a_mint, token_b_mint).as_ref(), 156 | get_second_key(token_a_mint, token_b_mint).as_ref(), 157 | get_trade_fee_bps_bytes(trade_fee_bps).as_ref(), 158 | ], 159 | &dynamic_amm::id(), 160 | ); 161 | pool 162 | } 163 | -------------------------------------------------------------------------------- /common/src/dynamic_vault/aux_lp_mint.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use solana_sdk::pubkey; 3 | use solana_sdk::pubkey::Pubkey; 4 | use std::collections::HashMap; 5 | 6 | lazy_static! { 7 | pub static ref VAULT_WITH_NON_PDA_BASED_LP_MINT: HashMap = HashMap::from_iter([ 8 | ( 9 | // ACUSD 10 | pubkey!("BFJP6RYDxJa4FmFtBpPDYcrPozjC98CELrXqVL7rGMVW"), 11 | pubkey!("5CuhvouXVx6t5XPiyhRkrfgK5omAf8XnqY1ef6CLjw7o"), 12 | ), 13 | ( 14 | // USH 15 | pubkey!("AzrUPWWyT9ZoAuMTgGHxYCnnWD2veh98FsCcMknVjg3Q"), 16 | pubkey!("9MSsSzDKq8VzokicRom6ciYPLhhZf65bCCBQLjnC7jUH") 17 | ), 18 | ( 19 | // afUSDC 20 | pubkey!("GGQfASSnFaqPu83jWrL1DMJBJEzG3rdwsDARDGt6Gxmj"), 21 | pubkey!("4da9saTYgDs37wRSuS8mnFoiWzSYeRtvSWaFRe8rtkFc"), 22 | ), 23 | ( 24 | // Bridged USD Coin (Wormhole Ethereum) 25 | pubkey!("GofttAxULhp5NE9faWNngsnDM1iJiL75AJ2AkSaaC2CC"), 26 | pubkey!("Bma9RZx1AjNGcojNJpstGe9Wcytxz17YA6rd2Lq1UirT"), 27 | ), 28 | ( 29 | // PAI 30 | pubkey!("671JaLe2zDgBeXK3UtFHBiid7WFCHAKZTmLqAaQxx7cL"), 31 | pubkey!("9NywobBSCyntrPSZxEZpUbJXLfgUzKbUF2ZqBBkJLEgB"), 32 | ), 33 | ( 34 | // UXD 35 | pubkey!("2dH3aSpt5aEwhoeSaThKRNtNppEpg2DhGKGa1C5Wecc1"), 36 | pubkey!("Afe5fiLmbKw7aBi1VgWZb9hEY8nRYtib6LNr5RGUJibP"), 37 | ), 38 | ( 39 | // WAVAX 40 | pubkey!("BVJACEffKRHvKbQT9VfEqoxrUWJN2UVdonTKYB2c4MgK"), 41 | pubkey!("FFmYsMk5xQq3zQf1r4A6Yyf3kaKd3LUQokeVa776rKWH"), 42 | ), 43 | ( 44 | // USDT 45 | pubkey!("5XCP3oD3JAuQyDpfBFFVUxsBxNjPQojpKuL4aVhHsDok"), 46 | pubkey!("EZun6G5514FeqYtUv26cBHWLqXjAEdjGuoX6ThBpBtKj"), 47 | ), 48 | ( 49 | // WBTC 50 | pubkey!("mPWBpKzzchEjitz7x4Q2d7cbQ3fHibF2BHWbWk8YGnH"), 51 | pubkey!("4nCGSVN8ZGuewX36TznzisceaNYzURWPesxyGtDvA2iP"), 52 | ), 53 | ( 54 | // mSOL 55 | pubkey!("8p1VKP45hhqq5iZG5fNGoi7ucme8nFLeChoDWNy7rWFm"), 56 | pubkey!("21bR3D4QR4GzopVco44PVMBXwHFpSYrbrdeNwdKk7umb"), 57 | ), 58 | ( 59 | // stSOL 60 | pubkey!("CGY4XQq8U4VAJpbkaFPHZeXpW3o4KQ5LowVsn6hnMwKe"), 61 | pubkey!("28KR3goEditLnzBZShRk2H7xvgzc176EoFwMogjdfSkn"), 62 | ), 63 | ( 64 | // wSOL 65 | pubkey!("FERjPVNEa7Udq8CEv68h6tPL46Tq7ieE49HrE2wea3XT"), 66 | pubkey!("FZN7QZ8ZUUAxMPfxYEYkH3cXUASzH8EqA6B4tyCL8f1j"), 67 | ), 68 | ( 69 | // USDC 70 | pubkey!("3ESUFCnRNgZ7Mn2mPPUMmXYaKU8jpnV9VtA17M7t2mHQ"), 71 | pubkey!("3RpEekjLE5cdcG15YcXJUpxSepemvq2FpmMcgo342BwC"), 72 | ), 73 | ]); 74 | } 75 | 76 | #[cfg(feature = "devnet")] 77 | lazy_static! { 78 | static ref VAULT_WITH_NON_PDA_BASED_LP_MINT: HashMap = HashMap::from_iter([ 79 | ( 80 | pubkey!("2u9ycJ7KEiWeR9vUhaHnohi5RdP2uLwuS1o8LynxhNBa"), 81 | pubkey!("DDrvEcscZagpLE361HqpaiTiwyTtyNnWPhE8xKuqgXKY") 82 | ), 83 | ( 84 | pubkey!("sr5nfQgnAmn2bTkxmpPSQS1iEDGN4Bnk48xxcEAqUsi"), 85 | pubkey!("3UhvDzg4dYtgE69QzjPaH94CoTJbLkczmYJWhq1P3MqC") 86 | ), 87 | ( 88 | pubkey!("G5qooe1TGxzsNCefw1xycto4SNy7H4Ad2AiPTCUJnM8W"), 89 | pubkey!("C1XV8Wd4zdDAy3VTGd6GBJn3KYkSE8MwNyFrPQUEW9py") 90 | ), 91 | ( 92 | pubkey!("2FiYEM3EVtUNj6soptXZJdxjBjNWHtUUUKh79QaywYRg"), 93 | pubkey!("Dq6j7SuMPhHh4eajA8WS1Nby9sbNynJfxyM3p7vxes9f") 94 | ), 95 | ( 96 | pubkey!("ATeQUJkKFRiWUfV76k5P5TfAyXwjWgBdck54z2sGvuNK"), 97 | pubkey!("BgPb3pzLMmSwECCPjTHoKLYQR3iirXBw3bVgF8ZaR7sc") 98 | ), 99 | ( 100 | pubkey!("FERjPVNEa7Udq8CEv68h6tPL46Tq7ieE49HrE2wea3XT"), 101 | pubkey!("BvoAjwEDhpLzs3jtu4H72j96ShKT5rvZE9RP1vgpfSM") 102 | ), 103 | ( 104 | pubkey!("8p1VKP45hhqq5iZG5fNGoi7ucme8nFLeChoDWNy7rWFm"), 105 | pubkey!("8YE7s4oCbsEUzH71hVwe9DBCyemprwAjyDzksZ8d9bPz") 106 | ), 107 | ( 108 | pubkey!("4cX1amsBFy9by77uPuTbhN9Qw3oEaMu4J3pAyPa2gmku"), 109 | pubkey!("2iGUnZPUPgjpjG6rT5Fi4VEeMoFw9DAwMJ8UFjXDpVs1") 110 | ), 111 | ( 112 | pubkey!("9Fze2yguDHYvX1KVfj1rgA9Q5moboWQFkw67wLGc61Z8"), 113 | pubkey!("CNJoMWip1hX5mq2zHQ88LeC5gGMrVbGdQ9ZP6jB3qvkn") 114 | ), 115 | ( 116 | pubkey!("BPNKnFRAi9jfbD4xNAUavZmCbkn9DxGc1FCy4cYWHTXf"), 117 | pubkey!("tewho86AFqTGmMvtKEvnNegHZfce4tTzDYENa58TLCq") 118 | ), 119 | ( 120 | pubkey!("DZwqzesnbNhoP5iPaxQkPG37JfDuqpZBfmsBw2wCpwQ1"), 121 | pubkey!("GDK7uxgtQYYnwHwSXE83T6pxiJbKAV2jDMAa3bmc3Qzm") 122 | ), 123 | ( 124 | pubkey!("CyAd2PPVUCytnCiMztYFqu7v56Df3KiXdrx94rCyWeJz"), 125 | pubkey!("HQU6SZNTTReKLXGyyPp9tt9tcBRo75yV9PgPMZavzXRG") 126 | ), 127 | ]); 128 | } 129 | -------------------------------------------------------------------------------- /common/src/dynamic_vault/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod aux_lp_mint; 2 | pub mod pda; 3 | 4 | pub use aux_lp_mint::*; 5 | -------------------------------------------------------------------------------- /common/src/dynamic_vault/pda.rs: -------------------------------------------------------------------------------- 1 | use super::VAULT_WITH_NON_PDA_BASED_LP_MINT; 2 | use solana_sdk::pubkey::Pubkey; 3 | 4 | pub fn derive_vault_key(mint: Pubkey) -> Pubkey { 5 | Pubkey::find_program_address( 6 | &[ 7 | dynamic_vault::seed::VAULT_PREFIX.as_ref(), 8 | mint.as_ref(), 9 | dynamic_vault::get_base_address().as_ref(), 10 | ], 11 | &dynamic_vault::ID, 12 | ) 13 | .0 14 | } 15 | 16 | pub fn derive_token_vault_key(vault: Pubkey) -> Pubkey { 17 | Pubkey::find_program_address( 18 | &[ 19 | dynamic_vault::seed::TOKEN_VAULT_PREFIX.as_ref(), 20 | vault.as_ref(), 21 | ], 22 | &dynamic_vault::ID, 23 | ) 24 | .0 25 | } 26 | 27 | pub fn derive_lp_mint_key(vault: Pubkey) -> Pubkey { 28 | let non_derived_based_lp_mint = VAULT_WITH_NON_PDA_BASED_LP_MINT.get(&vault).cloned(); 29 | 30 | if let Some(lp_mint) = non_derived_based_lp_mint { 31 | lp_mint 32 | } else { 33 | Pubkey::find_program_address( 34 | &[dynamic_vault::seed::LP_MINT_PREFIX.as_ref(), vault.as_ref()], 35 | &dynamic_vault::ID, 36 | ) 37 | .0 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /common/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod dynamic_amm; 2 | pub mod dynamic_vault; 3 | -------------------------------------------------------------------------------- /dynamic-amm-quote/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dynamic-amm-quote" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | 6 | [lib] 7 | crate-type = ["cdylib", "lib"] 8 | name = "dynamic_amm_quote" 9 | 10 | [dependencies] 11 | anchor-lang = "0.28.0" 12 | anchor-spl = "0.28.0" 13 | prog_dynamic_amm = { path = "../programs/dynamic-amm", package = "dynamic-amm" } 14 | prog_dynamic_vault = { path = "../programs/dynamic-vault", package = "dynamic-vault" } 15 | anyhow = "1.0.57" 16 | spl-token-swap = "3.0.0" 17 | meteora-marinade-sdk = { version = "0.1.0", features = ["cpi"] } 18 | spl-stake-pool = { git = "https://github.com/solana-labs/solana-program-library", rev = "cd79bba17331235ab489bae56600043ea853c70b", features = [ 19 | "no-entrypoint", 20 | ] } 21 | meteora-stable-swap-math = { git = "https://github.com/mercurial-finance/stable-swap", rev = "140c2e0d366765d49edc9a175ed12b1ad10c3b66", package = "stable-swap-math" } 22 | meteora-stable-swap-client = { git = "https://github.com/mercurial-finance/stable-swap", rev = "140c2e0d366765d49edc9a175ed12b1ad10c3b66", package = "stable-swap-client" } 23 | 24 | [dev-dependencies] 25 | solana-program-test = "1.16" 26 | anchor-client = "0.28.0" 27 | solana-sdk = "1.16" 28 | bincode = "1.3.3" 29 | -------------------------------------------------------------------------------- /dynamic-amm-quote/src/curve/curve_type.rs: -------------------------------------------------------------------------------- 1 | //! Curve type 2 | use anchor_lang::prelude::*; 3 | 4 | use prog_dynamic_amm::constants::fee::*; 5 | use prog_dynamic_amm::state::PoolFees; 6 | 7 | pub const PERMISSIONLESS_AMP: u64 = 100; 8 | 9 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Default, Copy, Eq, PartialEq)] 10 | /// Multiplier for the pool token. Used to normalized token with different decimal into the same precision. 11 | pub struct TokenMultiplier { 12 | /// Multiplier for token A of the pool. 13 | pub token_a_multiplier: u64, // 8 14 | /// Multiplier for token B of the pool. 15 | pub token_b_multiplier: u64, // 8 16 | /// Record the highest token decimal in the pool. For example, Token A is 6 decimal, token B is 9 decimal. This will save value of 9. 17 | pub precision_factor: u8, // 1 18 | } 19 | 20 | impl TokenMultiplier { 21 | /// Upscale the token amount using token_a_multiplier. 22 | pub fn upscale_token_a(&self, token_amount: u128) -> Option { 23 | token_amount.checked_mul(self.token_a_multiplier.into()) 24 | } 25 | /// Upscale the token amount using token_b_multiplier. 26 | pub fn upscale_token_b(&self, token_amount: u128) -> Option { 27 | token_amount.checked_mul(self.token_b_multiplier.into()) 28 | } 29 | 30 | /// Downscale the token amount using token_a_multiplier 31 | pub fn downscale_token_a(&self, token_amount: u128) -> Option { 32 | token_amount.checked_div(self.token_a_multiplier.into()) 33 | } 34 | /// Downscale the token amount using token_b_multiplier 35 | pub fn downscale_token_b(&self, token_amount: u128) -> Option { 36 | token_amount.checked_div(self.token_b_multiplier.into()) 37 | } 38 | } 39 | 40 | /// Type of depeg pool 41 | #[derive(Clone, Copy, Debug, AnchorDeserialize, AnchorSerialize, PartialEq)] 42 | pub enum DepegType { 43 | /// Indicate that it is not a depeg pool 44 | None, 45 | /// A depeg pool belongs to marinade finance 46 | Marinade, 47 | /// A depeg pool belongs to solido 48 | Lido, 49 | /// A depeg pool belongs to SPL stake pool program 50 | SplStake, 51 | } 52 | 53 | impl DepegType { 54 | /// Check whether the pool is a depeg pool or not 55 | pub fn is_none(&self) -> bool { 56 | matches!(self, DepegType::None) 57 | } 58 | } 59 | 60 | impl Default for DepegType { 61 | fn default() -> Self { 62 | Self::None 63 | } 64 | } 65 | 66 | /// Contains information for depeg pool 67 | #[derive(Clone, Copy, Debug, Default, AnchorSerialize, AnchorDeserialize)] 68 | pub struct Depeg { 69 | /// The virtual price of staking / interest bearing token 70 | pub base_virtual_price: u64, 71 | /// The virtual price of staking / interest bearing token 72 | pub base_cache_updated: u64, 73 | /// Type of the depeg pool 74 | pub depeg_type: DepegType, 75 | } 76 | 77 | #[derive(Clone, Copy, Debug, AnchorDeserialize, AnchorSerialize)] 78 | /// Type of the swap curve 79 | pub enum CurveType { 80 | /// Uniswap-style constant product curve, invariant = token_a_amount * token_b_amount 81 | ConstantProduct, 82 | /// Stable, like uniswap, but with wide zone of 1:1 instead of one point 83 | Stable { 84 | /// Amplification coefficient 85 | amp: u64, 86 | /// Multiplier for the pool token. Used to normalized token with different decimal into the same precision. 87 | token_multiplier: TokenMultiplier, 88 | /// Depeg pool information. Contains functions to allow token amount to be repeg using stake / interest bearing token virtual price 89 | depeg: Depeg, 90 | /// The last amp updated timestamp. Used to prevent update_curve_info called infinitely many times within a short period 91 | last_amp_updated_timestamp: u64, 92 | }, 93 | } 94 | 95 | impl Default for CurveType { 96 | fn default() -> Self { 97 | Self::Stable { 98 | amp: PERMISSIONLESS_AMP, 99 | token_multiplier: TokenMultiplier::default(), 100 | depeg: Depeg::default(), 101 | last_amp_updated_timestamp: 0, 102 | } 103 | } 104 | } 105 | 106 | impl CurveType { 107 | /// Determine whether the curve type is the same regardless of the curve parameters. 108 | pub fn is_same_type(&self, other: &CurveType) -> bool { 109 | matches!( 110 | (self, other), 111 | (CurveType::Stable { .. }, CurveType::Stable { .. }) 112 | | ( 113 | CurveType::ConstantProduct { .. }, 114 | CurveType::ConstantProduct { .. } 115 | ) 116 | ) 117 | } 118 | /// Get default fee settings 119 | pub fn get_default_fee(&self) -> PoolFees { 120 | match self { 121 | CurveType::ConstantProduct {} => PoolFees { 122 | trade_fee_numerator: CONSTANT_PRODUCT_TRADE_FEE_NUMERATOR, 123 | trade_fee_denominator: FEE_DENOMINATOR, 124 | protocol_trade_fee_numerator: CONSTANT_PRODUCT_PROTOCOL_TRADE_FEE_NUMERATOR, 125 | protocol_trade_fee_denominator: FEE_DENOMINATOR, 126 | }, 127 | CurveType::Stable { .. } => PoolFees { 128 | trade_fee_numerator: STABLE_SWAP_TRADE_FEE_NUMERATOR, 129 | trade_fee_denominator: FEE_DENOMINATOR, 130 | protocol_trade_fee_numerator: STABLE_SWAP_PROTOCOL_TRADE_FEE_NUMERATOR, 131 | protocol_trade_fee_denominator: FEE_DENOMINATOR, 132 | }, 133 | } 134 | } 135 | 136 | /// Get allowed trade fee bps 137 | pub fn get_allowed_trade_fee_bps(&self) -> &[u64] { 138 | match self { 139 | CurveType::ConstantProduct {} => &[25, 100, 400, 600], 140 | CurveType::Stable { .. } => &[1, 4, 10, 100], 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /dynamic-amm-quote/src/curve/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod curve_type; 2 | -------------------------------------------------------------------------------- /dynamic-amm-quote/src/depeg/marinade.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use marinade_sdk::state::State; 3 | use prog_dynamic_amm::constants::depeg; 4 | use std::convert::TryInto; 5 | 6 | pub fn get_virtual_price(bytes: &[u8]) -> Option { 7 | let stake_state = State::deserialize(&mut &bytes[8..]).ok()?; 8 | 9 | let virtual_price = (stake_state.msol_price as u128) 10 | .checked_mul(depeg::PRECISION as u128)? 11 | .checked_div(State::PRICE_DENOMINATOR as u128)?; 12 | 13 | virtual_price.try_into().ok() 14 | } 15 | 16 | pub mod stake { 17 | use super::*; 18 | declare_id!("8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC"); 19 | } 20 | -------------------------------------------------------------------------------- /dynamic-amm-quote/src/depeg/mod.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::{Clock, Pubkey}; 2 | use anyhow::{Context, Result}; 3 | 4 | use prog_dynamic_amm::constants::depeg::BASE_CACHE_EXPIRES; 5 | use prog_dynamic_amm::state::CurveType; 6 | use prog_dynamic_amm::state::DepegType; 7 | use prog_dynamic_amm::state::Pool; 8 | use std::collections::HashMap; 9 | 10 | /// Marinade module consists of functions to support marinade depeg pool operation 11 | pub mod marinade; 12 | /// Solido module consists of functions to support solido depeg pool operation 13 | pub mod solido; 14 | /// SPL stake pool module consists of functions to support SPL stake pool based depeg pool operation 15 | pub mod spl_stake; 16 | 17 | fn get_stake_pool_virtual_price( 18 | depeg_type: DepegType, 19 | spl_stake_pool: Pubkey, 20 | stake_data: HashMap>, 21 | ) -> Option { 22 | match depeg_type { 23 | DepegType::Lido => solido::get_virtual_price(&stake_data.get(&solido::stake::ID)?), 24 | DepegType::Marinade => marinade::get_virtual_price(&stake_data.get(&marinade::stake::ID)?), 25 | DepegType::SplStake => spl_stake::get_virtual_price(&stake_data.get(&spl_stake_pool)?), 26 | DepegType::None => None, 27 | } 28 | } 29 | 30 | /// Update depeg base virtual price 31 | pub fn update_base_virtual_price( 32 | pool: &mut Pool, 33 | clock: &Clock, 34 | stake_data: HashMap>, 35 | ) -> Result<()> { 36 | match &mut pool.curve_type { 37 | CurveType::ConstantProduct => Ok(()), 38 | CurveType::Stable { depeg, .. } => { 39 | if !depeg.depeg_type.is_none() { 40 | let cache_expire_time = depeg 41 | .base_cache_updated 42 | .checked_add(BASE_CACHE_EXPIRES) 43 | .context("Math overflow")?; 44 | 45 | if clock.unix_timestamp as u64 > cache_expire_time { 46 | let virtual_price = 47 | get_stake_pool_virtual_price(depeg.depeg_type, pool.stake, stake_data) 48 | .context("Fail to get stake pool virtual price")?; 49 | 50 | depeg.base_cache_updated = clock.unix_timestamp as u64; 51 | depeg.base_virtual_price = virtual_price; 52 | } 53 | } 54 | Ok(()) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /dynamic-amm-quote/src/depeg/solido.rs: -------------------------------------------------------------------------------- 1 | use prog_dynamic_amm::constants::depeg; 2 | use std::convert::TryInto; 3 | 4 | pub fn get_virtual_price(bytes: &[u8]) -> Option { 5 | let mut stsol_supply_byte = [0u8; 8]; 6 | let mut stol_balance_bytes = [0u8; 8]; 7 | 8 | stsol_supply_byte.copy_from_slice(&bytes[73..81]); 9 | stol_balance_bytes.copy_from_slice(&bytes[81..89]); 10 | 11 | let stsol_supply = u64::from_le_bytes(stsol_supply_byte); 12 | let sol_balance = u64::from_le_bytes(stol_balance_bytes); 13 | 14 | let stsol_price = (sol_balance as u128) 15 | .checked_mul(depeg::PRECISION as u128)? 16 | .checked_div(stsol_supply as u128)?; 17 | 18 | stsol_price.try_into().ok() 19 | } 20 | 21 | pub mod stake { 22 | use anchor_lang::prelude::declare_id; 23 | declare_id!("49Yi1TKkNyYjPAFdR9LBvoHcUjuPX4Df5T5yv39w2XTn"); 24 | } 25 | -------------------------------------------------------------------------------- /dynamic-amm-quote/src/depeg/spl_stake.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::solana_program::borsh0_10; 2 | use prog_dynamic_amm::constants::depeg; 3 | use spl_stake_pool::state::StakePool; 4 | use std::convert::TryInto; 5 | 6 | pub fn get_virtual_price(bytes: &[u8]) -> Option { 7 | let stake: StakePool = borsh0_10::try_from_slice_unchecked(bytes).ok()?; 8 | 9 | let virtual_price = (stake.total_lamports as u128) 10 | .checked_mul(depeg::PRECISION as u128)? 11 | .checked_div(stake.pool_token_supply as u128)?; 12 | 13 | virtual_price.try_into().ok() 14 | } 15 | -------------------------------------------------------------------------------- /dynamic-amm-quote/src/math/constant_product.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use spl_token_swap::curve::{calculator::SwapWithoutFeesResult, constant_product::swap}; 3 | 4 | pub struct ConstantProduct {} 5 | 6 | impl SwapCurve for ConstantProduct { 7 | fn swap( 8 | &self, 9 | source_amount: u64, 10 | swap_source_amount: u64, 11 | swap_destination_amount: u64, 12 | _trade_direction: TradeDirection, 13 | ) -> Option { 14 | let source_amount: u128 = source_amount.into(); 15 | let swap_source_amount: u128 = swap_source_amount.into(); 16 | let swap_destination_amount: u128 = swap_destination_amount.into(); 17 | 18 | let SwapWithoutFeesResult { 19 | source_amount_swapped, 20 | destination_amount_swapped, 21 | } = swap(source_amount, swap_source_amount, swap_destination_amount)?; 22 | 23 | Some(SwapResult { 24 | new_swap_source_amount: swap_source_amount.checked_add(source_amount_swapped)?, 25 | new_swap_destination_amount: swap_destination_amount 26 | .checked_sub(destination_amount_swapped)?, 27 | source_amount_swapped, 28 | destination_amount_swapped, 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /dynamic-amm-quote/src/math/mod.rs: -------------------------------------------------------------------------------- 1 | use spl_token_swap::curve::calculator::TradeDirection; 2 | 3 | use prog_dynamic_amm::state::CurveType; 4 | 5 | use self::{constant_product::ConstantProduct, stable_swap::StableSwap}; 6 | 7 | mod constant_product; 8 | mod stable_swap; 9 | 10 | /// Encodes all results of swapping 11 | #[derive(Debug, PartialEq)] 12 | pub struct SwapResult { 13 | /// New amount of source token 14 | pub new_swap_source_amount: u128, 15 | /// New amount of destination token 16 | pub new_swap_destination_amount: u128, 17 | /// Amount of source token swapped (includes fees) 18 | pub source_amount_swapped: u128, 19 | /// Amount of destination token swapped 20 | pub destination_amount_swapped: u128, 21 | } 22 | 23 | pub trait SwapCurve { 24 | fn swap( 25 | &self, 26 | source_amount: u64, 27 | swap_source_amount: u64, 28 | swap_destination_amount: u64, 29 | trade_direction: TradeDirection, 30 | ) -> Option; 31 | } 32 | 33 | /// Get swap curve for calculation 34 | pub fn get_swap_curve(curve_type: CurveType) -> Box { 35 | match curve_type { 36 | CurveType::ConstantProduct => Box::new(ConstantProduct {}), 37 | CurveType::Stable { 38 | amp, 39 | token_multiplier, 40 | depeg, 41 | last_amp_updated_timestamp, 42 | } => Box::new(StableSwap { 43 | amp, 44 | depeg, 45 | last_amp_updated_timestamp, 46 | token_multiplier, 47 | }), 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /dynamic-amm-quote/src/math/stable_swap.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use meteora_stable_swap_client::fees::Fees as SaberFees; 3 | use meteora_stable_swap_math::curve::StableSwap as SaberStableSwap; 4 | use prog_dynamic_amm::constants::{depeg::PRECISION, fee::FEE_DENOMINATOR}; 5 | use prog_dynamic_amm::state::{Depeg, DepegType, TokenMultiplier}; 6 | 7 | /// Stable swap curve 8 | #[derive(Clone, Copy, Debug)] 9 | pub struct StableSwap { 10 | pub amp: u64, 11 | pub token_multiplier: TokenMultiplier, 12 | pub depeg: Depeg, 13 | pub last_amp_updated_timestamp: u64, 14 | } 15 | 16 | impl StableSwap { 17 | fn upscale_token_a(&self, token_amount: u128) -> Option { 18 | let normalized_token_amount = self.token_multiplier.upscale_token_a(token_amount)?; 19 | 20 | if self.depeg.depeg_type != DepegType::None { 21 | normalized_token_amount.checked_mul(PRECISION.into()) 22 | } else { 23 | Some(normalized_token_amount) 24 | } 25 | } 26 | 27 | fn downscale_token_a(&self, token_amount: u128) -> Option { 28 | let denormalized_token_amount = self.token_multiplier.downscale_token_a(token_amount)?; 29 | 30 | if self.depeg.depeg_type != DepegType::None { 31 | denormalized_token_amount.checked_div(PRECISION.into()) 32 | } else { 33 | Some(denormalized_token_amount) 34 | } 35 | } 36 | 37 | fn upscale_token_b(&self, token_amount: u128) -> Option { 38 | let normalized_token_amount = self.token_multiplier.upscale_token_b(token_amount)?; 39 | 40 | if self.depeg.depeg_type != DepegType::None { 41 | normalized_token_amount.checked_mul(self.depeg.base_virtual_price.into()) 42 | } else { 43 | Some(normalized_token_amount) 44 | } 45 | } 46 | 47 | fn downscale_token_b(&self, token_amount: u128) -> Option { 48 | let denormalized_token_amount = self.token_multiplier.downscale_token_b(token_amount)?; 49 | 50 | if self.depeg.depeg_type != DepegType::None { 51 | denormalized_token_amount.checked_div(self.depeg.base_virtual_price.into()) 52 | } else { 53 | Some(denormalized_token_amount) 54 | } 55 | } 56 | } 57 | 58 | impl SwapCurve for StableSwap { 59 | fn swap( 60 | &self, 61 | source_amount: u64, 62 | swap_source_amount: u64, 63 | swap_destination_amount: u64, 64 | trade_direction: TradeDirection, 65 | ) -> Option { 66 | let (upscaled_source_amount, upscaled_swap_source_amount, upscaled_swap_destination_amount) = 67 | match trade_direction { 68 | TradeDirection::AtoB => ( 69 | self.upscale_token_a(source_amount.into())?, 70 | self.upscale_token_a(swap_source_amount.into())?, 71 | self.upscale_token_b(swap_destination_amount.into())?, 72 | ), 73 | TradeDirection::BtoA => ( 74 | self.upscale_token_b(source_amount.into())?, 75 | self.upscale_token_b(swap_source_amount.into())?, 76 | self.upscale_token_a(swap_destination_amount.into())?, 77 | ), 78 | }; 79 | 80 | let saber_stable_swap: SaberStableSwap = self.into(); 81 | let result = saber_stable_swap.swap_to2( 82 | upscaled_source_amount, 83 | upscaled_swap_source_amount, 84 | upscaled_swap_destination_amount, 85 | &SaberFees { 86 | admin_trade_fee_denominator: FEE_DENOMINATOR, 87 | admin_withdraw_fee_denominator: FEE_DENOMINATOR, 88 | trade_fee_denominator: FEE_DENOMINATOR, 89 | withdraw_fee_denominator: FEE_DENOMINATOR, 90 | ..Default::default() 91 | }, 92 | )?; 93 | 94 | let downscaled_destination_amount_swapped = match trade_direction { 95 | TradeDirection::AtoB => self.downscale_token_b(result.amount_swapped)?, 96 | TradeDirection::BtoA => self.downscale_token_a(result.amount_swapped)?, 97 | }; 98 | 99 | let swap_source_amount: u128 = swap_source_amount.into(); 100 | let swap_destination_amount: u128 = swap_destination_amount.into(); 101 | let source_amount_swapped: u128 = source_amount.into(); 102 | 103 | let new_swap_source_amount: u128 = swap_source_amount.checked_add(source_amount_swapped)?; 104 | let new_swap_destination_amount = 105 | swap_destination_amount.checked_sub(downscaled_destination_amount_swapped)?; 106 | 107 | Some(SwapResult { 108 | source_amount_swapped, 109 | destination_amount_swapped: downscaled_destination_amount_swapped, 110 | new_swap_source_amount, 111 | new_swap_destination_amount, 112 | }) 113 | } 114 | } 115 | 116 | impl From<&StableSwap> for SaberStableSwap { 117 | fn from(stable_swap: &StableSwap) -> Self { 118 | SaberStableSwap::new(stable_swap.amp, stable_swap.amp, 0, 0, 0) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/21bR3D4QR4GzopVco44PVMBXwHFpSYrbrdeNwdKk7umb.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "21bR3D4QR4GzopVco44PVMBXwHFpSYrbrdeNwdKk7umb", 3 | "account": { 4 | "lamports": 1461600, 5 | "data": [ 6 | "AQAAAHQMqJZ15aFdp8y8JXLSbnFb6oH+RX0aw4TnXuK0Zp80o9b0ohtKAAAJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 82 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/3ifhD4Ywaa8aBZAaQSqYgN4Q1kaFArioLU8uumJMaqkE.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "3ifhD4Ywaa8aBZAaQSqYgN4Q1kaFArioLU8uumJMaqkE", 3 | "account": { 4 | "lamports": 2039280, 5 | "data": [ 6 | "C2K6B09yLJ1BFPLY9woAxmACM3ub+QyHNlem0gHbTIB0DKiWdeWhXafMvCVy0m5xW+qB/kV9GsOE517itGafNEKUdUU2SgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 165 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC", 3 | "account": { 4 | "lamports": 19098240, 5 | "data": [ 6 | "2JJrXmhLtrELYroHT3IsnUEU8tj3CgDGYAIze5v5DIc2V6bSAdtMgCz0pOqMnBuYfUelv3scmcjjaofs/kslxbI+gD7+Ut+uC/49Oop/nKXYae0Dqcuh7qDJnWp2becvnXIEbPLV10KUupJ+KKToqJZx7UJwZ9wkAWDSf0aWriv0Dv97EAFr4P/98B0fAAAAAABYAgAAkXxLkU5TmaKSfT1u3GUbZ6yxn8JqR8F+QHm/uCY9TmU4AAAALAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA//9QRgAAAAAAAHgCAAAAAAAAAMqaOwAAAAD2AAAAwDIL/Z2D7Ta752Tw4gzH+4TU3CHhPclFCUyyyPHhXrM9AAAA+gUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv+PTqKf5yl2GntA6nLoe6gyZ1qdm3nL51yBGzy1ddCvkx8APwJFxQAHxMAAAT3xv1GEmfOZWpZn+8+77eG7hSUZQYRGrRQHOtpsr0a//7/XSpe5WhcF+B87eW++YMA1BcOu+LZnwZMS7Be6Xs13n0AUIpxGRMAAIQDAAABAAAAiBMAAER6byKGDQAAAAAAAAAAAAD//////////0vbrPWevQAAQf756ax7EACDyc8yAQAAAAsEAAAAAAAA7DetX2kbAAAAAAAAAAAAAAEAAAAAAAAAAQAAAAAAAAD//////////wAAAAAAAAAAkI1cukqxvvmHbzHoCpbW25/Jsuhuk7o3gXFWXUu9zhcAAAAAANwFAAABeAIAAAAAAABrD3QsimMAABAnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 2616 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/AYX6kFvQRacdbC1eBaK5kgEPGjzjYYd1fyduHg55Z1ai.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "AYX6kFvQRacdbC1eBaK5kgEPGjzjYYd1fyduHg55Z1ai", 3 | "account": { 4 | "lamports": 2039280, 5 | "data": [ 6 | "C2K6B09yLJ1BFPLY9woAxmACM3ub+QyHNlem0gHbTID1XEvqBOK7QWBl0Y2UbZnVUAPjJolE7qJ/8SYaXaaSvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 165 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/B2uEs9zjnz222hfUaUuRgesryUEYwy3JGuWe31sE9gsG.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "B2uEs9zjnz222hfUaUuRgesryUEYwy3JGuWe31sE9gsG", 3 | "account": { 4 | "lamports": 1461600, 5 | "data": [ 6 | "AQAAAPVcS+oE4rtBYGXRjZRtmdVQA+MmiUTuon/xJhpdppK+MFmHf+NxAAAJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 82 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/FZN7QZ8ZUUAxMPfxYEYkH3cXUASzH8EqA6B4tyCL8f1j.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "FZN7QZ8ZUUAxMPfxYEYkH3cXUASzH8EqA6B4tyCL8f1j", 3 | "account": { 4 | "lamports": 1461600, 5 | "data": [ 6 | "AQAAANN0IXa4nF4UyWt0iZhCHzgQHgnlxZMxsWi2Jc0nFbsmYW8Z0MiEAAAJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 82 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/GGG4DxkYa86g2v4KwtvR8Xu2tXEp1xd4BRC3yNnpve3g.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "GGG4DxkYa86g2v4KwtvR8Xu2tXEp1xd4BRC3yNnpve3g", 3 | "account": { 4 | "lamports": 2039280, 5 | "data": [ 6 | "DwKZdsKcLp8riaHAgy1T5ZYdnBlOiWZ5SUa/Wgo2UEr1XEvqBOK7QWBl0Y2UbZnVUAPjJolE7qJ/8SYaXaaSvmslxsPURwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 165 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/GYuyKhedWVsndqG5tyXwRiRSTxFKrLJysgedccqU7aH8.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "GYuyKhedWVsndqG5tyXwRiRSTxFKrLJysgedccqU7aH8", 3 | "account": { 4 | "lamports": 2039280, 5 | "data": [ 6 | "BpuIV/6rgYT7aH9jRhjANdrEOdwa6ztVmKDwAAAAAAH1XEvqBOK7QWBl0Y2UbZnVUAPjJolE7qJ/8SYaXaaSvgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEAAADwHR8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 165 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/HWnWnLvBzvSmXH5hnHJCFmuQbDTsX3Ba2w9CPE5zf4YD.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "HWnWnLvBzvSmXH5hnHJCFmuQbDTsX3Ba2w9CPE5zf4YD", 3 | "account": { 4 | "lamports": 2039280, 5 | "data": [ 6 | "2E4SkMGY54i6DhFPA4IUZ5YI2ij80goWMe/Dk/u/aSr1XEvqBOK7QWBl0Y2UbZnVUAPjJolE7qJ/8SYaXaaSvknJ11UhJgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 165 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/HZeLxbZ9uHtSpwZC3LBr4Nubd14iHwz7bRSghRZf5VCG.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "HZeLxbZ9uHtSpwZC3LBr4Nubd14iHwz7bRSghRZf5VCG", 3 | "account": { 4 | "lamports": 63190651879008, 5 | "data": [ 6 | "BpuIV/6rgYT7aH9jRhjANdrEOdwa6ztVmKDwAAAAAAHTdCF2uJxeFMlrdImYQh84EB4J5cWTMbFotiXNJxW7JnCgPLh4OQAAAAAAACCNsxRkCXS8q0WTSvTwSFWYFR/1oIvF8ggnbxxLTbFlAQEAAADwHR8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 165 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/HcjZvfeSNJbNkfLD4eEcRBr96AD3w1GpmMppaeRZf7ur.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "HcjZvfeSNJbNkfLD4eEcRBr96AD3w1GpmMppaeRZf7ur", 3 | "account": { 4 | "lamports": 10544400, 5 | "data": [ 6 | "8ZptBBGxbbyVEYRW8gXtmhkmbvr1ljBJwCg7O1lO0GY/wq9t+0WELwabiFf+q4GE+2h/Y0YYwDXaxDncGus7VZig8AAAAAABC2K6B09yLJ1BFPLY9woAxmACM3ub+QyHNlem0gHbTIDTdCF2uJxeFMlrdImYQh84EB4J5cWTMbFotiXNJxW7JnQMqJZ15aFdp8y8JXLSbnFb6oH+RX0aw4TnXuK0Zp809VxL6gTiu0FgZdGNlG2Z1VAD4yaJRO6if/EmGl2mkr7iyAoX3ZxhiQdWvg2S0aX/QHbSgOIpGxar96X/R8wzZ/8B5ww0HL5IWSR3u78ipHfHCXiQu1VbX9zoAAALAGcI3SuNzDh1Wl2wSU8kQKe5KY+TENvCcoLa1/8WSdLxs6i/I0j0Weo18CFGxFjKyXY/SxEFclXL7jxORa3uw9uLKhs3ZAAAAAAAAABAQg8AAAAAAAAAAAAAAAAAQEIPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABXA84AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHoAwAAAAAAAAEAAAAAAAAAAQAAAAAAAAAJk0kSAAAAAAA1bnlmAAAAAAEEdc1kAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", 7 | "base64" 8 | ], 9 | "owner": "Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 1387 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/accounts/mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "mSoLzYCxHdYgdzU16g5QSh3i5K3z3KZK7ytfqcJm7So", 3 | "account": { 4 | "lamports": 7897437308, 5 | "data": [ 6 | "AQAAACIoKeiXZ7IEPIbRtR8xNk5a2uuGH9Yuen9Gvk27xVykN1CCP6x7EAAJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 82 13 | } 14 | } -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/dynamic_amm.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dynamic-amm-sdk/b21e2efb3680c17a68149ed2e22465aeef9b3784/dynamic-amm-quote/tests/fixtures/dynamic_amm.so -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/dynamic_vault.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dynamic-amm-sdk/b21e2efb3680c17a68149ed2e22465aeef9b3784/dynamic-amm-quote/tests/fixtures/dynamic_vault.so -------------------------------------------------------------------------------- /dynamic-amm-quote/tests/fixtures/metaplex.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MeteoraAg/dynamic-amm-sdk/b21e2efb3680c17a68149ed2e22465aeef9b3784/dynamic-amm-quote/tests/fixtures/metaplex.so -------------------------------------------------------------------------------- /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] -------------------------------------------------------------------------------- /programs/dynamic-amm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dynamic-amm" 3 | version = "0.6.1" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [features] 8 | default = ["cpi"] 9 | no-entrypoint = [] 10 | cpi = ["no-entrypoint"] 11 | localnet = [] 12 | staging = [] 13 | 14 | [lib] 15 | crate-type = ["cdylib", "lib"] 16 | name = "dynamic_amm" 17 | 18 | [dependencies] 19 | anchor-lang = { version = "0.28.0", features = ["event-cpi"] } 20 | anchor-spl = "0.28.0" 21 | solana-program = "1.16.0" 22 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/constants.rs: -------------------------------------------------------------------------------- 1 | //! Constants module includes constants value of the program 2 | use anchor_lang::prelude::*; 3 | use anchor_lang::solana_program::pubkey; 4 | 5 | /// Minimum seconds between last AMP changes 6 | pub const MIN_CHANGE_AMP_DURATION: u64 = 600; // 10 minutes 7 | 8 | pub mod seeds { 9 | pub const CONFIG_PREFIX: &[u8] = b"config"; 10 | } 11 | 12 | /// Store constants related to fees 13 | pub mod fee { 14 | /// Trade fee numerator for constant product swap curve. 15 | // 25bps, https://docs.uniswap.org/protocol/V2/concepts/advanced-topics/fees 16 | pub const CONSTANT_PRODUCT_TRADE_FEE_NUMERATOR: u64 = 250; 17 | 18 | /// Trade fee numerator for stable swap curve. 19 | // 1bps, https://curve.fi/rootfaq 20 | pub const STABLE_SWAP_TRADE_FEE_NUMERATOR: u64 = 10; 21 | 22 | /// Protocol trade fee numerator for constant product swap curve. 23 | // 5bps, https://docs.uniswap.org/protocol/V2/concepts/advanced-topics/fees 24 | // pub const CONSTANT_PRODUCT_PROTOCOL_TRADE_FEE_NUMERATOR: u64 = 50; 25 | pub const CONSTANT_PRODUCT_PROTOCOL_TRADE_FEE_NUMERATOR: u64 = 0; // Set all protocol fees to zero, we will enable later when we have ve(3, 3) 26 | 27 | /// Protocol trade fee numerator for stable swap curve. 28 | // 0.5bps, https://curve.fi/rootfaq 29 | // pub const STABLE_SWAP_PROTOCOL_TRADE_FEE_NUMERATOR: u64 = 5; 30 | pub const STABLE_SWAP_PROTOCOL_TRADE_FEE_NUMERATOR: u64 = 0; // Set all protocol fees to zero, we will enable later when we have ve(3, 3) 31 | 32 | /// Host trade fee numerator 33 | // 20% of protocol trade fee 34 | pub const HOST_TRADE_FEE_NUMERATOR: u64 = 20000; 35 | 36 | /// Default fee denominator. DO NOT simply update it as it will break logic that depends on it as default value. 37 | pub const FEE_DENOMINATOR: u64 = 100000; 38 | /// Max fee BPS 39 | pub const MAX_FEE_BPS: u64 = 1500; // 15% 40 | /// Max basis point. 100% in pct 41 | pub const MAX_BASIS_POINT: u64 = 10000; 42 | 43 | // For meme coins 44 | pub const MEME_MIN_FEE_NUMERATOR: u64 = 250; // 250 / FEE_DENOMINATOR = 0.25% 45 | pub const MEME_MAX_FEE_NUMERATOR: u64 = 15000; // 15_000 / FEE_DENOMINATOR = 15% 46 | 47 | pub const MEME_MIN_FEE_BPS: u64 = 25; // 0.25% 48 | pub const MEME_MAX_FEE_BPS: u64 = 1500; // 15% 49 | 50 | pub const MEME_PROTOCOL_FEE_NUMERATOR: u64 = 20000; // 20% 51 | 52 | pub const MEME_MIN_FEE_UPDATE_WINDOW_DURATION: i64 = 60 * 30; // 30 minutes 53 | 54 | pub const MAX_PARTNER_FEE_NUMERATOR: u64 = 50000; // 50% 55 | } 56 | 57 | pub mod activation { 58 | #[cfg(not(feature = "test-bpf"))] 59 | pub const SLOT_BUFFER: u64 = 9000; 60 | #[cfg(feature = "test-bpf")] 61 | pub const SLOT_BUFFER: u64 = 5; 62 | 63 | #[cfg(not(feature = "test-bpf"))] 64 | pub const TIME_BUFFER: u64 = 3600; // 1 hour 65 | #[cfg(feature = "test-bpf")] 66 | pub const TIME_BUFFER: u64 = 5; // 5 secs 67 | 68 | #[cfg(not(feature = "test-bpf"))] 69 | pub const MAX_ACTIVATION_SLOT_DURATION: u64 = SLOT_BUFFER * 24 * 31; // 31 days 70 | #[cfg(feature = "test-bpf")] 71 | pub const MAX_ACTIVATION_SLOT_DURATION: u64 = 30; 72 | 73 | #[cfg(not(feature = "test-bpf"))] 74 | pub const MAX_ACTIVATION_TIME_DURATION: u64 = TIME_BUFFER * 24 * 31; // 31 days 75 | #[cfg(feature = "test-bpf")] 76 | pub const MAX_ACTIVATION_TIME_DURATION: u64 = 30; 77 | 78 | #[cfg(not(feature = "localnet"))] 79 | pub const FIVE_MINUTES_SLOT_BUFFER: u64 = SLOT_BUFFER / 12; // 5 minutes 80 | 81 | #[cfg(feature = "localnet")] 82 | pub const FIVE_MINUTES_SLOT_BUFFER: u64 = 5; 83 | 84 | #[cfg(not(feature = "localnet"))] 85 | pub const FIVE_MINUTES_TIME_BUFFER: u64 = TIME_BUFFER / 12; // 5 minutes 86 | 87 | #[cfg(feature = "localnet")] 88 | pub const FIVE_MINUTES_TIME_BUFFER: u64 = 5; 89 | } 90 | 91 | /// Store constants related to virtual price 92 | pub mod virtual_price { 93 | /// Decimal price of virtual price 94 | pub const DECIMAL: u8 = 8; 95 | /// Precision for virtual price calculation 96 | // Up-scaling, safe 97 | pub const PRECISION: i32 = 10_i32.pow(DECIMAL as u32); 98 | } 99 | 100 | /// Store constants related to stable swap curve 101 | 102 | pub mod stable_curve { 103 | /// Maximum supported amplification coefficient 104 | pub const MAX_AMP: u64 = 10_000; 105 | /// Maximum ramping of amplification coefficient 106 | pub const MAX_A_CHANGE: u64 = MAX_AMP; 107 | } 108 | 109 | /// Store constants related to depeg pool 110 | pub mod depeg { 111 | /// Base virtual time caching time, 10 minutes 112 | pub const BASE_CACHE_EXPIRES: u64 = 60 * 10; 113 | /// Precision for depeg pool virtual price calculation, 6 has been chosen because most of the token was 6 d.p 114 | pub const PRECISION: u64 = 10_u64.pow(6_u32); 115 | } 116 | 117 | // Supported quote mints 118 | const SOL: Pubkey = pubkey!("So11111111111111111111111111111111111111112"); 119 | const USDC: Pubkey = pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); 120 | pub const QUOTE_MINTS: [Pubkey; 2] = [SOL, USDC]; 121 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error module includes error messages and codes of the program 2 | use anchor_lang::prelude::*; 3 | 4 | /// Error messages and codes of the program 5 | #[error_code] 6 | #[derive(PartialEq)] 7 | pub enum PoolError { 8 | /// Mathematic operation results in overflow. 9 | #[msg("Math operation overflow")] 10 | MathOverflow, 11 | 12 | /// Invalid fee configuration 13 | #[msg("Invalid fee setup")] 14 | InvalidFee, 15 | 16 | /// Invalid invariant d amount 17 | #[msg("Invalid invariant d")] 18 | InvalidInvariant, 19 | 20 | /// Failed to calculate fees. 21 | #[msg("Fee calculation failure")] 22 | FeeCalculationFailure, 23 | 24 | /// The operation exceeds slippage defined by the user. 25 | #[msg("Exceeded slippage tolerance")] 26 | ExceededSlippage, 27 | 28 | /// Swap curve calculation results in error. 29 | #[msg("Invalid curve calculation")] 30 | InvalidCalculation, 31 | 32 | /// Swap curve calculation results in zero token A/B. 33 | #[msg("Given pool token amount results in zero trading tokens")] 34 | ZeroTradingTokens, 35 | 36 | /// Type conversion results in error. 37 | #[msg("Math conversion overflow")] 38 | ConversionError, 39 | 40 | /// Invalid LP mint account. 41 | #[msg("LP mint authority must be 'A' vault lp, without freeze authority, and 0 supply")] 42 | FaultyLpMint, 43 | 44 | /// Invalid token mint account. 45 | #[msg("Token mint mismatched")] 46 | MismatchedTokenMint, 47 | 48 | /// Invalid LP mint account. 49 | #[msg("LP mint mismatched")] 50 | MismatchedLpMint, 51 | 52 | /// Invalid owner account. 53 | #[msg("Invalid lp token owner")] 54 | MismatchedOwner, 55 | 56 | /// Invalid vault account. 57 | #[msg("Invalid vault account")] 58 | InvalidVaultAccount, 59 | 60 | /// Invalud vault LP account. 61 | #[msg("Invalid vault lp account")] 62 | InvalidVaultLpAccount, 63 | 64 | /// Invalid pool LP mint account. 65 | #[msg("Invalid pool lp mint account")] 66 | InvalidPoolLpMintAccount, 67 | 68 | /// The pool was disabled. 69 | #[msg("Pool disabled")] 70 | PoolDisabled, 71 | 72 | /// Invalid admin account. 73 | #[msg("Invalid admin account")] 74 | InvalidAdminAccount, 75 | 76 | /// Invalid protocol fee token account. 77 | #[msg("Invalid protocol fee account")] 78 | InvalidProtocolFeeAccount, 79 | 80 | /// Old and new admin are the same account. 81 | #[msg("Same admin account")] 82 | SameAdminAccount, 83 | 84 | /// Source and destination token mint are the same. 85 | #[msg("Identical user source and destination token account")] 86 | IdenticalSourceDestination, 87 | 88 | /// APY calculation results in error. 89 | #[msg("Apy calculation error")] 90 | ApyCalculationError, 91 | 92 | /// Insufficient virtual price snapshot. 93 | #[msg("Insufficient virtual price snapshot")] 94 | InsufficientSnapshot, 95 | 96 | /// Curve is not updatable. 97 | #[msg("Current curve is non-updatable")] 98 | NonUpdatableCurve, 99 | 100 | /// The new curve is not the same type as the old curve. 101 | #[msg("New curve is mismatched with old curve")] 102 | MisMatchedCurve, 103 | 104 | /// Invalid amplification coefficient value. 105 | #[msg("Amplification is invalid")] 106 | InvalidAmplification, 107 | 108 | /// The operation is not supported. 109 | #[msg("Operation is not supported")] 110 | UnsupportedOperation, 111 | 112 | /// The ramping of amplification coefficient over the allowed value. 113 | #[msg("Exceed max amplification changes")] 114 | ExceedMaxAChanges, 115 | 116 | /// Invalid number of remaining accounts 117 | #[msg("Invalid remaining accounts length")] 118 | InvalidRemainingAccountsLen, 119 | 120 | /// Invalid remaining accounts 121 | #[msg("Invalid remaining account")] 122 | InvalidRemainingAccounts, 123 | 124 | /// Pool token B mint doesn't match with depeg token mint address 125 | #[msg("Token mint B doesn't matches depeg type token mint")] 126 | MismatchedDepegMint, 127 | 128 | /// Invalid APY account 129 | #[msg("Invalid APY account")] 130 | InvalidApyAccount, 131 | 132 | /// Invalid token multiplier for stable swap curve 133 | #[msg("Invalid token multiplier")] 134 | InvalidTokenMultiplier, 135 | 136 | /// Invalid depeg information 137 | #[msg("Invalid depeg information")] 138 | InvalidDepegInformation, 139 | 140 | /// Update time violated the cooldown interval 141 | #[msg("Update time constraint violated")] 142 | UpdateTimeConstraint, 143 | 144 | /// Pool fee exceed allowed max fee bps 145 | #[msg("Exceeded max fee bps")] 146 | ExceedMaxFeeBps, 147 | 148 | /// Invalid admin 149 | #[msg("Invalid admin")] 150 | InvalidAdmin, 151 | 152 | /// Pool is not permissioned 153 | #[msg("Pool is not permissioned")] 154 | PoolIsNotPermissioned, 155 | 156 | /// Invalid deposit amount 157 | #[msg("Invalid deposit amount")] 158 | InvalidDepositAmount, 159 | 160 | /// Invalid fee owner 161 | #[msg("Invalid fee owner")] 162 | InvalidFeeOwner, 163 | 164 | /// Pool is not depleted 165 | #[msg("Pool is not depleted")] 166 | NonDepletedPool, 167 | 168 | /// Amount is not peg 169 | #[msg("Token amount is not 1:1")] 170 | AmountNotPeg, 171 | 172 | /// Amount is zero 173 | #[msg("Amount is zero")] 174 | AmountIsZero, 175 | 176 | /// Type case failed 177 | #[msg("Type cast error")] 178 | TypeCastFailed, 179 | 180 | /// AmountIsNotEnough 181 | #[msg("Amount is not enough")] 182 | AmountIsNotEnough, 183 | 184 | /// InvalidActivationDuration 185 | #[msg("Invalid activation duration")] 186 | InvalidActivationDuration, 187 | 188 | /// Pool is not launch pool 189 | #[msg("Pool is not launch pool")] 190 | PoolIsNotLaunchPool, 191 | 192 | /// UnableToModifyActivationPoint 193 | #[msg("Unable to modify activation point")] 194 | UnableToModifyActivationPoint, 195 | 196 | /// Invalid authority to create the pool 197 | #[msg("Invalid authority to create the pool")] 198 | InvalidAuthorityToCreateThePool, 199 | 200 | #[msg("Invalid activation type")] 201 | InvalidActivationType, 202 | 203 | #[msg("Invalid activation point")] 204 | InvalidActivationPoint, 205 | 206 | #[msg("Pre activation swap window started")] 207 | PreActivationSwapStarted, 208 | 209 | /// Invalid pool type 210 | #[msg("Invalid pool type")] 211 | InvalidPoolType, 212 | 213 | #[msg("Quote token must be SOL,USDC")] 214 | InvalidQuoteMint, 215 | } 216 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/event.rs: -------------------------------------------------------------------------------- 1 | //! Event module includes information about events of the program 2 | use anchor_lang::prelude::*; 3 | 4 | use crate::state::PoolType; 5 | 6 | /// Add liquidity event 7 | #[event] 8 | pub struct AddLiquidity { 9 | /// LP amount user received upon add liquidity. 10 | pub lp_mint_amount: u64, 11 | /// Amount of token A user deposited. 12 | pub token_a_amount: u64, 13 | /// Amount of token B user deposited. 14 | pub token_b_amount: u64, 15 | } 16 | 17 | /// Remove liquidity event 18 | #[event] 19 | pub struct RemoveLiquidity { 20 | /// LP amount burned from user upon add remove liquidity. 21 | pub lp_unmint_amount: u64, 22 | /// Amount of token A user received. 23 | pub token_a_out_amount: u64, 24 | /// Amount of token B user received. 25 | pub token_b_out_amount: u64, 26 | } 27 | 28 | /// Bootstrap liquidity event 29 | #[event] 30 | pub struct BootstrapLiquidity { 31 | /// LP amount user received upon add liquidity. 32 | pub lp_mint_amount: u64, 33 | /// Amount of token A user deposited. 34 | pub token_a_amount: u64, 35 | /// Amount of token B user deposited. 36 | pub token_b_amount: u64, 37 | /// Pool address 38 | pub pool: Pubkey, 39 | } 40 | 41 | /// Swap event 42 | #[event] 43 | pub struct Swap { 44 | /// Token amount user deposited to the pool for token exchange. 45 | pub in_amount: u64, 46 | /// Token amount user received from the pool. 47 | pub out_amount: u64, 48 | /// Trading fee charged for liquidity provider. 49 | pub trade_fee: u64, 50 | /// Trading fee charged for the protocol. 51 | pub protocol_fee: u64, 52 | /// Host fee charged 53 | pub host_fee: u64, 54 | } 55 | 56 | /// Set pool fees event 57 | #[event] 58 | pub struct SetPoolFees { 59 | /// New trade fee numerator 60 | pub trade_fee_numerator: u64, 61 | /// New trade fee denominator 62 | pub trade_fee_denominator: u64, 63 | /// New protocol fee numerator 64 | pub protocol_trade_fee_numerator: u64, 65 | /// New protocol fee denominator 66 | pub protocol_trade_fee_denominator: u64, 67 | /// Pool address 68 | pub pool: Pubkey, 69 | } 70 | 71 | /// Pool info event 72 | #[event] 73 | pub struct PoolInfo { 74 | /// Total token A amount in the pool 75 | pub token_a_amount: u64, 76 | /// Total token B amount in the pool 77 | pub token_b_amount: u64, 78 | /// Current virtual price 79 | pub virtual_price: f64, 80 | /// Current unix timestamp 81 | pub current_timestamp: u64, 82 | } 83 | 84 | /// Transfer admin event 85 | #[event] 86 | pub struct TransferAdmin { 87 | /// Old admin of the pool 88 | pub admin: Pubkey, 89 | /// New admin of the pool 90 | pub new_admin: Pubkey, 91 | /// Pool address 92 | pub pool: Pubkey, 93 | } 94 | 95 | /// Override curve param event 96 | #[event] 97 | pub struct OverrideCurveParam { 98 | /// The new amplification for stable curve 99 | pub new_amp: u64, 100 | /// Updated timestamp 101 | pub updated_timestamp: u64, 102 | /// Pool address 103 | pub pool: Pubkey, 104 | } 105 | 106 | /// New pool created event 107 | #[event] 108 | pub struct PoolCreated { 109 | /// LP token mint of the pool 110 | pub lp_mint: Pubkey, //32 111 | /// Token A mint of the pool. Eg: USDT 112 | pub token_a_mint: Pubkey, //32 113 | /// Token B mint of the pool. Eg: USDC 114 | pub token_b_mint: Pubkey, //32 115 | /// Pool type 116 | pub pool_type: PoolType, 117 | /// Pool address 118 | pub pool: Pubkey, 119 | } 120 | 121 | /// Pool enabled state change event 122 | #[event] 123 | pub struct PoolEnabled { 124 | /// Pool address 125 | pub pool: Pubkey, 126 | /// Pool enabled state 127 | pub enabled: bool, 128 | } 129 | 130 | /// Migrate fee account event 131 | #[event] 132 | pub struct MigrateFeeAccount { 133 | /// Pool address 134 | pub pool: Pubkey, 135 | /// New admin token a fee 136 | pub new_admin_token_a_fee: Pubkey, 137 | /// New admin token b fee 138 | pub new_admin_token_b_fee: Pubkey, 139 | /// Transfer token a fee amount 140 | pub token_a_amount: u64, 141 | /// Transfer token b fee amount 142 | pub token_b_amount: u64, 143 | } 144 | 145 | /// Create lock escrow 146 | #[event] 147 | pub struct CreateLockEscrow { 148 | /// Pool address 149 | pub pool: Pubkey, 150 | /// Owner of lock escrow 151 | pub owner: Pubkey, 152 | } 153 | 154 | /// Lock 155 | #[event] 156 | pub struct Lock { 157 | /// Pool address 158 | pub pool: Pubkey, 159 | /// Owner of lock escrow 160 | pub owner: Pubkey, 161 | /// Locked amount 162 | pub amount: u64, 163 | } 164 | 165 | /// Claim fee 166 | #[event] 167 | pub struct ClaimFee { 168 | /// Pool address 169 | pub pool: Pubkey, 170 | /// Owner of lock escrow 171 | pub owner: Pubkey, 172 | /// Lp amount 173 | pub amount: u64, 174 | /// A fee 175 | pub a_fee: u64, 176 | /// B fee 177 | pub b_fee: u64, 178 | } 179 | 180 | /// Create config 181 | #[event] 182 | pub struct CreateConfig { 183 | /// New trade fee numerator 184 | pub trade_fee_numerator: u64, 185 | /// New protocol fee numerator 186 | pub protocol_trade_fee_numerator: u64, 187 | /// Config pubkey 188 | pub config: Pubkey, 189 | } 190 | 191 | /// Close config 192 | #[event] 193 | pub struct CloseConfig { 194 | /// Config pubkey 195 | pub config: Pubkey, 196 | } 197 | 198 | /// Withdraw protocol fees 199 | #[event] 200 | pub struct WithdrawProtocolFees { 201 | /// Pool address 202 | pub pool: Pubkey, 203 | /// Protocol A fee 204 | pub protocol_a_fee: u64, 205 | /// Protocol B fee 206 | pub protocol_b_fee: u64, 207 | /// Protocol A fee owner 208 | pub protocol_a_fee_owner: Pubkey, 209 | /// Protocol B fee owner 210 | pub protocol_b_fee_owner: Pubkey, 211 | } 212 | 213 | /// Partner claim fees 214 | #[event] 215 | pub struct PartnerClaimFees { 216 | /// Pool address 217 | pub pool: Pubkey, 218 | /// Fee B 219 | pub fee_a: u64, 220 | /// Fee B 221 | pub fee_b: u64, 222 | /// Partner 223 | pub partner: Pubkey, 224 | } 225 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/add_balance_liquidity.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | /// Accounts for add or remove balanced liquidity instruction 4 | #[derive(Accounts)] 5 | pub struct AddOrRemoveBalanceLiquidity<'info> { 6 | #[account(mut)] 7 | /// CHECK: Pool account (PDA) 8 | pub pool: UncheckedAccount<'info>, 9 | #[account(mut)] 10 | /// CHECK: LP token mint of the pool 11 | pub lp_mint: UncheckedAccount<'info>, 12 | #[account(mut)] 13 | /// CHECK: user pool lp token account. lp will be burned from this account upon success liquidity removal. 14 | pub user_pool_lp: UncheckedAccount<'info>, 15 | 16 | #[account(mut)] 17 | /// CHECK: LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 18 | pub a_vault_lp: UncheckedAccount<'info>, 19 | #[account(mut)] 20 | /// CHECK: LP token account of vault B. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 21 | pub b_vault_lp: UncheckedAccount<'info>, 22 | 23 | #[account(mut)] 24 | /// CHECK: Vault account for token a. token a of the pool will be deposit / withdraw from this vault account. 25 | pub a_vault: UncheckedAccount<'info>, 26 | #[account(mut)] 27 | /// CHECK: Vault account for token b. token b of the pool will be deposit / withdraw from this vault account. 28 | pub b_vault: UncheckedAccount<'info>, 29 | 30 | #[account(mut)] 31 | /// CHECK: LP token mint of vault a 32 | pub a_vault_lp_mint: UncheckedAccount<'info>, 33 | #[account(mut)] 34 | /// CHECK: LP token mint of vault b 35 | pub b_vault_lp_mint: UncheckedAccount<'info>, 36 | 37 | #[account(mut)] 38 | /// CHECK: Token vault account of vault A 39 | pub a_token_vault: UncheckedAccount<'info>, 40 | #[account(mut)] 41 | /// CHECK: Token vault account of vault B 42 | pub b_token_vault: UncheckedAccount<'info>, 43 | #[account(mut)] 44 | /// CHECK: User token A account. Token will be transfer from this account if it is add liquidity operation. Else, token will be transfer into this account. 45 | pub user_a_token: UncheckedAccount<'info>, 46 | #[account(mut)] 47 | /// CHECK: User token B account. Token will be transfer from this account if it is add liquidity operation. Else, token will be transfer into this account. 48 | pub user_b_token: UncheckedAccount<'info>, 49 | /// CHECK: User account. Must be owner of user_a_token, and user_b_token. 50 | pub user: Signer<'info>, 51 | 52 | /// CHECK: Vault program. the pool will deposit/withdraw liquidity from the vault. 53 | pub vault_program: UncheckedAccount<'info>, 54 | /// CHECK: Token program. 55 | pub token_program: UncheckedAccount<'info>, 56 | } 57 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/bootstrap_liquidity.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | /// Bootstrap pool with zero liquidity 4 | /// Accounts for bootstrap pool instruction 5 | #[derive(Accounts)] 6 | pub struct BootstrapLiquidity<'info> { 7 | #[account(mut)] 8 | /// CHECK: Pool account (PDA) 9 | pub pool: UncheckedAccount<'info>, 10 | #[account(mut)] 11 | /// CHECK: LP token mint of the pool 12 | pub lp_mint: UncheckedAccount<'info>, 13 | #[account(mut)] 14 | /// CHECK: user pool lp token account. lp will be burned from this account upon success liquidity removal. 15 | pub user_pool_lp: UncheckedAccount<'info>, 16 | 17 | #[account(mut)] 18 | /// CHECK: LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 19 | pub a_vault_lp: UncheckedAccount<'info>, 20 | #[account(mut)] 21 | /// CHECK: LP token account of vault B. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 22 | pub b_vault_lp: UncheckedAccount<'info>, 23 | 24 | #[account(mut)] 25 | /// CHECK: Vault account for token a. token a of the pool will be deposit / withdraw from this vault account. 26 | pub a_vault: UncheckedAccount<'info>, 27 | #[account(mut)] 28 | /// CHECK: Vault account for token b. token b of the pool will be deposit / withdraw from this vault account. 29 | pub b_vault: UncheckedAccount<'info>, 30 | 31 | #[account(mut)] 32 | /// CHECK: LP token mint of vault a 33 | pub a_vault_lp_mint: UncheckedAccount<'info>, 34 | #[account(mut)] 35 | /// CHECK: LP token mint of vault b 36 | pub b_vault_lp_mint: UncheckedAccount<'info>, 37 | 38 | #[account(mut)] 39 | /// CHECK: Token vault account of vault A 40 | pub a_token_vault: UncheckedAccount<'info>, 41 | #[account(mut)] 42 | /// CHECK: Token vault account of vault B 43 | pub b_token_vault: UncheckedAccount<'info>, 44 | #[account(mut)] 45 | /// CHECK: User token A account. Token will be transfer from this account if it is add liquidity operation. Else, token will be transfer into this account. 46 | pub user_a_token: UncheckedAccount<'info>, 47 | #[account(mut)] 48 | /// CHECK: User token B account. Token will be transfer from this account if it is add liquidity operation. Else, token will be transfer into this account. 49 | pub user_b_token: UncheckedAccount<'info>, 50 | /// CHECK: User account. Must be owner of user_a_token, and user_b_token. 51 | pub user: Signer<'info>, 52 | 53 | /// CHECK: Vault program. the pool will deposit/withdraw liquidity from the vault. 54 | pub vault_program: UncheckedAccount<'info>, 55 | /// CHECK: Token program. 56 | pub token_program: UncheckedAccount<'info>, 57 | } 58 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/claim_fee.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | /// Accounts for claim fee instruction 4 | #[derive(Accounts)] 5 | pub struct ClaimFee<'info> { 6 | /// CHECK: Pool account 7 | #[account(mut)] 8 | pub pool: UncheckedAccount<'info>, 9 | 10 | /// CHECK: LP token mint of the pool 11 | #[account(mut)] 12 | pub lp_mint: UncheckedAccount<'info>, 13 | 14 | /// CHECK: Lock account 15 | #[account(mut)] 16 | pub lock_escrow: UncheckedAccount<'info>, 17 | 18 | /// CHECK: Owner of lock account 19 | #[account(mut)] 20 | pub owner: Signer<'info>, 21 | 22 | /// CHECK: owner lp token account 23 | #[account(mut)] 24 | pub source_tokens: UncheckedAccount<'info>, 25 | 26 | /// CHECK: Escrow vault 27 | #[account(mut)] 28 | pub escrow_vault: UncheckedAccount<'info>, 29 | 30 | /// CHECK: Token program. 31 | pub token_program: UncheckedAccount<'info>, 32 | 33 | #[account(mut)] 34 | /// CHECK: Token vault account of vault A 35 | pub a_token_vault: UncheckedAccount<'info>, 36 | #[account(mut)] 37 | /// CHECK: Token vault account of vault B 38 | pub b_token_vault: UncheckedAccount<'info>, 39 | 40 | /// CHECK: Vault account for token a. token a of the pool will be deposit / withdraw from this vault account. 41 | #[account(mut)] 42 | pub a_vault: UncheckedAccount<'info>, 43 | /// CHECK: Vault account for token b. token b of the pool will be deposit / withdraw from this vault account. 44 | #[account(mut)] 45 | pub b_vault: UncheckedAccount<'info>, 46 | /// CHECK: LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 47 | #[account(mut)] 48 | pub a_vault_lp: UncheckedAccount<'info>, 49 | /// CHECK: LP token account of vault B. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 50 | #[account(mut)] 51 | pub b_vault_lp: UncheckedAccount<'info>, 52 | /// CHECK: LP token mint of vault a 53 | #[account(mut)] 54 | pub a_vault_lp_mint: UncheckedAccount<'info>, 55 | /// CHECK: LP token mint of vault b 56 | #[account(mut)] 57 | pub b_vault_lp_mint: UncheckedAccount<'info>, 58 | 59 | #[account(mut)] 60 | /// CHECK: User token A account. Token will be transfer from this account if it is add liquidity operation. Else, token will be transfer into this account. 61 | pub user_a_token: UncheckedAccount<'info>, 62 | #[account(mut)] 63 | /// CHECK: User token B account. Token will be transfer from this account if it is add liquidity operation. Else, token will be transfer into this account. 64 | pub user_b_token: UncheckedAccount<'info>, 65 | 66 | /// CHECK: Vault program. the pool will deposit/withdraw liquidity from the vault. 67 | pub vault_program: UncheckedAccount<'info>, 68 | } 69 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/close_config.rs: -------------------------------------------------------------------------------- 1 | use crate::state::Config; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct CloseConfig<'info> { 6 | #[account( 7 | mut, 8 | close = rent_receiver 9 | )] 10 | pub config: Account<'info, Config>, 11 | 12 | #[account(mut)] 13 | pub admin: Signer<'info>, 14 | 15 | /// CHECK: Account to receive closed account rental SOL 16 | #[account(mut)] 17 | pub rent_receiver: UncheckedAccount<'info>, 18 | } 19 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/create_config.rs: -------------------------------------------------------------------------------- 1 | use crate::constants::seeds::CONFIG_PREFIX; 2 | use crate::state::Config; 3 | use anchor_lang::prelude::*; 4 | 5 | #[derive(AnchorSerialize, AnchorDeserialize)] 6 | pub struct ConfigParameters { 7 | pub trade_fee_numerator: u64, 8 | pub protocol_trade_fee_numerator: u64, 9 | pub activation_duration: u64, 10 | pub pool_creator_authority: Pubkey, 11 | pub activation_type: u8, 12 | pub index: u64, 13 | pub partner_fee_numerator: u64, 14 | } 15 | 16 | #[derive(Accounts)] 17 | #[instruction(config_parameters: ConfigParameters)] 18 | pub struct CreateConfig<'info> { 19 | #[account( 20 | init, 21 | seeds = [ 22 | CONFIG_PREFIX, 23 | config_parameters.index.to_le_bytes().as_ref() 24 | ], 25 | bump, 26 | payer = admin, 27 | space = 8 + Config::INIT_SPACE 28 | )] 29 | pub config: Account<'info, Config>, 30 | 31 | #[account(mut)] 32 | pub admin: Signer<'info>, 33 | 34 | pub system_program: Program<'info, System>, 35 | } 36 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/create_lock_escrow.rs: -------------------------------------------------------------------------------- 1 | use crate::state::LockEscrow; 2 | use anchor_lang::prelude::*; 3 | 4 | /// Accounts for create lock account instruction 5 | #[derive(Accounts)] 6 | pub struct CreateLockEscrow<'info> { 7 | /// CHECK: 8 | pub pool: UncheckedAccount<'info>, 9 | 10 | /// CHECK: Lock account 11 | #[account( 12 | init, 13 | seeds = [ 14 | "lock_escrow".as_ref(), 15 | pool.key().as_ref(), 16 | owner.key().as_ref(), 17 | ], 18 | space = 8 + std::mem::size_of::(), 19 | bump, 20 | payer = payer, 21 | )] 22 | pub lock_escrow: UncheckedAccount<'info>, 23 | 24 | /// CHECK: Owner account 25 | pub owner: UncheckedAccount<'info>, 26 | 27 | /// CHECK: LP token mint of the pool 28 | pub lp_mint: UncheckedAccount<'info>, 29 | 30 | /// CHECK: Payer account 31 | #[account(mut)] 32 | pub payer: Signer<'info>, 33 | 34 | /// CHECK: System program. 35 | pub system_program: UncheckedAccount<'info>, 36 | } 37 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/create_mint_metadata.rs: -------------------------------------------------------------------------------- 1 | use crate::state::Pool; 2 | use anchor_lang::prelude::*; 3 | use anchor_spl::token::{Mint, TokenAccount}; 4 | 5 | #[derive(Accounts)] 6 | pub struct CreateMintMetadata<'info> { 7 | /// Pool account 8 | pub pool: Box>, 9 | 10 | /// LP mint account of the pool 11 | pub lp_mint: Box>, 12 | 13 | /// Vault A LP account of the pool 14 | pub a_vault_lp: Box>, 15 | 16 | /// CHECK: LP mint metadata PDA. Metaplex do the checking 17 | // https://github.com/metaplex-foundation/mpl-token-metadata/blob/4e5bcca44000f151fe64682826bbe2eb61cd7b87/programs/token-metadata/program/src/utils/metadata.rs#L108 18 | #[account(mut)] 19 | pub mint_metadata: UncheckedAccount<'info>, 20 | 21 | /// CHECK: Metadata program 22 | pub metadata_program: UncheckedAccount<'info>, 23 | 24 | /// System program. 25 | pub system_program: Program<'info, System>, 26 | 27 | /// Payer 28 | #[account(mut)] 29 | pub payer: Signer<'info>, 30 | } 31 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/enable_pool.rs: -------------------------------------------------------------------------------- 1 | use crate::state::Pool; 2 | use anchor_lang::prelude::*; 3 | 4 | /// Accounts for enable or disable pool instruction 5 | #[derive(Accounts)] 6 | #[instruction(enable: bool)] 7 | pub struct EnableOrDisablePool<'info> { 8 | #[account( 9 | mut, 10 | constraint = pool.enabled != enable 11 | )] 12 | /// Pool account (PDA) 13 | pub pool: Box>, 14 | /// Admin account. Must be owner of the pool. 15 | pub admin: Signer<'info>, 16 | } 17 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/get_pool_info.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | /// Accounts for get pool info instruction 4 | #[derive(Accounts)] 5 | pub struct GetPoolInfo<'info> { 6 | /// CHECK: Pool account (PDA) 7 | pub pool: UncheckedAccount<'info>, 8 | /// CHECK: LP token mint of the pool 9 | pub lp_mint: UncheckedAccount<'info>, 10 | /// CHECK: LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 11 | pub a_vault_lp: UncheckedAccount<'info>, 12 | /// CHECK: LP token account of vault B. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 13 | pub b_vault_lp: UncheckedAccount<'info>, 14 | /// CHECK: Vault account for token a. token a of the pool will be deposit / withdraw from this vault account. 15 | pub a_vault: UncheckedAccount<'info>, 16 | /// CHECK: Vault account for token b. token b of the pool will be deposit / withdraw from this vault account. 17 | pub b_vault: UncheckedAccount<'info>, 18 | /// CHECK: LP token mint of vault a 19 | pub a_vault_lp_mint: UncheckedAccount<'info>, 20 | /// CHECK: LP token mint of vault b 21 | pub b_vault_lp_mint: UncheckedAccount<'info>, 22 | } 23 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/initialize_customizable_permissionless_constant_product_pool.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | constants::{activation::*, fee::*, QUOTE_MINTS}, 3 | error::PoolError, 4 | get_first_key, get_lp_mint_decimal, get_second_key, 5 | state::Pool, 6 | }; 7 | use anchor_lang::prelude::*; 8 | use anchor_spl::{ 9 | associated_token::AssociatedToken, 10 | token::{Mint, Token, TokenAccount}, 11 | }; 12 | use std::u64; 13 | 14 | #[derive(Accounts)] 15 | pub struct InitializeCustomizablePermissionlessConstantProductPool<'info> { 16 | #[account( 17 | init, 18 | seeds = [ 19 | b"pool", 20 | get_first_key(token_a_mint.key(), token_b_mint.key()).as_ref(), 21 | get_second_key(token_a_mint.key(), token_b_mint.key()).as_ref(), 22 | ], 23 | bump, 24 | payer = payer, 25 | space = 8 + std::mem::size_of::() 26 | )] 27 | /// Pool account (PDA address) 28 | pub pool: Box>, 29 | 30 | /// LP token mint of the pool 31 | #[account( 32 | init, 33 | seeds = [ 34 | "lp_mint".as_ref(), 35 | pool.key().as_ref() 36 | ], 37 | bump, 38 | payer = payer, 39 | mint::decimals = get_lp_mint_decimal(token_a_mint.decimals, token_b_mint.decimals), 40 | mint::authority = a_vault_lp, 41 | )] 42 | pub lp_mint: Box>, 43 | 44 | /// Token A mint of the pool. Eg: USDT 45 | pub token_a_mint: Box>, 46 | /// Token B mint of the pool. Eg: USDC 47 | #[account( 48 | constraint = token_b_mint.key() != token_a_mint.key() @ PoolError::MismatchedTokenMint 49 | )] 50 | pub token_b_mint: Box>, 51 | 52 | #[account(mut)] 53 | /// Vault account for token A. Token A of the pool will be deposit / withdraw from this vault account. 54 | pub a_vault: AccountInfo<'info>, 55 | #[account(mut)] 56 | /// Vault account for token B. Token B of the pool will be deposit / withdraw from this vault account. 57 | pub b_vault: AccountInfo<'info>, 58 | 59 | #[account(mut)] 60 | /// Token vault account of vault A 61 | pub a_token_vault: Box>, 62 | #[account(mut)] 63 | /// Token vault account of vault B 64 | pub b_token_vault: Box>, 65 | 66 | #[account(mut)] 67 | /// LP token mint of vault A 68 | pub a_vault_lp_mint: Box>, 69 | #[account(mut)] 70 | /// LP token mint of vault B 71 | pub b_vault_lp_mint: Box>, 72 | /// LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 73 | #[account( 74 | init, 75 | seeds = [ 76 | a_vault.key().as_ref(), 77 | pool.key().as_ref() 78 | ], 79 | bump, 80 | payer = payer, 81 | token::mint = a_vault_lp_mint, 82 | token::authority = a_vault_lp 83 | )] 84 | pub a_vault_lp: Box>, 85 | /// LP token account of vault B. Used to receive/burn vault LP upon deposit/withdraw from the vault. 86 | #[account( 87 | init, 88 | seeds = [ 89 | b_vault.key().as_ref(), 90 | pool.key().as_ref() 91 | ], 92 | bump, 93 | payer = payer, 94 | token::mint = b_vault_lp_mint, 95 | token::authority = a_vault_lp 96 | )] 97 | pub b_vault_lp: Box>, 98 | 99 | #[account(mut)] 100 | /// Payer token account for pool token A mint. Used to bootstrap the pool with initial liquidity. 101 | pub payer_token_a: Box>, 102 | #[account(mut)] 103 | /// Admin token account for pool token B mint. Used to bootstrap the pool with initial liquidity. 104 | pub payer_token_b: Box>, 105 | 106 | /// CHECK: Payer pool LP token account. Used to receive LP during first deposit (initialize pool) 107 | #[account( 108 | init, 109 | payer = payer, 110 | associated_token::mint = lp_mint, 111 | associated_token::authority = payer, 112 | )] 113 | pub payer_pool_lp: Box>, 114 | 115 | #[account( 116 | init, 117 | seeds = [ 118 | "fee".as_ref(), 119 | token_a_mint.key().as_ref(), 120 | pool.key().as_ref() 121 | ], 122 | bump, 123 | payer = payer, 124 | token::mint = token_a_mint, 125 | token::authority = a_vault_lp 126 | )] 127 | /// Protocol fee token account for token A. Used to receive trading fee. 128 | pub protocol_token_a_fee: Box>, 129 | 130 | /// Protocol fee token account for token B. Used to receive trading fee. 131 | #[account( 132 | init, 133 | seeds = [ 134 | "fee".as_ref(), 135 | token_b_mint.key().as_ref(), 136 | pool.key().as_ref() 137 | ], 138 | bump, 139 | payer = payer, 140 | token::mint = token_b_mint, 141 | token::authority = a_vault_lp 142 | )] 143 | pub protocol_token_b_fee: Box>, 144 | 145 | /// Admin account. This account will be the admin of the pool, and the payer for PDA during initialize pool. 146 | #[account(mut)] 147 | pub payer: Signer<'info>, 148 | 149 | /// Rent account. 150 | pub rent: Sysvar<'info, Rent>, 151 | 152 | /// CHECK: LP mint metadata PDA. Metaplex do the checking. 153 | #[account(mut)] 154 | pub mint_metadata: UncheckedAccount<'info>, 155 | 156 | /// CHECK: Metadata program 157 | pub metadata_program: UncheckedAccount<'info>, 158 | 159 | /// Vault program. The pool will deposit/withdraw liquidity from the vault. 160 | pub vault_program: UncheckedAccount<'info>, 161 | /// Token program. 162 | pub token_program: Program<'info, Token>, 163 | /// Associated token program. 164 | pub associated_token_program: Program<'info, AssociatedToken>, 165 | /// System program. 166 | pub system_program: Program<'info, System>, 167 | } 168 | 169 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy)] 170 | pub struct CustomizableParams { 171 | /// Trading fee. 172 | pub trade_fee_numerator: u32, 173 | /// The pool start trading. 174 | pub activation_point: Option, 175 | /// Whether the pool support alpha vault 176 | pub has_alpha_vault: bool, 177 | /// Activation type 178 | pub activation_type: u8, 179 | /// Padding 180 | pub padding: [u8; 90], 181 | } 182 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/initialize_permissioned_pool.rs: -------------------------------------------------------------------------------- 1 | // use crate::macros::pool_seeds; 2 | 3 | use crate::get_lp_mint_decimal; 4 | use crate::state::*; 5 | use anchor_lang::prelude::*; 6 | use anchor_spl::associated_token::AssociatedToken; 7 | use anchor_spl::token::{Mint, Token, TokenAccount}; 8 | 9 | /// Permissioned Initialize 10 | /// Accounts for initialize new pool instruction 11 | #[derive(Accounts)] 12 | pub struct InitializePermissionedPool<'info> { 13 | #[account( 14 | init, 15 | payer = admin, 16 | space = 8 + std::mem::size_of::() 17 | )] 18 | /// Pool account (arbitrary address) 19 | pub pool: Box>, 20 | 21 | /// LP token mint of the pool 22 | #[account( 23 | init, 24 | seeds = [ 25 | "lp_mint".as_ref(), 26 | pool.key().as_ref() 27 | ], 28 | bump, 29 | payer = admin, 30 | mint::decimals = get_lp_mint_decimal(token_a_mint.decimals, token_b_mint.decimals), 31 | mint::authority = a_vault_lp 32 | )] 33 | pub lp_mint: Box>, 34 | 35 | /// Token A mint of the pool. Eg: USDT 36 | pub token_a_mint: Box>, 37 | /// Token B mint of the pool. Eg: USDC 38 | pub token_b_mint: Box>, 39 | 40 | #[account(mut)] 41 | /// CHECK: Vault account for token A. Token A of the pool will be deposit / withdraw from this vault account. 42 | pub a_vault: AccountInfo<'info>, 43 | #[account(mut)] 44 | /// CHECK: Vault account for token B. Token B of the pool will be deposit / withdraw from this vault account. 45 | pub b_vault: AccountInfo<'info>, 46 | 47 | #[account(mut)] 48 | /// LP token mint of vault A 49 | pub a_vault_lp_mint: Box>, 50 | #[account(mut)] 51 | /// LP token mint of vault B 52 | pub b_vault_lp_mint: Box>, 53 | /// LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 54 | #[account( 55 | init, 56 | seeds = [ 57 | a_vault.key().as_ref(), 58 | pool.key().as_ref() 59 | ], 60 | bump, 61 | payer = admin, 62 | token::mint = a_vault_lp_mint, 63 | token::authority = a_vault_lp 64 | )] 65 | pub a_vault_lp: Box>, 66 | /// LP token account of vault B. Used to receive/burn vault LP upon deposit/withdraw from the vault. 67 | #[account( 68 | init, 69 | seeds = [ 70 | b_vault.key().as_ref(), 71 | pool.key().as_ref() 72 | ], 73 | bump, 74 | payer = admin, 75 | token::mint = b_vault_lp_mint, 76 | token::authority = a_vault_lp 77 | )] 78 | pub b_vault_lp: Box>, 79 | 80 | #[account(mut)] 81 | /// Admin token account for pool token A mint. Used to bootstrap the pool with initial liquidity. 82 | pub admin_token_a: Box>, 83 | #[account(mut)] 84 | /// Admin token account for pool token B mint. Used to bootstrap the pool with initial liquidity. 85 | pub admin_token_b: Box>, 86 | 87 | /// Admin pool LP token account. Used to receive LP during first deposit (initialize pool) 88 | #[account( 89 | init, 90 | payer = admin, 91 | associated_token::mint = lp_mint, 92 | associated_token::authority = admin 93 | )] 94 | /// Admin pool LP token account. Used to receive LP during first deposit (initialize pool) 95 | pub admin_pool_lp: Box>, 96 | 97 | #[account( 98 | init, 99 | seeds = [ 100 | "fee".as_ref(), 101 | token_a_mint.key().as_ref(), 102 | pool.key().as_ref() 103 | ], 104 | bump, 105 | payer = admin, 106 | token::mint = token_a_mint, 107 | token::authority = a_vault_lp 108 | )] 109 | /// Protocol fee token account for token A. Used to receive trading fee. 110 | pub protocol_token_a_fee: Box>, 111 | 112 | /// Protocol fee token account for token B. Used to receive trading fee. 113 | #[account( 114 | init, 115 | seeds = [ 116 | "fee".as_ref(), 117 | token_b_mint.key().as_ref(), 118 | pool.key().as_ref() 119 | ], 120 | bump, 121 | payer = admin, 122 | token::mint = token_b_mint, 123 | token::authority = a_vault_lp 124 | )] 125 | pub protocol_token_b_fee: Box>, 126 | 127 | /// Admin account. This account will be the admin of the pool, and the payer for PDA during initialize pool. 128 | #[account(mut)] 129 | pub admin: Signer<'info>, 130 | 131 | /// CHECK: Fee owner will be a_vault_lp 132 | #[allow(deprecated)] 133 | pub fee_owner: UncheckedAccount<'info>, 134 | 135 | /// Rent account. 136 | pub rent: Sysvar<'info, Rent>, 137 | 138 | /// CHECK: LP mint metadata PDA. Metaplex do the checking. 139 | #[account(mut)] 140 | pub mint_metadata: UncheckedAccount<'info>, 141 | 142 | /// CHECK: Metadata program 143 | pub metadata_program: UncheckedAccount<'info>, 144 | 145 | /// CHECK: Vault program. The pool will deposit/withdraw liquidity from the vault. 146 | pub vault_program: AccountInfo<'info>, 147 | /// Token program. 148 | pub token_program: Program<'info, Token>, 149 | 150 | /// Associated token program. 151 | pub associated_token_program: Program<'info, AssociatedToken>, 152 | /// System program. 153 | pub system_program: Program<'info, System>, 154 | } 155 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/initialize_permissionless_pool_with_config.rs: -------------------------------------------------------------------------------- 1 | use crate::get_lp_mint_decimal; 2 | use crate::{get_first_key, get_second_key, state::*}; 3 | use anchor_lang::prelude::*; 4 | use anchor_spl::{ 5 | associated_token::AssociatedToken, 6 | token::{Mint, Token, TokenAccount}, 7 | }; 8 | 9 | #[derive(Accounts)] 10 | pub struct InitializePermissionlessConstantProductPoolWithConfig<'info> { 11 | #[account( 12 | init, 13 | seeds = [ 14 | get_first_key(token_a_mint.key(), token_b_mint.key()).as_ref(), 15 | get_second_key(token_a_mint.key(), token_b_mint.key()).as_ref(), 16 | config.key().as_ref() 17 | ], 18 | bump, 19 | payer = payer, 20 | space = 8 + std::mem::size_of::() 21 | )] 22 | /// Pool account (PDA address) 23 | pub pool: Box>, 24 | 25 | pub config: Box>, 26 | 27 | /// LP token mint of the pool 28 | #[account( 29 | init, 30 | seeds = [ 31 | "lp_mint".as_ref(), 32 | pool.key().as_ref() 33 | ], 34 | bump, 35 | payer = payer, 36 | mint::decimals = get_lp_mint_decimal(token_a_mint.decimals, token_b_mint.decimals), 37 | mint::authority = a_vault_lp, 38 | )] 39 | pub lp_mint: Box>, 40 | 41 | /// Token A mint of the pool. Eg: USDT 42 | pub token_a_mint: Box>, 43 | /// Token B mint of the pool. Eg: USDC 44 | pub token_b_mint: Box>, 45 | 46 | #[account(mut)] 47 | /// CHECK: Vault account for token A. Token A of the pool will be deposit / withdraw from this vault account. 48 | pub a_vault: AccountInfo<'info>, 49 | #[account(mut)] 50 | /// CHECK: Vault account for token B. Token B of the pool will be deposit / withdraw from this vault account. 51 | pub b_vault: AccountInfo<'info>, 52 | 53 | #[account(mut)] 54 | /// Token vault account of vault A 55 | pub a_token_vault: Box>, 56 | #[account(mut)] 57 | /// Token vault account of vault B 58 | pub b_token_vault: Box>, 59 | 60 | #[account(mut)] 61 | /// LP token mint of vault A 62 | pub a_vault_lp_mint: Box>, 63 | 64 | #[account(mut)] 65 | /// LP token mint of vault B 66 | pub b_vault_lp_mint: Box>, 67 | /// LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 68 | #[account( 69 | init, 70 | seeds = [ 71 | a_vault.key().as_ref(), 72 | pool.key().as_ref() 73 | ], 74 | bump, 75 | payer = payer, 76 | token::mint = a_vault_lp_mint, 77 | token::authority = a_vault_lp 78 | )] 79 | pub a_vault_lp: Box>, 80 | /// LP token account of vault B. Used to receive/burn vault LP upon deposit/withdraw from the vault. 81 | #[account( 82 | init, 83 | seeds = [ 84 | b_vault.key().as_ref(), 85 | pool.key().as_ref() 86 | ], 87 | bump, 88 | payer = payer, 89 | token::mint = b_vault_lp_mint, 90 | token::authority = a_vault_lp 91 | )] 92 | pub b_vault_lp: Box>, 93 | 94 | #[account(mut)] 95 | /// Payer token account for pool token A mint. Used to bootstrap the pool with initial liquidity. 96 | pub payer_token_a: Box>, 97 | 98 | #[account(mut)] 99 | /// Admin token account for pool token B mint. Used to bootstrap the pool with initial liquidity. 100 | pub payer_token_b: Box>, 101 | 102 | /// CHECK: Payer pool LP token account. Used to receive LP during first deposit (initialize pool) 103 | #[account( 104 | init, 105 | payer = payer, 106 | associated_token::mint = lp_mint, 107 | associated_token::authority = payer, 108 | )] 109 | pub payer_pool_lp: Box>, 110 | 111 | #[account( 112 | init, 113 | seeds = [ 114 | "fee".as_ref(), 115 | token_a_mint.key().as_ref(), 116 | pool.key().as_ref() 117 | ], 118 | bump, 119 | payer = payer, 120 | token::mint = token_a_mint, 121 | token::authority = a_vault_lp 122 | )] 123 | /// Protocol fee token account for token A. Used to receive trading fee. 124 | pub protocol_token_a_fee: Box>, 125 | 126 | /// Protocol fee token account for token B. Used to receive trading fee. 127 | #[account( 128 | init, 129 | seeds = [ 130 | "fee".as_ref(), 131 | token_b_mint.key().as_ref(), 132 | pool.key().as_ref() 133 | ], 134 | bump, 135 | payer = payer, 136 | token::mint = token_b_mint, 137 | token::authority = a_vault_lp 138 | )] 139 | pub protocol_token_b_fee: Box>, 140 | 141 | /// Admin account. This account will be the admin of the pool, and the payer for PDA during initialize pool. 142 | #[account(mut)] 143 | pub payer: Signer<'info>, 144 | 145 | /// Rent account. 146 | pub rent: Sysvar<'info, Rent>, 147 | 148 | /// CHECK: LP mint metadata PDA. Metaplex do the checking. 149 | #[account(mut)] 150 | pub mint_metadata: UncheckedAccount<'info>, 151 | 152 | /// CHECK: Metadata program 153 | pub metadata_program: UncheckedAccount<'info>, 154 | 155 | /// CHECK: Vault program. The pool will deposit/withdraw liquidity from the vault. 156 | pub vault_program: UncheckedAccount<'info>, 157 | /// Token program. 158 | pub token_program: Program<'info, Token>, 159 | /// Associated token program. 160 | pub associated_token_program: Program<'info, AssociatedToken>, 161 | /// System program. 162 | pub system_program: Program<'info, System>, 163 | } 164 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/lock.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | /// Accounts for lock instruction 3 | #[derive(Accounts)] 4 | pub struct Lock<'info> { 5 | /// CHECK: Pool account 6 | #[account(mut)] 7 | pub pool: UncheckedAccount<'info>, 8 | 9 | /// CHECK: LP token mint of the pool 10 | pub lp_mint: UncheckedAccount<'info>, 11 | 12 | /// CHECK: Lock account 13 | #[account(mut)] 14 | pub lock_escrow: UncheckedAccount<'info>, 15 | 16 | /// CHECK: Owner of lock account 17 | #[account(mut)] 18 | pub owner: Signer<'info>, 19 | 20 | /// CHECK: owner lp token account 21 | #[account(mut)] 22 | pub source_tokens: UncheckedAccount<'info>, 23 | 24 | /// CHECK: Escrow vault 25 | #[account(mut)] 26 | pub escrow_vault: UncheckedAccount<'info>, 27 | 28 | /// CHECK: Token program. 29 | pub token_program: UncheckedAccount<'info>, 30 | 31 | /// CHECK: Vault account for token a. token a of the pool will be deposit / withdraw from this vault account. 32 | pub a_vault: UncheckedAccount<'info>, 33 | /// CHECK: Vault account for token b. token b of the pool will be deposit / withdraw from this vault account. 34 | pub b_vault: UncheckedAccount<'info>, 35 | /// CHECK: LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 36 | pub a_vault_lp: UncheckedAccount<'info>, 37 | /// CHECK: LP token account of vault B. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 38 | pub b_vault_lp: UncheckedAccount<'info>, 39 | /// CHECK: LP token mint of vault a 40 | pub a_vault_lp_mint: UncheckedAccount<'info>, 41 | /// CHECK: LP token mint of vault b 42 | pub b_vault_lp_mint: UncheckedAccount<'info>, 43 | } 44 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod swap; 2 | pub use swap::*; 3 | 4 | pub mod add_balance_liquidity; 5 | pub use add_balance_liquidity::*; 6 | pub mod claim_fee; 7 | pub use claim_fee::*; 8 | pub mod create_lock_escrow; 9 | pub use create_lock_escrow::*; 10 | pub mod initialize_permissionless_pool; 11 | pub use initialize_permissionless_pool::*; 12 | pub mod lock; 13 | pub use lock::*; 14 | pub mod get_pool_info; 15 | pub use get_pool_info::*; 16 | pub mod bootstrap_liquidity; 17 | pub use bootstrap_liquidity::*; 18 | pub mod remove_liquidity_single_side; 19 | pub use remove_liquidity_single_side::*; 20 | pub mod initialize_permissioned_pool; 21 | pub use initialize_permissioned_pool::*; 22 | pub mod enable_pool; 23 | pub use enable_pool::*; 24 | pub mod override_curve_param; 25 | pub use override_curve_param::*; 26 | pub mod create_mint_metadata; 27 | pub use create_mint_metadata::*; 28 | pub mod set_pool_fee; 29 | pub use set_pool_fee::*; 30 | pub mod initialize_permissionless_pool_with_config; 31 | pub use initialize_permissionless_pool_with_config::*; 32 | pub mod create_config; 33 | pub use create_config::*; 34 | pub mod close_config; 35 | pub use close_config::*; 36 | pub mod update_activation_point; 37 | pub use update_activation_point::*; 38 | pub mod initialize_customizable_permissionless_constant_product_pool; 39 | pub use initialize_customizable_permissionless_constant_product_pool::*; 40 | pub mod partner_claim_fees; 41 | pub use partner_claim_fees::*; 42 | pub mod move_locked_lp; 43 | pub use move_locked_lp::*; 44 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/move_locked_lp.rs: -------------------------------------------------------------------------------- 1 | use crate::event; 2 | use anchor_lang::prelude::*; 3 | 4 | /// Accounts for [amm::move_locked_lp]. 5 | #[derive(Accounts)] 6 | pub struct MoveLockedLp<'info> { 7 | /// Pool account 8 | #[account(mut)] 9 | pub pool: UncheckedAccount<'info>, 10 | 11 | /// LP token mint of the pool 12 | pub lp_mint: UncheckedAccount<'info>, 13 | 14 | /// From lock account 15 | #[account(mut)] 16 | pub from_lock_escrow: UncheckedAccount<'info>, 17 | 18 | /// To lock account 19 | #[account(mut)] 20 | pub to_lock_escrow: UncheckedAccount<'info>, 21 | 22 | /// Owner of lock account 23 | pub owner: Signer<'info>, 24 | 25 | /// From escrow vault 26 | #[account(mut)] 27 | pub from_escrow_vault: UncheckedAccount<'info>, 28 | 29 | /// To escrow vault 30 | #[account(mut)] 31 | pub to_escrow_vault: UncheckedAccount<'info>, 32 | 33 | /// Token program. 34 | pub token_program: UncheckedAccount<'info>, 35 | 36 | // /// Vault account for token a. token a of the pool will be deposit / withdraw from this vault account. 37 | pub a_vault: UncheckedAccount<'info>, 38 | // /// Vault account for token b. token b of the pool will be deposit / withdraw from this vault account. 39 | pub b_vault: UncheckedAccount<'info>, 40 | // /// LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 41 | pub a_vault_lp: UncheckedAccount<'info>, 42 | // /// LP token account of vault B. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 43 | pub b_vault_lp: UncheckedAccount<'info>, 44 | // /// LP token mint of vault a 45 | pub a_vault_lp_mint: UncheckedAccount<'info>, 46 | // /// LP token mint of vault b 47 | pub b_vault_lp_mint: UncheckedAccount<'info>, 48 | } 49 | 50 | /// move locked lp 51 | pub fn move_locked_lp(ctx: Context, max_amount: u64) -> Result<()> { 52 | Ok(()) 53 | } 54 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/override_curve_param.rs: -------------------------------------------------------------------------------- 1 | use crate::state::Pool; 2 | use anchor_lang::prelude::*; 3 | 4 | /// Accounts for override curve parameters instruction 5 | #[derive(Accounts)] 6 | pub struct OverrideCurveParam<'info> { 7 | #[account(mut)] 8 | /// Pool account (PDA) 9 | pub pool: Box>, 10 | /// Admin account. Must be owner of the pool. 11 | pub admin: Signer<'info>, 12 | } 13 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/partner_claim_fees.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::PoolError, event::PartnerClaimFees as PartnerClaimFeesEvent, state::Pool}; 2 | use anchor_lang::prelude::*; 3 | use anchor_spl::token::{transfer, Token, TokenAccount, Transfer}; 4 | 5 | /// Accounts for partner to claim fees 6 | #[derive(Accounts)] 7 | pub struct PartnerClaimFees<'info> { 8 | #[account( 9 | mut, 10 | has_one = protocol_token_a_fee, 11 | has_one = protocol_token_b_fee, 12 | has_one = a_vault_lp, 13 | constraint = pool.partner_info.partner_authority == partner_authority.key() @ PoolError::InvalidFeeOwner, 14 | )] 15 | /// Pool account (PDA) 16 | pub pool: Box>, 17 | 18 | pub a_vault_lp: Box>, 19 | 20 | #[account(mut)] 21 | pub protocol_token_a_fee: Box>, 22 | #[account(mut)] 23 | pub protocol_token_b_fee: Box>, 24 | 25 | #[account(mut)] 26 | pub partner_token_a: Box>, 27 | 28 | #[account(mut)] 29 | pub partner_token_b: Box>, 30 | 31 | pub token_program: Program<'info, Token>, 32 | 33 | pub partner_authority: Signer<'info>, 34 | } 35 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/remove_liquidity_single_side.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | /// Accounts for remove liquidity single sided instruction 3 | #[derive(Accounts)] 4 | pub struct RemoveLiquiditySingleSide<'info> { 5 | #[account(mut)] 6 | /// CHECK: Pool account (PDA) 7 | pub pool: UncheckedAccount<'info>, 8 | #[account(mut)] 9 | /// CHECK: LP token mint of the pool 10 | pub lp_mint: UncheckedAccount<'info>, 11 | #[account(mut)] 12 | /// CHECK: User pool lp token account. LP will be burned from this account upon success liquidity removal. 13 | pub user_pool_lp: UncheckedAccount<'info>, 14 | 15 | #[account(mut)] 16 | /// CHECK: LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 17 | pub a_vault_lp: UncheckedAccount<'info>, 18 | #[account(mut)] 19 | /// CHECK: LP token account of vault B. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 20 | pub b_vault_lp: UncheckedAccount<'info>, 21 | 22 | #[account(mut)] 23 | /// CHECK: Vault account for token A. Token A of the pool will be deposit / withdraw from this vault account. 24 | pub a_vault: UncheckedAccount<'info>, 25 | #[account(mut)] 26 | /// CHECK: Vault account for token B. Token B of the pool will be deposit / withdraw from this vault account. 27 | pub b_vault: UncheckedAccount<'info>, 28 | 29 | #[account(mut)] 30 | /// CHECK: LP token mint of vault A 31 | pub a_vault_lp_mint: UncheckedAccount<'info>, 32 | #[account(mut)] 33 | /// CHECK: LP token mint of vault B 34 | pub b_vault_lp_mint: UncheckedAccount<'info>, 35 | 36 | #[account(mut)] 37 | /// CHECK: Token vault account of vault A 38 | pub a_token_vault: UncheckedAccount<'info>, 39 | #[account(mut)] 40 | /// CHECK: Token vault account of vault B 41 | pub b_token_vault: UncheckedAccount<'info>, 42 | #[account(mut)] 43 | /// CHECK: User token account to receive token upon success liquidity removal. 44 | pub user_destination_token: UncheckedAccount<'info>, 45 | /// CHECK: User account. Must be owner of the user_pool_lp account. 46 | pub user: Signer<'info>, 47 | 48 | /// CHECK: Vault program. The pool will deposit/withdraw liquidity from the vault. 49 | pub vault_program: UncheckedAccount<'info>, 50 | /// CHECK: Token program. 51 | pub token_program: UncheckedAccount<'info>, 52 | } 53 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/set_pool_fee.rs: -------------------------------------------------------------------------------- 1 | use crate::state::Pool; 2 | use anchor_lang::prelude::*; 3 | 4 | /// Accounts for set pool fees instruction 5 | #[derive(Accounts)] 6 | pub struct SetPoolFees<'info> { 7 | #[account(mut)] 8 | /// Pool account (PDA) 9 | pub pool: Box>, 10 | /// Fee operator account 11 | pub fee_operator: Signer<'info>, 12 | } 13 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/swap.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | /// Accounts for swap instruction 4 | #[derive(Accounts)] 5 | pub struct Swap<'info> { 6 | #[account(mut)] 7 | /// CHECK: Pool account (PDA) 8 | pub pool: UncheckedAccount<'info>, 9 | 10 | #[account(mut)] 11 | /// CHECK: User token account. Token from this account will be transfer into the vault by the pool in exchange for another token of the pool. 12 | pub user_source_token: UncheckedAccount<'info>, 13 | #[account(mut)] 14 | /// CHECK: User token account. The exchanged token will be transfer into this account from the pool. 15 | pub user_destination_token: UncheckedAccount<'info>, 16 | 17 | #[account(mut)] 18 | /// CHECK: Vault account for token a. token a of the pool will be deposit / withdraw from this vault account. 19 | pub a_vault: UncheckedAccount<'info>, 20 | #[account(mut)] 21 | /// CHECK: Vault account for token b. token b of the pool will be deposit / withdraw from this vault account. 22 | pub b_vault: UncheckedAccount<'info>, 23 | 24 | #[account(mut)] 25 | /// CHECK: Token vault account of vault A 26 | pub a_token_vault: UncheckedAccount<'info>, 27 | #[account(mut)] 28 | /// CHECK: Token vault account of vault B 29 | pub b_token_vault: UncheckedAccount<'info>, 30 | 31 | #[account(mut)] 32 | /// CHECK: Lp token mint of vault a 33 | pub a_vault_lp_mint: UncheckedAccount<'info>, 34 | #[account(mut)] 35 | /// CHECK: Lp token mint of vault b 36 | pub b_vault_lp_mint: UncheckedAccount<'info>, 37 | 38 | #[account(mut)] 39 | /// CHECK: LP token account of vault A. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 40 | pub a_vault_lp: UncheckedAccount<'info>, 41 | #[account(mut)] 42 | /// CHECK: LP token account of vault B. Used to receive/burn the vault LP upon deposit/withdraw from the vault. 43 | pub b_vault_lp: UncheckedAccount<'info>, 44 | 45 | #[account(mut)] 46 | /// CHECK: Protocol fee token account. Used to receive trading fee. It's mint field must matched with user_source_token mint field. 47 | pub protocol_token_fee: UncheckedAccount<'info>, 48 | 49 | /// CHECK: User account. Must be owner of user_source_token. 50 | pub user: Signer<'info>, 51 | 52 | /// CHECK: Vault program. the pool will deposit/withdraw liquidity from the vault. 53 | pub vault_program: UncheckedAccount<'info>, 54 | /// CHECK: Token program. 55 | pub token_program: UncheckedAccount<'info>, 56 | } 57 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/instructions/update_activation_point.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::PoolError, state::Pool}; 2 | use anchor_lang::prelude::*; 3 | 4 | /// Accounts for update activation point 5 | #[derive(Accounts)] 6 | pub struct UpdateActivationPoint<'info> { 7 | #[account(mut)] 8 | /// Pool account (PDA) 9 | pub pool: Box>, 10 | /// Admin account. 11 | pub admin: Signer<'info>, 12 | } 13 | -------------------------------------------------------------------------------- /programs/dynamic-amm/src/seed.rs: -------------------------------------------------------------------------------- 1 | //! Seed prefix used by pool 2 | 3 | /// Pool prefix 4 | pub static POOL_PREFIX: &str = "pool"; 5 | /// Protocol fee prefix 6 | pub static PROTOCOL_FEE_PREFIX: &str = "fee"; 7 | /// LP mint prefix 8 | pub static LP_MINT_PREFIX: &str = "lp_mint"; 9 | /// Lock escrow prefix 10 | pub static LOCK_ESCROW_PREFIX: &str = "lock_escrow"; 11 | -------------------------------------------------------------------------------- /programs/dynamic-vault/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dynamic-vault" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [features] 8 | default = ["cpi"] 9 | no-entrypoint = [] 10 | cpi = ["no-entrypoint"] 11 | localnet = [] 12 | 13 | [lib] 14 | crate-type = ["cdylib", "lib"] 15 | name = "dynamic_vault" 16 | 17 | [dependencies] 18 | anchor-lang = { version = "0.28.0", features = ["event-cpi"] } 19 | anchor-spl = "0.28.0" 20 | -------------------------------------------------------------------------------- /programs/dynamic-vault/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Dynamic Yield Infrastructure For Solana 2 | #![deny(rustdoc::all)] 3 | #![allow(rustdoc::missing_doc_code_examples)] 4 | #![warn(clippy::unwrap_used)] 5 | #![warn(clippy::integer_arithmetic)] 6 | #![allow(warnings)] 7 | 8 | pub mod seed; 9 | pub mod state; 10 | 11 | use std::str::FromStr; 12 | 13 | use anchor_lang::prelude::*; 14 | use anchor_spl::token::{Mint, Token, TokenAccount}; 15 | 16 | use state::Vault; 17 | 18 | declare_id!("24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi"); 19 | 20 | /// Treasury address 21 | pub fn get_treasury_address() -> Pubkey { 22 | Pubkey::from_str("9kZeN47U2dubGbbzMrzzoRAUvpuxVLRcjW9XiFpYjUo4") 23 | .expect("Must be correct Solana address") 24 | } 25 | /// Base address, setup by Mercurial 26 | pub fn get_base_address() -> Pubkey { 27 | Pubkey::from_str("HWzXGcGHy4tcpYfaRDCyLNzXqBTv3E6BttpCH2vJxArv") 28 | .expect("Must be correct Solana address") 29 | } 30 | 31 | /// Base address, setup by Mercurial 32 | pub fn get_base_address_for_idle_vault() -> Pubkey { 33 | Pubkey::default() 34 | } 35 | 36 | /// Program for vault 37 | #[program] 38 | pub mod dynamic_vault { 39 | use super::*; 40 | /// initialize new vault 41 | pub fn initialize(ctx: Context) -> Result<()> { 42 | Ok(()) 43 | } 44 | } 45 | 46 | /// Accounts for [vault::initialize] 47 | #[derive(Accounts)] 48 | pub struct Initialize<'info> { 49 | /// This is base account for all vault 50 | /// No need base key now because we only allow 1 vault per token now 51 | // pub base: Signer<'info>, 52 | 53 | /// Vault account 54 | #[account( 55 | init, 56 | seeds = [ 57 | seed::VAULT_PREFIX.as_ref(), token_mint.key().as_ref(), get_base_address().as_ref() 58 | ], 59 | bump, 60 | payer = payer, 61 | space = 8 + std::mem::size_of::(), 62 | )] 63 | pub vault: Box>, 64 | 65 | /// Payer can be anyone 66 | #[account(mut)] 67 | pub payer: Signer<'info>, 68 | 69 | /// Token vault account 70 | #[account( 71 | init, 72 | seeds = [seed::TOKEN_VAULT_PREFIX.as_ref(), vault.key().as_ref()], 73 | bump, 74 | payer = payer, 75 | token::mint = token_mint, 76 | token::authority = vault, 77 | )] 78 | pub token_vault: Box>, 79 | /// Token mint account 80 | pub token_mint: Box>, // allocate some accounts in heap to avoid stack frame size limit 81 | /// LP mint account 82 | #[account( 83 | init, 84 | seeds = [seed::LP_MINT_PREFIX.as_ref(), vault.key().as_ref()], 85 | bump, 86 | payer = payer, 87 | mint::decimals = token_mint.decimals, 88 | mint::authority = vault, 89 | )] 90 | pub lp_mint: Box>, 91 | /// rent 92 | pub rent: Sysvar<'info, Rent>, 93 | /// token_program 94 | pub token_program: Program<'info, Token>, 95 | /// system_program 96 | pub system_program: Program<'info, System>, 97 | } 98 | -------------------------------------------------------------------------------- /programs/dynamic-vault/src/seed.rs: -------------------------------------------------------------------------------- 1 | //! Seed prefix used by vault 2 | 3 | /// Vault prefix 4 | pub static VAULT_PREFIX: &str = "vault"; 5 | /// Token vault prefix 6 | pub static TOKEN_VAULT_PREFIX: &str = "token_vault"; 7 | /// Lp mint prefix 8 | pub static LP_MINT_PREFIX: &str = "lp_mint"; 9 | /// Collateral vault prefix 10 | pub static COLLATERAL_VAULT_PREFIX: &str = "collateral_vault"; 11 | // /// Fee vault prefix 12 | // pub static FEE_VAULT_PREFIX: &str = "fee_vault"; 13 | /// Solend obligation prefix 14 | pub static SOLEND_OBLIGATION_PREFIX: &str = "solend_obligation"; 15 | /// Solend Obligation owner prefix 16 | pub static SOLEND_OBLIGATION_OWNER_PREFIX: &str = "solend_obligation_owner"; 17 | /// Kamino obligation prefix 18 | pub static KAMINO_OBLIGATION_PREFIX: &str = "kamino_obligation"; 19 | /// Solend Obligation owner prefix 20 | pub static KAMINO_OBLIGATION_OWNER_PREFIX: &str = "kamino_obligation_owner"; 21 | -------------------------------------------------------------------------------- /programs/dynamic-vault/src/state.rs: -------------------------------------------------------------------------------- 1 | //! Vault and strategy states 2 | 3 | use anchor_lang::prelude::*; 4 | use std::fmt::Debug; 5 | 6 | /// Max strategy number that a vault can support 7 | pub const MAX_STRATEGY: usize = 30; 8 | /// Max bump numer that a strategy can support 9 | pub const MAX_BUMPS: usize = 10; 10 | /// DENOMINATOR of degradation 11 | pub const LOCKED_PROFIT_DEGRADATION_DENOMINATOR: u128 = 1_000_000_000_000; 12 | 13 | /// Vault struct 14 | #[account] 15 | #[derive(Debug)] 16 | pub struct Vault { 17 | /// The flag, if admin set enable = false, then the user can only withdraw and cannot deposit in the vault. 18 | pub enabled: u8, 19 | /// Vault nonce, to create vault seeds 20 | pub bumps: VaultBumps, 21 | /// The total liquidity of the vault, including remaining tokens in token_vault and the liquidity in all strategies. 22 | pub total_amount: u64, 23 | /// Token account, hold liquidity in vault reserve 24 | pub token_vault: Pubkey, 25 | /// Hold lp token of vault, each time rebalance crank is called, vault calculate performance fee and mint corresponding lp token amount to fee_vault. fee_vault is owned by treasury address 26 | pub fee_vault: Pubkey, 27 | /// Token mint that vault supports 28 | pub token_mint: Pubkey, 29 | /// Lp mint of vault 30 | pub lp_mint: Pubkey, 31 | /// The list of strategy addresses that vault supports, vault can support up to MAX_STRATEGY strategies at the same time. 32 | pub strategies: [Pubkey; MAX_STRATEGY], 33 | /// The base address to create vault seeds 34 | pub base: Pubkey, 35 | /// Admin of vault 36 | pub admin: Pubkey, 37 | /// Person who can send the crank. Operator can only send liquidity to strategies that admin defined, and claim reward to account of treasury address 38 | pub operator: Pubkey, 39 | /// Stores information for locked profit. 40 | pub locked_profit_tracker: LockedProfitTracker, 41 | } 42 | 43 | impl Vault { 44 | /// Get amount by share 45 | pub fn get_amount_by_share( 46 | &self, 47 | current_time: u64, 48 | share: u64, 49 | total_supply: u64, 50 | ) -> Option { 51 | let total_amount = self.get_unlocked_amount(current_time)?; 52 | u64::try_from( 53 | u128::from(share) 54 | .checked_mul(u128::from(total_amount))? 55 | .checked_div(u128::from(total_supply))?, 56 | ) 57 | .ok() 58 | } 59 | /// Get unlocked amount of vault 60 | pub fn get_unlocked_amount(&self, current_time: u64) -> Option { 61 | self.total_amount.checked_sub( 62 | self.locked_profit_tracker 63 | .calculate_locked_profit(current_time)?, 64 | ) 65 | } 66 | 67 | /// Get unmint amount by token amount 68 | pub fn get_unmint_amount( 69 | &self, 70 | current_time: u64, 71 | out_token: u64, 72 | total_supply: u64, 73 | ) -> Option { 74 | let total_amount = self.get_unlocked_amount(current_time)?; 75 | u64::try_from( 76 | u128::from(out_token) 77 | .checked_mul(u128::from(total_supply))? 78 | .checked_div(u128::from(total_amount))?, 79 | ) 80 | .ok() 81 | } 82 | } 83 | 84 | /// LockedProfitTracker struct 85 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug)] 86 | pub struct LockedProfitTracker { 87 | /// The total locked profit from the last report 88 | pub last_updated_locked_profit: u64, 89 | /// The last timestamp (in seconds) rebalancing 90 | pub last_report: u64, 91 | /// Rate per second of degradation 92 | pub locked_profit_degradation: u64, 93 | } 94 | 95 | impl LockedProfitTracker { 96 | /// Calculate locked profit, based from Yearn `https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L825` 97 | pub fn calculate_locked_profit(&self, current_time: u64) -> Option { 98 | let duration = u128::from(current_time.checked_sub(self.last_report)?); 99 | let locked_profit_degradation = u128::from(self.locked_profit_degradation); 100 | let locked_fund_ratio = duration.checked_mul(locked_profit_degradation)?; 101 | 102 | if locked_fund_ratio > LOCKED_PROFIT_DEGRADATION_DENOMINATOR { 103 | return Some(0); 104 | } 105 | let locked_profit = u128::from(self.last_updated_locked_profit); 106 | 107 | let locked_profit = (locked_profit 108 | .checked_mul(LOCKED_PROFIT_DEGRADATION_DENOMINATOR - locked_fund_ratio)?) 109 | .checked_div(LOCKED_PROFIT_DEGRADATION_DENOMINATOR)?; 110 | let locked_profit = u64::try_from(locked_profit).ok()?; 111 | Some(locked_profit) 112 | } 113 | } 114 | 115 | impl Default for StrategyType { 116 | fn default() -> Self { 117 | StrategyType::Vault 118 | } 119 | } 120 | 121 | /// Strategy struct 122 | #[account] 123 | #[derive(Default, Debug)] 124 | pub struct Strategy { 125 | /// Lending pool address, that the strategy will deposit/withdraw balance 126 | pub reserve: Pubkey, 127 | /// The token account, that holds the collateral token 128 | pub collateral_vault: Pubkey, 129 | /// Specify type of strategy 130 | pub strategy_type: StrategyType, 131 | /// The liquidity in strategy at the time vault deposit/withdraw from a lending protocol 132 | pub current_liquidity: u64, 133 | /// Hold some bumps, in case the strategy needs to use other seeds to sign a CPI call. 134 | pub bumps: [u8; MAX_BUMPS], 135 | /// Vault address, that the strategy belongs 136 | pub vault: Pubkey, 137 | /// If we remove strategy by remove_strategy2 endpoint, this account will be never added again 138 | pub is_disable: u8, 139 | } 140 | 141 | /// Vault bumps struct 142 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default, Debug)] 143 | pub struct VaultBumps { 144 | /// vault_bump 145 | pub vault_bump: u8, 146 | /// token_vault_bump 147 | pub token_vault_bump: u8, 148 | } 149 | 150 | /// Strategy bumps struct 151 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default, Debug)] 152 | pub struct StrategyBumps { 153 | /// strategy_index 154 | pub strategy_index: u8, 155 | /// Bumps of PDAs for the integrated protocol. 156 | pub other_bumps: [u8; MAX_BUMPS], 157 | } 158 | 159 | /// StrategyType struct 160 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, PartialEq)] 161 | pub enum StrategyType { 162 | /// Deposit in PortFinance’s reserve to get collateral, the value of collateral will increase overtime by accruing interest, and we can claim more liquidity later 163 | PortFinanceWithoutLM, 164 | /// Currently we don’t support this strategy 165 | PortFinanceWithLM, 166 | /// Deposit in Solend’s reserve 167 | SolendWithoutLM, 168 | /// Deposit in Mango’s reserve 169 | Mango, 170 | /// Deposit in Solend’s reserve with obligation 171 | SolendWithLM, 172 | /// Deposit in Apricot’s reserve 173 | ApricotWithoutLM, 174 | /// Deposit in Francium’s reserve 175 | Francium, 176 | /// Deposit in Tulip's reserve 177 | Tulip, 178 | /// This implementation is to compatible with remove_strategy2 endpoint 179 | Vault, 180 | /// Deposit in Drift's spot market 181 | Drift, 182 | /// Deposit in Frakt 183 | Frakt, 184 | /// Deposit in Marginfi 185 | Marginfi, 186 | /// Deposit in Kamino 187 | Kamino, 188 | } 189 | -------------------------------------------------------------------------------- /rust-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = { workspace = true } 4 | edition = { workspace = true } 5 | 6 | [dependencies] 7 | anchor-lang = "0.28.0" 8 | anchor-spl = "0.28.0" 9 | anchor-client = "0.28.0" 10 | clap = { version = "3.2.25", features = ["derive", "env"] } 11 | solana-program = { version = "1.16.0" } 12 | solana-rpc-client = { version = "1.16.0" } 13 | solana-sdk = { version = "1.16.0" } 14 | spl-associated-token-account = "2.2.0" 15 | anyhow = "1.0.57" 16 | spl-token = "3.5.0" 17 | bincode = "1.3.3" 18 | serde = "1.0.137" 19 | serde_json = "1.0.102" 20 | mpl-token-metadata = "3.2.3" 21 | prog_dynamic_amm = { path = "../programs/dynamic-amm", package = "dynamic-amm" } 22 | prog_dynamic_vault = { path = "../programs/dynamic-vault", package = "dynamic-vault" } 23 | regex = "1" 24 | dynamic-amm-quote = { path = "../dynamic-amm-quote" } 25 | common = { path = "../common" } 26 | -------------------------------------------------------------------------------- /rust-client/README.md: -------------------------------------------------------------------------------- 1 | # Mercurial Dynamic AMM CLI 2 | 3 | # Build 4 | `cargo build -p cli` 5 | 6 | # Example 7 | 8 | ``` 9 | $cli --rpc-url $rpc --priority-fee $priority_fee --keypair-path $root_keypair dynamic-amm create-pool\ 10 | --token-a-mint $token_a_mint --token-b-mint $token_b_mint --trade-fee-bps $trade_fee_bps --token-a-amount $token_a_amount --token-b-amount $token_b_amount 11 | ``` 12 | 13 | Allowed trade_fee_bps: 14 | 15 | ``` 16 | // 0.25%, 1%, 4%, 6% 17 | &[25, 100, 400, 600] 18 | ``` -------------------------------------------------------------------------------- /rust-client/src/fee_estimation.rs: -------------------------------------------------------------------------------- 1 | pub const TRANSFER_SOL_COMPUTE_UNIT: u32 = 500; 2 | pub const DEFAULT_SIGNATURE_FEE: u64 = 5000; 3 | pub const RENT_EXEMPTION_SYSTEM_ACCOUNT: u64 = 890880; 4 | pub const TRANSFER_TOKEN_COMPUTE_UNIT: u32 = 50000; 5 | pub const DEFAULT_COMPUTE_UNIT: u32 = 200000; 6 | pub const CREATE_POOL_COMPUTE_UNIT: u32 = 400000; 7 | 8 | pub fn estimate_sol_transfer_fee(priority_fee: u64) -> u64 { 9 | let compute_unit: u64 = TRANSFER_SOL_COMPUTE_UNIT.into(); 10 | priority_fee * compute_unit + DEFAULT_SIGNATURE_FEE 11 | } 12 | 13 | pub fn get_max_transfer_sol(current_balance: u64, transfer_fee: u64) -> u64 { 14 | let max_transfer_amount = current_balance 15 | .checked_sub(transfer_fee) 16 | .unwrap_or(0) 17 | .checked_sub(RENT_EXEMPTION_SYSTEM_ACCOUNT) 18 | .unwrap_or(0); 19 | max_transfer_amount 20 | } 21 | -------------------------------------------------------------------------------- /rust-client/src/file.rs: -------------------------------------------------------------------------------- 1 | use solana_sdk::signer::keypair::Keypair; 2 | use std::collections::BTreeMap; 3 | use std::fs::File; 4 | use std::io::BufReader; 5 | use std::io::Read; 6 | use std::io::Write; 7 | use std::path::PathBuf; 8 | 9 | pub fn read_keypair(path: PathBuf) -> Result { 10 | let file = File::open(path).unwrap(); 11 | let mut reader = BufReader::new(file); 12 | let mut buf = String::new(); 13 | if let Ok(_) = reader.read_to_string(&mut buf) { 14 | let keypair = Keypair::from_base58_string(&buf); 15 | return Ok(keypair); 16 | } 17 | Err("Cannot read keypair") 18 | } 19 | 20 | pub fn parse_send_signature(path: &PathBuf) -> anyhow::Result> { 21 | let mut file = File::open(path)?; 22 | let mut data = String::new(); 23 | file.read_to_string(&mut data).unwrap(); 24 | let hashmap = serde_json::from_str(&data)?; 25 | Ok(hashmap) 26 | } 27 | 28 | pub fn write_signature_to_file(signatures: BTreeMap, path: &PathBuf) { 29 | let serialized = serde_json::to_string_pretty(&signatures).unwrap(); 30 | let mut file: File = File::create(path).unwrap(); 31 | file.write_all(serialized.as_bytes()).unwrap(); 32 | } 33 | -------------------------------------------------------------------------------- /rust-client/src/instructions/dynamic_amm/create_pool.rs: -------------------------------------------------------------------------------- 1 | use self::fee_estimation::CREATE_POOL_COMPUTE_UNIT; 2 | use crate::*; 3 | use anchor_lang::AccountDeserialize; 4 | use anchor_lang::InstructionData; 5 | use anchor_lang::ToAccountMetas; 6 | use anchor_spl::associated_token::get_associated_token_address; 7 | use common::dynamic_amm::pda::derive_permissionless_pool_key_with_fee_tier; 8 | use common::dynamic_amm::pda::derive_protocol_fee_key; 9 | use common::dynamic_amm::pda::derive_vault_lp_key; 10 | use common::dynamic_vault::pda::derive_token_vault_key; 11 | use common::dynamic_vault::pda::derive_vault_key; 12 | use prog_dynamic_amm::state::CurveType; 13 | use prog_dynamic_vault::state::Vault; 14 | use solana_rpc_client::rpc_client::RpcClient; 15 | use solana_sdk::commitment_config::CommitmentConfig; 16 | use solana_sdk::compute_budget::ComputeBudgetInstruction; 17 | use solana_sdk::instruction::Instruction; 18 | use solana_sdk::signer::keypair::read_keypair_file; 19 | use solana_sdk::signer::Signer; 20 | use solana_sdk::transaction::Transaction; 21 | #[derive(Parser, Debug, Clone)] 22 | pub struct CreateDynamicAmmPoolArgs { 23 | #[clap(long, env)] 24 | pub token_a_mint: Pubkey, 25 | #[clap(long, env)] 26 | pub token_b_mint: Pubkey, 27 | #[clap(long, env)] 28 | pub trade_fee_bps: u64, 29 | #[clap(long, env)] 30 | pub token_a_amount: u64, 31 | #[clap(long, env)] 32 | pub token_b_amount: u64, 33 | } 34 | 35 | pub fn process_new_dynamic_pool(args: &Args, sub_args: &CreateDynamicAmmPoolArgs) { 36 | let CreateDynamicAmmPoolArgs { 37 | token_a_mint, 38 | token_b_mint, 39 | trade_fee_bps, 40 | token_a_amount, 41 | token_b_amount, 42 | } = sub_args; 43 | 44 | let client = RpcClient::new_with_commitment(&args.rpc_url, CommitmentConfig::finalized()); 45 | let keypair = read_keypair_file(&args.keypair_path.clone().unwrap()).unwrap(); 46 | 47 | let mut ixs = vec![ 48 | ComputeBudgetInstruction::set_compute_unit_price(args.priority_fee), 49 | ComputeBudgetInstruction::set_compute_unit_limit(CREATE_POOL_COMPUTE_UNIT), 50 | ]; 51 | // check vault 52 | let a_vault = derive_vault_key(*token_a_mint); 53 | let a_token_vault = derive_token_vault_key(a_vault); 54 | let a_vault_lp_mint = common::dynamic_vault::pda::derive_lp_mint_key(a_vault); 55 | if client.get_account(&a_vault).is_err() { 56 | ixs.push(Instruction { 57 | program_id: prog_dynamic_vault::ID, 58 | accounts: prog_dynamic_vault::accounts::Initialize { 59 | vault: a_vault, 60 | token_vault: a_token_vault, 61 | token_mint: *token_a_mint, 62 | token_program: spl_token::ID, 63 | lp_mint: a_vault_lp_mint, 64 | rent: anchor_client::solana_sdk::sysvar::rent::ID, 65 | system_program: solana_program::system_program::ID, 66 | payer: keypair.pubkey(), 67 | } 68 | .to_account_metas(None), 69 | data: prog_dynamic_vault::instruction::Initialize {}.data(), 70 | }); 71 | } 72 | 73 | let b_vault = derive_vault_key(*token_b_mint); 74 | let b_token_vault = derive_token_vault_key(b_vault); 75 | let b_vault_lp_mint = common::dynamic_vault::pda::derive_lp_mint_key(b_vault); 76 | if client.get_account(&b_vault).is_err() { 77 | ixs.push(Instruction { 78 | program_id: prog_dynamic_vault::ID, 79 | accounts: prog_dynamic_vault::accounts::Initialize { 80 | vault: b_vault, 81 | token_vault: b_token_vault, 82 | token_mint: *token_b_mint, 83 | token_program: spl_token::ID, 84 | lp_mint: b_vault_lp_mint, 85 | rent: anchor_client::solana_sdk::sysvar::rent::ID, 86 | system_program: solana_program::system_program::ID, 87 | payer: keypair.pubkey(), 88 | } 89 | .to_account_metas(None), 90 | data: prog_dynamic_vault::instruction::Initialize {}.data(), 91 | }); 92 | } 93 | 94 | let pool = derive_permissionless_pool_key_with_fee_tier( 95 | CurveType::ConstantProduct, 96 | *token_a_mint, 97 | *token_b_mint, 98 | *trade_fee_bps, 99 | ); 100 | let pool_lp_mint = common::dynamic_amm::pda::derive_lp_mint_key(pool); 101 | let (mint_metadata, _bump) = mpl_token_metadata::accounts::Metadata::find_pda(&pool_lp_mint); 102 | ixs.push(Instruction { 103 | program_id: prog_dynamic_amm::ID, 104 | accounts: prog_dynamic_amm::accounts::InitializePermissionlessPoolWithFeeTier { 105 | pool, 106 | rent: anchor_client::solana_sdk::sysvar::rent::ID, 107 | system_program: solana_program::system_program::ID, 108 | payer: keypair.pubkey(), 109 | a_vault, 110 | b_vault, 111 | a_token_vault, 112 | b_token_vault, 113 | a_vault_lp_mint, 114 | b_vault_lp_mint, 115 | lp_mint: pool_lp_mint, 116 | token_a_mint: *token_a_mint, 117 | token_b_mint: *token_b_mint, 118 | token_program: spl_token::ID, 119 | associated_token_program: spl_associated_token_account::ID, 120 | a_vault_lp: derive_vault_lp_key(a_vault, pool), 121 | b_vault_lp: derive_vault_lp_key(b_vault, pool), 122 | payer_token_a: get_associated_token_address(&keypair.pubkey(), token_a_mint), 123 | payer_token_b: get_associated_token_address(&keypair.pubkey(), token_b_mint), 124 | payer_pool_lp: get_associated_token_address(&keypair.pubkey(), &pool_lp_mint), 125 | protocol_token_a_fee: derive_protocol_fee_key(*token_a_mint, pool), 126 | protocol_token_b_fee: derive_protocol_fee_key(*token_b_mint, pool), 127 | fee_owner: Pubkey::default(), 128 | vault_program: prog_dynamic_vault::ID, 129 | metadata_program: mpl_token_metadata::ID, 130 | mint_metadata, 131 | } 132 | .to_account_metas(None), 133 | data: prog_dynamic_amm::instruction::InitializePermissionlessPoolWithFeeTier { 134 | curve_type: CurveType::ConstantProduct, 135 | trade_fee_bps: *trade_fee_bps, 136 | token_a_amount: *token_a_amount, 137 | token_b_amount: *token_b_amount, 138 | } 139 | .data(), 140 | }); 141 | 142 | let blockhash = client.get_latest_blockhash().unwrap(); 143 | let tx = 144 | Transaction::new_signed_with_payer(&ixs, Some(&keypair.pubkey()), &[&keypair], blockhash); 145 | let payload = args 146 | .to_rpc_args() 147 | .send_transaction_wrapper( 148 | &tx, 149 | MAX_RETRIES, 150 | keypair.pubkey(), 151 | "".to_string(), 152 | sucess_cb, 153 | failed_cb, 154 | ) 155 | .unwrap(); 156 | let mut result = BTreeMap::new(); 157 | result.insert(0, Some(payload)); 158 | handle_collect_cb_by_tx_action(args.tx_action, None, &result); 159 | } 160 | 161 | fn sucess_cb(_wallet_memo: String, sig: Signature) { 162 | println!("done create pool {:?}", sig); 163 | } 164 | fn failed_cb(_wallet_memo: String) { 165 | println!("cannot create pool"); 166 | } 167 | -------------------------------------------------------------------------------- /rust-client/src/instructions/dynamic_amm/get_pool_info.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_lang::AccountDeserialize; 3 | use anchor_spl::token::Mint; 4 | use anchor_spl::token::TokenAccount; 5 | use anyhow::Context; 6 | use bincode::deserialize; 7 | use prog_dynamic_amm::state::Pool; 8 | use prog_dynamic_vault::state::Vault; 9 | use solana_sdk::sysvar::clock; 10 | use solana_sdk::sysvar::clock::Clock; 11 | #[derive(Parser, Debug, Clone)] 12 | pub struct PoolInfoDynamicAmmArgs { 13 | #[clap(long, env)] 14 | pub pool: Pubkey, 15 | } 16 | 17 | pub fn process_get_dynamic_pool_info(args: &Args, sub_args: &PoolInfoDynamicAmmArgs) { 18 | let PoolInfoDynamicAmmArgs { pool } = sub_args; 19 | 20 | let program_dynamic_amm = args.to_rpc_args().get_program_client(prog_dynamic_amm::ID); 21 | 22 | let program_dynamic_vault = args 23 | .to_rpc_args() 24 | .get_program_client(prog_dynamic_vault::ID); 25 | 26 | let pool_state: Pool = program_dynamic_amm.account(*pool).unwrap(); 27 | let vault_a: Vault = program_dynamic_vault.account(pool_state.a_vault).unwrap(); 28 | let vault_b: Vault = program_dynamic_vault.account(pool_state.b_vault).unwrap(); 29 | 30 | let accounts = program_dynamic_amm 31 | .rpc() 32 | .get_multiple_accounts(&[ 33 | pool_state.a_vault_lp, 34 | pool_state.b_vault_lp, 35 | vault_a.lp_mint, 36 | vault_b.lp_mint, 37 | clock::id(), 38 | ]) 39 | .unwrap(); 40 | 41 | let accounts = accounts 42 | .into_iter() 43 | .map(|account| account.unwrap().data) 44 | .collect::>>(); 45 | 46 | let mut data = accounts[0].as_slice(); 47 | let pool_vault_a_lp_token = TokenAccount::try_deserialize_unchecked(&mut data).unwrap(); 48 | 49 | let mut data = accounts[1].as_slice(); 50 | let pool_vault_b_lp_token = TokenAccount::try_deserialize_unchecked(&mut data).unwrap(); 51 | 52 | let mut data = accounts[2].as_slice(); 53 | let vault_a_lp_mint = Mint::try_deserialize_unchecked(&mut data).unwrap(); 54 | 55 | let mut data = accounts[3].as_slice(); 56 | let vault_b_lp_mint = Mint::try_deserialize_unchecked(&mut data).unwrap(); 57 | 58 | let data = accounts[4].as_slice(); 59 | let clock = deserialize::(&data).unwrap(); 60 | 61 | let current_time: u64 = clock.unix_timestamp.try_into().unwrap(); 62 | 63 | let token_a_amount = vault_a 64 | .get_amount_by_share( 65 | current_time, 66 | pool_vault_a_lp_token.amount, 67 | vault_a_lp_mint.supply, 68 | ) 69 | .context("Fail to get token a amount") 70 | .unwrap(); 71 | 72 | let token_b_amount = vault_b 73 | .get_amount_by_share( 74 | current_time, 75 | pool_vault_b_lp_token.amount, 76 | vault_b_lp_mint.supply, 77 | ) 78 | .context("Fail to get token b amount") 79 | .unwrap(); 80 | 81 | println!( 82 | "token_a_amount {} token_b_amount {}", 83 | token_a_amount, token_b_amount 84 | ); 85 | } 86 | -------------------------------------------------------------------------------- /rust-client/src/instructions/dynamic_amm/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | pub mod create_pool; 3 | pub use create_pool::*; 4 | pub mod deposit; 5 | pub use deposit::*; 6 | pub mod withdraw; 7 | pub use withdraw::*; 8 | 9 | pub mod swap; 10 | pub use swap::*; 11 | 12 | pub mod quote; 13 | pub use quote::*; 14 | 15 | pub mod get_pool_info; 16 | pub use get_pool_info::*; 17 | 18 | #[derive(Debug, Parser, Clone)] 19 | pub enum DynamicAmmCommands { 20 | /// Create pool 21 | CreatePool(CreateDynamicAmmPoolArgs), 22 | /// Deposit 23 | Deposit(DepositDynamicAmmArgs), 24 | /// Withdraw 25 | Withdraw(WithdrawDynamicAmmArgs), 26 | /// Swap 27 | Swap(SwapDynamicAmmArgs), 28 | /// Quote 29 | Quote(QuoteDynamicAmmArgs), 30 | /// Get pool info 31 | GetPoolInfo(PoolInfoDynamicAmmArgs), 32 | } 33 | -------------------------------------------------------------------------------- /rust-client/src/instructions/dynamic_amm/quote.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use anchor_lang::AccountDeserialize; 3 | use anchor_spl::token::Mint; 4 | use anchor_spl::token::TokenAccount; 5 | use bincode::deserialize; 6 | use dynamic_amm_quote::compute_quote; 7 | use dynamic_amm_quote::QuoteData; 8 | use prog_dynamic_amm::state::Pool; 9 | use prog_dynamic_vault::state::Vault; 10 | use solana_sdk::sysvar::clock; 11 | use solana_sdk::sysvar::clock::Clock; 12 | use std::collections::HashMap; 13 | #[derive(Parser, Debug, Clone)] 14 | pub struct QuoteDynamicAmmArgs { 15 | #[clap(long, env)] 16 | pub pool: Pubkey, 17 | #[clap(long, env)] 18 | pub source_token: Pubkey, 19 | #[clap(long, env)] 20 | pub in_amount: u64, 21 | } 22 | 23 | pub fn process_quote_dynamic_pool(args: &Args, sub_args: &QuoteDynamicAmmArgs) { 24 | let QuoteDynamicAmmArgs { 25 | pool, 26 | in_amount, 27 | source_token, 28 | } = sub_args; 29 | 30 | let program_dynamic_amm = args.to_rpc_args().get_program_client(prog_dynamic_amm::ID); 31 | let program_dynamic_vault = args 32 | .to_rpc_args() 33 | .get_program_client(prog_dynamic_vault::ID); 34 | 35 | let pool_state: Pool = program_dynamic_amm.account(*pool).unwrap(); 36 | let vault_a: Vault = program_dynamic_vault.account(pool_state.a_vault).unwrap(); 37 | let vault_b: Vault = program_dynamic_vault.account(pool_state.b_vault).unwrap(); 38 | 39 | let accounts = program_dynamic_amm 40 | .rpc() 41 | .get_multiple_accounts(&[ 42 | pool_state.a_vault_lp, 43 | pool_state.b_vault_lp, 44 | vault_a.lp_mint, 45 | vault_b.lp_mint, 46 | vault_a.token_vault, 47 | vault_b.token_vault, 48 | clock::id(), 49 | ]) 50 | .unwrap(); 51 | 52 | let accounts = accounts 53 | .into_iter() 54 | .map(|account| account.unwrap().data) 55 | .collect::>>(); 56 | 57 | let mut data = accounts[0].as_slice(); 58 | let pool_vault_a_lp_token = TokenAccount::try_deserialize_unchecked(&mut data).unwrap(); 59 | 60 | let mut data = accounts[1].as_slice(); 61 | let pool_vault_b_lp_token = TokenAccount::try_deserialize_unchecked(&mut data).unwrap(); 62 | 63 | let mut data = accounts[2].as_slice(); 64 | let vault_a_lp_mint = Mint::try_deserialize_unchecked(&mut data).unwrap(); 65 | 66 | let mut data = accounts[3].as_slice(); 67 | let vault_b_lp_mint = Mint::try_deserialize_unchecked(&mut data).unwrap(); 68 | 69 | let mut data = accounts[4].as_slice(); 70 | let vault_a_token = TokenAccount::try_deserialize_unchecked(&mut data).unwrap(); 71 | 72 | let mut data = accounts[5].as_slice(); 73 | let vault_b_token = TokenAccount::try_deserialize_unchecked(&mut data).unwrap(); 74 | 75 | let data = accounts[6].as_slice(); 76 | let clock = deserialize::(&data).unwrap(); 77 | 78 | let stake_data = if pool_state.stake != Pubkey::default() { 79 | let account = program_dynamic_amm 80 | .rpc() 81 | .get_account(&pool_state.stake) 82 | .unwrap(); 83 | let mut stake_data = HashMap::new(); 84 | stake_data.insert(pool_state.stake, account.data); 85 | stake_data 86 | } else { 87 | HashMap::new() 88 | }; 89 | 90 | let quote_data = QuoteData { 91 | pool: pool_state, 92 | vault_a, 93 | vault_b, 94 | pool_vault_a_lp_token, 95 | pool_vault_b_lp_token, 96 | vault_a_lp_mint, 97 | vault_b_lp_mint, 98 | vault_a_token, 99 | vault_b_token, 100 | clock, 101 | stake_data, 102 | }; 103 | let quote = compute_quote(*source_token, *in_amount, quote_data); 104 | println!("{:?}", quote); 105 | } 106 | -------------------------------------------------------------------------------- /rust-client/src/instructions/dynamic_amm/swap.rs: -------------------------------------------------------------------------------- 1 | use self::fee_estimation::DEFAULT_COMPUTE_UNIT; 2 | use crate::*; 3 | use anchor_lang::InstructionData; 4 | use anchor_lang::ToAccountMetas; 5 | use anchor_spl::associated_token::get_associated_token_address; 6 | use prog_dynamic_amm::state::Pool; 7 | use prog_dynamic_vault::state::Vault; 8 | use solana_rpc_client::rpc_client::RpcClient; 9 | use solana_sdk::commitment_config::CommitmentConfig; 10 | use solana_sdk::compute_budget::ComputeBudgetInstruction; 11 | use solana_sdk::instruction::Instruction; 12 | use solana_sdk::signer::keypair::read_keypair_file; 13 | use solana_sdk::signer::Signer; 14 | use solana_sdk::transaction::Transaction; 15 | #[derive(Parser, Debug, Clone)] 16 | pub struct SwapDynamicAmmArgs { 17 | #[clap(long, env)] 18 | pub pool: Pubkey, 19 | #[clap(long, env)] 20 | pub source_token: Pubkey, 21 | #[clap(long, env)] 22 | pub in_amount: u64, 23 | #[clap(long, env)] 24 | pub minimum_out_amount: u64, 25 | } 26 | 27 | pub fn process_swap_dynamic_pool(args: &Args, sub_args: &SwapDynamicAmmArgs) { 28 | let SwapDynamicAmmArgs { 29 | pool, 30 | in_amount, 31 | minimum_out_amount, 32 | source_token, 33 | } = sub_args; 34 | 35 | let client = RpcClient::new_with_commitment(&args.rpc_url, CommitmentConfig::finalized()); 36 | let keypair = read_keypair_file(&args.keypair_path.clone().unwrap()).unwrap(); 37 | 38 | let mut ixs = vec![ 39 | ComputeBudgetInstruction::set_compute_unit_price(args.priority_fee), 40 | ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT), 41 | ]; 42 | 43 | let program_amm_client = args.to_rpc_args().get_program_client(prog_dynamic_amm::ID); 44 | let pool_state: Pool = program_amm_client.account(*pool).unwrap(); 45 | 46 | let program_vault_client = args.to_rpc_args().get_program_client(prog_dynamic_amm::ID); 47 | let a_vault_state: Vault = program_vault_client.account(pool_state.a_vault).unwrap(); 48 | let b_vault_state: Vault = program_vault_client.account(pool_state.b_vault).unwrap(); 49 | 50 | let (user_destination_token, protocol_token_fee, destination_mint) = 51 | if *source_token == pool_state.token_a_mint { 52 | ( 53 | get_associated_token_address(&keypair.pubkey(), &pool_state.token_b_mint), 54 | pool_state.protocol_token_a_fee, 55 | pool_state.token_b_mint, 56 | ) 57 | } else { 58 | ( 59 | get_associated_token_address(&keypair.pubkey(), &pool_state.token_a_mint), 60 | pool_state.protocol_token_b_fee, 61 | pool_state.token_a_mint, 62 | ) 63 | }; 64 | 65 | if client.get_account(&user_destination_token).is_err() { 66 | ixs.push( 67 | spl_associated_token_account::instruction::create_associated_token_account( 68 | &keypair.pubkey(), 69 | &keypair.pubkey(), 70 | &destination_mint, 71 | &spl_token::ID, 72 | ), 73 | ); 74 | } 75 | 76 | ixs.push(Instruction { 77 | program_id: prog_dynamic_amm::ID, 78 | accounts: prog_dynamic_amm::accounts::Swap { 79 | pool: *pool, 80 | user_source_token: get_associated_token_address(&keypair.pubkey(), source_token), 81 | user_destination_token, 82 | a_vault_lp: pool_state.a_vault_lp, 83 | b_vault_lp: pool_state.b_vault_lp, 84 | a_vault: pool_state.a_vault, 85 | b_vault: pool_state.b_vault, 86 | a_vault_lp_mint: a_vault_state.lp_mint, 87 | b_vault_lp_mint: b_vault_state.lp_mint, 88 | a_token_vault: a_vault_state.token_vault, 89 | b_token_vault: b_vault_state.token_vault, 90 | user: keypair.pubkey(), 91 | vault_program: prog_dynamic_vault::ID, 92 | token_program: spl_token::ID, 93 | protocol_token_fee, 94 | } 95 | .to_account_metas(None), 96 | data: prog_dynamic_amm::instruction::Swap { 97 | in_amount: *in_amount, 98 | minimum_out_amount: *minimum_out_amount, 99 | } 100 | .data(), 101 | }); 102 | 103 | let blockhash = client.get_latest_blockhash().unwrap(); 104 | let tx = 105 | Transaction::new_signed_with_payer(&ixs, Some(&keypair.pubkey()), &[&keypair], blockhash); 106 | let payload = args 107 | .to_rpc_args() 108 | .send_transaction_wrapper( 109 | &tx, 110 | MAX_RETRIES, 111 | keypair.pubkey(), 112 | "".to_string(), 113 | sucess_cb, 114 | failed_cb, 115 | ) 116 | .unwrap(); 117 | let mut result = BTreeMap::new(); 118 | result.insert(0, Some(payload)); 119 | handle_collect_cb_by_tx_action(args.tx_action, None, &result); 120 | } 121 | 122 | fn sucess_cb(_wallet_memo: String, sig: Signature) { 123 | println!("done swap {:?}", sig); 124 | } 125 | fn failed_cb(_wallet_memo: String) { 126 | println!("cannot swap"); 127 | } 128 | -------------------------------------------------------------------------------- /rust-client/src/instructions/dynamic_amm/withdraw.rs: -------------------------------------------------------------------------------- 1 | use self::fee_estimation::DEFAULT_COMPUTE_UNIT; 2 | use crate::*; 3 | use anchor_lang::InstructionData; 4 | use anchor_lang::ToAccountMetas; 5 | use anchor_spl::associated_token::get_associated_token_address; 6 | use prog_dynamic_amm::state::Pool; 7 | use prog_dynamic_vault::state::Vault; 8 | use solana_rpc_client::rpc_client::RpcClient; 9 | use solana_sdk::commitment_config::CommitmentConfig; 10 | use solana_sdk::compute_budget::ComputeBudgetInstruction; 11 | use solana_sdk::instruction::Instruction; 12 | use solana_sdk::signer::keypair::read_keypair_file; 13 | use solana_sdk::signer::Signer; 14 | use solana_sdk::transaction::Transaction; 15 | #[derive(Parser, Debug, Clone)] 16 | pub struct WithdrawDynamicAmmArgs { 17 | #[clap(long, env)] 18 | pub pool: Pubkey, 19 | #[clap(long, env)] 20 | pub pool_token_amount: u64, 21 | #[clap(long, env)] 22 | pub minimum_a_token_out: u64, 23 | #[clap(long, env)] 24 | pub minimum_b_token_out: u64, 25 | } 26 | 27 | pub fn process_withdraw_dynamic_pool(args: &Args, sub_args: &WithdrawDynamicAmmArgs) { 28 | let WithdrawDynamicAmmArgs { 29 | pool, 30 | pool_token_amount, 31 | minimum_a_token_out, 32 | minimum_b_token_out, 33 | } = sub_args; 34 | 35 | let client = RpcClient::new_with_commitment(&args.rpc_url, CommitmentConfig::finalized()); 36 | let keypair = read_keypair_file(&args.keypair_path.clone().unwrap()).unwrap(); 37 | 38 | let mut ixs = vec![ 39 | ComputeBudgetInstruction::set_compute_unit_price(args.priority_fee), 40 | ComputeBudgetInstruction::set_compute_unit_limit(DEFAULT_COMPUTE_UNIT), 41 | ]; 42 | 43 | let program_amm_client = args.to_rpc_args().get_program_client(prog_dynamic_amm::ID); 44 | let pool_state: Pool = program_amm_client.account(*pool).unwrap(); 45 | 46 | let user_pool_lp = get_associated_token_address(&keypair.pubkey(), &pool_state.lp_mint); 47 | let program_vault_client = args.to_rpc_args().get_program_client(prog_dynamic_amm::ID); 48 | let a_vault_state: Vault = program_vault_client.account(pool_state.a_vault).unwrap(); 49 | let b_vault_state: Vault = program_vault_client.account(pool_state.b_vault).unwrap(); 50 | 51 | ixs.push(Instruction { 52 | program_id: prog_dynamic_amm::ID, 53 | accounts: prog_dynamic_amm::accounts::AddOrRemoveBalanceLiquidity { 54 | pool: *pool, 55 | lp_mint: pool_state.lp_mint, 56 | user_pool_lp, 57 | a_vault_lp: pool_state.a_vault_lp, 58 | b_vault_lp: pool_state.b_vault_lp, 59 | a_vault: pool_state.a_vault, 60 | b_vault: pool_state.b_vault, 61 | a_vault_lp_mint: a_vault_state.lp_mint, 62 | b_vault_lp_mint: b_vault_state.lp_mint, 63 | a_token_vault: a_vault_state.token_vault, 64 | b_token_vault: b_vault_state.token_vault, 65 | user_a_token: get_associated_token_address(&keypair.pubkey(), &pool_state.token_a_mint), 66 | user_b_token: get_associated_token_address(&keypair.pubkey(), &pool_state.token_b_mint), 67 | user: keypair.pubkey(), 68 | vault_program: prog_dynamic_vault::ID, 69 | token_program: spl_token::ID, 70 | } 71 | .to_account_metas(None), 72 | data: prog_dynamic_amm::instruction::RemoveBalanceLiquidity { 73 | pool_token_amount: *pool_token_amount, 74 | minimum_a_token_out: *minimum_a_token_out, 75 | minimum_b_token_out: *minimum_b_token_out, 76 | } 77 | .data(), 78 | }); 79 | 80 | let blockhash = client.get_latest_blockhash().unwrap(); 81 | let tx = 82 | Transaction::new_signed_with_payer(&ixs, Some(&keypair.pubkey()), &[&keypair], blockhash); 83 | let payload = args 84 | .to_rpc_args() 85 | .send_transaction_wrapper( 86 | &tx, 87 | MAX_RETRIES, 88 | keypair.pubkey(), 89 | "".to_string(), 90 | sucess_cb, 91 | failed_cb, 92 | ) 93 | .unwrap(); 94 | let mut result = BTreeMap::new(); 95 | result.insert(0, Some(payload)); 96 | handle_collect_cb_by_tx_action(args.tx_action, None, &result); 97 | } 98 | 99 | fn sucess_cb(_wallet_memo: String, sig: Signature) { 100 | println!("done withdraw {:?}", sig); 101 | } 102 | fn failed_cb(_wallet_memo: String) { 103 | println!("cannot withdraw"); 104 | } 105 | -------------------------------------------------------------------------------- /rust-client/src/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod dynamic_amm; 2 | pub use dynamic_amm::*; 3 | -------------------------------------------------------------------------------- /rust-client/src/main.rs: -------------------------------------------------------------------------------- 1 | pub mod fee_estimation; 2 | pub mod file; 3 | pub mod instructions; 4 | pub mod rpc; 5 | pub mod transaction_utils; 6 | use crate::instructions::*; 7 | 8 | use clap::{Parser, Subcommand}; 9 | 10 | use file::write_signature_to_file; 11 | pub use rpc::*; 12 | use solana_sdk::signature::Signature; 13 | use solana_sdk::{pubkey::Pubkey, signer::keypair::Keypair}; 14 | use std::collections::BTreeMap; 15 | use std::path::PathBuf; 16 | 17 | pub const MAX_RETRIES: u64 = 5; 18 | 19 | #[derive(Parser, Debug, Clone)] 20 | #[clap(author, version, about, long_about = None)] 21 | pub struct Args { 22 | #[clap(subcommand)] 23 | pub command: Commands, 24 | 25 | /// RPC url 26 | #[clap(long, env, default_value = "http://localhost:8899")] 27 | pub rpc_url: String, 28 | 29 | /// Payer keypair 30 | #[clap(long, env)] 31 | pub keypair_path: Option, 32 | 33 | /// Priority fee 34 | #[clap(long, env, default_value = "0")] 35 | pub priority_fee: u64, 36 | 37 | /// Is simulation 38 | #[clap(long, env, default_value = "0")] 39 | pub tx_action: u8, 40 | } 41 | 42 | impl Args { 43 | pub fn to_rpc_args(&self) -> RpcArgs { 44 | RpcArgs { 45 | rpc_url: self.rpc_url.clone(), 46 | priority_fee: self.priority_fee, 47 | tx_action: self.tx_action, 48 | keypair_path: self.keypair_path.clone(), 49 | } 50 | } 51 | } 52 | 53 | pub fn handle_collect_cb_by_tx_action( 54 | tx_action: u8, 55 | dump_signature_path: Option, 56 | result: &BTreeMap>, 57 | ) { 58 | match tx_action { 59 | TX_ACTION_ESTIMATE_FEE => { 60 | let mut total_fee = 0u64; 61 | for (k, v) in result.iter() { 62 | if let Some(v) = v { 63 | let fee: u64 = bincode::deserialize(&v.payload).unwrap(); 64 | println!("Fee {} {}", v.wallet_memo, fee); 65 | total_fee += fee; 66 | } else { 67 | println!("wallet {} doesn't have fee", k); 68 | } 69 | } 70 | println!("Total fee {}", total_fee); 71 | } 72 | TX_ACTION_SIMULATION => { 73 | let mut total_sol_comsumed = 0u64; 74 | let mut total_sol_earned = 0u64; 75 | for (k, v) in result.iter() { 76 | if let Some(v) = v { 77 | let SimulationResult { 78 | result, 79 | pre_payer_balance, 80 | post_payer_balance, 81 | } = bincode::deserialize(&v.payload).unwrap(); 82 | match result.value.err { 83 | Some(err) => { 84 | println!( 85 | "Wallet {}\nerror {:?}\nlog {:?}", 86 | v.wallet_memo, err, result.value.logs 87 | ); 88 | } 89 | None => { 90 | println!("Wallet {} simulate sucessfully", v.wallet_memo); 91 | if post_payer_balance > pre_payer_balance { 92 | total_sol_earned += 93 | post_payer_balance.checked_sub(pre_payer_balance).unwrap(); 94 | } else { 95 | total_sol_comsumed += 96 | pre_payer_balance.checked_sub(post_payer_balance).unwrap(); 97 | } 98 | } 99 | } 100 | } else { 101 | println!("wallet {} doesn't have simulation result", k); 102 | } 103 | } 104 | println!( 105 | "TOTAL SOL CONSUMED: {} EARNED: {}", 106 | total_sol_comsumed, total_sol_earned 107 | ); 108 | } 109 | TX_ACTION_SENT_TX => { 110 | let mut signatures = BTreeMap::new(); 111 | for (k, v) in result.iter() { 112 | if let Some(v) = v { 113 | let sig: Signature = bincode::deserialize(&v.payload).unwrap(); 114 | println!("wallet {} Sig {}", v.wallet_memo.clone(), sig); 115 | signatures.insert(v.wallet_memo.clone(), sig.to_string()); 116 | } else { 117 | println!("wallet {} doesn't have signature", k); 118 | } 119 | } 120 | 121 | match dump_signature_path { 122 | Some(value) => { 123 | write_signature_to_file(signatures, &value); 124 | } 125 | None => { 126 | // Do nothing 127 | } 128 | } 129 | } 130 | _ => { 131 | // DO nothing 132 | } 133 | } 134 | } 135 | 136 | // Subcommands 137 | #[derive(Subcommand, Debug, Clone)] 138 | pub enum Commands { 139 | /// Dynamic amm 140 | #[clap(subcommand)] 141 | DynamicAmm(DynamicAmmCommands), 142 | } 143 | 144 | fn main() { 145 | let args = Args::parse(); 146 | 147 | match &args.command { 148 | Commands::DynamicAmm(sub_command) => match sub_command { 149 | DynamicAmmCommands::CreatePool(sub_args) => { 150 | dynamic_amm::process_new_dynamic_pool(&args, sub_args) 151 | } 152 | DynamicAmmCommands::Deposit(sub_args) => { 153 | dynamic_amm::process_deposit_dynamic_pool(&args, sub_args) 154 | } 155 | DynamicAmmCommands::Withdraw(sub_args) => { 156 | dynamic_amm::process_withdraw_dynamic_pool(&args, sub_args) 157 | } 158 | DynamicAmmCommands::Swap(sub_args) => { 159 | dynamic_amm::process_swap_dynamic_pool(&args, sub_args) 160 | } 161 | DynamicAmmCommands::Quote(sub_args) => { 162 | dynamic_amm::process_quote_dynamic_pool(&args, sub_args) 163 | } 164 | DynamicAmmCommands::GetPoolInfo(sub_args) => { 165 | dynamic_amm::process_get_dynamic_pool_info(&args, sub_args) 166 | } 167 | }, 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /rust-client/src/transaction_utils.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | use solana_sdk::pubkey::Pubkey; 3 | 4 | pub fn parse_event_log< 5 | T: anchor_lang::AnchorDeserialize + anchor_lang::AnchorSerialize + anchor_lang::Discriminator, 6 | >( 7 | logs: &Vec, 8 | program_id: Pubkey, 9 | ) -> Option { 10 | let program_start_pattern = Regex::new(r"Program .* invoke \[\d{1}\]").ok()?; 11 | let program_end_pattern = Regex::new(r"Program .* success").ok()?; 12 | let mut execution_stack: Vec = vec![]; 13 | for log in logs.into_iter() { 14 | if program_start_pattern.is_match(log) { 15 | let parsed_program_id: String = log.chars().skip(8).take(44).collect(); 16 | let parsed_program_id = parsed_program_id.trim(); 17 | execution_stack.push(parsed_program_id.to_string()); 18 | } 19 | if program_end_pattern.is_match(log) { 20 | execution_stack.pop(); 21 | } 22 | if log.starts_with("Program data:") && *execution_stack.last()? == program_id.to_string() { 23 | // Skip the prefix "Program data: " 24 | // Event logged has been changed to Program data: instead of Program log: 25 | // https://github.com/project-serum/anchor/pull/1608/files 26 | let log_info: String = log.chars().skip(14).collect(); 27 | let log_buf = anchor_lang::__private::base64::decode(log_info.as_bytes()); 28 | if log_buf.is_ok() { 29 | let log_buf = log_buf.ok()?; 30 | // Check for event discriminator, it is a 8-byte prefix 31 | if log_buf[0..8] == T::discriminator() { 32 | // Skip event discriminator when deserialize 33 | return T::try_from_slice(&log_buf[8..]).ok(); 34 | } 35 | } 36 | } 37 | } 38 | None 39 | } 40 | -------------------------------------------------------------------------------- /ts-client/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "printWidth": 120, 6 | "tabWidth": 2 7 | } 8 | -------------------------------------------------------------------------------- /ts-client/index.ts: -------------------------------------------------------------------------------- 1 | import { VaultIdl as Vault, IDL as VaultIdl } from '@mercurial-finance/vault-sdk'; 2 | import AmmImpl from './src/amm'; 3 | import DynamicAmmError from './src/amm/error'; 4 | import { StableSwap, ConstantProductSwap } from './src/amm/curve'; 5 | import { 6 | PROGRAM_ID, 7 | MAINNET_POOL, 8 | DEVNET_POOL, 9 | CURVE_TYPE_ACCOUNTS, 10 | CONSTANT_PRODUCT_ALLOWED_TRADE_FEE_BPS, 11 | STABLE_SWAP_ALLOWED_TRADE_FEE_BPS, 12 | } from './src/amm/constants'; 13 | import { 14 | getOnchainTime, 15 | calculateMaxSwapOutAmount, 16 | calculateSwapQuote, 17 | calculatePoolInfo, 18 | getDepegAccounts, 19 | checkPoolExists, 20 | getTokensMintFromPoolAddress, 21 | derivePoolAddress, 22 | } from './src/amm/utils'; 23 | import { Amm, IDL as AmmIdl } from './src/amm/idl'; 24 | 25 | export default AmmImpl; 26 | export { 27 | AmmImpl, 28 | // Classes 29 | ConstantProductSwap, 30 | StableSwap, 31 | DynamicAmmError, 32 | // Utils 33 | getDepegAccounts, 34 | getOnchainTime, 35 | calculateMaxSwapOutAmount, 36 | calculateSwapQuote, 37 | calculatePoolInfo, 38 | checkPoolExists, 39 | getTokensMintFromPoolAddress, 40 | derivePoolAddress, 41 | // Constant 42 | PROGRAM_ID, 43 | MAINNET_POOL, 44 | DEVNET_POOL, 45 | CURVE_TYPE_ACCOUNTS, 46 | CONSTANT_PRODUCT_ALLOWED_TRADE_FEE_BPS, 47 | STABLE_SWAP_ALLOWED_TRADE_FEE_BPS, 48 | // IDL 49 | AmmIdl, 50 | VaultIdl, 51 | }; 52 | 53 | export type { 54 | AmmImplementation, 55 | DepositQuote, 56 | WithdrawQuote, 57 | SwapQuote, 58 | PoolState, 59 | LockEscrow, 60 | PoolInformation, 61 | ParsedClockState, 62 | ConstantProductCurve, 63 | StableSwapCurve, 64 | SwapQuoteParam, 65 | Bootstrapping, 66 | } from './src/amm/types'; 67 | export type { VaultState } from '@mercurial-finance/vault-sdk'; 68 | export type { Amm, Vault }; 69 | -------------------------------------------------------------------------------- /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 | setupFiles: ['/jest/setup.js'], 11 | testTimeout: TIMEOUT_SEC * 90, 12 | }; 13 | -------------------------------------------------------------------------------- /ts-client/jest/setup.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | -------------------------------------------------------------------------------- /ts-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@meteora-ag/dynamic-amm-sdk", 3 | "version": "1.3.8", 4 | "description": "Meteora Dynamic Amm SDK is a typescript library that allows you to interact with Meteora's AMM.", 5 | "main": "dist/cjs/index.js", 6 | "module": "dist/esm/index.js", 7 | "scripts": { 8 | "build": "rm -rf dist && tsc -p tsconfig.build.json && tsc -p tsconfig.esm.json", 9 | "test": "jest ./src/amm/tests/*.test.ts --runInBand" 10 | }, 11 | "files": [ 12 | "dist" 13 | ], 14 | "dependencies": { 15 | "@coral-xyz/anchor": "0.29.0", 16 | "@coral-xyz/borsh": "0.29.0", 17 | "@mercurial-finance/token-math": "6.0.0", 18 | "@mercurial-finance/vault-sdk": "^2.2.1", 19 | "@metaplex-foundation/mpl-token-metadata": "~2.13.0", 20 | "@meteora-ag/m3m3": "1.0.10", 21 | "@meteora-ag/vault-sdk": "2.3.1", 22 | "@project-serum/anchor": "^0.24.2", 23 | "@solana/buffer-layout": "^3 || ^4", 24 | "@solana/spl-token": "^0.4.6", 25 | "@solana/web3.js": "1.98.0", 26 | "bn-sqrt": "^1.0.0", 27 | "bn.js": "5.2.1", 28 | "decimal.js": "^10.4.1", 29 | "dotenv": "^16.0.1", 30 | "invariant": "^2.2.4" 31 | }, 32 | "devDependencies": { 33 | "@tsconfig/recommended": "^1.0.1", 34 | "@types/bn.js": "^5.1.0", 35 | "@types/chai": "^4.3.1", 36 | "@types/invariant": "^2.2.35", 37 | "@types/jest": "^27.5.1", 38 | "@types/mocha": "^9.1.1", 39 | "chai": "^4.3.6", 40 | "jest": "^28.1.0", 41 | "mocha": "^10.0.0", 42 | "ts-jest": "^28.0.2", 43 | "ts-mocha": "^10.0.0", 44 | "typescript": "^5.5.4" 45 | }, 46 | "peerDependencies": { 47 | "@solana/buffer-layout": "^3 || ^4" 48 | }, 49 | "resolutions": { 50 | "@solana/buffer-layout": "^4" 51 | }, 52 | "directories": { 53 | "test": "tests" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/MeteoraAg/dynamic-amm-sdk.git" 58 | }, 59 | "keywords": [ 60 | "mercurial", 61 | "finance", 62 | "dynamic", 63 | "amm", 64 | "sdk", 65 | "solana" 66 | ], 67 | "author": "meteora-ag", 68 | "license": "MIT", 69 | "bugs": { 70 | "url": "https://github.com/MeteoraAg/dynamic-amm-sdk/issues" 71 | }, 72 | "homepage": "https://github.com/meteora-ag/mercurial-dynamic-amm-sdk#readme" 73 | } 74 | -------------------------------------------------------------------------------- /ts-client/src/amm/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { BN } from 'bn.js'; 3 | 4 | export const ERROR = { 5 | POOL_NOT_LOAD: 'Pool not loaded', 6 | INVALID_MINT: 'Invalid mint', 7 | INVALID_ACCOUNT: 'Account not found', 8 | }; 9 | 10 | export const PROGRAM_ID = 'Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB'; 11 | export const STAGING_PROGRAM_ID = 'ammbh4CQztZ6txJ8AaQgPsWjd6o7GhmvopS2JAo5bCB'; 12 | 13 | export const DEVNET_POOL = Object.freeze({ 14 | USDT_USDC: new PublicKey('BAHscmu1NncGS7t4rc5gSBPv1UFEMkvLaon1Ahdd5rHi'), 15 | USDT_SOL: new PublicKey('Bgf1Sy5kfeDgib4go4NgzHuZwek8wE8NZus56z6uizzi'), 16 | SOL_MSOL: new PublicKey('2rkn2yM4wJcHPV57T8fPWeBksrfSpiNZoEjRgjtxNDEQ'), 17 | }); 18 | 19 | export const MAINNET_POOL = Object.freeze({ 20 | USDT_USDC: new PublicKey('32D4zRxNc1EssbJieVHfPhZM3rH6CzfUPrWUuWxD9prG'), 21 | USDC_SOL: new PublicKey('5yuefgbJJpmFNK2iiYbLSpv1aZXq7F9AUKkZKErTYCvs'), 22 | SOL_STSOL: new PublicKey('7EJSgV2pthhDfb4UiER9vzTqe2eojei9GEQAQnkqJ96e'), 23 | SOL_MSOL: new PublicKey('HcjZvfeSNJbNkfLD4eEcRBr96AD3w1GpmMppaeRZf7ur'), 24 | }); 25 | 26 | // Extra accounts for depeg pools. Might add more addresses in the future when more different types of pools are being added 27 | export const CURVE_TYPE_ACCOUNTS = { 28 | // Stake account of Marinade finance. Used to retrieve mSol virtual price 29 | marinade: new PublicKey('8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC'), 30 | // Stake account of Solana Lido. Used to retrieve stSol virtual price 31 | lido: new PublicKey('49Yi1TKkNyYjPAFdR9LBvoHcUjuPX4Df5T5yv39w2XTn'), 32 | }; 33 | 34 | export const SEEDS = Object.freeze({ 35 | APY: 'apy', 36 | FEE: 'fee', 37 | LP_MINT: 'lp_mint', 38 | LOCK_ESCROW: 'lock_escrow', 39 | }); 40 | 41 | export const VAULT_BASE_KEY = new PublicKey('HWzXGcGHy4tcpYfaRDCyLNzXqBTv3E6BttpCH2vJxArv'); 42 | 43 | export const POOL_BASE_KEY = new PublicKey('H9NnqW5Thn9dUzW3DRXe2xDhKjwZd4qbjngnZwEvnDuC'); 44 | 45 | export const DEFAULT_SLIPPAGE = 1; 46 | 47 | export const UNLOCK_AMOUNT_BUFFER = 0.998; 48 | 49 | export const VIRTUAL_PRICE_PRECISION = new BN(100_000_000); 50 | 51 | export const PERMISSIONLESS_AMP = new BN(100); 52 | 53 | export const FEE_OWNER = new PublicKey('6WaLrrRfReGKBYUSkmx2K6AuT21ida4j8at2SUiZdXu8'); 54 | 55 | export const CONSTANT_PRODUCT_DEFAULT_TRADE_FEE_BPS = 25; 56 | 57 | export const CONSTANT_PRODUCT_ALLOWED_TRADE_FEE_BPS = [25, 100, 400, 600]; 58 | 59 | export const STABLE_SWAP_DEFAULT_TRADE_FEE_BPS = 1; 60 | 61 | export const STABLE_SWAP_ALLOWED_TRADE_FEE_BPS = [1, 4, 10, 100]; 62 | 63 | export const METAPLEX_PROGRAM = new PublicKey('metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'); 64 | 65 | export const U64_MAX = new BN('18446744073709551615'); // max amount in program side 66 | -------------------------------------------------------------------------------- /ts-client/src/amm/curve/constant-product.ts: -------------------------------------------------------------------------------- 1 | import sqrt from 'bn-sqrt'; 2 | import { BN } from '@coral-xyz/anchor'; 3 | import { getPriceImpact, OutResult, SwapCurve, TradeDirection } from '.'; 4 | import { PoolFees } from '../types'; 5 | 6 | // Typescript implementation of https://github.com/solana-labs/solana-program-library/blob/master/libraries/math/src/checked_ceil_div.rs#L29 7 | function ceilDiv(lhs: BN, rhs: BN) { 8 | let quotient = lhs.div(rhs); 9 | // Avoid dividing a small number by a big one and returning 1, and instead 10 | // fail. 11 | if (quotient.eq(new BN(0))) { 12 | throw new Error('ceilDiv result in zero'); 13 | } 14 | 15 | let remainder = lhs.mod(rhs); 16 | 17 | if (remainder.gt(new BN(0))) { 18 | quotient = quotient.add(new BN(1)); 19 | rhs = lhs.div(quotient); 20 | remainder = lhs.mod(quotient); 21 | if (remainder.gt(new BN(0))) { 22 | rhs = rhs.add(new BN(1)); 23 | } 24 | } 25 | 26 | return [quotient, rhs]; 27 | } 28 | 29 | export class ConstantProductSwap implements SwapCurve { 30 | constructor() { } 31 | 32 | private computeOutAmountWithoutSlippage(sourceAmount: BN, swapSourceAmount: BN, swapDestinationAmount: BN): BN { 33 | return sourceAmount.mul(swapDestinationAmount).div(swapSourceAmount); 34 | } 35 | 36 | // Typescript implementation of https://github.com/solana-labs/solana-program-library/blob/master/token-swap/program/src/curve/constant_product.rs#L27 37 | computeOutAmount( 38 | sourceAmount: BN, 39 | swapSourceAmount: BN, 40 | swapDestinationAmount: BN, 41 | _tradeDirection: TradeDirection, 42 | ): OutResult { 43 | let invariant = swapSourceAmount.mul(swapDestinationAmount); 44 | let [newSwapDestinationAmount, _newSwapSourceAmount] = ceilDiv(invariant, swapSourceAmount.add(sourceAmount)); 45 | let destinationAmountSwapped = swapDestinationAmount.sub(newSwapDestinationAmount); 46 | if (destinationAmountSwapped.eq(new BN(0))) { 47 | throw new Error('Swap result in zero'); 48 | } 49 | const destinationAmountWithoutSlippage = this.computeOutAmountWithoutSlippage( 50 | sourceAmount, 51 | swapSourceAmount, 52 | swapDestinationAmount, 53 | ); 54 | 55 | return { 56 | outAmount: destinationAmountSwapped, 57 | priceImpact: getPriceImpact(destinationAmountSwapped, destinationAmountWithoutSlippage), 58 | }; 59 | } 60 | computeD(tokenAAmount: BN, tokenBAmount: BN): BN { 61 | return sqrt(tokenAAmount.mul(tokenBAmount)); 62 | } 63 | computeInAmount( 64 | destAmount: BN, 65 | swapSourceAmount: BN, 66 | swapDestinationAmount: BN, 67 | _tradeDirection: TradeDirection, 68 | ): BN { 69 | let invariant = swapSourceAmount.mul(swapDestinationAmount); 70 | let [newSwapSourceAmount, _newSwapDestinationAmount] = ceilDiv(invariant, swapDestinationAmount.sub(destAmount)); 71 | let sourceAmount = newSwapSourceAmount.sub(swapSourceAmount); 72 | 73 | if (sourceAmount.eq(new BN(0))) { 74 | throw new Error('Swap result in zero'); 75 | } 76 | return sourceAmount; 77 | } 78 | computeImbalanceDeposit( 79 | _depositAAmount: BN, 80 | _depositBAmount: BN, 81 | _swapTokenAAmount: BN, 82 | _swapTokenBAmount: BN, 83 | _lpSupply: BN, 84 | _fees: PoolFees, 85 | ): BN { 86 | throw new Error('UnsupportedOperation'); 87 | } 88 | 89 | computeWithdrawOne( 90 | _lpAmount: BN, 91 | _lpSupply: BN, 92 | _swapTokenAAmount: BN, 93 | _swapTokenBAmount: BN, 94 | _fees: PoolFees, 95 | _tradeDirection: TradeDirection, 96 | ): BN { 97 | throw new Error('UnsupportedOperation'); 98 | } 99 | 100 | getRemainingAccounts() { 101 | return []; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /ts-client/src/amm/curve/index.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from '@solana/web3.js'; 2 | import { BN } from '@coral-xyz/anchor'; 3 | import Decimal from 'decimal.js'; 4 | import { Depeg, PoolFees } from '../types'; 5 | 6 | export interface OutResult { 7 | outAmount: BN; 8 | priceImpact: Decimal; 9 | } 10 | 11 | export interface SwapCurve { 12 | depeg?: Depeg; 13 | 14 | computeOutAmount( 15 | sourceAmount: BN, 16 | swapSourceAmount: BN, 17 | swapDestinationAmount: BN, 18 | tradeDirection: TradeDirection, 19 | ): OutResult; 20 | 21 | computeD(tokenAAmount: BN, tokenBAmount: BN): BN; 22 | 23 | computeInAmount(destAmount: BN, swapSourceAmount: BN, swapDestinationAmount: BN, tradeDirection: TradeDirection): BN; 24 | 25 | computeImbalanceDeposit( 26 | depositAAmount: BN, 27 | depositBAmount: BN, 28 | swapTokenAAmount: BN, 29 | swapTokenBAmount: BN, 30 | lpSupply: BN, 31 | fees: PoolFees, 32 | ): BN; 33 | 34 | computeWithdrawOne( 35 | lpAmount: BN, 36 | lpSupply: BN, 37 | swapTokenAAmount: BN, 38 | swapTokenBAmount: BN, 39 | fees: PoolFees, 40 | tradeDirection: TradeDirection, 41 | ): BN; 42 | 43 | getRemainingAccounts(): Array<{ 44 | pubkey: PublicKey; 45 | isWritable: boolean; 46 | isSigner: boolean; 47 | }>; 48 | } 49 | 50 | export enum TradeDirection { 51 | AToB, 52 | BToA, 53 | } 54 | 55 | export const getPriceImpact = (amount: BN, amountWithoutSlippage: BN): Decimal => { 56 | const diff = amountWithoutSlippage.sub(amount); 57 | return new Decimal(diff.toString()).div(new Decimal(amountWithoutSlippage.toString())); 58 | }; 59 | 60 | export * from './stable-swap'; 61 | export * from './constant-product'; 62 | -------------------------------------------------------------------------------- /ts-client/src/amm/curve/stable-swap-math/calculator.ts: -------------------------------------------------------------------------------- 1 | import { Fraction, ONE, Percent, ZERO } from '@mercurial-finance/token-math'; 2 | 3 | export { Fraction, ONE, Percent, ZERO } from '@mercurial-finance/token-math'; 4 | 5 | const N_COINS = BigInt(2); // n 6 | 7 | const abs = (a: bigint): bigint => { 8 | if (a > ZERO) { 9 | return a; 10 | } 11 | return -a; 12 | }; 13 | 14 | // maximum iterations of newton's method approximation 15 | const MAX_ITERS = 20; 16 | 17 | /** 18 | * Compute the StableSwap invariant 19 | * @param ampFactor Amplification coefficient (A) 20 | * @param amountA Swap balance of token A 21 | * @param amountB Swap balance of token B 22 | * Reference: https://github.com/curvefi/curve-contract/blob/7116b4a261580813ef057887c5009e22473ddb7d/tests/simulation.py#L31 23 | */ 24 | export const computeD = (ampFactor: bigint, amountA: bigint, amountB: bigint): bigint => { 25 | const Ann = ampFactor * N_COINS; // A*n^n 26 | const S = amountA + amountB; // sum(x_i), a.k.a S 27 | if (S === ZERO) { 28 | return ZERO; 29 | } 30 | 31 | let dPrev = ZERO; 32 | let d = S; 33 | 34 | for (let i = 0; abs(d - dPrev) > ONE && i < MAX_ITERS; i++) { 35 | dPrev = d; 36 | let dP = d; 37 | dP = (dP * d) / (amountA * N_COINS); 38 | dP = (dP * d) / (amountB * N_COINS); 39 | 40 | const dNumerator = d * (Ann * S + dP * N_COINS); 41 | const dDenominator = d * (Ann - ONE) + dP * (N_COINS + ONE); 42 | d = dNumerator / dDenominator; 43 | } 44 | 45 | return d; 46 | }; 47 | 48 | /** 49 | * Compute Y amount in respect to X on the StableSwap curve 50 | * @param ampFactor Amplification coefficient (A) 51 | * @param x The quantity of underlying asset 52 | * @param d StableSwap invariant 53 | * Reference: https://github.com/curvefi/curve-contract/blob/7116b4a261580813ef057887c5009e22473ddb7d/tests/simulation.py#L55 54 | */ 55 | export const computeY = (ampFactor: bigint, x: bigint, d: bigint): bigint => { 56 | const Ann = ampFactor * N_COINS; // A*n^n 57 | // sum' = prod' = x 58 | const b = x + d / Ann - d; // b = sum' - (A*n**n - 1) * D / (A * n**n) 59 | // c = D ** (n + 1) / (n ** (2 * n) * prod' * A) 60 | const c = (d * d * d) / (N_COINS * (N_COINS * (x * Ann))); 61 | 62 | let yPrev = ZERO; 63 | let y = d; 64 | for (let i = 0; i < MAX_ITERS && abs(y - yPrev) > ONE; i++) { 65 | yPrev = y; 66 | y = (y * y + c) / (N_COINS * y + b); 67 | } 68 | 69 | return y; 70 | }; 71 | 72 | export type Fees = { 73 | trade: Percent; 74 | withdraw: Percent; 75 | adminTrade: Percent; 76 | adminWithdraw: Percent; 77 | }; 78 | 79 | /** 80 | * Compute normalized fee for symmetric/asymmetric deposits/withdraws 81 | */ 82 | export const normalizedTradeFee = ({ trade }: Fees, n_coins: bigint, amount: bigint): Fraction => { 83 | const adjustedTradeFee = new Fraction(n_coins, (n_coins - ONE) * BigInt(4)); 84 | return new Fraction(amount, 1).multiply(trade).multiply(adjustedTradeFee); 85 | }; 86 | -------------------------------------------------------------------------------- /ts-client/src/amm/curve/stable-swap-math/index.ts: -------------------------------------------------------------------------------- 1 | export * from './calculator'; -------------------------------------------------------------------------------- /ts-client/src/amm/error.ts: -------------------------------------------------------------------------------- 1 | import { IDL } from '../amm/idl'; 2 | import { AnchorError } from '@coral-xyz/anchor'; 3 | import { PROGRAM_ID } from './constants'; 4 | 5 | type Codes = (typeof IDL.errors)[number]['code']; 6 | 7 | class DynamicAmmError extends Error { 8 | public errorCode: number; 9 | public errorName: string; 10 | public errorMessage: string; 11 | 12 | constructor(error: object | Codes) { 13 | let _errorCode = 0; 14 | let _errorName = 'Something went wrong'; 15 | let _errorMessage = 'Something went wrong'; 16 | 17 | if (error instanceof Error) { 18 | const anchorError = AnchorError.parse(JSON.parse(JSON.stringify(error)).transactionLogs as string[]); 19 | 20 | if (anchorError?.program.toBase58() === PROGRAM_ID) { 21 | _errorCode = anchorError.error.errorCode.number; 22 | _errorName = anchorError.error.errorCode.code; 23 | _errorMessage = anchorError.error.errorMessage; 24 | } 25 | } else { 26 | const idlError = IDL.errors.find((err) => err.code === error); 27 | 28 | if (idlError) { 29 | _errorCode = idlError.code; 30 | _errorName = idlError.name; 31 | _errorMessage = idlError.msg; 32 | } 33 | } 34 | 35 | super(_errorMessage); 36 | 37 | this.errorCode = _errorCode; 38 | this.errorName = _errorName; 39 | this.errorMessage = _errorMessage; 40 | } 41 | } 42 | 43 | export default DynamicAmmError; 44 | -------------------------------------------------------------------------------- /ts-client/src/amm/tests/bundlePoolQuote.test.ts: -------------------------------------------------------------------------------- 1 | import VaultImpl from '@meteora-ag/vault-sdk'; 2 | import { AccountLayout, MintLayout, NATIVE_MINT, RawAccount, RawMint } from '@solana/spl-token'; 3 | import { clusterApiUrl, Connection, PublicKey, SYSVAR_CLOCK_PUBKEY } from '@solana/web3.js'; 4 | import { calculateSwapQuoteForGoingToCreateMemecoinPool } from '../utils'; 5 | import { BN } from 'bn.js'; 6 | import { Clock, ClockLayout } from '../types'; 7 | import AmmImpl from '..'; 8 | 9 | describe('Bundle pool quote', () => { 10 | const connection = new Connection(clusterApiUrl('mainnet-beta'), 'confirmed'); 11 | const quoteMint = NATIVE_MINT; 12 | 13 | it('Able to quote', async () => { 14 | const quoteDynamicVault = await VaultImpl.create(connection, quoteMint); 15 | const inAmount = new BN('50000'); // 0.00005 SOL 16 | const tokenAAmount = new BN('1000000000000000'); // 1000000000 memecoin 17 | const tokenBAmount = new BN('50000'); // 0.00005 SOL 18 | 19 | // Memecoin config 20 | const config = await AmmImpl.getPoolConfig( 21 | connection, 22 | new PublicKey('FiENCCbPi3rFh5pW2AJ59HC53yM32eLaCjMKxRqanKFJ'), 23 | ); 24 | 25 | const [vaultReserveAccount, vaultLpMintAccount, clockAccount] = await connection.getMultipleAccountsInfo([ 26 | quoteDynamicVault.vaultState.tokenVault, 27 | quoteDynamicVault.vaultState.lpMint, 28 | SYSVAR_CLOCK_PUBKEY, 29 | ]); 30 | const vaultReserve: RawAccount = AccountLayout.decode(vaultReserveAccount!.data); 31 | const vaultMint: RawMint = MintLayout.decode(vaultLpMintAccount!.data); 32 | const clock: Clock = ClockLayout.decode(clockAccount!.data); 33 | 34 | const { amountOut } = calculateSwapQuoteForGoingToCreateMemecoinPool( 35 | inAmount, 36 | tokenAAmount, 37 | tokenBAmount, 38 | false, 39 | { 40 | tradeFeeNumerator: config.poolFees.tradeFeeNumerator, 41 | tradeFeeDenominator: config.poolFees.tradeFeeDenominator, 42 | protocolTradeFeeNumerator: config.poolFees.protocolTradeFeeNumerator, 43 | protocolTradeFeeDenominator: config.poolFees.protocolTradeFeeDenominator, 44 | }, 45 | { 46 | // Memecoin vault doesn't exists until we create the pool for it 47 | vaultA: undefined, 48 | vaultB: { 49 | vault: quoteDynamicVault.vaultState, 50 | reserve: new BN(vaultReserve.amount.toString()), 51 | lpSupply: new BN(vaultMint.supply.toString()), 52 | }, 53 | currentTime: clock.unixTimestamp.toNumber(), 54 | }, 55 | ); 56 | 57 | console.log('Out amount', amountOut.toString()); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /ts-client/src/amm/tests/decode.test.ts: -------------------------------------------------------------------------------- 1 | import { Connection, SYSVAR_CLOCK_PUBKEY } from '@solana/web3.js'; 2 | import { Clock, ClockLayout } from '../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/amm/tests/error.test.ts: -------------------------------------------------------------------------------- 1 | import AmmImpl from '..'; 2 | import { airDropSol, getOrCreateATA, mockWallet } from './utils'; 3 | import { 4 | CONSTANT_PRODUCT_DEFAULT_TRADE_FEE_BPS, 5 | DEFAULT_SLIPPAGE, 6 | MAINNET_POOL, 7 | STABLE_SWAP_DEFAULT_TRADE_FEE_BPS, 8 | } from '../constants'; 9 | import { Connection, PublicKey } from '@solana/web3.js'; 10 | import { AnchorProvider, BN } from '@coral-xyz/anchor'; 11 | import DynamicAmmError from '../error'; 12 | import { IDL } from '../idl'; 13 | import { createMint, getMint, Mint, mintTo } from '@solana/spl-token'; 14 | import { derivePoolAddress } from '../utils'; 15 | 16 | describe('Error parsing', () => { 17 | const connection = new Connection('http://127.0.0.1:8899', 'confirmed'); 18 | const provider = new AnchorProvider(connection, mockWallet, { 19 | commitment: connection.commitment, 20 | }); 21 | 22 | let USDT: PublicKey; 23 | let USDC: PublicKey; 24 | 25 | let usdtMint: Mint; 26 | let usdcMint: Mint; 27 | 28 | let mockWalletUsdtATA: PublicKey; 29 | let mockWalletUsdcATA: PublicKey; 30 | 31 | let usdtDecimal = 6; 32 | let usdcDecimal = 6; 33 | 34 | const usdtMultiplier = 10 ** usdtDecimal; 35 | const usdcMultiplier = 10 ** usdcDecimal; 36 | 37 | let stablePool: AmmImpl; 38 | let cpPool: AmmImpl; 39 | let lstPool: AmmImpl; 40 | 41 | beforeAll(async () => { 42 | await airDropSol(connection, mockWallet.publicKey, 10); 43 | 44 | [USDT, USDC] = await Promise.all( 45 | [usdtDecimal, usdcDecimal].map((decimal) => 46 | createMint(provider.connection, mockWallet.payer, mockWallet.publicKey, null, decimal), 47 | ), 48 | ); 49 | [mockWalletUsdtATA, mockWalletUsdcATA] = await Promise.all( 50 | [USDT, USDC].map((m) => getOrCreateATA(connection, m, mockWallet.publicKey, mockWallet.payer)), 51 | ); 52 | [usdtMint, usdcMint] = await Promise.all([USDT, USDC].map((mint) => getMint(connection, mint))); 53 | 54 | await mintTo( 55 | provider.connection, 56 | mockWallet.payer, 57 | USDT, 58 | mockWalletUsdtATA, 59 | mockWallet.payer.publicKey, 60 | 1000000 * usdtMultiplier, 61 | [], 62 | { 63 | commitment: 'confirmed', 64 | }, 65 | ); 66 | 67 | await mintTo( 68 | provider.connection, 69 | mockWallet.payer, 70 | USDC, 71 | mockWalletUsdcATA, 72 | mockWallet.payer.publicKey, 73 | 1000000 * usdcMultiplier, 74 | [], 75 | { 76 | commitment: 'confirmed', 77 | }, 78 | ); 79 | }); 80 | 81 | beforeAll(async () => { 82 | await airDropSol(connection, mockWallet.publicKey, 10); 83 | 84 | const usdtDepositAmount = new BN(10000 * usdtMultiplier); 85 | const usdcDepositAmount = new BN(10000 * usdcMultiplier); 86 | 87 | let tradeFeeBps = new BN(STABLE_SWAP_DEFAULT_TRADE_FEE_BPS); 88 | let transaction = await AmmImpl.createPermissionlessPool( 89 | connection, 90 | mockWallet.publicKey, 91 | USDT, 92 | USDC, 93 | usdtDepositAmount, 94 | usdcDepositAmount, 95 | true, 96 | tradeFeeBps, 97 | ); 98 | 99 | transaction.sign(mockWallet.payer); 100 | let txHash = await connection.sendRawTransaction(transaction.serialize()); 101 | await connection.confirmTransaction(txHash, 'finalized'); 102 | 103 | let poolKey = derivePoolAddress( 104 | connection, 105 | usdtMint.address, 106 | usdcMint.address, 107 | usdtMint.decimals, 108 | usdcMint.decimals, 109 | true, 110 | tradeFeeBps, 111 | ); 112 | stablePool = await AmmImpl.create(connection, poolKey); 113 | 114 | tradeFeeBps = new BN(CONSTANT_PRODUCT_DEFAULT_TRADE_FEE_BPS); 115 | transaction = await AmmImpl.createPermissionlessPool( 116 | connection, 117 | mockWallet.publicKey, 118 | USDT, 119 | USDC, 120 | usdtDepositAmount, 121 | usdcDepositAmount, 122 | false, 123 | tradeFeeBps, 124 | ); 125 | 126 | transaction.sign(mockWallet.payer); 127 | txHash = await connection.sendRawTransaction(transaction.serialize()); 128 | await connection.confirmTransaction(txHash, 'finalized'); 129 | 130 | poolKey = derivePoolAddress( 131 | connection, 132 | usdtMint.address, 133 | usdcMint.address, 134 | usdtMint.decimals, 135 | usdcMint.decimals, 136 | false, 137 | tradeFeeBps, 138 | ); 139 | cpPool = await AmmImpl.create(connection, poolKey); 140 | 141 | lstPool = await AmmImpl.create(connection, MAINNET_POOL.SOL_MSOL); 142 | }); 143 | 144 | test('Should throw slippage error', async () => { 145 | const inAmountBLamport = new BN(0.1 * 10 ** cpPool.tokenBMint.decimals); 146 | 147 | const { tokenAInAmount: cpTokenAInAmount, tokenBInAmount: cpTokenBInAmount } = cpPool.getDepositQuote( 148 | new BN(0), 149 | inAmountBLamport, 150 | true, 151 | DEFAULT_SLIPPAGE, 152 | ); 153 | 154 | const cpDepositTx = await cpPool.deposit( 155 | mockWallet.publicKey, 156 | cpTokenAInAmount, 157 | cpTokenBInAmount, 158 | new BN(Number.MAX_SAFE_INTEGER), 159 | ); 160 | 161 | await provider.sendAndConfirm(cpDepositTx).catch((error) => { 162 | const ammError = new DynamicAmmError(error); 163 | 164 | expect(ammError.errorCode).toBe(IDL.errors[4].code); 165 | }); 166 | 167 | const inLamportAmount = new BN(1 * 10 ** lstPool.tokenAMint.decimals); 168 | const { tokenAInAmount: depegTokenAInAmount, tokenBInAmount: depegTokenBInAmount } = lstPool.getDepositQuote( 169 | inLamportAmount, 170 | new BN(0), 171 | false, 172 | DEFAULT_SLIPPAGE, 173 | ); 174 | 175 | const lstPoolDepositTx = await lstPool.deposit( 176 | mockWallet.publicKey, 177 | depegTokenAInAmount, 178 | depegTokenBInAmount, 179 | new BN(Number.MAX_SAFE_INTEGER), 180 | ); 181 | 182 | await provider.sendAndConfirm(lstPoolDepositTx).catch((error) => { 183 | const ammError = new DynamicAmmError(error); 184 | expect(ammError.errorCode).toBe(IDL.errors[4].code); 185 | }); 186 | 187 | const { tokenAInAmount: stableTokenAInAmount, tokenBInAmount: stableTokenBInAmount } = stablePool.getDepositQuote( 188 | new BN(0), 189 | inAmountBLamport, 190 | true, 191 | DEFAULT_SLIPPAGE, 192 | ); 193 | 194 | const stablePoolDepositTx = await stablePool.deposit( 195 | mockWallet.publicKey, 196 | stableTokenAInAmount, 197 | stableTokenBInAmount, 198 | new BN(Number.MAX_SAFE_INTEGER), 199 | ); 200 | 201 | await provider.sendAndConfirm(stablePoolDepositTx).catch((error) => { 202 | const ammError = new DynamicAmmError(error); 203 | 204 | expect(ammError.errorCode).toBe(IDL.errors[4].code); 205 | }); 206 | }); 207 | }); 208 | -------------------------------------------------------------------------------- /ts-client/src/amm/tests/initializeCustomizablePermissionlessConstantProductPool.test.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider } from '@coral-xyz/anchor'; 2 | import { Connection, PublicKey } from '@solana/web3.js'; 3 | import { airDropSol, getOrCreateATA, mockWallet, wrapSol } from './utils'; 4 | import { createMint, mintTo, NATIVE_MINT } from '@solana/spl-token'; 5 | import AmmImpl from '..'; 6 | import { BN } from 'bn.js'; 7 | import { ActivationType } from '../types'; 8 | import { createProgram, deriveCustomizablePermissionlessConstantProductPoolAddress } from '../utils'; 9 | 10 | const connection = new Connection('http://127.0.0.1:8899', 'confirmed'); 11 | const provider = new AnchorProvider(connection, mockWallet, { 12 | commitment: connection.commitment, 13 | }); 14 | 15 | describe('Initialize customizable permissionless constant product pool', () => { 16 | let MEME: PublicKey; 17 | 18 | let memeDecimal = 9; 19 | 20 | let mockWalletMemeATA: PublicKey; 21 | let mockWalletSolATA: PublicKey; 22 | 23 | const memeMultiplier = 10 ** memeDecimal; 24 | 25 | beforeAll(async () => { 26 | await airDropSol(connection, mockWallet.publicKey, 10); 27 | 28 | MEME = await createMint(provider.connection, mockWallet.payer, mockWallet.publicKey, null, memeDecimal); 29 | 30 | mockWalletMemeATA = await getOrCreateATA(connection, MEME, mockWallet.publicKey, mockWallet.payer); 31 | mockWalletSolATA = await getOrCreateATA(connection, NATIVE_MINT, mockWallet.publicKey, mockWallet.payer); 32 | 33 | await mintTo( 34 | provider.connection, 35 | mockWallet.payer, 36 | MEME, 37 | mockWalletMemeATA, 38 | mockWallet.payer.publicKey, 39 | 1000000 * memeMultiplier, 40 | [], 41 | { 42 | commitment: 'confirmed', 43 | }, 44 | ); 45 | 46 | await wrapSol(connection, new BN(1_000_000), mockWallet.payer); 47 | }); 48 | 49 | test('Initialize customizable CP pool', async () => { 50 | const tokenAAmount = new BN(1_000_000); 51 | const tokenBAmount = new BN(1_000_000); 52 | 53 | const initializeTx = await AmmImpl.createCustomizablePermissionlessConstantProductPool( 54 | connection, 55 | mockWallet.publicKey, 56 | MEME, 57 | NATIVE_MINT, 58 | tokenAAmount, 59 | tokenBAmount, 60 | { 61 | // Denominator is default to 100_000 62 | tradeFeeNumerator: 2500, 63 | activationType: ActivationType.Timestamp, 64 | activationPoint: null, 65 | hasAlphaVault: false, 66 | padding: Array(90).fill(0), 67 | }, 68 | ); 69 | 70 | initializeTx.sign(mockWallet.payer); 71 | // 1124 bytes 72 | const txHash = await connection.sendRawTransaction(initializeTx.serialize()); 73 | await connection.confirmTransaction(txHash, 'finalized'); 74 | 75 | const poolKey = deriveCustomizablePermissionlessConstantProductPoolAddress( 76 | MEME, 77 | NATIVE_MINT, 78 | createProgram(connection).ammProgram.programId, 79 | ); 80 | 81 | const pool = await AmmImpl.create(connection, poolKey); 82 | expect(pool.feeBps.eq(new BN(250))).toBeTruthy(); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /ts-client/src/amm/tests/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { BN, Wallet } from '@coral-xyz/anchor'; 2 | import { getOrCreateAssociatedTokenAccount, NATIVE_MINT } from '@solana/spl-token'; 3 | import { Connection, Keypair, LAMPORTS_PER_SOL, PublicKey, Transaction } from '@solana/web3.js'; 4 | import { getAssociatedTokenAccount, wrapSOLInstruction } from '../../utils'; 5 | 6 | export const wrapSol = async (connection: Connection, amount: BN, user: Keypair) => { 7 | const userAta = getAssociatedTokenAccount(NATIVE_MINT, user.publicKey); 8 | const wrapSolIx = wrapSOLInstruction(user.publicKey, userAta, BigInt(amount.toString())); 9 | const latestBlockHash = await connection.getLatestBlockhash(); 10 | const tx = new Transaction({ 11 | feePayer: user.publicKey, 12 | ...latestBlockHash, 13 | }).add(...wrapSolIx); 14 | tx.sign(user); 15 | const txHash = await connection.sendRawTransaction(tx.serialize()); 16 | await connection.confirmTransaction(txHash, 'finalized'); 17 | }; 18 | 19 | export const airDropSol = async (connection: Connection, publicKey: PublicKey, amount = 1) => { 20 | try { 21 | const airdropSignature = await connection.requestAirdrop(publicKey, amount * LAMPORTS_PER_SOL); 22 | const latestBlockHash = await connection.getLatestBlockhash(); 23 | await connection.confirmTransaction( 24 | { 25 | blockhash: latestBlockHash.blockhash, 26 | lastValidBlockHeight: latestBlockHash.lastValidBlockHeight, 27 | signature: airdropSignature, 28 | }, 29 | connection.commitment, 30 | ); 31 | } catch (error) { 32 | console.error(error); 33 | throw error; 34 | } 35 | }; 36 | 37 | export const airDropSolIfBalanceNotEnough = async (connection: Connection, publicKey: PublicKey, balance = 1) => { 38 | const walletBalance = await connection.getBalance(publicKey); 39 | if (walletBalance < balance * LAMPORTS_PER_SOL) { 40 | await airDropSol(connection, publicKey); 41 | } 42 | }; 43 | 44 | export const getOrCreateATA = async (connection: Connection, mint: PublicKey, owner: PublicKey, payer: Keypair) => { 45 | const ata = await getOrCreateAssociatedTokenAccount(connection, payer, mint, owner, true); 46 | return ata.address; 47 | }; 48 | 49 | export const mockWallet = new Wallet(Keypair.generate()); 50 | 51 | // export const MAINNET = { 52 | // connection: new Connection(process.env.MAINNET_RPC_ENDPOINT as string), 53 | // cluster: 'mainnet-beta', 54 | // }; 55 | 56 | // export const DEVNET = { 57 | // connection: new Connection('https://api.devnet.solana.com/', { 58 | // commitment: 'confirmed', 59 | // }), 60 | // cluster: 'devnet', 61 | // }; 62 | -------------------------------------------------------------------------------- /ts-client/src/examples/create_pool_and_lock_liquidity.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey, Keypair } from '@solana/web3.js'; 2 | import BN from 'bn.js'; 3 | import { Wallet, AnchorProvider } from '@coral-xyz/anchor'; 4 | import AmmImpl from '../amm'; 5 | import { PROGRAM_ID, SEEDS } from '../amm/constants'; 6 | import { 7 | getAssociatedTokenAccount, 8 | derivePoolAddressWithConfig as deriveConstantProductPoolAddressWithConfig, 9 | } from '../amm/utils'; 10 | import fs from 'fs'; 11 | 12 | function loadKeypairFromFile(filename: string): Keypair { 13 | const secret = JSON.parse(fs.readFileSync(filename).toString()) as number[]; 14 | const secretKey = Uint8Array.from(secret); 15 | return Keypair.fromSecretKey(secretKey); 16 | } 17 | 18 | const mainnetConnection = new Connection('https://api.devnet.solana.com'); 19 | const payerKP = loadKeypairFromFile('~/.config/solana/id.json'); 20 | const payerWallet = new Wallet(payerKP); 21 | console.log('payer %s', payerKP.publicKey); 22 | 23 | const provider = new AnchorProvider(mainnetConnection, payerWallet, { 24 | commitment: 'confirmed', 25 | }); 26 | 27 | type AllocationByPercentage = { 28 | address: PublicKey; 29 | percentage: number; 30 | }; 31 | 32 | type AllocationByAmount = { 33 | address: PublicKey; 34 | amount: BN; 35 | }; 36 | 37 | function fromAllocationsToAmount(lpAmount: BN, allocations: AllocationByPercentage[]): AllocationByAmount[] { 38 | const sumPercentage = allocations.reduce((partialSum, a) => partialSum + a.percentage, 0); 39 | if (sumPercentage === 0) { 40 | throw Error('sumPercentage is zero'); 41 | } 42 | 43 | let amounts: AllocationByAmount[] = []; 44 | let sum = new BN(0); 45 | for (let i = 0; i < allocations.length - 1; i++) { 46 | const amount = lpAmount.mul(new BN(allocations[i].percentage)).div(new BN(sumPercentage)); 47 | sum = sum.add(amount); 48 | amounts.push({ 49 | address: allocations[i].address, 50 | amount, 51 | }); 52 | } 53 | // the last wallet get remaining amount 54 | amounts.push({ 55 | address: allocations[allocations.length - 1].address, 56 | amount: lpAmount.sub(sum), 57 | }); 58 | return amounts; 59 | } 60 | 61 | async function createPoolAndLockLiquidity( 62 | tokenAMint: PublicKey, 63 | tokenBMint: PublicKey, 64 | tokenAAmount: BN, 65 | tokenBAmount: BN, 66 | config: PublicKey, 67 | allocations: AllocationByPercentage[], 68 | ) { 69 | const programID = new PublicKey(PROGRAM_ID); 70 | const poolPubkey = deriveConstantProductPoolAddressWithConfig(tokenAMint, tokenBMint, config, programID); 71 | // Create the pool 72 | console.log('create pool %s', poolPubkey); 73 | let transactions = await AmmImpl.createPermissionlessConstantProductPoolWithConfig( 74 | provider.connection, 75 | payerWallet.publicKey, 76 | tokenAMint, 77 | tokenBMint, 78 | tokenAAmount, 79 | tokenBAmount, 80 | config, 81 | ); 82 | for (const transaction of transactions) { 83 | transaction.sign(payerWallet.payer); 84 | const txHash = await provider.connection.sendRawTransaction(transaction.serialize()); 85 | await provider.connection.confirmTransaction(txHash, 'finalized'); 86 | console.log('transaction %s', txHash); 87 | } 88 | 89 | // Create escrow and lock liquidity 90 | const [lpMint] = PublicKey.findProgramAddressSync([Buffer.from(SEEDS.LP_MINT), poolPubkey.toBuffer()], programID); 91 | const payerPoolLp = await getAssociatedTokenAccount(lpMint, payerWallet.publicKey); 92 | const payerPoolLpBalance = (await provider.connection.getTokenAccountBalance(payerPoolLp)).value.amount; 93 | console.log('payerPoolLpBalance %s', payerPoolLpBalance.toString()); 94 | 95 | let allocationByAmounts = fromAllocationsToAmount(new BN(payerPoolLpBalance), allocations); 96 | const pool = await AmmImpl.create(provider.connection, poolPubkey); 97 | for (const allocation of allocationByAmounts) { 98 | console.log('Lock liquidity %s', allocation.address.toString()); 99 | let transaction = await pool.lockLiquidity(allocation.address, allocation.amount, payerWallet.publicKey); 100 | transaction.sign(payerWallet.payer); 101 | const txHash = await provider.connection.sendRawTransaction(transaction.serialize()); 102 | await provider.connection.confirmTransaction(txHash, 'finalized'); 103 | console.log('transaction %s', txHash); 104 | } 105 | } 106 | 107 | /** 108 | * Example script to create a new pool and lock liquidity to it 109 | */ 110 | async function main() { 111 | // 1. Token A/B address of the pool. 112 | const tokenAMint = new PublicKey('BjhBG7jkHYMBMos2HtRdFrw8rvSguBe5c3a3EJYXhyUf'); 113 | const tokenBMint = new PublicKey('9KMeJp868Pdk8PrJEkwoAHMA1ctdxfVhe2TjeS4BcWjs'); 114 | 115 | // 2. Configuration address for the pool. It will decide the fees of the pool. 116 | const config = new PublicKey('21PjsfQVgrn56jSypUT5qXwwSjwKWvuoBCKbVZrgTLz4'); 117 | 118 | // 3. Allocation of the locked LP to multiple address. In the below example 119 | // 4sBMz7zmDWPzdEnECJW3NA9mEcNwkjYtVnL2KySaWYAf will get 80% of the fee of the locked liquidity 120 | // CVV5MxfwA24PsM7iuS2ddssYgySf5SxVJ8PpAwGN2yVy will get 20% of the fee of the locked liquidity 121 | let allocations = [ 122 | { 123 | address: new PublicKey('4sBMz7zmDWPzdEnECJW3NA9mEcNwkjYtVnL2KySaWYAf'), 124 | percentage: 80, 125 | }, 126 | { 127 | address: new PublicKey('CVV5MxfwA24PsM7iuS2ddssYgySf5SxVJ8PpAwGN2yVy'), 128 | percentage: 20, 129 | }, 130 | ]; 131 | 132 | // 4. Amount of token A and B to be deposited to the pool, and will be locked. 133 | let tokenAAmount = new BN(100_000); 134 | let tokenBAmount = new BN(500_000); 135 | 136 | await createPoolAndLockLiquidity(tokenAMint, tokenBMint, tokenAAmount, tokenBAmount, config, allocations); 137 | } 138 | 139 | main(); 140 | -------------------------------------------------------------------------------- /ts-client/src/examples/create_pool_with_authorized_config.ts: -------------------------------------------------------------------------------- 1 | import { Wallet } from '@coral-xyz/anchor'; 2 | import { Connection, Keypair, PublicKey } from '@solana/web3.js'; 3 | import BN from 'bn.js'; 4 | import fs from 'fs'; 5 | import AmmImpl from '../amm'; 6 | 7 | function loadKeypairFromFile(filename: string): Keypair { 8 | const secret = JSON.parse(fs.readFileSync(filename).toString()) as number[]; 9 | const secretKey = Uint8Array.from(secret); 10 | return Keypair.fromSecretKey(secretKey); 11 | } 12 | 13 | const connection = new Connection('https://api.devnet.solana.com'); 14 | const payerKP = loadKeypairFromFile('~/.config/solana/id.json'); 15 | const payerWallet = new Wallet(payerKP); 16 | console.log('payer %s', payerKP.publicKey); 17 | 18 | async function createPool( 19 | tokenAMint: PublicKey, 20 | tokenBMint: PublicKey, 21 | tokenAAmount: BN, 22 | tokenBAmount: BN, 23 | config: PublicKey, 24 | payer: Keypair, 25 | ) { 26 | const transactions = await AmmImpl.createPermissionlessConstantProductPoolWithConfig( 27 | connection, 28 | payer.publicKey, 29 | tokenAMint, 30 | tokenBMint, 31 | tokenAAmount, 32 | tokenBAmount, 33 | config, 34 | ); 35 | 36 | for (const transaction of transactions) { 37 | transaction.sign(payer); 38 | const txHash = await connection.sendRawTransaction(transaction.serialize()); 39 | await connection.confirmTransaction(txHash, 'finalized'); 40 | console.log('Transaction %s', txHash); 41 | } 42 | } 43 | 44 | async function main() { 45 | const tokenAMint = new PublicKey('BjhBG7jkHYMBMos2HtRdFrw8rvSguBe5c3a3EJYXhyUf'); 46 | const tokenBMint = new PublicKey('9KMeJp868Pdk8PrJEkwoAHMA1ctdxfVhe2TjeS4BcWjs'); 47 | 48 | // Retrieve config accounts where authorized pool creator key is the payerKP 49 | const configs = await AmmImpl.getPoolConfigsWithPoolCreatorAuthority(connection, payerWallet.publicKey); 50 | 51 | // Select config which the fees fit your requirement. Please contact meteora team if you're not whitelisted. 52 | const config = configs[0]; 53 | 54 | // Create pool and deposit 55 | const tokenADepositAmount = new BN(1000000); 56 | const tokenBDepositAmount = new BN(1000000); 57 | 58 | // Create pool 59 | await createPool( 60 | tokenAMint, 61 | tokenBMint, 62 | tokenADepositAmount, 63 | tokenBDepositAmount, 64 | config.publicKey, 65 | payerWallet.payer, 66 | ); 67 | } 68 | 69 | main(); 70 | -------------------------------------------------------------------------------- /ts-client/src/examples/get_claimable_fee.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey, Keypair } from '@solana/web3.js'; 2 | import { Wallet, AnchorProvider } from '@coral-xyz/anchor'; 3 | import AmmImpl from '../amm'; 4 | 5 | const mainnetConnection = new Connection('https://api.mainnet-beta.solana.com'); 6 | const provider = new AnchorProvider(mainnetConnection, new Wallet(Keypair.generate()), { 7 | commitment: 'confirmed', 8 | }); 9 | 10 | async function getClaimableFee(poolAddress: PublicKey, owner: PublicKey) { 11 | const pool = await AmmImpl.create(provider.connection, poolAddress); 12 | let result = await pool.getUserLockEscrow(owner); 13 | console.log('unClaimed: %s', result?.fee.unClaimed.lp?.toString()); 14 | console.log(result) 15 | } 16 | 17 | async function main() { 18 | // mainnet-beta, SOL-USDC 19 | const poolAddress = 'FRd5CJfLU2TDAUK2m3onvwqXs5md3y96Ad1RUMB5fyii'; 20 | const owner = '3CCocQighVbWdoav1Fhp6t2K6v7kWtUEd6Sp59UU77Vt'; 21 | await getClaimableFee(new PublicKey(poolAddress), new PublicKey(owner)); 22 | } 23 | 24 | main(); 25 | -------------------------------------------------------------------------------- /ts-client/src/examples/get_pool_info.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey, Keypair } from '@solana/web3.js'; 2 | import BN from 'bn.js'; 3 | import { Wallet, AnchorProvider, Program } from '@coral-xyz/anchor'; 4 | import AmmImpl from '../amm'; 5 | import { Amm as AmmIdl, IDL as AmmIDL } from '../amm/idl'; 6 | import { PROGRAM_ID } from '../amm/constants'; 7 | import fs from 'fs'; 8 | import os from 'os'; 9 | 10 | function loadKeypairFromFile(filename: string): Keypair { 11 | const secret = JSON.parse(fs.readFileSync(filename.replace('~', os.homedir)).toString()) as number[]; 12 | const secretKey = Uint8Array.from(secret); 13 | return Keypair.fromSecretKey(secretKey); 14 | } 15 | const payerKP = loadKeypairFromFile('~/.config/solana/id.json'); 16 | const payerWallet = new Wallet(payerKP); 17 | console.log('Wallet Address: %s \n', payerKP.publicKey); 18 | 19 | const mainnetConnection = new Connection('https://api.mainnet-beta.solana.com'); 20 | const provider = new AnchorProvider(mainnetConnection, payerWallet, { 21 | commitment: 'confirmed', 22 | }); 23 | 24 | async function getPoolInfo(poolAddress: PublicKey) { 25 | const pool = await AmmImpl.create(provider.connection, poolAddress); 26 | const poolInfo = pool.poolInfo; 27 | 28 | console.log('Pool Address: %s', poolAddress.toString()); 29 | const poolTokenAddress = await pool.getPoolTokenMint(); 30 | console.log('Pool LP Token Mint Address: %s', poolTokenAddress.toString()); 31 | const LockedLpAmount = await pool.getLockedLpAmount(); 32 | console.log('Locked Lp Amount: %s', LockedLpAmount.toNumber()); 33 | const lpSupply = await pool.getLpSupply(); 34 | console.log('Pool LP Supply: %s \n', lpSupply.toNumber() / Math.pow(10, pool.decimals)); 35 | 36 | console.log( 37 | 'tokenA %s Amount: %s ', 38 | pool.tokenAMint.address, 39 | poolInfo.tokenAAmount.toNumber() / Math.pow(10, pool.tokenAMint.decimals), 40 | ); 41 | console.log( 42 | 'tokenB %s Amount: %s', 43 | pool.tokenBMint.address, 44 | poolInfo.tokenBAmount.toNumber() / Math.pow(10, pool.tokenBMint.decimals), 45 | ); 46 | console.log('virtualPrice: %s', poolInfo.virtualPrice); 47 | console.log('virtualPriceRaw to String: %s \n', poolInfo.virtualPriceRaw.toString()); 48 | } 49 | 50 | async function main() { 51 | // mainnet-beta, SOL-USDC 52 | const poolAddress = '6SWtsTzXrurtVWZdEHvnQdE9oM8tTtyg8rfEo3b4nM93'; 53 | await getPoolInfo(new PublicKey(poolAddress)); 54 | } 55 | 56 | main(); 57 | -------------------------------------------------------------------------------- /ts-client/src/examples/swap.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey, Keypair } from '@solana/web3.js'; 2 | import BN from 'bn.js'; 3 | import { Wallet, AnchorProvider, Program } from '@coral-xyz/anchor'; 4 | import AmmImpl from '../amm'; 5 | import fs from 'fs'; 6 | import os from 'os'; 7 | import { SwapQuote } from '../amm/types'; 8 | 9 | function loadKeypairFromFile(filename: string): Keypair { 10 | const secret = JSON.parse(fs.readFileSync(filename.replace('~', os.homedir)).toString()) as number[]; 11 | const secretKey = Uint8Array.from(secret); 12 | return Keypair.fromSecretKey(secretKey); 13 | } 14 | const payerKP = loadKeypairFromFile('~/.config/solana/id.json'); 15 | const payerWallet = new Wallet(payerKP); 16 | console.log('Wallet Address: %s \n', payerKP.publicKey); 17 | 18 | const devnetConnection = new Connection('https://api.devnet.solana.com'); 19 | const provider = new AnchorProvider(devnetConnection, payerWallet, { 20 | commitment: 'confirmed', 21 | }); 22 | 23 | async function swap(poolAddress: PublicKey, swapAmount: BN, swapAtoB: boolean) { 24 | const pool = await AmmImpl.create(provider.connection, poolAddress); 25 | const poolInfo = pool.poolInfo; 26 | 27 | const poolTokenAddress = await pool.getPoolTokenMint(); 28 | console.log('Pool LP Token Mint Address: %s', poolTokenAddress.toString()); 29 | const lpSupply = await pool.getLpSupply(); 30 | console.log('Pool LP Supply: %s', lpSupply.toNumber() / Math.pow(10, pool.decimals)); 31 | const LockedLpAmount = await pool.getLockedLpAmount(); 32 | console.log('Locked Lp Amount: %s \n', LockedLpAmount.toNumber()); 33 | 34 | console.log( 35 | 'tokenA %s Amount: %s ', 36 | pool.tokenAMint.address, 37 | poolInfo.tokenAAmount.toNumber() / Math.pow(10, pool.tokenAMint.decimals), 38 | ); 39 | console.log( 40 | 'tokenB %s Amount: %s ', 41 | pool.tokenBMint.address, 42 | poolInfo.tokenBAmount.toNumber() / Math.pow(10, pool.tokenBMint.decimals), 43 | ); 44 | console.log('virtualPrice: %s \n', poolInfo.virtualPrice); 45 | 46 | let swapInToken = swapAtoB ? pool.tokenAMint : pool.tokenBMint; 47 | let swapOutToken = swapAtoB ? pool.tokenBMint : pool.tokenAMint; 48 | let inTokenMint = new PublicKey(swapInToken.address); 49 | let swapQuote: SwapQuote = pool.getSwapQuote(inTokenMint, swapAmount, 100); 50 | console.log('🚀 ~ swapQuote:'); 51 | console.log( 52 | 'Swap In %s, Amount %s ', 53 | swapInToken.address, 54 | swapQuote.swapInAmount.toNumber() / Math.pow(10, swapInToken.decimals), 55 | ); 56 | console.log( 57 | 'Swap Out %s, Amount %s \n', 58 | swapOutToken.address, 59 | swapQuote.swapOutAmount.toNumber() / Math.pow(10, swapOutToken.decimals), 60 | ); 61 | console.log( 62 | 'Fee of the Swap %s %s', 63 | swapQuote.fee.toNumber() / Math.pow(10, swapInToken.decimals), 64 | swapInToken.address, 65 | ); 66 | console.log('Price Impact of the Swap %s \n', swapQuote.priceImpact); 67 | 68 | console.log('Swapping...↔️ Please wait for a while😊☕️'); 69 | 70 | const swapTx = await pool.swap( 71 | payerWallet.publicKey, 72 | new PublicKey(swapInToken.address), 73 | swapAmount, 74 | swapQuote.minSwapOutAmount, 75 | ); 76 | const swapResult = await provider.sendAndConfirm(swapTx); 77 | 78 | console.log('Swap Transaction Hash: %s ', swapResult); 79 | } 80 | 81 | async function main() { 82 | // devnet, 9NG-SOL 83 | const poolAddress = 'Bgf1Sy5kfeDgib4go4NgzHuZwek8wE8NZus56z6uizzi'; 84 | 85 | // swap 5 9NG token to SOL 86 | // await swap(new PublicKey(poolAddress), new BN(5000_000_000), true); 87 | 88 | // swap 0.01 SOL to 9NG token 89 | await swap(new PublicKey(poolAddress), new BN(10_000_000), false); 90 | } 91 | 92 | main(); 93 | -------------------------------------------------------------------------------- /ts-client/src/examples/swap_quote.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | Keypair, 5 | } from "@solana/web3.js"; 6 | import BN from "bn.js"; 7 | import { Wallet, AnchorProvider, Program } from '@coral-xyz/anchor'; 8 | import AmmImpl from '../amm'; 9 | import { Amm as AmmIdl, IDL as AmmIDL } from '../amm/idl'; 10 | import { PROGRAM_ID } from "../amm/constants"; 11 | 12 | const mainnetConnection = new Connection('https://api.mainnet-beta.solana.com'); 13 | const mockWallet = new Wallet(new Keypair()); 14 | const provider = new AnchorProvider(mainnetConnection, mockWallet, { 15 | commitment: 'confirmed', 16 | }); 17 | 18 | async function swapQuote(poolAddress: PublicKey, swapAmount: BN, swapAtoB: boolean) { 19 | const ammProgram = new Program(AmmIDL, PROGRAM_ID, provider); 20 | let poolState = await ammProgram.account.pool.fetch(poolAddress); 21 | const pool = await AmmImpl.create(provider.connection, poolAddress); 22 | let inTokenMint = swapAtoB ? poolState.tokenAMint : poolState.tokenBMint; 23 | let swapQuote = pool.getSwapQuote(inTokenMint, swapAmount, 100); 24 | console.log("🚀 ~ swapQuote:", swapQuote); 25 | console.log("SwapInAmount %s swapOutAmount %s", swapQuote.swapInAmount.toString(), swapQuote.swapOutAmount.toString()); 26 | } 27 | 28 | async function main() { 29 | await swapQuote(new PublicKey( 30 | "5yuefgbJJpmFNK2iiYbLSpv1aZXq7F9AUKkZKErTYCvs" 31 | ), new BN(10_000_000_000), true); 32 | } 33 | 34 | 35 | main() -------------------------------------------------------------------------------- /ts-client/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["ES2022"], 5 | "outDir": "./dist/cjs/", 6 | "moduleResolution": "node", 7 | "noEmit": false, 8 | "declaration": true, 9 | "declarationMap": true, 10 | "sourceMap": true, 11 | "esModuleInterop": true, 12 | "resolveJsonModule": true, 13 | "noImplicitAny": false, 14 | "skipLibCheck": true 15 | }, 16 | "exclude": ["**/*.test.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /ts-client/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "compilerOptions": { 4 | "lib": ["ES2022"], 5 | "outDir": "dist/esm/", 6 | "target": "ES6", 7 | "module": "ESNext" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ts-client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/recommended/tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "types": ["jest", "mocha", "chai"], 6 | "typeRoots": ["./node_modules/@types"], 7 | "module": "commonjs", 8 | // "target": "es6", 9 | "target": "ES2022", 10 | "moduleResolution": "node", 11 | "esModuleInterop": true, 12 | "resolveJsonModule": true, 13 | "noImplicitAny": false, 14 | "skipLibCheck": true 15 | } 16 | } 17 | --------------------------------------------------------------------------------