1pub mod deposit;
3
4pub mod withdrawal;
6
7pub mod order;
9
10pub mod shift;
12
13pub mod auto_deleveraging;
15
16pub mod position_cut;
18
19pub mod treasury;
21
22use std::{future::Future, ops::Deref};
23
24use anchor_client::{
25 anchor_lang::system_program,
26 solana_sdk::{pubkey::Pubkey, signer::Signer},
27};
28use auto_deleveraging::UpdateAdlBuilder;
29use gmsol_solana_utils::transaction_builder::TransactionBuilder;
30use gmsol_store::{
31 accounts, instruction,
32 ops::order::PositionCutKind,
33 states::{
34 feature::{ActionDisabledFlag, DomainDisabledFlag},
35 order::OrderKind,
36 NonceBytes, UpdateOrderParams,
37 },
38};
39use order::{CloseOrderBuilder, OrderParams};
40use position_cut::PositionCutBuilder;
41use rand::{distributions::Standard, Rng};
42use shift::{CloseShiftBuilder, CreateShiftBuilder, ExecuteShiftBuilder};
43use treasury::ClaimFeesBuilder;
44
45use crate::store::market::VaultOps;
46
47use self::{
48 deposit::{CloseDepositBuilder, CreateDepositBuilder, ExecuteDepositBuilder},
49 order::{CreateOrderBuilder, ExecuteOrderBuilder},
50 withdrawal::{CloseWithdrawalBuilder, CreateWithdrawalBuilder, ExecuteWithdrawalBuilder},
51};
52
53pub trait ExchangeOps<C> {
55 fn toggle_feature(
57 &self,
58 store: &Pubkey,
59 domian: DomainDisabledFlag,
60 action: ActionDisabledFlag,
61 enable: bool,
62 ) -> TransactionBuilder<C>;
63
64 fn claim_fees(
66 &self,
67 store: &Pubkey,
68 market_token: &Pubkey,
69 is_long_token: bool,
70 ) -> ClaimFeesBuilder<C>;
71
72 #[allow(clippy::too_many_arguments)]
74 fn create_market(
75 &self,
76 store: &Pubkey,
77 name: &str,
78 index_token: &Pubkey,
79 long_token: &Pubkey,
80 short_token: &Pubkey,
81 enable: bool,
82 token_map: Option<&Pubkey>,
83 ) -> impl Future<Output = crate::Result<(TransactionBuilder<C>, Pubkey)>>;
84
85 fn fund_market(
87 &self,
88 store: &Pubkey,
89 market_token: &Pubkey,
90 source_account: &Pubkey,
91 amount: u64,
92 token: Option<&Pubkey>,
93 ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
94
95 fn create_deposit(&self, store: &Pubkey, market_token: &Pubkey) -> CreateDepositBuilder<C>;
97
98 fn close_deposit(&self, store: &Pubkey, deposit: &Pubkey) -> CloseDepositBuilder<C>;
100
101 fn execute_deposit(
103 &self,
104 store: &Pubkey,
105 oracle: &Pubkey,
106 deposit: &Pubkey,
107 cancel_on_execution_error: bool,
108 ) -> ExecuteDepositBuilder<C>;
109
110 fn create_withdrawal(
112 &self,
113 store: &Pubkey,
114 market_token: &Pubkey,
115 amount: u64,
116 ) -> CreateWithdrawalBuilder<C>;
117
118 fn close_withdrawal(&self, store: &Pubkey, withdrawal: &Pubkey) -> CloseWithdrawalBuilder<C>;
120
121 fn execute_withdrawal(
123 &self,
124 store: &Pubkey,
125 oracle: &Pubkey,
126 withdrawal: &Pubkey,
127 cancel_on_execution_error: bool,
128 ) -> ExecuteWithdrawalBuilder<C>;
129
130 fn create_order(
132 &self,
133 store: &Pubkey,
134 market_token: &Pubkey,
135 is_output_token_long: bool,
136 params: OrderParams,
137 ) -> CreateOrderBuilder<C>;
138
139 fn update_order(
141 &self,
142 store: &Pubkey,
143 market_token: &Pubkey,
144 order: &Pubkey,
145 params: UpdateOrderParams,
146 ) -> crate::Result<TransactionBuilder<C>>;
147
148 fn execute_order(
150 &self,
151 store: &Pubkey,
152 oracle: &Pubkey,
153 order: &Pubkey,
154 cancel_on_execution_error: bool,
155 ) -> crate::Result<ExecuteOrderBuilder<C>>;
156
157 fn close_order(&self, order: &Pubkey) -> crate::Result<CloseOrderBuilder<C>>;
159
160 fn cancel_order_if_no_position(
162 &self,
163 store: &Pubkey,
164 order: &Pubkey,
165 position_hint: Option<&Pubkey>,
166 ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
167
168 fn liquidate(&self, oracle: &Pubkey, position: &Pubkey)
170 -> crate::Result<PositionCutBuilder<C>>;
171
172 fn auto_deleverage(
174 &self,
175 oracle: &Pubkey,
176 position: &Pubkey,
177 size_delta_usd: u128,
178 ) -> crate::Result<PositionCutBuilder<C>>;
179
180 fn update_adl(
182 &self,
183 store: &Pubkey,
184 oracle: &Pubkey,
185 market_token: &Pubkey,
186 for_long: bool,
187 for_short: bool,
188 ) -> crate::Result<UpdateAdlBuilder<C>>;
189
190 fn market_increase(
192 &self,
193 store: &Pubkey,
194 market_token: &Pubkey,
195 is_collateral_token_long: bool,
196 initial_collateral_amount: u64,
197 is_long: bool,
198 increment_size_in_usd: u128,
199 ) -> CreateOrderBuilder<C> {
200 let params = OrderParams {
201 kind: OrderKind::MarketIncrease,
202 decrease_position_swap_type: None,
203 min_output_amount: 0,
204 size_delta_usd: increment_size_in_usd,
205 initial_collateral_delta_amount: initial_collateral_amount,
206 acceptable_price: None,
207 trigger_price: None,
208 is_long,
209 valid_from_ts: None,
210 };
211 self.create_order(store, market_token, is_collateral_token_long, params)
212 }
213
214 fn market_decrease(
216 &self,
217 store: &Pubkey,
218 market_token: &Pubkey,
219 is_collateral_token_long: bool,
220 collateral_withdrawal_amount: u64,
221 is_long: bool,
222 decrement_size_in_usd: u128,
223 ) -> CreateOrderBuilder<C> {
224 let params = OrderParams {
225 kind: OrderKind::MarketDecrease,
226 decrease_position_swap_type: None,
227 min_output_amount: 0,
228 size_delta_usd: decrement_size_in_usd,
229 initial_collateral_delta_amount: collateral_withdrawal_amount,
230 acceptable_price: None,
231 trigger_price: None,
232 is_long,
233 valid_from_ts: None,
234 };
235 self.create_order(store, market_token, is_collateral_token_long, params)
236 }
237
238 fn market_swap<'a, S>(
240 &self,
241 store: &Pubkey,
242 market_token: &Pubkey,
243 is_output_token_long: bool,
244 initial_swap_in_token: &Pubkey,
245 initial_swap_in_token_amount: u64,
246 swap_path: impl IntoIterator<Item = &'a Pubkey>,
247 ) -> CreateOrderBuilder<C>
248 where
249 C: Deref<Target = S> + Clone,
250 S: Signer,
251 {
252 let params = OrderParams {
253 kind: OrderKind::MarketSwap,
254 decrease_position_swap_type: None,
255 min_output_amount: 0,
256 size_delta_usd: 0,
257 initial_collateral_delta_amount: initial_swap_in_token_amount,
258 acceptable_price: None,
259 trigger_price: None,
260 is_long: true,
261 valid_from_ts: None,
262 };
263 let mut builder = self.create_order(store, market_token, is_output_token_long, params);
264 builder
265 .initial_collateral_token(initial_swap_in_token, None)
266 .swap_path(swap_path.into_iter().copied().collect());
267 builder
268 }
269
270 #[allow(clippy::too_many_arguments)]
272 fn limit_increase(
273 &self,
274 store: &Pubkey,
275 market_token: &Pubkey,
276 is_long: bool,
277 increment_size_in_usd: u128,
278 price: u128,
279 is_collateral_token_long: bool,
280 initial_collateral_amount: u64,
281 ) -> CreateOrderBuilder<C> {
282 let params = OrderParams {
283 kind: OrderKind::LimitIncrease,
284 decrease_position_swap_type: None,
285 min_output_amount: 0,
286 size_delta_usd: increment_size_in_usd,
287 initial_collateral_delta_amount: initial_collateral_amount,
288 acceptable_price: None,
289 trigger_price: Some(price),
290 is_long,
291 valid_from_ts: None,
292 };
293 self.create_order(store, market_token, is_collateral_token_long, params)
294 }
295
296 #[allow(clippy::too_many_arguments)]
298 fn limit_decrease(
299 &self,
300 store: &Pubkey,
301 market_token: &Pubkey,
302 is_long: bool,
303 decrement_size_in_usd: u128,
304 price: u128,
305 is_collateral_token_long: bool,
306 collateral_withdrawal_amount: u64,
307 ) -> CreateOrderBuilder<C> {
308 let params = OrderParams {
309 kind: OrderKind::LimitDecrease,
310 decrease_position_swap_type: None,
311 min_output_amount: 0,
312 size_delta_usd: decrement_size_in_usd,
313 initial_collateral_delta_amount: collateral_withdrawal_amount,
314 acceptable_price: None,
315 trigger_price: Some(price),
316 is_long,
317 valid_from_ts: None,
318 };
319 self.create_order(store, market_token, is_collateral_token_long, params)
320 }
321
322 #[allow(clippy::too_many_arguments)]
324 fn stop_loss(
325 &self,
326 store: &Pubkey,
327 market_token: &Pubkey,
328 is_long: bool,
329 decrement_size_in_usd: u128,
330 price: u128,
331 is_collateral_token_long: bool,
332 collateral_withdrawal_amount: u64,
333 ) -> CreateOrderBuilder<C> {
334 let params = OrderParams {
335 kind: OrderKind::StopLossDecrease,
336 decrease_position_swap_type: None,
337 min_output_amount: 0,
338 size_delta_usd: decrement_size_in_usd,
339 initial_collateral_delta_amount: collateral_withdrawal_amount,
340 acceptable_price: None,
341 trigger_price: Some(price),
342 is_long,
343 valid_from_ts: None,
344 };
345 self.create_order(store, market_token, is_collateral_token_long, params)
346 }
347
348 #[allow(clippy::too_many_arguments)]
350 fn limit_swap<'a, S>(
351 &self,
352 store: &Pubkey,
353 market_token: &Pubkey,
354 is_output_token_long: bool,
355 min_output_amount: u64,
356 initial_swap_in_token: &Pubkey,
357 initial_swap_in_token_amount: u64,
358 swap_path: impl IntoIterator<Item = &'a Pubkey>,
359 ) -> CreateOrderBuilder<C>
360 where
361 C: Deref<Target = S> + Clone,
362 S: Signer,
363 {
364 let params = OrderParams {
365 kind: OrderKind::LimitSwap,
366 decrease_position_swap_type: None,
367 min_output_amount: u128::from(min_output_amount),
368 size_delta_usd: 0,
369 initial_collateral_delta_amount: initial_swap_in_token_amount,
370 acceptable_price: None,
371 trigger_price: None,
372 is_long: true,
373 valid_from_ts: None,
374 };
375 let mut builder = self.create_order(store, market_token, is_output_token_long, params);
376 builder
377 .initial_collateral_token(initial_swap_in_token, None)
378 .swap_path(swap_path.into_iter().copied().collect());
379 builder
380 }
381
382 fn create_shift(
384 &self,
385 store: &Pubkey,
386 from_market_token: &Pubkey,
387 to_market_token: &Pubkey,
388 amount: u64,
389 ) -> CreateShiftBuilder<C>;
390
391 fn close_shift(&self, shift: &Pubkey) -> CloseShiftBuilder<C>;
393
394 fn execute_shift(
396 &self,
397 oracle: &Pubkey,
398 shift: &Pubkey,
399 cancel_on_execution_error: bool,
400 ) -> ExecuteShiftBuilder<C>;
401}
402
403impl<S, C> ExchangeOps<C> for crate::Client<C>
404where
405 C: Deref<Target = S> + Clone,
406 S: Signer,
407{
408 fn toggle_feature(
409 &self,
410 store: &Pubkey,
411 domian: DomainDisabledFlag,
412 action: ActionDisabledFlag,
413 enable: bool,
414 ) -> TransactionBuilder<C> {
415 self.store_transaction()
416 .anchor_args(gmsol_store::instruction::ToggleFeature {
417 domain: domian.to_string(),
418 action: action.to_string(),
419 enable,
420 })
421 .anchor_accounts(gmsol_store::accounts::ToggleFeature {
422 authority: self.payer(),
423 store: *store,
424 })
425 }
426
427 fn claim_fees(
428 &self,
429 store: &Pubkey,
430 market_token: &Pubkey,
431 is_long_token: bool,
432 ) -> ClaimFeesBuilder<C> {
433 ClaimFeesBuilder::new(self, store, market_token, is_long_token)
434 }
435
436 fn create_deposit(&self, store: &Pubkey, market_token: &Pubkey) -> CreateDepositBuilder<C> {
437 CreateDepositBuilder::new(self, *store, *market_token)
438 }
439
440 fn close_deposit(&self, store: &Pubkey, deposit: &Pubkey) -> CloseDepositBuilder<C> {
441 CloseDepositBuilder::new(self, store, deposit)
442 }
443
444 fn execute_deposit(
445 &self,
446 store: &Pubkey,
447 oracle: &Pubkey,
448 deposit: &Pubkey,
449 cancel_on_execution_error: bool,
450 ) -> ExecuteDepositBuilder<C> {
451 ExecuteDepositBuilder::new(self, store, oracle, deposit, cancel_on_execution_error)
452 }
453
454 fn create_withdrawal(
455 &self,
456 store: &Pubkey,
457 market_token: &Pubkey,
458 amount: u64,
459 ) -> CreateWithdrawalBuilder<C> {
460 CreateWithdrawalBuilder::new(self, *store, *market_token, amount)
461 }
462
463 fn close_withdrawal(&self, store: &Pubkey, withdrawal: &Pubkey) -> CloseWithdrawalBuilder<C> {
464 CloseWithdrawalBuilder::new(self, store, withdrawal)
465 }
466
467 fn execute_withdrawal(
468 &self,
469 store: &Pubkey,
470 oracle: &Pubkey,
471 withdrawal: &Pubkey,
472 cancel_on_execution_error: bool,
473 ) -> ExecuteWithdrawalBuilder<C> {
474 ExecuteWithdrawalBuilder::new(self, store, oracle, withdrawal, cancel_on_execution_error)
475 }
476
477 async fn create_market(
478 &self,
479 store: &Pubkey,
480 name: &str,
481 index_token: &Pubkey,
482 long_token: &Pubkey,
483 short_token: &Pubkey,
484 enable: bool,
485 token_map: Option<&Pubkey>,
486 ) -> crate::Result<(TransactionBuilder<C>, Pubkey)> {
487 let token_map = match token_map {
488 Some(token_map) => *token_map,
489 None => self
490 .authorized_token_map_address(store)
491 .await?
492 .ok_or(crate::Error::NotFound)?,
493 };
494 let authority = self.payer();
495 let market_token =
496 self.find_market_token_address(store, index_token, long_token, short_token);
497 let prepare_long_token_vault = self.initialize_market_vault(store, long_token).0;
498 let prepare_short_token_vault = self.initialize_market_vault(store, short_token).0;
499 let prepare_market_token_vault = self.initialize_market_vault(store, &market_token).0;
500 let builder = self
501 .store_transaction()
502 .anchor_accounts(gmsol_store::accounts::InitializeMarket {
503 authority,
504 store: *store,
505 token_map,
506 market: self.find_market_address(store, &market_token),
507 market_token_mint: market_token,
508 long_token_mint: *long_token,
509 short_token_mint: *short_token,
510 long_token_vault: self.find_market_vault_address(store, long_token),
511 short_token_vault: self.find_market_vault_address(store, short_token),
512 system_program: system_program::ID,
513 token_program: anchor_spl::token::ID,
514 })
515 .anchor_args(gmsol_store::instruction::InitializeMarket {
516 name: name.to_string(),
517 index_token_mint: *index_token,
518 enable,
519 });
520 Ok((
521 prepare_long_token_vault
522 .merge(prepare_short_token_vault)
523 .merge(builder)
524 .merge(prepare_market_token_vault),
525 market_token,
526 ))
527 }
528
529 async fn fund_market(
530 &self,
531 store: &Pubkey,
532 market_token: &Pubkey,
533 source_account: &Pubkey,
534 amount: u64,
535 token: Option<&Pubkey>,
536 ) -> crate::Result<TransactionBuilder<C>> {
537 use anchor_spl::token::TokenAccount;
538
539 let token = match token {
540 Some(token) => *token,
541 None => {
542 let account = self
543 .account::<TokenAccount>(source_account)
544 .await?
545 .ok_or(crate::Error::NotFound)?;
546 account.mint
547 }
548 };
549 let vault = self.find_market_vault_address(store, &token);
550 let market = self.find_market_address(store, market_token);
551 Ok(self
552 .store_transaction()
553 .anchor_args(gmsol_store::instruction::MarketTransferIn { amount })
554 .anchor_accounts(gmsol_store::accounts::MarketTransferIn {
555 authority: self.payer(),
556 from_authority: self.payer(),
557 store: *store,
558 market,
559 vault,
560 from: *source_account,
561 token_program: anchor_spl::token::ID,
562 event_authority: self.store_event_authority(),
563 program: *self.store_program_id(),
564 }))
565 }
566
567 fn create_order(
568 &self,
569 store: &Pubkey,
570 market_token: &Pubkey,
571 is_output_token_long: bool,
572 params: OrderParams,
573 ) -> CreateOrderBuilder<C> {
574 CreateOrderBuilder::new(self, store, market_token, params, is_output_token_long)
575 }
576
577 fn update_order(
578 &self,
579 store: &Pubkey,
580 market_token: &Pubkey,
581 order: &Pubkey,
582 params: UpdateOrderParams,
583 ) -> crate::Result<TransactionBuilder<C>> {
584 Ok(self
585 .store_transaction()
586 .anchor_accounts(gmsol_store::accounts::UpdateOrder {
587 owner: self.payer(),
588 store: *store,
589 market: self.find_market_address(store, market_token),
590 order: *order,
591 })
592 .anchor_args(gmsol_store::instruction::UpdateOrder { params }))
593 }
594
595 fn execute_order(
596 &self,
597 store: &Pubkey,
598 oracle: &Pubkey,
599 order: &Pubkey,
600 cancel_on_execution_error: bool,
601 ) -> crate::Result<ExecuteOrderBuilder<C>> {
602 ExecuteOrderBuilder::try_new(self, store, oracle, order, cancel_on_execution_error)
603 }
604
605 fn close_order(&self, order: &Pubkey) -> crate::Result<CloseOrderBuilder<C>> {
606 Ok(CloseOrderBuilder::new(self, order))
607 }
608
609 async fn cancel_order_if_no_position(
610 &self,
611 store: &Pubkey,
612 order: &Pubkey,
613 position_hint: Option<&Pubkey>,
614 ) -> crate::Result<TransactionBuilder<C>> {
615 let position = match position_hint {
616 Some(position) => *position,
617 None => {
618 let order = self.order(order).await?;
619
620 let position = order.params().position().ok_or_else(|| {
621 crate::Error::invalid_argument("this order does not have position")
622 })?;
623
624 *position
625 }
626 };
627
628 Ok(self
629 .store_transaction()
630 .anchor_args(instruction::CancelOrderIfNoPosition {})
631 .anchor_accounts(accounts::CancelOrderIfNoPosition {
632 authority: self.payer(),
633 store: *store,
634 order: *order,
635 position,
636 }))
637 }
638
639 fn liquidate(
640 &self,
641 oracle: &Pubkey,
642 position: &Pubkey,
643 ) -> crate::Result<PositionCutBuilder<C>> {
644 PositionCutBuilder::try_new(self, PositionCutKind::Liquidate, oracle, position)
645 }
646
647 fn auto_deleverage(
648 &self,
649 oracle: &Pubkey,
650 position: &Pubkey,
651 size_delta_usd: u128,
652 ) -> crate::Result<PositionCutBuilder<C>> {
653 PositionCutBuilder::try_new(
654 self,
655 PositionCutKind::AutoDeleverage(size_delta_usd),
656 oracle,
657 position,
658 )
659 }
660
661 fn update_adl(
662 &self,
663 store: &Pubkey,
664 oracle: &Pubkey,
665 market_token: &Pubkey,
666 for_long: bool,
667 for_short: bool,
668 ) -> crate::Result<UpdateAdlBuilder<C>> {
669 UpdateAdlBuilder::try_new(self, store, oracle, market_token, for_long, for_short)
670 }
671
672 fn create_shift(
673 &self,
674 store: &Pubkey,
675 from_market_token: &Pubkey,
676 to_market_token: &Pubkey,
677 amount: u64,
678 ) -> CreateShiftBuilder<C> {
679 CreateShiftBuilder::new(self, store, from_market_token, to_market_token, amount)
680 }
681
682 fn close_shift(&self, shift: &Pubkey) -> CloseShiftBuilder<C> {
683 CloseShiftBuilder::new(self, shift)
684 }
685
686 fn execute_shift(
687 &self,
688 oracle: &Pubkey,
689 shift: &Pubkey,
690 cancel_on_execution_error: bool,
691 ) -> ExecuteShiftBuilder<C> {
692 ExecuteShiftBuilder::new(self, oracle, shift, cancel_on_execution_error)
693 }
694}
695
696impl<C: Deref<Target = impl Signer> + Clone> crate::Client<C> {
697 pub fn create_first_deposit(
699 &self,
700 store: &Pubkey,
701 market_token: &Pubkey,
702 ) -> CreateDepositBuilder<C> {
703 let mut builder = self.create_deposit(store, market_token);
704 builder.receiver(Some(self.find_first_deposit_owner_address()));
705 builder
706 }
707}
708
709pub(crate) fn generate_nonce() -> NonceBytes {
710 rand::thread_rng()
711 .sample_iter(Standard)
712 .take(32)
713 .collect::<Vec<u8>>()
714 .try_into()
715 .unwrap()
716}
717
718pub(crate) fn get_ata_or_owner(
719 owner: &Pubkey,
720 mint: &Pubkey,
721 should_unwrap_native_token: bool,
722) -> Pubkey {
723 get_ata_or_owner_with_program_id(
724 owner,
725 mint,
726 should_unwrap_native_token,
727 &anchor_spl::token::ID,
728 )
729}
730
731pub(crate) fn get_ata_or_owner_with_program_id(
732 owner: &Pubkey,
733 mint: &Pubkey,
734 should_unwrap_native_token: bool,
735 token_program_id: &Pubkey,
736) -> Pubkey {
737 use anchor_spl::{
738 associated_token::get_associated_token_address_with_program_id,
739 token::spl_token::native_mint,
740 };
741
742 if should_unwrap_native_token && *mint == native_mint::ID {
743 *owner
744 } else {
745 get_associated_token_address_with_program_id(owner, mint, token_program_id)
746 }
747}