gmsol/exchange/
order.rs

1use std::{
2    collections::{HashMap, HashSet},
3    ops::Deref,
4    sync::Arc,
5};
6
7use anchor_client::{
8    anchor_lang::{system_program, Id},
9    solana_sdk::{
10        address_lookup_table::AddressLookupTableAccount, instruction::AccountMeta, pubkey::Pubkey,
11        signer::Signer,
12    },
13};
14use anchor_spl::associated_token::get_associated_token_address;
15use gmsol_model::action::decrease_position::DecreasePositionSwapType;
16use gmsol_solana_utils::{
17    bundle_builder::{BundleBuilder, BundleOptions},
18    compute_budget::ComputeBudget,
19    transaction_builder::TransactionBuilder,
20};
21use gmsol_store::{
22    accounts, instruction,
23    ops::order::CreateOrderParams,
24    states::{
25        common::{action::Action, swap::SwapActionParams, TokensWithFeed},
26        order::{Order, OrderKind},
27        position::PositionKind,
28        user::UserHeader,
29        Market, MarketMeta, NonceBytes, PriceProviderKind, Pyth, Store, TokenMapAccess,
30    },
31};
32
33use crate::{
34    store::{token::TokenAccountOps, utils::FeedsParser},
35    utils::{
36        builder::{
37            FeedAddressMap, FeedIds, MakeBundleBuilder, PullOraclePriceConsumer, SetExecutionFee,
38        },
39        fix_optional_account_metas, TokenAccountParams, ZeroCopy,
40    },
41};
42
43use super::{generate_nonce, get_ata_or_owner, ExchangeOps};
44
45/// `execute_order` compute budget.
46pub const EXECUTE_ORDER_COMPUTE_BUDGET: u32 = 400_000;
47
48/// Order Params.
49#[derive(Debug, Clone)]
50pub struct OrderParams {
51    /// Order kind.
52    pub kind: OrderKind,
53    /// Decrease Position Swap Type.
54    pub decrease_position_swap_type: Option<DecreasePositionSwapType>,
55    /// Minimum amount or value for output tokens.
56    ///
57    /// - Amount for swap orders.
58    /// - Value for decrease position orders.
59    pub min_output_amount: u128,
60    /// Size delta usd.
61    pub size_delta_usd: u128,
62    /// Initial collateral delta amount.
63    pub initial_collateral_delta_amount: u64,
64    /// Trigger price (unit price).
65    pub trigger_price: Option<u128>,
66    /// Acceptable price (unit price).
67    pub acceptable_price: Option<u128>,
68    /// Whether the order is for a long or short position.
69    pub is_long: bool,
70    /// Valid from timestamp.
71    pub valid_from_ts: Option<i64>,
72}
73
74impl OrderParams {
75    /// Get position kind.
76    pub fn to_position_kind(&self) -> crate::Result<PositionKind> {
77        if self.kind.is_swap() {
78            Err(crate::Error::invalid_argument("position is not required"))
79        } else {
80            Ok(if self.is_long {
81                PositionKind::Long
82            } else {
83                PositionKind::Short
84            })
85        }
86    }
87}
88
89/// Create Order Builder.
90pub struct CreateOrderBuilder<'a, C> {
91    client: &'a crate::Client<C>,
92    store: Pubkey,
93    market_token: Pubkey,
94    is_output_token_long: bool,
95    nonce: Option<NonceBytes>,
96    execution_fee: u64,
97    params: OrderParams,
98    swap_path: Vec<Pubkey>,
99    hint: Option<CreateOrderHint>,
100    initial_token: TokenAccountParams,
101    final_token: Option<Pubkey>,
102    long_token_account: Option<Pubkey>,
103    short_token_account: Option<Pubkey>,
104    should_unwrap_native_token: bool,
105    receiver: Pubkey,
106}
107
108/// Create Order Hint.
109#[derive(Clone, Copy)]
110pub struct CreateOrderHint {
111    long_token: Pubkey,
112    short_token: Pubkey,
113}
114
115impl<'a, C, S> CreateOrderBuilder<'a, C>
116where
117    C: Deref<Target = S> + Clone,
118    S: Signer,
119{
120    pub(super) fn new(
121        client: &'a crate::Client<C>,
122        store: &Pubkey,
123        market_token: &Pubkey,
124        params: OrderParams,
125        is_output_token_long: bool,
126    ) -> Self {
127        Self {
128            client,
129            store: *store,
130            market_token: *market_token,
131            nonce: None,
132            execution_fee: Order::MIN_EXECUTION_LAMPORTS,
133            params,
134            swap_path: vec![],
135            is_output_token_long,
136            hint: None,
137            initial_token: Default::default(),
138            final_token: Default::default(),
139            long_token_account: None,
140            short_token_account: None,
141            should_unwrap_native_token: true,
142            receiver: client.payer(),
143        }
144    }
145
146    /// Set the nonce.
147    pub fn nonce(&mut self, nonce: NonceBytes) -> &mut Self {
148        self.nonce = Some(nonce);
149        self
150    }
151
152    /// Set extra exectuion fee allowed to use.
153    ///
154    /// Defaults to `0` means only allowed to use at most `rent-exempt` amount of fee.
155    pub fn execution_fee(&mut self, amount: u64) -> &mut Self {
156        self.execution_fee = amount;
157        self
158    }
159
160    /// Setup hint with the given market meta.
161    pub fn hint(&mut self, meta: &MarketMeta) -> &mut Self {
162        self.hint = Some(CreateOrderHint {
163            long_token: meta.long_token_mint,
164            short_token: meta.short_token_mint,
165        });
166        self
167    }
168
169    /// Set decrease position swap type.
170    pub fn decrease_position_swap_type(
171        &mut self,
172        ty: Option<DecreasePositionSwapType>,
173    ) -> &mut Self {
174        self.params.decrease_position_swap_type = ty;
175        self
176    }
177
178    /// Set swap path.
179    pub fn swap_path(&mut self, market_tokens: Vec<Pubkey>) -> &mut Self {
180        self.swap_path = market_tokens;
181        self
182    }
183
184    /// Set initial collateral token (or swap-in token) params.
185    pub fn initial_collateral_token(
186        &mut self,
187        token: &Pubkey,
188        token_account: Option<&Pubkey>,
189    ) -> &mut Self {
190        self.initial_token.set_token(*token);
191        if let Some(account) = token_account {
192            self.initial_token.set_token_account(*account);
193        }
194        self
195    }
196
197    /// Set final output token params (position order only).
198    pub fn final_output_token(&mut self, token: &Pubkey) -> &mut Self {
199        self.final_token = Some(*token);
200        self
201    }
202
203    /// Set long token account.
204    pub fn long_token_account(&mut self, account: &Pubkey) -> &mut Self {
205        self.long_token_account = Some(*account);
206        self
207    }
208
209    /// Set short token account.
210    pub fn short_token_account(&mut self, account: &Pubkey) -> &mut Self {
211        self.short_token_account = Some(*account);
212        self
213    }
214
215    /// Set min output amount.
216    pub fn min_output_amount(&mut self, amount: u128) -> &mut Self {
217        self.params.min_output_amount = amount;
218        self
219    }
220
221    /// Set acceptable price.
222    pub fn acceptable_price(&mut self, unit_price: u128) -> &mut Self {
223        self.params.acceptable_price = Some(unit_price);
224        self
225    }
226
227    /// Set valid from ts.
228    pub fn valid_from_ts(&mut self, ts: i64) -> &mut Self {
229        self.params.valid_from_ts = Some(ts);
230        self
231    }
232
233    /// Set whether to unwrap native token.
234    /// Defaults to should unwrap.
235    pub fn should_unwrap_native_token(&mut self, should_unwrap: bool) -> &mut Self {
236        self.should_unwrap_native_token = should_unwrap;
237        self
238    }
239
240    /// Set receiver.
241    /// Defaults to the payer.
242    pub fn receiver(&mut self, receiver: Pubkey) -> &mut Self {
243        self.receiver = receiver;
244        self
245    }
246
247    fn market(&self) -> Pubkey {
248        self.client
249            .find_market_address(&self.store, &self.market_token)
250    }
251
252    async fn prepare_hint(&mut self) -> crate::Result<CreateOrderHint> {
253        loop {
254            if let Some(hint) = self.hint {
255                return Ok(hint);
256            }
257            let market = self.client.market(&self.market()).await?;
258            self.hint(market.meta());
259        }
260    }
261
262    async fn output_token(&mut self) -> crate::Result<Pubkey> {
263        let hint = self.prepare_hint().await?;
264        let output_token = if self.is_output_token_long {
265            hint.long_token
266        } else {
267            hint.short_token
268        };
269        Ok(output_token)
270    }
271
272    async fn position(&mut self) -> crate::Result<Option<Pubkey>> {
273        let output_token = self.output_token().await?;
274        match &self.params.kind {
275            OrderKind::MarketIncrease
276            | OrderKind::MarketDecrease
277            | OrderKind::Liquidation
278            | OrderKind::LimitIncrease
279            | OrderKind::LimitDecrease
280            | OrderKind::StopLossDecrease => {
281                let position = self.client.find_position_address(
282                    &self.store,
283                    &self.client.payer(),
284                    &self.market_token,
285                    &output_token,
286                    self.params.to_position_kind()?,
287                )?;
288                Ok(Some(position))
289            }
290            OrderKind::MarketSwap | OrderKind::LimitSwap => Ok(None),
291            kind => Err(crate::Error::invalid_argument(format!(
292                "unsupported order kind: {kind:?}"
293            ))),
294        }
295    }
296
297    /// Get initial collateral token account and vault.
298    ///
299    /// Returns `(initial_collateral_token, initial_collateral_token_account)`.
300    async fn initial_collateral_accounts(&mut self) -> crate::Result<Option<(Pubkey, Pubkey)>> {
301        match &self.params.kind {
302            OrderKind::MarketIncrease
303            | OrderKind::MarketSwap
304            | OrderKind::LimitIncrease
305            | OrderKind::LimitSwap => {
306                if self.initial_token.is_empty() {
307                    let output_token = self.output_token().await?;
308                    self.initial_token.set_token(output_token);
309                }
310                let Some((token, account)) = self
311                    .initial_token
312                    .get_or_fetch_token_and_token_account(self.client, Some(&self.client.payer()))
313                    .await?
314                else {
315                    return Err(crate::Error::invalid_argument(
316                        "missing initial collateral token parameters",
317                    ));
318                };
319                Ok(Some((token, account)))
320            }
321            OrderKind::MarketDecrease
322            | OrderKind::Liquidation
323            | OrderKind::LimitDecrease
324            | OrderKind::StopLossDecrease => Ok(None),
325            kind => Err(crate::Error::invalid_argument(format!(
326                "unsupported order kind: {kind:?}"
327            ))),
328        }
329    }
330
331    async fn get_final_output_token(&mut self) -> crate::Result<Pubkey> {
332        match &self.params.kind {
333            OrderKind::MarketDecrease | OrderKind::LimitDecrease | OrderKind::StopLossDecrease => {
334                if self.final_token.is_none() {
335                    let output_token = self.output_token().await?;
336                    self.final_token = Some(output_token);
337                }
338                Ok(self.final_token.unwrap())
339            }
340            OrderKind::MarketIncrease
341            | OrderKind::MarketSwap
342            | OrderKind::LimitIncrease
343            | OrderKind::LimitSwap => Ok(self.output_token().await?),
344            kind => Err(crate::Error::invalid_argument(format!(
345                "unsupported order kind: {kind:?}"
346            ))),
347        }
348    }
349
350    /// Create [`TransactionBuilder`] and return order address.
351    pub async fn build_with_address(
352        &mut self,
353    ) -> crate::Result<(TransactionBuilder<'a, C>, Pubkey)> {
354        let (rpc, order, _) = self.build_with_addresses().await?;
355        Ok((rpc, order))
356    }
357
358    /// Create [`TransactionBuilder`] and return order address and optional position address.
359    pub async fn build_with_addresses(
360        &mut self,
361    ) -> crate::Result<(TransactionBuilder<'a, C>, Pubkey, Option<Pubkey>)> {
362        let token_program_id = anchor_spl::token::ID;
363
364        let nonce = self.nonce.unwrap_or_else(generate_nonce);
365        let owner = &self.client.payer();
366        let receiver = self.receiver;
367        let order = self.client.find_order_address(&self.store, owner, &nonce);
368        let (initial_collateral_token, initial_collateral_token_account) =
369            self.initial_collateral_accounts().await?.unzip();
370        let final_output_token = self.get_final_output_token().await?;
371        let hint = self.prepare_hint().await?;
372        let (long_token, short_token) = if self.params.kind.is_swap() {
373            (None, None)
374        } else {
375            (Some(hint.long_token), Some(hint.short_token))
376        };
377
378        let initial_collateral_token_escrow = initial_collateral_token
379            .as_ref()
380            .map(|token| get_associated_token_address(&order, token));
381        let long_token_accounts = long_token.as_ref().map(|token| {
382            let escrow = get_associated_token_address(&order, token);
383            let ata = get_associated_token_address(&receiver, token);
384            (escrow, ata)
385        });
386        let short_token_accounts = short_token.as_ref().map(|token| {
387            let escrow = get_associated_token_address(&order, token);
388            let ata = get_associated_token_address(&receiver, token);
389            (escrow, ata)
390        });
391        let final_output_token_accounts =
392            if self.params.kind.is_swap() || self.params.kind.is_decrease_position() {
393                let escrow = get_associated_token_address(&order, &final_output_token);
394                let ata = get_associated_token_address(&receiver, &final_output_token);
395                Some((escrow, ata))
396            } else {
397                None
398            };
399        let position = self.position().await?;
400        let user = self.client.find_user_address(&self.store, owner);
401
402        let kind = self.params.kind;
403        let params = CreateOrderParams {
404            execution_lamports: self.execution_fee,
405            swap_path_length: self
406                .swap_path
407                .len()
408                .try_into()
409                .map_err(|_| crate::Error::NumberOutOfRange)?,
410            kind,
411            decrease_position_swap_type: self.params.decrease_position_swap_type,
412            initial_collateral_delta_amount: self.params.initial_collateral_delta_amount,
413            size_delta_value: self.params.size_delta_usd,
414            is_long: self.params.is_long,
415            is_collateral_long: self.is_output_token_long,
416            min_output: Some(self.params.min_output_amount),
417            trigger_price: self.params.trigger_price,
418            acceptable_price: self.params.acceptable_price,
419            should_unwrap_native_token: self.should_unwrap_native_token,
420            valid_from_ts: self.params.valid_from_ts,
421        };
422
423        let prepare = match kind {
424            OrderKind::MarketSwap | OrderKind::LimitSwap => {
425                let swap_in_token = initial_collateral_token.ok_or(
426                    crate::Error::invalid_argument("swap in token is not provided"),
427                )?;
428                let escrow = self
429                    .client
430                    .prepare_associated_token_account(
431                        &swap_in_token,
432                        &token_program_id,
433                        Some(&order),
434                    )
435                    .merge(self.client.prepare_associated_token_account(
436                        &final_output_token,
437                        &token_program_id,
438                        Some(&order),
439                    ));
440                let ata = self.client.prepare_associated_token_account(
441                    &final_output_token,
442                    &token_program_id,
443                    Some(&receiver),
444                );
445                escrow.merge(ata)
446            }
447            OrderKind::MarketIncrease | OrderKind::LimitIncrease => {
448                let initial_collateral_token = initial_collateral_token.ok_or(
449                    crate::Error::invalid_argument("initial collateral token is not provided"),
450                )?;
451                let long_token = long_token
452                    .ok_or(crate::Error::invalid_argument("long token is not provided"))?;
453                let short_token = short_token.ok_or(crate::Error::invalid_argument(
454                    "short token is not provided",
455                ))?;
456
457                let escrow = self
458                    .client
459                    .prepare_associated_token_account(
460                        &initial_collateral_token,
461                        &token_program_id,
462                        Some(&order),
463                    )
464                    .merge(self.client.prepare_associated_token_account(
465                        &long_token,
466                        &token_program_id,
467                        Some(&order),
468                    ))
469                    .merge(self.client.prepare_associated_token_account(
470                        &short_token,
471                        &token_program_id,
472                        Some(&order),
473                    ));
474                let long_token_ata = self.client.prepare_associated_token_account(
475                    &long_token,
476                    &token_program_id,
477                    Some(&receiver),
478                );
479                let short_token_ata = self.client.prepare_associated_token_account(
480                    &short_token,
481                    &token_program_id,
482                    Some(&receiver),
483                );
484
485                let prepare_position = self
486                    .client
487                    .store_transaction()
488                    .anchor_accounts(accounts::PreparePosition {
489                        owner: *owner,
490                        store: self.store,
491                        market: self.market(),
492                        position: position.expect("must provided"),
493                        system_program: system_program::ID,
494                    })
495                    .anchor_args(instruction::PreparePosition {
496                        params: params.clone(),
497                    });
498
499                escrow
500                    .merge(long_token_ata)
501                    .merge(short_token_ata)
502                    .merge(prepare_position)
503            }
504            OrderKind::MarketDecrease | OrderKind::LimitDecrease | OrderKind::StopLossDecrease => {
505                let long_token = long_token
506                    .ok_or(crate::Error::invalid_argument("long token is not provided"))?;
507                let short_token = short_token.ok_or(crate::Error::invalid_argument(
508                    "short token is not provided",
509                ))?;
510
511                let escrow = self
512                    .client
513                    .prepare_associated_token_account(
514                        &final_output_token,
515                        &token_program_id,
516                        Some(&order),
517                    )
518                    .merge(self.client.prepare_associated_token_account(
519                        &long_token,
520                        &token_program_id,
521                        Some(&order),
522                    ))
523                    .merge(self.client.prepare_associated_token_account(
524                        &short_token,
525                        &token_program_id,
526                        Some(&order),
527                    ));
528
529                let long_token_ata = self.client.prepare_associated_token_account(
530                    &long_token,
531                    &token_program_id,
532                    Some(&receiver),
533                );
534                let short_token_ata = self.client.prepare_associated_token_account(
535                    &short_token,
536                    &token_program_id,
537                    Some(&receiver),
538                );
539                let final_output_token_ata = self.client.prepare_associated_token_account(
540                    &final_output_token,
541                    &token_program_id,
542                    Some(&receiver),
543                );
544
545                escrow
546                    .merge(long_token_ata)
547                    .merge(short_token_ata)
548                    .merge(final_output_token_ata)
549            }
550            _ => {
551                return Err(crate::Error::invalid_argument("unsupported order kind"));
552            }
553        };
554
555        let prepare_user = self
556            .client
557            .store_transaction()
558            .anchor_accounts(accounts::PrepareUser {
559                owner: *owner,
560                store: self.store,
561                user,
562                system_program: system_program::ID,
563            })
564            .anchor_args(instruction::PrepareUser {});
565
566        let create = self
567            .client
568            .store_transaction()
569            .accounts(crate::utils::fix_optional_account_metas(
570                accounts::CreateOrder {
571                    store: self.store,
572                    order,
573                    position,
574                    market: self.market(),
575                    owner: *owner,
576                    receiver,
577                    user,
578                    initial_collateral_token,
579                    final_output_token,
580                    long_token,
581                    short_token,
582                    initial_collateral_token_escrow,
583                    final_output_token_escrow: final_output_token_accounts
584                        .map(|(escrow, _)| escrow),
585                    long_token_escrow: long_token_accounts.map(|(escrow, _)| escrow),
586                    short_token_escrow: short_token_accounts.map(|(escrow, _)| escrow),
587                    initial_collateral_token_source: initial_collateral_token_account,
588                    system_program: system_program::ID,
589                    token_program: anchor_spl::token::ID,
590                    associated_token_program: anchor_spl::associated_token::ID,
591                },
592                &gmsol_store::id(),
593                self.client.store_program_id(),
594            ))
595            .anchor_args(instruction::CreateOrder { nonce, params })
596            .accounts(
597                self.swap_path
598                    .iter()
599                    .map(|mint| AccountMeta {
600                        pubkey: self.client.find_market_address(&self.store, mint),
601                        is_signer: false,
602                        is_writable: false,
603                    })
604                    .collect::<Vec<_>>(),
605            );
606
607        Ok((prepare.merge(prepare_user).merge(create), order, position))
608    }
609}
610
611/// Execute Order Builder.
612pub struct ExecuteOrderBuilder<'a, C> {
613    client: &'a crate::Client<C>,
614    store: Pubkey,
615    oracle: Pubkey,
616    order: Pubkey,
617    execution_fee: u64,
618    price_provider: Pubkey,
619    feeds_parser: FeedsParser,
620    recent_timestamp: i64,
621    hint: Option<ExecuteOrderHint>,
622    token_map: Option<Pubkey>,
623    cancel_on_execution_error: bool,
624    close: bool,
625    event_buffer_index: u16,
626    alts: HashMap<Pubkey, Vec<Pubkey>>,
627}
628
629/// Hint for executing order.
630#[derive(Clone)]
631pub struct ExecuteOrderHint {
632    kind: OrderKind,
633    store_program_id: Pubkey,
634    store: Arc<Store>,
635    market_token: Pubkey,
636    position: Option<Pubkey>,
637    owner: Pubkey,
638    receiver: Pubkey,
639    rent_receiver: Pubkey,
640    user: Pubkey,
641    referrer: Option<Pubkey>,
642    initial_collateral_token_and_account: Option<(Pubkey, Pubkey)>,
643    final_output_token_and_account: Option<(Pubkey, Pubkey)>,
644    long_token_and_account: Option<(Pubkey, Pubkey)>,
645    short_token_and_account: Option<(Pubkey, Pubkey)>,
646    long_token_mint: Pubkey,
647    short_token_mint: Pubkey,
648    pnl_token: Pubkey,
649    /// Feeds.
650    pub feeds: TokensWithFeed,
651    swap: SwapActionParams,
652    should_unwrap_native_token: bool,
653}
654
655impl ExecuteOrderHint {
656    fn long_token_vault(&self, store: &Pubkey) -> Option<Pubkey> {
657        let token = self.long_token_and_account.as_ref()?.0;
658        Some(crate::pda::find_market_vault_address(store, &token, &self.store_program_id).0)
659    }
660
661    fn short_token_vault(&self, store: &Pubkey) -> Option<Pubkey> {
662        let token = self.short_token_and_account.as_ref()?.0;
663        Some(crate::pda::find_market_vault_address(store, &token, &self.store_program_id).0)
664    }
665
666    fn claimable_long_token_account(
667        &self,
668        store: &Pubkey,
669        timestamp: i64,
670    ) -> crate::Result<Pubkey> {
671        Ok(crate::pda::find_claimable_account_pda(
672            store,
673            &self.long_token_mint,
674            &self.owner,
675            &self.store.claimable_time_key(timestamp)?,
676            &self.store_program_id,
677        )
678        .0)
679    }
680
681    fn claimable_short_token_account(
682        &self,
683        store: &Pubkey,
684        timestamp: i64,
685    ) -> crate::Result<Pubkey> {
686        Ok(crate::pda::find_claimable_account_pda(
687            store,
688            &self.short_token_mint,
689            &self.owner,
690            &self.store.claimable_time_key(timestamp)?,
691            &self.store_program_id,
692        )
693        .0)
694    }
695
696    fn claimable_pnl_token_account_for_holding(
697        &self,
698        store: &Pubkey,
699        timestamp: i64,
700    ) -> crate::Result<Pubkey> {
701        Ok(crate::pda::find_claimable_account_pda(
702            store,
703            &self.pnl_token,
704            self.store.holding(),
705            &self.store.claimable_time_key(timestamp)?,
706            &self.store_program_id,
707        )
708        .0)
709    }
710}
711
712impl<'a, S, C> ExecuteOrderBuilder<'a, C>
713where
714    C: Deref<Target = S> + Clone,
715    S: Signer,
716{
717    pub(super) fn try_new(
718        client: &'a crate::Client<C>,
719        store: &Pubkey,
720        oracle: &Pubkey,
721        order: &Pubkey,
722        cancel_on_execution_error: bool,
723    ) -> crate::Result<Self> {
724        Ok(Self {
725            client,
726            store: *store,
727            oracle: *oracle,
728            order: *order,
729            execution_fee: 0,
730            price_provider: Pyth::id(),
731            feeds_parser: Default::default(),
732            recent_timestamp: recent_timestamp()?,
733            hint: None,
734            token_map: None,
735            cancel_on_execution_error,
736            close: true,
737            event_buffer_index: 0,
738            alts: Default::default(),
739        })
740    }
741
742    /// Set price provider to the given.
743    pub fn price_provider(&mut self, program: Pubkey) -> &mut Self {
744        self.price_provider = program;
745        self
746    }
747
748    /// Set whether to close order after execution.
749    pub fn close(&mut self, close: bool) -> &mut Self {
750        self.close = close;
751        self
752    }
753
754    /// Set event buffer index.
755    pub fn event_buffer_index(&mut self, index: u16) -> &mut Self {
756        self.event_buffer_index = index;
757        self
758    }
759
760    /// Insert an Address Lookup Table.
761    pub fn add_alt(&mut self, account: AddressLookupTableAccount) -> &mut Self {
762        self.alts.insert(account.key, account.addresses);
763        self
764    }
765
766    /// Set hint with the given order.
767    pub fn hint(
768        &mut self,
769        order: &Order,
770        market: &Market,
771        store: &Arc<Store>,
772        map: &impl TokenMapAccess,
773        user: Option<&UserHeader>,
774    ) -> crate::Result<&mut Self> {
775        let params = order.params();
776        let swap = order.swap();
777        let market_token = *order.market_token();
778        let kind = params.kind()?;
779        let tokens = order.tokens();
780        let owner = *order.header().owner();
781        let rent_receiver = *order.header().rent_receiver();
782        let user_address = self.client.find_user_address(&self.store, &owner);
783        let referrer = user.and_then(|user| user.referral().referrer().copied());
784        self.hint = Some(ExecuteOrderHint {
785            kind,
786            store_program_id: *self.client.store_program_id(),
787            store: store.clone(),
788            market_token,
789            position: params.position().copied(),
790            owner,
791            receiver: order.header().receiver(),
792            rent_receiver,
793            user: user_address,
794            referrer,
795            long_token_mint: market.meta().long_token_mint,
796            short_token_mint: market.meta().short_token_mint,
797            pnl_token: if params.side()?.is_long() {
798                market.meta().long_token_mint
799            } else {
800                market.meta().short_token_mint
801            },
802            feeds: swap.to_feeds(map)?,
803            swap: *swap,
804            initial_collateral_token_and_account: tokens.initial_collateral().token_and_account(),
805            final_output_token_and_account: tokens.final_output_token().token_and_account(),
806            long_token_and_account: tokens.long_token().token_and_account(),
807            short_token_and_account: tokens.short_token().token_and_account(),
808            should_unwrap_native_token: order.header().should_unwrap_native_token(),
809        });
810        Ok(self)
811    }
812
813    /// Prepare [`ExecuteOrderHint`].
814    pub async fn prepare_hint(&mut self) -> crate::Result<ExecuteOrderHint> {
815        loop {
816            match &self.hint {
817                Some(hint) => return Ok(hint.clone()),
818                None => {
819                    let order = self.client.order(&self.order).await?;
820                    let market = self.client.market(order.header().market()).await?;
821                    let store = self.client.store(&self.store).await?;
822                    let token_map_address = self.get_token_map().await?;
823                    let token_map = self.client.token_map(&token_map_address).await?;
824                    let owner = order.header().owner();
825                    let user = self.client.find_user_address(&self.store, owner);
826                    let user = self
827                        .client
828                        .account::<ZeroCopy<UserHeader>>(&user)
829                        .await?
830                        .map(|user| user.0);
831                    self.hint(&order, &market, &store, &token_map, user.as_ref())?;
832                }
833            }
834        }
835    }
836
837    /// Set recent timestamp with the given.
838    ///
839    /// Default to current unix timestamp.
840    pub fn recent_timestamp(&mut self, timestamp: i64) -> &mut Self {
841        self.recent_timestamp = timestamp;
842        self
843    }
844
845    /// Get claimable accounts.
846    ///
847    /// The returned values are of the form `[long_for_user, short_for_user, pnl_for_holding]`.
848    pub async fn claimable_accounts(&mut self) -> crate::Result<[Pubkey; 3]> {
849        let hint = self.prepare_hint().await?;
850        let long_for_user =
851            hint.claimable_long_token_account(&self.store, self.recent_timestamp)?;
852        let short_for_user =
853            hint.claimable_short_token_account(&self.store, self.recent_timestamp)?;
854        let pnl_for_holding =
855            hint.claimable_pnl_token_account_for_holding(&self.store, self.recent_timestamp)?;
856        Ok([long_for_user, short_for_user, pnl_for_holding])
857    }
858
859    async fn get_token_map(&mut self) -> crate::Result<Pubkey> {
860        if let Some(address) = self.token_map {
861            Ok(address)
862        } else {
863            let address = self
864                .client
865                .authorized_token_map_address(&self.store)
866                .await?
867                .ok_or(crate::Error::invalid_argument(
868                    "token map is not set for this store",
869                ))?;
870            self.token_map = Some(address);
871            Ok(address)
872        }
873    }
874
875    /// Set token map.
876    pub fn token_map(&mut self, address: Pubkey) -> &mut Self {
877        self.token_map = Some(address);
878        self
879    }
880
881    async fn build_txns(&mut self, options: BundleOptions) -> crate::Result<BundleBuilder<'a, C>> {
882        let hint = self.prepare_hint().await?;
883        let [claimable_long_token_account_for_user, claimable_short_token_account_for_user, claimable_pnl_token_account_for_holding] =
884            self.claimable_accounts().await?;
885
886        let authority = self.client.payer();
887        let feeds = self
888            .feeds_parser
889            .parse(&hint.feeds)
890            .collect::<Result<Vec<_>, _>>()?;
891        let token_map = self.get_token_map().await?;
892        let swap_markets = hint
893            .swap
894            .unique_market_tokens_excluding_current(&hint.market_token)
895            .map(|mint| AccountMeta {
896                pubkey: self.client.find_market_address(&self.store, mint),
897                is_signer: false,
898                is_writable: true,
899            });
900        let event = self.client.find_trade_event_buffer_address(
901            &self.store,
902            &authority,
903            self.event_buffer_index,
904        );
905
906        let kind = hint.kind;
907        let mut require_claimable_accounts = false;
908
909        let mut execute_order = match kind {
910            OrderKind::MarketDecrease | OrderKind::LimitDecrease | OrderKind::StopLossDecrease => {
911                require_claimable_accounts = true;
912
913                self.client
914                    .store_transaction()
915                    .accounts(fix_optional_account_metas(
916                        accounts::ExecuteDecreaseOrder {
917                            authority,
918                            owner: hint.owner,
919                            user: hint.user,
920                            store: self.store,
921                            oracle: self.oracle,
922                            token_map,
923                            market: self
924                                .client
925                                .find_market_address(&self.store, &hint.market_token),
926                            order: self.order,
927                            position: hint
928                                .position
929                                .ok_or(crate::Error::invalid_argument("missing position"))?,
930                            event,
931                            final_output_token_vault: hint
932                                .final_output_token_and_account
933                                .as_ref()
934                                .map(|(token, _)| {
935                                    self.client.find_market_vault_address(&self.store, token)
936                                })
937                                .ok_or(crate::Error::invalid_argument(
938                                    "missing final output token",
939                                ))?,
940                            long_token_vault: hint
941                                .long_token_vault(&self.store)
942                                .ok_or(crate::Error::invalid_argument("missing long token"))?,
943                            short_token_vault: hint
944                                .short_token_vault(&self.store)
945                                .ok_or(crate::Error::invalid_argument("missing short token"))?,
946                            claimable_long_token_account_for_user,
947                            claimable_short_token_account_for_user,
948                            claimable_pnl_token_account_for_holding,
949                            event_authority: self.client.store_event_authority(),
950                            token_program: anchor_spl::token::ID,
951                            system_program: system_program::ID,
952                            long_token: hint
953                                .long_token_and_account
954                                .map(|(token, _)| token)
955                                .ok_or(crate::Error::invalid_argument("missing long token"))?,
956                            short_token: hint
957                                .short_token_and_account
958                                .map(|(token, _)| token)
959                                .ok_or(crate::Error::invalid_argument("missing short token"))?,
960                            final_output_token: hint
961                                .final_output_token_and_account
962                                .map(|(token, _)| token)
963                                .ok_or(crate::Error::invalid_argument(
964                                    "missing final output token",
965                                ))?,
966                            final_output_token_escrow: hint
967                                .final_output_token_and_account
968                                .map(|(_, account)| account)
969                                .ok_or(crate::Error::invalid_argument(
970                                    "missing final output token",
971                                ))?,
972                            long_token_escrow: hint
973                                .long_token_and_account
974                                .map(|(_, account)| account)
975                                .ok_or(crate::Error::invalid_argument("missing long token"))?,
976                            short_token_escrow: hint
977                                .short_token_and_account
978                                .map(|(_, account)| account)
979                                .ok_or(crate::Error::invalid_argument("missing short token"))?,
980                            program: *self.client.store_program_id(),
981                            chainlink_program: None,
982                        },
983                        &crate::program_ids::DEFAULT_GMSOL_STORE_ID,
984                        self.client.store_program_id(),
985                    ))
986                    .anchor_args(instruction::ExecuteDecreaseOrder {
987                        recent_timestamp: self.recent_timestamp,
988                        execution_fee: self.execution_fee,
989                        throw_on_execution_error: !self.cancel_on_execution_error,
990                    })
991            }
992            _ => self
993                .client
994                .store_transaction()
995                .accounts(crate::utils::fix_optional_account_metas(
996                    accounts::ExecuteIncreaseOrSwapOrder {
997                        authority,
998                        owner: hint.owner,
999                        user: hint.user,
1000                        store: self.store,
1001                        oracle: self.oracle,
1002                        token_map,
1003                        market: self
1004                            .client
1005                            .find_market_address(&self.store, &hint.market_token),
1006                        order: self.order,
1007                        position: hint.position,
1008                        event: (!kind.is_swap()).then_some(event),
1009                        final_output_token_vault: hint.final_output_token_and_account.as_ref().map(
1010                            |(token, _)| self.client.find_market_vault_address(&self.store, token),
1011                        ),
1012                        long_token_vault: hint.long_token_vault(&self.store),
1013                        short_token_vault: hint.short_token_vault(&self.store),
1014                        event_authority: self.client.store_event_authority(),
1015                        token_program: anchor_spl::token::ID,
1016                        system_program: system_program::ID,
1017                        initial_collateral_token: hint
1018                            .initial_collateral_token_and_account
1019                            .map(|(token, _)| token),
1020                        initial_collateral_token_vault: hint
1021                            .initial_collateral_token_and_account
1022                            .map(|(token, _)| {
1023                                self.client.find_market_vault_address(&self.store, &token)
1024                            }),
1025                        initial_collateral_token_escrow: hint
1026                            .initial_collateral_token_and_account
1027                            .map(|(_, account)| account),
1028                        long_token: hint.long_token_and_account.map(|(token, _)| token),
1029                        short_token: hint.short_token_and_account.map(|(token, _)| token),
1030                        final_output_token: hint
1031                            .final_output_token_and_account
1032                            .map(|(token, _)| token),
1033                        final_output_token_escrow: hint
1034                            .final_output_token_and_account
1035                            .map(|(_, account)| account),
1036                        long_token_escrow: hint.long_token_and_account.map(|(_, account)| account),
1037                        short_token_escrow: hint
1038                            .short_token_and_account
1039                            .map(|(_, account)| account),
1040                        program: *self.client.store_program_id(),
1041                        chainlink_program: None,
1042                    },
1043                    &crate::program_ids::DEFAULT_GMSOL_STORE_ID,
1044                    self.client.store_program_id(),
1045                ))
1046                .anchor_args(instruction::ExecuteIncreaseOrSwapOrder {
1047                    recent_timestamp: self.recent_timestamp,
1048                    execution_fee: self.execution_fee,
1049                    throw_on_execution_error: !self.cancel_on_execution_error,
1050                }),
1051        };
1052
1053        execute_order = execute_order
1054            .accounts(feeds.into_iter().chain(swap_markets).collect::<Vec<_>>())
1055            .compute_budget(ComputeBudget::default().with_limit(EXECUTE_ORDER_COMPUTE_BUDGET))
1056            .lookup_tables(self.alts.clone());
1057
1058        if !kind.is_swap() {
1059            let prepare_event_buffer = self
1060                .client
1061                .store_transaction()
1062                .anchor_accounts(accounts::PrepareTradeEventBuffer {
1063                    authority,
1064                    store: self.store,
1065                    event,
1066                    system_program: system_program::ID,
1067                })
1068                .anchor_args(instruction::PrepareTradeEventBuffer {
1069                    index: self.event_buffer_index,
1070                });
1071            execute_order = prepare_event_buffer.merge(execute_order);
1072        }
1073
1074        if self.close {
1075            let close = self
1076                .client
1077                .close_order(&self.order)?
1078                .reason("executed")
1079                .hint(CloseOrderHint {
1080                    owner: hint.owner,
1081                    receiver: hint.receiver,
1082                    store: self.store,
1083                    initial_collateral_token_and_account: hint.initial_collateral_token_and_account,
1084                    final_output_token_and_account: hint.final_output_token_and_account,
1085                    long_token_and_account: hint.long_token_and_account,
1086                    short_token_and_account: hint.short_token_and_account,
1087                    user: hint.user,
1088                    referrer: hint.referrer,
1089                    rent_receiver: hint.rent_receiver,
1090                    should_unwrap_native_token: hint.should_unwrap_native_token,
1091                })
1092                .build()
1093                .await?;
1094            execute_order = execute_order.merge(close);
1095        }
1096
1097        let mut builder = ClaimableAccountsBuilder::new(
1098            self.recent_timestamp,
1099            self.store,
1100            hint.owner,
1101            *hint.store.holding(),
1102        );
1103
1104        if require_claimable_accounts {
1105            builder.claimable_long_token_account_for_user(
1106                &hint.long_token_mint,
1107                &claimable_long_token_account_for_user,
1108            );
1109            builder.claimable_short_token_account_for_user(
1110                &hint.short_token_mint,
1111                &claimable_short_token_account_for_user,
1112            );
1113            builder.claimable_pnl_token_account_for_holding(
1114                &hint.pnl_token,
1115                &claimable_pnl_token_account_for_holding,
1116            );
1117        }
1118
1119        let (pre_builder, post_builder) = builder.build(self.client);
1120
1121        let mut bundle = self.client.bundle_with_options(options);
1122        bundle
1123            .try_push(pre_builder)?
1124            .try_push(execute_order)?
1125            .try_push(post_builder)?;
1126        Ok(bundle)
1127    }
1128}
1129
1130impl<'a, C: Deref<Target = impl Signer> + Clone> MakeBundleBuilder<'a, C>
1131    for ExecuteOrderBuilder<'a, C>
1132{
1133    async fn build_with_options(
1134        &mut self,
1135        options: BundleOptions,
1136    ) -> gmsol_solana_utils::Result<BundleBuilder<'a, C>> {
1137        self.build_txns(options)
1138            .await
1139            .map_err(gmsol_solana_utils::Error::custom)
1140    }
1141}
1142
1143impl<C: Deref<Target = impl Signer> + Clone> PullOraclePriceConsumer
1144    for ExecuteOrderBuilder<'_, C>
1145{
1146    async fn feed_ids(&mut self) -> crate::Result<FeedIds> {
1147        let hint = self.prepare_hint().await?;
1148        Ok(FeedIds::new(self.store, hint.feeds))
1149    }
1150
1151    fn process_feeds(
1152        &mut self,
1153        provider: PriceProviderKind,
1154        map: FeedAddressMap,
1155    ) -> crate::Result<()> {
1156        self.feeds_parser
1157            .insert_pull_oracle_feed_parser(provider, map);
1158        Ok(())
1159    }
1160}
1161
1162impl<C> SetExecutionFee for ExecuteOrderBuilder<'_, C> {
1163    fn set_execution_fee(&mut self, lamports: u64) -> &mut Self {
1164        self.execution_fee = lamports;
1165        self
1166    }
1167}
1168
1169/// Close Order Builder.
1170pub struct CloseOrderBuilder<'a, C> {
1171    client: &'a crate::Client<C>,
1172    order: Pubkey,
1173    hint: Option<CloseOrderHint>,
1174    reason: String,
1175}
1176
1177/// Close Order Hint.
1178#[derive(Clone, Copy)]
1179pub struct CloseOrderHint {
1180    pub(super) owner: Pubkey,
1181    pub(super) receiver: Pubkey,
1182    pub(super) store: Pubkey,
1183    pub(super) initial_collateral_token_and_account: Option<(Pubkey, Pubkey)>,
1184    pub(super) final_output_token_and_account: Option<(Pubkey, Pubkey)>,
1185    pub(super) long_token_and_account: Option<(Pubkey, Pubkey)>,
1186    pub(super) short_token_and_account: Option<(Pubkey, Pubkey)>,
1187    pub(super) user: Pubkey,
1188    pub(super) referrer: Option<Pubkey>,
1189    pub(super) rent_receiver: Pubkey,
1190    pub(super) should_unwrap_native_token: bool,
1191}
1192
1193impl CloseOrderHint {
1194    /// Create hint from order and user account.
1195    pub fn new(
1196        order: &Order,
1197        user: Option<&UserHeader>,
1198        program_id: &Pubkey,
1199    ) -> crate::Result<Self> {
1200        let tokens = order.tokens();
1201        let owner = order.header().owner();
1202        let store = order.header().store();
1203        let user_address = crate::pda::find_user_pda(store, owner, program_id).0;
1204        let referrer = user.and_then(|user| user.referral().referrer().copied());
1205        let rent_receiver = *order.header().rent_receiver();
1206        Ok(Self {
1207            owner: *owner,
1208            receiver: order.header().receiver(),
1209            store: *store,
1210            user: user_address,
1211            referrer,
1212            initial_collateral_token_and_account: tokens.initial_collateral().token_and_account(),
1213            final_output_token_and_account: tokens.final_output_token().token_and_account(),
1214            long_token_and_account: tokens.long_token().token_and_account(),
1215            short_token_and_account: tokens.short_token().token_and_account(),
1216            rent_receiver,
1217            should_unwrap_native_token: order.header().should_unwrap_native_token(),
1218        })
1219    }
1220}
1221
1222impl<'a, S, C> CloseOrderBuilder<'a, C>
1223where
1224    C: Deref<Target = S> + Clone,
1225    S: Signer,
1226{
1227    pub(super) fn new(client: &'a crate::Client<C>, order: &Pubkey) -> Self {
1228        Self {
1229            client,
1230            order: *order,
1231            hint: None,
1232            reason: "cancelled".into(),
1233        }
1234    }
1235
1236    /// Set hint with the given order.
1237    pub fn hint_with_order(
1238        &mut self,
1239        order: &Order,
1240        user: Option<&UserHeader>,
1241        program_id: &Pubkey,
1242    ) -> crate::Result<&mut Self> {
1243        Ok(self.hint(CloseOrderHint::new(order, user, program_id)?))
1244    }
1245
1246    /// Set hint.
1247    pub fn hint(&mut self, hint: CloseOrderHint) -> &mut Self {
1248        self.hint = Some(hint);
1249        self
1250    }
1251
1252    /// Set reason.
1253    pub fn reason(&mut self, reason: impl ToString) -> &mut Self {
1254        self.reason = reason.to_string();
1255        self
1256    }
1257
1258    async fn prepare_hint(&mut self) -> crate::Result<CloseOrderHint> {
1259        match &self.hint {
1260            Some(hint) => Ok(*hint),
1261            None => {
1262                let order: ZeroCopy<Order> = self
1263                    .client
1264                    .account_with_config(&self.order, Default::default())
1265                    .await?
1266                    .into_value()
1267                    .ok_or(crate::Error::invalid_argument("order not found"))?;
1268                let user = self
1269                    .client
1270                    .find_user_address(order.0.header().store(), order.0.header().owner());
1271                let user = self.client.account::<ZeroCopy<_>>(&user).await?;
1272                let hint = CloseOrderHint::new(
1273                    &order.0,
1274                    user.as_ref().map(|user| &user.0),
1275                    self.client.store_program_id(),
1276                )?;
1277                self.hint = Some(hint);
1278                Ok(hint)
1279            }
1280        }
1281    }
1282
1283    /// Build [`TransactionBuilder`] for cancelling the order.
1284    pub async fn build(&mut self) -> crate::Result<TransactionBuilder<'a, C>> {
1285        let hint = self.prepare_hint().await?;
1286        let payer = self.client.payer();
1287        let owner = hint.owner;
1288        let referrer_user = hint
1289            .referrer
1290            .map(|owner| self.client.find_user_address(&hint.store, &owner));
1291        Ok(self
1292            .client
1293            .store_transaction()
1294            .accounts(crate::utils::fix_optional_account_metas(
1295                accounts::CloseOrder {
1296                    store: hint.store,
1297                    store_wallet: self.client.find_store_wallet_address(&hint.store),
1298                    event_authority: self.client.store_event_authority(),
1299                    order: self.order,
1300                    executor: payer,
1301                    owner,
1302                    receiver: hint.receiver,
1303                    rent_receiver: hint.rent_receiver,
1304                    user: hint.user,
1305                    referrer_user,
1306                    initial_collateral_token: hint
1307                        .initial_collateral_token_and_account
1308                        .map(|(token, _)| token),
1309                    initial_collateral_token_escrow: hint
1310                        .initial_collateral_token_and_account
1311                        .map(|(_, account)| account),
1312                    long_token: hint.long_token_and_account.map(|(token, _)| token),
1313                    short_token: hint.short_token_and_account.map(|(token, _)| token),
1314                    final_output_token: hint.final_output_token_and_account.map(|(token, _)| token),
1315                    final_output_token_escrow: hint
1316                        .final_output_token_and_account
1317                        .map(|(_, account)| account),
1318                    long_token_escrow: hint.long_token_and_account.map(|(_, account)| account),
1319                    short_token_escrow: hint.short_token_and_account.map(|(_, account)| account),
1320                    initial_collateral_token_ata: hint
1321                        .initial_collateral_token_and_account
1322                        .as_ref()
1323                        .map(|(token, _)| {
1324                            get_ata_or_owner(&owner, token, hint.should_unwrap_native_token)
1325                        }),
1326                    final_output_token_ata: hint.final_output_token_and_account.as_ref().map(
1327                        |(token, _)| {
1328                            get_ata_or_owner(&hint.receiver, token, hint.should_unwrap_native_token)
1329                        },
1330                    ),
1331                    long_token_ata: hint.long_token_and_account.as_ref().map(|(token, _)| {
1332                        get_ata_or_owner(&hint.receiver, token, hint.should_unwrap_native_token)
1333                    }),
1334                    short_token_ata: hint.short_token_and_account.as_ref().map(|(token, _)| {
1335                        get_ata_or_owner(&hint.receiver, token, hint.should_unwrap_native_token)
1336                    }),
1337                    associated_token_program: anchor_spl::associated_token::ID,
1338                    token_program: anchor_spl::token::ID,
1339                    system_program: system_program::ID,
1340                    program: *self.client.store_program_id(),
1341                },
1342                &gmsol_store::ID,
1343                self.client.store_program_id(),
1344            ))
1345            .anchor_args(instruction::CloseOrder {
1346                reason: self.reason.clone(),
1347            }))
1348    }
1349}
1350
1351pub(super) fn recent_timestamp() -> crate::Result<i64> {
1352    use std::time::SystemTime;
1353
1354    SystemTime::now()
1355        .duration_since(SystemTime::UNIX_EPOCH)
1356        .map_err(crate::Error::unknown)?
1357        .as_secs()
1358        .try_into()
1359        .map_err(|_| crate::Error::unknown("failed to convert timestamp"))
1360}
1361
1362pub(super) struct ClaimableAccountsBuilder {
1363    recent_timestamp: i64,
1364    store: Pubkey,
1365    user: Pubkey,
1366    holding: Pubkey,
1367    claimable_long_token_account_for_user: Option<(Pubkey, Pubkey)>,
1368    claimable_short_token_account_for_user: Option<(Pubkey, Pubkey)>,
1369    claimable_pnl_token_account_for_holding: Option<(Pubkey, Pubkey)>,
1370}
1371
1372impl ClaimableAccountsBuilder {
1373    pub(super) fn new(recent_timestamp: i64, store: Pubkey, user: Pubkey, holding: Pubkey) -> Self {
1374        Self {
1375            recent_timestamp,
1376            store,
1377            user,
1378            holding,
1379            claimable_long_token_account_for_user: None,
1380            claimable_short_token_account_for_user: None,
1381            claimable_pnl_token_account_for_holding: None,
1382        }
1383    }
1384
1385    pub(super) fn claimable_long_token_account_for_user(
1386        &mut self,
1387        long_token_mint: &Pubkey,
1388        account: &Pubkey,
1389    ) -> &mut Self {
1390        self.claimable_long_token_account_for_user = Some((*long_token_mint, *account));
1391        self
1392    }
1393
1394    pub(super) fn claimable_short_token_account_for_user(
1395        &mut self,
1396        short_token_mint: &Pubkey,
1397        account: &Pubkey,
1398    ) -> &mut Self {
1399        self.claimable_short_token_account_for_user = Some((*short_token_mint, *account));
1400        self
1401    }
1402
1403    pub(super) fn claimable_pnl_token_account_for_holding(
1404        &mut self,
1405        pnl_token_mint: &Pubkey,
1406        account: &Pubkey,
1407    ) -> &mut Self {
1408        self.claimable_pnl_token_account_for_holding = Some((*pnl_token_mint, *account));
1409        self
1410    }
1411
1412    pub(super) fn build<'a, C: Deref<Target = impl Signer> + Clone>(
1413        &self,
1414        client: &'a crate::Client<C>,
1415    ) -> (TransactionBuilder<'a, C>, TransactionBuilder<'a, C>) {
1416        use crate::store::token::TokenAccountOps;
1417
1418        let mut pre_builder = client.store_transaction();
1419        let mut post_builder = client.store_transaction();
1420        let mut accounts: HashSet<&Pubkey> = Default::default();
1421        if let Some((long_token_mint, account)) =
1422            self.claimable_long_token_account_for_user.as_ref()
1423        {
1424            pre_builder = pre_builder.merge(client.use_claimable_account(
1425                &self.store,
1426                long_token_mint,
1427                &self.user,
1428                self.recent_timestamp,
1429                account,
1430                0,
1431            ));
1432            post_builder = post_builder.merge(client.close_empty_claimable_account(
1433                &self.store,
1434                long_token_mint,
1435                &self.user,
1436                self.recent_timestamp,
1437                account,
1438            ));
1439            accounts.insert(account);
1440        }
1441        if let Some((short_token_mint, account)) =
1442            self.claimable_short_token_account_for_user.as_ref()
1443        {
1444            if !accounts.contains(account) {
1445                pre_builder = pre_builder.merge(client.use_claimable_account(
1446                    &self.store,
1447                    short_token_mint,
1448                    &self.user,
1449                    self.recent_timestamp,
1450                    account,
1451                    0,
1452                ));
1453                post_builder = post_builder.merge(client.close_empty_claimable_account(
1454                    &self.store,
1455                    short_token_mint,
1456                    &self.user,
1457                    self.recent_timestamp,
1458                    account,
1459                ));
1460                accounts.insert(account);
1461            }
1462        }
1463        if let Some((pnl_token_mint, account)) =
1464            self.claimable_pnl_token_account_for_holding.as_ref()
1465        {
1466            if !accounts.contains(account) {
1467                let holding = &self.holding;
1468                pre_builder = pre_builder.merge(client.use_claimable_account(
1469                    &self.store,
1470                    pnl_token_mint,
1471                    holding,
1472                    self.recent_timestamp,
1473                    account,
1474                    0,
1475                ));
1476                post_builder = post_builder.merge(client.close_empty_claimable_account(
1477                    &self.store,
1478                    pnl_token_mint,
1479                    holding,
1480                    self.recent_timestamp,
1481                    account,
1482                ));
1483            }
1484        }
1485        (pre_builder, post_builder)
1486    }
1487}