gmsol_sdk/client/ops/
glv.rs

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
29/// GLV operations.
30pub trait GlvOps<C> {
31    /// Initialize GLV.
32    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    /// GLV Update Market Config.
40    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    /// GLV toggle market flag.
50    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    /// Update GLV config.
60    fn update_glv_config(
61        &self,
62        store: &Pubkey,
63        glv_token: &Pubkey,
64        params: UpdateGlvParams,
65    ) -> TransactionBuilder<C>;
66
67    /// Insert GLV market.
68    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    /// Remove GLV market.
77    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    /// Get glv token value.
86    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
269/// Builder for `get_glv_token_value` instruction.
270pub 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/// Hint for [`GetGlvTokenValueBuilder`].
284#[derive(Debug, Clone)]
285pub struct GetGlvTokenValueHint {
286    /// Token map address.
287    pub token_map: Pubkey,
288    /// Market token mints in GLV.
289    pub glv_market_tokens: BTreeSet<Pubkey>,
290    /// Feeds.
291    pub feeds: TokensWithFeed,
292}
293
294impl<C> GetGlvTokenValueBuilder<'_, C> {
295    /// Set whether to maximize the computed value. Defaults to `false`.
296    pub fn maximize(&mut self, maximize: bool) -> &mut Self {
297        self.maximize = maximize;
298        self
299    }
300
301    /// Set max age (seconds). Defaults to `120`.
302    pub fn max_age(&mut self, max_age: u32) -> &mut Self {
303        self.max_age = max_age;
304        self
305    }
306
307    /// Set whether to emit event. Defaults to `true`
308    pub fn emit_event(&mut self, emit: bool) -> &mut Self {
309        self.emit_event = emit;
310        self
311    }
312
313    /// Set hint.
314    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    /// Prepare hint.
343    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}