1use std::ops::Deref;
2
3use anchor_client::{
4 anchor_lang::system_program,
5 solana_sdk::{instruction::AccountMeta, pubkey::Pubkey, signer::Signer},
6};
7use anchor_spl::associated_token::get_associated_token_address;
8use gmsol_solana_utils::{
9 bundle_builder::{BundleBuilder, BundleOptions},
10 compute_budget::ComputeBudget,
11 transaction_builder::TransactionBuilder,
12};
13use gmsol_store::{
14 accounts, instruction,
15 ops::deposit::CreateDepositParams,
16 states::{
17 common::{action::Action, swap::SwapActionParams, TokensWithFeed},
18 Deposit, NonceBytes, PriceProviderKind, TokenMapAccess,
19 },
20};
21
22use crate::{
23 exchange::ExchangeOps,
24 store::{token::TokenAccountOps, utils::FeedsParser},
25 utils::builder::{
26 FeedAddressMap, FeedIds, MakeBundleBuilder, PullOraclePriceConsumer, SetExecutionFee,
27 },
28};
29
30use super::{generate_nonce, get_ata_or_owner};
31
32#[cfg(feature = "pyth-pull-oracle")]
33use crate::pyth::pull_oracle::Prices;
34
35pub const EXECUTE_DEPOSIT_COMPUTE_BUDGET: u32 = 400_000;
37
38pub struct CreateDepositBuilder<'a, C> {
40 client: &'a crate::Client<C>,
41 store: Pubkey,
42 market_token: Pubkey,
43 execution_fee: u64,
44 long_token_swap_path: Vec<Pubkey>,
45 short_token_swap_path: Vec<Pubkey>,
46 initial_long_token: Option<Pubkey>,
47 initial_short_token: Option<Pubkey>,
48 initial_long_token_account: Option<Pubkey>,
49 initial_short_token_account: Option<Pubkey>,
50 initial_long_token_amount: u64,
51 initial_short_token_amount: u64,
52 min_market_token: u64,
53 receiver: Option<Pubkey>,
54 nonce: Option<NonceBytes>,
55 should_unwrap_native_token: bool,
56}
57
58impl<C> CreateDepositBuilder<'_, C> {
59 pub fn nonce(&mut self, nonce: NonceBytes) -> &mut Self {
61 self.nonce = Some(nonce);
62 self
63 }
64
65 pub fn execution_fee(&mut self, fee: u64) -> &mut Self {
67 self.execution_fee = fee;
68 self
69 }
70
71 pub fn min_market_token(&mut self, amount: u64) -> &mut Self {
73 self.min_market_token = amount;
74 self
75 }
76
77 pub fn long_token_swap_path(&mut self, market_tokens: Vec<Pubkey>) -> &mut Self {
79 self.long_token_swap_path = market_tokens;
80 self
81 }
82
83 pub fn short_token_swap_path(&mut self, market_tokens: Vec<Pubkey>) -> &mut Self {
85 self.short_token_swap_path = market_tokens;
86 self
87 }
88
89 pub fn receiver(&mut self, receiver: Option<Pubkey>) -> &mut Self {
92 self.receiver = receiver;
93 self
94 }
95
96 pub fn should_unwrap_native_token(&mut self, should_unwrap: bool) -> &mut Self {
99 self.should_unwrap_native_token = should_unwrap;
100 self
101 }
102}
103
104impl<'a, C, S> CreateDepositBuilder<'a, C>
105where
106 C: Deref<Target = S> + Clone,
107 S: Signer,
108{
109 pub(super) fn new(client: &'a crate::Client<C>, store: Pubkey, market_token: Pubkey) -> Self {
110 Self {
111 client,
112 store,
113 market_token,
114 execution_fee: Deposit::MIN_EXECUTION_LAMPORTS,
115 long_token_swap_path: vec![],
116 short_token_swap_path: vec![],
117 initial_long_token: None,
118 initial_short_token: None,
119 initial_long_token_account: None,
120 initial_short_token_account: None,
121 initial_long_token_amount: 0,
122 initial_short_token_amount: 0,
123 min_market_token: 0,
124 receiver: None,
125 nonce: None,
126 should_unwrap_native_token: true,
127 }
128 }
129
130 fn get_receiver(&self) -> Pubkey {
131 self.receiver.unwrap_or(self.client.payer())
132 }
133
134 fn get_or_find_associated_initial_long_token_account(
135 &self,
136 token: Option<&Pubkey>,
137 ) -> Option<Pubkey> {
138 let token = token?;
139 match self.initial_long_token_account {
140 Some(account) => Some(account),
141 None => Some(get_associated_token_address(&self.client.payer(), token)),
142 }
143 }
144
145 fn get_or_find_associated_initial_short_token_account(
146 &self,
147 token: Option<&Pubkey>,
148 ) -> Option<Pubkey> {
149 let token = token?;
150 match self.initial_short_token_account {
151 Some(account) => Some(account),
152 None => Some(get_associated_token_address(&self.client.payer(), token)),
153 }
154 }
155
156 async fn get_or_fetch_initial_tokens(
157 &self,
158 market: &Pubkey,
159 ) -> crate::Result<(Option<Pubkey>, Option<Pubkey>)> {
160 let res = match (
161 self.initial_long_token,
162 self.initial_long_token_amount,
163 self.initial_short_token,
164 self.initial_short_token_amount,
165 ) {
166 (Some(long_token), _, Some(short_token), _) => (Some(long_token), Some(short_token)),
167 (_, 0, _, 0) => {
168 return Err(crate::Error::EmptyDeposit);
169 }
170 (None, 0, Some(short_token), _) => (None, Some(short_token)),
171 (Some(long_token), _, None, 0) => (Some(long_token), None),
172 (mut long_token, long_amount, mut short_token, short_amount) => {
173 debug_assert!(
174 (long_token.is_none() && long_amount != 0)
175 || (short_token.is_none() && short_amount != 0)
176 );
177 let market = self.client.market(market).await?;
178 if long_amount != 0 && long_token.is_none() {
179 long_token = Some(market.meta().long_token_mint);
180 }
181 if short_amount != 0 && short_token.is_none() {
182 short_token = Some(market.meta().short_token_mint);
183 }
184 (long_token, short_token)
185 }
186 };
187 Ok(res)
188 }
189
190 pub fn long_token(
192 &mut self,
193 amount: u64,
194 token: Option<&Pubkey>,
195 token_account: Option<&Pubkey>,
196 ) -> &mut Self {
197 self.initial_long_token = token.copied();
198 self.initial_long_token_amount = amount;
199 self.initial_long_token_account = token_account.copied();
200 self
201 }
202
203 pub fn short_token(
205 &mut self,
206 amount: u64,
207 token: Option<&Pubkey>,
208 token_account: Option<&Pubkey>,
209 ) -> &mut Self {
210 self.initial_short_token = token.cloned();
211 self.initial_short_token_amount = amount;
212 self.initial_short_token_account = token_account.copied();
213 self
214 }
215
216 pub async fn build_with_address(&self) -> crate::Result<(TransactionBuilder<'a, C>, Pubkey)> {
218 let token_program_id = anchor_spl::token::ID;
219 let Self {
220 client,
221 store,
222 nonce,
223 market_token,
224 execution_fee,
225 long_token_swap_path,
226 short_token_swap_path,
227 initial_long_token_amount,
228 initial_short_token_amount,
229 min_market_token,
230 should_unwrap_native_token,
231 ..
232 } = self;
233 let nonce = nonce.unwrap_or_else(generate_nonce);
234 let owner = client.payer();
235 let receiver = self.get_receiver();
236 let deposit = client.find_deposit_address(store, &owner, &nonce);
237 let market = client.find_market_address(store, market_token);
238
239 let (long_token, short_token) = self.get_or_fetch_initial_tokens(&market).await?;
240
241 let initial_long_token_account =
242 self.get_or_find_associated_initial_long_token_account(long_token.as_ref());
243 let initial_short_token_account =
244 self.get_or_find_associated_initial_short_token_account(short_token.as_ref());
245 let market_token_ata = get_associated_token_address(&receiver, market_token);
246
247 let market_token_escrow = get_associated_token_address(&deposit, market_token);
248 let initial_long_token_escrow = long_token
249 .as_ref()
250 .map(|mint| get_associated_token_address(&deposit, mint));
251 let initial_short_token_escrow = short_token
252 .as_ref()
253 .map(|mint| get_associated_token_address(&deposit, mint));
254
255 let mut prepare = client.prepare_associated_token_account(
256 market_token,
257 &token_program_id,
258 Some(&deposit),
259 );
260
261 for token in long_token.iter().chain(short_token.iter()) {
262 prepare = prepare.merge(client.prepare_associated_token_account(
263 token,
264 &token_program_id,
265 Some(&deposit),
266 ));
267 }
268
269 let create = client
270 .store_transaction()
271 .accounts(crate::utils::fix_optional_account_metas(
272 accounts::CreateDeposit {
273 owner,
274 receiver,
275 store: *store,
276 market,
277 deposit,
278 market_token: *market_token,
279 initial_long_token: long_token,
280 initial_short_token: short_token,
281 market_token_ata,
282 market_token_escrow,
283 initial_long_token_escrow,
284 initial_short_token_escrow,
285 initial_long_token_source: initial_long_token_account,
286 initial_short_token_source: initial_short_token_account,
287 system_program: system_program::ID,
288 token_program: token_program_id,
289 associated_token_program: anchor_spl::associated_token::ID,
290 },
291 &gmsol_store::id(),
292 client.store_program_id(),
293 ))
294 .anchor_args(instruction::CreateDeposit {
295 nonce,
296 params: CreateDepositParams {
297 execution_lamports: *execution_fee,
298 long_token_swap_length: long_token_swap_path
299 .len()
300 .try_into()
301 .map_err(|_| crate::Error::NumberOutOfRange)?,
302 short_token_swap_length: short_token_swap_path
303 .len()
304 .try_into()
305 .map_err(|_| crate::Error::NumberOutOfRange)?,
306 initial_long_token_amount: *initial_long_token_amount,
307 initial_short_token_amount: *initial_short_token_amount,
308 min_market_token_amount: *min_market_token,
309 should_unwrap_native_token: *should_unwrap_native_token,
310 },
311 })
312 .accounts(
313 long_token_swap_path
314 .iter()
315 .enumerate()
316 .map(|(idx, mint)| AccountMeta {
317 pubkey: client.find_market_address(store, mint),
318 is_signer: false,
319 is_writable: idx == 0,
320 })
321 .chain(short_token_swap_path.iter().enumerate().map(|(idx, mint)| {
322 AccountMeta {
323 pubkey: client.find_market_address(store, mint),
324 is_signer: false,
325 is_writable: idx == 0,
326 }
327 }))
328 .collect::<Vec<_>>(),
329 );
330 let builder = prepare.merge(create);
331 Ok((builder, deposit))
332 }
333}
334
335pub struct CloseDepositBuilder<'a, C> {
337 client: &'a crate::Client<C>,
338 store: Pubkey,
339 deposit: Pubkey,
340 reason: String,
341 hint: Option<CloseDepositHint>,
342}
343
344#[derive(Debug, Clone, Copy)]
346pub struct CloseDepositHint {
347 owner: Pubkey,
348 receiver: Pubkey,
349 market_token: Pubkey,
350 market_token_account: Pubkey,
351 initial_long_token: Option<Pubkey>,
352 initial_short_token: Option<Pubkey>,
353 initial_long_token_account: Option<Pubkey>,
354 initial_short_token_account: Option<Pubkey>,
355 should_unwrap_native_token: bool,
356}
357
358impl CloseDepositHint {
359 pub fn new(deposit: &Deposit) -> Self {
361 Self {
362 owner: *deposit.header().owner(),
363 receiver: deposit.header().receiver(),
364 market_token: deposit.tokens().market_token(),
365 market_token_account: deposit.tokens().market_token_account(),
366 initial_long_token: deposit.tokens().initial_long_token.token(),
367 initial_short_token: deposit.tokens().initial_short_token.token(),
368 initial_long_token_account: deposit.tokens().initial_long_token.account(),
369 initial_short_token_account: deposit.tokens().initial_short_token.account(),
370 should_unwrap_native_token: deposit.header().should_unwrap_native_token(),
371 }
372 }
373}
374
375impl<'a, S, C> CloseDepositBuilder<'a, C>
376where
377 C: Deref<Target = S> + Clone,
378 S: Signer,
379{
380 pub(super) fn new(client: &'a crate::Client<C>, store: &Pubkey, deposit: &Pubkey) -> Self {
381 Self {
382 client,
383 store: *store,
384 deposit: *deposit,
385 reason: "cancelled".to_string(),
386 hint: None,
387 }
388 }
389
390 pub fn hint_with_deposit(&mut self, deposit: &Deposit) -> &mut Self {
392 self.hint(CloseDepositHint::new(deposit))
393 }
394
395 pub fn hint(&mut self, hint: CloseDepositHint) -> &mut Self {
397 self.hint = Some(hint);
398 self
399 }
400
401 pub fn reason(&mut self, reason: impl ToString) -> &mut Self {
403 self.reason = reason.to_string();
404 self
405 }
406
407 async fn get_or_fetch_deposit_info(&self) -> crate::Result<CloseDepositHint> {
408 match &self.hint {
409 Some(hint) => Ok(*hint),
410 None => {
411 let deposit = self.client.deposit(&self.deposit).await?;
412 Ok(CloseDepositHint::new(&deposit))
413 }
414 }
415 }
416
417 pub async fn build(&self) -> crate::Result<TransactionBuilder<'a, C>> {
419 let executor = self.client.payer();
420 let hint = self.get_or_fetch_deposit_info().await?;
421 let Self {
422 client,
423 store,
424 deposit,
425 ..
426 } = self;
427 let owner = hint.owner;
428 let receiver = hint.receiver;
429 let market_token_ata = get_associated_token_address(&receiver, &hint.market_token);
430 let should_unwrap_native_token = hint.should_unwrap_native_token;
431 let initial_long_token_ata = hint
432 .initial_long_token
433 .as_ref()
434 .map(|mint| get_ata_or_owner(&owner, mint, should_unwrap_native_token));
435 let initial_short_token_ata = hint
436 .initial_short_token
437 .as_ref()
438 .map(|mint| get_ata_or_owner(&owner, mint, should_unwrap_native_token));
439 Ok(client
440 .store_transaction()
441 .accounts(crate::utils::fix_optional_account_metas(
442 accounts::CloseDeposit {
443 executor,
444 store: *store,
445 store_wallet: client.find_store_wallet_address(store),
446 owner,
447 receiver,
448 market_token: hint.market_token,
449 initial_long_token: hint.initial_long_token,
450 initial_short_token: hint.initial_short_token,
451 deposit: *deposit,
452 market_token_escrow: hint.market_token_account,
453 initial_long_token_escrow: hint.initial_long_token_account,
454 initial_short_token_escrow: hint.initial_short_token_account,
455 market_token_ata,
456 initial_long_token_ata,
457 initial_short_token_ata,
458 associated_token_program: anchor_spl::associated_token::ID,
459 token_program: anchor_spl::token::ID,
460 system_program: system_program::ID,
461 event_authority: client.store_event_authority(),
462 program: *client.store_program_id(),
463 },
464 &gmsol_store::id(),
465 client.store_program_id(),
466 ))
467 .anchor_args(instruction::CloseDeposit {
468 reason: self.reason.clone(),
469 }))
470 }
471}
472
473pub struct ExecuteDepositBuilder<'a, C> {
475 client: &'a crate::Client<C>,
476 store: Pubkey,
477 oracle: Pubkey,
478 deposit: Pubkey,
479 execution_fee: u64,
480 feeds_parser: FeedsParser,
481 hint: Option<ExecuteDepositHint>,
482 token_map: Option<Pubkey>,
483 cancel_on_execution_error: bool,
484 close: bool,
485}
486
487#[derive(Clone, Debug)]
489pub struct ExecuteDepositHint {
490 owner: Pubkey,
491 receiver: Pubkey,
492 market_token_escrow: Pubkey,
493 market_token_mint: Pubkey,
494 pub feeds: TokensWithFeed,
496 swap: SwapActionParams,
497 initial_long_token_escrow: Option<Pubkey>,
498 initial_short_token_escrow: Option<Pubkey>,
499 initial_long_token: Option<Pubkey>,
500 initial_short_token: Option<Pubkey>,
501 should_unwrap_native_token: bool,
502}
503
504impl ExecuteDepositHint {
505 pub fn new(deposit: &Deposit, map: &impl TokenMapAccess) -> crate::Result<Self> {
507 Ok(Self {
508 owner: *deposit.header().owner(),
509 receiver: deposit.header().receiver(),
510 market_token_escrow: deposit.tokens().market_token_account(),
511 market_token_mint: deposit.tokens().market_token(),
512 feeds: deposit.swap().to_feeds(map)?,
513 swap: *deposit.swap(),
514 initial_long_token: deposit.tokens().initial_long_token.token(),
515 initial_short_token: deposit.tokens().initial_short_token.token(),
516 initial_long_token_escrow: deposit.tokens().initial_long_token.account(),
517 initial_short_token_escrow: deposit.tokens().initial_short_token.account(),
518 should_unwrap_native_token: deposit.header().should_unwrap_native_token(),
519 })
520 }
521}
522
523impl<'a, S, C> ExecuteDepositBuilder<'a, C>
524where
525 C: Deref<Target = S> + Clone,
526 S: Signer,
527{
528 pub(super) fn new(
529 client: &'a crate::Client<C>,
530 store: &Pubkey,
531 oracle: &Pubkey,
532 deposit: &Pubkey,
533 cancel_on_execution_error: bool,
534 ) -> Self {
535 Self {
536 client,
537 store: *store,
538 oracle: *oracle,
539 deposit: *deposit,
540 execution_fee: 0,
541 hint: None,
542 feeds_parser: Default::default(),
543 token_map: None,
544 cancel_on_execution_error,
545 close: true,
546 }
547 }
548
549 pub fn close(&mut self, close: bool) -> &mut Self {
551 self.close = close;
552 self
553 }
554
555 pub fn hint(
557 &mut self,
558 deposit: &Deposit,
559 map: &impl TokenMapAccess,
560 ) -> crate::Result<&mut Self> {
561 self.hint = Some(ExecuteDepositHint::new(deposit, map)?);
562 Ok(self)
563 }
564
565 #[cfg(feature = "pyth-pull-oracle")]
567 pub fn parse_with_pyth_price_updates(&mut self, price_updates: Prices) -> &mut Self {
568 self.feeds_parser.with_pyth_price_updates(price_updates);
569 self
570 }
571
572 pub async fn prepare_hint(&mut self) -> crate::Result<ExecuteDepositHint> {
574 match &self.hint {
575 Some(hint) => Ok(hint.clone()),
576 None => {
577 let map = self.client.authorized_token_map(&self.store).await?;
578 let deposit = self.client.deposit(&self.deposit).await?;
579 let hint = ExecuteDepositHint::new(&deposit, &map)?;
580 self.hint = Some(hint.clone());
581 Ok(hint)
582 }
583 }
584 }
585
586 async fn get_token_map(&self) -> crate::Result<Pubkey> {
587 if let Some(address) = self.token_map {
588 Ok(address)
589 } else {
590 Ok(self
591 .client
592 .authorized_token_map_address(&self.store)
593 .await?
594 .ok_or(crate::Error::NotFound)?)
595 }
596 }
597
598 pub fn token_map(&mut self, address: Pubkey) -> &mut Self {
600 self.token_map = Some(address);
601 self
602 }
603}
604
605impl<'a, C: Deref<Target = impl Signer> + Clone> MakeBundleBuilder<'a, C>
606 for ExecuteDepositBuilder<'a, C>
607{
608 async fn build_with_options(
609 &mut self,
610 options: BundleOptions,
611 ) -> gmsol_solana_utils::Result<BundleBuilder<'a, C>> {
612 let token_map = self
613 .get_token_map()
614 .await
615 .map_err(gmsol_solana_utils::Error::custom)?;
616 let hint = self
617 .prepare_hint()
618 .await
619 .map_err(gmsol_solana_utils::Error::custom)?;
620 let Self {
621 client,
622 store,
623 oracle,
624 deposit,
625 execution_fee,
626 cancel_on_execution_error,
627 ..
628 } = &self;
629 let authority = client.payer();
630 let feeds = self
631 .feeds_parser
632 .parse(&hint.feeds)
633 .collect::<Result<Vec<_>, _>>()
634 .map_err(gmsol_solana_utils::Error::custom)?;
635 let markets = hint
636 .swap
637 .unique_market_tokens_excluding_current(&hint.market_token_mint)
638 .map(|mint| AccountMeta {
639 pubkey: client.find_market_address(store, mint),
640 is_signer: false,
641 is_writable: true,
642 });
643
644 let execute = client
646 .store_transaction()
647 .accounts(crate::utils::fix_optional_account_metas(
648 accounts::ExecuteDeposit {
649 authority,
650 store: *store,
651 oracle: *oracle,
652 token_map,
653 deposit: *deposit,
654 market: client.find_market_address(store, &hint.market_token_mint),
655 market_token: hint.market_token_mint,
656 initial_long_token_vault: hint
657 .initial_long_token
658 .as_ref()
659 .map(|token| client.find_market_vault_address(store, token)),
660 initial_short_token_vault: hint
661 .initial_short_token
662 .as_ref()
663 .map(|token| client.find_market_vault_address(store, token)),
664 token_program: anchor_spl::token::ID,
665 system_program: system_program::ID,
666 initial_long_token: hint.initial_long_token,
667 initial_short_token: hint.initial_short_token,
668 market_token_escrow: hint.market_token_escrow,
669 initial_long_token_escrow: hint.initial_long_token_escrow,
670 initial_short_token_escrow: hint.initial_short_token_escrow,
671 chainlink_program: None,
672 event_authority: client.store_event_authority(),
673 program: *client.store_program_id(),
674 },
675 &gmsol_store::ID,
676 self.client.store_program_id(),
677 ))
678 .anchor_args(instruction::ExecuteDeposit {
679 execution_fee: *execution_fee,
680 throw_on_execution_error: !*cancel_on_execution_error,
681 })
682 .accounts(feeds.into_iter().chain(markets).collect::<Vec<_>>())
683 .compute_budget(ComputeBudget::default().with_limit(EXECUTE_DEPOSIT_COMPUTE_BUDGET));
684
685 let rpc = if self.close {
686 let close = self
687 .client
688 .close_deposit(store, deposit)
689 .hint(CloseDepositHint {
690 owner: hint.owner,
691 receiver: hint.receiver,
692 market_token: hint.market_token_mint,
693 market_token_account: hint.market_token_escrow,
694 initial_long_token: hint.initial_long_token,
695 initial_short_token: hint.initial_short_token,
696 initial_long_token_account: hint.initial_long_token_escrow,
697 initial_short_token_account: hint.initial_short_token_escrow,
698 should_unwrap_native_token: hint.should_unwrap_native_token,
699 })
700 .reason("executed")
701 .build()
702 .await
703 .map_err(gmsol_solana_utils::Error::custom)?;
704 execute.merge(close)
705 } else {
706 execute
707 };
708
709 let mut tx = self.client.bundle_with_options(options);
710
711 tx.try_push(rpc)?;
712
713 Ok(tx)
714 }
715}
716
717impl<C: Deref<Target = impl Signer> + Clone> PullOraclePriceConsumer
718 for ExecuteDepositBuilder<'_, C>
719{
720 async fn feed_ids(&mut self) -> crate::Result<FeedIds> {
721 let hint = self.prepare_hint().await?;
722 Ok(FeedIds::new(self.store, hint.feeds))
723 }
724
725 fn process_feeds(
726 &mut self,
727 provider: PriceProviderKind,
728 map: FeedAddressMap,
729 ) -> crate::Result<()> {
730 self.feeds_parser
731 .insert_pull_oracle_feed_parser(provider, map);
732 Ok(())
733 }
734}
735
736impl<C> SetExecutionFee for ExecuteDepositBuilder<'_, C> {
737 fn set_execution_fee(&mut self, lamports: u64) -> &mut Self {
738 self.execution_fee = lamports;
739 self
740 }
741}