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