1use std::{collections::BTreeSet, ops::Deref};
2
3use anchor_spl::associated_token::get_associated_token_address_with_program_id;
4use gmsol_programs::gmsol_store::{
5 accounts::Glv,
6 client::{accounts, args},
7 types::UpdateGlvParams,
8};
9use gmsol_solana_utils::{
10 make_bundle_builder::MakeBundleBuilder, transaction_builder::TransactionBuilder,
11};
12use gmsol_utils::{
13 glv::GlvMarketFlag, oracle::PriceProviderKind, swap::SwapActionParams,
14 token_config::TokensWithFeed,
15};
16use solana_sdk::{pubkey::Pubkey, signer::Signer, system_program};
17
18use crate::{
19 client::{
20 feeds_parser::{FeedAddressMap, FeedsParser},
21 pull_oracle::{FeedIds, PullOraclePriceConsumer},
22 },
23 utils::{glv::split_to_accounts, zero_copy::ZeroCopy},
24 Client,
25};
26
27const DEFAULT_MAX_AGE: u32 = 120;
28
29pub trait GlvOps<C> {
31 fn initialize_glv(
33 &self,
34 store: &Pubkey,
35 index: u16,
36 market_tokens: impl IntoIterator<Item = Pubkey>,
37 ) -> crate::Result<(TransactionBuilder<C>, Pubkey)>;
38
39 fn update_glv_market_config(
41 &self,
42 store: &Pubkey,
43 glv_token: &Pubkey,
44 market_token: &Pubkey,
45 max_amount: Option<u64>,
46 max_value: Option<u128>,
47 ) -> TransactionBuilder<C>;
48
49 fn toggle_glv_market_flag(
51 &self,
52 store: &Pubkey,
53 glv_token: &Pubkey,
54 market_token: &Pubkey,
55 flag: GlvMarketFlag,
56 enable: bool,
57 ) -> TransactionBuilder<C>;
58
59 fn update_glv_config(
61 &self,
62 store: &Pubkey,
63 glv_token: &Pubkey,
64 params: UpdateGlvParams,
65 ) -> TransactionBuilder<C>;
66
67 fn insert_glv_market(
69 &self,
70 store: &Pubkey,
71 glv_token: &Pubkey,
72 market_token: &Pubkey,
73 token_program_id: Option<&Pubkey>,
74 ) -> TransactionBuilder<C>;
75
76 fn remove_glv_market(
78 &self,
79 store: &Pubkey,
80 glv_token: &Pubkey,
81 market_token: &Pubkey,
82 token_program_id: Option<&Pubkey>,
83 ) -> TransactionBuilder<C>;
84
85 fn get_glv_token_value(
87 &self,
88 store: &Pubkey,
89 oracle: &Pubkey,
90 glv_token: &Pubkey,
91 amount: u64,
92 ) -> GetGlvTokenValueBuilder<'_, C>;
93}
94
95impl<C: Deref<Target = impl Signer> + Clone> GlvOps<C> for crate::Client<C> {
96 fn initialize_glv(
97 &self,
98 store: &Pubkey,
99 index: u16,
100 market_tokens: impl IntoIterator<Item = Pubkey>,
101 ) -> crate::Result<(TransactionBuilder<C>, Pubkey)> {
102 let authority = self.payer();
103 let glv_token = self.find_glv_token_address(store, index);
104 let glv = self.find_glv_address(&glv_token);
105 let market_token_program_id = anchor_spl::token::ID;
106
107 let (accounts, length) = split_to_accounts(
108 market_tokens,
109 &glv,
110 store,
111 self.store_program_id(),
112 &market_token_program_id,
113 true,
114 );
115
116 let rpc = self
117 .store_transaction()
118 .anchor_accounts(accounts::InitializeGlv {
119 authority,
120 store: *store,
121 glv_token,
122 glv,
123 system_program: system_program::ID,
124 token_program: anchor_spl::token_2022::ID,
125 market_token_program: market_token_program_id,
126 associated_token_program: anchor_spl::associated_token::ID,
127 })
128 .anchor_args(args::InitializeGlv {
129 index,
130 length: length
131 .try_into()
132 .map_err(|_| crate::Error::custom("too many markets"))?,
133 })
134 .accounts(accounts);
135 Ok((rpc, glv_token))
136 }
137
138 fn update_glv_market_config(
139 &self,
140 store: &Pubkey,
141 glv_token: &Pubkey,
142 market_token: &Pubkey,
143 max_amount: Option<u64>,
144 max_value: Option<u128>,
145 ) -> TransactionBuilder<C> {
146 let glv = self.find_glv_address(glv_token);
147 self.store_transaction()
148 .anchor_accounts(accounts::UpdateGlvMarketConfig {
149 authority: self.payer(),
150 store: *store,
151 glv,
152 market_token: *market_token,
153 })
154 .anchor_args(args::UpdateGlvMarketConfig {
155 max_amount,
156 max_value,
157 })
158 }
159
160 fn toggle_glv_market_flag(
161 &self,
162 store: &Pubkey,
163 glv_token: &Pubkey,
164 market_token: &Pubkey,
165 flag: GlvMarketFlag,
166 enable: bool,
167 ) -> TransactionBuilder<C> {
168 let glv = self.find_glv_address(glv_token);
169 self.store_transaction()
170 .anchor_accounts(accounts::UpdateGlvMarketConfig {
171 authority: self.payer(),
172 store: *store,
173 glv,
174 market_token: *market_token,
175 })
176 .anchor_args(args::ToggleGlvMarketFlag {
177 flag: flag.to_string(),
178 enable,
179 })
180 }
181
182 fn update_glv_config(
183 &self,
184 store: &Pubkey,
185 glv_token: &Pubkey,
186 params: UpdateGlvParams,
187 ) -> TransactionBuilder<C> {
188 let glv = self.find_glv_address(glv_token);
189 self.store_transaction()
190 .anchor_accounts(accounts::UpdateGlvConfig {
191 authority: self.payer(),
192 store: *store,
193 glv,
194 })
195 .anchor_args(args::UpdateGlvConfig { params })
196 }
197
198 fn insert_glv_market(
199 &self,
200 store: &Pubkey,
201 glv_token: &Pubkey,
202 market_token: &Pubkey,
203 token_program_id: Option<&Pubkey>,
204 ) -> TransactionBuilder<C> {
205 let token_program_id = token_program_id.unwrap_or(&anchor_spl::token::ID);
206 let glv = self.find_glv_address(glv_token);
207 let market = self.find_market_address(store, market_token);
208 let vault =
209 get_associated_token_address_with_program_id(&glv, market_token, token_program_id);
210 self.store_transaction()
211 .anchor_accounts(accounts::InsertGlvMarket {
212 authority: self.payer(),
213 store: *store,
214 glv,
215 market_token: *market_token,
216 market,
217 vault,
218 system_program: system_program::ID,
219 token_program: *token_program_id,
220 associated_token_program: anchor_spl::associated_token::ID,
221 })
222 .anchor_args(args::InsertGlvMarket {})
223 }
224
225 fn remove_glv_market(
226 &self,
227 store: &Pubkey,
228 glv_token: &Pubkey,
229 market_token: &Pubkey,
230 token_program_id: Option<&Pubkey>,
231 ) -> TransactionBuilder<C> {
232 let token_program_id = token_program_id.unwrap_or(&anchor_spl::token::ID);
233 let glv = self.find_glv_address(glv_token);
234 let vault =
235 get_associated_token_address_with_program_id(&glv, market_token, token_program_id);
236 let store_wallet = self.find_store_wallet_address(store);
237 let store_wallet_ata = get_associated_token_address_with_program_id(
238 &store_wallet,
239 market_token,
240 token_program_id,
241 );
242 self.store_transaction()
243 .anchor_accounts(accounts::RemoveGlvMarket {
244 authority: self.payer(),
245 store: *store,
246 store_wallet,
247 glv,
248 market_token: *market_token,
249 vault,
250 store_wallet_ata,
251 token_program: *token_program_id,
252 associated_token_program: anchor_spl::associated_token::ID,
253 system_program: system_program::ID,
254 })
255 .anchor_args(args::RemoveGlvMarket {})
256 }
257
258 fn get_glv_token_value(
259 &self,
260 store: &Pubkey,
261 oracle: &Pubkey,
262 glv_token: &Pubkey,
263 amount: u64,
264 ) -> GetGlvTokenValueBuilder<'_, C> {
265 GetGlvTokenValueBuilder::new(self, *store, *oracle, *glv_token, amount)
266 }
267}
268
269pub struct GetGlvTokenValueBuilder<'a, C> {
271 client: &'a Client<C>,
272 store: Pubkey,
273 oracle: Pubkey,
274 glv_token: Pubkey,
275 amount: u64,
276 maximize: bool,
277 max_age: u32,
278 emit_event: bool,
279 feeds_parser: FeedsParser,
280 hint: Option<GetGlvTokenValueHint>,
281}
282
283#[derive(Debug, Clone)]
285pub struct GetGlvTokenValueHint {
286 pub token_map: Pubkey,
288 pub glv_market_tokens: BTreeSet<Pubkey>,
290 pub feeds: TokensWithFeed,
292}
293
294impl<C> GetGlvTokenValueBuilder<'_, C> {
295 pub fn maximize(&mut self, maximize: bool) -> &mut Self {
297 self.maximize = maximize;
298 self
299 }
300
301 pub fn max_age(&mut self, max_age: u32) -> &mut Self {
303 self.max_age = max_age;
304 self
305 }
306
307 pub fn emit_event(&mut self, emit: bool) -> &mut Self {
309 self.emit_event = emit;
310 self
311 }
312
313 pub fn hint(&mut self, hint: Option<GetGlvTokenValueHint>) -> &mut Self {
315 self.hint = hint;
316 self
317 }
318}
319
320impl<'a, C: Deref<Target = impl Signer> + Clone> GetGlvTokenValueBuilder<'a, C> {
321 fn new(
322 client: &'a Client<C>,
323 store: Pubkey,
324 oracle: Pubkey,
325 glv_token: Pubkey,
326 amount: u64,
327 ) -> Self {
328 Self {
329 client,
330 store,
331 oracle,
332 glv_token,
333 amount,
334 maximize: false,
335 max_age: DEFAULT_MAX_AGE,
336 emit_event: true,
337 feeds_parser: Default::default(),
338 hint: None,
339 }
340 }
341
342 pub async fn prepare_hint(&mut self) -> crate::Result<GetGlvTokenValueHint> {
344 if let Some(hint) = self.hint.as_ref() {
345 return Ok(hint.clone());
346 }
347
348 let store = self.client.store(&self.store).await?;
349 let token_map_address = store.token_map;
350 let token_map = self.client.token_map(&token_map_address).await?;
351
352 let glv = self.client.find_glv_address(&self.glv_token);
353 let glv = self
354 .client
355 .account::<ZeroCopy<Glv>>(&glv)
356 .await?
357 .ok_or(crate::Error::NotFound)?
358 .0;
359
360 let mut collector = glv.tokens_collector(None::<&SwapActionParams>);
361 for token in glv.market_tokens() {
362 let market = self.client.market_by_token(&self.store, &token).await?;
363 collector.insert_token(&market.meta.index_token_mint);
364 }
365
366 let glv_market_tokens = glv.market_tokens().collect();
367 let hint = GetGlvTokenValueHint {
368 token_map: token_map_address,
369 glv_market_tokens,
370 feeds: collector
371 .to_feeds(&token_map)
372 .map_err(crate::Error::custom)?,
373 };
374 self.hint = Some(hint.clone());
375 Ok(hint)
376 }
377
378 async fn build_txn(&mut self) -> crate::Result<TransactionBuilder<'a, C>> {
379 let token_program_id = anchor_spl::token::ID;
380 let hint = self.prepare_hint().await?;
381 let Self {
382 client,
383 store,
384 oracle,
385 glv_token,
386 amount,
387 maximize,
388 max_age,
389 feeds_parser,
390 emit_event,
391 ..
392 } = self;
393 let authority = client.payer();
394 let feeds = feeds_parser
395 .parse(&hint.feeds)
396 .collect::<Result<Vec<_>, _>>()?;
397 let glv = client.find_glv_address(glv_token);
398 let glv_accounts = split_to_accounts(
399 hint.glv_market_tokens.iter().copied(),
400 &glv,
401 store,
402 client.store_program_id(),
403 &token_program_id,
404 false,
405 )
406 .0;
407 let txn = client
408 .store_transaction()
409 .anchor_args(args::GetGlvTokenValue {
410 amount: *amount,
411 maximize: *maximize,
412 max_age: *max_age,
413 emit_event: *emit_event,
414 })
415 .anchor_accounts(accounts::GetGlvTokenValue {
416 authority,
417 store: *store,
418 token_map: hint.token_map,
419 oracle: *oracle,
420 glv,
421 glv_token: *glv_token,
422 event_authority: client.store_event_authority(),
423 program: *client.store_program_id(),
424 })
425 .accounts(glv_accounts)
426 .accounts(feeds);
427 Ok(txn)
428 }
429}
430
431impl<'a, C: Deref<Target = impl Signer> + Clone> MakeBundleBuilder<'a, C>
432 for GetGlvTokenValueBuilder<'a, C>
433{
434 async fn build_with_options(
435 &mut self,
436 options: gmsol_solana_utils::bundle_builder::BundleOptions,
437 ) -> gmsol_solana_utils::Result<gmsol_solana_utils::bundle_builder::BundleBuilder<'a, C>> {
438 let mut tx = self.client.bundle_with_options(options);
439
440 tx.try_push(
441 self.build_txn()
442 .await
443 .map_err(gmsol_solana_utils::Error::custom)?,
444 )?;
445
446 Ok(tx)
447 }
448}
449
450impl<C: Deref<Target = impl Signer> + Clone> PullOraclePriceConsumer
451 for GetGlvTokenValueBuilder<'_, C>
452{
453 async fn feed_ids(&mut self) -> crate::Result<FeedIds> {
454 let hint = self.prepare_hint().await?;
455 Ok(FeedIds::new(self.store, hint.feeds))
456 }
457
458 fn process_feeds(
459 &mut self,
460 provider: PriceProviderKind,
461 map: FeedAddressMap,
462 ) -> crate::Result<()> {
463 self.feeds_parser
464 .insert_pull_oracle_feed_parser(provider, map);
465 Ok(())
466 }
467}