1use std::ops::Deref;
2
3use anchor_client::{
4 anchor_lang::system_program,
5 solana_sdk::{pubkey::Pubkey, signer::Signer},
6};
7use anchor_spl::associated_token::get_associated_token_address;
8use gmsol_solana_utils::{
9 bundle_builder::{BundleBuilder, BundleOptions},
10 transaction_builder::TransactionBuilder,
11};
12use gmsol_store::{
13 accounts, instruction,
14 ops::shift::CreateShiftParams,
15 states::{
16 common::{action::Action, TokensWithFeed},
17 HasMarketMeta, NonceBytes, PriceProviderKind, Shift, Store, TokenMapAccess,
18 },
19};
20use gmsol_utils::market::ordered_tokens;
21
22use crate::{
23 exchange::generate_nonce,
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::ExchangeOps;
34
35pub struct CreateShiftBuilder<'a, C> {
37 client: &'a crate::Client<C>,
38 store: Pubkey,
39 from_market_token: Pubkey,
40 to_market_token: Pubkey,
41 execution_fee: u64,
42 amount: u64,
43 min_to_market_token_amount: u64,
44 nonce: Option<NonceBytes>,
45 hint: CreateShiftHint,
46 receiver: Pubkey,
47}
48
49#[derive(Default)]
51pub struct CreateShiftHint {
52 from_market_token_source: Option<Pubkey>,
53}
54
55impl<'a, C: Deref<Target = impl Signer> + Clone> CreateShiftBuilder<'a, C> {
56 pub(super) fn new(
57 client: &'a crate::Client<C>,
58 store: &Pubkey,
59 from_market_token: &Pubkey,
60 to_market_token: &Pubkey,
61 amount: u64,
62 ) -> Self {
63 Self {
64 client,
65 store: *store,
66 from_market_token: *from_market_token,
67 to_market_token: *to_market_token,
68 execution_fee: Shift::MIN_EXECUTION_LAMPORTS,
69 amount,
70 min_to_market_token_amount: 0,
71 nonce: None,
72 hint: Default::default(),
73 receiver: client.payer(),
74 }
75 }
76
77 pub fn nonce(&mut self, nonce: NonceBytes) -> &mut Self {
79 self.nonce = Some(nonce);
80 self
81 }
82
83 pub fn execution_fee(&mut self, amount: u64) -> &mut Self {
85 self.execution_fee = amount;
86 self
87 }
88
89 pub fn min_to_market_token_amount(&mut self, amount: u64) -> &mut Self {
91 self.min_to_market_token_amount = amount;
92 self
93 }
94
95 pub fn hint(&mut self, hint: CreateShiftHint) -> &mut Self {
97 self.hint = hint;
98 self
99 }
100
101 pub fn receiver(&mut self, receiver: Pubkey) -> &mut Self {
104 self.receiver = receiver;
105 self
106 }
107
108 fn get_from_market_token_source(&self) -> Pubkey {
109 match self.hint.from_market_token_source {
110 Some(address) => address,
111 None => get_associated_token_address(&self.client.payer(), &self.from_market_token),
112 }
113 }
114
115 fn get_create_shift_params(&self) -> CreateShiftParams {
116 CreateShiftParams {
117 execution_lamports: self.execution_fee,
118 from_market_token_amount: self.amount,
119 min_to_market_token_amount: self.min_to_market_token_amount,
120 }
121 }
122
123 pub fn build_with_address(&self) -> crate::Result<(TransactionBuilder<'a, C>, Pubkey)> {
125 let token_program_id = anchor_spl::token::ID;
126
127 let owner = self.client.payer();
128 let receiver = self.receiver;
129 let nonce = self.nonce.unwrap_or_else(generate_nonce);
130 let shift = self.client.find_shift_address(&self.store, &owner, &nonce);
131
132 let from_market = self
133 .client
134 .find_market_address(&self.store, &self.from_market_token);
135 let to_market = self
136 .client
137 .find_market_address(&self.store, &self.to_market_token);
138
139 let from_market_token_escrow =
140 get_associated_token_address(&shift, &self.from_market_token);
141 let to_market_token_escrow = get_associated_token_address(&shift, &self.to_market_token);
142 let to_market_token_ata = get_associated_token_address(&receiver, &self.to_market_token);
143
144 let prepare_escrow = self
145 .client
146 .prepare_associated_token_account(
147 &self.from_market_token,
148 &token_program_id,
149 Some(&shift),
150 )
151 .merge(self.client.prepare_associated_token_account(
152 &self.to_market_token,
153 &token_program_id,
154 Some(&shift),
155 ));
156
157 let prepare_ata = self.client.prepare_associated_token_account(
158 &self.to_market_token,
159 &token_program_id,
160 Some(&receiver),
161 );
162
163 let rpc = self
164 .client
165 .store_transaction()
166 .anchor_accounts(accounts::CreateShift {
167 owner,
168 receiver,
169 store: self.store,
170 from_market,
171 to_market,
172 shift,
173 from_market_token: self.from_market_token,
174 to_market_token: self.to_market_token,
175 from_market_token_escrow,
176 to_market_token_escrow,
177 from_market_token_source: self.get_from_market_token_source(),
178 to_market_token_ata,
179 system_program: system_program::ID,
180 token_program: token_program_id,
181 associated_token_program: anchor_spl::associated_token::ID,
182 })
183 .anchor_args(instruction::CreateShift {
184 nonce,
185 params: self.get_create_shift_params(),
186 });
187
188 Ok((prepare_escrow.merge(prepare_ata).merge(rpc), shift))
189 }
190}
191
192pub struct CloseShiftBuilder<'a, C> {
194 client: &'a crate::Client<C>,
195 shift: Pubkey,
196 reason: String,
197 hint: Option<CloseShiftHint>,
198}
199
200#[derive(Clone)]
202pub struct CloseShiftHint {
203 store: Pubkey,
204 owner: Pubkey,
205 receiver: Pubkey,
206 from_market_token: Pubkey,
207 to_market_token: Pubkey,
208 from_market_token_escrow: Pubkey,
209 to_market_token_escrow: Pubkey,
210}
211
212impl CloseShiftHint {
213 pub fn new(shift: &Shift) -> crate::Result<Self> {
215 let tokens = shift.tokens();
216 Ok(Self {
217 store: *shift.header().store(),
218 owner: *shift.header().owner(),
219 receiver: shift.header().receiver(),
220 from_market_token: tokens.from_market_token(),
221 from_market_token_escrow: tokens.from_market_token_account(),
222 to_market_token: tokens.to_market_token(),
223 to_market_token_escrow: tokens.to_market_token_account(),
224 })
225 }
226
227 #[allow(clippy::wrong_self_convention)]
228 fn from_market_token_ata(&self) -> Pubkey {
229 get_associated_token_address(&self.owner, &self.from_market_token)
230 }
231
232 fn to_market_token_ata(&self) -> Pubkey {
233 get_associated_token_address(&self.receiver, &self.to_market_token)
234 }
235}
236
237impl<'a, C: Deref<Target = impl Signer> + Clone> CloseShiftBuilder<'a, C> {
238 pub(super) fn new(client: &'a crate::Client<C>, shift: &Pubkey) -> Self {
239 Self {
240 client,
241 shift: *shift,
242 hint: None,
243 reason: String::from("cancelled"),
244 }
245 }
246
247 pub fn hint(&mut self, hint: CloseShiftHint) -> &mut Self {
249 self.hint = Some(hint);
250 self
251 }
252
253 pub fn reason(&mut self, reason: impl ToString) -> &mut Self {
255 self.reason = reason.to_string();
256 self
257 }
258
259 pub async fn prepare_hint(&mut self) -> crate::Result<CloseShiftHint> {
261 let hint = match &self.hint {
262 Some(hint) => hint.clone(),
263 None => {
264 let shift = self
265 .client
266 .account::<ZeroCopy<_>>(&self.shift)
267 .await?
268 .ok_or(crate::Error::NotFound)?;
269 let hint = CloseShiftHint::new(&shift.0)?;
270 self.hint = Some(hint.clone());
271 hint
272 }
273 };
274
275 Ok(hint)
276 }
277
278 pub async fn build(&mut self) -> crate::Result<TransactionBuilder<'a, C>> {
280 let hint = self.prepare_hint().await?;
281 let executor = self.client.payer();
282 let rpc = self
283 .client
284 .store_transaction()
285 .anchor_accounts(accounts::CloseShift {
286 executor,
287 store: hint.store,
288 store_wallet: self.client.find_store_wallet_address(&hint.store),
289 owner: hint.owner,
290 receiver: hint.receiver,
291 shift: self.shift,
292 from_market_token: hint.from_market_token,
293 to_market_token: hint.to_market_token,
294 from_market_token_escrow: hint.from_market_token_escrow,
295 to_market_token_escrow: hint.to_market_token_escrow,
296 from_market_token_ata: hint.from_market_token_ata(),
297 to_market_token_ata: hint.to_market_token_ata(),
298 system_program: system_program::ID,
299 token_program: anchor_spl::token::ID,
300 associated_token_program: anchor_spl::associated_token::ID,
301 event_authority: self.client.store_event_authority(),
302 program: *self.client.store_program_id(),
303 })
304 .anchor_args(instruction::CloseShift {
305 reason: self.reason.clone(),
306 });
307
308 Ok(rpc)
309 }
310}
311
312pub struct ExecuteShiftBuilder<'a, C> {
314 client: &'a crate::Client<C>,
315 shift: Pubkey,
316 execution_fee: u64,
317 cancel_on_execution_error: bool,
318 oracle: Pubkey,
319 hint: Option<ExecuteShiftHint>,
320 close: bool,
321 feeds_parser: FeedsParser,
322}
323
324#[derive(Clone)]
326pub struct ExecuteShiftHint {
327 store: Pubkey,
328 token_map: Pubkey,
329 owner: Pubkey,
330 receiver: Pubkey,
331 from_market_token: Pubkey,
332 to_market_token: Pubkey,
333 from_market_token_escrow: Pubkey,
334 to_market_token_escrow: Pubkey,
335 pub feeds: TokensWithFeed,
337}
338
339impl ExecuteShiftHint {
340 pub fn new(
342 shift: &Shift,
343 store: &Store,
344 map: &impl TokenMapAccess,
345 from_market: &impl HasMarketMeta,
346 to_market: &impl HasMarketMeta,
347 ) -> crate::Result<Self> {
348 use gmsol_store::states::common::token_with_feeds::token_records;
349
350 let token_infos = shift.tokens();
351
352 let ordered_tokens = ordered_tokens(from_market, to_market);
353 let token_records = token_records(map, &ordered_tokens)?;
354 let feeds = TokensWithFeed::try_from_records(token_records)?;
355
356 Ok(Self {
357 store: *shift.header().store(),
358 owner: *shift.header().owner(),
359 receiver: shift.header().receiver(),
360 from_market_token: token_infos.from_market_token(),
361 from_market_token_escrow: token_infos.from_market_token_account(),
362 to_market_token: token_infos.to_market_token(),
363 to_market_token_escrow: token_infos.to_market_token_account(),
364 token_map: *store.token_map().ok_or(crate::Error::NotFound)?,
365 feeds,
366 })
367 }
368}
369
370impl<'a, C: Deref<Target = impl Signer> + Clone> ExecuteShiftBuilder<'a, C> {
371 pub(super) fn new(
372 client: &'a crate::Client<C>,
373 oracle: &Pubkey,
374 shift: &Pubkey,
375 cancel_on_execution_error: bool,
376 ) -> Self {
377 Self {
378 client,
379 shift: *shift,
380 hint: None,
381 execution_fee: 0,
382 cancel_on_execution_error,
383 oracle: *oracle,
384 close: true,
385 feeds_parser: Default::default(),
386 }
387 }
388
389 pub fn hint(&mut self, hint: ExecuteShiftHint) -> &mut Self {
391 self.hint = Some(hint);
392 self
393 }
394
395 pub fn cancel_on_execution_error(&mut self, cancel: bool) -> &mut Self {
397 self.cancel_on_execution_error = cancel;
398 self
399 }
400
401 pub fn close(&mut self, close: bool) -> &mut Self {
403 self.close = close;
404 self
405 }
406
407 async fn prepare_hint(&mut self) -> crate::Result<ExecuteShiftHint> {
408 let hint = match &self.hint {
409 Some(hint) => hint.clone(),
410 None => {
411 let shift = self
412 .client
413 .account::<ZeroCopy<Shift>>(&self.shift)
414 .await?
415 .ok_or(crate::Error::NotFound)?;
416 let store = self.client.store(shift.0.header().store()).await?;
417 let token_map_address = store
418 .token_map()
419 .ok_or(crate::Error::invalid_argument("token map is not set"))?;
420 let token_map = self.client.token_map(token_map_address).await?;
421 let from_market_token = shift.0.tokens().from_market_token();
422 let to_market_token = shift.0.tokens().to_market_token();
423 let from_market = self
424 .client
425 .find_market_address(shift.0.header().store(), &from_market_token);
426 let from_market = self.client.market(&from_market).await?;
427 let to_market = self
428 .client
429 .find_market_address(shift.0.header().store(), &to_market_token);
430 let to_market = self.client.market(&to_market).await?;
431 let hint = ExecuteShiftHint::new(
432 &shift.0,
433 &store,
434 &token_map,
435 &*from_market,
436 &*to_market,
437 )?;
438 self.hint = Some(hint.clone());
439 hint
440 }
441 };
442
443 Ok(hint)
444 }
445
446 async fn build_rpc(&mut self) -> crate::Result<TransactionBuilder<'a, C>> {
448 let hint = self.prepare_hint().await?;
449 let authority = self.client.payer();
450
451 let from_market = self
452 .client
453 .find_market_address(&hint.store, &hint.from_market_token);
454 let to_market = self
455 .client
456 .find_market_address(&hint.store, &hint.to_market_token);
457 let from_market_token_vault = self
458 .client
459 .find_market_vault_address(&hint.store, &hint.from_market_token);
460
461 let feeds = self.feeds_parser.parse_and_sort_by_tokens(&hint.feeds)?;
462
463 let mut rpc = self
464 .client
465 .store_transaction()
466 .accounts(fix_optional_account_metas(
467 accounts::ExecuteShift {
468 authority,
469 store: hint.store,
470 token_map: hint.token_map,
471 oracle: self.oracle,
472 from_market,
473 to_market,
474 shift: self.shift,
475 from_market_token: hint.from_market_token,
476 to_market_token: hint.to_market_token,
477 from_market_token_escrow: hint.from_market_token_escrow,
478 to_market_token_escrow: hint.to_market_token_escrow,
479 from_market_token_vault,
480 token_program: anchor_spl::token::ID,
481 chainlink_program: None,
482 event_authority: self.client.store_event_authority(),
483 program: *self.client.store_program_id(),
484 },
485 &crate::program_ids::DEFAULT_GMSOL_STORE_ID,
486 self.client.store_program_id(),
487 ))
488 .anchor_args(instruction::ExecuteShift {
489 execution_lamports: self.execution_fee,
490 throw_on_execution_error: !self.cancel_on_execution_error,
491 })
492 .accounts(feeds);
493
494 if self.close {
495 let close = self
496 .client
497 .close_shift(&self.shift)
498 .hint(CloseShiftHint {
499 store: hint.store,
500 owner: hint.owner,
501 receiver: hint.receiver,
502 from_market_token: hint.from_market_token,
503 to_market_token: hint.to_market_token,
504 from_market_token_escrow: hint.from_market_token_escrow,
505 to_market_token_escrow: hint.to_market_token_escrow,
506 })
507 .reason("executed")
508 .build()
509 .await?;
510 rpc = rpc.merge(close);
511 }
512
513 Ok(rpc)
514 }
515}
516
517impl<'a, C: Deref<Target = impl Signer> + Clone> MakeBundleBuilder<'a, C>
518 for ExecuteShiftBuilder<'a, C>
519{
520 async fn build_with_options(
521 &mut self,
522 options: BundleOptions,
523 ) -> gmsol_solana_utils::Result<BundleBuilder<'a, C>> {
524 let mut tx = self.client.bundle_with_options(options);
525 tx.try_push(
526 self.build_rpc()
527 .await
528 .map_err(gmsol_solana_utils::Error::custom)?,
529 )?;
530 Ok(tx)
531 }
532}
533
534impl<C: Deref<Target = impl Signer> + Clone> PullOraclePriceConsumer
535 for ExecuteShiftBuilder<'_, C>
536{
537 async fn feed_ids(&mut self) -> crate::Result<FeedIds> {
538 let hint = self.prepare_hint().await?;
539 Ok(FeedIds::new(hint.store, hint.feeds))
540 }
541
542 fn process_feeds(
543 &mut self,
544 provider: PriceProviderKind,
545 map: FeedAddressMap,
546 ) -> crate::Result<()> {
547 self.feeds_parser
548 .insert_pull_oracle_feed_parser(provider, map);
549 Ok(())
550 }
551}
552
553impl<C> SetExecutionFee for ExecuteShiftBuilder<'_, C> {
554 fn set_execution_fee(&mut self, lamports: u64) -> &mut Self {
555 self.execution_fee = lamports;
556 self
557 }
558}