├── .github
├── actions
│ ├── setup-anchor-cli
│ │ └── action.yml
│ ├── setup-dep
│ │ └── action.yml
│ └── setup-solana
│ │ └── action.yml
└── workflows
│ ├── ci_rust_test_on_pull.yml
│ └── ci_ts_test_on_pull.yml
├── .gitignore
├── Anchor.toml
├── CHANGELOG.md
├── Cargo.lock
├── Cargo.toml
├── README.md
├── programs
└── vault
│ ├── Cargo.toml
│ ├── README.md
│ ├── Xargo.toml
│ └── src
│ ├── context.rs
│ ├── lib.rs
│ ├── seed.rs
│ ├── state.rs
│ ├── strategy
│ ├── apricot_without_lm.rs
│ ├── base.rs
│ ├── cypher.rs
│ ├── frakt.rs
│ ├── mango.rs
│ ├── marginfi.rs
│ └── mod.rs
│ └── utils.rs
├── rust-client
├── Cargo.toml
├── README.md
└── src
│ ├── lib.rs
│ ├── main.rs
│ ├── user.rs
│ └── utils.rs
├── rust-toolchain
└── ts-client
├── .prettierrc
├── README.md
├── index.ts
├── jest.config.js
├── lib.es5.d.ts
├── package.json
├── pnpm-lock.yaml
├── src
└── vault
│ ├── affiliate-idl.ts
│ ├── constants.ts
│ ├── helper
│ └── index.ts
│ ├── idl.ts
│ ├── index.ts
│ ├── strategy
│ ├── index.ts
│ └── vault.ts
│ ├── tests
│ ├── affiliate.test.ts
│ ├── utils
│ │ └── index.ts
│ └── vault.test.ts
│ ├── types
│ └── index.ts
│ └── utils
│ └── index.ts
├── tsconfig.build.json
├── tsconfig.esm.json
└── tsconfig.json
/.github/actions/setup-anchor-cli/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 | - run: npm install -g @coral-xyz/anchor-cli@${{ env.ANCHOR_CLI_VERSION }} yarn
8 | shell: bash
9 |
--------------------------------------------------------------------------------
/.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 ca-certificates pkg-config build-essential libudev-dev libdigest-sha-perl libssl-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@v2
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 | # - name: Check if Solana exists
15 | # id: check-solana-avail
16 | # run: solana --version
17 | # shell: bash
18 | - run: sh -c "$(curl -sSfL https://release.solana.com/v${{ env.SOLANA_CLI_VERSION }}/install)"
19 | shell: bash
20 | # if: steps.check-solana-avail.outputs.status == 'failure'
21 | - run: echo "$HOME/.local/share/solana/install/active_release/bin/" >> $GITHUB_PATH
22 | shell: bash
23 | - run: solana-keygen new --no-bip39-passphrase --force
24 | shell: bash
25 | # if: steps.check-solana-avail.outputs.status == 'failure'
26 | - run: solana config set --url localhost
27 | shell: bash
28 | # if: steps.check-solana-avail.outputs.status == 'failure'
29 |
--------------------------------------------------------------------------------
/.github/workflows/ci_rust_test_on_pull.yml:
--------------------------------------------------------------------------------
1 | name: CI on pull_request
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 |
15 | env:
16 | SOLANA_CLI_VERSION: 1.16.13
17 | ANCHOR_CLI_VERSION: 0.28.0
18 |
19 | jobs:
20 | changed_files_rust:
21 | runs-on: ubuntu-latest
22 | outputs:
23 | program: ${{steps.changed-files-specific.outputs.any_changed}}
24 | steps:
25 | - uses: actions/checkout@v2
26 | with:
27 | fetch-depth: 0
28 | - name: Get specific changed files
29 | id: changed-files-specific
30 | uses: tj-actions/changed-files@v18.6
31 | with:
32 | files: |
33 | rust-client
34 | programs
35 |
36 | rust-test:
37 | needs: changed_files_rust
38 | runs-on: ubuntu-latest
39 | if: needs.changed_files_rust.outputs.program == 'true'
40 | steps:
41 | - uses: actions/checkout@v2
42 | - uses: ./.github/actions/setup-dep
43 | - uses: ./.github/actions/setup-solana
44 | - uses: ./.github/actions/setup-anchor-cli
45 | # Install rust + toolchain
46 | - uses: actions-rs/toolchain@v1
47 | with:
48 | components: clippy, rustfmt
49 | # Cargo build cache
50 | - name: Cargo Cache
51 | uses: actions/cache@v1
52 | with:
53 | path: |
54 | ~/.cargo/
55 | target
56 | key: ${{ runner.os }}-cargo-${{ steps.extract_branch.outputs.branch }}
57 | restore-keys: |
58 | ${{ runner.os }}-cargo-${{ steps.extract_branch.outputs.branch }}
59 | ${{ runner.os }}-cargo
60 | - name: Cargo fmt check
61 | run: cargo fmt -- --check
62 | shell: bash
63 | - name: Cargo test
64 | run: cargo test -- --nocapture
65 | shell: bash
66 | - name: Anchor build
67 | run: anchor build
68 | - name: Cargo build
69 | run: cargo build --package rust-client
70 |
--------------------------------------------------------------------------------
/.github/workflows/ci_ts_test_on_pull.yml:
--------------------------------------------------------------------------------
1 | name: Continuous Integration NPM
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 |
15 | env:
16 | working_dir: ./ts-client
17 |
18 | jobs:
19 | changed_files_ts:
20 | runs-on: ubuntu-latest
21 | outputs:
22 | program: ${{steps.changed-files-specific.outputs.any_changed}}
23 | steps:
24 | - uses: actions/checkout@v2
25 | with:
26 | fetch-depth: 0
27 | - name: Get specific changed files
28 | id: changed-files-specific
29 | uses: tj-actions/changed-files@v18.6
30 | with:
31 | files: |
32 | ts-client
33 |
34 | ts-test:
35 | needs: changed_files_ts
36 | runs-on: ubuntu-latest
37 | if: needs.changed_files_ts.outputs.program == 'true'
38 | steps:
39 | - uses: actions/checkout@v2
40 | - name: Set up Node.js
41 | uses: actions/setup-node@v1
42 | with:
43 | node-version: ${{ matrix.node }}
44 | - uses: pnpm/action-setup@v4
45 | with:
46 | version: 6.0.2
47 | - name: Install dependencies
48 | working-directory: ${{ env.working_dir }}
49 | run: pnpm install
50 | - name: Run tests
51 | working-directory: ${{ env.working_dir }}
52 | run: pnpm run test
53 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .anchor
3 | .DS_Store
4 | target
5 | **/*.rs.bk
6 | node_modules
7 |
8 | ts-client/dist/
--------------------------------------------------------------------------------
/Anchor.toml:
--------------------------------------------------------------------------------
1 | [features]
2 | seeds = false
3 | [programs.localnet]
4 | mercurial_sdk = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
5 |
6 | [registry]
7 | url = "https://anchor.projectserum.com"
8 |
9 | [provider]
10 | cluster = "localnet"
11 | wallet = "~/.config/solana/id.json"
12 |
13 | [scripts]
14 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ### Added
11 |
12 | ### Changed
13 |
14 | ### Deprecated
15 |
16 | ### Removed
17 |
18 | ### Fixed
19 |
20 | ### Security
21 |
22 | ## @meteora-ag/vault-sdk[2.3.1] - PR [#135](https://github.com/mercurial-finance/vault-sdk/pull/135)
23 |
24 | ### Changed
25 |
26 | - remove `@solana/spl-token-registry` deps
27 |
28 | ## @meteora-ag/vault-sdk[2.3.0] - PR [#134](https://github.com/mercurial-finance/vault-sdk/pull/134)
29 |
30 | ### Changed
31 |
32 | - move to `meteora-ag` org
33 |
34 | ## @mercurial-finance/vault-sdk [2.2.1] - PR [#131](https://github.com/mercurial-finance/vault-sdk/pull/131)
35 |
36 | ### Added
37 |
38 | - new utils `deserializeMint`
39 |
40 | ## @mercurial-finance/vault-sdk [2.2.0] - PR [#130](https://github.com/mercurial-finance/vault-sdk/pull/130)
41 |
42 | ### Fixed
43 |
44 | - fix `tokenMint` & `tokenLpMint`
45 |
46 | ### Removed
47 |
48 | - remove `lpMintPda` field
49 |
50 | ## @mercurial-finance/vault-sdk [2.1.1] - PR [#129](https://github.com/mercurial-finance/vault-sdk/pull/129)
51 |
52 | ### Changed
53 |
54 | - update `createMultiple` & `createMultiplePda` to optimize rpc call
55 |
56 | ## @mercurial-finance/vault-sdk [2.1.0] - PR [#128](https://github.com/mercurial-finance/vault-sdk/pull/128)
57 |
58 | ### Fixed
59 |
60 | - fix `createMultiple` to take in less param
61 |
62 | ## @mercurial-finance/vault-sdk [2.0.1] - PR [#126](https://github.com/mercurial-finance/vault-sdk/pull/126)
63 |
64 | ### Fixed
65 |
66 | - fix `refreshVaultState` not working
67 |
68 | ## @mercurial-finance/vault-sdk [2.0.0] - PR [#124](https://github.com/mercurial-finance/vault-sdk/pull/124)
69 |
70 | ### Changed
71 |
72 | - Pump "@solana/spl-token" to 0.4.6 and various relevant packages
73 |
74 | ### Removed
75 |
76 | - Remove logic to withdraw directly from strategy
77 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "programs/*",
4 | "rust-client"
5 | ]
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mercurial Vault SDK
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Program ID:
9 |
10 | 24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi
11 |
12 |
13 | ## Getting started (Rust)
14 | Refer to [Rust Client Readme](https://github.com/mercurial-finance/vault-sdk/blob/main/rust-client/README.md) for full installation guide
15 |
16 | ### Affiliate
17 | Refer to [Affiliate Rust Client Readme](https://github.com/mercurial-finance/vault-periphery/tree/main/affiliate/rust-client) for affiliate Readme
18 |
19 |
20 |
21 | ## Getting started (TypeScript)
22 | NPM: https://www.npmjs.com/package/@meteora-ag/vault-sdk
23 |
24 | SDK: https://github.com/meteora-ag/vault-sdk
25 |
26 | Docs: https://docs.mercurial.finance/mercurial-dynamic-yield-infra/
27 |
28 | Discord: https://discord.com/channels/841152225564950528/864859354335412224
29 |
30 |
31 |
32 | Refer to [Typescript Readme](https://github.com/meteora-ag/vault-sdk/blob/main/ts-client/README.md) for full installation guide
33 |
34 | ### Install & Run
35 |
36 | 1. `pnpm i`
37 | 2. `pnpm run test`
38 |
39 | ### Install & Run Affiliate
40 | 1. `pnpm i`
41 | 2. `pnpm run test-affiliate`
--------------------------------------------------------------------------------
/programs/vault/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "mercurial-vault"
3 | version = "0.5.0"
4 | description = "Created with Anchor"
5 | edition = "2018"
6 | license = "MIT"
7 | readme = "README.md"
8 |
9 | [lib]
10 | crate-type = ["cdylib", "lib"]
11 | name = "mercurial_vault"
12 | licence = "MIT"
13 | version = "0.0.1"
14 |
15 | [features]
16 | no-entrypoint = []
17 | no-idl = []
18 | cpi = ["no-entrypoint"]
19 | default = []
20 | test-bpf = []
21 | devnet = []
22 | staging = []
23 | no-capture = []
24 |
25 | [dependencies]
26 | serde = { version = "1.0.136" }
27 | anchor-lang = "0.28.0"
28 | anchor-spl = "0.28.0"
--------------------------------------------------------------------------------
/programs/vault/README.md:
--------------------------------------------------------------------------------
1 | ## VAULT SDK
2 |
3 | - Author: [Mercurial Finance](https://www.mercurial.finance/)
4 | - Purpose: SDK for Vault - Mercurial Finance
5 |
6 | - [Discord](https://discord.gg/yfGeKKbpM8)
7 | - [Github](hhttps://github.com/meteora-ag/vault-sdk)
8 | - [Crates](https://crates.io/crates/mercurial-vault)
9 |
--------------------------------------------------------------------------------
/programs/vault/Xargo.toml:
--------------------------------------------------------------------------------
1 | [target.bpfel-unknown-unknown.dependencies.std]
2 | features = []
3 |
--------------------------------------------------------------------------------
/programs/vault/src/context.rs:
--------------------------------------------------------------------------------
1 | use crate::get_base_address_for_idle_vault;
2 | use crate::get_base_key;
3 | use crate::get_treasury_address;
4 | use crate::seed;
5 | use crate::state::{Strategy, Vault, MAX_BUMPS};
6 | use anchor_lang::prelude::*;
7 | use anchor_spl::token::{Mint, Token, TokenAccount};
8 |
9 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default, Debug)]
10 | pub struct VaultBumps {
11 | pub vault_bump: u8,
12 | pub token_vault_bump: u8,
13 | }
14 |
15 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default, Debug)]
16 | pub struct StrategyBumps {
17 | pub strategy_index: u8,
18 | pub strategy_bump: u8,
19 | pub collateral_vault_bump: u8,
20 | pub other_bumps: [u8; MAX_BUMPS],
21 | }
22 |
23 | #[derive(Accounts)]
24 | pub struct DepositWithdrawLiquidity<'info> {
25 | #[account(
26 | mut,
27 | has_one = token_vault,
28 | has_one = lp_mint,
29 | )]
30 | pub vault: Box>,
31 | #[account(mut)]
32 | pub token_vault: Account<'info, TokenAccount>,
33 | #[account(mut)]
34 | pub lp_mint: Account<'info, Mint>,
35 | #[account(mut)]
36 | pub user_token: Account<'info, TokenAccount>,
37 | #[account(mut)]
38 | pub user_lp: Account<'info, TokenAccount>,
39 | pub user: Signer<'info>,
40 | pub token_program: Program<'info, Token>,
41 | }
42 |
43 | #[derive(Accounts)]
44 | pub struct WithdrawDirectlyFromStrategy<'info> {
45 | #[account(
46 | mut,
47 | has_one = token_vault,
48 | has_one = lp_mint,
49 | has_one = fee_vault,
50 | )]
51 | pub vault: Box>,
52 | #[account(mut)]
53 | pub strategy: Box>,
54 | /// CHECK: Reserve account
55 | #[account(mut, constraint = strategy.reserve == reserve.key())]
56 | pub reserve: AccountInfo<'info>,
57 | /// CHECK: Strategy program
58 | pub strategy_program: AccountInfo<'info>,
59 | #[account( mut, constraint = strategy.collateral_vault == collateral_vault.key())]
60 | pub collateral_vault: Box>,
61 | #[account(mut)]
62 | pub token_vault: Account<'info, TokenAccount>,
63 | #[account(mut)]
64 | pub lp_mint: Account<'info, Mint>,
65 | #[account(mut)]
66 | pub fee_vault: Box>,
67 | #[account(mut)]
68 | pub user_token: Account<'info, TokenAccount>,
69 | #[account(mut)]
70 | pub user_lp: Account<'info, TokenAccount>,
71 | pub user: Signer<'info>,
72 | pub token_program: Program<'info, Token>,
73 | }
74 |
75 | #[derive(Accounts)]
76 | pub struct GetUnlockedAmount<'info> {
77 | pub vault: Box>,
78 | }
79 |
80 | #[derive(Accounts)]
81 | pub struct RebalanceStrategy<'info> {
82 | #[account(
83 | mut,
84 | has_one = token_vault,
85 | has_one = lp_mint,
86 | has_one = fee_vault,
87 | )]
88 | pub vault: Box>,
89 | #[account(mut)]
90 | pub strategy: Box>,
91 |
92 | #[account(mut)]
93 | pub token_vault: Box>,
94 |
95 | #[account(mut)]
96 | pub fee_vault: Box>,
97 |
98 | #[account(mut)]
99 | pub lp_mint: Box>,
100 |
101 | /// CHECK: Strategy program
102 | pub strategy_program: AccountInfo<'info>,
103 |
104 | #[account( mut, constraint = strategy.collateral_vault == collateral_vault.key())]
105 | pub collateral_vault: Box>,
106 |
107 | /// CHECK: Reserve account
108 | #[account(mut, constraint = strategy.reserve == reserve.key())]
109 | pub reserve: AccountInfo<'info>,
110 |
111 | pub token_program: Program<'info, Token>,
112 |
113 | #[account(constraint = vault.admin == operator.key() || vault.operator == operator.key())]
114 | pub operator: Signer<'info>,
115 | }
116 | /// Accounts for initialize a new vault
117 | #[derive(Accounts)]
118 | pub struct Initialize<'info> {
119 | /// This is base account for all vault
120 | /// No need base key now because we only allow 1 vault per token now
121 | // pub base: Signer<'info>,
122 |
123 | /// Vault account
124 | #[account(
125 | init,
126 | seeds = [
127 | seed::VAULT_PREFIX.as_ref(), token_mint.key().as_ref(), get_base_key().as_ref()
128 | ],
129 | bump,
130 | payer = payer,
131 | space = 8 + std::mem::size_of::(),
132 | )]
133 | pub vault: Box>,
134 |
135 | /// Payer can be anyone
136 | #[account(mut)]
137 | pub payer: Signer<'info>,
138 |
139 | /// Token vault account
140 | #[account(
141 | init,
142 | seeds = [seed::TOKEN_VAULT_PREFIX.as_ref(), vault.key().as_ref()],
143 | bump,
144 | payer = payer,
145 | token::mint = token_mint,
146 | token::authority = vault,
147 | )]
148 | pub token_vault: Box>,
149 | /// Token mint account
150 | pub token_mint: Box>, // allocate some accounts in heap to avoid stack frame size limit
151 | #[account(
152 | init,
153 | seeds = [seed::LP_MINT_PREFIX.as_ref(), vault.key().as_ref()],
154 | bump,
155 | payer = payer,
156 | mint::decimals = token_mint.decimals,
157 | mint::authority = vault,
158 | )]
159 | pub lp_mint: Box>,
160 | /// rent
161 | pub rent: Sysvar<'info, Rent>,
162 | /// token_program
163 | pub token_program: Program<'info, Token>,
164 | /// system_program
165 | pub system_program: Program<'info, System>,
166 | }
167 |
168 | /// Accounts for InitializeIdleVault
169 | #[derive(Accounts)]
170 | pub struct InitializeIdleVault<'info> {
171 | /// Vault account
172 | #[account(
173 | init,
174 | seeds = [
175 | seed::VAULT_PREFIX.as_ref(), token_mint.key().as_ref(), get_base_address_for_idle_vault().as_ref()
176 | ],
177 | bump,
178 | payer = payer,
179 | space = 8 + std::mem::size_of::(),
180 | )]
181 | pub vault: Box>,
182 |
183 | /// Payer can be anyone
184 | #[account(mut)]
185 | pub payer: Signer<'info>,
186 |
187 | /// Token vault account
188 | #[account(
189 | init,
190 | seeds = [seed::TOKEN_VAULT_PREFIX.as_ref(), vault.key().as_ref()],
191 | bump,
192 | payer = payer,
193 | token::mint = token_mint,
194 | token::authority = vault,
195 | )]
196 | pub token_vault: Box>,
197 | /// Token mint account
198 | pub token_mint: Box>, // allocate some accounts in heap to avoid stack frame size limit
199 | /// LP mint
200 | #[account(
201 | init,
202 | seeds = [seed::LP_MINT_PREFIX.as_ref(), vault.key().as_ref()],
203 | bump,
204 | payer = payer,
205 | mint::decimals = token_mint.decimals,
206 | mint::authority = vault,
207 | )]
208 | pub lp_mint: Box>,
209 | /// rent
210 | pub rent: Sysvar<'info, Rent>,
211 | /// token_program
212 | pub token_program: Program<'info, Token>,
213 | /// system_program
214 | pub system_program: Program<'info, System>,
215 | }
216 |
--------------------------------------------------------------------------------
/programs/vault/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod context;
2 | pub mod seed;
3 | pub mod state;
4 | pub mod strategy;
5 | pub mod utils;
6 |
7 | use crate::strategy::base::StrategyType;
8 | use anchor_lang::prelude::*;
9 | use context::*;
10 | use std::convert::TryFrom;
11 | use std::str::FromStr;
12 |
13 | #[cfg(feature = "staging")]
14 | declare_id!("6YRZW57XsrT2DxSNLXHHQd4QmiqBode4d6btASkRqcFo");
15 |
16 | #[cfg(not(feature = "staging"))]
17 | declare_id!("24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi");
18 |
19 | // Performance fee when rebalancing
20 | pub const PERFORMANCE_FEE_NUMERATOR: u128 = 500u128; // 5%
21 | pub const PERFORMANCE_FEE_DENOMINATOR: u128 = 10000u128;
22 |
23 | // get vault address from base key and token mint
24 | // let (vault, _vault_bump) = Pubkey::find_program_address(
25 | // &[b"vault".as_ref(), token_mint.as_ref(), get_base_key().as_ref()],
26 | // &program_client.id(),
27 | // );
28 | pub fn get_base_key() -> Pubkey {
29 | Pubkey::from_str("HWzXGcGHy4tcpYfaRDCyLNzXqBTv3E6BttpCH2vJxArv").unwrap()
30 | }
31 |
32 | /// Treasury address
33 | pub fn get_treasury_address() -> Pubkey {
34 | Pubkey::from_str("9kZeN47U2dubGbbzMrzzoRAUvpuxVLRcjW9XiFpYjUo4").unwrap()
35 | }
36 |
37 | pub fn get_base_address_for_idle_vault() -> Pubkey {
38 | Pubkey::default()
39 | }
40 |
41 | #[program]
42 | pub mod vault {
43 | use super::*;
44 |
45 | #[allow(unused_variables)]
46 | pub fn initialize(ctx: Context) -> Result<()> {
47 | Ok(())
48 | }
49 |
50 | #[allow(unused_variables)]
51 | pub fn initialize_idle_vault(ctx: Context) -> Result<()> {
52 | Ok(())
53 | }
54 |
55 | #[allow(unused_variables)]
56 | pub fn deposit(
57 | ctx: Context,
58 | token_amount: u64,
59 | minimum_lp_token_amount: u64,
60 | ) -> Result<()> {
61 | Ok(())
62 | }
63 |
64 | #[allow(unused_variables)]
65 | pub fn withdraw(
66 | ctx: Context,
67 | unmint_amount: u64,
68 | min_out_amount: u64,
69 | ) -> Result<()> {
70 | Ok(())
71 | }
72 |
73 | #[allow(unused_variables)]
74 | pub fn withdraw_directly_from_strategy<'a, 'b, 'c, 'info>(
75 | ctx: Context<'a, 'b, 'c, 'info, WithdrawDirectlyFromStrategy<'info>>,
76 | unmint_amount: u64,
77 | min_out_amount: u64,
78 | ) -> Result<()> {
79 | Ok(())
80 | }
81 |
82 | // simulate function to get unlocked amount
83 | pub fn get_unlocked_amount(ctx: Context) -> Result<()> {
84 | let vault = &ctx.accounts.vault;
85 | let current_time = u64::try_from(Clock::get()?.unix_timestamp)
86 | .ok()
87 | .ok_or(VaultError::MathOverflow)?;
88 | let total_amount = vault
89 | .get_unlocked_amount(current_time)
90 | .ok_or(VaultError::MathOverflow)?;
91 |
92 | emit!(TotalAmount { total_amount });
93 |
94 | Ok(())
95 | }
96 |
97 | #[allow(unused_variables)]
98 | pub fn deposit_strategy<'a, 'b, 'c, 'info>(
99 | ctx: Context<'a, 'b, 'c, 'info, RebalanceStrategy<'info>>,
100 | amount: u64,
101 | ) -> Result<()> {
102 | Ok(())
103 | }
104 |
105 | #[allow(unused_variables)]
106 | pub fn withdraw_strategy<'a, 'b, 'c, 'info>(
107 | ctx: Context<'a, 'b, 'c, 'info, RebalanceStrategy<'info>>,
108 | amount: u64,
109 | ) -> Result<()> {
110 | Ok(())
111 | }
112 | }
113 |
114 | #[error_code]
115 | pub enum VaultError {
116 | #[msg("Vault is disabled")]
117 | VaultIsDisabled,
118 |
119 | #[msg("Exceeded slippage tolerance")]
120 | ExceededSlippage,
121 |
122 | #[msg("Strategy is not existed")]
123 | StrategyIsNotExisted,
124 |
125 | #[msg("UnAuthorized")]
126 | UnAuthorized,
127 |
128 | #[msg("Math operation overflow")]
129 | MathOverflow,
130 |
131 | #[msg("Protocol is not supported")]
132 | ProtocolIsNotSupported,
133 |
134 | #[msg("Reserve does not support token mint")]
135 | UnMatchReserve,
136 |
137 | #[msg("lockedProfitDegradation is invalid")]
138 | InvalidLockedProfitDegradation,
139 |
140 | #[msg("Maximum number of strategies have been reached")]
141 | MaxStrategyReached,
142 |
143 | #[msg("Strategy existed")]
144 | StrategyExisted,
145 |
146 | #[msg("Invalid unmint amount")]
147 | InvalidUnmintAmount,
148 |
149 | #[msg("Invalid accounts for strategy")]
150 | InvalidAccountsForStrategy,
151 |
152 | #[msg("Invalid bump")]
153 | InvalidBump,
154 |
155 | #[msg("Amount must be greater than 0")]
156 | AmountMustGreaterThanZero,
157 |
158 | #[msg("Mango is not supported anymore")]
159 | MangoIsNotSupportedAnymore,
160 |
161 | #[msg("Strategy is not supported")]
162 | StrategyIsNotSupported,
163 |
164 | #[msg("Pay amount is exceeded")]
165 | PayAmountIsExeeced,
166 | }
167 |
168 | #[event]
169 | pub struct AddLiquidity {
170 | pub lp_mint_amount: u64,
171 | pub token_amount: u64,
172 | }
173 |
174 | #[event]
175 | pub struct RemoveLiquidity {
176 | pub lpunmint_amount: u64,
177 | pub token_amount: u64,
178 | }
179 | #[event]
180 | pub struct StrategyDeposit {
181 | pub strategy_type: StrategyType,
182 | pub token_amount: u64,
183 | }
184 |
185 | #[event]
186 | pub struct StrategyWithdraw {
187 | pub strategy_type: StrategyType,
188 | pub collateral_amount: u64,
189 | pub estimated_token_amount: u64,
190 | }
191 |
192 | #[event]
193 | pub struct StakingReward {
194 | pub strategy_type: StrategyType,
195 | pub token_amount: u64,
196 | pub mint_account: Pubkey,
197 | }
198 |
199 | #[event]
200 | pub struct PerformanceFee {
201 | pub lp_mint_more: u64,
202 | }
203 |
204 | #[event]
205 | pub struct ReportLoss {
206 | pub strategy: Pubkey,
207 | pub loss: u64,
208 | }
209 |
210 | #[event]
211 | pub struct TotalAmount {
212 | pub total_amount: u64,
213 | }
214 |
--------------------------------------------------------------------------------
/programs/vault/src/seed.rs:
--------------------------------------------------------------------------------
1 | pub static VAULT_PREFIX: &str = "vault";
2 | pub static TOKEN_VAULT_PREFIX: &str = "token_vault";
3 | pub static LP_MINT_PREFIX: &str = "lp_mint";
4 | pub static COLLATERAL_VAULT_PREFIX: &str = "collateral_vault";
5 | pub static FEE_VAULT_PREFIX: &str = "fee_vault";
6 | pub static SOLEND_OBLIGATION_PREFIX: &str = "solend_obligation";
7 | pub static SOLEND_OBLIGATION_OWNER_PREFIX: &str = "solend_obligation_owner";
8 | pub static APRICOT_USER_INFO_SIGNER_PREFIX: &str = "apricot_user_info_signer";
9 |
--------------------------------------------------------------------------------
/programs/vault/src/state.rs:
--------------------------------------------------------------------------------
1 | use crate::context::VaultBumps;
2 | use crate::strategy::base::StrategyType;
3 | use anchor_lang::prelude::*;
4 | use std::convert::TryFrom;
5 | use std::fmt::Debug;
6 |
7 | pub const MAX_STRATEGY: usize = 30;
8 | pub const MAX_BUMPS: usize = 10;
9 | pub const LOCKED_PROFIT_DEGRADATION_DENOMINATOR: u128 = 1_000_000_000_000;
10 |
11 | #[account]
12 | #[derive(Default, Debug)]
13 | pub struct Vault {
14 | pub enabled: u8,
15 | pub bumps: VaultBumps,
16 |
17 | pub total_amount: u64,
18 |
19 | pub token_vault: Pubkey,
20 | pub fee_vault: Pubkey,
21 | pub token_mint: Pubkey,
22 |
23 | pub lp_mint: Pubkey,
24 | pub strategies: [Pubkey; MAX_STRATEGY],
25 |
26 | pub base: Pubkey,
27 | pub admin: Pubkey,
28 | pub operator: Pubkey, // person to send crank
29 | pub locked_profit_tracker: LockedProfitTracker,
30 | }
31 |
32 | #[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug)]
33 | pub struct LockedProfitTracker {
34 | pub last_updated_locked_profit: u64,
35 | pub last_report: u64,
36 | pub locked_profit_degradation: u64,
37 | }
38 |
39 | impl Default for LockedProfitTracker {
40 | fn default() -> Self {
41 | LockedProfitTracker {
42 | last_updated_locked_profit: 0,
43 | last_report: 0,
44 | locked_profit_degradation: u64::try_from(LOCKED_PROFIT_DEGRADATION_DENOMINATOR)
45 | .unwrap()
46 | / (6 * 3600), // locked profit is fully dripped in 6 hour
47 | }
48 | }
49 | }
50 | impl LockedProfitTracker {
51 | // based from yearn vault
52 | // https://github.com/yearn/yearn-vaults/blob/main/contracts/Vault.vy#L825
53 | pub fn calculate_locked_profit(&self, current_time: u64) -> Option {
54 | let duration = u128::from(current_time.checked_sub(self.last_report)?);
55 | let locked_profit_degradation = u128::from(self.locked_profit_degradation);
56 | let locked_fund_ratio = duration.checked_mul(locked_profit_degradation)?;
57 |
58 | if locked_fund_ratio > LOCKED_PROFIT_DEGRADATION_DENOMINATOR {
59 | return Some(0);
60 | }
61 | let locked_profit = u128::from(self.last_updated_locked_profit);
62 |
63 | let locked_profit = (locked_profit
64 | .checked_mul(LOCKED_PROFIT_DEGRADATION_DENOMINATOR - locked_fund_ratio)?)
65 | .checked_div(LOCKED_PROFIT_DEGRADATION_DENOMINATOR)?;
66 | let locked_profit = u64::try_from(locked_profit).ok()?;
67 | Some(locked_profit)
68 | }
69 | }
70 |
71 | impl Vault {
72 | pub fn get_unlocked_amount(&self, current_time: u64) -> Option {
73 | self.total_amount.checked_sub(
74 | self.locked_profit_tracker
75 | .calculate_locked_profit(current_time)?,
76 | )
77 | }
78 |
79 | pub fn get_amount_by_share(
80 | &self,
81 | current_time: u64,
82 | share: u64,
83 | total_supply: u64,
84 | ) -> Option {
85 | let total_amount = self.get_unlocked_amount(current_time)?;
86 | u64::try_from(
87 | u128::from(share)
88 | .checked_mul(u128::from(total_amount))?
89 | .checked_div(u128::from(total_supply))?,
90 | )
91 | .ok()
92 | }
93 |
94 | pub fn get_unmint_amount(
95 | &self,
96 | current_time: u64,
97 | out_token: u64,
98 | total_supply: u64,
99 | ) -> Option {
100 | let total_amount = self.get_unlocked_amount(current_time)?;
101 | u64::try_from(
102 | u128::from(out_token)
103 | .checked_mul(u128::from(total_supply))?
104 | .checked_div(u128::from(total_amount))?,
105 | )
106 | .ok()
107 | }
108 |
109 | pub fn is_strategy_existed(&self, pubkey: Pubkey) -> bool {
110 | for item in self.strategies.iter() {
111 | if *item == pubkey {
112 | return true;
113 | }
114 | }
115 | false
116 | }
117 | }
118 |
119 | impl Default for StrategyType {
120 | fn default() -> Self {
121 | StrategyType::PortFinanceWithoutLM
122 | }
123 | }
124 |
125 | #[account]
126 | #[derive(Default, Debug)]
127 | pub struct Strategy {
128 | pub reserve: Pubkey,
129 | pub collateral_vault: Pubkey,
130 | pub strategy_type: StrategyType,
131 | pub current_liquidity: u64,
132 | pub bumps: [u8; MAX_BUMPS],
133 | pub vault: Pubkey,
134 | pub is_disable: u8,
135 | }
136 |
--------------------------------------------------------------------------------
/programs/vault/src/strategy/apricot_without_lm.rs:
--------------------------------------------------------------------------------
1 | use crate::ID;
2 | use anchor_lang::prelude::Pubkey;
3 |
4 | /// Return user signer PDA
5 | pub fn get_user_signer(strategy: &Pubkey) -> (Pubkey, u8) {
6 | Pubkey::find_program_address(
7 | &[
8 | crate::seed::APRICOT_USER_INFO_SIGNER_PREFIX.as_ref(),
9 | strategy.as_ref(),
10 | ],
11 | &ID,
12 | )
13 | }
14 |
--------------------------------------------------------------------------------
/programs/vault/src/strategy/base.rs:
--------------------------------------------------------------------------------
1 | use anchor_lang::prelude::*;
2 | use serde::{Deserialize, Serialize};
3 | use std::str::FromStr;
4 |
5 | #[derive(
6 | AnchorSerialize,
7 | AnchorDeserialize,
8 | Clone,
9 | Copy,
10 | Debug,
11 | PartialEq,
12 | Serialize,
13 | Deserialize,
14 | Eq,
15 | Hash,
16 | )]
17 | pub enum StrategyType {
18 | PortFinanceWithoutLM,
19 | PortFinanceWithLM,
20 | SolendWithoutLM,
21 | Mango, // Mango is no longer supported
22 | SolendWithLM,
23 | ApricotWithoutLM,
24 | Francium,
25 | Tulip,
26 | // This is for compatibility with some administrative endpoint
27 | Vault,
28 | Drift,
29 | Frakt,
30 | Marginfi,
31 | Kamino,
32 | }
33 |
34 | impl std::fmt::Display for StrategyType {
35 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
36 | write!(f, "{:?}", self)
37 | // or, alternatively:
38 | // fmt::Debug::fmt(self, f)
39 | }
40 | }
41 |
42 | pub fn get_cypher_program_id() -> Pubkey {
43 | Pubkey::from_str("CYPH3o83JX6jY6NkbproSpdmQ5VWJtxjfJ5P8veyYVu3").unwrap()
44 | }
45 |
46 | pub fn get_tulip_program_id() -> Pubkey {
47 | Pubkey::from_str("4bcFeLv4nydFrsZqV5CgwCVrPhkQKsXtzfy2KyMz7ozM").unwrap()
48 | }
49 |
50 | pub fn get_francium_program_id() -> Pubkey {
51 | Pubkey::from_str("FC81tbGt6JWRXidaWYFXxGnTk4VgobhJHATvTRVMqgWj").unwrap()
52 | }
53 |
54 | pub fn get_apricot_program_id() -> Pubkey {
55 | Pubkey::from_str("6UeJYTLU1adaoHWeApWsoj1xNEDbWA2RhM2DLc8CrDDi").unwrap()
56 | }
57 |
58 | #[cfg(feature = "devnet")]
59 | pub fn get_port_finance_program_id() -> Pubkey {
60 | Pubkey::from_str("pdQ2rQQU5zH2rDgZ7xH2azMBJegUzUyunJ5Jd637hC4").unwrap()
61 | }
62 |
63 | #[cfg(not(feature = "devnet"))]
64 | pub fn get_port_finance_program_id() -> Pubkey {
65 | Pubkey::from_str("Port7uDYB3wk6GJAw4KT1WpTeMtSu9bTcChBHkX2LfR").unwrap()
66 | }
67 |
68 | #[cfg(feature = "devnet")]
69 | pub fn get_solend_program_id() -> Pubkey {
70 | Pubkey::from_str("ALend7Ketfx5bxh6ghsCDXAoDrhvEmsXT3cynB6aPLgx").unwrap()
71 | }
72 |
73 | #[cfg(not(feature = "devnet"))]
74 | pub fn get_solend_program_id() -> Pubkey {
75 | Pubkey::from_str("So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo").unwrap()
76 | }
77 |
78 | #[cfg(feature = "staging")]
79 | pub fn get_kamino_program_id() -> Pubkey {
80 | Pubkey::from_str("SLendK7ySfcEzyaFqy93gDnD3RtrpXJcnRwb6zFHJSh").unwrap()
81 | }
82 |
83 | #[cfg(not(feature = "staging"))]
84 | pub fn get_kamino_program_id() -> Pubkey {
85 | Pubkey::from_str("KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD").unwrap()
86 | }
87 |
--------------------------------------------------------------------------------
/programs/vault/src/strategy/cypher.rs:
--------------------------------------------------------------------------------
1 | use crate::ID;
2 | use anchor_lang::prelude::Pubkey;
3 |
4 | /// Return strategy owner PDA
5 | pub fn get_strategy_owner(strategy_pubkey: &Pubkey) -> (Pubkey, u8) {
6 | Pubkey::find_program_address(&[b"cypher", strategy_pubkey.as_ref()], &ID)
7 | }
8 |
--------------------------------------------------------------------------------
/programs/vault/src/strategy/frakt.rs:
--------------------------------------------------------------------------------
1 | use crate::ID;
2 | use anchor_lang::prelude::Pubkey;
3 |
4 | /// return vault owner
5 | pub fn get_strategy_owner(strategy_pubkey: &Pubkey) -> (Pubkey, u8) {
6 | Pubkey::find_program_address(&["frakt".as_ref(), strategy_pubkey.as_ref()], &ID)
7 | }
8 |
--------------------------------------------------------------------------------
/programs/vault/src/strategy/mango.rs:
--------------------------------------------------------------------------------
1 | use crate::ID;
2 | use anchor_lang::prelude::Pubkey;
3 |
4 | /// Account num
5 | pub const ACCOUNT_NUM: u32 = 0u32;
6 |
7 | /// get strategy owner for mango
8 | pub fn get_strategy_owner(strategy_pubkey: &Pubkey) -> (Pubkey, u8) {
9 | Pubkey::find_program_address(&["mango".as_ref(), strategy_pubkey.as_ref()], &ID)
10 | }
11 |
--------------------------------------------------------------------------------
/programs/vault/src/strategy/marginfi.rs:
--------------------------------------------------------------------------------
1 | use crate::ID;
2 | use anchor_lang::solana_program::pubkey::Pubkey;
3 |
4 | /// return strategy owner
5 | pub fn get_strategy_owner(strategy: &Pubkey) -> (Pubkey, u8) {
6 | Pubkey::find_program_address(&["marginfi_strategy".as_ref(), strategy.as_ref()], &ID)
7 | }
8 |
9 | /// get_marginfi_account
10 | pub fn get_marginfi_account(strategy: &Pubkey) -> (Pubkey, u8) {
11 | Pubkey::find_program_address(&["marginfi_account".as_ref(), strategy.as_ref()], &ID)
12 | }
13 |
--------------------------------------------------------------------------------
/programs/vault/src/strategy/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod apricot_without_lm;
2 | pub mod base;
3 | pub mod cypher;
4 | pub mod frakt;
5 | pub mod mango;
6 | pub mod marginfi;
7 |
--------------------------------------------------------------------------------
/programs/vault/src/utils.rs:
--------------------------------------------------------------------------------
1 | use anchor_lang::prelude::Pubkey;
2 |
3 | use crate::seed;
4 |
5 | pub fn derive_vault_address(token_mint: Pubkey, base: Pubkey) -> (Pubkey, u8) {
6 | Pubkey::find_program_address(
7 | &[
8 | seed::VAULT_PREFIX.as_ref(),
9 | token_mint.as_ref(),
10 | base.as_ref(),
11 | ],
12 | &crate::ID,
13 | )
14 | }
15 |
16 | pub fn derive_token_vault_address(vault: Pubkey) -> (Pubkey, u8) {
17 | Pubkey::find_program_address(
18 | &[seed::TOKEN_VAULT_PREFIX.as_ref(), vault.as_ref()],
19 | &crate::ID,
20 | )
21 | }
22 |
23 | pub fn derive_strategy_address(vault: Pubkey, reserve: Pubkey, index: u8) -> (Pubkey, u8) {
24 | Pubkey::find_program_address(&[vault.as_ref(), reserve.as_ref(), &[index]], &crate::ID)
25 | }
26 |
27 | pub fn derive_collateral_vault_address(strategy: Pubkey) -> (Pubkey, u8) {
28 | Pubkey::find_program_address(
29 | &[seed::COLLATERAL_VAULT_PREFIX.as_ref(), strategy.as_ref()],
30 | &crate::ID,
31 | )
32 | }
33 |
--------------------------------------------------------------------------------
/rust-client/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "rust-client"
3 | version = "0.5.0"
4 | edition = "2018"
5 |
6 | [features]
7 | devnet = []
8 |
9 | [dependencies]
10 | anchor-lang = {version = "0.28.0"}
11 | anchor-spl = {version = "0.28.0"}
12 | anchor-client = {version = "0.28.0"}
13 | anyhow = "1.0"
14 | clap = {version = "3.0.10", features=["derive"]}
15 | mercurial-vault = { path = "../programs/vault", features = ["cpi"] }
16 | shellexpand = "2.1.0"
17 | spl-associated-token-account = { version = "2.1.0", features = ["no-entrypoint"] }
18 | rust_decimal="1.20.0"
19 | bincode = "^1.3.1"
--------------------------------------------------------------------------------
/rust-client/README.md:
--------------------------------------------------------------------------------
1 |
2 | # CLI for mercurial vault
3 |
4 | ## Build
5 |
6 | `cargo build`
7 |
8 | ## Command
9 |
10 | Check command with `../target/debug/rust-client --help`
11 |
12 | ```
13 | USAGE:
14 | rust-client [OPTIONS]
15 |
16 | OPTIONS:
17 | -h, --help Print help information
18 | --provider.admin
19 | --provider.base
20 | --provider.cluster Cluster override
21 | --provider.program_id Program id override
22 | --provider.token_mint Token mint override
23 | --provider.wallet Wallet override
24 |
25 | SUBCOMMANDS:
26 | deposit
27 | get-unlocked-amount
28 | help Print this message or the help of the given subcommand(s)
29 | show
30 | withdraw
31 | ```
32 |
33 |
34 | ## Example
35 |
36 | ```
37 | ../target/debug/rust-client show --provider.token_mint So11111111111111111111111111111111111111112
38 |
39 | ../target/debug/rust-client deposit 100 --provider.token_mint So11111111111111111111111111111111111111112
40 |
41 | ../target/debug/rust-client withdraw 100 --provider.token_mint So11111111111111111111111111111111111111112
42 | ```
--------------------------------------------------------------------------------
/rust-client/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod user;
2 |
--------------------------------------------------------------------------------
/rust-client/src/main.rs:
--------------------------------------------------------------------------------
1 | mod user;
2 | mod utils;
3 | use anchor_client::solana_sdk::commitment_config::CommitmentConfig;
4 | use anchor_client::solana_sdk::pubkey::Pubkey;
5 | use anchor_client::solana_sdk::signature::Signer;
6 | use anchor_client::solana_sdk::signature::{read_keypair_file, Keypair};
7 | use anchor_client::Client;
8 | use anchor_client::Cluster;
9 | use anchor_lang::solana_program::clock::Clock;
10 | use anchor_lang::solana_program::sysvar;
11 | use anyhow::Result;
12 | use bincode::deserialize;
13 | use clap::Parser;
14 | use mercurial_vault::get_base_key;
15 | use std::convert::TryFrom;
16 | use std::ops::Deref;
17 | use std::rc::Rc;
18 | use std::str::FromStr;
19 | use user::*;
20 |
21 | #[derive(Default, Debug, Parser)]
22 | pub struct ConfigOverride {
23 | /// Cluster override.
24 | #[clap(global = true, long = "provider.cluster")]
25 | pub cluster: Option,
26 | /// Wallet override.
27 | #[clap(global = true, long = "provider.wallet")]
28 | pub wallet: Option,
29 |
30 | /// Program id override
31 | #[clap(global = true, long = "provider.program_id")]
32 | pub program_id: Option,
33 |
34 | /// Token mint override
35 | #[clap(global = true, long = "provider.token_mint")]
36 | pub token_mint: Option,
37 |
38 | #[clap(global = true, long = "provider.base")]
39 | pub base: Option,
40 | }
41 |
42 | #[derive(Debug, Parser)]
43 | pub enum Command {
44 | Show {},
45 | GetUnlockedAmount {},
46 | #[clap(flatten)]
47 | User(UserCommand),
48 | }
49 |
50 | #[derive(Debug, Parser)]
51 | pub enum UserCommand {
52 | Deposit { token_amount: u64 },
53 | Withdraw { unmint_amount: u64 },
54 | }
55 |
56 | #[derive(Parser)]
57 | pub struct Opts {
58 | #[clap(flatten)]
59 | pub cfg_override: ConfigOverride,
60 | #[clap(subcommand)]
61 | pub command: Command,
62 | }
63 | fn main() -> Result<()> {
64 | let opts = Opts::parse();
65 |
66 | let payer = match opts.cfg_override.wallet {
67 | Some(wallet) => read_keypair_file(wallet).expect("Requires a keypair file"),
68 | None => default_keypair(),
69 | };
70 | let url = match opts.cfg_override.cluster {
71 | Some(cluster) => cluster,
72 | None => Cluster::Devnet,
73 | };
74 |
75 | let client = Client::new_with_options(
76 | url,
77 | Rc::new(Keypair::from_bytes(&payer.to_bytes())?),
78 | CommitmentConfig::processed(),
79 | );
80 |
81 | let program_id = match opts.cfg_override.program_id {
82 | Some(program_id) => Pubkey::from_str(&program_id).unwrap(),
83 | None => mercurial_vault::id(),
84 | };
85 |
86 | let program_client = client.program(program_id)?;
87 |
88 | let token_mint = match opts.cfg_override.token_mint {
89 | Some(token_mint) => Pubkey::from_str(&token_mint).unwrap(),
90 | None => Pubkey::default(),
91 | };
92 |
93 | let base = match opts.cfg_override.base {
94 | Some(base) => Pubkey::from_str(&base).unwrap(),
95 | None => get_base_key(),
96 | };
97 |
98 | let (vault, _) = mercurial_vault::utils::derive_vault_address(token_mint, base);
99 |
100 | println!("ProgramID {}", program_id);
101 | println!("TOKEN MINT {}", token_mint);
102 | println!("Base {}", base);
103 | println!("VAULT {}", vault);
104 |
105 | // Fee payer is the admin
106 | match opts.command {
107 | Command::Show {} => show(&program_client, vault)?,
108 | Command::GetUnlockedAmount {} => get_unlocked_amount(&program_client, vault, &payer)?,
109 | Command::User(user) => match user {
110 | UserCommand::Deposit { token_amount } => {
111 | deposit(&program_client, token_mint, base, token_amount)?
112 | }
113 | UserCommand::Withdraw { unmint_amount } => {
114 | withdraw(&program_client, token_mint, base, unmint_amount)?
115 | }
116 | },
117 | };
118 |
119 | Ok(())
120 | }
121 |
122 | fn show + Clone>(
123 | program_client: &anchor_client::Program,
124 | vault: Pubkey,
125 | ) -> Result<()> {
126 | let vault_data: mercurial_vault::state::Vault = program_client.account(vault)?;
127 | println!("VAULT DATA: {:#?}", vault_data);
128 | let token_mint: anchor_spl::token::Mint = program_client.account(vault_data.lp_mint)?;
129 |
130 | let current_timestamp = get_current_node_clock_time(program_client)?;
131 |
132 | println!(
133 | "TOTAL_AMOUNT: {}, TOTAL_UNLOCKED_AMOUNT: {}, lp_mint {}",
134 | vault_data.total_amount,
135 | vault_data.get_unlocked_amount(current_timestamp).unwrap(),
136 | token_mint.supply
137 | );
138 |
139 | let token_data: anchor_spl::token::TokenAccount =
140 | program_client.account(vault_data.token_vault)?;
141 |
142 | println!("TOKEN AMOUNT: {}", token_data.amount);
143 |
144 | let mut strategy_amount = 0u64;
145 | for (_i, &strategy_pubkey) in vault_data.strategies.iter().enumerate() {
146 | if strategy_pubkey != Pubkey::default() {
147 | let strategy_state: mercurial_vault::state::Strategy =
148 | program_client.account(strategy_pubkey)?;
149 |
150 | println!("STRATEGY DATA {}: {:#?}", strategy_pubkey, strategy_state);
151 |
152 | strategy_amount += strategy_state.current_liquidity;
153 | }
154 | }
155 | assert_eq!(vault_data.total_amount, token_data.amount + strategy_amount);
156 | println!("Ok");
157 | Ok(())
158 | }
159 |
160 | pub fn get_current_node_clock_time + Clone>(
161 | program_client: &anchor_client::Program,
162 | ) -> Result {
163 | let rpc = program_client.rpc();
164 | let clock_account = rpc.get_account(&sysvar::clock::id())?;
165 | let clock = deserialize::(&clock_account.data)?;
166 | let current_time = u64::try_from(clock.unix_timestamp)?;
167 | Ok(current_time)
168 | }
169 |
170 | fn get_unlocked_amount + Clone>(
171 | program_client: &anchor_client::Program,
172 | vault: Pubkey,
173 | payer: &Keypair,
174 | ) -> Result<()> {
175 | let builder = program_client
176 | .request()
177 | .accounts(mercurial_vault::accounts::GetUnlockedAmount { vault })
178 | .args(mercurial_vault::instruction::GetUnlockedAmount {});
179 |
180 | let simulation = utils::simulate_transaction(&builder, program_client, &vec![payer]).unwrap();
181 | let logs = simulation.value.logs.expect("No log in simulation found");
182 | let unlocked_amount: mercurial_vault::TotalAmount =
183 | utils::parse_event_log(&logs).expect("Event log not found");
184 | println!("UNLOCKED AMOUNT: {}", unlocked_amount.total_amount);
185 | Ok(())
186 | }
187 |
188 | pub fn default_keypair() -> Keypair {
189 | read_keypair_file(&*shellexpand::tilde("~/.config/solana/id.json"))
190 | .expect("Requires a keypair file")
191 | }
192 |
--------------------------------------------------------------------------------
/rust-client/src/user.rs:
--------------------------------------------------------------------------------
1 | use anchor_client::solana_sdk::signature::Signer;
2 | use anchor_client::solana_sdk::signer::keypair::Keypair;
3 | use anchor_client::solana_sdk::system_instruction;
4 | use anchor_lang::solana_program::program_pack::Pack;
5 | use anchor_lang::solana_program::pubkey::Pubkey;
6 | use anchor_spl::token::spl_token;
7 | use anyhow::Result;
8 | use std::ops::Deref;
9 |
10 | pub fn deposit + Clone>(
11 | program_client: &anchor_client::Program,
12 | token_mint: Pubkey,
13 | base: Pubkey,
14 | token_amount: u64,
15 | ) -> Result<()> {
16 | println!("deposit {}", token_amount);
17 |
18 | let (vault, _vault_bump) = mercurial_vault::utils::derive_vault_address(token_mint, base);
19 |
20 | let (token_vault, _token_vault_bump) =
21 | mercurial_vault::utils::derive_token_vault_address(vault);
22 |
23 | let vault_state: mercurial_vault::state::Vault = program_client.account(vault)?;
24 | let lp_mint = vault_state.lp_mint;
25 |
26 | let user_token = get_or_create_ata(program_client, token_mint, program_client.payer())?;
27 | let user_lp = get_or_create_ata(program_client, lp_mint, program_client.payer())?;
28 |
29 | let builder = program_client
30 | .request()
31 | .accounts(mercurial_vault::accounts::DepositWithdrawLiquidity {
32 | vault,
33 | token_vault,
34 | lp_mint,
35 | user_token,
36 | user_lp,
37 | user: program_client.payer(),
38 | token_program: spl_token::id(),
39 | })
40 | .args(mercurial_vault::instruction::Deposit {
41 | token_amount,
42 | minimum_lp_token_amount: 0,
43 | });
44 |
45 | let signature = builder.send()?;
46 | println!("{}", signature);
47 |
48 | Ok(())
49 | }
50 |
51 | pub fn withdraw + Clone>(
52 | program_client: &anchor_client::Program,
53 | token_mint: Pubkey,
54 | base: Pubkey,
55 | unmint_amount: u64,
56 | ) -> Result<()> {
57 | println!("withdraw {} lp token", unmint_amount);
58 |
59 | let (vault, _vault_bump) = mercurial_vault::utils::derive_vault_address(token_mint, base);
60 |
61 | let (token_vault, _token_vault_bump) =
62 | mercurial_vault::utils::derive_token_vault_address(vault);
63 |
64 | let vault_state: mercurial_vault::state::Vault = program_client.account(vault)?;
65 | let lp_mint = vault_state.lp_mint;
66 |
67 | let user_token = get_or_create_ata(program_client, token_mint, program_client.payer())?;
68 | let user_lp = get_or_create_ata(program_client, lp_mint, program_client.payer())?;
69 |
70 | let builder = program_client
71 | .request()
72 | .accounts(mercurial_vault::accounts::DepositWithdrawLiquidity {
73 | vault,
74 | token_vault,
75 | lp_mint,
76 | user_token,
77 | user_lp,
78 | user: program_client.payer(),
79 | token_program: spl_token::id(),
80 | })
81 | .args(mercurial_vault::instruction::Withdraw {
82 | unmint_amount,
83 | min_out_amount: 0,
84 | });
85 |
86 | let signature = builder.send()?;
87 | println!("{}", signature);
88 |
89 | Ok(())
90 | }
91 |
92 | pub fn get_or_create_ata + Clone>(
93 | program_client: &anchor_client::Program,
94 | token_mint: Pubkey,
95 | user: Pubkey,
96 | ) -> Result {
97 | let user_token_account =
98 | spl_associated_token_account::get_associated_token_address(&user, &token_mint);
99 | let rpc_client = program_client.rpc();
100 | if rpc_client.get_account_data(&user_token_account).is_err() {
101 | println!("Create ATA for TOKEN {} \n", &token_mint);
102 |
103 | let builder = program_client.request().instruction(
104 | spl_associated_token_account::create_associated_token_account(
105 | &program_client.payer(),
106 | &user,
107 | &token_mint,
108 | ),
109 | );
110 |
111 | let signature = builder.send()?;
112 | println!("{}", signature);
113 | }
114 | Ok(user_token_account)
115 | }
116 |
117 | pub fn create_mint + Clone>(
118 | program_client: &anchor_client::Program,
119 | mint_keypair: &Keypair,
120 | authority: Pubkey,
121 | decimals: u8,
122 | ) -> Result<()> {
123 | let rpc = program_client.rpc();
124 |
125 | let token_mint_account_rent =
126 | rpc.get_minimum_balance_for_rent_exemption(spl_token::state::Mint::LEN)?;
127 |
128 | let instructions = vec![
129 | system_instruction::create_account(
130 | &program_client.payer(),
131 | &mint_keypair.pubkey(),
132 | token_mint_account_rent,
133 | spl_token::state::Mint::LEN as u64,
134 | &spl_token::id(),
135 | ),
136 | spl_token::instruction::initialize_mint(
137 | &spl_token::id(),
138 | &mint_keypair.pubkey(),
139 | &authority,
140 | None,
141 | decimals,
142 | )
143 | .unwrap(),
144 | ];
145 |
146 | let builder = program_client.request();
147 | let builder = builder.signer(mint_keypair);
148 | let builder = instructions
149 | .into_iter()
150 | .fold(builder, |bld, ix| bld.instruction(ix));
151 | let signature = builder.send()?;
152 | println!("{}", signature);
153 | Ok(())
154 | }
155 |
--------------------------------------------------------------------------------
/rust-client/src/utils.rs:
--------------------------------------------------------------------------------
1 | use anchor_client::solana_client::rpc_response::RpcSimulateTransactionResult;
2 | use anchor_client::RequestBuilder;
3 | use anchor_client::{
4 | solana_client::rpc_response::Response,
5 | solana_sdk::{signature::Signer, transaction::Transaction},
6 | Program,
7 | };
8 | use core::ops::Deref;
9 |
10 | pub fn parse_event_log<
11 | T: anchor_lang::AnchorDeserialize + anchor_lang::AnchorSerialize + anchor_lang::Discriminator,
12 | >(
13 | logs: &Vec,
14 | ) -> Option {
15 | for log in logs.iter() {
16 | if log.starts_with("Program data:") {
17 | // Skip the prefix "Program data: "
18 | // Event logged has been changed to Program data: instead of Program log:
19 | // https://github.com/project-serum/anchor/pull/1608/files
20 | let log_info: String = log.chars().skip(14).collect();
21 | let log_buf = anchor_lang::__private::base64::decode(log_info.as_bytes());
22 | if log_buf.is_ok() {
23 | let log_buf = log_buf.unwrap();
24 | // Check for event discriminator, it is a 8-byte prefix
25 | if log_buf[0..8] == T::discriminator() {
26 | // Skip event discriminator when deserialize
27 | return T::try_from_slice(&log_buf[8..]).ok();
28 | }
29 | }
30 | }
31 | }
32 | None
33 | }
34 |
35 | pub fn simulate_transaction + Clone>(
36 | builder: &RequestBuilder,
37 | program: &Program,
38 | signers: &Vec<&dyn Signer>,
39 | ) -> Result, Box> {
40 | let instructions = builder.instructions()?;
41 | let rpc_client = program.rpc();
42 | let recent_blockhash = rpc_client.get_latest_blockhash()?;
43 | let tx = Transaction::new_signed_with_payer(
44 | &instructions,
45 | Some(&program.payer()),
46 | signers,
47 | recent_blockhash,
48 | );
49 | let simulation = rpc_client.simulate_transaction(&tx)?;
50 | Ok(simulation)
51 | }
52 |
--------------------------------------------------------------------------------
/rust-toolchain:
--------------------------------------------------------------------------------
1 | 1.70.0
--------------------------------------------------------------------------------
/ts-client/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "trailingComma": "all",
4 | "singleQuote": true,
5 | "printWidth": 120,
6 | "tabWidth": 2
7 | }
8 |
--------------------------------------------------------------------------------
/ts-client/README.md:
--------------------------------------------------------------------------------
1 | # Mercurial Vault SDK
2 |
3 |
4 |
5 |
6 |
7 |
8 | ## Getting started
9 |
10 | NPM: https://www.npmjs.com/package/@meteora-ag/vault-sdk
11 |
12 | SDK: https://github.com/meteora-ag/vault-sdk
13 |
14 | Docs: https://docs.mercurial.finance/mercurial-dynamic-yield-infra/
15 |
16 | Discord: https://discord.com/channels/841152225564950528/864859354335412224
17 |
18 |
19 |
20 | ## Install
21 |
22 | 1. Install deps
23 |
24 | ```
25 | npm i @meteora-ag/vault-sdk @coral-xyz/anchor @solana/web3.js@1 @solana/spl-token
26 | ```
27 |
28 | 2. Initialize VaultImpl instance
29 |
30 | - Affiliate or partner? refer to the [Vault Affiliate Program]()
31 |
32 | ```ts
33 | import VaultImpl from '@meteora-ag/vault-sdk';
34 | import { PublicKey } from '@solana/web3.js';
35 | import { NATIVE_MINT } from "@solana/spl-token";
36 | import { Wallet, AnchorProvider } from '@coral-xyz/anchor';
37 |
38 | // Connection, Wallet, and AnchorProvider to interact with the network
39 | const mainnetConnection = new Connection('https://api.mainnet-beta.solana.com');
40 | const mockWallet = new Wallet(new Keypair());
41 | const provider = new AnchorProvider(mainnetConnection, mockWallet, {
42 | commitment: 'confirmed',
43 | });
44 |
45 | const vaultImpl = await VaultImpl.create(connection, NATIVE_MINT);
46 | ```
47 |
48 | 3. To interact with the VaultImpl
49 |
50 | ```ts
51 | // To refetch the vault's latest supply
52 | // Alternatively, use `vaultImpl.lpSupply`
53 | const lpSupply = await vaultImpl.getVaultSupply();
54 |
55 | // Rewards are not instantly redeemable, and are subject to a lock.
56 | // This function returns the amount of LP that are redeemable.
57 | const unlockedAmount = await vaultImpl.getWithdrawableAmount();
58 |
59 | // To deposit into the vault
60 | const amountInLamports = 1 * 10 ** 9; // 1.0 SOL
61 | const depositTx = await vaultImpl.deposit(mockWallet.publicKey, new BN(amountInLamports)); // Web3 Transaction Object
62 | const depositResult = await provider.sendAndConfirm(depositTx); // Transaction hash
63 |
64 | // Get the user's ATA LP balance
65 | const userLpBalance = await vaultImpl.getUserBalance(mockWallet.publicKey);
66 |
67 | // To withdraw from the vault
68 | const withdrawTx = await vaultImpl.withdraw(mockWallet.publicKey, new BN(userLpBalance)); // Web3 Transaction Object
69 | const withdrawResult = await provider.sendAndConfirm(withdrawTx); // Transaction hash
70 | ```
71 |
72 | 4. Helper function
73 |
74 | ```ts
75 | import { helper } from '@meteora-ag/vault-sdk';
76 |
77 | const userShare = await vaultImpl.getUserBalance(mockWallet.publicKey);
78 | const unlockedAmount = await vaultImpl.getWithdrawableAmount();
79 | const lpSupply = await vaultImpl.getVaultSupply();
80 |
81 | // To convert user's LP balance into underlying token amount
82 | const underlyingShare = helper.getAmountByShare(userShare, unlockedAmount, lpSupply);
83 |
84 | // To convert underlying token amount into user's LP balance
85 | const amountInLamports = 1 * 10 ** 9; // 1.0 SOL
86 | const lpToUnmint = helper.getUnmintAmount(new BN(amountInLamports), unlockedAmount, lpSupply); // To withdraw 1.0 SOL
87 | ```
88 |
89 |
90 |
91 | ## Vault Affiliate
92 |
93 | To be a part of the Mercurial Finance's Vault Affiliate Program, visit our Discord above!
94 |
95 |
96 |
97 | #### To initialize vault with affiliate
98 |
99 | Affiliates only need to initialize the vault instance with the third paratemer `opt.affiliate`, subsequently, all interaction with the vault are the same as the usage guide above, no further configuration required.
100 |
101 | ```ts
102 | const vaultImpl = await VaultImpl.create(
103 | connection,
104 | NATIVE_MINT,
105 | {
106 | affiliateId: new PublicKey('YOUR_PARTNER_PUBLIC_KEY');
107 | }
108 | );
109 | ```
110 |
111 | #### To check Partner info
112 |
113 | ```ts
114 | // Affiliate / Partner info
115 | const partnerInfo = await vaultImpl.getAffiliateInfo();
116 | ```
117 |
--------------------------------------------------------------------------------
/ts-client/index.ts:
--------------------------------------------------------------------------------
1 | import VaultImpl from './src/vault/index';
2 | import { PROGRAM_ID, AFFILIATE_PROGRAM_ID, KEEPER_URL } from './src/vault/constants';
3 | import { getVaultPdas, getOnchainTime, getLpSupply } from './src/vault/utils';
4 | import { getAmountByShare, getUnmintAmount, calculateWithdrawableAmount } from './src/vault/helper';
5 | import { IDL } from './src/vault/idl';
6 |
7 | export default VaultImpl;
8 | export {
9 | IDL,
10 | // Constant
11 | PROGRAM_ID,
12 | AFFILIATE_PROGRAM_ID,
13 | KEEPER_URL,
14 | // Utils
15 | getVaultPdas,
16 | getOnchainTime,
17 | getLpSupply,
18 | // Helper
19 | getAmountByShare,
20 | getUnmintAmount,
21 | calculateWithdrawableAmount,
22 | };
23 |
24 | export type { VaultImplementation, VaultState, AffiliateInfo, ParsedClockState } from './src/vault/types';
25 | export type { StrategyType } from './src/vault/strategy';
26 | export type { Vault as VaultIdl } from './src/vault/idl';
27 |
--------------------------------------------------------------------------------
/ts-client/jest.config.js:
--------------------------------------------------------------------------------
1 | const TIMEOUT_SEC = 1000;
2 |
3 | module.exports = {
4 | preset: "ts-jest",
5 | testEnvironment: "node",
6 | transform: {
7 | "^.+\\.ts?$": "ts-jest",
8 | },
9 | transformIgnorePatterns: ["/node_modules/"],
10 | testTimeout: TIMEOUT_SEC * 90,
11 | };
12 |
--------------------------------------------------------------------------------
/ts-client/lib.es5.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Fixes https://github.com/microsoft/TypeScript/issues/16655 for `Array.prototype.filter()`
3 | * For example, using the fix the type of `bar` is `string[]` in the below snippet as it should be.
4 | *
5 | * const foo: (string | null | undefined)[] = [];
6 | * const bar = foo.filter(Boolean);
7 | *
8 | * For related definitions, see https://github.com/microsoft/TypeScript/blob/master/src/lib/es5.d.ts
9 | *
10 | * Original licenses apply, see
11 | * - https://github.com/microsoft/TypeScript/blob/master/LICENSE.txt
12 | * - https://stackoverflow.com/help/licensing
13 | */
14 |
15 | /** See https://stackoverflow.com/a/51390763/1470607 */
16 | type Falsy = false | 0 | '' | null | undefined;
17 |
18 | interface Array {
19 | /**
20 | * Returns the elements of an array that meet the condition specified in a callback function.
21 | * @param predicate A function that accepts up to three arguments. The filter method calls the predicate function one time for each element in the array.
22 | * @param thisArg An object to which the this keyword can refer in the predicate function. If thisArg is omitted, undefined is used as the this value.
23 | */
24 | filter(predicate: BooleanConstructor, thisArg?: any): Exclude[];
25 | }
26 |
--------------------------------------------------------------------------------
/ts-client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@meteora-ag/vault-sdk",
3 | "version": "2.3.1",
4 | "description": "Mercurial Vault SDK is a typescript library that allows you to interact with Meteora's dynamic vault.",
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/vault/tests/*.test.ts --runInBand",
10 | "test-affiliate": "jest ./src/vault/tests/affiliate.test.ts --runInBand"
11 | },
12 | "files": [
13 | "dist"
14 | ],
15 | "keywords": [],
16 | "author": "meteora-ag",
17 | "license": "MIT",
18 | "dependencies": {
19 | "@coral-xyz/anchor": "^0.28.0",
20 | "@solana/buffer-layout": "^4.0.1",
21 | "@solana/spl-token": "^0.4.6",
22 | "@solana/web3.js": "^1.91.6",
23 | "bn.js": "5.2.1",
24 | "cross-fetch": "^3.1.5",
25 | "decimal.js": "10.3.1",
26 | "jsbi": "4.3.0"
27 | },
28 | "devDependencies": {
29 | "@tsconfig/recommended": "^1.0.1",
30 | "@types/bn.js": "^5.1.0",
31 | "@types/chai": "^4.3.1",
32 | "@types/jest": "^27.5.1",
33 | "@types/mocha": "^9.1.1",
34 | "chai": "^4.3.6",
35 | "jest": "^28.1.0",
36 | "mocha": "^10.0.0",
37 | "ts-jest": "^28.0.2",
38 | "ts-mocha": "^10.0.0",
39 | "typescript": "^5.5.4"
40 | },
41 | "directories": {
42 | "test": "tests"
43 | },
44 | "repository": {
45 | "type": "git",
46 | "url": "git+https://github.com/MeteoraAg/vault-sdk.git"
47 | },
48 | "bugs": {
49 | "url": "https://github.com/MeteoraAg/vault-sdk/issues"
50 | },
51 | "homepage": "https://github.com/MeteoraAg/vault-sdk#readme"
52 | }
53 |
--------------------------------------------------------------------------------
/ts-client/src/vault/affiliate-idl.ts:
--------------------------------------------------------------------------------
1 | export type AffiliateVault = {
2 | version: '0.1.0';
3 | name: 'affiliate-vault';
4 | instructions: [
5 | {
6 | name: 'initPartner';
7 | accounts: [
8 | {
9 | name: 'partner';
10 | isMut: true;
11 | isSigner: false;
12 | },
13 | {
14 | name: 'vault';
15 | isMut: false;
16 | isSigner: false;
17 | },
18 | {
19 | name: 'partnerToken';
20 | isMut: false;
21 | isSigner: false;
22 | },
23 | {
24 | name: 'admin';
25 | isMut: true;
26 | isSigner: true;
27 | },
28 | {
29 | name: 'systemProgram';
30 | isMut: false;
31 | isSigner: false;
32 | },
33 | {
34 | name: 'rent';
35 | isMut: false;
36 | isSigner: false;
37 | },
38 | {
39 | name: 'tokenProgram';
40 | isMut: false;
41 | isSigner: false;
42 | },
43 | ];
44 | args: [];
45 | },
46 | {
47 | name: 'updateFeeRatio';
48 | accounts: [
49 | {
50 | name: 'partner';
51 | isMut: true;
52 | isSigner: false;
53 | },
54 | {
55 | name: 'admin';
56 | isMut: false;
57 | isSigner: true;
58 | },
59 | ];
60 | args: [
61 | {
62 | name: 'feeRatio';
63 | type: 'u64';
64 | },
65 | ];
66 | },
67 | {
68 | name: 'initUser';
69 | accounts: [
70 | {
71 | name: 'user';
72 | isMut: true;
73 | isSigner: false;
74 | },
75 | {
76 | name: 'partner';
77 | isMut: false;
78 | isSigner: false;
79 | },
80 | {
81 | name: 'owner';
82 | isMut: true;
83 | isSigner: true;
84 | },
85 | {
86 | name: 'systemProgram';
87 | isMut: false;
88 | isSigner: false;
89 | },
90 | {
91 | name: 'rent';
92 | isMut: false;
93 | isSigner: false;
94 | },
95 | ];
96 | args: [];
97 | },
98 | {
99 | name: 'deposit';
100 | accounts: [
101 | {
102 | name: 'partner';
103 | isMut: true;
104 | isSigner: false;
105 | },
106 | {
107 | name: 'user';
108 | isMut: true;
109 | isSigner: false;
110 | },
111 | {
112 | name: 'vaultProgram';
113 | isMut: false;
114 | isSigner: false;
115 | },
116 | {
117 | name: 'vault';
118 | isMut: true;
119 | isSigner: false;
120 | },
121 | {
122 | name: 'tokenVault';
123 | isMut: true;
124 | isSigner: false;
125 | },
126 | {
127 | name: 'vaultLpMint';
128 | isMut: true;
129 | isSigner: false;
130 | },
131 | {
132 | name: 'userToken';
133 | isMut: true;
134 | isSigner: false;
135 | },
136 | {
137 | name: 'userLp';
138 | isMut: true;
139 | isSigner: false;
140 | },
141 | {
142 | name: 'owner';
143 | isMut: false;
144 | isSigner: true;
145 | },
146 | {
147 | name: 'tokenProgram';
148 | isMut: false;
149 | isSigner: false;
150 | },
151 | ];
152 | args: [
153 | {
154 | name: 'tokenAmount';
155 | type: 'u64';
156 | },
157 | {
158 | name: 'minimumLpTokenAmount';
159 | type: 'u64';
160 | },
161 | ];
162 | },
163 | {
164 | name: 'withdraw';
165 | accounts: [
166 | {
167 | name: 'partner';
168 | isMut: true;
169 | isSigner: false;
170 | },
171 | {
172 | name: 'user';
173 | isMut: true;
174 | isSigner: false;
175 | },
176 | {
177 | name: 'vaultProgram';
178 | isMut: false;
179 | isSigner: false;
180 | },
181 | {
182 | name: 'vault';
183 | isMut: true;
184 | isSigner: false;
185 | },
186 | {
187 | name: 'tokenVault';
188 | isMut: true;
189 | isSigner: false;
190 | },
191 | {
192 | name: 'vaultLpMint';
193 | isMut: true;
194 | isSigner: false;
195 | },
196 | {
197 | name: 'userToken';
198 | isMut: true;
199 | isSigner: false;
200 | },
201 | {
202 | name: 'userLp';
203 | isMut: true;
204 | isSigner: false;
205 | },
206 | {
207 | name: 'owner';
208 | isMut: false;
209 | isSigner: true;
210 | },
211 | {
212 | name: 'tokenProgram';
213 | isMut: false;
214 | isSigner: false;
215 | },
216 | ];
217 | args: [
218 | {
219 | name: 'unmintAmount';
220 | type: 'u64';
221 | },
222 | {
223 | name: 'minOutAmount';
224 | type: 'u64';
225 | },
226 | ];
227 | },
228 | {
229 | name: 'withdrawDirectlyFromStrategy';
230 | accounts: [
231 | {
232 | name: 'partner';
233 | isMut: true;
234 | isSigner: false;
235 | },
236 | {
237 | name: 'user';
238 | isMut: true;
239 | isSigner: false;
240 | },
241 | {
242 | name: 'vaultProgram';
243 | isMut: false;
244 | isSigner: false;
245 | },
246 | {
247 | name: 'vault';
248 | isMut: true;
249 | isSigner: false;
250 | },
251 | {
252 | name: 'strategy';
253 | isMut: true;
254 | isSigner: false;
255 | },
256 | {
257 | name: 'reserve';
258 | isMut: true;
259 | isSigner: false;
260 | },
261 | {
262 | name: 'strategyProgram';
263 | isMut: false;
264 | isSigner: false;
265 | },
266 | {
267 | name: 'collateralVault';
268 | isMut: true;
269 | isSigner: false;
270 | },
271 | {
272 | name: 'tokenVault';
273 | isMut: true;
274 | isSigner: false;
275 | },
276 | {
277 | name: 'vaultLpMint';
278 | isMut: true;
279 | isSigner: false;
280 | },
281 | {
282 | name: 'feeVault';
283 | isMut: true;
284 | isSigner: false;
285 | },
286 | {
287 | name: 'userToken';
288 | isMut: true;
289 | isSigner: false;
290 | },
291 | {
292 | name: 'userLp';
293 | isMut: true;
294 | isSigner: false;
295 | },
296 | {
297 | name: 'owner';
298 | isMut: false;
299 | isSigner: true;
300 | },
301 | {
302 | name: 'tokenProgram';
303 | isMut: false;
304 | isSigner: false;
305 | },
306 | ];
307 | args: [
308 | {
309 | name: 'unmintAmount';
310 | type: 'u64';
311 | },
312 | {
313 | name: 'minOutAmount';
314 | type: 'u64';
315 | },
316 | ];
317 | },
318 | {
319 | name: 'fundPartner';
320 | accounts: [
321 | {
322 | name: 'partner';
323 | isMut: true;
324 | isSigner: false;
325 | },
326 | {
327 | name: 'partnerToken';
328 | isMut: true;
329 | isSigner: false;
330 | },
331 | {
332 | name: 'funderToken';
333 | isMut: true;
334 | isSigner: false;
335 | },
336 | {
337 | name: 'funder';
338 | isMut: false;
339 | isSigner: true;
340 | },
341 | {
342 | name: 'tokenProgram';
343 | isMut: false;
344 | isSigner: false;
345 | },
346 | ];
347 | args: [
348 | {
349 | name: 'amount';
350 | type: 'u64';
351 | },
352 | ];
353 | },
354 | ];
355 | accounts: [
356 | {
357 | name: 'partner';
358 | type: {
359 | kind: 'struct';
360 | fields: [
361 | {
362 | name: 'partnerToken';
363 | type: 'publicKey';
364 | },
365 | {
366 | name: 'vault';
367 | type: 'publicKey';
368 | },
369 | {
370 | name: 'outstandingFee';
371 | type: 'u64';
372 | },
373 | {
374 | name: 'feeRatio';
375 | type: 'u64';
376 | },
377 | {
378 | name: 'cummulativeFee';
379 | type: 'u128';
380 | },
381 | ];
382 | };
383 | },
384 | {
385 | name: 'user';
386 | type: {
387 | kind: 'struct';
388 | fields: [
389 | {
390 | name: 'owner';
391 | type: 'publicKey';
392 | },
393 | {
394 | name: 'partner';
395 | type: 'publicKey';
396 | },
397 | {
398 | name: 'currentVirtualPrice';
399 | type: 'u64';
400 | },
401 | {
402 | name: 'lpToken';
403 | type: 'u64';
404 | },
405 | {
406 | name: 'bump';
407 | type: 'u8';
408 | },
409 | ];
410 | };
411 | },
412 | ];
413 | events: [
414 | {
415 | name: 'ParnerFee';
416 | fields: [
417 | {
418 | name: 'fee';
419 | type: 'u64';
420 | index: false;
421 | },
422 | ];
423 | },
424 | ];
425 | errors: [
426 | {
427 | code: 6000;
428 | name: 'MathOverflow';
429 | msg: 'Math operation overflow';
430 | },
431 | {
432 | code: 6001;
433 | name: 'InvalidOwner';
434 | msg: 'Invalid owner';
435 | },
436 | {
437 | code: 6002;
438 | name: 'InvalidFeeRatio';
439 | msg: 'Invalid ratio';
440 | },
441 | {
442 | code: 6003;
443 | name: 'WrongFunderToken';
444 | msg: 'Funder token account must be different from partner token account';
445 | },
446 | ];
447 | };
448 |
449 | export const IDL: AffiliateVault = {
450 | version: '0.1.0',
451 | name: 'affiliate-vault',
452 | instructions: [
453 | {
454 | name: 'initPartner',
455 | accounts: [
456 | {
457 | name: 'partner',
458 | isMut: true,
459 | isSigner: false,
460 | },
461 | {
462 | name: 'vault',
463 | isMut: false,
464 | isSigner: false,
465 | },
466 | {
467 | name: 'partnerToken',
468 | isMut: false,
469 | isSigner: false,
470 | },
471 | {
472 | name: 'admin',
473 | isMut: true,
474 | isSigner: true,
475 | },
476 | {
477 | name: 'systemProgram',
478 | isMut: false,
479 | isSigner: false,
480 | },
481 | {
482 | name: 'rent',
483 | isMut: false,
484 | isSigner: false,
485 | },
486 | {
487 | name: 'tokenProgram',
488 | isMut: false,
489 | isSigner: false,
490 | },
491 | ],
492 | args: [],
493 | },
494 | {
495 | name: 'updateFeeRatio',
496 | accounts: [
497 | {
498 | name: 'partner',
499 | isMut: true,
500 | isSigner: false,
501 | },
502 | {
503 | name: 'admin',
504 | isMut: false,
505 | isSigner: true,
506 | },
507 | ],
508 | args: [
509 | {
510 | name: 'feeRatio',
511 | type: 'u64',
512 | },
513 | ],
514 | },
515 | {
516 | name: 'initUser',
517 | accounts: [
518 | {
519 | name: 'user',
520 | isMut: true,
521 | isSigner: false,
522 | },
523 | {
524 | name: 'partner',
525 | isMut: false,
526 | isSigner: false,
527 | },
528 | {
529 | name: 'owner',
530 | isMut: true,
531 | isSigner: true,
532 | },
533 | {
534 | name: 'systemProgram',
535 | isMut: false,
536 | isSigner: false,
537 | },
538 | {
539 | name: 'rent',
540 | isMut: false,
541 | isSigner: false,
542 | },
543 | ],
544 | args: [],
545 | },
546 | {
547 | name: 'deposit',
548 | accounts: [
549 | {
550 | name: 'partner',
551 | isMut: true,
552 | isSigner: false,
553 | },
554 | {
555 | name: 'user',
556 | isMut: true,
557 | isSigner: false,
558 | },
559 | {
560 | name: 'vaultProgram',
561 | isMut: false,
562 | isSigner: false,
563 | },
564 | {
565 | name: 'vault',
566 | isMut: true,
567 | isSigner: false,
568 | },
569 | {
570 | name: 'tokenVault',
571 | isMut: true,
572 | isSigner: false,
573 | },
574 | {
575 | name: 'vaultLpMint',
576 | isMut: true,
577 | isSigner: false,
578 | },
579 | {
580 | name: 'userToken',
581 | isMut: true,
582 | isSigner: false,
583 | },
584 | {
585 | name: 'userLp',
586 | isMut: true,
587 | isSigner: false,
588 | },
589 | {
590 | name: 'owner',
591 | isMut: false,
592 | isSigner: true,
593 | },
594 | {
595 | name: 'tokenProgram',
596 | isMut: false,
597 | isSigner: false,
598 | },
599 | ],
600 | args: [
601 | {
602 | name: 'tokenAmount',
603 | type: 'u64',
604 | },
605 | {
606 | name: 'minimumLpTokenAmount',
607 | type: 'u64',
608 | },
609 | ],
610 | },
611 | {
612 | name: 'withdraw',
613 | accounts: [
614 | {
615 | name: 'partner',
616 | isMut: true,
617 | isSigner: false,
618 | },
619 | {
620 | name: 'user',
621 | isMut: true,
622 | isSigner: false,
623 | },
624 | {
625 | name: 'vaultProgram',
626 | isMut: false,
627 | isSigner: false,
628 | },
629 | {
630 | name: 'vault',
631 | isMut: true,
632 | isSigner: false,
633 | },
634 | {
635 | name: 'tokenVault',
636 | isMut: true,
637 | isSigner: false,
638 | },
639 | {
640 | name: 'vaultLpMint',
641 | isMut: true,
642 | isSigner: false,
643 | },
644 | {
645 | name: 'userToken',
646 | isMut: true,
647 | isSigner: false,
648 | },
649 | {
650 | name: 'userLp',
651 | isMut: true,
652 | isSigner: false,
653 | },
654 | {
655 | name: 'owner',
656 | isMut: false,
657 | isSigner: true,
658 | },
659 | {
660 | name: 'tokenProgram',
661 | isMut: false,
662 | isSigner: false,
663 | },
664 | ],
665 | args: [
666 | {
667 | name: 'unmintAmount',
668 | type: 'u64',
669 | },
670 | {
671 | name: 'minOutAmount',
672 | type: 'u64',
673 | },
674 | ],
675 | },
676 | {
677 | name: 'withdrawDirectlyFromStrategy',
678 | accounts: [
679 | {
680 | name: 'partner',
681 | isMut: true,
682 | isSigner: false,
683 | },
684 | {
685 | name: 'user',
686 | isMut: true,
687 | isSigner: false,
688 | },
689 | {
690 | name: 'vaultProgram',
691 | isMut: false,
692 | isSigner: false,
693 | },
694 | {
695 | name: 'vault',
696 | isMut: true,
697 | isSigner: false,
698 | },
699 | {
700 | name: 'strategy',
701 | isMut: true,
702 | isSigner: false,
703 | },
704 | {
705 | name: 'reserve',
706 | isMut: true,
707 | isSigner: false,
708 | },
709 | {
710 | name: 'strategyProgram',
711 | isMut: false,
712 | isSigner: false,
713 | },
714 | {
715 | name: 'collateralVault',
716 | isMut: true,
717 | isSigner: false,
718 | },
719 | {
720 | name: 'tokenVault',
721 | isMut: true,
722 | isSigner: false,
723 | },
724 | {
725 | name: 'vaultLpMint',
726 | isMut: true,
727 | isSigner: false,
728 | },
729 | {
730 | name: 'feeVault',
731 | isMut: true,
732 | isSigner: false,
733 | },
734 | {
735 | name: 'userToken',
736 | isMut: true,
737 | isSigner: false,
738 | },
739 | {
740 | name: 'userLp',
741 | isMut: true,
742 | isSigner: false,
743 | },
744 | {
745 | name: 'owner',
746 | isMut: false,
747 | isSigner: true,
748 | },
749 | {
750 | name: 'tokenProgram',
751 | isMut: false,
752 | isSigner: false,
753 | },
754 | ],
755 | args: [
756 | {
757 | name: 'unmintAmount',
758 | type: 'u64',
759 | },
760 | {
761 | name: 'minOutAmount',
762 | type: 'u64',
763 | },
764 | ],
765 | },
766 | {
767 | name: 'fundPartner',
768 | accounts: [
769 | {
770 | name: 'partner',
771 | isMut: true,
772 | isSigner: false,
773 | },
774 | {
775 | name: 'partnerToken',
776 | isMut: true,
777 | isSigner: false,
778 | },
779 | {
780 | name: 'funderToken',
781 | isMut: true,
782 | isSigner: false,
783 | },
784 | {
785 | name: 'funder',
786 | isMut: false,
787 | isSigner: true,
788 | },
789 | {
790 | name: 'tokenProgram',
791 | isMut: false,
792 | isSigner: false,
793 | },
794 | ],
795 | args: [
796 | {
797 | name: 'amount',
798 | type: 'u64',
799 | },
800 | ],
801 | },
802 | ],
803 | accounts: [
804 | {
805 | name: 'partner',
806 | type: {
807 | kind: 'struct',
808 | fields: [
809 | {
810 | name: 'partnerToken',
811 | type: 'publicKey',
812 | },
813 | {
814 | name: 'vault',
815 | type: 'publicKey',
816 | },
817 | {
818 | name: 'outstandingFee',
819 | type: 'u64',
820 | },
821 | {
822 | name: 'feeRatio',
823 | type: 'u64',
824 | },
825 | {
826 | name: 'cummulativeFee',
827 | type: 'u128',
828 | },
829 | ],
830 | },
831 | },
832 | {
833 | name: 'user',
834 | type: {
835 | kind: 'struct',
836 | fields: [
837 | {
838 | name: 'owner',
839 | type: 'publicKey',
840 | },
841 | {
842 | name: 'partner',
843 | type: 'publicKey',
844 | },
845 | {
846 | name: 'currentVirtualPrice',
847 | type: 'u64',
848 | },
849 | {
850 | name: 'lpToken',
851 | type: 'u64',
852 | },
853 | {
854 | name: 'bump',
855 | type: 'u8',
856 | },
857 | ],
858 | },
859 | },
860 | ],
861 | events: [
862 | {
863 | name: 'ParnerFee',
864 | fields: [
865 | {
866 | name: 'fee',
867 | type: 'u64',
868 | index: false,
869 | },
870 | ],
871 | },
872 | ],
873 | errors: [
874 | {
875 | code: 6000,
876 | name: 'MathOverflow',
877 | msg: 'Math operation overflow',
878 | },
879 | {
880 | code: 6001,
881 | name: 'InvalidOwner',
882 | msg: 'Invalid owner',
883 | },
884 | {
885 | code: 6002,
886 | name: 'InvalidFeeRatio',
887 | msg: 'Invalid ratio',
888 | },
889 | {
890 | code: 6003,
891 | name: 'WrongFunderToken',
892 | msg: 'Funder token account must be different from partner token account',
893 | },
894 | ],
895 | };
896 |
--------------------------------------------------------------------------------
/ts-client/src/vault/constants.ts:
--------------------------------------------------------------------------------
1 | import { Cluster, PublicKey } from '@solana/web3.js';
2 | import { BN } from '@coral-xyz/anchor';
3 |
4 | export const PROGRAM_ID = '24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi';
5 | export const AFFILIATE_PROGRAM_ID = 'GacY9YuN16HNRTy7ZWwULPccwvfFSBeNLuAQP7y38Du3';
6 |
7 | export const REWARDER = 'GuHrjvzqDvLTB27ebd9iFKwceCxKvSswzTByDQUTsvdm';
8 |
9 | // Mainnet addresses
10 | export const USDC_MINT = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
11 | export const USDT_MINT = new PublicKey('Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB');
12 |
13 | export const VAULT_BASE_KEY = new PublicKey('HWzXGcGHy4tcpYfaRDCyLNzXqBTv3E6BttpCH2vJxArv');
14 |
15 | export const SEEDS = Object.freeze({
16 | VAULT_PREFIX: 'vault',
17 | TOKEN_VAULT_PREFIX: 'token_vault',
18 | LP_MINT_PREFIX: 'lp_mint',
19 | COLLATERAL_VAULT_PREFIX: 'collateral_vault',
20 | OBLIGATION_PREFIX: 'obligation',
21 | OBLIGATION_OWNER_PREFIX: 'obligation_owner',
22 | STAKING_PREFIX: 'staking',
23 | MINER: 'Miner',
24 | QUARRY: 'Quarry',
25 | APRICOT_USER_INFO_SIGNER_PREFIX: 'apricot_user_info_signer',
26 | FRAKT: 'frakt',
27 | DEPOSIT: 'deposit',
28 | FRAKT_LENDING: 'nftlendingv2',
29 | CYPHER: 'cypher',
30 | MANGO_ACCOUNT: 'MangoAccount',
31 | MANGO: 'mango',
32 | PSYLEND: 'psylend',
33 | PSYLEND_OWNER: 'deposits',
34 | MARGINFI_STRATEGY: 'marginfi_strategy',
35 | MARGINFI_ACCOUNT: 'marginfi_account',
36 | });
37 |
38 | export const StrategyProgram: Record<
39 | Cluster,
40 | {
41 | solend: PublicKey;
42 | portFinance: PublicKey;
43 | }
44 | > = {
45 | testnet: {
46 | solend: new PublicKey('ALend7Ketfx5bxh6ghsCDXAoDrhvEmsXT3cynB6aPLgx'),
47 | portFinance: new PublicKey('pdQ2rQQU5zH2rDgZ7xH2azMBJegUzUyunJ5Jd637hC4'),
48 | },
49 | devnet: {
50 | solend: new PublicKey('ALend7Ketfx5bxh6ghsCDXAoDrhvEmsXT3cynB6aPLgx'),
51 | portFinance: new PublicKey('pdQ2rQQU5zH2rDgZ7xH2azMBJegUzUyunJ5Jd637hC4'),
52 | },
53 | 'mainnet-beta': {
54 | solend: new PublicKey('So1endDq2YkqhipRh3WViPa8hdiSpxWy6z3Z6tMCpAo'),
55 | portFinance: new PublicKey('Port7uDYB3wk6GJAw4KT1WpTeMtSu9bTcChBHkX2LfR'),
56 | },
57 | };
58 |
59 | export const KEEPER_URL: Record = {
60 | testnet: 'https://staging-keeper.raccoons.dev',
61 | devnet: 'https://dev-keeper.raccoons.dev',
62 | 'mainnet-beta': 'https://merv2-api.mercurial.finance',
63 | };
64 |
65 | export const VAULT_STRATEGY_ADDRESS = '11111111111111111111111111111111';
66 | export const LOCKED_PROFIT_DEGRADATION_DENOMINATOR = new BN(1_000_000_000_000);
67 |
--------------------------------------------------------------------------------
/ts-client/src/vault/helper/index.ts:
--------------------------------------------------------------------------------
1 | import BN from 'bn.js';
2 | import { LOCKED_PROFIT_DEGRADATION_DENOMINATOR } from '../constants';
3 | import { VaultState } from '../types';
4 |
5 | /**
6 | *
7 | * @param share - user's share
8 | * @param withdrawableAmount - vault's withdrawable amount, vaultImpl.getWithdrawableAmount()
9 | * @param totalSupply - vault's total lp supply
10 | * @returns
11 | */
12 | export function getAmountByShare(share: BN, withdrawableAmount: BN, totalSupply: BN): BN {
13 | return totalSupply.isZero() ? new BN(0) : share.mul(withdrawableAmount).div(totalSupply);
14 | }
15 |
16 | /**
17 | *
18 | * @param amount - amount of desired underlying token to unstake
19 | * @param withdrawableAmount - vault's withdrawable amount, vaultImpl.getWithdrawableAmount()
20 | * @param totalSupply - vault's total lp supply, vaultImpl.lpSupply
21 | * @returns BN
22 | */
23 | export function getUnmintAmount(amount: BN, withdrawableAmount: BN, totalSupply: BN) {
24 | return amount.mul(totalSupply).div(withdrawableAmount);
25 | }
26 |
27 | /**
28 | * `calculateWithdrawableAmount` calculates the amount of funds that can be withdrawn from a vault with vault state provided
29 | * @param {number} onChainTime - the current time on the blockchain
30 | * @param {VaultState} vaultState - VaultState
31 | * @returns The amount of the vault that can be withdrawn.
32 | */
33 | export function calculateWithdrawableAmount(onChainTime: number, vaultState: VaultState) {
34 | const {
35 | lockedProfitTracker: { lastReport, lockedProfitDegradation, lastUpdatedLockedProfit },
36 | totalAmount: vaultTotalAmount,
37 | } = vaultState;
38 |
39 | const duration = new BN(onChainTime).sub(lastReport);
40 |
41 | const lockedFundRatio = duration.mul(lockedProfitDegradation);
42 | if (lockedFundRatio.gt(LOCKED_PROFIT_DEGRADATION_DENOMINATOR)) {
43 | return vaultTotalAmount;
44 | }
45 |
46 | const lockedProfit = lastUpdatedLockedProfit
47 | .mul(LOCKED_PROFIT_DEGRADATION_DENOMINATOR.sub(lockedFundRatio))
48 | .div(LOCKED_PROFIT_DEGRADATION_DENOMINATOR);
49 |
50 | return vaultTotalAmount.sub(lockedProfit);
51 | }
52 |
--------------------------------------------------------------------------------
/ts-client/src/vault/idl.ts:
--------------------------------------------------------------------------------
1 | export type Vault = {
2 | version: '0.7.2';
3 | name: 'vault';
4 | instructions: [
5 | {
6 | name: 'initialize';
7 | accounts: [
8 | {
9 | name: 'vault';
10 | isMut: true;
11 | isSigner: false;
12 | },
13 | {
14 | name: 'payer';
15 | isMut: true;
16 | isSigner: true;
17 | },
18 | {
19 | name: 'tokenVault';
20 | isMut: true;
21 | isSigner: false;
22 | },
23 | {
24 | name: 'tokenMint';
25 | isMut: false;
26 | isSigner: false;
27 | },
28 | {
29 | name: 'lpMint';
30 | isMut: true;
31 | isSigner: false;
32 | },
33 | {
34 | name: 'rent';
35 | isMut: false;
36 | isSigner: false;
37 | },
38 | {
39 | name: 'tokenProgram';
40 | isMut: false;
41 | isSigner: false;
42 | },
43 | {
44 | name: 'systemProgram';
45 | isMut: false;
46 | isSigner: false;
47 | },
48 | ];
49 | args: [];
50 | },
51 | {
52 | name: 'enableVault';
53 | accounts: [
54 | {
55 | name: 'vault';
56 | isMut: true;
57 | isSigner: false;
58 | },
59 | {
60 | name: 'admin';
61 | isMut: false;
62 | isSigner: true;
63 | },
64 | ];
65 | args: [
66 | {
67 | name: 'enabled';
68 | type: 'u8';
69 | },
70 | ];
71 | },
72 | {
73 | name: 'setOperator';
74 | accounts: [
75 | {
76 | name: 'vault';
77 | isMut: true;
78 | isSigner: false;
79 | },
80 | {
81 | name: 'operator';
82 | isMut: false;
83 | isSigner: false;
84 | },
85 | {
86 | name: 'admin';
87 | isMut: false;
88 | isSigner: true;
89 | },
90 | ];
91 | args: [];
92 | },
93 | {
94 | name: 'updateLockedProfitDegradation';
95 | accounts: [
96 | {
97 | name: 'vault';
98 | isMut: true;
99 | isSigner: false;
100 | },
101 | {
102 | name: 'admin';
103 | isMut: false;
104 | isSigner: true;
105 | },
106 | ];
107 | args: [
108 | {
109 | name: 'lockedProfitDegradation';
110 | type: 'u64';
111 | },
112 | ];
113 | },
114 | {
115 | name: 'getUnlockedAmount';
116 | accounts: [
117 | {
118 | name: 'vault';
119 | isMut: false;
120 | isSigner: false;
121 | },
122 | ];
123 | args: [];
124 | },
125 | {
126 | name: 'transferAdmin';
127 | accounts: [
128 | {
129 | name: 'vault';
130 | isMut: true;
131 | isSigner: false;
132 | },
133 | {
134 | name: 'admin';
135 | isMut: false;
136 | isSigner: true;
137 | },
138 | {
139 | name: 'newAdmin';
140 | isMut: false;
141 | isSigner: true;
142 | },
143 | ];
144 | args: [];
145 | },
146 | {
147 | name: 'transferFeeVault';
148 | accounts: [
149 | {
150 | name: 'vault';
151 | isMut: true;
152 | isSigner: false;
153 | },
154 | {
155 | name: 'admin';
156 | isMut: false;
157 | isSigner: true;
158 | },
159 | {
160 | name: 'newFeeVault';
161 | isMut: false;
162 | isSigner: false;
163 | },
164 | ];
165 | args: [];
166 | },
167 | {
168 | name: 'initializeStrategy';
169 | accounts: [
170 | {
171 | name: 'vault';
172 | isMut: true;
173 | isSigner: false;
174 | },
175 | {
176 | name: 'strategyProgram';
177 | isMut: false;
178 | isSigner: false;
179 | },
180 | {
181 | name: 'strategy';
182 | isMut: true;
183 | isSigner: false;
184 | },
185 | {
186 | name: 'reserve';
187 | isMut: true;
188 | isSigner: false;
189 | },
190 | {
191 | name: 'collateralVault';
192 | isMut: true;
193 | isSigner: false;
194 | },
195 | {
196 | name: 'collateralMint';
197 | isMut: false;
198 | isSigner: false;
199 | },
200 | {
201 | name: 'admin';
202 | isMut: true;
203 | isSigner: true;
204 | },
205 | {
206 | name: 'systemProgram';
207 | isMut: false;
208 | isSigner: false;
209 | },
210 | {
211 | name: 'rent';
212 | isMut: false;
213 | isSigner: false;
214 | },
215 | {
216 | name: 'tokenProgram';
217 | isMut: false;
218 | isSigner: false;
219 | },
220 | ];
221 | args: [
222 | {
223 | name: 'bumps';
224 | type: {
225 | defined: 'StrategyBumps';
226 | };
227 | },
228 | {
229 | name: 'strategyType';
230 | type: {
231 | defined: 'StrategyType';
232 | };
233 | },
234 | ];
235 | },
236 | {
237 | name: 'removeStrategy';
238 | accounts: [
239 | {
240 | name: 'vault';
241 | isMut: true;
242 | isSigner: false;
243 | },
244 | {
245 | name: 'strategy';
246 | isMut: true;
247 | isSigner: false;
248 | },
249 | {
250 | name: 'strategyProgram';
251 | isMut: false;
252 | isSigner: false;
253 | },
254 | {
255 | name: 'collateralVault';
256 | isMut: true;
257 | isSigner: false;
258 | },
259 | {
260 | name: 'reserve';
261 | isMut: true;
262 | isSigner: false;
263 | },
264 | {
265 | name: 'tokenVault';
266 | isMut: true;
267 | isSigner: false;
268 | },
269 | {
270 | name: 'feeVault';
271 | isMut: true;
272 | isSigner: false;
273 | },
274 | {
275 | name: 'lpMint';
276 | isMut: true;
277 | isSigner: false;
278 | },
279 | {
280 | name: 'tokenProgram';
281 | isMut: false;
282 | isSigner: false;
283 | },
284 | {
285 | name: 'admin';
286 | isMut: false;
287 | isSigner: true;
288 | },
289 | ];
290 | args: [];
291 | },
292 | {
293 | name: 'removeStrategy2';
294 | accounts: [
295 | {
296 | name: 'vault';
297 | isMut: true;
298 | isSigner: false;
299 | },
300 | {
301 | name: 'strategy';
302 | isMut: true;
303 | isSigner: false;
304 | },
305 | {
306 | name: 'strategyProgram';
307 | isMut: false;
308 | isSigner: false;
309 | },
310 | {
311 | name: 'collateralVault';
312 | isMut: true;
313 | isSigner: false;
314 | },
315 | {
316 | name: 'reserve';
317 | isMut: true;
318 | isSigner: false;
319 | },
320 | {
321 | name: 'tokenVault';
322 | isMut: true;
323 | isSigner: false;
324 | },
325 | {
326 | name: 'tokenAdminAdvancePayment';
327 | isMut: true;
328 | isSigner: false;
329 | },
330 | {
331 | name: 'tokenVaultAdvancePayment';
332 | isMut: true;
333 | isSigner: false;
334 | },
335 | {
336 | name: 'feeVault';
337 | isMut: true;
338 | isSigner: false;
339 | },
340 | {
341 | name: 'lpMint';
342 | isMut: true;
343 | isSigner: false;
344 | },
345 | {
346 | name: 'tokenProgram';
347 | isMut: false;
348 | isSigner: false;
349 | },
350 | {
351 | name: 'admin';
352 | isMut: false;
353 | isSigner: true;
354 | },
355 | ];
356 | args: [
357 | {
358 | name: 'maxAdminPayAmount';
359 | type: 'u64';
360 | },
361 | ];
362 | },
363 | {
364 | name: 'collectDust';
365 | accounts: [
366 | {
367 | name: 'vault';
368 | isMut: false;
369 | isSigner: false;
370 | },
371 | {
372 | name: 'tokenVault';
373 | isMut: true;
374 | isSigner: false;
375 | },
376 | {
377 | name: 'tokenAdmin';
378 | isMut: true;
379 | isSigner: false;
380 | },
381 | {
382 | name: 'admin';
383 | isMut: false;
384 | isSigner: true;
385 | },
386 | {
387 | name: 'tokenProgram';
388 | isMut: false;
389 | isSigner: false;
390 | },
391 | ];
392 | args: [];
393 | },
394 | {
395 | name: 'addStrategy';
396 | accounts: [
397 | {
398 | name: 'vault';
399 | isMut: true;
400 | isSigner: false;
401 | },
402 | {
403 | name: 'strategy';
404 | isMut: false;
405 | isSigner: false;
406 | },
407 | {
408 | name: 'admin';
409 | isMut: false;
410 | isSigner: true;
411 | },
412 | ];
413 | args: [];
414 | },
415 | {
416 | name: 'depositStrategy';
417 | accounts: [
418 | {
419 | name: 'vault';
420 | isMut: true;
421 | isSigner: false;
422 | },
423 | {
424 | name: 'strategy';
425 | isMut: true;
426 | isSigner: false;
427 | },
428 | {
429 | name: 'tokenVault';
430 | isMut: true;
431 | isSigner: false;
432 | },
433 | {
434 | name: 'feeVault';
435 | isMut: true;
436 | isSigner: false;
437 | },
438 | {
439 | name: 'lpMint';
440 | isMut: true;
441 | isSigner: false;
442 | },
443 | {
444 | name: 'strategyProgram';
445 | isMut: false;
446 | isSigner: false;
447 | },
448 | {
449 | name: 'collateralVault';
450 | isMut: true;
451 | isSigner: false;
452 | },
453 | {
454 | name: 'reserve';
455 | isMut: true;
456 | isSigner: false;
457 | },
458 | {
459 | name: 'tokenProgram';
460 | isMut: false;
461 | isSigner: false;
462 | },
463 | {
464 | name: 'operator';
465 | isMut: false;
466 | isSigner: true;
467 | },
468 | ];
469 | args: [
470 | {
471 | name: 'amount';
472 | type: 'u64';
473 | },
474 | ];
475 | },
476 | {
477 | name: 'withdrawStrategy';
478 | accounts: [
479 | {
480 | name: 'vault';
481 | isMut: true;
482 | isSigner: false;
483 | },
484 | {
485 | name: 'strategy';
486 | isMut: true;
487 | isSigner: false;
488 | },
489 | {
490 | name: 'tokenVault';
491 | isMut: true;
492 | isSigner: false;
493 | },
494 | {
495 | name: 'feeVault';
496 | isMut: true;
497 | isSigner: false;
498 | },
499 | {
500 | name: 'lpMint';
501 | isMut: true;
502 | isSigner: false;
503 | },
504 | {
505 | name: 'strategyProgram';
506 | isMut: false;
507 | isSigner: false;
508 | },
509 | {
510 | name: 'collateralVault';
511 | isMut: true;
512 | isSigner: false;
513 | },
514 | {
515 | name: 'reserve';
516 | isMut: true;
517 | isSigner: false;
518 | },
519 | {
520 | name: 'tokenProgram';
521 | isMut: false;
522 | isSigner: false;
523 | },
524 | {
525 | name: 'operator';
526 | isMut: false;
527 | isSigner: true;
528 | },
529 | ];
530 | args: [
531 | {
532 | name: 'amount';
533 | type: 'u64';
534 | },
535 | ];
536 | },
537 | {
538 | name: 'claimRewards';
539 | accounts: [
540 | {
541 | name: 'vault';
542 | isMut: false;
543 | isSigner: false;
544 | },
545 | {
546 | name: 'strategy';
547 | isMut: false;
548 | isSigner: false;
549 | },
550 | {
551 | name: 'tokenProgram';
552 | isMut: false;
553 | isSigner: false;
554 | },
555 | {
556 | name: 'tokenRewardAcc';
557 | isMut: true;
558 | isSigner: false;
559 | },
560 | {
561 | name: 'operator';
562 | isMut: false;
563 | isSigner: true;
564 | },
565 | ];
566 | args: [];
567 | },
568 | {
569 | name: 'withdraw2';
570 | accounts: [
571 | {
572 | name: 'vault';
573 | isMut: true;
574 | isSigner: false;
575 | },
576 | {
577 | name: 'tokenVault';
578 | isMut: true;
579 | isSigner: false;
580 | },
581 | {
582 | name: 'lpMint';
583 | isMut: true;
584 | isSigner: false;
585 | },
586 | {
587 | name: 'userToken';
588 | isMut: true;
589 | isSigner: false;
590 | },
591 | {
592 | name: 'userLp';
593 | isMut: true;
594 | isSigner: false;
595 | },
596 | {
597 | name: 'user';
598 | isMut: false;
599 | isSigner: true;
600 | },
601 | {
602 | name: 'tokenProgram';
603 | isMut: false;
604 | isSigner: false;
605 | },
606 | ];
607 | args: [
608 | {
609 | name: 'unmintAmount';
610 | type: 'u64';
611 | },
612 | {
613 | name: 'minOutAmount';
614 | type: 'u64';
615 | },
616 | ];
617 | },
618 | {
619 | name: 'deposit';
620 | accounts: [
621 | {
622 | name: 'vault';
623 | isMut: true;
624 | isSigner: false;
625 | },
626 | {
627 | name: 'tokenVault';
628 | isMut: true;
629 | isSigner: false;
630 | },
631 | {
632 | name: 'lpMint';
633 | isMut: true;
634 | isSigner: false;
635 | },
636 | {
637 | name: 'userToken';
638 | isMut: true;
639 | isSigner: false;
640 | },
641 | {
642 | name: 'userLp';
643 | isMut: true;
644 | isSigner: false;
645 | },
646 | {
647 | name: 'user';
648 | isMut: false;
649 | isSigner: true;
650 | },
651 | {
652 | name: 'tokenProgram';
653 | isMut: false;
654 | isSigner: false;
655 | },
656 | ];
657 | args: [
658 | {
659 | name: 'tokenAmount';
660 | type: 'u64';
661 | },
662 | {
663 | name: 'minimumLpTokenAmount';
664 | type: 'u64';
665 | },
666 | ];
667 | },
668 | {
669 | name: 'withdraw';
670 | accounts: [
671 | {
672 | name: 'vault';
673 | isMut: true;
674 | isSigner: false;
675 | },
676 | {
677 | name: 'tokenVault';
678 | isMut: true;
679 | isSigner: false;
680 | },
681 | {
682 | name: 'lpMint';
683 | isMut: true;
684 | isSigner: false;
685 | },
686 | {
687 | name: 'userToken';
688 | isMut: true;
689 | isSigner: false;
690 | },
691 | {
692 | name: 'userLp';
693 | isMut: true;
694 | isSigner: false;
695 | },
696 | {
697 | name: 'user';
698 | isMut: false;
699 | isSigner: true;
700 | },
701 | {
702 | name: 'tokenProgram';
703 | isMut: false;
704 | isSigner: false;
705 | },
706 | ];
707 | args: [
708 | {
709 | name: 'unmintAmount';
710 | type: 'u64';
711 | },
712 | {
713 | name: 'minOutAmount';
714 | type: 'u64';
715 | },
716 | ];
717 | },
718 | {
719 | name: 'withdrawDirectlyFromStrategy';
720 | accounts: [
721 | {
722 | name: 'vault';
723 | isMut: true;
724 | isSigner: false;
725 | },
726 | {
727 | name: 'strategy';
728 | isMut: true;
729 | isSigner: false;
730 | },
731 | {
732 | name: 'reserve';
733 | isMut: true;
734 | isSigner: false;
735 | },
736 | {
737 | name: 'strategyProgram';
738 | isMut: false;
739 | isSigner: false;
740 | },
741 | {
742 | name: 'collateralVault';
743 | isMut: true;
744 | isSigner: false;
745 | },
746 | {
747 | name: 'tokenVault';
748 | isMut: true;
749 | isSigner: false;
750 | },
751 | {
752 | name: 'lpMint';
753 | isMut: true;
754 | isSigner: false;
755 | },
756 | {
757 | name: 'feeVault';
758 | isMut: true;
759 | isSigner: false;
760 | },
761 | {
762 | name: 'userToken';
763 | isMut: true;
764 | isSigner: false;
765 | },
766 | {
767 | name: 'userLp';
768 | isMut: true;
769 | isSigner: false;
770 | },
771 | {
772 | name: 'user';
773 | isMut: false;
774 | isSigner: true;
775 | },
776 | {
777 | name: 'tokenProgram';
778 | isMut: false;
779 | isSigner: false;
780 | },
781 | ];
782 | args: [
783 | {
784 | name: 'unmintAmount';
785 | type: 'u64';
786 | },
787 | {
788 | name: 'minOutAmount';
789 | type: 'u64';
790 | },
791 | ];
792 | },
793 | ];
794 | accounts: [
795 | {
796 | name: 'vault';
797 | type: {
798 | kind: 'struct';
799 | fields: [
800 | {
801 | name: 'enabled';
802 | type: 'u8';
803 | },
804 | {
805 | name: 'bumps';
806 | type: {
807 | defined: 'VaultBumps';
808 | };
809 | },
810 | {
811 | name: 'totalAmount';
812 | type: 'u64';
813 | },
814 | {
815 | name: 'tokenVault';
816 | type: 'publicKey';
817 | },
818 | {
819 | name: 'feeVault';
820 | type: 'publicKey';
821 | },
822 | {
823 | name: 'tokenMint';
824 | type: 'publicKey';
825 | },
826 | {
827 | name: 'lpMint';
828 | type: 'publicKey';
829 | },
830 | {
831 | name: 'strategies';
832 | type: {
833 | array: ['publicKey', 30];
834 | };
835 | },
836 | {
837 | name: 'base';
838 | type: 'publicKey';
839 | },
840 | {
841 | name: 'admin';
842 | type: 'publicKey';
843 | },
844 | {
845 | name: 'operator';
846 | type: 'publicKey';
847 | },
848 | {
849 | name: 'lockedProfitTracker';
850 | type: {
851 | defined: 'LockedProfitTracker';
852 | };
853 | },
854 | ];
855 | };
856 | },
857 | {
858 | name: 'strategy';
859 | type: {
860 | kind: 'struct';
861 | fields: [
862 | {
863 | name: 'reserve';
864 | type: 'publicKey';
865 | },
866 | {
867 | name: 'collateralVault';
868 | type: 'publicKey';
869 | },
870 | {
871 | name: 'strategyType';
872 | type: {
873 | defined: 'StrategyType';
874 | };
875 | },
876 | {
877 | name: 'currentLiquidity';
878 | type: 'u64';
879 | },
880 | {
881 | name: 'bumps';
882 | type: {
883 | array: ['u8', 10];
884 | };
885 | },
886 | {
887 | name: 'vault';
888 | type: 'publicKey';
889 | },
890 | {
891 | name: 'isDisable';
892 | type: 'u8';
893 | },
894 | ];
895 | };
896 | },
897 | ];
898 | types: [
899 | {
900 | name: 'VaultBumps';
901 | type: {
902 | kind: 'struct';
903 | fields: [
904 | {
905 | name: 'vaultBump';
906 | type: 'u8';
907 | },
908 | {
909 | name: 'tokenVaultBump';
910 | type: 'u8';
911 | },
912 | ];
913 | };
914 | },
915 | {
916 | name: 'StrategyBumps';
917 | type: {
918 | kind: 'struct';
919 | fields: [
920 | {
921 | name: 'strategyIndex';
922 | type: 'u8';
923 | },
924 | {
925 | name: 'otherBumps';
926 | type: {
927 | array: ['u8', 10];
928 | };
929 | },
930 | ];
931 | };
932 | },
933 | {
934 | name: 'LockedProfitTracker';
935 | type: {
936 | kind: 'struct';
937 | fields: [
938 | {
939 | name: 'lastUpdatedLockedProfit';
940 | type: 'u64';
941 | },
942 | {
943 | name: 'lastReport';
944 | type: 'u64';
945 | },
946 | {
947 | name: 'lockedProfitDegradation';
948 | type: 'u64';
949 | },
950 | ];
951 | };
952 | },
953 | {
954 | name: 'StrategyType';
955 | type: {
956 | kind: 'enum';
957 | variants: [
958 | {
959 | name: 'PortFinanceWithoutLM';
960 | },
961 | {
962 | name: 'PortFinanceWithLM';
963 | },
964 | {
965 | name: 'SolendWithoutLM';
966 | },
967 | {
968 | name: 'Mango';
969 | },
970 | {
971 | name: 'SolendWithLM';
972 | },
973 | {
974 | name: 'ApricotWithoutLM';
975 | },
976 | {
977 | name: 'Francium';
978 | },
979 | {
980 | name: 'Tulip';
981 | },
982 | {
983 | name: 'Vault';
984 | },
985 | {
986 | name: 'Drift';
987 | },
988 | {
989 | name: 'Frakt';
990 | },
991 | {
992 | name: 'Marginfi';
993 | },
994 | ];
995 | };
996 | },
997 | ];
998 | events: [
999 | {
1000 | name: 'AddLiquidity';
1001 | fields: [
1002 | {
1003 | name: 'lpMintAmount';
1004 | type: 'u64';
1005 | index: false;
1006 | },
1007 | {
1008 | name: 'tokenAmount';
1009 | type: 'u64';
1010 | index: false;
1011 | },
1012 | ];
1013 | },
1014 | {
1015 | name: 'RemoveLiquidity';
1016 | fields: [
1017 | {
1018 | name: 'lpUnmintAmount';
1019 | type: 'u64';
1020 | index: false;
1021 | },
1022 | {
1023 | name: 'tokenAmount';
1024 | type: 'u64';
1025 | index: false;
1026 | },
1027 | ];
1028 | },
1029 | {
1030 | name: 'StrategyDeposit';
1031 | fields: [
1032 | {
1033 | name: 'strategyType';
1034 | type: {
1035 | defined: 'StrategyType';
1036 | };
1037 | index: false;
1038 | },
1039 | {
1040 | name: 'tokenAmount';
1041 | type: 'u64';
1042 | index: false;
1043 | },
1044 | ];
1045 | },
1046 | {
1047 | name: 'StrategyWithdraw';
1048 | fields: [
1049 | {
1050 | name: 'strategyType';
1051 | type: {
1052 | defined: 'StrategyType';
1053 | };
1054 | index: false;
1055 | },
1056 | {
1057 | name: 'collateralAmount';
1058 | type: 'u64';
1059 | index: false;
1060 | },
1061 | {
1062 | name: 'estimatedTokenAmount';
1063 | type: 'u64';
1064 | index: false;
1065 | },
1066 | ];
1067 | },
1068 | {
1069 | name: 'ClaimReward';
1070 | fields: [
1071 | {
1072 | name: 'strategyType';
1073 | type: {
1074 | defined: 'StrategyType';
1075 | };
1076 | index: false;
1077 | },
1078 | {
1079 | name: 'tokenAmount';
1080 | type: 'u64';
1081 | index: false;
1082 | },
1083 | {
1084 | name: 'mintAccount';
1085 | type: 'publicKey';
1086 | index: false;
1087 | },
1088 | ];
1089 | },
1090 | {
1091 | name: 'PerformanceFee';
1092 | fields: [
1093 | {
1094 | name: 'lpMintMore';
1095 | type: 'u64';
1096 | index: false;
1097 | },
1098 | ];
1099 | },
1100 | {
1101 | name: 'ReportLoss';
1102 | fields: [
1103 | {
1104 | name: 'strategy';
1105 | type: 'publicKey';
1106 | index: false;
1107 | },
1108 | {
1109 | name: 'loss';
1110 | type: 'u64';
1111 | index: false;
1112 | },
1113 | ];
1114 | },
1115 | {
1116 | name: 'TotalAmount';
1117 | fields: [
1118 | {
1119 | name: 'totalAmount';
1120 | type: 'u64';
1121 | index: false;
1122 | },
1123 | ];
1124 | },
1125 | ];
1126 | errors: [
1127 | {
1128 | code: 6000;
1129 | name: 'VaultIsDisabled';
1130 | msg: 'Vault is disabled';
1131 | },
1132 | {
1133 | code: 6001;
1134 | name: 'ExceededSlippage';
1135 | msg: 'Exceeded slippage tolerance';
1136 | },
1137 | {
1138 | code: 6002;
1139 | name: 'StrategyIsNotExisted';
1140 | msg: 'Strategy is not existed';
1141 | },
1142 | {
1143 | code: 6003;
1144 | name: 'UnAuthorized';
1145 | msg: 'UnAuthorized';
1146 | },
1147 | {
1148 | code: 6004;
1149 | name: 'MathOverflow';
1150 | msg: 'Math operation overflow';
1151 | },
1152 | {
1153 | code: 6005;
1154 | name: 'ProtocolIsNotSupported';
1155 | msg: 'Protocol is not supported';
1156 | },
1157 | {
1158 | code: 6006;
1159 | name: 'UnMatchReserve';
1160 | msg: 'Reserve does not support token mint';
1161 | },
1162 | {
1163 | code: 6007;
1164 | name: 'InvalidLockedProfitDegradation';
1165 | msg: 'lockedProfitDegradation is invalid';
1166 | },
1167 | {
1168 | code: 6008;
1169 | name: 'MaxStrategyReached';
1170 | msg: 'Maximum number of strategies have been reached';
1171 | },
1172 | {
1173 | code: 6009;
1174 | name: 'StrategyExisted';
1175 | msg: 'Strategy existed';
1176 | },
1177 | {
1178 | code: 6010;
1179 | name: 'InvalidUnmintAmount';
1180 | msg: 'Invalid unmint amount';
1181 | },
1182 | {
1183 | code: 6011;
1184 | name: 'InvalidAccountsForStrategy';
1185 | msg: 'Invalid accounts for strategy';
1186 | },
1187 | {
1188 | code: 6012;
1189 | name: 'InvalidBump';
1190 | msg: 'Invalid bump';
1191 | },
1192 | {
1193 | code: 6013;
1194 | name: 'AmountMustGreaterThanZero';
1195 | msg: 'Amount must be greater than 0';
1196 | },
1197 | {
1198 | code: 6014;
1199 | name: 'MangoIsNotSupportedAnymore';
1200 | msg: 'Mango is not supported anymore';
1201 | },
1202 | {
1203 | code: 6015;
1204 | name: 'StrategyIsNotSupported';
1205 | msg: 'Strategy is not supported';
1206 | },
1207 | {
1208 | code: 6016;
1209 | name: 'PayAmountIsExeeced';
1210 | msg: 'Pay amount is exceeded';
1211 | },
1212 | {
1213 | code: 6017;
1214 | name: 'FeeVaultIsNotSet';
1215 | msg: 'Fee vault is not set';
1216 | },
1217 | {
1218 | code: 6018;
1219 | name: 'LendingAssertionViolation';
1220 | msg: 'deposit amount in lending is not matched';
1221 | },
1222 | {
1223 | code: 6019;
1224 | name: 'HaveMoneyInLending';
1225 | msg: 'Cannot remove strategy becase we have some in lending';
1226 | },
1227 | ];
1228 | };
1229 |
1230 | export const IDL: Vault = {
1231 | version: '0.7.2',
1232 | name: 'vault',
1233 | instructions: [
1234 | {
1235 | name: 'initialize',
1236 | accounts: [
1237 | {
1238 | name: 'vault',
1239 | isMut: true,
1240 | isSigner: false,
1241 | },
1242 | {
1243 | name: 'payer',
1244 | isMut: true,
1245 | isSigner: true,
1246 | },
1247 | {
1248 | name: 'tokenVault',
1249 | isMut: true,
1250 | isSigner: false,
1251 | },
1252 | {
1253 | name: 'tokenMint',
1254 | isMut: false,
1255 | isSigner: false,
1256 | },
1257 | {
1258 | name: 'lpMint',
1259 | isMut: true,
1260 | isSigner: false,
1261 | },
1262 | {
1263 | name: 'rent',
1264 | isMut: false,
1265 | isSigner: false,
1266 | },
1267 | {
1268 | name: 'tokenProgram',
1269 | isMut: false,
1270 | isSigner: false,
1271 | },
1272 | {
1273 | name: 'systemProgram',
1274 | isMut: false,
1275 | isSigner: false,
1276 | },
1277 | ],
1278 | args: [],
1279 | },
1280 | {
1281 | name: 'enableVault',
1282 | accounts: [
1283 | {
1284 | name: 'vault',
1285 | isMut: true,
1286 | isSigner: false,
1287 | },
1288 | {
1289 | name: 'admin',
1290 | isMut: false,
1291 | isSigner: true,
1292 | },
1293 | ],
1294 | args: [
1295 | {
1296 | name: 'enabled',
1297 | type: 'u8',
1298 | },
1299 | ],
1300 | },
1301 | {
1302 | name: 'setOperator',
1303 | accounts: [
1304 | {
1305 | name: 'vault',
1306 | isMut: true,
1307 | isSigner: false,
1308 | },
1309 | {
1310 | name: 'operator',
1311 | isMut: false,
1312 | isSigner: false,
1313 | },
1314 | {
1315 | name: 'admin',
1316 | isMut: false,
1317 | isSigner: true,
1318 | },
1319 | ],
1320 | args: [],
1321 | },
1322 | {
1323 | name: 'updateLockedProfitDegradation',
1324 | accounts: [
1325 | {
1326 | name: 'vault',
1327 | isMut: true,
1328 | isSigner: false,
1329 | },
1330 | {
1331 | name: 'admin',
1332 | isMut: false,
1333 | isSigner: true,
1334 | },
1335 | ],
1336 | args: [
1337 | {
1338 | name: 'lockedProfitDegradation',
1339 | type: 'u64',
1340 | },
1341 | ],
1342 | },
1343 | {
1344 | name: 'getUnlockedAmount',
1345 | accounts: [
1346 | {
1347 | name: 'vault',
1348 | isMut: false,
1349 | isSigner: false,
1350 | },
1351 | ],
1352 | args: [],
1353 | },
1354 | {
1355 | name: 'transferAdmin',
1356 | accounts: [
1357 | {
1358 | name: 'vault',
1359 | isMut: true,
1360 | isSigner: false,
1361 | },
1362 | {
1363 | name: 'admin',
1364 | isMut: false,
1365 | isSigner: true,
1366 | },
1367 | {
1368 | name: 'newAdmin',
1369 | isMut: false,
1370 | isSigner: true,
1371 | },
1372 | ],
1373 | args: [],
1374 | },
1375 | {
1376 | name: 'transferFeeVault',
1377 | accounts: [
1378 | {
1379 | name: 'vault',
1380 | isMut: true,
1381 | isSigner: false,
1382 | },
1383 | {
1384 | name: 'admin',
1385 | isMut: false,
1386 | isSigner: true,
1387 | },
1388 | {
1389 | name: 'newFeeVault',
1390 | isMut: false,
1391 | isSigner: false,
1392 | },
1393 | ],
1394 | args: [],
1395 | },
1396 | {
1397 | name: 'initializeStrategy',
1398 | accounts: [
1399 | {
1400 | name: 'vault',
1401 | isMut: true,
1402 | isSigner: false,
1403 | },
1404 | {
1405 | name: 'strategyProgram',
1406 | isMut: false,
1407 | isSigner: false,
1408 | },
1409 | {
1410 | name: 'strategy',
1411 | isMut: true,
1412 | isSigner: false,
1413 | },
1414 | {
1415 | name: 'reserve',
1416 | isMut: true,
1417 | isSigner: false,
1418 | },
1419 | {
1420 | name: 'collateralVault',
1421 | isMut: true,
1422 | isSigner: false,
1423 | },
1424 | {
1425 | name: 'collateralMint',
1426 | isMut: false,
1427 | isSigner: false,
1428 | },
1429 | {
1430 | name: 'admin',
1431 | isMut: true,
1432 | isSigner: true,
1433 | },
1434 | {
1435 | name: 'systemProgram',
1436 | isMut: false,
1437 | isSigner: false,
1438 | },
1439 | {
1440 | name: 'rent',
1441 | isMut: false,
1442 | isSigner: false,
1443 | },
1444 | {
1445 | name: 'tokenProgram',
1446 | isMut: false,
1447 | isSigner: false,
1448 | },
1449 | ],
1450 | args: [
1451 | {
1452 | name: 'bumps',
1453 | type: {
1454 | defined: 'StrategyBumps',
1455 | },
1456 | },
1457 | {
1458 | name: 'strategyType',
1459 | type: {
1460 | defined: 'StrategyType',
1461 | },
1462 | },
1463 | ],
1464 | },
1465 | {
1466 | name: 'removeStrategy',
1467 | accounts: [
1468 | {
1469 | name: 'vault',
1470 | isMut: true,
1471 | isSigner: false,
1472 | },
1473 | {
1474 | name: 'strategy',
1475 | isMut: true,
1476 | isSigner: false,
1477 | },
1478 | {
1479 | name: 'strategyProgram',
1480 | isMut: false,
1481 | isSigner: false,
1482 | },
1483 | {
1484 | name: 'collateralVault',
1485 | isMut: true,
1486 | isSigner: false,
1487 | },
1488 | {
1489 | name: 'reserve',
1490 | isMut: true,
1491 | isSigner: false,
1492 | },
1493 | {
1494 | name: 'tokenVault',
1495 | isMut: true,
1496 | isSigner: false,
1497 | },
1498 | {
1499 | name: 'feeVault',
1500 | isMut: true,
1501 | isSigner: false,
1502 | },
1503 | {
1504 | name: 'lpMint',
1505 | isMut: true,
1506 | isSigner: false,
1507 | },
1508 | {
1509 | name: 'tokenProgram',
1510 | isMut: false,
1511 | isSigner: false,
1512 | },
1513 | {
1514 | name: 'admin',
1515 | isMut: false,
1516 | isSigner: true,
1517 | },
1518 | ],
1519 | args: [],
1520 | },
1521 | {
1522 | name: 'removeStrategy2',
1523 | accounts: [
1524 | {
1525 | name: 'vault',
1526 | isMut: true,
1527 | isSigner: false,
1528 | },
1529 | {
1530 | name: 'strategy',
1531 | isMut: true,
1532 | isSigner: false,
1533 | },
1534 | {
1535 | name: 'strategyProgram',
1536 | isMut: false,
1537 | isSigner: false,
1538 | },
1539 | {
1540 | name: 'collateralVault',
1541 | isMut: true,
1542 | isSigner: false,
1543 | },
1544 | {
1545 | name: 'reserve',
1546 | isMut: true,
1547 | isSigner: false,
1548 | },
1549 | {
1550 | name: 'tokenVault',
1551 | isMut: true,
1552 | isSigner: false,
1553 | },
1554 | {
1555 | name: 'tokenAdminAdvancePayment',
1556 | isMut: true,
1557 | isSigner: false,
1558 | },
1559 | {
1560 | name: 'tokenVaultAdvancePayment',
1561 | isMut: true,
1562 | isSigner: false,
1563 | },
1564 | {
1565 | name: 'feeVault',
1566 | isMut: true,
1567 | isSigner: false,
1568 | },
1569 | {
1570 | name: 'lpMint',
1571 | isMut: true,
1572 | isSigner: false,
1573 | },
1574 | {
1575 | name: 'tokenProgram',
1576 | isMut: false,
1577 | isSigner: false,
1578 | },
1579 | {
1580 | name: 'admin',
1581 | isMut: false,
1582 | isSigner: true,
1583 | },
1584 | ],
1585 | args: [
1586 | {
1587 | name: 'maxAdminPayAmount',
1588 | type: 'u64',
1589 | },
1590 | ],
1591 | },
1592 | {
1593 | name: 'collectDust',
1594 | accounts: [
1595 | {
1596 | name: 'vault',
1597 | isMut: false,
1598 | isSigner: false,
1599 | },
1600 | {
1601 | name: 'tokenVault',
1602 | isMut: true,
1603 | isSigner: false,
1604 | },
1605 | {
1606 | name: 'tokenAdmin',
1607 | isMut: true,
1608 | isSigner: false,
1609 | },
1610 | {
1611 | name: 'admin',
1612 | isMut: false,
1613 | isSigner: true,
1614 | },
1615 | {
1616 | name: 'tokenProgram',
1617 | isMut: false,
1618 | isSigner: false,
1619 | },
1620 | ],
1621 | args: [],
1622 | },
1623 | {
1624 | name: 'addStrategy',
1625 | accounts: [
1626 | {
1627 | name: 'vault',
1628 | isMut: true,
1629 | isSigner: false,
1630 | },
1631 | {
1632 | name: 'strategy',
1633 | isMut: false,
1634 | isSigner: false,
1635 | },
1636 | {
1637 | name: 'admin',
1638 | isMut: false,
1639 | isSigner: true,
1640 | },
1641 | ],
1642 | args: [],
1643 | },
1644 | {
1645 | name: 'depositStrategy',
1646 | accounts: [
1647 | {
1648 | name: 'vault',
1649 | isMut: true,
1650 | isSigner: false,
1651 | },
1652 | {
1653 | name: 'strategy',
1654 | isMut: true,
1655 | isSigner: false,
1656 | },
1657 | {
1658 | name: 'tokenVault',
1659 | isMut: true,
1660 | isSigner: false,
1661 | },
1662 | {
1663 | name: 'feeVault',
1664 | isMut: true,
1665 | isSigner: false,
1666 | },
1667 | {
1668 | name: 'lpMint',
1669 | isMut: true,
1670 | isSigner: false,
1671 | },
1672 | {
1673 | name: 'strategyProgram',
1674 | isMut: false,
1675 | isSigner: false,
1676 | },
1677 | {
1678 | name: 'collateralVault',
1679 | isMut: true,
1680 | isSigner: false,
1681 | },
1682 | {
1683 | name: 'reserve',
1684 | isMut: true,
1685 | isSigner: false,
1686 | },
1687 | {
1688 | name: 'tokenProgram',
1689 | isMut: false,
1690 | isSigner: false,
1691 | },
1692 | {
1693 | name: 'operator',
1694 | isMut: false,
1695 | isSigner: true,
1696 | },
1697 | ],
1698 | args: [
1699 | {
1700 | name: 'amount',
1701 | type: 'u64',
1702 | },
1703 | ],
1704 | },
1705 | {
1706 | name: 'withdrawStrategy',
1707 | accounts: [
1708 | {
1709 | name: 'vault',
1710 | isMut: true,
1711 | isSigner: false,
1712 | },
1713 | {
1714 | name: 'strategy',
1715 | isMut: true,
1716 | isSigner: false,
1717 | },
1718 | {
1719 | name: 'tokenVault',
1720 | isMut: true,
1721 | isSigner: false,
1722 | },
1723 | {
1724 | name: 'feeVault',
1725 | isMut: true,
1726 | isSigner: false,
1727 | },
1728 | {
1729 | name: 'lpMint',
1730 | isMut: true,
1731 | isSigner: false,
1732 | },
1733 | {
1734 | name: 'strategyProgram',
1735 | isMut: false,
1736 | isSigner: false,
1737 | },
1738 | {
1739 | name: 'collateralVault',
1740 | isMut: true,
1741 | isSigner: false,
1742 | },
1743 | {
1744 | name: 'reserve',
1745 | isMut: true,
1746 | isSigner: false,
1747 | },
1748 | {
1749 | name: 'tokenProgram',
1750 | isMut: false,
1751 | isSigner: false,
1752 | },
1753 | {
1754 | name: 'operator',
1755 | isMut: false,
1756 | isSigner: true,
1757 | },
1758 | ],
1759 | args: [
1760 | {
1761 | name: 'amount',
1762 | type: 'u64',
1763 | },
1764 | ],
1765 | },
1766 | {
1767 | name: 'claimRewards',
1768 | accounts: [
1769 | {
1770 | name: 'vault',
1771 | isMut: false,
1772 | isSigner: false,
1773 | },
1774 | {
1775 | name: 'strategy',
1776 | isMut: false,
1777 | isSigner: false,
1778 | },
1779 | {
1780 | name: 'tokenProgram',
1781 | isMut: false,
1782 | isSigner: false,
1783 | },
1784 | {
1785 | name: 'tokenRewardAcc',
1786 | isMut: true,
1787 | isSigner: false,
1788 | },
1789 | {
1790 | name: 'operator',
1791 | isMut: false,
1792 | isSigner: true,
1793 | },
1794 | ],
1795 | args: [],
1796 | },
1797 | {
1798 | name: 'withdraw2',
1799 | accounts: [
1800 | {
1801 | name: 'vault',
1802 | isMut: true,
1803 | isSigner: false,
1804 | },
1805 | {
1806 | name: 'tokenVault',
1807 | isMut: true,
1808 | isSigner: false,
1809 | },
1810 | {
1811 | name: 'lpMint',
1812 | isMut: true,
1813 | isSigner: false,
1814 | },
1815 | {
1816 | name: 'userToken',
1817 | isMut: true,
1818 | isSigner: false,
1819 | },
1820 | {
1821 | name: 'userLp',
1822 | isMut: true,
1823 | isSigner: false,
1824 | },
1825 | {
1826 | name: 'user',
1827 | isMut: false,
1828 | isSigner: true,
1829 | },
1830 | {
1831 | name: 'tokenProgram',
1832 | isMut: false,
1833 | isSigner: false,
1834 | },
1835 | ],
1836 | args: [
1837 | {
1838 | name: 'unmintAmount',
1839 | type: 'u64',
1840 | },
1841 | {
1842 | name: 'minOutAmount',
1843 | type: 'u64',
1844 | },
1845 | ],
1846 | },
1847 | {
1848 | name: 'deposit',
1849 | accounts: [
1850 | {
1851 | name: 'vault',
1852 | isMut: true,
1853 | isSigner: false,
1854 | },
1855 | {
1856 | name: 'tokenVault',
1857 | isMut: true,
1858 | isSigner: false,
1859 | },
1860 | {
1861 | name: 'lpMint',
1862 | isMut: true,
1863 | isSigner: false,
1864 | },
1865 | {
1866 | name: 'userToken',
1867 | isMut: true,
1868 | isSigner: false,
1869 | },
1870 | {
1871 | name: 'userLp',
1872 | isMut: true,
1873 | isSigner: false,
1874 | },
1875 | {
1876 | name: 'user',
1877 | isMut: false,
1878 | isSigner: true,
1879 | },
1880 | {
1881 | name: 'tokenProgram',
1882 | isMut: false,
1883 | isSigner: false,
1884 | },
1885 | ],
1886 | args: [
1887 | {
1888 | name: 'tokenAmount',
1889 | type: 'u64',
1890 | },
1891 | {
1892 | name: 'minimumLpTokenAmount',
1893 | type: 'u64',
1894 | },
1895 | ],
1896 | },
1897 | {
1898 | name: 'withdraw',
1899 | accounts: [
1900 | {
1901 | name: 'vault',
1902 | isMut: true,
1903 | isSigner: false,
1904 | },
1905 | {
1906 | name: 'tokenVault',
1907 | isMut: true,
1908 | isSigner: false,
1909 | },
1910 | {
1911 | name: 'lpMint',
1912 | isMut: true,
1913 | isSigner: false,
1914 | },
1915 | {
1916 | name: 'userToken',
1917 | isMut: true,
1918 | isSigner: false,
1919 | },
1920 | {
1921 | name: 'userLp',
1922 | isMut: true,
1923 | isSigner: false,
1924 | },
1925 | {
1926 | name: 'user',
1927 | isMut: false,
1928 | isSigner: true,
1929 | },
1930 | {
1931 | name: 'tokenProgram',
1932 | isMut: false,
1933 | isSigner: false,
1934 | },
1935 | ],
1936 | args: [
1937 | {
1938 | name: 'unmintAmount',
1939 | type: 'u64',
1940 | },
1941 | {
1942 | name: 'minOutAmount',
1943 | type: 'u64',
1944 | },
1945 | ],
1946 | },
1947 | {
1948 | name: 'withdrawDirectlyFromStrategy',
1949 | accounts: [
1950 | {
1951 | name: 'vault',
1952 | isMut: true,
1953 | isSigner: false,
1954 | },
1955 | {
1956 | name: 'strategy',
1957 | isMut: true,
1958 | isSigner: false,
1959 | },
1960 | {
1961 | name: 'reserve',
1962 | isMut: true,
1963 | isSigner: false,
1964 | },
1965 | {
1966 | name: 'strategyProgram',
1967 | isMut: false,
1968 | isSigner: false,
1969 | },
1970 | {
1971 | name: 'collateralVault',
1972 | isMut: true,
1973 | isSigner: false,
1974 | },
1975 | {
1976 | name: 'tokenVault',
1977 | isMut: true,
1978 | isSigner: false,
1979 | },
1980 | {
1981 | name: 'lpMint',
1982 | isMut: true,
1983 | isSigner: false,
1984 | },
1985 | {
1986 | name: 'feeVault',
1987 | isMut: true,
1988 | isSigner: false,
1989 | },
1990 | {
1991 | name: 'userToken',
1992 | isMut: true,
1993 | isSigner: false,
1994 | },
1995 | {
1996 | name: 'userLp',
1997 | isMut: true,
1998 | isSigner: false,
1999 | },
2000 | {
2001 | name: 'user',
2002 | isMut: false,
2003 | isSigner: true,
2004 | },
2005 | {
2006 | name: 'tokenProgram',
2007 | isMut: false,
2008 | isSigner: false,
2009 | },
2010 | ],
2011 | args: [
2012 | {
2013 | name: 'unmintAmount',
2014 | type: 'u64',
2015 | },
2016 | {
2017 | name: 'minOutAmount',
2018 | type: 'u64',
2019 | },
2020 | ],
2021 | },
2022 | ],
2023 | accounts: [
2024 | {
2025 | name: 'vault',
2026 | type: {
2027 | kind: 'struct',
2028 | fields: [
2029 | {
2030 | name: 'enabled',
2031 | type: 'u8',
2032 | },
2033 | {
2034 | name: 'bumps',
2035 | type: {
2036 | defined: 'VaultBumps',
2037 | },
2038 | },
2039 | {
2040 | name: 'totalAmount',
2041 | type: 'u64',
2042 | },
2043 | {
2044 | name: 'tokenVault',
2045 | type: 'publicKey',
2046 | },
2047 | {
2048 | name: 'feeVault',
2049 | type: 'publicKey',
2050 | },
2051 | {
2052 | name: 'tokenMint',
2053 | type: 'publicKey',
2054 | },
2055 | {
2056 | name: 'lpMint',
2057 | type: 'publicKey',
2058 | },
2059 | {
2060 | name: 'strategies',
2061 | type: {
2062 | array: ['publicKey', 30],
2063 | },
2064 | },
2065 | {
2066 | name: 'base',
2067 | type: 'publicKey',
2068 | },
2069 | {
2070 | name: 'admin',
2071 | type: 'publicKey',
2072 | },
2073 | {
2074 | name: 'operator',
2075 | type: 'publicKey',
2076 | },
2077 | {
2078 | name: 'lockedProfitTracker',
2079 | type: {
2080 | defined: 'LockedProfitTracker',
2081 | },
2082 | },
2083 | ],
2084 | },
2085 | },
2086 | {
2087 | name: 'strategy',
2088 | type: {
2089 | kind: 'struct',
2090 | fields: [
2091 | {
2092 | name: 'reserve',
2093 | type: 'publicKey',
2094 | },
2095 | {
2096 | name: 'collateralVault',
2097 | type: 'publicKey',
2098 | },
2099 | {
2100 | name: 'strategyType',
2101 | type: {
2102 | defined: 'StrategyType',
2103 | },
2104 | },
2105 | {
2106 | name: 'currentLiquidity',
2107 | type: 'u64',
2108 | },
2109 | {
2110 | name: 'bumps',
2111 | type: {
2112 | array: ['u8', 10],
2113 | },
2114 | },
2115 | {
2116 | name: 'vault',
2117 | type: 'publicKey',
2118 | },
2119 | {
2120 | name: 'isDisable',
2121 | type: 'u8',
2122 | },
2123 | ],
2124 | },
2125 | },
2126 | ],
2127 | types: [
2128 | {
2129 | name: 'VaultBumps',
2130 | type: {
2131 | kind: 'struct',
2132 | fields: [
2133 | {
2134 | name: 'vaultBump',
2135 | type: 'u8',
2136 | },
2137 | {
2138 | name: 'tokenVaultBump',
2139 | type: 'u8',
2140 | },
2141 | ],
2142 | },
2143 | },
2144 | {
2145 | name: 'StrategyBumps',
2146 | type: {
2147 | kind: 'struct',
2148 | fields: [
2149 | {
2150 | name: 'strategyIndex',
2151 | type: 'u8',
2152 | },
2153 | {
2154 | name: 'otherBumps',
2155 | type: {
2156 | array: ['u8', 10],
2157 | },
2158 | },
2159 | ],
2160 | },
2161 | },
2162 | {
2163 | name: 'LockedProfitTracker',
2164 | type: {
2165 | kind: 'struct',
2166 | fields: [
2167 | {
2168 | name: 'lastUpdatedLockedProfit',
2169 | type: 'u64',
2170 | },
2171 | {
2172 | name: 'lastReport',
2173 | type: 'u64',
2174 | },
2175 | {
2176 | name: 'lockedProfitDegradation',
2177 | type: 'u64',
2178 | },
2179 | ],
2180 | },
2181 | },
2182 | {
2183 | name: 'StrategyType',
2184 | type: {
2185 | kind: 'enum',
2186 | variants: [
2187 | {
2188 | name: 'PortFinanceWithoutLM',
2189 | },
2190 | {
2191 | name: 'PortFinanceWithLM',
2192 | },
2193 | {
2194 | name: 'SolendWithoutLM',
2195 | },
2196 | {
2197 | name: 'Mango',
2198 | },
2199 | {
2200 | name: 'SolendWithLM',
2201 | },
2202 | {
2203 | name: 'ApricotWithoutLM',
2204 | },
2205 | {
2206 | name: 'Francium',
2207 | },
2208 | {
2209 | name: 'Tulip',
2210 | },
2211 | {
2212 | name: 'Vault',
2213 | },
2214 | {
2215 | name: 'Drift',
2216 | },
2217 | {
2218 | name: 'Frakt',
2219 | },
2220 | {
2221 | name: 'Marginfi',
2222 | },
2223 | ],
2224 | },
2225 | },
2226 | ],
2227 | events: [
2228 | {
2229 | name: 'AddLiquidity',
2230 | fields: [
2231 | {
2232 | name: 'lpMintAmount',
2233 | type: 'u64',
2234 | index: false,
2235 | },
2236 | {
2237 | name: 'tokenAmount',
2238 | type: 'u64',
2239 | index: false,
2240 | },
2241 | ],
2242 | },
2243 | {
2244 | name: 'RemoveLiquidity',
2245 | fields: [
2246 | {
2247 | name: 'lpUnmintAmount',
2248 | type: 'u64',
2249 | index: false,
2250 | },
2251 | {
2252 | name: 'tokenAmount',
2253 | type: 'u64',
2254 | index: false,
2255 | },
2256 | ],
2257 | },
2258 | {
2259 | name: 'StrategyDeposit',
2260 | fields: [
2261 | {
2262 | name: 'strategyType',
2263 | type: {
2264 | defined: 'StrategyType',
2265 | },
2266 | index: false,
2267 | },
2268 | {
2269 | name: 'tokenAmount',
2270 | type: 'u64',
2271 | index: false,
2272 | },
2273 | ],
2274 | },
2275 | {
2276 | name: 'StrategyWithdraw',
2277 | fields: [
2278 | {
2279 | name: 'strategyType',
2280 | type: {
2281 | defined: 'StrategyType',
2282 | },
2283 | index: false,
2284 | },
2285 | {
2286 | name: 'collateralAmount',
2287 | type: 'u64',
2288 | index: false,
2289 | },
2290 | {
2291 | name: 'estimatedTokenAmount',
2292 | type: 'u64',
2293 | index: false,
2294 | },
2295 | ],
2296 | },
2297 | {
2298 | name: 'ClaimReward',
2299 | fields: [
2300 | {
2301 | name: 'strategyType',
2302 | type: {
2303 | defined: 'StrategyType',
2304 | },
2305 | index: false,
2306 | },
2307 | {
2308 | name: 'tokenAmount',
2309 | type: 'u64',
2310 | index: false,
2311 | },
2312 | {
2313 | name: 'mintAccount',
2314 | type: 'publicKey',
2315 | index: false,
2316 | },
2317 | ],
2318 | },
2319 | {
2320 | name: 'PerformanceFee',
2321 | fields: [
2322 | {
2323 | name: 'lpMintMore',
2324 | type: 'u64',
2325 | index: false,
2326 | },
2327 | ],
2328 | },
2329 | {
2330 | name: 'ReportLoss',
2331 | fields: [
2332 | {
2333 | name: 'strategy',
2334 | type: 'publicKey',
2335 | index: false,
2336 | },
2337 | {
2338 | name: 'loss',
2339 | type: 'u64',
2340 | index: false,
2341 | },
2342 | ],
2343 | },
2344 | {
2345 | name: 'TotalAmount',
2346 | fields: [
2347 | {
2348 | name: 'totalAmount',
2349 | type: 'u64',
2350 | index: false,
2351 | },
2352 | ],
2353 | },
2354 | ],
2355 | errors: [
2356 | {
2357 | code: 6000,
2358 | name: 'VaultIsDisabled',
2359 | msg: 'Vault is disabled',
2360 | },
2361 | {
2362 | code: 6001,
2363 | name: 'ExceededSlippage',
2364 | msg: 'Exceeded slippage tolerance',
2365 | },
2366 | {
2367 | code: 6002,
2368 | name: 'StrategyIsNotExisted',
2369 | msg: 'Strategy is not existed',
2370 | },
2371 | {
2372 | code: 6003,
2373 | name: 'UnAuthorized',
2374 | msg: 'UnAuthorized',
2375 | },
2376 | {
2377 | code: 6004,
2378 | name: 'MathOverflow',
2379 | msg: 'Math operation overflow',
2380 | },
2381 | {
2382 | code: 6005,
2383 | name: 'ProtocolIsNotSupported',
2384 | msg: 'Protocol is not supported',
2385 | },
2386 | {
2387 | code: 6006,
2388 | name: 'UnMatchReserve',
2389 | msg: 'Reserve does not support token mint',
2390 | },
2391 | {
2392 | code: 6007,
2393 | name: 'InvalidLockedProfitDegradation',
2394 | msg: 'lockedProfitDegradation is invalid',
2395 | },
2396 | {
2397 | code: 6008,
2398 | name: 'MaxStrategyReached',
2399 | msg: 'Maximum number of strategies have been reached',
2400 | },
2401 | {
2402 | code: 6009,
2403 | name: 'StrategyExisted',
2404 | msg: 'Strategy existed',
2405 | },
2406 | {
2407 | code: 6010,
2408 | name: 'InvalidUnmintAmount',
2409 | msg: 'Invalid unmint amount',
2410 | },
2411 | {
2412 | code: 6011,
2413 | name: 'InvalidAccountsForStrategy',
2414 | msg: 'Invalid accounts for strategy',
2415 | },
2416 | {
2417 | code: 6012,
2418 | name: 'InvalidBump',
2419 | msg: 'Invalid bump',
2420 | },
2421 | {
2422 | code: 6013,
2423 | name: 'AmountMustGreaterThanZero',
2424 | msg: 'Amount must be greater than 0',
2425 | },
2426 | {
2427 | code: 6014,
2428 | name: 'MangoIsNotSupportedAnymore',
2429 | msg: 'Mango is not supported anymore',
2430 | },
2431 | {
2432 | code: 6015,
2433 | name: 'StrategyIsNotSupported',
2434 | msg: 'Strategy is not supported',
2435 | },
2436 | {
2437 | code: 6016,
2438 | name: 'PayAmountIsExeeced',
2439 | msg: 'Pay amount is exceeded',
2440 | },
2441 | {
2442 | code: 6017,
2443 | name: 'FeeVaultIsNotSet',
2444 | msg: 'Fee vault is not set',
2445 | },
2446 | {
2447 | code: 6018,
2448 | name: 'LendingAssertionViolation',
2449 | msg: 'deposit amount in lending is not matched',
2450 | },
2451 | {
2452 | code: 6019,
2453 | name: 'HaveMoneyInLending',
2454 | msg: 'Cannot remove strategy becase we have some in lending',
2455 | },
2456 | ],
2457 | };
2458 |
--------------------------------------------------------------------------------
/ts-client/src/vault/index.ts:
--------------------------------------------------------------------------------
1 | import { AnchorProvider, Program, BN } from '@coral-xyz/anchor';
2 | import {
3 | PublicKey,
4 | TransactionInstruction,
5 | Connection,
6 | Transaction,
7 | Cluster,
8 | SYSVAR_RENT_PUBKEY,
9 | SystemProgram,
10 | } from '@solana/web3.js';
11 | import { TOKEN_PROGRAM_ID, NATIVE_MINT, getMint, Mint, unpackMint } from '@solana/spl-token';
12 |
13 | import {
14 | AffiliateInfo,
15 | AffiliateVaultProgram,
16 | VaultImplementation,
17 | VaultProgram,
18 | VaultState,
19 | VaultStateAndLp,
20 | } from './types';
21 | import {
22 | chunkedFetchMultipleVaultAccount,
23 | chunkedGetMultipleAccountInfos,
24 | deserializeAccount,
25 | getAssociatedTokenAccount,
26 | getOnchainTime,
27 | getOrCreateATAInstruction,
28 | getVaultPdas,
29 | unwrapSOLInstruction,
30 | wrapSOLInstruction,
31 | } from './utils';
32 | import { AFFILIATE_PROGRAM_ID, PROGRAM_ID, VAULT_STRATEGY_ADDRESS } from './constants';
33 | import { StrategyState } from './strategy';
34 | import { IDL, Vault as VaultIdl } from './idl';
35 | import { IDL as AffiliateIDL, AffiliateVault as AffiliateVaultIdl } from './affiliate-idl';
36 | import { calculateWithdrawableAmount } from './helper';
37 | import VaultHandler from './strategy/vault';
38 |
39 | type VaultDetails = {
40 | vaultMint: Mint;
41 | vaultLpMint: Mint;
42 | vaultPda: PublicKey;
43 | tokenVaultPda: PublicKey;
44 | vaultState: VaultState;
45 | };
46 |
47 | type WithdrawOpt = {
48 | affiliate: {
49 | affiliateId: PublicKey;
50 | affiliateProgram: AffiliateVaultProgram;
51 | partner: PublicKey;
52 | user: PublicKey;
53 | };
54 | };
55 |
56 | const getAllVaultState = async (
57 | tokensAddress: Array,
58 | program: VaultProgram,
59 | seedBaseKey?: PublicKey,
60 | ): Promise> => {
61 | const vaultAccountPdas = tokensAddress.map((tokenMint) =>
62 | getVaultPdas(tokenMint, new PublicKey(program.programId), seedBaseKey),
63 | );
64 |
65 | const vaultsPda = vaultAccountPdas.map(({ vaultPda }) => vaultPda);
66 | const vaultsState = (await chunkedFetchMultipleVaultAccount(program, vaultsPda)) as Array;
67 |
68 | if (vaultsState.length !== tokensAddress.length) {
69 | throw new Error('Some of the vault state cannot be fetched');
70 | }
71 |
72 | const vaultLpMints = vaultsState.map((vaultState) => vaultState.lpMint);
73 | const vaultMints = vaultsState.map((vaultState) => vaultState.tokenMint);
74 | const vaultLpAccounts = await chunkedGetMultipleAccountInfos(program.provider.connection, [
75 | ...vaultLpMints,
76 | ...vaultMints,
77 | ]);
78 |
79 | return vaultsState.map((vaultState, index) => {
80 | const vaultAccountPda = vaultAccountPdas[index];
81 | if (!vaultAccountPda) throw new Error('Missing vault account pda');
82 | const vaultLpAccount = vaultLpAccounts[index];
83 | if (!vaultLpAccount) throw new Error('Missing vault lp account');
84 | const vaultLpMint = unpackMint(vaultState.lpMint, vaultLpAccount, vaultLpAccount.owner);
85 | const vaultAccount = vaultLpAccounts[index + vaultLpMints.length];
86 | if (!vaultAccount) throw new Error('Missing vault account');
87 | const vaultMint = unpackMint(vaultState.tokenMint, vaultAccount, vaultAccount.owner);
88 |
89 | return { vaultPda: vaultAccountPda.vaultPda, vaultState, vaultMint, vaultLpMint };
90 | });
91 | };
92 |
93 | const getAllVaultStateByPda = async (
94 | vaultsPda: Array,
95 | program: VaultProgram,
96 | ): Promise> => {
97 | const vaultsState = (await chunkedFetchMultipleVaultAccount(program, vaultsPda)) as Array;
98 |
99 | if (vaultsState.length !== vaultsPda.length) {
100 | throw new Error('Some of the vault state cannot be fetched');
101 | }
102 |
103 | const vaultLpMints = vaultsState.map((vaultState) => vaultState.lpMint);
104 | const vaultMints = vaultsState.map((vaultState) => vaultState.tokenMint);
105 | const vaultLpAccounts = await chunkedGetMultipleAccountInfos(program.provider.connection, [
106 | ...vaultLpMints,
107 | ...vaultMints,
108 | ]);
109 |
110 | return vaultsState.map((vaultState, index) => {
111 | const vaultPda = vaultsPda[index];
112 | if (!vaultPda) throw new Error('Missing vault account pda');
113 | const vaultLpAccount = vaultLpAccounts[index];
114 | if (!vaultLpAccount) throw new Error('Missing vault lp account');
115 | const vaultLpMint = unpackMint(vaultState.lpMint, vaultLpAccount, vaultLpAccount.owner);
116 | const vaultAccount = vaultLpAccounts[index + vaultLpMints.length];
117 | if (!vaultAccount) throw new Error('Missing vault account');
118 | const vaultMint = unpackMint(vaultState.tokenMint, vaultAccount, vaultAccount.owner);
119 |
120 | return {
121 | vaultPda,
122 | vaultState,
123 | vaultLpMint,
124 | vaultMint,
125 | };
126 | });
127 | };
128 |
129 | const getVaultState = async (
130 | tokenAddress: PublicKey,
131 | program: VaultProgram,
132 | seedBaseKey?: PublicKey,
133 | ): Promise => {
134 | const { vaultPda } = getVaultPdas(tokenAddress, new PublicKey(program.programId), seedBaseKey);
135 | const vaultState = (await program.account.vault.fetchNullable(vaultPda)) as VaultState;
136 |
137 | if (!vaultState) {
138 | throw 'Cannot get vault state';
139 | }
140 |
141 | const [vaultLpAccount, vaultAccount] = await chunkedGetMultipleAccountInfos(program.provider.connection, [
142 | vaultState.lpMint,
143 | vaultState.tokenMint,
144 | ]);
145 | const vaultLpMint = unpackMint(vaultState.lpMint, vaultLpAccount, vaultLpAccount?.owner);
146 | const vaultMint = unpackMint(vaultState.tokenMint, vaultAccount, vaultAccount?.owner);
147 |
148 | return {
149 | vaultPda,
150 | vaultState,
151 | vaultLpMint,
152 | vaultMint,
153 | };
154 | };
155 |
156 | const getVaultStateByPda = async (vaultPda: PublicKey, program: VaultProgram): Promise => {
157 | const vaultState = (await program.account.vault.fetchNullable(vaultPda)) as VaultState;
158 |
159 | if (!vaultState) {
160 | throw 'Cannot get vault state';
161 | }
162 |
163 | const [vaultLpAccount, vaultAccount] = await chunkedGetMultipleAccountInfos(program.provider.connection, [
164 | vaultState.lpMint,
165 | vaultState.tokenMint,
166 | ]);
167 | const vaultLpMint = unpackMint(vaultState.lpMint, vaultLpAccount, vaultLpAccount?.owner);
168 | const vaultMint = unpackMint(vaultState.tokenMint, vaultAccount, vaultAccount?.owner);
169 |
170 | return {
171 | vaultPda,
172 | vaultState,
173 | vaultLpMint,
174 | vaultMint,
175 | };
176 | };
177 |
178 | const getVaultLiquidity = async (connection: Connection, tokenVaultPda: PublicKey): Promise => {
179 | const vaultLiquidityResponse = await connection.getAccountInfo(tokenVaultPda);
180 | if (!vaultLiquidityResponse) return null;
181 |
182 | const vaultLiquiditySerialize = deserializeAccount(vaultLiquidityResponse.data);
183 | return vaultLiquiditySerialize?.amount.toString() || null;
184 | };
185 |
186 | export default class VaultImpl implements VaultImplementation {
187 | private connection: Connection;
188 | private cluster: Cluster = 'mainnet-beta';
189 |
190 | // Vault
191 | private program: VaultProgram;
192 | private affiliateId: PublicKey | undefined;
193 | private affiliateProgram: AffiliateVaultProgram | undefined;
194 |
195 | private allowOwnerOffCurve?: boolean;
196 | public seedBaseKey?: PublicKey;
197 |
198 | public tokenMint: Mint;
199 | public tokenLpMint: Mint;
200 | public vaultPda: PublicKey;
201 | public tokenVaultPda: PublicKey;
202 | public vaultState: VaultState;
203 |
204 | private constructor(
205 | program: VaultProgram,
206 | vaultDetails: VaultDetails,
207 | opt?: {
208 | seedBaseKey?: PublicKey;
209 | allowOwnerOffCurve?: boolean;
210 | cluster?: Cluster;
211 | affiliateId?: PublicKey;
212 | affiliateProgram?: AffiliateVaultProgram;
213 | },
214 | ) {
215 | this.connection = program.provider.connection;
216 | this.cluster = opt?.cluster ?? 'mainnet-beta';
217 |
218 | this.program = program;
219 | this.affiliateProgram = opt?.affiliateProgram;
220 | this.affiliateId = opt?.affiliateId;
221 |
222 | this.allowOwnerOffCurve = opt?.allowOwnerOffCurve;
223 |
224 | this.tokenLpMint = vaultDetails.vaultLpMint;
225 | this.tokenMint = vaultDetails.vaultMint;
226 | this.vaultPda = vaultDetails.vaultPda;
227 | this.tokenVaultPda = vaultDetails.tokenVaultPda;
228 | this.vaultState = vaultDetails.vaultState;
229 | }
230 |
231 | public static async createPermissionlessVaultInstruction(
232 | connection: Connection,
233 | payer: PublicKey,
234 | tokenMint: PublicKey,
235 | opt?: {
236 | cluster?: Cluster;
237 | programId?: string;
238 | },
239 | ) {
240 | const provider = new AnchorProvider(connection, {} as any, AnchorProvider.defaultOptions());
241 | const program = new Program(IDL as VaultIdl, opt?.programId || PROGRAM_ID, provider);
242 |
243 | const {
244 | vaultPda: vault,
245 | tokenVaultPda: tokenVault,
246 | lpMintPda: lpMint,
247 | } = getVaultPdas(tokenMint, program.programId);
248 |
249 | return program.methods
250 | .initialize()
251 | .accounts({
252 | vault,
253 | payer,
254 | tokenVault,
255 | tokenMint,
256 | lpMint,
257 | systemProgram: SystemProgram.programId,
258 | rent: SYSVAR_RENT_PUBKEY,
259 | tokenProgram: TOKEN_PROGRAM_ID,
260 | })
261 | .instruction();
262 | }
263 |
264 | public static async fetchMultipleUserBalance(
265 | connection: Connection,
266 | lpMintList: Array,
267 | owner: PublicKey,
268 | ): Promise> {
269 | const ataAccounts = lpMintList.map((lpMint) => getAssociatedTokenAccount(lpMint, owner));
270 |
271 | const accountsInfo = await chunkedGetMultipleAccountInfos(connection, ataAccounts);
272 |
273 | return accountsInfo.map((accountInfo) => {
274 | if (!accountInfo) return new BN(0);
275 |
276 | const accountBalance = deserializeAccount(accountInfo.data);
277 | if (!accountBalance) throw new Error('Failed to parse user account for LP token.');
278 |
279 | return new BN(accountBalance.amount.toString());
280 | });
281 | }
282 |
283 | public static async createMultiple(
284 | connection: Connection,
285 | tokenMints: Array,
286 | opt?: {
287 | seedBaseKey?: PublicKey;
288 | allowOwnerOffCurve?: boolean;
289 | cluster?: Cluster;
290 | programId?: string;
291 | affiliateId?: PublicKey;
292 | affiliateProgramId?: string;
293 | },
294 | ): Promise> {
295 | const provider = new AnchorProvider(connection, {} as any, AnchorProvider.defaultOptions());
296 | const program = new Program(IDL as VaultIdl, opt?.programId || PROGRAM_ID, provider);
297 |
298 | const vaultsStateInfo = await getAllVaultState(tokenMints, program);
299 |
300 | return Promise.all(
301 | vaultsStateInfo.map(async ({ vaultPda, vaultState, vaultLpMint, vaultMint }) => {
302 | return new VaultImpl(
303 | program,
304 | {
305 | vaultPda,
306 | tokenVaultPda: vaultState.tokenVault,
307 | vaultState,
308 | vaultLpMint,
309 | vaultMint,
310 | },
311 | {
312 | ...opt,
313 | affiliateId: opt?.affiliateId,
314 | affiliateProgram: opt?.affiliateId
315 | ? new Program(
316 | AffiliateIDL as AffiliateVaultIdl,
317 | opt?.affiliateProgramId || AFFILIATE_PROGRAM_ID,
318 | provider,
319 | )
320 | : undefined,
321 | },
322 | );
323 | }),
324 | );
325 | }
326 |
327 | public static async createMultipleWithPda(
328 | connection: Connection,
329 | vaultsPda: Array,
330 | opt?: {
331 | seedBaseKey?: PublicKey;
332 | allowOwnerOffCurve?: boolean;
333 | cluster?: Cluster;
334 | programId?: string;
335 | affiliateId?: PublicKey;
336 | affiliateProgramId?: string;
337 | },
338 | ): Promise> {
339 | const provider = new AnchorProvider(connection, {} as any, AnchorProvider.defaultOptions());
340 | const program = new Program(IDL as VaultIdl, opt?.programId || PROGRAM_ID, provider);
341 |
342 | const vaultsStateInfo = await getAllVaultStateByPda(vaultsPda, program);
343 |
344 | return Promise.all(
345 | vaultsStateInfo.map(async ({ vaultPda, vaultState, vaultMint, vaultLpMint }) => {
346 | return new VaultImpl(
347 | program,
348 | {
349 | vaultPda,
350 | tokenVaultPda: vaultState.tokenVault,
351 | vaultState,
352 | vaultMint,
353 | vaultLpMint,
354 | },
355 | {
356 | ...opt,
357 | affiliateId: opt?.affiliateId,
358 | affiliateProgram: opt?.affiliateId
359 | ? new Program(
360 | AffiliateIDL as AffiliateVaultIdl,
361 | opt?.affiliateProgramId || AFFILIATE_PROGRAM_ID,
362 | provider,
363 | )
364 | : undefined,
365 | },
366 | );
367 | }),
368 | );
369 | }
370 |
371 | public static async create(
372 | connection: Connection,
373 | tokenAddress: PublicKey,
374 | opt?: {
375 | seedBaseKey?: PublicKey;
376 | allowOwnerOffCurve?: boolean;
377 | cluster?: Cluster;
378 | programId?: string;
379 | affiliateId?: PublicKey;
380 | affiliateProgramId?: string;
381 | },
382 | ): Promise {
383 | const provider = new AnchorProvider(connection, {} as any, AnchorProvider.defaultOptions());
384 | const program = new Program(IDL as VaultIdl, opt?.programId || PROGRAM_ID, provider);
385 |
386 | const { vaultPda, vaultState, vaultMint, vaultLpMint } = await getVaultState(tokenAddress, program);
387 | return new VaultImpl(
388 | program,
389 | {
390 | vaultMint,
391 | vaultLpMint,
392 | vaultPda,
393 | tokenVaultPda: vaultState.tokenVault,
394 | vaultState,
395 | },
396 | {
397 | ...opt,
398 | affiliateId: opt?.affiliateId,
399 | affiliateProgram: opt?.affiliateId
400 | ? new Program(
401 | AffiliateIDL as AffiliateVaultIdl,
402 | opt?.affiliateProgramId || AFFILIATE_PROGRAM_ID,
403 | provider,
404 | )
405 | : undefined,
406 | },
407 | );
408 | }
409 |
410 | public async getUserBalance(owner: PublicKey): Promise {
411 | const isAffiliated = this.affiliateId && this.affiliateProgram;
412 |
413 | const address = await (async () => {
414 | // User deposit directly
415 | if (!isAffiliated) {
416 | return getAssociatedTokenAccount(this.vaultState.lpMint, owner);
417 | }
418 |
419 | // Get user affiliated address with the partner
420 | const { userLpToken } = await this.createAffiliateATAPreInstructions(owner);
421 | return userLpToken;
422 | })();
423 | const accountInfo = await this.connection.getAccountInfo(address);
424 |
425 | if (!accountInfo) {
426 | return new BN(0);
427 | }
428 |
429 | const result = deserializeAccount(accountInfo.data);
430 | if (result == undefined) {
431 | throw new Error('Failed to parse user account for LP token.');
432 | }
433 |
434 | return new BN(result.amount.toString());
435 | }
436 |
437 | /** To refetch the latest lpSupply */
438 | /** Use vaultImpl.lpSupply to use cached result */
439 | public async getVaultSupply(): Promise {
440 | const vaultLpMint = await getMint(this.connection, this.vaultState.lpMint);
441 | this.tokenLpMint = vaultLpMint;
442 | return new BN(vaultLpMint.supply.toString());
443 | }
444 |
445 | public async getWithdrawableAmount(): Promise {
446 | const currentTime = await getOnchainTime(this.connection);
447 |
448 | return calculateWithdrawableAmount(currentTime, this.vaultState);
449 | }
450 |
451 | public async refreshVaultState() {
452 | const { vaultState, vaultMint } = await getVaultStateByPda(this.vaultPda, this.program);
453 | this.vaultState = vaultState;
454 | this.tokenMint = vaultMint;
455 | }
456 |
457 | private async createATAPreInstructions(owner: PublicKey) {
458 | let preInstructions: TransactionInstruction[] = [];
459 | const [userToken, createUserTokenIx] = await getOrCreateATAInstruction(
460 | this.tokenMint.address,
461 | owner,
462 | this.connection,
463 | );
464 | const [userLpToken, createUserLpTokenIx] = await getOrCreateATAInstruction(
465 | this.vaultState.lpMint,
466 | owner,
467 | this.connection,
468 | );
469 | if (createUserTokenIx) {
470 | preInstructions.push(createUserTokenIx);
471 | }
472 | if (createUserLpTokenIx) {
473 | preInstructions.push(createUserLpTokenIx);
474 | }
475 |
476 | return {
477 | preInstructions,
478 | userToken,
479 | userLpToken,
480 | };
481 | }
482 |
483 | private async createAffiliateATAPreInstructions(owner: PublicKey) {
484 | if (!this.affiliateId || !this.affiliateProgram) throw new Error('Affiliate ID or program not found');
485 |
486 | const partner = this.affiliateId;
487 | const partnerToken = getAssociatedTokenAccount(this.tokenMint.address, partner);
488 |
489 | const [partnerAddress, _nonce] = PublicKey.findProgramAddressSync(
490 | [this.vaultPda.toBuffer(), partnerToken.toBuffer()],
491 | this.affiliateProgram.programId,
492 | );
493 | const [userAddress, _nonceUser] = PublicKey.findProgramAddressSync(
494 | [partnerAddress.toBuffer(), owner.toBuffer()],
495 | this.affiliateProgram.programId,
496 | );
497 |
498 | let preInstructions: TransactionInstruction[] = [];
499 | const [userToken, createUserTokenIx] = await getOrCreateATAInstruction(
500 | this.tokenMint.address,
501 | owner,
502 | this.connection,
503 | );
504 | const [userLpToken, createUserLpTokenIx] = await getOrCreateATAInstruction(
505 | this.vaultState.lpMint,
506 | userAddress,
507 | this.connection,
508 | {
509 | payer: owner,
510 | },
511 | );
512 | if (createUserTokenIx) {
513 | preInstructions.push(createUserTokenIx);
514 | }
515 | if (createUserLpTokenIx) {
516 | preInstructions.push(createUserLpTokenIx);
517 | }
518 |
519 | return {
520 | preInstructions,
521 | partner,
522 | partnerAddress,
523 | userAddress,
524 | userToken,
525 | userLpToken,
526 | };
527 | }
528 |
529 | public async deposit(owner: PublicKey, baseTokenAmount: BN): Promise {
530 | // Refresh vault state
531 | await this.refreshVaultState();
532 |
533 | let preInstructions: TransactionInstruction[] = [];
534 |
535 | let partnerAddress: PublicKey | undefined;
536 | let userAddress: PublicKey | undefined;
537 | let userToken: PublicKey | undefined;
538 | let userLpToken: PublicKey | undefined;
539 |
540 | // Withdraw with Affiliate
541 | if (this.affiliateId && this.affiliateProgram) {
542 | const {
543 | preInstructions: preInstructionsATA,
544 | partnerAddress: partnerAddressATA,
545 | userAddress: userAddressATA,
546 | userToken: userTokenATA,
547 | userLpToken: userLpTokenATA,
548 | } = await this.createAffiliateATAPreInstructions(owner);
549 | preInstructions = preInstructionsATA;
550 | userToken = userTokenATA;
551 | userLpToken = userLpTokenATA;
552 | partnerAddress = partnerAddressATA;
553 | userAddress = userAddressATA;
554 | } else {
555 | // Without affiliate
556 | const {
557 | preInstructions: preInstructionsATA,
558 | userToken: userTokenATA,
559 | userLpToken: userLpTokenATA,
560 | } = await this.createATAPreInstructions(owner);
561 | preInstructions = preInstructionsATA;
562 | userToken = userTokenATA;
563 | userLpToken = userLpTokenATA;
564 | }
565 |
566 | // If it's SOL vault, wrap desired amount of SOL
567 | if (this.tokenMint.address.equals(NATIVE_MINT)) {
568 | preInstructions = preInstructions.concat(wrapSOLInstruction(owner, userToken, baseTokenAmount));
569 | }
570 |
571 | let depositTx: Transaction;
572 | if (partnerAddress && userAddress && this.affiliateId && this.affiliateProgram) {
573 | const userPda = await this.connection.getParsedAccountInfo(userAddress);
574 | if (!userPda || !userPda.value?.data) {
575 | // Init first time user
576 | preInstructions.push(
577 | await this.affiliateProgram.methods
578 | .initUser()
579 | .accounts({
580 | user: userAddress,
581 | partner: partnerAddress,
582 | owner,
583 | systemProgram: SystemProgram.programId,
584 | rent: SYSVAR_RENT_PUBKEY,
585 | })
586 | .instruction(),
587 | );
588 | }
589 |
590 | depositTx = await this.affiliateProgram.methods
591 | .deposit(new BN(baseTokenAmount.toString()), new BN(0)) // Vault does not have slippage, second parameter is ignored.
592 | .accounts({
593 | partner: partnerAddress,
594 | user: userAddress,
595 | vaultProgram: this.program.programId,
596 | vault: this.vaultPda,
597 | tokenVault: this.tokenVaultPda,
598 | vaultLpMint: this.vaultState.lpMint,
599 | userToken,
600 | userLp: userLpToken,
601 | owner,
602 | tokenProgram: TOKEN_PROGRAM_ID,
603 | })
604 | .preInstructions(preInstructions)
605 | .transaction();
606 | } else {
607 | depositTx = await this.program.methods
608 | .deposit(new BN(baseTokenAmount.toString()), new BN(0)) // Vault does not have slippage, second parameter is ignored.
609 | .accounts({
610 | vault: this.vaultPda,
611 | tokenVault: this.tokenVaultPda,
612 | lpMint: this.vaultState.lpMint,
613 | userToken,
614 | userLp: userLpToken,
615 | user: owner,
616 | tokenProgram: TOKEN_PROGRAM_ID,
617 | })
618 | .preInstructions(preInstructions)
619 | .transaction();
620 | }
621 | return new Transaction({ feePayer: owner, ...(await this.connection.getLatestBlockhash()) }).add(depositTx);
622 | }
623 |
624 | public async getStrategiesState(): Promise> {
625 | return (
626 | await this.program.account.strategy.fetchMultiple(
627 | this.vaultState.strategies.filter((address) => address.toString() !== VAULT_STRATEGY_ADDRESS),
628 | )
629 | ).filter(Boolean);
630 | }
631 |
632 | private async getStrategyWithHighestLiquidity(strategy?: PublicKey) {
633 | // Reserved for testing
634 | if (strategy) {
635 | const strategyState = (await this.program.account.strategy.fetchNullable(strategy)) as unknown as StrategyState;
636 | return { publicKey: strategy, strategyState };
637 | }
638 |
639 | const vaultStrategiesStatePromise = this.vaultState.strategies
640 | .filter((address) => address.toString() !== VAULT_STRATEGY_ADDRESS)
641 | .map(async (strat) => {
642 | const strategyState = (await this.program.account.strategy.fetch(strat)) as unknown as StrategyState;
643 | return { publicKey: strat, strategyState };
644 | });
645 | const vaultStrategiesState = await Promise.allSettled(vaultStrategiesStatePromise);
646 | const settledVaultStrategiesState = vaultStrategiesState
647 | .map((item) => (item.status === 'fulfilled' ? item.value : undefined))
648 | .filter(Boolean);
649 |
650 | const highestLiquidity = settledVaultStrategiesState.sort((a, b) =>
651 | b.strategyState.currentLiquidity.sub(a.strategyState.currentLiquidity).toNumber(),
652 | )[0];
653 | return highestLiquidity;
654 | }
655 |
656 | public async withdraw(owner: PublicKey, baseTokenAmount: BN): Promise {
657 | // Refresh vault state
658 | await this.refreshVaultState();
659 | const lpSupply = await this.getVaultSupply();
660 |
661 | let preInstructions: TransactionInstruction[] = [];
662 | let userToken: PublicKey | undefined;
663 | let userLpToken: PublicKey | undefined;
664 | let withdrawOpt: WithdrawOpt | undefined;
665 |
666 | // Withdraw with Affiliate
667 | if (this.affiliateId && this.affiliateProgram) {
668 | const {
669 | preInstructions: preInstructionsATA,
670 | partnerAddress,
671 | userAddress,
672 | userToken: userTokenATA,
673 | userLpToken: userLpTokenATA,
674 | } = await this.createAffiliateATAPreInstructions(owner);
675 |
676 | preInstructions = preInstructionsATA;
677 | userToken = userTokenATA;
678 | userLpToken = userLpTokenATA;
679 | withdrawOpt =
680 | this.affiliateId && this.affiliateProgram
681 | ? {
682 | affiliate: {
683 | affiliateId: this.affiliateId,
684 | affiliateProgram: this.affiliateProgram,
685 | partner: partnerAddress,
686 | user: userAddress,
687 | },
688 | }
689 | : undefined;
690 | } else {
691 | // Without affiliate
692 | const {
693 | preInstructions: preInstructionsATA,
694 | userToken: userTokenATA,
695 | userLpToken: userLpTokenATA,
696 | } = await this.createATAPreInstructions(owner);
697 | preInstructions = preInstructionsATA;
698 | userToken = userTokenATA;
699 | userLpToken = userLpTokenATA;
700 | }
701 |
702 | const unlockedAmount = await this.getWithdrawableAmount();
703 | const amountToWithdraw = baseTokenAmount.mul(unlockedAmount).div(lpSupply);
704 | const vaultLiquidity = new BN((await getVaultLiquidity(this.connection, this.tokenVaultPda)) || 0);
705 |
706 | if (
707 | amountToWithdraw.lt(vaultLiquidity) // If withdraw amount lesser than vault reserve
708 | ) {
709 | return this.withdrawFromVaultReserve(
710 | owner,
711 | baseTokenAmount,
712 | userToken,
713 | userLpToken,
714 | preInstructions,
715 | withdrawOpt,
716 | );
717 | }
718 |
719 | // Get strategy with highest liquidity
720 | // opt.strategy reserved for testing
721 | const selectedStrategy = await this.getStrategyWithHighestLiquidity();
722 |
723 | const currentLiquidity = new BN(selectedStrategy.strategyState.currentLiquidity);
724 | const availableAmount = currentLiquidity.add(vaultLiquidity);
725 |
726 | if (amountToWithdraw.gt(availableAmount)) {
727 | throw new Error('Selected strategy does not have enough liquidity.');
728 | }
729 |
730 | const strategyHandler = new VaultHandler();
731 |
732 | // Unwrap SOL
733 | const postInstruction: Array = [];
734 | if (this.tokenMint.address.equals(NATIVE_MINT)) {
735 | const closeWrappedSOLIx = await unwrapSOLInstruction(owner);
736 | if (closeWrappedSOLIx) {
737 | postInstruction.push(closeWrappedSOLIx);
738 | }
739 | }
740 |
741 | const withdrawFromStrategyTx = await strategyHandler.withdraw(
742 | owner,
743 | this.program,
744 | {
745 | pubkey: selectedStrategy.publicKey,
746 | state: selectedStrategy.strategyState,
747 | },
748 | this.vaultPda,
749 | this.tokenVaultPda,
750 | this.vaultState,
751 | userToken,
752 | userLpToken,
753 | baseTokenAmount,
754 | preInstructions,
755 | postInstruction,
756 | withdrawOpt,
757 | );
758 |
759 | const tx = new Transaction({ feePayer: owner, ...(await this.connection.getLatestBlockhash()) }).add(
760 | withdrawFromStrategyTx,
761 | );
762 |
763 | return tx;
764 | }
765 |
766 | // Reserved code to withdraw from Vault Reserves directly.
767 | // The only situation this piece of code will be required, is when a single Vault have no other strategy, and only have its own reserve.
768 | private async withdrawFromVaultReserve(
769 | owner: PublicKey,
770 | baseTokenAmount: BN,
771 | userToken: PublicKey,
772 | userLpToken: PublicKey,
773 | preInstructions: Array,
774 | withdrawOpt?: WithdrawOpt,
775 | ): Promise {
776 | // Unwrap SOL
777 | const postInstruction: Array = [];
778 | if (this.tokenMint.address.equals(NATIVE_MINT)) {
779 | const closeWrappedSOLIx = await unwrapSOLInstruction(owner);
780 | if (closeWrappedSOLIx) {
781 | postInstruction.push(closeWrappedSOLIx);
782 | }
783 | }
784 |
785 | let withdrawTx;
786 | if (withdrawOpt?.affiliate) {
787 | withdrawTx = await withdrawOpt.affiliate.affiliateProgram.methods
788 | .withdraw(baseTokenAmount, new BN(0))
789 | .accounts({
790 | vault: this.vaultPda,
791 | tokenVault: this.tokenVaultPda,
792 | vaultLpMint: this.vaultState.lpMint,
793 | partner: withdrawOpt.affiliate.partner,
794 | owner,
795 | userToken,
796 | vaultProgram: this.program.programId,
797 | userLp: userLpToken,
798 | user: withdrawOpt.affiliate.user,
799 | tokenProgram: TOKEN_PROGRAM_ID,
800 | })
801 | .preInstructions(preInstructions)
802 | .postInstructions(postInstruction)
803 | .transaction();
804 | } else {
805 | withdrawTx = await this.program.methods
806 | .withdraw(baseTokenAmount, new BN(0)) // Vault does not have slippage, second parameter is ignored.
807 | .accounts({
808 | vault: this.vaultPda,
809 | tokenVault: this.tokenVaultPda,
810 | lpMint: this.vaultState.lpMint,
811 | userToken,
812 | userLp: userLpToken,
813 | user: owner,
814 | tokenProgram: TOKEN_PROGRAM_ID,
815 | })
816 | .preInstructions(preInstructions)
817 | .postInstructions(postInstruction)
818 | .transaction();
819 | }
820 |
821 | return new Transaction({ feePayer: owner, ...(await this.connection.getLatestBlockhash()) }).add(withdrawTx);
822 | }
823 |
824 | public async getAffiliateInfo(): Promise {
825 | if (!this.affiliateId || !this.affiliateProgram) throw new Error('No affiliateId or affiliate program found');
826 |
827 | const partner = this.affiliateId;
828 | const partnerToken = getAssociatedTokenAccount(this.tokenMint.address, partner);
829 |
830 | const [partnerAddress, _nonce] = PublicKey.findProgramAddressSync(
831 | [this.vaultPda.toBuffer(), partnerToken.toBuffer()],
832 | this.affiliateProgram.programId,
833 | );
834 |
835 | const partnerDetails = (await this.affiliateProgram.account.partner.fetchNullable(partnerAddress)) as AffiliateInfo;
836 | return partnerDetails;
837 | }
838 | }
839 |
--------------------------------------------------------------------------------
/ts-client/src/vault/strategy/index.ts:
--------------------------------------------------------------------------------
1 | import { BN } from '@coral-xyz/anchor';
2 | import { PublicKey, Transaction, TransactionInstruction } from '@solana/web3.js';
3 | import type { AffiliateVaultProgram, VaultProgram, VaultState } from '../types';
4 |
5 | export type StrategyType =
6 | | 'portFinanceWithoutLm'
7 | | 'portFinanceWithLm'
8 | | 'solendWithoutLm'
9 | | 'solendWithLm'
10 | | 'francium'
11 | | 'apricotWithoutLM'
12 | | 'mango'
13 | | 'tulip'
14 | | 'vault'
15 | | 'drift'
16 | | 'frakt'
17 | | 'cypher'
18 | | 'psylend'
19 | | 'marginfi';
20 |
21 | export type StrategyState = {
22 | reserve: PublicKey;
23 | collateralVault: PublicKey;
24 | strategyType: object;
25 | bumps: number[];
26 | currentLiquidity: BN;
27 | vault: PublicKey;
28 | };
29 |
30 | export type Strategy = {
31 | pubkey: PublicKey;
32 | state: StrategyState;
33 | };
34 |
35 | export type ReserveState = {
36 | collateral: {
37 | mintPubkey: PublicKey;
38 | mintTotalSupply: number;
39 | supplyPubkey: String;
40 | };
41 | state: unknown;
42 | };
43 |
44 | export interface StrategyHandler {
45 | strategyProgram?: PublicKey;
46 | withdraw(
47 | walletPubKey: PublicKey,
48 | program: VaultProgram,
49 | strategy: any,
50 | vault: PublicKey,
51 | tokenVault: PublicKey,
52 | vaultState: VaultState,
53 | userToken: PublicKey,
54 | userLp: PublicKey,
55 | amount: BN,
56 | preInstructions: TransactionInstruction[],
57 | postInstructions: TransactionInstruction[],
58 | opt?: {
59 | affiliate?: {
60 | affiliateId: PublicKey;
61 | affiliateProgram: AffiliateVaultProgram;
62 | partner: PublicKey;
63 | user: PublicKey;
64 | };
65 | },
66 | ): Promise;
67 | }
68 |
69 | export const getStrategyType = (strategyResponse: any) => {
70 | return Object.keys(strategyResponse)[0] as StrategyType;
71 | };
72 |
--------------------------------------------------------------------------------
/ts-client/src/vault/strategy/vault.ts:
--------------------------------------------------------------------------------
1 | import { PublicKey, TransactionInstruction } from '@solana/web3.js';
2 | import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
3 | import * as anchor from '@coral-xyz/anchor';
4 |
5 | import { StrategyHandler, Strategy } from '.';
6 | import { AffiliateVaultProgram, VaultProgram, VaultState } from '../types';
7 |
8 | export default class VaultHandler implements StrategyHandler {
9 | async withdraw(
10 | walletPubKey: PublicKey,
11 | program: VaultProgram,
12 | _strategy: Strategy,
13 | vault: PublicKey,
14 | tokenVault: PublicKey,
15 | vaultState: VaultState,
16 | userToken: PublicKey,
17 | userLp: PublicKey,
18 | amount: anchor.BN,
19 | preInstructions: TransactionInstruction[],
20 | postInstructions: TransactionInstruction[],
21 | opt?: {
22 | affiliate?: {
23 | affiliateId: PublicKey;
24 | affiliateProgram: AffiliateVaultProgram;
25 | partner: PublicKey;
26 | user: PublicKey;
27 | };
28 | },
29 | ) {
30 | const txAccounts = {
31 | vault,
32 | tokenVault,
33 | userToken,
34 | userLp,
35 | tokenProgram: TOKEN_PROGRAM_ID,
36 | };
37 |
38 | if (opt?.affiliate) {
39 | const tx = await opt.affiliate.affiliateProgram.methods
40 | .withdraw(amount, new anchor.BN(0))
41 | .accounts({
42 | ...txAccounts,
43 | partner: opt.affiliate.partner,
44 | user: opt.affiliate.user,
45 | vaultProgram: program.programId,
46 | vaultLpMint: vaultState.lpMint,
47 | owner: walletPubKey,
48 | })
49 | .preInstructions(preInstructions)
50 | .postInstructions(postInstructions)
51 | .transaction();
52 |
53 | return tx;
54 | }
55 |
56 | const tx = await program.methods
57 | .withdraw(amount, new anchor.BN(0))
58 | .accounts({
59 | ...txAccounts,
60 | lpMint: vaultState.lpMint,
61 | user: walletPubKey,
62 | })
63 | .preInstructions(preInstructions)
64 | .postInstructions(postInstructions)
65 | .transaction();
66 |
67 | return tx;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/ts-client/src/vault/tests/affiliate.test.ts:
--------------------------------------------------------------------------------
1 | import { Connection, Keypair, PublicKey } from '@solana/web3.js';
2 | import { Wallet, AnchorProvider, BN } from '@coral-xyz/anchor';
3 |
4 | import VaultImpl from '..';
5 | import { airDropSol } from './utils';
6 | import { NATIVE_MINT } from '@solana/spl-token';
7 | import fs from "fs";
8 | import os from 'os'
9 |
10 | let mockWallet = new Wallet(new Keypair());
11 |
12 | // devnet ATA creation and reading must use confirmed.
13 | const devnetConnection = new Connection('https://api.devnet.solana.com/', { commitment: 'confirmed' });
14 |
15 |
16 | // TODO: Remove this fake partner ID
17 | const TEMPORARY_PARTNER_PUBLIC_KEY = new PublicKey('7236FoaWTXJyzbfFPZcrzg3tBpPhGiTgXsGWvjwrYfiF');
18 | describe('Interact with Vault in devnet', () => {
19 | const provider = new AnchorProvider(devnetConnection, mockWallet, {
20 | commitment: 'confirmed',
21 | });
22 |
23 | let vaultImpl: VaultImpl;
24 | beforeAll(async () => {
25 | await airDropSol(devnetConnection, mockWallet.publicKey);
26 | vaultImpl = await VaultImpl.create(devnetConnection, NATIVE_MINT, {
27 | cluster: 'devnet',
28 | affiliateId: TEMPORARY_PARTNER_PUBLIC_KEY,
29 | });
30 | });
31 |
32 | test('Test affiliate init user, check balance, deposits, then withdraw all', async () => {
33 | // First deposit
34 | const depositTx = await vaultImpl.deposit(mockWallet.publicKey, new BN(100_000_000));
35 | const depositResult = await provider.sendAndConfirm(depositTx);
36 | console.log('Deposit result', depositResult);
37 | expect(typeof depositResult).toBe('string');
38 |
39 | // Check balance
40 | const userBalanceDeposit = await vaultImpl.getUserBalance(mockWallet.publicKey);
41 | expect(Number(userBalanceDeposit)).toBeGreaterThan(0);
42 |
43 | // Subsequent deposit should not create ATA, and no need to init user
44 | const depositTx2 = await vaultImpl.deposit(mockWallet.publicKey, new BN(100_000_000));
45 | expect(depositTx2.instructions.map((ix) => ix.programId.toString())).toEqual([
46 | '11111111111111111111111111111111', // Wrap SOL
47 | 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', // Sync nativve
48 | 'GacY9YuN16HNRTy7ZWwULPccwvfFSBeNLuAQP7y38Du3', // Affiliate program deposit
49 | ]);
50 | const depositResult2 = await provider.sendAndConfirm(depositTx2);
51 | console.log('Deposit result', depositResult2);
52 | expect(typeof depositResult2).toBe('string');
53 |
54 | // Check balance again, should be greater than first deposit
55 | const userBalanceDeposit2 = await vaultImpl.getUserBalance(mockWallet.publicKey);
56 | expect(Number(userBalanceDeposit2)).toBeGreaterThan(Number(userBalanceDeposit));
57 |
58 | // Withdraw
59 | const withdrawTx = await vaultImpl.withdraw(mockWallet.publicKey, userBalanceDeposit2);
60 | const withdrawResult = await provider.sendAndConfirm(withdrawTx);
61 | console.log('Withdraw result', withdrawResult);
62 | expect(typeof withdrawResult).toBe('string');
63 |
64 | // Check final balance to be zero
65 | const userBalanceDeposit3 = await vaultImpl.getUserBalance(mockWallet.publicKey);
66 | expect(Number(userBalanceDeposit3)).toEqual(0);
67 | });
68 |
69 | test('Get affiliate partner info', async () => {
70 | const partnerInfo = await vaultImpl.getAffiliateInfo();
71 | expect(Object.keys(partnerInfo)).toEqual(
72 | expect.arrayContaining(['partnerToken', 'vault', 'outstandingFee', 'feeRatio', 'cummulativeFee']),
73 | );
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/ts-client/src/vault/tests/utils/index.ts:
--------------------------------------------------------------------------------
1 | import { Connection, PublicKey } from '@solana/web3.js';
2 |
3 | const LAMPORTS_PER_SOL = 1e9;
4 | export const airDropSol = async (connection: Connection, publicKey: PublicKey, amount = 1 * LAMPORTS_PER_SOL) => {
5 | try {
6 | const airdropSignature = await connection.requestAirdrop(publicKey, amount);
7 | const latestBlockHash = await connection.getLatestBlockhash();
8 | await connection.confirmTransaction({
9 | blockhash: latestBlockHash.blockhash,
10 | lastValidBlockHeight: latestBlockHash.lastValidBlockHeight,
11 | signature: airdropSignature,
12 | });
13 | } catch (error) {
14 | console.error(error);
15 | throw error;
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/ts-client/src/vault/tests/vault.test.ts:
--------------------------------------------------------------------------------
1 | import { Connection, Keypair, PublicKey } from '@solana/web3.js';
2 | import { Wallet, AnchorProvider, BN } from '@coral-xyz/anchor';
3 |
4 | import VaultImpl from '..';
5 | import { airDropSol } from './utils';
6 | import { getVaultPdas } from '../utils';
7 | import { PROGRAM_ID, USDC_MINT, USDT_MINT } from '../constants';
8 | import { NATIVE_MINT } from '@solana/spl-token';
9 |
10 | const mockWallet = new Wallet(new Keypair());
11 | const mainnetConnection = new Connection('https://api.mainnet-beta.solana.com');
12 | // devnet ATA creation and reading must use confirmed.
13 | const devnetConnection = new Connection('https://api.devnet.solana.com/', { commitment: 'confirmed' });
14 |
15 | describe('Get Mainnet vault state', () => {
16 | let vaults: VaultImpl[] = [];
17 | let vaultsForPool: VaultImpl[] = [];
18 |
19 | // Make sure all vaults can be initialized
20 | beforeAll(async () => {
21 | const tokenAddresses = [NATIVE_MINT, USDC_MINT, USDT_MINT];
22 | const tokensInfoPda = tokenAddresses.map((tokenAddress) => {
23 | const { vaultPda } = getVaultPdas(tokenAddress, new PublicKey(PROGRAM_ID));
24 | return vaultPda;
25 | });
26 | vaults = await VaultImpl.createMultiple(mainnetConnection, tokenAddresses);
27 | vaultsForPool = await VaultImpl.createMultipleWithPda(mainnetConnection, tokensInfoPda);
28 | });
29 |
30 | test('Get LP Supply', async () => {
31 | const vaultLpSupplies = await Promise.all(
32 | vaults.map(async (vault) => {
33 | const lpSupply = await vault.getVaultSupply();
34 |
35 | return lpSupply;
36 | }),
37 | );
38 |
39 | vaultLpSupplies.forEach((lpSupply) => {
40 | expect(lpSupply.gtn(0)).toBeTruthy();
41 | });
42 |
43 | const vaultLpSuppliesForPool = await Promise.all(
44 | vaultsForPool.map(async (vault) => {
45 | const lpSupply = await vault.getVaultSupply();
46 |
47 | return lpSupply;
48 | }),
49 | );
50 |
51 | vaultLpSuppliesForPool.forEach((lpSupply) => {
52 | expect(lpSupply.gtn(0)).toBeTruthy();
53 | });
54 | });
55 |
56 | test('Get unlocked amount', async () => {
57 | vaults.forEach(async (vault) => {
58 | const unlockedAmount = await vault.getWithdrawableAmount();
59 | expect(Number(unlockedAmount)).toBeGreaterThan(0);
60 | });
61 | });
62 | });
63 |
64 | describe('Interact with Vault in devnet', () => {
65 | const provider = new AnchorProvider(devnetConnection, mockWallet, {
66 | commitment: 'confirmed',
67 | });
68 |
69 | let vault: VaultImpl;
70 | beforeAll(async () => {
71 | await airDropSol(devnetConnection, mockWallet.publicKey);
72 | vault = await VaultImpl.create(devnetConnection, NATIVE_MINT, { cluster: 'devnet' });
73 | });
74 |
75 | test('Deposit, check balance, withdraw', async () => {
76 | // Deposit
77 | const depositTx = await vault.deposit(mockWallet.publicKey, new BN(100_000_000));
78 | const depositResult = await provider.sendAndConfirm(depositTx);
79 | console.log('Deposit result', depositResult);
80 | expect(typeof depositResult).toBe('string');
81 |
82 | // Check balance
83 | const userBalanceDeposit = await vault.getUserBalance(mockWallet.publicKey);
84 | expect(Number(userBalanceDeposit)).toBeGreaterThan(0);
85 |
86 | // Withdraw all lp
87 | const withdrawTx = await vault.withdraw(mockWallet.publicKey, userBalanceDeposit);
88 | const withdrawResult = await provider.sendAndConfirm(withdrawTx);
89 | console.log('Withdraw result', withdrawResult);
90 | expect(typeof withdrawResult).toBe('string');
91 |
92 | // Check balance
93 | const userBalanceWithdraw = await vault.getUserBalance(mockWallet.publicKey);
94 | expect(Number(userBalanceWithdraw)).toEqual(0);
95 | });
96 |
97 | test('Vault Withdraw SOL from all strategy', async () => {
98 | for (var strategy of vault.vaultState.strategies) {
99 | if (!strategy.equals(PublicKey.default)) {
100 | console.log('Test with ', strategy.toString());
101 |
102 | // Deposit
103 | const depositTx = await vault.deposit(mockWallet.publicKey, new BN(1_000_000));
104 | const depositResult = await provider.sendAndConfirm(depositTx);
105 | expect(typeof depositResult).toBe('string');
106 |
107 | // Withdraw from specific strategy
108 | const withdrawTx = await vault.withdraw(mockWallet.publicKey, new BN(1000));
109 |
110 | try {
111 | const withdrawResult = await provider.sendAndConfirm(withdrawTx);
112 | console.log('Strategy withdraw result', withdrawResult);
113 | expect(typeof withdrawResult).toBe('string');
114 | } catch (error) {
115 | console.log('Error creating withdrawFromStrategy instruction', error);
116 | }
117 | }
118 | }
119 | });
120 | });
121 |
--------------------------------------------------------------------------------
/ts-client/src/vault/types/index.ts:
--------------------------------------------------------------------------------
1 | import { BN, IdlTypes, Program } from '@coral-xyz/anchor';
2 | import { PublicKey, Transaction } from '@solana/web3.js';
3 | import { TypeDef } from '@coral-xyz/anchor/dist/cjs/program/namespace/types';
4 |
5 | import { Vault as VaultIdl } from '../idl';
6 | import { AffiliateVault as AffiliateVaultIdl } from '../affiliate-idl';
7 | import { Mint } from '@solana/spl-token';
8 |
9 | export type VaultProgram = Program;
10 | export type AffiliateVaultProgram = Program;
11 |
12 | export type VaultImplementation = {
13 | getUserBalance: (owner: PublicKey) => Promise;
14 | getVaultSupply: () => Promise;
15 | getWithdrawableAmount: (ownerPublicKey: PublicKey) => Promise;
16 | deposit: (owner: PublicKey, baseTokenAmount: BN) => Promise;
17 | withdraw: (owner: PublicKey, baseTokenAmount: BN) => Promise;
18 |
19 | // Affiliate
20 | getAffiliateInfo: () => Promise;
21 | };
22 |
23 | export type VaultState = TypeDef>;
24 |
25 | /** Affiliate */
26 | export interface AffiliateInfo {
27 | partnerToken: PublicKey;
28 | vault: PublicKey;
29 | outstandingFee: BN;
30 | feeRatio: BN;
31 | cummulativeFee: BN;
32 | }
33 |
34 | /** Utils */
35 | export interface ParsedClockState {
36 | info: {
37 | epoch: number;
38 | epochStartTimestamp: number;
39 | leaderScheduleEpoch: number;
40 | slot: number;
41 | unixTimestamp: number;
42 | };
43 | type: string;
44 | program: string;
45 | space: number;
46 | }
47 |
48 | export interface VaultStateAndLp {
49 | vaultPda: PublicKey;
50 | vaultState: VaultState;
51 | vaultLpMint: Mint;
52 | vaultMint: Mint;
53 | }
54 |
--------------------------------------------------------------------------------
/ts-client/src/vault/utils/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ASSOCIATED_TOKEN_PROGRAM_ID,
3 | TOKEN_PROGRAM_ID,
4 | NATIVE_MINT,
5 | getAssociatedTokenAddressSync,
6 | createAssociatedTokenAccountInstruction,
7 | createCloseAccountInstruction,
8 | RawAccount,
9 | AccountLayout,
10 | MintLayout,
11 | RawMint,
12 | } from '@solana/spl-token';
13 | import {
14 | Connection,
15 | ParsedAccountData,
16 | PublicKey,
17 | SystemProgram,
18 | SYSVAR_CLOCK_PUBKEY,
19 | TransactionInstruction,
20 | } from '@solana/web3.js';
21 | import { BN } from '@coral-xyz/anchor';
22 |
23 | import { SEEDS, VAULT_BASE_KEY } from '../constants';
24 | import { ParsedClockState, VaultProgram } from '../types';
25 |
26 | export const getAssociatedTokenAccount = (tokenMint: PublicKey, owner: PublicKey) => {
27 | return getAssociatedTokenAddressSync(tokenMint, owner, true, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
28 | };
29 |
30 | export const deserializeAccount = (data: Buffer | undefined): RawAccount | undefined => {
31 | if (data == undefined || data.length == 0) {
32 | return undefined;
33 | }
34 | const accountInfo = AccountLayout.decode(data);
35 | return accountInfo;
36 | };
37 |
38 | export const deserializeMint = (data: Buffer | undefined): RawMint | undefined => {
39 | if (data == undefined || data.length == 0) {
40 | return undefined;
41 | }
42 | const mintInfo = MintLayout.decode(data);
43 | return mintInfo;
44 | };
45 |
46 | export const getOrCreateATAInstruction = async (
47 | tokenAddress: PublicKey,
48 | owner: PublicKey,
49 | connection: Connection,
50 | opt?: {
51 | payer?: PublicKey;
52 | },
53 | ): Promise<[PublicKey, TransactionInstruction?]> => {
54 | let toAccount;
55 | try {
56 | toAccount = getAssociatedTokenAddressSync(tokenAddress, owner, true, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID);
57 | const account = await connection.getAccountInfo(toAccount);
58 | if (!account) {
59 | const ix = createAssociatedTokenAccountInstruction(
60 | opt?.payer || owner,
61 | toAccount,
62 | owner,
63 | tokenAddress,
64 | TOKEN_PROGRAM_ID,
65 | ASSOCIATED_TOKEN_PROGRAM_ID,
66 | );
67 | return [toAccount, ix];
68 | }
69 | return [toAccount, undefined];
70 | } catch (e) {
71 | /* handle error */
72 | console.error('Error::getOrCreateATAInstruction', e);
73 | throw e;
74 | }
75 | };
76 |
77 | export const getVaultPdas = (tokenMint: PublicKey, programId: PublicKey, seedBaseKey?: PublicKey) => {
78 | const [vault, _vaultBump] = PublicKey.findProgramAddressSync(
79 | [Buffer.from(SEEDS.VAULT_PREFIX), tokenMint.toBuffer(), (seedBaseKey ?? VAULT_BASE_KEY).toBuffer()],
80 | programId,
81 | );
82 |
83 | const [tokenVault] = PublicKey.findProgramAddressSync(
84 | [Buffer.from(SEEDS.TOKEN_VAULT_PREFIX), vault.toBuffer()],
85 | programId,
86 | );
87 | const [lpMint] = PublicKey.findProgramAddressSync([Buffer.from(SEEDS.LP_MINT_PREFIX), vault.toBuffer()], programId);
88 |
89 | return {
90 | vaultPda: vault,
91 | tokenVaultPda: tokenVault,
92 | lpMintPda: lpMint,
93 | };
94 | };
95 |
96 | export const wrapSOLInstruction = (from: PublicKey, to: PublicKey, amount: BN): TransactionInstruction[] => {
97 | return [
98 | SystemProgram.transfer({
99 | fromPubkey: from,
100 | toPubkey: to,
101 | lamports: amount.toNumber(),
102 | }),
103 | new TransactionInstruction({
104 | keys: [
105 | {
106 | pubkey: to,
107 | isSigner: false,
108 | isWritable: true,
109 | },
110 | ],
111 | data: Buffer.from(new Uint8Array([17])),
112 | programId: TOKEN_PROGRAM_ID,
113 | }),
114 | ];
115 | };
116 |
117 | export const unwrapSOLInstruction = async (walletPublicKey: PublicKey) => {
118 | const wSolATAAccount = getAssociatedTokenAddressSync(
119 | NATIVE_MINT,
120 | walletPublicKey,
121 | true,
122 | TOKEN_PROGRAM_ID,
123 | ASSOCIATED_TOKEN_PROGRAM_ID,
124 | );
125 |
126 | if (wSolATAAccount) {
127 | const closedWrappedSolInstruction = createCloseAccountInstruction(
128 | wSolATAAccount,
129 | walletPublicKey,
130 | walletPublicKey,
131 | [],
132 | );
133 | return closedWrappedSolInstruction;
134 | }
135 | return null;
136 | };
137 |
138 | export const getOnchainTime = async (connection: Connection) => {
139 | const parsedClock = await connection.getParsedAccountInfo(SYSVAR_CLOCK_PUBKEY);
140 |
141 | const parsedClockAccount = (parsedClock.value!.data as ParsedAccountData).parsed as ParsedClockState;
142 |
143 | const currentTime = parsedClockAccount.info.unixTimestamp;
144 | return currentTime;
145 | };
146 |
147 | export const getLpSupply = async (connection: Connection, tokenMint: PublicKey): Promise => {
148 | const context = await connection.getTokenSupply(tokenMint);
149 | return new BN(context.value.amount);
150 | };
151 |
152 | export function chunks(array: T[], size: number): T[][] {
153 | return Array.apply(0, new Array(Math.ceil(array.length / size))).map((_, index) =>
154 | array.slice(index * size, (index + 1) * size),
155 | );
156 | }
157 |
158 | export async function chunkedFetchMultipleVaultAccount(
159 | program: VaultProgram,
160 | pks: PublicKey[],
161 | chunkSize: number = 100,
162 | ) {
163 | const accounts = (
164 | await Promise.all(chunks(pks, chunkSize).map((chunk) => program.account.vault.fetchMultiple(chunk)))
165 | ).flat();
166 |
167 | return accounts.filter(Boolean);
168 | }
169 |
170 | export async function chunkedGetMultipleAccountInfos(
171 | connection: Connection,
172 | pks: PublicKey[],
173 | chunkSize: number = 100,
174 | ) {
175 | const accountInfos = (
176 | await Promise.all(chunks(pks, chunkSize).map((chunk) => connection.getMultipleAccountsInfo(chunk)))
177 | ).flat();
178 |
179 | return accountInfos;
180 | }
181 |
--------------------------------------------------------------------------------
/ts-client/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./dist/cjs/",
5 | "moduleResolution": "node",
6 | "noEmit": false,
7 | "declaration": true,
8 | "declarationMap": true,
9 | "sourceMap": true,
10 | "esModuleInterop": true,
11 | "resolveJsonModule": true,
12 | "noImplicitAny": false,
13 | "skipLibCheck": true
14 | },
15 | "exclude": ["**/*.test.ts"]
16 | }
17 |
--------------------------------------------------------------------------------
/ts-client/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.build.json",
3 | "compilerOptions": {
4 | "outDir": "dist/esm/",
5 | "target": "ES6",
6 | "module": "ESNext"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/ts-client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@tsconfig/recommended/tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "dist",
5 | "lib": ["dom", "dom.iterable", "esnext"],
6 | "types": ["jest", "mocha", "chai"],
7 | "typeRoots": ["./node_modules/@types"],
8 | "module": "commonjs",
9 | "target": "es6",
10 | "moduleResolution": "node",
11 | "esModuleInterop": true,
12 | "resolveJsonModule": true,
13 | "noImplicitAny": false,
14 | "skipLibCheck": true
15 | }
16 | }
17 |
--------------------------------------------------------------------------------