solana_farm_client/client/
fund_instructions.rs

1//! Solana Farm Client Fund Instructions
2
3use {
4    crate::error::FarmClientError,
5    solana_farm_sdk::{
6        farm::FarmRoute,
7        fund::{
8            FundAssetType, FundAssetsTrackingConfig, FundCustodyType, FundSchedule, FundVaultType,
9        },
10        id::zero,
11        instruction::fund::FundInstruction,
12        math,
13        pool::PoolRoute,
14        program::multisig::Multisig,
15        string::str_to_as64,
16        token::OracleType,
17        vault::VaultStrategy,
18    },
19    solana_sdk::{
20        instruction::{AccountMeta, Instruction},
21        program_error::ProgramError,
22        pubkey::Pubkey,
23        system_program, sysvar,
24    },
25};
26
27use super::FarmClient;
28
29impl FarmClient {
30    /// Creates a new Fund Init Instruction
31    pub fn new_instruction_init_fund(
32        &self,
33        admin_address: &Pubkey,
34        fund_name: &str,
35        step: u64,
36    ) -> Result<Instruction, FarmClientError> {
37        // get fund info
38        let fund = self.get_fund(fund_name)?;
39        let fund_ref = self.get_fund_ref(fund_name)?;
40        let fund_token = self.get_token_by_ref(&fund.fund_token_ref)?;
41
42        // fill in accounts and instruction data
43        let data = FundInstruction::Init { step }.to_vec()?;
44        let accounts = vec![
45            AccountMeta::new_readonly(*admin_address, true),
46            AccountMeta::new_readonly(fund_ref, false),
47            AccountMeta::new(fund.info_account, false),
48            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
49            AccountMeta::new(fund.fund_authority, false),
50            AccountMeta::new_readonly(fund.fund_program_id, false),
51            AccountMeta::new_readonly(system_program::id(), false),
52            AccountMeta::new_readonly(spl_token::id(), false),
53            AccountMeta::new_readonly(sysvar::rent::id(), false),
54            AccountMeta::new(fund_token.mint, false),
55            AccountMeta::new_readonly(fund.fund_token_ref, false),
56            AccountMeta::new(fund.vaults_assets_info, false),
57            AccountMeta::new(fund.custodies_assets_info, false),
58        ];
59
60        Ok(Instruction {
61            program_id: fund.fund_program_id,
62            data,
63            accounts,
64        })
65    }
66
67    /// Creates a new Instruction for initializing a new User for the Fund
68    pub fn new_instruction_user_init_fund(
69        &self,
70        wallet_address: &Pubkey,
71        fund_name: &str,
72        token_name: &str,
73    ) -> Result<Instruction, FarmClientError> {
74        // get fund info
75        let fund = self.get_fund(fund_name)?;
76        let fund_ref = self.get_fund_ref(fund_name)?;
77        let token_ref = self.get_token_ref(token_name)?;
78        let user_info_account = self.get_fund_user_info_account(wallet_address, fund_name)?;
79        let user_requests_account =
80            self.get_fund_user_requests_account(wallet_address, fund_name, token_name)?;
81
82        // fill in accounts and instruction data
83        let data = FundInstruction::UserInit.to_vec()?;
84        let accounts = vec![
85            AccountMeta::new(*wallet_address, true),
86            AccountMeta::new_readonly(fund_ref, false),
87            AccountMeta::new(fund.info_account, false),
88            AccountMeta::new_readonly(*wallet_address, false),
89            AccountMeta::new(user_info_account, false),
90            AccountMeta::new(user_requests_account, false),
91            AccountMeta::new_readonly(token_ref, false),
92            AccountMeta::new_readonly(system_program::id(), false),
93        ];
94
95        Ok(Instruction {
96            program_id: fund.fund_program_id,
97            data,
98            accounts,
99        })
100    }
101
102    /// Creates a new instruction for initializing Fund's multisig with a new set of signers
103    pub fn new_instruction_set_fund_admins(
104        &self,
105        admin_address: &Pubkey,
106        fund_name: &str,
107        admin_signers: &[Pubkey],
108        min_signatures: u8,
109    ) -> Result<Instruction, FarmClientError> {
110        if admin_signers.is_empty() || min_signatures == 0 {
111            return Err(FarmClientError::ValueError(
112                "At least one signer is required".to_string(),
113            ));
114        } else if min_signatures as usize > admin_signers.len()
115            || admin_signers.len() > Multisig::MAX_SIGNERS
116        {
117            return Err(FarmClientError::ValueError(
118                "Invalid number of signatures".to_string(),
119            ));
120        }
121
122        // get fund info
123        let fund = self.get_fund(fund_name)?;
124        let fund_ref = self.get_fund_ref(fund_name)?;
125
126        // fill in accounts and instruction data
127        let mut inst = Instruction {
128            program_id: fund.fund_program_id,
129            data: FundInstruction::SetAdminSigners { min_signatures }.to_vec()?,
130            accounts: vec![
131                AccountMeta::new_readonly(*admin_address, true),
132                AccountMeta::new_readonly(fund_ref, false),
133                AccountMeta::new(fund.info_account, false),
134                AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
135                AccountMeta::new(self.get_fund_multisig_account(fund_name)?, false),
136                AccountMeta::new_readonly(system_program::id(), false),
137            ],
138        };
139
140        for key in admin_signers {
141            inst.accounts.push(AccountMeta::new_readonly(*key, false));
142        }
143
144        Ok(inst)
145    }
146
147    /// Creates a new instruction for removing Fund's multisig
148    pub fn new_instruction_remove_fund_multisig(
149        &self,
150        admin_address: &Pubkey,
151        fund_name: &str,
152    ) -> Result<Instruction, FarmClientError> {
153        // get fund info
154        let fund = self.get_fund(fund_name)?;
155        let fund_ref = self.get_fund_ref(fund_name)?;
156
157        // fill in accounts and instruction data
158        let inst = Instruction {
159            program_id: fund.fund_program_id,
160            data: FundInstruction::RemoveMultisig.to_vec()?,
161            accounts: vec![
162                AccountMeta::new_readonly(*admin_address, true),
163                AccountMeta::new_readonly(fund_ref, false),
164                AccountMeta::new(fund.info_account, false),
165                AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
166                AccountMeta::new(self.get_fund_multisig_account(fund_name)?, false),
167            ],
168        };
169
170        Ok(inst)
171    }
172
173    /// Creates a new set fund assets tracking config Instruction
174    pub fn new_instruction_set_fund_assets_tracking_config(
175        &self,
176        admin_address: &Pubkey,
177        fund_name: &str,
178        config: &FundAssetsTrackingConfig,
179    ) -> Result<Instruction, FarmClientError> {
180        // get fund info
181        let fund = self.get_fund(fund_name)?;
182        let fund_ref = self.get_fund_ref(fund_name)?;
183
184        // fill in accounts and instruction data
185        let data = FundInstruction::SetAssetsTrackingConfig { config: *config }.to_vec()?;
186        let accounts = vec![
187            AccountMeta::new_readonly(*admin_address, true),
188            AccountMeta::new_readonly(fund_ref, false),
189            AccountMeta::new(fund.info_account, false),
190            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
191        ];
192
193        Ok(Instruction {
194            program_id: fund.fund_program_id,
195            data,
196            accounts,
197        })
198    }
199
200    /// Creates a new Instruction for adding a new custody to the Fund
201    pub fn new_instruction_add_fund_custody(
202        &self,
203        admin_address: &Pubkey,
204        fund_name: &str,
205        token_name: &str,
206        custody_type: FundCustodyType,
207    ) -> Result<Instruction, FarmClientError> {
208        // get fund info
209        let fund = self.get_fund(fund_name)?;
210        let fund_ref = self.get_fund_ref(fund_name)?;
211        let token = self.get_token(token_name)?;
212        let token_ref = self.get_token_ref(token_name)?;
213
214        // get custodies
215        let custodies = self.get_fund_custodies(fund_name)?;
216        let custody_metadata =
217            self.get_fund_custody_account(fund_name, token_name, custody_type)?;
218        let fund_assets_account =
219            self.get_fund_assets_account(fund_name, FundAssetType::Custody)?;
220        let custody_token_account =
221            self.get_fund_custody_token_account(fund_name, token_name, custody_type)?;
222        let custody_fees_token_account =
223            self.get_fund_custody_fees_token_account(fund_name, token_name, custody_type)?;
224
225        // instruction params
226        let custody_id = if custodies.is_empty() {
227            0
228        } else if custodies.last().unwrap().custody_id < u32::MAX {
229            custodies.last().unwrap().custody_id + 1
230        } else {
231            return Err(FarmClientError::ValueError(
232                "Number of custodies are over the limit".to_string(),
233            ));
234        };
235
236        let current_hash = self
237            .get_fund_assets(fund_name, FundAssetType::Custody)?
238            .target_hash;
239
240        let target_hash = if FarmClient::is_liquidity_token(token_name) {
241            current_hash
242        } else {
243            math::hash_address(current_hash, &custody_token_account)
244        };
245
246        // fill in accounts and instruction data
247        let data = FundInstruction::AddCustody {
248            target_hash,
249            custody_id,
250            custody_type,
251        }
252        .to_vec()?;
253        let accounts = vec![
254            AccountMeta::new_readonly(*admin_address, true),
255            AccountMeta::new_readonly(fund_ref, false),
256            AccountMeta::new(fund.info_account, false),
257            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
258            AccountMeta::new(self.get_fund_multisig_account(fund_name)?, false),
259            AccountMeta::new_readonly(fund.fund_authority, false),
260            AccountMeta::new_readonly(system_program::id(), false),
261            AccountMeta::new_readonly(spl_token::id(), false),
262            AccountMeta::new_readonly(spl_associated_token_account::id(), false),
263            AccountMeta::new_readonly(sysvar::rent::id(), false),
264            AccountMeta::new(fund_assets_account, false),
265            AccountMeta::new(custody_token_account, false),
266            AccountMeta::new(custody_fees_token_account, false),
267            AccountMeta::new(custody_metadata, false),
268            AccountMeta::new_readonly(token_ref, false),
269            AccountMeta::new(token.mint, false),
270        ];
271
272        Ok(Instruction {
273            program_id: fund.fund_program_id,
274            data,
275            accounts,
276        })
277    }
278
279    /// Creates a new Instruction for removing the custody from the Fund
280    pub fn new_instruction_remove_fund_custody(
281        &self,
282        admin_address: &Pubkey,
283        fund_name: &str,
284        token_name: &str,
285        custody_type: FundCustodyType,
286    ) -> Result<Instruction, FarmClientError> {
287        // get fund info
288        let fund = self.get_fund(fund_name)?;
289        let fund_ref = self.get_fund_ref(fund_name)?;
290        let token_ref = self.get_token_ref(token_name)?;
291
292        // get custodies
293        let custodies = self.get_fund_custodies(fund_name)?;
294        if custodies.is_empty() {
295            return Err(FarmClientError::ValueError(
296                "No active custodies found".to_string(),
297            ));
298        }
299        let custody_metadata =
300            self.get_fund_custody_account(fund_name, token_name, custody_type)?;
301        let fund_assets_account =
302            self.get_fund_assets_account(fund_name, FundAssetType::Custody)?;
303        let custody_token_account =
304            self.get_fund_custody_token_account(fund_name, token_name, custody_type)?;
305        let custody_fees_token_account =
306            self.get_fund_custody_fees_token_account(fund_name, token_name, custody_type)?;
307
308        // instruction params
309        let mut target_hash = 0;
310        for custody in custodies {
311            if custody.address != custody_token_account && !custody.is_vault_token {
312                target_hash = math::hash_address(target_hash, &custody.address);
313            }
314        }
315
316        // fill in accounts and instruction data
317        let data = FundInstruction::RemoveCustody {
318            target_hash,
319            custody_type,
320        }
321        .to_vec()?;
322        let accounts = vec![
323            AccountMeta::new_readonly(*admin_address, true),
324            AccountMeta::new_readonly(fund_ref, false),
325            AccountMeta::new(fund.info_account, false),
326            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
327            AccountMeta::new(self.get_fund_multisig_account(fund_name)?, false),
328            AccountMeta::new_readonly(fund.fund_authority, false),
329            AccountMeta::new_readonly(system_program::id(), false),
330            AccountMeta::new_readonly(spl_token::id(), false),
331            AccountMeta::new(fund_assets_account, false),
332            AccountMeta::new(custody_token_account, false),
333            AccountMeta::new(custody_fees_token_account, false),
334            AccountMeta::new(custody_metadata, false),
335            AccountMeta::new_readonly(token_ref, false),
336        ];
337
338        Ok(Instruction {
339            program_id: fund.fund_program_id,
340            data,
341            accounts,
342        })
343    }
344
345    /// Creates a new Instruction for adding a new Vault to the Fund
346    pub fn new_instruction_add_fund_vault(
347        &self,
348        admin_address: &Pubkey,
349        fund_name: &str,
350        vault_name: &str,
351        vault_type: FundVaultType,
352    ) -> Result<Instruction, FarmClientError> {
353        // get fund info
354        let fund = self.get_fund(fund_name)?;
355        let fund_ref = self.get_fund_ref(fund_name)?;
356
357        // get vaults
358        let vaults = self.get_fund_vaults(fund_name)?;
359        let fund_vault_metadata = self.get_fund_vault_account(fund_name, vault_name, vault_type)?;
360        let fund_assets_account = self.get_fund_assets_account(fund_name, FundAssetType::Vault)?;
361        let target_vault_metadata = match vault_type {
362            FundVaultType::Vault => self.get_vault_ref(vault_name)?,
363            FundVaultType::Pool => self.get_pool_ref(vault_name)?,
364            FundVaultType::Farm => self.get_farm_ref(vault_name)?,
365        };
366        let underlying_pool_ref = match vault_type {
367            FundVaultType::Vault => {
368                let vault = self.get_vault(vault_name)?;
369                match vault.strategy {
370                    VaultStrategy::StakeLpCompoundRewards { pool_ref, .. } => pool_ref,
371                    _ => unreachable!(),
372                }
373            }
374            FundVaultType::Farm => {
375                let farm = self.get_farm(vault_name)?;
376                let lp_token = self.get_token_by_ref(&farm.lp_token_ref.ok_or_else(|| {
377                    FarmClientError::ValueError("Farms w/o LP tokens are not supported".to_string())
378                })?)?;
379                let pools = self.find_pools_with_lp(&lp_token.name)?;
380                if pools.is_empty() {
381                    return Err(FarmClientError::RecordNotFound(format!(
382                        "Pools with LP token {}",
383                        lp_token.name
384                    )));
385                }
386                self.get_pool_ref(&pools[0].name)?
387            }
388            FundVaultType::Pool => target_vault_metadata,
389        };
390
391        // instruction params
392        let vault_id = if vaults.is_empty() {
393            0
394        } else if vaults.last().unwrap().vault_id < u32::MAX {
395            vaults.last().unwrap().vault_id + 1
396        } else {
397            return Err(FarmClientError::ValueError(
398                "Number of vaults are over the limit".to_string(),
399            ));
400        };
401
402        let current_hash = self
403            .get_fund_assets(fund_name, FundAssetType::Vault)?
404            .target_hash;
405
406        let target_hash = if vault_type == FundVaultType::Farm {
407            current_hash
408        } else {
409            math::hash_address(current_hash, &target_vault_metadata)
410        };
411
412        // fill in accounts and instruction data
413        let data = FundInstruction::AddVault {
414            target_hash,
415            vault_id,
416            vault_type,
417        }
418        .to_vec()?;
419
420        let (router_program_id, underlying_pool_id, underlying_lp_token_metadata) = match vault_type
421        {
422            FundVaultType::Pool => {
423                let pool = self.get_pool(vault_name)?;
424                let pool_ammid = match pool.route {
425                    PoolRoute::Raydium { amm_id, .. } => amm_id,
426                    PoolRoute::Saber { swap_account, .. } => swap_account,
427                    PoolRoute::Orca { amm_id, .. } => amm_id,
428                };
429                (
430                    pool.router_program_id,
431                    pool_ammid,
432                    pool.lp_token_ref.ok_or_else(|| {
433                        FarmClientError::ValueError(
434                            "Pools w/o LP tokens are not supported".to_string(),
435                        )
436                    })?,
437                )
438            }
439            FundVaultType::Farm => {
440                let farm = self.get_farm(vault_name)?;
441                let farm_id = match farm.route {
442                    FarmRoute::Raydium { farm_id, .. } => farm_id,
443                    FarmRoute::Saber { quarry, .. } => quarry,
444                    FarmRoute::Orca { farm_id, .. } => farm_id,
445                };
446                (
447                    farm.router_program_id,
448                    farm_id,
449                    farm.lp_token_ref.ok_or_else(|| {
450                        FarmClientError::ValueError(
451                            "Farms w/o LP tokens are not supported".to_string(),
452                        )
453                    })?,
454                )
455            }
456            FundVaultType::Vault => {
457                let vault = self.get_vault(vault_name)?;
458                let pool = self.get_pool_by_ref(&underlying_pool_ref)?;
459
460                (
461                    vault.vault_program_id,
462                    target_vault_metadata,
463                    pool.lp_token_ref.ok_or_else(|| {
464                        FarmClientError::ValueError(
465                            "Underlying Pools w/o LP tokens are not supported".to_string(),
466                        )
467                    })?,
468                )
469            }
470        };
471
472        let accounts = vec![
473            AccountMeta::new_readonly(*admin_address, true),
474            AccountMeta::new_readonly(fund_ref, false),
475            AccountMeta::new(fund.info_account, false),
476            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
477            AccountMeta::new_readonly(fund.fund_authority, false),
478            AccountMeta::new_readonly(system_program::id(), false),
479            AccountMeta::new(fund_assets_account, false),
480            AccountMeta::new(fund_vault_metadata, false),
481            AccountMeta::new_readonly(target_vault_metadata, false),
482            AccountMeta::new_readonly(router_program_id, false),
483            AccountMeta::new_readonly(underlying_pool_id, false),
484            AccountMeta::new_readonly(underlying_pool_ref, false),
485            AccountMeta::new_readonly(underlying_lp_token_metadata, false),
486        ];
487
488        Ok(Instruction {
489            program_id: fund.fund_program_id,
490            data,
491            accounts,
492        })
493    }
494
495    /// Creates a new Instruction for removing the Vault from the Fund
496    pub fn new_instruction_remove_fund_vault(
497        &self,
498        admin_address: &Pubkey,
499        fund_name: &str,
500        vault_name: &str,
501        vault_type: FundVaultType,
502    ) -> Result<Instruction, FarmClientError> {
503        // get fund info
504        let fund = self.get_fund(fund_name)?;
505        let fund_ref = self.get_fund_ref(fund_name)?;
506
507        // get vaults
508        let vaults = self.get_fund_vaults(fund_name)?;
509        if vaults.is_empty() {
510            return Err(FarmClientError::ValueError(
511                "No active vaults found".to_string(),
512            ));
513        }
514        let vault_metadata = self.get_fund_vault_account(fund_name, vault_name, vault_type)?;
515        let fund_assets_account = self.get_fund_assets_account(fund_name, FundAssetType::Vault)?;
516        let vault_info = match vault_type {
517            FundVaultType::Vault => self.get_vault_ref(vault_name)?,
518            FundVaultType::Pool => self.get_pool_ref(vault_name)?,
519            FundVaultType::Farm => self.get_farm_ref(vault_name)?,
520        };
521
522        // instruction params
523        let mut target_hash = 0;
524        for vault in vaults {
525            if vault.vault_ref != vault_info && vault.vault_type != FundVaultType::Farm {
526                target_hash = math::hash_address(target_hash, &vault.vault_ref);
527            }
528        }
529
530        // fill in accounts and instruction data
531        let data = FundInstruction::RemoveVault {
532            target_hash,
533            vault_type,
534        }
535        .to_vec()?;
536        let accounts = vec![
537            AccountMeta::new_readonly(*admin_address, true),
538            AccountMeta::new_readonly(fund_ref, false),
539            AccountMeta::new(fund.info_account, false),
540            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
541            AccountMeta::new_readonly(fund.fund_authority, false),
542            AccountMeta::new_readonly(system_program::id(), false),
543            AccountMeta::new(fund_assets_account, false),
544            AccountMeta::new(vault_metadata, false),
545        ];
546
547        Ok(Instruction {
548            program_id: fund.fund_program_id,
549            data,
550            accounts,
551        })
552    }
553
554    /// Creates a new set deposit schedule Instruction
555    pub fn new_instruction_set_fund_deposit_schedule(
556        &self,
557        admin_address: &Pubkey,
558        fund_name: &str,
559        schedule: &FundSchedule,
560    ) -> Result<Instruction, FarmClientError> {
561        // get fund info
562        let fund = self.get_fund(fund_name)?;
563        let fund_ref = self.get_fund_ref(fund_name)?;
564
565        // fill in accounts and instruction data
566        let data = FundInstruction::SetDepositSchedule {
567            schedule: *schedule,
568        }
569        .to_vec()?;
570        let accounts = vec![
571            AccountMeta::new_readonly(*admin_address, true),
572            AccountMeta::new_readonly(fund_ref, false),
573            AccountMeta::new(fund.info_account, false),
574            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
575        ];
576
577        Ok(Instruction {
578            program_id: fund.fund_program_id,
579            data,
580            accounts,
581        })
582    }
583
584    /// Creates a new Instruction for disabling deposits to the Fund
585    pub fn new_instruction_disable_deposits_fund(
586        &self,
587        admin_address: &Pubkey,
588        fund_name: &str,
589    ) -> Result<Instruction, FarmClientError> {
590        // get fund info
591        let fund = self.get_fund(fund_name)?;
592        let fund_ref = self.get_fund_ref(fund_name)?;
593
594        // fill in accounts and instruction data
595        let data = FundInstruction::DisableDeposits.to_vec()?;
596        let accounts = vec![
597            AccountMeta::new_readonly(*admin_address, true),
598            AccountMeta::new_readonly(fund_ref, false),
599            AccountMeta::new(fund.info_account, false),
600            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
601        ];
602
603        Ok(Instruction {
604            program_id: fund.fund_program_id,
605            data,
606            accounts,
607        })
608    }
609
610    /// Creates a new Instruction for requesting deposit to the Fund
611    pub fn new_instruction_request_deposit_fund(
612        &self,
613        wallet_address: &Pubkey,
614        fund_name: &str,
615        token_name: &str,
616        ui_amount: f64,
617    ) -> Result<Instruction, FarmClientError> {
618        if ui_amount < 0.0 {
619            return Err(FarmClientError::ValueError(format!(
620                "Invalid deposit amount {} specified for Fund {}: Must be greater or equal to zero.",
621                ui_amount, fund_name
622            )));
623        }
624        // get fund info
625        let fund = self.get_fund(fund_name)?;
626        let fund_ref = self.get_fund_ref(fund_name)?;
627        let fund_token = self.get_token_by_ref(&fund.fund_token_ref)?;
628        let token = self.get_token(token_name)?;
629        let token_ref = self.get_token_ref(token_name)?;
630        let user_info_account = self.get_fund_user_info_account(wallet_address, fund_name)?;
631        let user_requests_account =
632            self.get_fund_user_requests_account(wallet_address, fund_name, token_name)?;
633        let user_deposit_token_account =
634            self.get_associated_token_address(wallet_address, token.name.as_str())?;
635        let user_fund_token_account =
636            self.get_associated_token_address(wallet_address, fund_token.name.as_str())?;
637        let custody_metadata =
638            self.get_fund_custody_account(fund_name, token_name, FundCustodyType::DepositWithdraw)?;
639        let custody_token_account = self.get_fund_custody_token_account(
640            fund_name,
641            token_name,
642            FundCustodyType::DepositWithdraw,
643        )?;
644        let custody_fees_token_account = self.get_fund_custody_fees_token_account(
645            fund_name,
646            token_name,
647            FundCustodyType::DepositWithdraw,
648        )?;
649        let (_, oracle_account) = self.get_oracle(token_name)?;
650
651        // fill in accounts and instruction data
652        let data = FundInstruction::RequestDeposit {
653            amount: self.to_token_amount(ui_amount, &token)?,
654        }
655        .to_vec()?;
656        let accounts = vec![
657            AccountMeta::new_readonly(*wallet_address, true),
658            AccountMeta::new_readonly(fund_ref, false),
659            AccountMeta::new(fund.info_account, false),
660            AccountMeta::new_readonly(fund.fund_authority, false),
661            AccountMeta::new_readonly(spl_token::id(), false),
662            AccountMeta::new(fund_token.mint, false),
663            AccountMeta::new(user_info_account, false),
664            AccountMeta::new(user_requests_account, false),
665            AccountMeta::new(user_deposit_token_account, false),
666            AccountMeta::new(user_fund_token_account, false),
667            AccountMeta::new(custody_token_account, false),
668            AccountMeta::new(custody_fees_token_account, false),
669            AccountMeta::new_readonly(custody_metadata, false),
670            AccountMeta::new_readonly(token_ref, false),
671            AccountMeta::new_readonly(oracle_account.unwrap_or_else(zero::id), false),
672        ];
673
674        Ok(Instruction {
675            program_id: fund.fund_program_id,
676            data,
677            accounts,
678        })
679    }
680
681    /// Creates a new Instruction for canceling pending deposit to the Fund
682    pub fn new_instruction_cancel_deposit_fund(
683        &self,
684        wallet_address: &Pubkey,
685        fund_name: &str,
686        token_name: &str,
687    ) -> Result<Instruction, FarmClientError> {
688        // get fund info
689        let fund = self.get_fund(fund_name)?;
690        let fund_ref = self.get_fund_ref(fund_name)?;
691        let token = self.get_token(token_name)?;
692        let token_ref = self.get_token_ref(token_name)?;
693        let user_requests_account =
694            self.get_fund_user_requests_account(wallet_address, fund_name, token_name)?;
695        let user_deposit_token_account =
696            self.get_associated_token_address(wallet_address, token.name.as_str())?;
697
698        // fill in accounts and instruction data
699        let data = FundInstruction::CancelDeposit.to_vec()?;
700        let accounts = vec![
701            AccountMeta::new_readonly(*wallet_address, true),
702            AccountMeta::new_readonly(fund_ref, false),
703            AccountMeta::new(fund.info_account, false),
704            AccountMeta::new_readonly(spl_token::id(), false),
705            AccountMeta::new(user_requests_account, false),
706            AccountMeta::new(user_deposit_token_account, false),
707            AccountMeta::new_readonly(token_ref, false),
708        ];
709
710        Ok(Instruction {
711            program_id: fund.fund_program_id,
712            data,
713            accounts,
714        })
715    }
716
717    /// Creates a new Instruction for approving deposit to the Fund
718    pub fn new_instruction_approve_deposit_fund(
719        &self,
720        admin_address: &Pubkey,
721        user_address: &Pubkey,
722        fund_name: &str,
723        token_name: &str,
724        ui_amount: f64,
725    ) -> Result<Instruction, FarmClientError> {
726        if ui_amount < 0.0 {
727            return Err(FarmClientError::ValueError(format!(
728                "Invalid approve amount {} specified for Fund {}: Must be greater or equal to zero.",
729                ui_amount, fund_name
730            )));
731        }
732        // get fund info
733        let fund = self.get_fund(fund_name)?;
734        let fund_ref = self.get_fund_ref(fund_name)?;
735        let fund_token = self.get_token_by_ref(&fund.fund_token_ref)?;
736        let token = self.get_token(token_name)?;
737        let token_ref = self.get_token_ref(token_name)?;
738        let user_info_account = self.get_fund_user_info_account(user_address, fund_name)?;
739        let user_requests_account =
740            self.get_fund_user_requests_account(user_address, fund_name, token_name)?;
741        let user_deposit_token_account =
742            self.get_associated_token_address(user_address, token.name.as_str())?;
743        let user_fund_token_account =
744            self.get_associated_token_address(user_address, fund_token.name.as_str())?;
745        let custody_metadata =
746            self.get_fund_custody_account(fund_name, token_name, FundCustodyType::DepositWithdraw)?;
747        let custody_token_account = self.get_fund_custody_token_account(
748            fund_name,
749            token_name,
750            FundCustodyType::DepositWithdraw,
751        )?;
752        let custody_fees_token_account = self.get_fund_custody_fees_token_account(
753            fund_name,
754            token_name,
755            FundCustodyType::DepositWithdraw,
756        )?;
757        let (_, oracle_account) = self.get_oracle(token_name)?;
758
759        // fill in accounts and instruction data
760        let data = FundInstruction::ApproveDeposit {
761            amount: self.to_token_amount(ui_amount, &token)?,
762        }
763        .to_vec()?;
764        let accounts = vec![
765            AccountMeta::new_readonly(*admin_address, true),
766            AccountMeta::new_readonly(fund_ref, false),
767            AccountMeta::new(fund.info_account, false),
768            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
769            AccountMeta::new_readonly(fund.fund_authority, false),
770            AccountMeta::new_readonly(spl_token::id(), false),
771            AccountMeta::new(fund_token.mint, false),
772            AccountMeta::new_readonly(*user_address, false),
773            AccountMeta::new(user_info_account, false),
774            AccountMeta::new(user_requests_account, false),
775            AccountMeta::new(user_deposit_token_account, false),
776            AccountMeta::new(user_fund_token_account, false),
777            AccountMeta::new(custody_token_account, false),
778            AccountMeta::new(custody_fees_token_account, false),
779            AccountMeta::new_readonly(custody_metadata, false),
780            AccountMeta::new_readonly(token_ref, false),
781            AccountMeta::new_readonly(oracle_account.unwrap_or_else(zero::id), false),
782        ];
783
784        Ok(Instruction {
785            program_id: fund.fund_program_id,
786            data,
787            accounts,
788        })
789    }
790
791    /// Creates a new Instruction for denying deposit to the Fund
792    pub fn new_instruction_deny_deposit_fund(
793        &self,
794        admin_address: &Pubkey,
795        user_address: &Pubkey,
796        fund_name: &str,
797        token_name: &str,
798        deny_reason: &str,
799    ) -> Result<Instruction, FarmClientError> {
800        // get fund info
801        let fund = self.get_fund(fund_name)?;
802        let fund_ref = self.get_fund_ref(fund_name)?;
803        let token_ref = self.get_token_ref(token_name)?;
804        let user_requests_account =
805            self.get_fund_user_requests_account(user_address, fund_name, token_name)?;
806
807        // fill in accounts and instruction data
808        let data = FundInstruction::DenyDeposit {
809            deny_reason: str_to_as64(deny_reason)?,
810        }
811        .to_vec()?;
812        let accounts = vec![
813            AccountMeta::new_readonly(*admin_address, true),
814            AccountMeta::new_readonly(fund_ref, false),
815            AccountMeta::new(fund.info_account, false),
816            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
817            AccountMeta::new_readonly(*user_address, false),
818            AccountMeta::new(user_requests_account, false),
819            AccountMeta::new_readonly(token_ref, false),
820        ];
821
822        Ok(Instruction {
823            program_id: fund.fund_program_id,
824            data,
825            accounts,
826        })
827    }
828
829    /// Creates a new set withdrawal schedule Instruction
830    pub fn new_instruction_set_fund_withdrawal_schedule(
831        &self,
832        admin_address: &Pubkey,
833        fund_name: &str,
834        schedule: &FundSchedule,
835    ) -> Result<Instruction, FarmClientError> {
836        // get fund info
837        let fund = self.get_fund(fund_name)?;
838        let fund_ref = self.get_fund_ref(fund_name)?;
839
840        // fill in accounts and instruction data
841        let data = FundInstruction::SetWithdrawalSchedule {
842            schedule: *schedule,
843        }
844        .to_vec()?;
845        let accounts = vec![
846            AccountMeta::new_readonly(*admin_address, true),
847            AccountMeta::new_readonly(fund_ref, false),
848            AccountMeta::new(fund.info_account, false),
849            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
850        ];
851
852        Ok(Instruction {
853            program_id: fund.fund_program_id,
854            data,
855            accounts,
856        })
857    }
858
859    /// Creates a new Instruction for disabling withdrawals from the Fund
860    pub fn new_instruction_disable_withdrawals_fund(
861        &self,
862        admin_address: &Pubkey,
863        fund_name: &str,
864    ) -> Result<Instruction, FarmClientError> {
865        // get fund info
866        let fund = self.get_fund(fund_name)?;
867        let fund_ref = self.get_fund_ref(fund_name)?;
868
869        // fill in accounts and instruction data
870        let data = FundInstruction::DisableWithdrawals.to_vec()?;
871        let accounts = vec![
872            AccountMeta::new_readonly(*admin_address, true),
873            AccountMeta::new_readonly(fund_ref, false),
874            AccountMeta::new(fund.info_account, false),
875            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
876        ];
877
878        Ok(Instruction {
879            program_id: fund.fund_program_id,
880            data,
881            accounts,
882        })
883    }
884
885    /// Creates a new Instruction for requesting withdrawal from the Fund
886    pub fn new_instruction_request_withdrawal_fund(
887        &self,
888        wallet_address: &Pubkey,
889        fund_name: &str,
890        token_name: &str,
891        ui_amount: f64,
892    ) -> Result<Instruction, FarmClientError> {
893        if ui_amount < 0.0 {
894            return Err(FarmClientError::ValueError(format!(
895                "Invalid withdrawal amount {} specified for Fund {}: Must be greater or equal to zero.",
896                ui_amount, fund_name
897            )));
898        }
899        // get fund info
900        let fund = self.get_fund(fund_name)?;
901        let fund_ref = self.get_fund_ref(fund_name)?;
902        let fund_token = self.get_token_by_ref(&fund.fund_token_ref)?;
903        let token = self.get_token(token_name)?;
904        let token_ref = self.get_token_ref(token_name)?;
905        let user_info_account = self.get_fund_user_info_account(wallet_address, fund_name)?;
906        let user_requests_account =
907            self.get_fund_user_requests_account(wallet_address, fund_name, token_name)?;
908        let user_withdrawal_token_account =
909            self.get_associated_token_address(wallet_address, token.name.as_str())?;
910        let user_fund_token_account =
911            self.get_associated_token_address(wallet_address, fund_token.name.as_str())?;
912        let custody_metadata =
913            self.get_fund_custody_account(fund_name, token_name, FundCustodyType::DepositWithdraw)?;
914        let custody_token_account = self.get_fund_custody_token_account(
915            fund_name,
916            token_name,
917            FundCustodyType::DepositWithdraw,
918        )?;
919        let custody_fees_token_account = self.get_fund_custody_fees_token_account(
920            fund_name,
921            token_name,
922            FundCustodyType::DepositWithdraw,
923        )?;
924        let (_, oracle_account) = self.get_oracle(token_name)?;
925
926        // fill in accounts and instruction data
927        let data = FundInstruction::RequestWithdrawal {
928            amount: self.to_token_amount(ui_amount, &fund_token)?,
929        }
930        .to_vec()?;
931        let accounts = vec![
932            AccountMeta::new_readonly(*wallet_address, true),
933            AccountMeta::new_readonly(fund_ref, false),
934            AccountMeta::new(fund.info_account, false),
935            AccountMeta::new_readonly(fund.fund_authority, false),
936            AccountMeta::new_readonly(spl_token::id(), false),
937            AccountMeta::new(fund_token.mint, false),
938            AccountMeta::new(user_info_account, false),
939            AccountMeta::new(user_requests_account, false),
940            AccountMeta::new(user_withdrawal_token_account, false),
941            AccountMeta::new(user_fund_token_account, false),
942            AccountMeta::new(custody_token_account, false),
943            AccountMeta::new(custody_fees_token_account, false),
944            AccountMeta::new_readonly(custody_metadata, false),
945            AccountMeta::new_readonly(token_ref, false),
946            AccountMeta::new_readonly(oracle_account.unwrap_or_else(zero::id), false),
947        ];
948
949        Ok(Instruction {
950            program_id: fund.fund_program_id,
951            data,
952            accounts,
953        })
954    }
955
956    /// Creates a new Instruction for canceling pending withdrawal from the Fund
957    pub fn new_instruction_cancel_withdrawal_fund(
958        &self,
959        wallet_address: &Pubkey,
960        fund_name: &str,
961        token_name: &str,
962    ) -> Result<Instruction, FarmClientError> {
963        // get fund info
964        let fund = self.get_fund(fund_name)?;
965        let fund_ref = self.get_fund_ref(fund_name)?;
966        let token = self.get_token(token_name)?;
967        let token_ref = self.get_token_ref(token_name)?;
968        let user_requests_account =
969            self.get_fund_user_requests_account(wallet_address, fund_name, token_name)?;
970        let user_withdrawal_token_account =
971            self.get_associated_token_address(wallet_address, token.name.as_str())?;
972
973        // fill in accounts and instruction data
974        let data = FundInstruction::CancelWithdrawal.to_vec()?;
975        let accounts = vec![
976            AccountMeta::new_readonly(*wallet_address, true),
977            AccountMeta::new_readonly(fund_ref, false),
978            AccountMeta::new(fund.info_account, false),
979            AccountMeta::new_readonly(spl_token::id(), false),
980            AccountMeta::new(user_requests_account, false),
981            AccountMeta::new(user_withdrawal_token_account, false),
982            AccountMeta::new_readonly(token_ref, false),
983        ];
984
985        Ok(Instruction {
986            program_id: fund.fund_program_id,
987            data,
988            accounts,
989        })
990    }
991
992    /// Creates a new Instruction for approving withdrawal from the Fund
993    pub fn new_instruction_approve_withdrawal_fund(
994        &self,
995        admin_address: &Pubkey,
996        user_address: &Pubkey,
997        fund_name: &str,
998        token_name: &str,
999        ui_amount: f64,
1000    ) -> Result<Instruction, FarmClientError> {
1001        if ui_amount < 0.0 {
1002            return Err(FarmClientError::ValueError(format!(
1003                "Invalid approve amount {} specified for Fund {}: Must be greater or equal to zero.",
1004                ui_amount, fund_name
1005            )));
1006        }
1007        // get fund info
1008        let fund = self.get_fund(fund_name)?;
1009        let fund_ref = self.get_fund_ref(fund_name)?;
1010        let fund_token = self.get_token_by_ref(&fund.fund_token_ref)?;
1011        let token = self.get_token(token_name)?;
1012        let token_ref = self.get_token_ref(token_name)?;
1013        let user_info_account = self.get_fund_user_info_account(user_address, fund_name)?;
1014        let user_requests_account =
1015            self.get_fund_user_requests_account(user_address, fund_name, token_name)?;
1016        let user_withdrawal_token_account =
1017            self.get_associated_token_address(user_address, token.name.as_str())?;
1018        let user_fund_token_account =
1019            self.get_associated_token_address(user_address, fund_token.name.as_str())?;
1020        let custody_metadata =
1021            self.get_fund_custody_account(fund_name, token_name, FundCustodyType::DepositWithdraw)?;
1022        let custody_token_account = self.get_fund_custody_token_account(
1023            fund_name,
1024            token_name,
1025            FundCustodyType::DepositWithdraw,
1026        )?;
1027        let custody_fees_token_account = self.get_fund_custody_fees_token_account(
1028            fund_name,
1029            token_name,
1030            FundCustodyType::DepositWithdraw,
1031        )?;
1032        let (_, oracle_account) = self.get_oracle(token_name)?;
1033
1034        // fill in accounts and instruction data
1035        let data = FundInstruction::ApproveWithdrawal {
1036            amount: self.to_token_amount(ui_amount, &fund_token)?,
1037        }
1038        .to_vec()?;
1039        let accounts = vec![
1040            AccountMeta::new_readonly(*admin_address, true),
1041            AccountMeta::new_readonly(fund_ref, false),
1042            AccountMeta::new(fund.info_account, false),
1043            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
1044            AccountMeta::new_readonly(fund.fund_authority, false),
1045            AccountMeta::new_readonly(spl_token::id(), false),
1046            AccountMeta::new(fund_token.mint, false),
1047            AccountMeta::new_readonly(*user_address, false),
1048            AccountMeta::new(user_info_account, false),
1049            AccountMeta::new(user_requests_account, false),
1050            AccountMeta::new(user_withdrawal_token_account, false),
1051            AccountMeta::new(user_fund_token_account, false),
1052            AccountMeta::new(custody_token_account, false),
1053            AccountMeta::new(custody_fees_token_account, false),
1054            AccountMeta::new_readonly(custody_metadata, false),
1055            AccountMeta::new_readonly(token_ref, false),
1056            AccountMeta::new_readonly(oracle_account.unwrap_or_else(zero::id), false),
1057        ];
1058
1059        Ok(Instruction {
1060            program_id: fund.fund_program_id,
1061            data,
1062            accounts,
1063        })
1064    }
1065
1066    /// Creates a new Instruction for denying withdrawal from the Fund
1067    pub fn new_instruction_deny_withdrawal_fund(
1068        &self,
1069        admin_address: &Pubkey,
1070        user_address: &Pubkey,
1071        fund_name: &str,
1072        token_name: &str,
1073        deny_reason: &str,
1074    ) -> Result<Instruction, FarmClientError> {
1075        // get fund info
1076        let fund = self.get_fund(fund_name)?;
1077        let fund_ref = self.get_fund_ref(fund_name)?;
1078        let token_ref = self.get_token_ref(token_name)?;
1079        let user_requests_account =
1080            self.get_fund_user_requests_account(user_address, fund_name, token_name)?;
1081
1082        // fill in accounts and instruction data
1083        let data = FundInstruction::DenyWithdrawal {
1084            deny_reason: str_to_as64(deny_reason)?,
1085        }
1086        .to_vec()?;
1087        let accounts = vec![
1088            AccountMeta::new_readonly(*admin_address, true),
1089            AccountMeta::new_readonly(fund_ref, false),
1090            AccountMeta::new(fund.info_account, false),
1091            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
1092            AccountMeta::new_readonly(*user_address, false),
1093            AccountMeta::new(user_requests_account, false),
1094            AccountMeta::new_readonly(token_ref, false),
1095        ];
1096
1097        Ok(Instruction {
1098            program_id: fund.fund_program_id,
1099            data,
1100            accounts,
1101        })
1102    }
1103
1104    /// Creates a new Instruction for moving deposited assets to the Fund
1105    pub fn new_instruction_lock_assets_fund(
1106        &self,
1107        admin_address: &Pubkey,
1108        fund_name: &str,
1109        token_name: &str,
1110        ui_amount: f64,
1111    ) -> Result<Instruction, FarmClientError> {
1112        if ui_amount < 0.0 {
1113            return Err(FarmClientError::ValueError(format!(
1114                "Invalid lock amount {} specified for Fund {}: Must be greater or equal to zero.",
1115                ui_amount, fund_name
1116            )));
1117        }
1118        // get fund info
1119        let fund = self.get_fund(fund_name)?;
1120        let fund_ref = self.get_fund_ref(fund_name)?;
1121        let token = self.get_token(token_name)?;
1122        let token_ref = self.get_token_ref(token_name)?;
1123        let wd_custody_metadata =
1124            self.get_fund_custody_account(fund_name, token_name, FundCustodyType::DepositWithdraw)?;
1125        let wd_custody_token_account = self.get_fund_custody_token_account(
1126            fund_name,
1127            token_name,
1128            FundCustodyType::DepositWithdraw,
1129        )?;
1130        let trading_custody_metadata =
1131            self.get_fund_custody_account(fund_name, token_name, FundCustodyType::Trading)?;
1132        let trading_custody_token_account =
1133            self.get_fund_custody_token_account(fund_name, token_name, FundCustodyType::Trading)?;
1134
1135        // fill in accounts and instruction data
1136        let data = FundInstruction::LockAssets {
1137            amount: self.to_token_amount(ui_amount, &token)?,
1138        }
1139        .to_vec()?;
1140        let accounts = vec![
1141            AccountMeta::new_readonly(*admin_address, true),
1142            AccountMeta::new_readonly(fund_ref, false),
1143            AccountMeta::new(fund.info_account, false),
1144            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
1145            AccountMeta::new_readonly(fund.fund_authority, false),
1146            AccountMeta::new_readonly(spl_token::id(), false),
1147            AccountMeta::new(wd_custody_token_account, false),
1148            AccountMeta::new(wd_custody_metadata, false),
1149            AccountMeta::new(trading_custody_token_account, false),
1150            AccountMeta::new(trading_custody_metadata, false),
1151            AccountMeta::new_readonly(token_ref, false),
1152        ];
1153
1154        Ok(Instruction {
1155            program_id: fund.fund_program_id,
1156            data,
1157            accounts,
1158        })
1159    }
1160
1161    /// Creates a new Instruction for releasing assets from the Fund to Deposit/Withdraw custody
1162    pub fn new_instruction_unlock_assets_fund(
1163        &self,
1164        admin_address: &Pubkey,
1165        fund_name: &str,
1166        token_name: &str,
1167        ui_amount: f64,
1168    ) -> Result<Instruction, FarmClientError> {
1169        if ui_amount < 0.0 {
1170            return Err(FarmClientError::ValueError(format!(
1171                "Invalid unlock amount {} specified for Fund {}: Must be greater or equal to zero.",
1172                ui_amount, fund_name
1173            )));
1174        }
1175        // get fund info
1176        let fund = self.get_fund(fund_name)?;
1177        let fund_ref = self.get_fund_ref(fund_name)?;
1178        let token = self.get_token(token_name)?;
1179        let token_ref = self.get_token_ref(token_name)?;
1180        let wd_custody_metadata =
1181            self.get_fund_custody_account(fund_name, token_name, FundCustodyType::DepositWithdraw)?;
1182        let wd_custody_token_account = self.get_fund_custody_token_account(
1183            fund_name,
1184            token_name,
1185            FundCustodyType::DepositWithdraw,
1186        )?;
1187        let trading_custody_metadata =
1188            self.get_fund_custody_account(fund_name, token_name, FundCustodyType::Trading)?;
1189        let trading_custody_token_account =
1190            self.get_fund_custody_token_account(fund_name, token_name, FundCustodyType::Trading)?;
1191
1192        // fill in accounts and instruction data
1193        let data = FundInstruction::UnlockAssets {
1194            amount: self.to_token_amount(ui_amount, &token)?,
1195        }
1196        .to_vec()?;
1197        let accounts = vec![
1198            AccountMeta::new_readonly(*admin_address, true),
1199            AccountMeta::new_readonly(fund_ref, false),
1200            AccountMeta::new(fund.info_account, false),
1201            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
1202            AccountMeta::new_readonly(fund.fund_authority, false),
1203            AccountMeta::new_readonly(spl_token::id(), false),
1204            AccountMeta::new(wd_custody_token_account, false),
1205            AccountMeta::new(wd_custody_metadata, false),
1206            AccountMeta::new(trading_custody_token_account, false),
1207            AccountMeta::new(trading_custody_metadata, false),
1208            AccountMeta::new_readonly(token_ref, false),
1209        ];
1210
1211        Ok(Instruction {
1212            program_id: fund.fund_program_id,
1213            data,
1214            accounts,
1215        })
1216    }
1217
1218    /// Creates a new Instruction for initiating liquidation of the Fund
1219    pub fn new_instruction_start_liquidation_fund(
1220        &self,
1221        wallet_address: &Pubkey,
1222        fund_name: &str,
1223    ) -> Result<Instruction, FarmClientError> {
1224        // get fund info
1225        let fund = self.get_fund(fund_name)?;
1226        let fund_ref = self.get_fund_ref(fund_name)?;
1227        let fund_token = self.get_token_by_ref(&fund.fund_token_ref)?;
1228        let user_info_account = self.get_fund_user_info_account(wallet_address, fund_name)?;
1229        let user_fund_token_account =
1230            self.get_associated_token_address(wallet_address, fund_token.name.as_str())?;
1231
1232        // fill in accounts and instruction data
1233        let data = FundInstruction::StartLiquidation.to_vec()?;
1234        let accounts = vec![
1235            AccountMeta::new_readonly(*wallet_address, true),
1236            AccountMeta::new_readonly(fund_ref, false),
1237            AccountMeta::new(fund.info_account, false),
1238            AccountMeta::new(fund_token.mint, false),
1239            AccountMeta::new_readonly(user_info_account, false),
1240            AccountMeta::new_readonly(user_fund_token_account, false),
1241            AccountMeta::new_readonly(sysvar::instructions::id(), false),
1242        ];
1243
1244        Ok(Instruction {
1245            program_id: fund.fund_program_id,
1246            data,
1247            accounts,
1248        })
1249    }
1250
1251    /// Creates a new Instruction for stopping liquidation of the Fund
1252    pub fn new_instruction_stop_liquidation_fund(
1253        &self,
1254        admin_address: &Pubkey,
1255        fund_name: &str,
1256    ) -> Result<Instruction, FarmClientError> {
1257        // get fund info
1258        let fund = self.get_fund(fund_name)?;
1259        let fund_ref = self.get_fund_ref(fund_name)?;
1260
1261        // fill in accounts and instruction data
1262        let data = FundInstruction::StopLiquidation.to_vec()?;
1263        let accounts = vec![
1264            AccountMeta::new_readonly(*admin_address, true),
1265            AccountMeta::new_readonly(fund_ref, false),
1266            AccountMeta::new(fund.info_account, false),
1267            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
1268        ];
1269
1270        Ok(Instruction {
1271            program_id: fund.fund_program_id,
1272            data,
1273            accounts,
1274        })
1275    }
1276
1277    /// Creates a new Instruction for fees withdrawal from the Fund
1278    pub fn new_instruction_withdraw_fees_fund(
1279        &self,
1280        wallet_address: &Pubkey,
1281        fund_name: &str,
1282        token_name: &str,
1283        custody_type: FundCustodyType,
1284        ui_amount: f64,
1285        receiver: &Pubkey,
1286    ) -> Result<Instruction, FarmClientError> {
1287        // get fund info
1288        let fund = self.get_fund(fund_name)?;
1289        let fund_ref = self.get_fund_ref(fund_name)?;
1290
1291        // get custodies
1292        let custody_fees_token_account =
1293            self.get_fund_custody_fees_token_account(fund_name, token_name, custody_type)?;
1294
1295        // fill in accounts and instruction data
1296        let token = self.get_token(token_name)?;
1297        let data = FundInstruction::WithdrawFees {
1298            amount: self.ui_amount_to_tokens_with_decimals(ui_amount, token.decimals)?,
1299        }
1300        .to_vec()?;
1301        let accounts = vec![
1302            AccountMeta::new_readonly(*wallet_address, true),
1303            AccountMeta::new_readonly(fund_ref, false),
1304            AccountMeta::new(fund.info_account, false),
1305            AccountMeta::new(self.get_fund_active_multisig_account(fund_name)?, false),
1306            AccountMeta::new(self.get_fund_multisig_account(fund_name)?, false),
1307            AccountMeta::new_readonly(spl_token::id(), false),
1308            AccountMeta::new(custody_fees_token_account, false),
1309            AccountMeta::new(*receiver, false),
1310        ];
1311
1312        Ok(Instruction {
1313            program_id: fund.fund_program_id,
1314            data,
1315            accounts,
1316        })
1317    }
1318
1319    /// Creates a new Instruction for updating Fund assets based on custody holdings
1320    pub fn new_instruction_update_fund_assets_with_custody(
1321        &self,
1322        wallet_address: &Pubkey,
1323        fund_name: &str,
1324        custody_id: u32,
1325    ) -> Result<Instruction, FarmClientError> {
1326        // get fund info
1327        let fund = self.get_fund(fund_name)?;
1328        let fund_ref = self.get_fund_ref(fund_name)?;
1329
1330        // get custodies
1331        let custodies = self.get_fund_custodies(fund_name)?;
1332        let custody = custodies
1333            .iter()
1334            .find(|&c| c.custody_id == custody_id)
1335            .ok_or_else(|| {
1336                FarmClientError::RecordNotFound(format!("Custody with ID {}", custody_id))
1337            })?;
1338        let token = self.get_token_by_ref(&custody.token_ref)?;
1339        let custody_metadata =
1340            self.get_fund_custody_account(fund_name, token.name.as_str(), custody.custody_type)?;
1341        let custodies_assets_account =
1342            self.get_fund_assets_account(fund_name, FundAssetType::Custody)?;
1343        let vaults_assets_account =
1344            self.get_fund_assets_account(fund_name, FundAssetType::Vault)?;
1345        let custody_token_account = self.get_fund_custody_token_account(
1346            fund_name,
1347            token.name.as_str(),
1348            custody.custody_type,
1349        )?;
1350        let (_, oracle_account) = self.get_oracle(&token.name)?;
1351
1352        // fill in accounts and instruction data
1353        let accounts = vec![
1354            AccountMeta::new_readonly(*wallet_address, true),
1355            AccountMeta::new_readonly(fund_ref, false),
1356            AccountMeta::new(fund.info_account, false),
1357            AccountMeta::new(custodies_assets_account, false),
1358            AccountMeta::new_readonly(vaults_assets_account, false),
1359            AccountMeta::new(custody_token_account, false),
1360            AccountMeta::new(custody_metadata, false),
1361            AccountMeta::new_readonly(custody.token_ref, false),
1362            AccountMeta::new_readonly(oracle_account.unwrap_or_else(zero::id), false),
1363        ];
1364
1365        Ok(Instruction {
1366            program_id: fund.fund_program_id,
1367            data: FundInstruction::UpdateAssetsWithCustody.to_vec()?,
1368            accounts,
1369        })
1370    }
1371
1372    /// Creates a new Instruction for updating Fund assets with Vault holdings
1373    pub fn new_instruction_update_fund_assets_with_vault(
1374        &self,
1375        wallet_address: &Pubkey,
1376        fund_name: &str,
1377        vault_id: u32,
1378    ) -> Result<Instruction, FarmClientError> {
1379        // get fund info
1380        let fund = self.get_fund(fund_name)?;
1381        let fund_ref = self.get_fund_ref(fund_name)?;
1382
1383        // get vaults
1384        let vaults = self.get_fund_vaults(fund_name)?;
1385        let vault = vaults
1386            .iter()
1387            .find(|&c| c.vault_id == vault_id)
1388            .ok_or_else(|| {
1389                FarmClientError::RecordNotFound(format!("Fund Vault with ID {}", vault_id))
1390            })?;
1391        if vault.vault_type == FundVaultType::Farm {
1392            return Err(FarmClientError::ValueError(
1393                "Nothing to do: Farms are not processed to avoid double counting".to_string(),
1394            ));
1395        }
1396        let vault_name = match vault.vault_type {
1397            FundVaultType::Vault => self.get_vault_by_ref(&vault.vault_ref)?.name,
1398            FundVaultType::Pool => self.get_pool_by_ref(&vault.vault_ref)?.name,
1399            FundVaultType::Farm => unreachable!(),
1400        };
1401        let token_names = match vault.vault_type {
1402            FundVaultType::Vault => self.get_vault_token_names(&vault_name)?,
1403            FundVaultType::Pool => self.get_pool_token_names(&vault_name)?,
1404            FundVaultType::Farm => unreachable!(),
1405        };
1406        let target_vault_metadata = match vault.vault_type {
1407            FundVaultType::Vault => self.get_vault_ref(&vault_name)?,
1408            FundVaultType::Pool => self.get_pool_ref(&vault_name)?,
1409            FundVaultType::Farm => unreachable!(),
1410        };
1411        let underlying_pool_ref = if vault.vault_type == FundVaultType::Vault {
1412            match self.get_vault(&vault_name)?.strategy {
1413                VaultStrategy::StakeLpCompoundRewards { pool_ref, .. } => pool_ref,
1414                _ => unreachable!(),
1415            }
1416        } else {
1417            target_vault_metadata
1418        };
1419        let underlying_pool = self.get_pool_by_ref(&underlying_pool_ref)?;
1420        let underlying_lp_token = self.get_token_by_ref(
1421            &underlying_pool
1422                .lp_token_ref
1423                .ok_or(ProgramError::UninitializedAccount)?,
1424        )?;
1425        let (amm_id, amm_open_orders) = match underlying_pool.route {
1426            PoolRoute::Raydium {
1427                amm_id,
1428                amm_open_orders,
1429                ..
1430            } => (amm_id, amm_open_orders),
1431            PoolRoute::Orca { amm_id, .. } => (amm_id, zero::id()),
1432            _ => {
1433                return Err(FarmClientError::ValueError(
1434                    "Unsupported pool route".to_string(),
1435                ));
1436            }
1437        };
1438        let vault_metadata =
1439            self.get_fund_vault_account(fund_name, vault_name.as_str(), vault.vault_type)?;
1440        let custodies_assets_account =
1441            self.get_fund_assets_account(fund_name, FundAssetType::Custody)?;
1442        let vaults_assets_account =
1443            self.get_fund_assets_account(fund_name, FundAssetType::Vault)?;
1444        let (_, oracle_account_token_a) = if token_names.0.is_empty() {
1445            (OracleType::Unsupported, None)
1446        } else {
1447            self.get_oracle(&token_names.0)?
1448        };
1449        let (_, oracle_account_token_b) = if token_names.1.is_empty() {
1450            (OracleType::Unsupported, None)
1451        } else {
1452            self.get_oracle(&token_names.1)?
1453        };
1454
1455        // fill in accounts and instruction data
1456        let accounts = vec![
1457            AccountMeta::new_readonly(*wallet_address, true),
1458            AccountMeta::new_readonly(fund_ref, false),
1459            AccountMeta::new(fund.info_account, false),
1460            AccountMeta::new_readonly(custodies_assets_account, false),
1461            AccountMeta::new(vaults_assets_account, false),
1462            AccountMeta::new(vault_metadata, false),
1463            AccountMeta::new_readonly(vault.vault_ref, false),
1464            AccountMeta::new_readonly(underlying_pool_ref, false),
1465            AccountMeta::new_readonly(
1466                underlying_pool
1467                    .token_a_ref
1468                    .ok_or(ProgramError::UninitializedAccount)?,
1469                false,
1470            ),
1471            AccountMeta::new_readonly(
1472                underlying_pool
1473                    .token_b_ref
1474                    .ok_or(ProgramError::UninitializedAccount)?,
1475                false,
1476            ),
1477            AccountMeta::new_readonly(underlying_lp_token.mint, false),
1478            AccountMeta::new_readonly(
1479                underlying_pool
1480                    .token_a_account
1481                    .ok_or(ProgramError::UninitializedAccount)?,
1482                false,
1483            ),
1484            AccountMeta::new_readonly(
1485                underlying_pool
1486                    .token_b_account
1487                    .ok_or(ProgramError::UninitializedAccount)?,
1488                false,
1489            ),
1490            AccountMeta::new_readonly(amm_id, false),
1491            AccountMeta::new_readonly(amm_open_orders, false),
1492            AccountMeta::new_readonly(oracle_account_token_a.unwrap_or_else(zero::id), false),
1493            AccountMeta::new_readonly(oracle_account_token_b.unwrap_or_else(zero::id), false),
1494            AccountMeta::new_readonly(sysvar::instructions::id(), false),
1495        ];
1496
1497        Ok(Instruction {
1498            program_id: fund.fund_program_id,
1499            data: FundInstruction::UpdateAssetsWithVault.to_vec()?,
1500            accounts,
1501        })
1502    }
1503
1504    /// Creates a new complete set of Instructions for requesting a new deposit to the Fund
1505    pub fn all_instructions_request_deposit_fund(
1506        &self,
1507        wallet_address: &Pubkey,
1508        fund_name: &str,
1509        token_name: &str,
1510        ui_amount: f64,
1511    ) -> Result<Vec<Instruction>, FarmClientError> {
1512        let mut inst = Vec::<Instruction>::new();
1513        let _ =
1514            self.check_fund_accounts(wallet_address, fund_name, token_name, ui_amount, &mut inst)?;
1515
1516        // create and send the instruction
1517        inst.push(self.new_instruction_request_deposit_fund(
1518            wallet_address,
1519            fund_name,
1520            token_name,
1521            ui_amount,
1522        )?);
1523
1524        Ok(inst)
1525    }
1526
1527    /// Creates a new complete set of Instructions for requesting a new withdrawal from the Fund
1528    pub fn all_instructions_request_withdrawal_fund(
1529        &self,
1530        wallet_address: &Pubkey,
1531        fund_name: &str,
1532        token_name: &str,
1533        ui_amount: f64,
1534    ) -> Result<Vec<Instruction>, FarmClientError> {
1535        let mut inst = Vec::<Instruction>::new();
1536        let fund = self.get_fund(fund_name)?;
1537        let fund_token = Some(self.get_token_by_ref(&fund.fund_token_ref)?);
1538        let asset_token = Some(self.get_token(token_name)?);
1539        let _ = self.check_token_account(wallet_address, &fund_token, ui_amount, &mut inst)?;
1540        let _ = self.check_token_account(wallet_address, &asset_token, 0.0, &mut inst)?;
1541
1542        if self
1543            .get_fund_user_requests(wallet_address, fund_name, token_name)
1544            .is_err()
1545        {
1546            inst.push(self.new_instruction_user_init_fund(
1547                wallet_address,
1548                fund_name,
1549                token_name,
1550            )?);
1551        }
1552
1553        // create and send the instruction
1554        inst.push(self.new_instruction_request_withdrawal_fund(
1555            wallet_address,
1556            fund_name,
1557            token_name,
1558            ui_amount,
1559        )?);
1560
1561        Ok(inst)
1562    }
1563}