gmsol/
treasury.rs

1use std::{future::Future, ops::Deref};
2
3use anchor_client::{
4    anchor_lang::{prelude::AccountMeta, system_program, Id},
5    solana_client::rpc_config::RpcAccountInfoConfig,
6    solana_sdk::{pubkey::Pubkey, signer::Signer},
7};
8use anchor_spl::associated_token::get_associated_token_address_with_program_id;
9use gmsol_solana_utils::{
10    bundle_builder::{BundleBuilder, BundleOptions},
11    transaction_builder::TransactionBuilder,
12};
13use gmsol_store::states::{
14    common::TokensWithFeed, gt::GtExchange, Chainlink, NonceBytes, PriceProviderKind,
15};
16use gmsol_treasury::{
17    accounts, instruction,
18    states::{treasury::TokenFlag, Config, GtBank, TreasuryVaultConfig},
19};
20use solana_account_decoder::UiAccountEncoding;
21
22use crate::{
23    exchange::generate_nonce,
24    store::{gt::GtOps, token::TokenAccountOps, utils::FeedsParser},
25    utils::{
26        builder::{
27            FeedAddressMap, FeedIds, MakeBundleBuilder, PullOraclePriceConsumer, SetExecutionFee,
28        },
29        fix_optional_account_metas, ZeroCopy,
30    },
31};
32
33/// Treasury instructions.
34pub trait TreasuryOps<C> {
35    /// Initialize [`Config`] account.
36    fn initialize_config(&self, store: &Pubkey) -> TransactionBuilder<C, Pubkey>;
37
38    /// Set treasury vault config.
39    fn set_treasury_vault_config(
40        &self,
41        store: &Pubkey,
42        treasury_vault_config: &Pubkey,
43    ) -> TransactionBuilder<C>;
44
45    /// Set GT factor.
46    fn set_gt_factor(&self, store: &Pubkey, factor: u128) -> crate::Result<TransactionBuilder<C>>;
47
48    /// Set buyback factor.
49    fn set_buyback_factor(
50        &self,
51        store: &Pubkey,
52        factor: u128,
53    ) -> crate::Result<TransactionBuilder<C>>;
54
55    /// Initialize [`TreasuryVaultConfig`].
56    fn initialize_treasury_vault_config(
57        &self,
58        store: &Pubkey,
59        index: u16,
60    ) -> TransactionBuilder<C, Pubkey>;
61
62    /// Insert token to treasury.
63    fn insert_token_to_treasury(
64        &self,
65        store: &Pubkey,
66        treasury_vault_config: Option<&Pubkey>,
67        token_mint: &Pubkey,
68    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
69
70    /// Remove token from treasury.
71    fn remove_token_from_treasury(
72        &self,
73        store: &Pubkey,
74        treasury_vault_config: Option<&Pubkey>,
75        token_mint: &Pubkey,
76    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
77
78    /// Toggle token flag.
79    fn toggle_token_flag(
80        &self,
81        store: &Pubkey,
82        treasury_vault_config: Option<&Pubkey>,
83        token_mint: &Pubkey,
84        flag: TokenFlag,
85        value: bool,
86    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
87
88    /// Deposit to treasury vault.
89    fn deposit_to_treasury_valut(
90        &self,
91        store: &Pubkey,
92        treasury_vault_config_hint: Option<&Pubkey>,
93        token_mint: &Pubkey,
94        token_program_id: Option<&Pubkey>,
95        time_window: u32,
96    ) -> impl Future<Output = crate::Result<TransactionBuilder<C, Pubkey>>>;
97
98    /// Withdraw from treasury vault.
99    #[allow(clippy::too_many_arguments)]
100    fn withdraw_from_treasury_vault(
101        &self,
102        store: &Pubkey,
103        treasury_vault_config_hint: Option<&Pubkey>,
104        token_mint: &Pubkey,
105        token_program_id: Option<&Pubkey>,
106        amount: u64,
107        decimals: u8,
108        target: &Pubkey,
109    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
110
111    /// Confirm GT buyback.
112    fn confirm_gt_buyback(
113        &self,
114        store: &Pubkey,
115        gt_exchange_vault: &Pubkey,
116        oracle: &Pubkey,
117    ) -> ConfirmGtBuybackBuilder<C>;
118
119    /// Transfer receiver.
120    fn transfer_receiver(&self, store: &Pubkey, new_receiver: &Pubkey) -> TransactionBuilder<C>;
121
122    /// Set referral reward factors.
123    fn set_referral_reward(&self, store: &Pubkey, factors: Vec<u128>) -> TransactionBuilder<C>;
124
125    /// Claim fees to receiver vault.
126    fn claim_fees_to_receiver_vault(
127        &self,
128        store: &Pubkey,
129        market_token: &Pubkey,
130        token_mint: &Pubkey,
131        min_amount: u64,
132    ) -> TransactionBuilder<C>;
133
134    /// Prepare GT bank.
135    fn prepare_gt_bank(
136        &self,
137        store: &Pubkey,
138        treasury_vault_config_hint: Option<&Pubkey>,
139        gt_exchange_vault: &Pubkey,
140    ) -> impl Future<Output = crate::Result<TransactionBuilder<C, Pubkey>>>;
141
142    /// Sync GT bank.
143    fn sync_gt_bank(
144        &self,
145        store: &Pubkey,
146        treasury_vault_config_hint: Option<&Pubkey>,
147        gt_exchange_vault: &Pubkey,
148        token_mint: &Pubkey,
149        token_program_id: Option<&Pubkey>,
150    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
151
152    /// Complete GT exchange.
153    fn complete_gt_exchange(
154        &self,
155        store: &Pubkey,
156        exchange: &Pubkey,
157        treasury_vault_config_hint: Option<&Pubkey>,
158        tokens_hint: Option<Vec<(Pubkey, Pubkey)>>,
159        gt_exchange_vault_hint: Option<&Pubkey>,
160    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
161
162    /// Create a swap.
163    fn create_treasury_swap(
164        &self,
165        store: &Pubkey,
166        market_token: &Pubkey,
167        swap_in_token: &Pubkey,
168        swap_out_token: &Pubkey,
169        swap_in_token_amount: u64,
170        options: CreateTreasurySwapOptions,
171    ) -> impl Future<Output = crate::Result<TransactionBuilder<C, Pubkey>>>;
172
173    /// Cancel a swap.
174    fn cancel_treasury_swap(
175        &self,
176        store: &Pubkey,
177        order: &Pubkey,
178        hint: Option<(&Pubkey, &Pubkey)>,
179    ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
180}
181
182/// Create Treasury Swap Options.
183#[derive(Debug, Clone, Default)]
184pub struct CreateTreasurySwapOptions {
185    /// Nonce.
186    pub nonce: Option<NonceBytes>,
187    /// The market tokens of the swap path.
188    pub swap_path: Vec<Pubkey>,
189    /// Min swap out amount.
190    pub min_swap_out_amount: Option<u64>,
191    /// Hint for the treasury vault config address.
192    pub treasury_vault_config_hint: Option<Pubkey>,
193}
194
195impl<S, C> TreasuryOps<C> for crate::Client<C>
196where
197    C: Deref<Target = S> + Clone,
198    S: Signer,
199{
200    fn initialize_config(&self, store: &Pubkey) -> TransactionBuilder<C, Pubkey> {
201        let config = self.find_treasury_config_address(store);
202        self.treasury_transaction()
203            .anchor_args(instruction::InitializeConfig {})
204            .anchor_accounts(accounts::InitializeConfig {
205                payer: self.payer(),
206                store: *store,
207                config,
208                receiver: self.find_treasury_receiver_address(&config),
209                store_program: *self.store_program_id(),
210                system_program: system_program::ID,
211            })
212            .output(config)
213    }
214
215    fn set_treasury_vault_config(
216        &self,
217        store: &Pubkey,
218        treasury_vault_config: &Pubkey,
219    ) -> TransactionBuilder<C> {
220        let config = self.find_treasury_config_address(store);
221        self.treasury_transaction()
222            .anchor_args(instruction::SetTreasuryVaultConfig {})
223            .anchor_accounts(accounts::SetTreasuryVaultConfig {
224                authority: self.payer(),
225                store: *store,
226                config,
227                treasury_vault_config: *treasury_vault_config,
228                store_program: *self.store_program_id(),
229            })
230    }
231
232    fn set_gt_factor(&self, store: &Pubkey, factor: u128) -> crate::Result<TransactionBuilder<C>> {
233        if factor > crate::constants::MARKET_USD_UNIT {
234            return Err(crate::Error::invalid_argument(
235                "cannot use a factor greater than 1",
236            ));
237        }
238        let config = self.find_treasury_config_address(store);
239        Ok(self
240            .treasury_transaction()
241            .anchor_args(instruction::SetGtFactor { factor })
242            .anchor_accounts(accounts::UpdateConfig {
243                authority: self.payer(),
244                store: *store,
245                config,
246                store_program: *self.store_program_id(),
247            }))
248    }
249
250    fn set_buyback_factor(
251        &self,
252        store: &Pubkey,
253        factor: u128,
254    ) -> crate::Result<TransactionBuilder<C>> {
255        if factor > crate::constants::MARKET_USD_UNIT {
256            return Err(crate::Error::invalid_argument(
257                "cannot use a factor greater than 1",
258            ));
259        }
260        let config = self.find_treasury_config_address(store);
261        Ok(self
262            .treasury_transaction()
263            .anchor_args(instruction::SetBuybackFactor { factor })
264            .anchor_accounts(accounts::UpdateConfig {
265                authority: self.payer(),
266                store: *store,
267                config,
268                store_program: *self.store_program_id(),
269            }))
270    }
271
272    fn initialize_treasury_vault_config(
273        &self,
274        store: &Pubkey,
275        index: u16,
276    ) -> TransactionBuilder<C, Pubkey> {
277        let config = self.find_treasury_config_address(store);
278        let treasury_vault_config = self.find_treasury_vault_config_address(&config, index);
279        self.treasury_transaction()
280            .anchor_args(instruction::InitializeTreasuryVaultConfig { index })
281            .anchor_accounts(accounts::InitializeTreasuryVaultConfig {
282                authority: self.payer(),
283                store: *store,
284                config,
285                treasury_vault_config,
286                store_program: *self.store_program_id(),
287                system_program: system_program::ID,
288            })
289            .output(treasury_vault_config)
290    }
291
292    async fn insert_token_to_treasury(
293        &self,
294        store: &Pubkey,
295        treasury_vault_config: Option<&Pubkey>,
296        token_mint: &Pubkey,
297    ) -> crate::Result<TransactionBuilder<C>> {
298        let (config, treasury_vault_config) =
299            find_config_addresses(self, store, treasury_vault_config).await?;
300        Ok(self
301            .treasury_transaction()
302            .anchor_args(instruction::InsertTokenToTreasuryVault {})
303            .anchor_accounts(accounts::InsertTokenToTreasuryVault {
304                authority: self.payer(),
305                store: *store,
306                config,
307                treasury_vault_config,
308                token: *token_mint,
309                store_program: *self.store_program_id(),
310            }))
311    }
312
313    async fn remove_token_from_treasury(
314        &self,
315        store: &Pubkey,
316        treasury_vault_config: Option<&Pubkey>,
317        token_mint: &Pubkey,
318    ) -> crate::Result<TransactionBuilder<C>> {
319        let (config, treasury_vault_config) =
320            find_config_addresses(self, store, treasury_vault_config).await?;
321        Ok(self
322            .treasury_transaction()
323            .anchor_args(instruction::RemoveTokenFromTreasuryVault {})
324            .anchor_accounts(accounts::RemoveTokenFromTreasuryVault {
325                authority: self.payer(),
326                store: *store,
327                config,
328                treasury_vault_config,
329                token: *token_mint,
330                store_program: *self.store_program_id(),
331            }))
332    }
333
334    async fn toggle_token_flag(
335        &self,
336        store: &Pubkey,
337        treasury_vault_config: Option<&Pubkey>,
338        token_mint: &Pubkey,
339        flag: TokenFlag,
340        value: bool,
341    ) -> crate::Result<TransactionBuilder<C>> {
342        let (config, treasury_vault_config) =
343            find_config_addresses(self, store, treasury_vault_config).await?;
344        Ok(self
345            .treasury_transaction()
346            .anchor_args(instruction::ToggleTokenFlag {
347                flag: flag.to_string(),
348                value,
349            })
350            .anchor_accounts(accounts::ToggleTokenFlag {
351                authority: self.payer(),
352                store: *store,
353                config,
354                treasury_vault_config,
355                token: *token_mint,
356                store_program: *self.store_program_id(),
357            }))
358    }
359
360    async fn deposit_to_treasury_valut(
361        &self,
362        store: &Pubkey,
363        treasury_vault_config_hint: Option<&Pubkey>,
364        token_mint: &Pubkey,
365        token_program_id: Option<&Pubkey>,
366        time_window: u32,
367    ) -> crate::Result<TransactionBuilder<C, Pubkey>> {
368        let (config, treasury_vault_config) =
369            find_config_addresses(self, store, treasury_vault_config_hint).await?;
370
371        let (prepare_gt_exchange_vault, gt_exchange_vault) = self
372            .prepare_gt_exchange_vault_with_time_window(store, time_window)?
373            .swap_output(());
374
375        let (prepare_gt_bank, gt_bank) = self
376            .prepare_gt_bank(store, Some(&treasury_vault_config), &gt_exchange_vault)
377            .await?
378            .swap_output(());
379
380        let token_program_id = token_program_id.unwrap_or(&anchor_spl::token::ID);
381
382        let receiver = self.find_treasury_receiver_address(&config);
383
384        let receiver_vault =
385            get_associated_token_address_with_program_id(&receiver, token_mint, token_program_id);
386        let treasury_vault = get_associated_token_address_with_program_id(
387            &treasury_vault_config,
388            token_mint,
389            token_program_id,
390        );
391        let gt_bank_vault =
392            get_associated_token_address_with_program_id(&gt_bank, token_mint, token_program_id);
393
394        let prepare_treasury_vault = self.prepare_associated_token_account(
395            token_mint,
396            token_program_id,
397            Some(&treasury_vault_config),
398        );
399        let prepare_gt_bank_vault =
400            self.prepare_associated_token_account(token_mint, token_program_id, Some(&gt_bank));
401
402        let deposit = self
403            .treasury_transaction()
404            .anchor_args(instruction::DepositToTreasuryVault {})
405            .anchor_accounts(accounts::DepositToTreasuryVault {
406                authority: self.payer(),
407                store: *store,
408                config,
409                treasury_vault_config,
410                receiver,
411                gt_exchange_vault,
412                gt_bank,
413                token: *token_mint,
414                receiver_vault,
415                treasury_vault,
416                gt_bank_vault,
417                store_program: *self.store_program_id(),
418                token_program: *token_program_id,
419                associated_token_program: anchor_spl::associated_token::ID,
420            });
421        Ok(prepare_gt_exchange_vault
422            .merge(prepare_gt_bank)
423            .merge(prepare_treasury_vault)
424            .merge(prepare_gt_bank_vault)
425            .merge(deposit)
426            .output(gt_exchange_vault))
427    }
428
429    async fn withdraw_from_treasury_vault(
430        &self,
431        store: &Pubkey,
432        treasury_vault_config_hint: Option<&Pubkey>,
433        token_mint: &Pubkey,
434        token_program_id: Option<&Pubkey>,
435        amount: u64,
436        decimals: u8,
437        target: &Pubkey,
438    ) -> crate::Result<TransactionBuilder<C>> {
439        let token_program_id = token_program_id.unwrap_or(&anchor_spl::token::ID);
440
441        let (config, treasury_vault_config) =
442            find_config_addresses(self, store, treasury_vault_config_hint).await?;
443
444        let treasury_vault = get_associated_token_address_with_program_id(
445            &treasury_vault_config,
446            token_mint,
447            token_program_id,
448        );
449
450        Ok(self
451            .treasury_transaction()
452            .anchor_args(instruction::WithdrawFromTreasuryVault { amount, decimals })
453            .anchor_accounts(accounts::WithdrawFromTreasuryVault {
454                authority: self.payer(),
455                store: *store,
456                config,
457                treasury_vault_config,
458                token: *token_mint,
459                treasury_vault,
460                target: *target,
461                store_program: *self.store_program_id(),
462                token_program: *token_program_id,
463            }))
464    }
465
466    fn confirm_gt_buyback(
467        &self,
468        store: &Pubkey,
469        gt_exchange_vault: &Pubkey,
470        oracle: &Pubkey,
471    ) -> ConfirmGtBuybackBuilder<C> {
472        ConfirmGtBuybackBuilder::new(self, store, gt_exchange_vault, oracle)
473    }
474
475    fn transfer_receiver(&self, store: &Pubkey, new_receiver: &Pubkey) -> TransactionBuilder<C> {
476        let config = self.find_treasury_config_address(store);
477        let receiver = self.find_treasury_receiver_address(&config);
478        self.treasury_transaction()
479            .anchor_args(instruction::TransferReceiver {})
480            .anchor_accounts(accounts::TransferReceiver {
481                authority: self.payer(),
482                store: *store,
483                config,
484                receiver,
485                next_receiver: *new_receiver,
486                store_program: *self.store_program_id(),
487                system_program: system_program::ID,
488            })
489    }
490
491    fn set_referral_reward(&self, store: &Pubkey, factors: Vec<u128>) -> TransactionBuilder<C> {
492        self.treasury_transaction()
493            .anchor_args(instruction::SetReferralReward { factors })
494            .anchor_accounts(accounts::SetReferralReward {
495                authority: self.payer(),
496                store: *store,
497                config: self.find_treasury_config_address(store),
498                store_program: *self.store_program_id(),
499            })
500    }
501
502    fn claim_fees_to_receiver_vault(
503        &self,
504        store: &Pubkey,
505        market_token: &Pubkey,
506        token_mint: &Pubkey,
507        min_amount: u64,
508    ) -> TransactionBuilder<C> {
509        let config = self.find_treasury_config_address(store);
510        let token_program_id = anchor_spl::token::ID;
511        let receiver = self.find_treasury_receiver_address(&config);
512        let receiver_vault =
513            get_associated_token_address_with_program_id(&receiver, token_mint, &token_program_id);
514        self.treasury_transaction()
515            .anchor_args(instruction::ClaimFees { min_amount })
516            .anchor_accounts(accounts::ClaimFees {
517                authority: self.payer(),
518                store: *store,
519                config,
520                receiver,
521                market: self.find_market_address(store, market_token),
522                token: *token_mint,
523                vault: self.find_market_vault_address(store, token_mint),
524                receiver_vault,
525                event_authority: self.store_event_authority(),
526                store_program: *self.store_program_id(),
527                token_program: token_program_id,
528                associated_token_program: anchor_spl::associated_token::ID,
529                system_program: system_program::ID,
530            })
531    }
532
533    async fn prepare_gt_bank(
534        &self,
535        store: &Pubkey,
536        treasury_vault_config_hint: Option<&Pubkey>,
537        gt_exchange_vault: &Pubkey,
538    ) -> crate::Result<TransactionBuilder<C, Pubkey>> {
539        let (config, treasury_vault_config) =
540            find_config_addresses(self, store, treasury_vault_config_hint).await?;
541        let gt_bank = self.find_gt_bank_address(&treasury_vault_config, gt_exchange_vault);
542        Ok(self
543            .treasury_transaction()
544            .anchor_args(instruction::PrepareGtBank {})
545            .anchor_accounts(accounts::PrepareGtBank {
546                authority: self.payer(),
547                store: *store,
548                config,
549                treasury_vault_config,
550                gt_exchange_vault: *gt_exchange_vault,
551                gt_bank,
552                store_program: *self.store_program_id(),
553                system_program: system_program::ID,
554            })
555            .output(gt_bank))
556    }
557
558    async fn sync_gt_bank(
559        &self,
560        store: &Pubkey,
561        treasury_vault_config_hint: Option<&Pubkey>,
562        gt_exchange_vault: &Pubkey,
563        token_mint: &Pubkey,
564        token_program_id: Option<&Pubkey>,
565    ) -> crate::Result<TransactionBuilder<C>> {
566        let (config, treasury_vault_config) =
567            find_config_addresses(self, store, treasury_vault_config_hint).await?;
568        let gt_bank = self.find_gt_bank_address(&treasury_vault_config, gt_exchange_vault);
569        let token_program_id = token_program_id.unwrap_or(&anchor_spl::token::ID);
570
571        let treasury_vault = get_associated_token_address_with_program_id(
572            &treasury_vault_config,
573            token_mint,
574            token_program_id,
575        );
576        let gt_bank_vault =
577            get_associated_token_address_with_program_id(&gt_bank, token_mint, token_program_id);
578
579        let prepare_treasury_vault = self.prepare_associated_token_account(
580            token_mint,
581            token_program_id,
582            Some(&treasury_vault_config),
583        );
584        let prepare_gt_bank_vault =
585            self.prepare_associated_token_account(token_mint, token_program_id, Some(&gt_bank));
586
587        let sync = self
588            .treasury_transaction()
589            .anchor_args(instruction::SyncGtBank {})
590            .anchor_accounts(accounts::SyncGtBank {
591                authority: self.payer(),
592                store: *store,
593                config,
594                treasury_vault_config,
595                gt_bank,
596                token: *token_mint,
597                treasury_vault,
598                gt_bank_vault,
599                store_program: *self.store_program_id(),
600                token_program: *token_program_id,
601                associated_token_program: anchor_spl::associated_token::ID,
602            });
603
604        Ok(prepare_treasury_vault
605            .merge(prepare_gt_bank_vault)
606            .merge(sync))
607    }
608
609    async fn complete_gt_exchange(
610        &self,
611        store: &Pubkey,
612        exchange: &Pubkey,
613        treasury_vault_config_hint: Option<&Pubkey>,
614        tokens_hint: Option<Vec<(Pubkey, Pubkey)>>,
615        gt_exchange_vault_hint: Option<&Pubkey>,
616    ) -> crate::Result<TransactionBuilder<C>> {
617        let owner = self.payer();
618        let (config, treasury_vault_config) =
619            find_config_addresses(self, store, treasury_vault_config_hint).await?;
620        let gt_exchange_vault = match gt_exchange_vault_hint {
621            Some(address) => *address,
622            None => {
623                let exchange = self
624                    .account::<ZeroCopy<GtExchange>>(exchange)
625                    .await?
626                    .ok_or(crate::Error::NotFound)?
627                    .0;
628                *exchange.vault()
629            }
630        };
631        let gt_bank = self.find_gt_bank_address(&treasury_vault_config, &gt_exchange_vault);
632
633        let tokens = match tokens_hint {
634            Some(tokens) => tokens,
635            None => {
636                let gt_bank = self
637                    .account::<ZeroCopy<GtBank>>(&gt_bank)
638                    .await?
639                    .ok_or_else(|| {
640                        crate::Error::invalid_argument("treasury vault config not exist")
641                    })?
642                    .0;
643
644                let tokens = gt_bank.tokens().collect::<Vec<_>>();
645                self.treasury_program()
646                    .rpc()
647                    .get_multiple_accounts_with_config(
648                        &tokens,
649                        RpcAccountInfoConfig {
650                            encoding: Some(UiAccountEncoding::Base64),
651                            data_slice: Some(solana_account_decoder::UiDataSliceConfig {
652                                offset: 0,
653                                length: 0,
654                            }),
655                            ..Default::default()
656                        },
657                    )
658                    .await
659                    .map_err(crate::Error::invalid_argument)?
660                    .value
661                    .into_iter()
662                    .zip(&tokens)
663                    .map(|(account, address)| {
664                        let account = account.ok_or(crate::Error::NotFound)?;
665                        Ok((*address, account.owner))
666                    })
667                    .collect::<crate::Result<Vec<_>>>()?
668            }
669        };
670
671        let token_mints = tokens.iter().map(|pubkey| AccountMeta {
672            pubkey: pubkey.0,
673            is_signer: false,
674            is_writable: false,
675        });
676        let gt_bank_vaults = tokens.iter().map(|(mint, token_program_id)| {
677            let gt_bank_vault =
678                get_associated_token_address_with_program_id(&gt_bank, mint, token_program_id);
679            AccountMeta {
680                pubkey: gt_bank_vault,
681                is_signer: false,
682                is_writable: true,
683            }
684        });
685        let atas = tokens.iter().map(|(mint, token_program_id)| {
686            let ata = get_associated_token_address_with_program_id(&owner, mint, token_program_id);
687            AccountMeta {
688                pubkey: ata,
689                is_signer: false,
690                is_writable: true,
691            }
692        });
693
694        Ok(self
695            .treasury_transaction()
696            .anchor_args(instruction::CompleteGtExchange {})
697            .anchor_accounts(accounts::CompleteGtExchange {
698                owner,
699                store: *store,
700                config,
701                treasury_vault_config,
702                gt_exchange_vault,
703                gt_bank,
704                exchange: *exchange,
705                store_program: *self.store_program_id(),
706                token_program: anchor_spl::token::ID,
707                token_2022_program: anchor_spl::token_2022::ID,
708            })
709            .accounts(
710                token_mints
711                    .chain(gt_bank_vaults)
712                    .chain(atas)
713                    .collect::<Vec<_>>(),
714            ))
715    }
716
717    async fn create_treasury_swap(
718        &self,
719        store: &Pubkey,
720        market_token: &Pubkey,
721        swap_in_token: &Pubkey,
722        swap_out_token: &Pubkey,
723        swap_in_token_amount: u64,
724        options: CreateTreasurySwapOptions,
725    ) -> crate::Result<TransactionBuilder<C, Pubkey>> {
726        let nonce = options.nonce.unwrap_or_else(generate_nonce);
727        let swap_path = options
728            .swap_path
729            .iter()
730            .chain(Some(market_token))
731            .map(|token| {
732                let pubkey = self.find_market_address(store, token);
733                AccountMeta {
734                    pubkey,
735                    is_signer: false,
736                    is_writable: false,
737                }
738            })
739            .collect::<Vec<_>>();
740        let (config, treasury_vault_config) =
741            find_config_addresses(self, store, options.treasury_vault_config_hint.as_ref()).await?;
742
743        let receiver = self.find_treasury_receiver_address(&config);
744
745        // Currently only SPL-Token is supported.
746        let token_program_id = anchor_spl::token::ID;
747
748        let swap_in_token_receiver_vault = get_associated_token_address_with_program_id(
749            &receiver,
750            swap_in_token,
751            &token_program_id,
752        );
753
754        let market = self.find_market_address(store, market_token);
755
756        let user = self.find_user_address(store, &receiver);
757
758        let order = self.find_order_address(store, &receiver, &nonce);
759
760        let swap_in_token_escrow =
761            get_associated_token_address_with_program_id(&order, swap_in_token, &token_program_id);
762        let swap_out_token_escrow =
763            get_associated_token_address_with_program_id(&order, swap_out_token, &token_program_id);
764
765        let prepare_swap_in_escrow =
766            self.prepare_associated_token_account(swap_in_token, &token_program_id, Some(&order));
767        let prepare_swap_out_escrow =
768            self.prepare_associated_token_account(swap_out_token, &token_program_id, Some(&order));
769        let prepare_ata = self.prepare_associated_token_account(
770            swap_out_token,
771            &token_program_id,
772            Some(&receiver),
773        );
774
775        let create = self
776            .treasury_transaction()
777            .anchor_args(instruction::CreateSwap {
778                nonce,
779                swap_path_length: swap_path
780                    .len()
781                    .try_into()
782                    .map_err(|_| crate::Error::invalid_argument("swap path is too long"))?,
783                swap_in_amount: swap_in_token_amount,
784                min_swap_out_amount: options.min_swap_out_amount,
785            })
786            .anchor_accounts(accounts::CreateSwap {
787                authority: self.payer(),
788                store: *store,
789                config,
790                treasury_vault_config,
791                swap_in_token: *swap_in_token,
792                swap_out_token: *swap_out_token,
793                swap_in_token_receiver_vault,
794                market,
795                receiver,
796                user,
797                swap_in_token_escrow,
798                swap_out_token_escrow,
799                order,
800                store_program: *self.store_program_id(),
801                token_program: token_program_id,
802                associated_token_program: anchor_spl::associated_token::ID,
803                system_program: system_program::ID,
804            })
805            .accounts(swap_path);
806
807        Ok(prepare_ata
808            .merge(prepare_swap_in_escrow)
809            .merge(prepare_swap_out_escrow)
810            .merge(create)
811            .output(order))
812    }
813
814    async fn cancel_treasury_swap(
815        &self,
816        store: &Pubkey,
817        order: &Pubkey,
818        hint: Option<(&Pubkey, &Pubkey)>,
819    ) -> crate::Result<TransactionBuilder<C>> {
820        let config = self.find_treasury_config_address(store);
821        let receiver = self.find_treasury_receiver_address(&config);
822        let user = self.find_user_address(store, &receiver);
823
824        let (swap_in_token, swap_out_token) = match hint {
825            Some((swap_in_token, swap_out_token)) => (*swap_in_token, *swap_out_token),
826            None => {
827                let order = self.order(order).await?;
828                let swap_in_token =
829                    order.tokens().initial_collateral().token().ok_or_else(|| {
830                        crate::Error::invalid_argument("invalid swap order: missing swap in token")
831                    })?;
832
833                let swap_out_token =
834                    order.tokens().final_output_token().token().ok_or_else(|| {
835                        crate::Error::invalid_argument("invalid swap order: missing swap out token")
836                    })?;
837                (swap_in_token, swap_out_token)
838            }
839        };
840
841        // Currently only SPL-Token is supported.
842        let token_program_id = anchor_spl::token::ID;
843
844        let swap_in_token_receiver_vault = get_associated_token_address_with_program_id(
845            &receiver,
846            &swap_in_token,
847            &token_program_id,
848        );
849        let swap_out_token_receiver_vault = get_associated_token_address_with_program_id(
850            &receiver,
851            &swap_out_token,
852            &token_program_id,
853        );
854        let swap_in_token_escrow =
855            get_associated_token_address_with_program_id(order, &swap_in_token, &token_program_id);
856        let swap_out_token_escrow =
857            get_associated_token_address_with_program_id(order, &swap_out_token, &token_program_id);
858
859        let prepare = self.prepare_associated_token_account(
860            &swap_out_token,
861            &token_program_id,
862            Some(&receiver),
863        );
864
865        let cancel = self
866            .treasury_transaction()
867            .anchor_args(instruction::CancelSwap {})
868            .anchor_accounts(accounts::CancelSwap {
869                authority: self.payer(),
870                store: *store,
871                store_wallet: self.find_store_wallet_address(store),
872                config,
873                receiver,
874                user,
875                swap_in_token,
876                swap_out_token,
877                swap_in_token_receiver_vault,
878                swap_out_token_receiver_vault,
879                swap_in_token_escrow,
880                swap_out_token_escrow,
881                order: *order,
882                event_authority: self.store_event_authority(),
883                store_program: *self.store_program_id(),
884                token_program: token_program_id,
885                associated_token_program: anchor_spl::associated_token::ID,
886                system_program: system_program::ID,
887            });
888
889        Ok(prepare.merge(cancel))
890    }
891}
892
893async fn find_config_addresses<C: Deref<Target = impl Signer> + Clone>(
894    client: &crate::Client<C>,
895    store: &Pubkey,
896    treasury_vault_config: Option<&Pubkey>,
897) -> crate::Result<(Pubkey, Pubkey)> {
898    let config = client.find_treasury_config_address(store);
899    match treasury_vault_config {
900        Some(address) => Ok((config, *address)),
901        None => {
902            let config_account = client
903                .account::<ZeroCopy<Config>>(&config)
904                .await?
905                .ok_or(crate::Error::NotFound)?
906                .0;
907            Ok((
908                config,
909                *config_account.treasury_vault_config().ok_or_else(|| {
910                    crate::Error::invalid_argument("treasury vault config is not set")
911                })?,
912            ))
913        }
914    }
915}
916
917/// Confirm GT buyback builder.
918pub struct ConfirmGtBuybackBuilder<'a, C> {
919    client: &'a crate::Client<C>,
920    store: Pubkey,
921    gt_exchange_vault: Pubkey,
922    oracle: Pubkey,
923    with_chainlink_program: bool,
924    feeds_parser: FeedsParser,
925    hint: Option<ConfirmGtBuybackHint>,
926}
927
928/// Hint for confirming GT buyback.
929#[derive(Debug, Clone)]
930pub struct ConfirmGtBuybackHint {
931    config: Pubkey,
932    treasury_vault_config: Pubkey,
933    token_map: Pubkey,
934    treasury_tokens: Vec<Pubkey>,
935    feeds: TokensWithFeed,
936}
937
938impl<'a, C: Deref<Target = impl Signer> + Clone> ConfirmGtBuybackBuilder<'a, C> {
939    pub(super) fn new(
940        client: &'a crate::Client<C>,
941        store: &Pubkey,
942        gt_exchange_vault: &Pubkey,
943        oracle: &Pubkey,
944    ) -> Self {
945        Self {
946            client,
947            store: *store,
948            gt_exchange_vault: *gt_exchange_vault,
949            oracle: *oracle,
950            with_chainlink_program: false,
951            feeds_parser: Default::default(),
952            hint: None,
953        }
954    }
955
956    /// Prepare [`ConfirmGtBuybackHint`].
957    pub async fn prepare_hint(&mut self) -> crate::Result<ConfirmGtBuybackHint> {
958        match &self.hint {
959            Some(hint) => Ok(hint.clone()),
960            None => {
961                let (config, treasury_vault_config_address) =
962                    find_config_addresses(self.client, &self.store, None).await?;
963                let gt_bank = self
964                    .client
965                    .find_gt_bank_address(&treasury_vault_config_address, &self.gt_exchange_vault);
966                let map_address = self
967                    .client
968                    .authorized_token_map_address(&self.store)
969                    .await?
970                    .ok_or_else(|| crate::Error::invalid_argument("token map is not set"))?;
971                let map = self.client.token_map(&map_address).await?;
972                let gt_bank = self
973                    .client
974                    .account::<ZeroCopy<GtBank>>(&gt_bank)
975                    .await?
976                    .ok_or(crate::Error::NotFound)?
977                    .0;
978                let treasury_vault_config = self
979                    .client
980                    .account::<ZeroCopy<TreasuryVaultConfig>>(&treasury_vault_config_address)
981                    .await?
982                    .ok_or(crate::Error::NotFound)?
983                    .0;
984                let hint = ConfirmGtBuybackHint {
985                    config,
986                    treasury_vault_config: treasury_vault_config_address,
987                    token_map: map_address,
988                    treasury_tokens: treasury_vault_config.tokens().collect(),
989                    feeds: gt_bank.to_feeds(&map, &treasury_vault_config)?,
990                };
991                self.hint = Some(hint.clone());
992                Ok(hint)
993            }
994        }
995    }
996
997    async fn build_txn(&mut self) -> crate::Result<TransactionBuilder<'a, C>> {
998        let hint = self.prepare_hint().await?;
999
1000        let gt_bank = self
1001            .client
1002            .find_gt_bank_address(&hint.treasury_vault_config, &self.gt_exchange_vault);
1003
1004        let chainlink_program = if self.with_chainlink_program {
1005            Some(Chainlink::id())
1006        } else {
1007            None
1008        };
1009
1010        let token_program_id = anchor_spl::token::ID;
1011
1012        let feeds = self.feeds_parser.parse_and_sort_by_tokens(&hint.feeds)?;
1013        let tokens = hint.treasury_tokens.iter().map(|pubkey| AccountMeta {
1014            pubkey: *pubkey,
1015            is_signer: false,
1016            is_writable: false,
1017        });
1018        let vaults = hint.treasury_tokens.iter().map(|token| {
1019            let pubkey = get_associated_token_address_with_program_id(
1020                &hint.treasury_vault_config,
1021                token,
1022                &token_program_id,
1023            );
1024            AccountMeta {
1025                pubkey,
1026                is_signer: false,
1027                is_writable: false,
1028            }
1029        });
1030
1031        let rpc = self
1032            .client
1033            .treasury_transaction()
1034            .anchor_args(instruction::ConfirmGtBuyback {})
1035            .accounts(fix_optional_account_metas(
1036                accounts::ConfirmGtBuyback {
1037                    authority: self.client.payer(),
1038                    store: self.store,
1039                    config: hint.config,
1040                    treasury_vault_config: hint.treasury_vault_config,
1041                    gt_exchange_vault: self.gt_exchange_vault,
1042                    gt_bank,
1043                    token_map: hint.token_map,
1044                    oracle: self.oracle,
1045                    event_authority: self.client.store_event_authority(),
1046                    store_program: *self.client.store_program_id(),
1047                    chainlink_program,
1048                },
1049                &gmsol_treasury::ID,
1050                self.client.treasury_program_id(),
1051            ))
1052            .accounts(feeds)
1053            .accounts(tokens.chain(vaults).collect::<Vec<_>>());
1054
1055        Ok(rpc)
1056    }
1057}
1058
1059impl<'a, C: Deref<Target = impl Signer> + Clone> MakeBundleBuilder<'a, C>
1060    for ConfirmGtBuybackBuilder<'a, C>
1061{
1062    async fn build_with_options(
1063        &mut self,
1064        options: BundleOptions,
1065    ) -> gmsol_solana_utils::Result<BundleBuilder<'a, C>> {
1066        let mut tx = self.client.bundle_with_options(options);
1067        tx.try_push(
1068            self.build_txn()
1069                .await
1070                .map_err(gmsol_solana_utils::Error::custom)?,
1071        )?;
1072
1073        Ok(tx)
1074    }
1075}
1076
1077impl<C: Deref<Target = impl Signer> + Clone> PullOraclePriceConsumer
1078    for ConfirmGtBuybackBuilder<'_, C>
1079{
1080    async fn feed_ids(&mut self) -> crate::Result<FeedIds> {
1081        let hint = self.prepare_hint().await?;
1082        Ok(FeedIds::new(self.store, hint.feeds))
1083    }
1084
1085    fn process_feeds(
1086        &mut self,
1087        provider: PriceProviderKind,
1088        map: FeedAddressMap,
1089    ) -> crate::Result<()> {
1090        self.feeds_parser
1091            .insert_pull_oracle_feed_parser(provider, map);
1092        Ok(())
1093    }
1094}
1095
1096impl<C> SetExecutionFee for ConfirmGtBuybackBuilder<'_, C> {
1097    fn set_execution_fee(&mut self, _lamports: u64) -> &mut Self {
1098        self
1099    }
1100}