gmsol/exchange/
mod.rs

1/// Deposit.
2pub mod deposit;
3
4/// Withdrawal.
5pub mod withdrawal;
6
7/// Order.
8pub mod order;
9
10/// Shift.
11pub mod shift;
12
13/// Auto-deleveraging.
14pub mod auto_deleveraging;
15
16/// Position cut.
17pub mod position_cut;
18
19/// Treasury.
20pub 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
53/// Exchange instructions.
54pub trait ExchangeOps<C> {
55    /// Toggle feature.
56    fn toggle_feature(
57        &self,
58        store: &Pubkey,
59        domian: DomainDisabledFlag,
60        action: ActionDisabledFlag,
61        enable: bool,
62    ) -> TransactionBuilder<C>;
63
64    /// Claim fees.
65    fn claim_fees(
66        &self,
67        store: &Pubkey,
68        market_token: &Pubkey,
69        is_long_token: bool,
70    ) -> ClaimFeesBuilder<C>;
71
72    /// Create a new market and return its token mint address.
73    #[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    /// Fund the given market.
86    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    /// Create a deposit.
96    fn create_deposit(&self, store: &Pubkey, market_token: &Pubkey) -> CreateDepositBuilder<C>;
97
98    /// Cancel a deposit.
99    fn close_deposit(&self, store: &Pubkey, deposit: &Pubkey) -> CloseDepositBuilder<C>;
100
101    /// Execute a deposit.
102    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    /// Create a withdrawal.
111    fn create_withdrawal(
112        &self,
113        store: &Pubkey,
114        market_token: &Pubkey,
115        amount: u64,
116    ) -> CreateWithdrawalBuilder<C>;
117
118    /// Close a withdrawal.
119    fn close_withdrawal(&self, store: &Pubkey, withdrawal: &Pubkey) -> CloseWithdrawalBuilder<C>;
120
121    /// Execute a withdrawal.
122    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    /// Create an order.
131    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    /// Update an order.
140    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    /// Execute an order.
149    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    /// Close an order.
158    fn close_order(&self, order: &Pubkey) -> crate::Result<CloseOrderBuilder<C>>;
159
160    /// Cancel order if the position does not exist.
161    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    /// Liquidate a position.
169    fn liquidate(&self, oracle: &Pubkey, position: &Pubkey)
170        -> crate::Result<PositionCutBuilder<C>>;
171
172    /// Auto-deleverage a position.
173    fn auto_deleverage(
174        &self,
175        oracle: &Pubkey,
176        position: &Pubkey,
177        size_delta_usd: u128,
178    ) -> crate::Result<PositionCutBuilder<C>>;
179
180    /// Update ADL state.
181    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    /// Create a market increase position order.
191    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    /// Create a market decrease position order.
215    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    /// Create a market swap order.
239    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    /// Create a limit increase order.
271    #[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    /// Create a limit decrease order.
297    #[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    /// Create a stop-loss decrease order.
323    #[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    /// Create a limit swap order.
349    #[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    /// Create shift.
383    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    /// Close shift.
392    fn close_shift(&self, shift: &Pubkey) -> CloseShiftBuilder<C>;
393
394    /// Execute shift.
395    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    /// Create first deposit.
698    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}