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
45pub const EXECUTE_ORDER_COMPUTE_BUDGET: u32 = 400_000;
47
48#[derive(Debug, Clone)]
50pub struct OrderParams {
51 pub kind: OrderKind,
53 pub decrease_position_swap_type: Option<DecreasePositionSwapType>,
55 pub min_output_amount: u128,
60 pub size_delta_usd: u128,
62 pub initial_collateral_delta_amount: u64,
64 pub trigger_price: Option<u128>,
66 pub acceptable_price: Option<u128>,
68 pub is_long: bool,
70 pub valid_from_ts: Option<i64>,
72}
73
74impl OrderParams {
75 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
89pub 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#[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 pub fn nonce(&mut self, nonce: NonceBytes) -> &mut Self {
148 self.nonce = Some(nonce);
149 self
150 }
151
152 pub fn execution_fee(&mut self, amount: u64) -> &mut Self {
156 self.execution_fee = amount;
157 self
158 }
159
160 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 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 pub fn swap_path(&mut self, market_tokens: Vec<Pubkey>) -> &mut Self {
180 self.swap_path = market_tokens;
181 self
182 }
183
184 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 pub fn final_output_token(&mut self, token: &Pubkey) -> &mut Self {
199 self.final_token = Some(*token);
200 self
201 }
202
203 pub fn long_token_account(&mut self, account: &Pubkey) -> &mut Self {
205 self.long_token_account = Some(*account);
206 self
207 }
208
209 pub fn short_token_account(&mut self, account: &Pubkey) -> &mut Self {
211 self.short_token_account = Some(*account);
212 self
213 }
214
215 pub fn min_output_amount(&mut self, amount: u128) -> &mut Self {
217 self.params.min_output_amount = amount;
218 self
219 }
220
221 pub fn acceptable_price(&mut self, unit_price: u128) -> &mut Self {
223 self.params.acceptable_price = Some(unit_price);
224 self
225 }
226
227 pub fn valid_from_ts(&mut self, ts: i64) -> &mut Self {
229 self.params.valid_from_ts = Some(ts);
230 self
231 }
232
233 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 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 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 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 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
611pub 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#[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 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 pub fn price_provider(&mut self, program: Pubkey) -> &mut Self {
744 self.price_provider = program;
745 self
746 }
747
748 pub fn close(&mut self, close: bool) -> &mut Self {
750 self.close = close;
751 self
752 }
753
754 pub fn event_buffer_index(&mut self, index: u16) -> &mut Self {
756 self.event_buffer_index = index;
757 self
758 }
759
760 pub fn add_alt(&mut self, account: AddressLookupTableAccount) -> &mut Self {
762 self.alts.insert(account.key, account.addresses);
763 self
764 }
765
766 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 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 pub fn recent_timestamp(&mut self, timestamp: i64) -> &mut Self {
841 self.recent_timestamp = timestamp;
842 self
843 }
844
845 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 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
1169pub struct CloseOrderBuilder<'a, C> {
1171 client: &'a crate::Client<C>,
1172 order: Pubkey,
1173 hint: Option<CloseOrderHint>,
1174 reason: String,
1175}
1176
1177#[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 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 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 pub fn hint(&mut self, hint: CloseOrderHint) -> &mut Self {
1248 self.hint = Some(hint);
1249 self
1250 }
1251
1252 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 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}