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