1use std::{future::Future, ops::Deref};
2
3use anchor_spl::associated_token::get_associated_token_address_with_program_id;
4use gmsol_model::{price::Prices, PnlFactorKind};
5use gmsol_programs::gmsol_store::{
6 client::{accounts, args},
7 types::EntryArgs,
8};
9use gmsol_solana_utils::{
10 make_bundle_builder::MakeBundleBuilder, transaction_builder::TransactionBuilder,
11 IntoAtomicGroup,
12};
13use gmsol_utils::{
14 market::{MarketConfigFactor, MarketConfigFlag, MarketConfigKey, MarketMeta},
15 oracle::PriceProviderKind,
16 token_config::{token_records, TokensWithFeed},
17};
18use indexmap::IndexMap;
19use solana_sdk::{pubkey::Pubkey, signer::Signer, system_program};
20
21use crate::{
22 builders::market::SetMarketConfigUpdatable,
23 client::{
24 feeds_parser::{FeedAddressMap, FeedsParser},
25 pull_oracle::{FeedIds, PullOraclePriceConsumer},
26 },
27 utils::market::ordered_tokens,
28 Client,
29};
30
31use super::token_account::TokenAccountOps;
32
33type Factor = u128;
34
35const DEFAULT_MAX_AGE: u32 = 120;
36
37pub trait MarketOps<C> {
39 fn initialize_market_vault(
41 &self,
42 store: &Pubkey,
43 token: &Pubkey,
44 ) -> (TransactionBuilder<C>, Pubkey);
45
46 #[allow(clippy::too_many_arguments)]
48 fn create_market(
49 &self,
50 store: &Pubkey,
51 name: &str,
52 index_token: &Pubkey,
53 long_token: &Pubkey,
54 short_token: &Pubkey,
55 enable: bool,
56 token_map: Option<&Pubkey>,
57 ) -> impl Future<Output = crate::Result<(TransactionBuilder<C>, Pubkey)>>;
58
59 fn fund_market(
61 &self,
62 store: &Pubkey,
63 market_token: &Pubkey,
64 source_account: &Pubkey,
65 amount: u64,
66 token: Option<&Pubkey>,
67 ) -> impl Future<Output = crate::Result<TransactionBuilder<C>>>;
68
69 fn claim_fees(
71 &self,
72 store: &Pubkey,
73 market_token: &Pubkey,
74 is_long_token: bool,
75 ) -> ClaimFeesBuilder<C>;
76
77 fn get_market_status(
79 &self,
80 store: &Pubkey,
81 market_token: &Pubkey,
82 prices: Prices<u128>,
83 maximize_pnl: bool,
84 maximize_pool_value: bool,
85 ) -> TransactionBuilder<C>;
86
87 fn get_market_token_price(
89 &self,
90 store: &Pubkey,
91 market_token: &Pubkey,
92 prices: Prices<u128>,
93 pnl_factor: PnlFactorKind,
94 maximize: bool,
95 ) -> TransactionBuilder<C>;
96
97 fn get_market_token_value(
99 &self,
100 store: &Pubkey,
101 oracle: &Pubkey,
102 market_token: &Pubkey,
103 amount: u64,
104 ) -> GetMarketTokenValueBuilder<'_, C>;
105
106 fn update_market_config(
108 &self,
109 store: &Pubkey,
110 market_token: &Pubkey,
111 key: &str,
112 value: &Factor,
113 ) -> crate::Result<TransactionBuilder<C>>;
114
115 fn update_market_config_flag(
117 &self,
118 store: &Pubkey,
119 market_token: &Pubkey,
120 key: &str,
121 value: bool,
122 ) -> crate::Result<TransactionBuilder<C>>;
123
124 fn update_market_config_by_key(
126 &self,
127 store: &Pubkey,
128 market_token: &Pubkey,
129 key: MarketConfigKey,
130 value: &Factor,
131 ) -> crate::Result<TransactionBuilder<C>> {
132 let key = key.to_string();
133 self.update_market_config(store, market_token, &key, value)
134 }
135
136 fn update_market_config_flag_by_key(
138 &self,
139 store: &Pubkey,
140 market_token: &Pubkey,
141 key: MarketConfigFlag,
142 value: bool,
143 ) -> crate::Result<TransactionBuilder<C>> {
144 let key = key.to_string();
145 self.update_market_config_flag(store, market_token, &key, value)
146 }
147
148 fn toggle_market(
150 &self,
151 store: &Pubkey,
152 market_token: &Pubkey,
153 enable: bool,
154 ) -> TransactionBuilder<C>;
155
156 fn toggle_gt_minting(
158 &self,
159 store: &Pubkey,
160 market_token: &Pubkey,
161 enable: bool,
162 ) -> TransactionBuilder<C>;
163
164 fn initialize_market_config_buffer<'a>(
166 &'a self,
167 store: &Pubkey,
168 buffer: &'a dyn Signer,
169 expire_after_secs: u32,
170 ) -> TransactionBuilder<'a, C>;
171
172 fn close_marekt_config_buffer(
174 &self,
175 buffer: &Pubkey,
176 receiver: Option<&Pubkey>,
177 ) -> TransactionBuilder<C>;
178
179 fn push_to_market_config_buffer<S: ToString>(
181 &self,
182 buffer: &Pubkey,
183 new_configs: impl IntoIterator<Item = (S, Factor)>,
184 ) -> TransactionBuilder<C>;
185
186 fn set_market_config_buffer_authority(
188 &self,
189 buffer: &Pubkey,
190 new_authority: &Pubkey,
191 ) -> TransactionBuilder<C>;
192
193 fn update_market_config_with_buffer(
195 &self,
196 store: &Pubkey,
197 market_token: &Pubkey,
198 buffer: &Pubkey,
199 ) -> TransactionBuilder<C>;
200
201 fn set_market_config_updatable(
203 &self,
204 store: &Pubkey,
205 flags: impl Into<IndexMap<MarketConfigFlag, bool>>,
206 factors: impl Into<IndexMap<MarketConfigFactor, bool>>,
207 ) -> crate::Result<TransactionBuilder<C>>;
208}
209
210impl<C: Deref<Target = impl Signer> + Clone> MarketOps<C> for crate::Client<C> {
211 fn initialize_market_vault(
212 &self,
213 store: &Pubkey,
214 token: &Pubkey,
215 ) -> (TransactionBuilder<C>, Pubkey) {
216 let authority = self.payer();
217 let vault = self.find_market_vault_address(store, token);
218 let builder = self
219 .store_transaction()
220 .anchor_accounts(accounts::InitializeMarketVault {
221 authority,
222 store: *store,
223 mint: *token,
224 vault,
225 system_program: system_program::ID,
226 token_program: anchor_spl::token::ID,
227 })
228 .anchor_args(args::InitializeMarketVault {});
229 (builder, vault)
230 }
231
232 async fn create_market(
233 &self,
234 store: &Pubkey,
235 name: &str,
236 index_token: &Pubkey,
237 long_token: &Pubkey,
238 short_token: &Pubkey,
239 enable: bool,
240 token_map: Option<&Pubkey>,
241 ) -> crate::Result<(TransactionBuilder<C>, Pubkey)> {
242 let token_map = match token_map {
243 Some(token_map) => *token_map,
244 None => self
245 .authorized_token_map_address(store)
246 .await?
247 .ok_or(crate::Error::NotFound)?,
248 };
249 let authority = self.payer();
250 let market_token =
251 self.find_market_token_address(store, index_token, long_token, short_token);
252 let prepare_long_token_vault = self.initialize_market_vault(store, long_token).0;
253 let prepare_short_token_vault = self.initialize_market_vault(store, short_token).0;
254 let prepare_market_token_vault = self.initialize_market_vault(store, &market_token).0;
255 let builder = self
256 .store_transaction()
257 .anchor_accounts(accounts::InitializeMarket {
258 authority,
259 store: *store,
260 token_map,
261 market: self.find_market_address(store, &market_token),
262 market_token_mint: market_token,
263 long_token_mint: *long_token,
264 short_token_mint: *short_token,
265 long_token_vault: self.find_market_vault_address(store, long_token),
266 short_token_vault: self.find_market_vault_address(store, short_token),
267 system_program: system_program::ID,
268 token_program: anchor_spl::token::ID,
269 })
270 .anchor_args(args::InitializeMarket {
271 name: name.to_string(),
272 index_token_mint: *index_token,
273 enable,
274 });
275 Ok((
276 prepare_long_token_vault
277 .merge(prepare_short_token_vault)
278 .merge(builder)
279 .merge(prepare_market_token_vault),
280 market_token,
281 ))
282 }
283
284 async fn fund_market(
285 &self,
286 store: &Pubkey,
287 market_token: &Pubkey,
288 source_account: &Pubkey,
289 amount: u64,
290 token: Option<&Pubkey>,
291 ) -> crate::Result<TransactionBuilder<C>> {
292 use anchor_spl::token::TokenAccount;
293
294 let token = match token {
295 Some(token) => *token,
296 None => {
297 let account = self
298 .account::<TokenAccount>(source_account)
299 .await?
300 .ok_or(crate::Error::NotFound)?;
301 account.mint
302 }
303 };
304 let vault = self.find_market_vault_address(store, &token);
305 let market = self.find_market_address(store, market_token);
306 Ok(self
307 .store_transaction()
308 .anchor_args(args::MarketTransferIn { amount })
309 .anchor_accounts(accounts::MarketTransferIn {
310 authority: self.payer(),
311 from_authority: self.payer(),
312 store: *store,
313 market,
314 vault,
315 from: *source_account,
316 token_program: anchor_spl::token::ID,
317 event_authority: self.store_event_authority(),
318 program: *self.store_program_id(),
319 }))
320 }
321
322 fn claim_fees(
323 &self,
324 store: &Pubkey,
325 market_token: &Pubkey,
326 is_long_token: bool,
327 ) -> ClaimFeesBuilder<C> {
328 ClaimFeesBuilder::new(self, store, market_token, is_long_token)
329 }
330
331 fn get_market_status(
332 &self,
333 store: &Pubkey,
334 market_token: &Pubkey,
335 prices: Prices<u128>,
336 maximize_pnl: bool,
337 maximize_pool_value: bool,
338 ) -> TransactionBuilder<C> {
339 self.store_transaction()
340 .anchor_args(args::GetMarketStatus {
341 prices: prices.into(),
342 maximize_pnl,
343 maximize_pool_value,
344 })
345 .anchor_accounts(accounts::GetMarketStatus {
346 market: self.find_market_address(store, market_token),
347 })
348 }
349
350 fn get_market_token_price(
351 &self,
352 store: &Pubkey,
353 market_token: &Pubkey,
354 prices: Prices<u128>,
355 pnl_factor: PnlFactorKind,
356 maximize: bool,
357 ) -> TransactionBuilder<C> {
358 self.store_transaction()
359 .anchor_args(args::GetMarketTokenPrice {
360 prices: prices.into(),
361 pnl_factor: pnl_factor.to_string(),
362 maximize,
363 })
364 .anchor_accounts(accounts::GetMarketTokenPrice {
365 market: self.find_market_address(store, market_token),
366 market_token: *market_token,
367 })
368 }
369
370 fn get_market_token_value(
371 &self,
372 store: &Pubkey,
373 oracle: &Pubkey,
374 market_token: &Pubkey,
375 amount: u64,
376 ) -> GetMarketTokenValueBuilder<'_, C> {
377 GetMarketTokenValueBuilder::new(self, *store, *oracle, *market_token, amount)
378 }
379
380 fn update_market_config(
381 &self,
382 store: &Pubkey,
383 market_token: &Pubkey,
384 key: &str,
385 value: &Factor,
386 ) -> crate::Result<TransactionBuilder<C>> {
387 let req = self
388 .store_transaction()
389 .anchor_args(args::UpdateMarketConfig {
390 key: key.to_string(),
391 value: *value,
392 })
393 .anchor_accounts(accounts::UpdateMarketConfig {
394 authority: self.payer(),
395 store: *store,
396 market: self.find_market_address(store, market_token),
397 });
398 Ok(req)
399 }
400
401 fn update_market_config_flag(
402 &self,
403 store: &Pubkey,
404 market_token: &Pubkey,
405 key: &str,
406 value: bool,
407 ) -> crate::Result<TransactionBuilder<C>> {
408 let req = self
409 .store_transaction()
410 .anchor_args(args::UpdateMarketConfigFlag {
411 key: key.to_string(),
412 value,
413 })
414 .anchor_accounts(accounts::UpdateMarketConfig {
415 authority: self.payer(),
416 store: *store,
417 market: self.find_market_address(store, market_token),
418 });
419 Ok(req)
420 }
421
422 fn toggle_market(
423 &self,
424 store: &Pubkey,
425 market_token: &Pubkey,
426 enable: bool,
427 ) -> TransactionBuilder<C> {
428 self.store_transaction()
429 .anchor_args(args::ToggleMarket { enable })
430 .anchor_accounts(accounts::ToggleMarket {
431 authority: self.payer(),
432 store: *store,
433 market: self.find_market_address(store, market_token),
434 })
435 }
436
437 fn toggle_gt_minting(
438 &self,
439 store: &Pubkey,
440 market_token: &Pubkey,
441 enable: bool,
442 ) -> TransactionBuilder<C> {
443 self.store_transaction()
444 .anchor_args(args::ToggleGtMinting { enable })
445 .anchor_accounts(accounts::ToggleGtMinting {
446 authority: self.payer(),
447 store: *store,
448 market: self.find_market_address(store, market_token),
449 })
450 }
451
452 fn initialize_market_config_buffer<'a>(
453 &'a self,
454 store: &Pubkey,
455 buffer: &'a dyn Signer,
456 expire_after_secs: u32,
457 ) -> TransactionBuilder<'a, C> {
458 self.store_transaction()
459 .anchor_args(args::InitializeMarketConfigBuffer { expire_after_secs })
460 .anchor_accounts(accounts::InitializeMarketConfigBuffer {
461 authority: self.payer(),
462 store: *store,
463 buffer: buffer.pubkey(),
464 system_program: system_program::ID,
465 })
466 .signer(buffer)
467 }
468
469 fn close_marekt_config_buffer(
470 &self,
471 buffer: &Pubkey,
472 receiver: Option<&Pubkey>,
473 ) -> TransactionBuilder<C> {
474 self.store_transaction()
475 .anchor_args(args::CloseMarketConfigBuffer {})
476 .anchor_accounts(accounts::CloseMarketConfigBuffer {
477 authority: self.payer(),
478 buffer: *buffer,
479 receiver: receiver.copied().unwrap_or(self.payer()),
480 })
481 }
482
483 fn push_to_market_config_buffer<K: ToString>(
484 &self,
485 buffer: &Pubkey,
486 new_configs: impl IntoIterator<Item = (K, Factor)>,
487 ) -> TransactionBuilder<C> {
488 self.store_transaction()
489 .anchor_args(args::PushToMarketConfigBuffer {
490 new_configs: new_configs
491 .into_iter()
492 .map(|(key, value)| EntryArgs {
493 key: key.to_string(),
494 value,
495 })
496 .collect(),
497 })
498 .anchor_accounts(accounts::PushToMarketConfigBuffer {
499 authority: self.payer(),
500 buffer: *buffer,
501 system_program: system_program::ID,
502 })
503 }
504
505 fn set_market_config_buffer_authority(
506 &self,
507 buffer: &Pubkey,
508 new_authority: &Pubkey,
509 ) -> TransactionBuilder<C> {
510 self.store_transaction()
511 .anchor_args(args::SetMarketConfigBufferAuthority {
512 new_authority: *new_authority,
513 })
514 .anchor_accounts(accounts::SetMarketConfigBufferAuthority {
515 authority: self.payer(),
516 buffer: *buffer,
517 })
518 }
519
520 fn update_market_config_with_buffer(
521 &self,
522 store: &Pubkey,
523 market_token: &Pubkey,
524 buffer: &Pubkey,
525 ) -> TransactionBuilder<C> {
526 self.store_transaction()
527 .anchor_args(args::UpdateMarketConfigWithBuffer {})
528 .anchor_accounts(accounts::UpdateMarketConfigWithBuffer {
529 authority: self.payer(),
530 store: *store,
531 market: self.find_market_address(store, market_token),
532 buffer: *buffer,
533 })
534 }
535
536 fn set_market_config_updatable(
537 &self,
538 store: &Pubkey,
539 flags: impl Into<IndexMap<MarketConfigFlag, bool>>,
540 factors: impl Into<IndexMap<MarketConfigFactor, bool>>,
541 ) -> crate::Result<TransactionBuilder<C>> {
542 let ag = SetMarketConfigUpdatable::builder()
543 .payer(self.payer())
544 .store_program(self.store_program_for_builders(store))
545 .flags(flags)
546 .factors(factors)
547 .build()
548 .into_atomic_group(&())?;
549 Ok(self.store_transaction().pre_atomic_group(ag, true))
550 }
551}
552
553pub struct ClaimFeesBuilder<'a, C> {
555 client: &'a crate::Client<C>,
556 store: Pubkey,
557 market_token: Pubkey,
558 is_long_token: bool,
559 hint_token: Option<Pubkey>,
560}
561
562impl<'a, C: Deref<Target = impl Signer> + Clone> ClaimFeesBuilder<'a, C> {
563 pub fn new(
565 client: &'a crate::Client<C>,
566 store: &Pubkey,
567 market_token: &Pubkey,
568 is_long_token: bool,
569 ) -> Self {
570 Self {
571 client,
572 store: *store,
573 market_token: *market_token,
574 is_long_token,
575 hint_token: None,
576 }
577 }
578
579 pub fn set_hint(&mut self, token: Pubkey) -> &mut Self {
581 self.hint_token = Some(token);
582 self
583 }
584
585 pub async fn build(&self) -> crate::Result<TransactionBuilder<'a, C>> {
587 let market = self
588 .client
589 .find_market_address(&self.store, &self.market_token);
590 let token = match self.hint_token {
591 Some(token) => token,
592 None => {
593 let market = self.client.market(&market).await?;
594 MarketMeta::from(market.meta).pnl_token(self.is_long_token)
595 }
596 };
597
598 let authority = self.client.payer();
599 let vault = self.client.find_market_vault_address(&self.store, &token);
600 let token_program = anchor_spl::token::ID;
602 let target =
603 get_associated_token_address_with_program_id(&authority, &token, &token_program);
604
605 let prepare = self
606 .client
607 .prepare_associated_token_account(&token, &token_program, None);
608
609 let rpc = self
610 .client
611 .store_transaction()
612 .anchor_accounts(accounts::ClaimFeesFromMarket {
613 authority,
614 store: self.store,
615 market,
616 token_mint: token,
617 vault,
618 target,
619 token_program,
620 event_authority: self.client.store_event_authority(),
621 program: *self.client.store_program_id(),
622 })
623 .anchor_args(args::ClaimFeesFromMarket {});
624
625 Ok(prepare.merge(rpc))
626 }
627}
628
629pub struct GetMarketTokenValueBuilder<'a, C> {
631 client: &'a Client<C>,
632 store: Pubkey,
633 oracle: Pubkey,
634 market_token: Pubkey,
635 amount: u64,
636 pnl_factor: PnlFactorKind,
637 maximize: bool,
638 max_age: u32,
639 emit_event: bool,
640 feeds_parser: FeedsParser,
641 hint: Option<GetMarketTokenValueHint>,
642}
643
644#[derive(Debug, Clone)]
646pub struct GetMarketTokenValueHint {
647 pub token_map: Pubkey,
649 pub feeds: TokensWithFeed,
651}
652
653impl<C> GetMarketTokenValueBuilder<'_, C> {
654 pub fn pnl_factor(&mut self, kind: PnlFactorKind) -> &mut Self {
656 self.pnl_factor = kind;
657 self
658 }
659
660 pub fn maximize(&mut self, maximize: bool) -> &mut Self {
662 self.maximize = maximize;
663 self
664 }
665
666 pub fn max_age(&mut self, max_age: u32) -> &mut Self {
668 self.max_age = max_age;
669 self
670 }
671
672 pub fn emit_event(&mut self, emit: bool) -> &mut Self {
674 self.emit_event = emit;
675 self
676 }
677
678 pub fn hint(&mut self, hint: Option<GetMarketTokenValueHint>) -> &mut Self {
680 self.hint = hint;
681 self
682 }
683}
684
685impl<'a, C: Deref<Target = impl Signer> + Clone> GetMarketTokenValueBuilder<'a, C> {
686 fn new(
687 client: &'a Client<C>,
688 store: Pubkey,
689 oracle: Pubkey,
690 market_token: Pubkey,
691 amount: u64,
692 ) -> Self {
693 Self {
694 client,
695 store,
696 oracle,
697 market_token,
698 amount,
699 pnl_factor: PnlFactorKind::MaxAfterDeposit,
700 maximize: false,
701 max_age: DEFAULT_MAX_AGE,
702 emit_event: true,
703 feeds_parser: Default::default(),
704 hint: None,
705 }
706 }
707
708 pub async fn prepare_hint(&mut self) -> crate::Result<GetMarketTokenValueHint> {
710 if let Some(hint) = self.hint.as_ref() {
711 return Ok(hint.clone());
712 }
713
714 let store = self.client.store(&self.store).await?;
715 let token_map_address = store.token_map;
716 let market = self
717 .client
718 .market_by_token(&self.store, &self.market_token)
719 .await?;
720 let token_map = self.client.token_map(&token_map_address).await?;
721 let tokens = ordered_tokens(&market.meta.into());
722 let records = token_records(&token_map, &tokens).map_err(crate::Error::custom)?;
723 let feeds = TokensWithFeed::try_from_records(records).map_err(crate::Error::custom)?;
724 let hint = GetMarketTokenValueHint {
725 token_map: token_map_address,
726 feeds,
727 };
728 self.hint = Some(hint.clone());
729 Ok(hint)
730 }
731
732 async fn build_txn(&mut self) -> crate::Result<TransactionBuilder<'a, C>> {
733 let hint = self.prepare_hint().await?;
734 let Self {
735 client,
736 store,
737 oracle,
738 market_token,
739 amount,
740 pnl_factor,
741 maximize,
742 max_age,
743 feeds_parser,
744 emit_event,
745 ..
746 } = self;
747 let authority = client.payer();
748 let feeds = feeds_parser
749 .parse(&hint.feeds)
750 .collect::<Result<Vec<_>, _>>()?;
751 let market = client.find_market_address(store, market_token);
752 let txn = client
753 .store_transaction()
754 .anchor_args(args::GetMarketTokenValue {
755 amount: *amount,
756 pnl_factor: pnl_factor.to_string(),
757 maximize: *maximize,
758 max_age: *max_age,
759 emit_event: *emit_event,
760 })
761 .anchor_accounts(accounts::GetMarketTokenValue {
762 authority,
763 store: *store,
764 token_map: hint.token_map,
765 oracle: *oracle,
766 market,
767 market_token: *market_token,
768 event_authority: client.store_event_authority(),
769 program: *client.store_program_id(),
770 })
771 .accounts(feeds);
772 Ok(txn)
773 }
774}
775
776impl<'a, C: Deref<Target = impl Signer> + Clone> MakeBundleBuilder<'a, C>
777 for GetMarketTokenValueBuilder<'a, C>
778{
779 async fn build_with_options(
780 &mut self,
781 options: gmsol_solana_utils::bundle_builder::BundleOptions,
782 ) -> gmsol_solana_utils::Result<gmsol_solana_utils::bundle_builder::BundleBuilder<'a, C>> {
783 let mut tx = self.client.bundle_with_options(options);
784
785 tx.try_push(
786 self.build_txn()
787 .await
788 .map_err(gmsol_solana_utils::Error::custom)?,
789 )?;
790
791 Ok(tx)
792 }
793}
794
795impl<C: Deref<Target = impl Signer> + Clone> PullOraclePriceConsumer
796 for GetMarketTokenValueBuilder<'_, C>
797{
798 async fn feed_ids(&mut self) -> crate::Result<FeedIds> {
799 let hint = self.prepare_hint().await?;
800 Ok(FeedIds::new(self.store, hint.feeds))
801 }
802
803 fn process_feeds(
804 &mut self,
805 provider: PriceProviderKind,
806 map: FeedAddressMap,
807 ) -> crate::Result<()> {
808 self.feeds_parser
809 .insert_pull_oracle_feed_parser(provider, map);
810 Ok(())
811 }
812}