light_client/rpc/
client.rs

1use std::{
2    fmt::{Debug, Display, Formatter},
3    time::Duration,
4};
5
6use async_trait::async_trait;
7use borsh::BorshDeserialize;
8use bs58;
9use light_compressed_account::TreeType;
10use light_event::{
11    event::{BatchPublicTransactionEvent, PublicTransactionEvent},
12    parse::event_from_light_transaction,
13};
14use solana_account::Account;
15use solana_clock::Slot;
16use solana_commitment_config::CommitmentConfig;
17use solana_hash::Hash;
18use solana_instruction::Instruction;
19use solana_keypair::Keypair;
20use solana_pubkey::{pubkey, Pubkey};
21use solana_rpc_client::rpc_client::RpcClient;
22use solana_rpc_client_api::config::{RpcSendTransactionConfig, RpcTransactionConfig};
23use solana_signature::Signature;
24use solana_transaction::Transaction;
25use solana_transaction_status_client_types::{
26    option_serializer::OptionSerializer, TransactionStatus, UiInstruction, UiTransactionEncoding,
27};
28use tokio::time::{sleep, Instant};
29use tracing::warn;
30
31use super::LightClientConfig;
32use crate::{
33    indexer::{photon_indexer::PhotonIndexer, Indexer, TreeInfo},
34    rpc::{
35        errors::RpcError,
36        get_light_state_tree_infos::{
37            default_state_tree_lookup_tables, get_light_state_tree_infos,
38        },
39        merkle_tree::MerkleTreeExt,
40        Rpc,
41    },
42};
43
44pub enum RpcUrl {
45    Testnet,
46    Devnet,
47    Localnet,
48    ZKTestnet,
49    Custom(String),
50}
51
52impl Display for RpcUrl {
53    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
54        let str = match self {
55            RpcUrl::Testnet => "https://api.testnet.solana.com".to_string(),
56            RpcUrl::Devnet => "https://api.devnet.solana.com".to_string(),
57            RpcUrl::Localnet => "http://localhost:8899".to_string(),
58            RpcUrl::ZKTestnet => "https://zk-testnet.helius.dev:8899".to_string(),
59            RpcUrl::Custom(url) => url.clone(),
60        };
61        write!(f, "{}", str)
62    }
63}
64
65#[derive(Clone, Debug, Copy)]
66pub struct RetryConfig {
67    pub max_retries: u32,
68    pub retry_delay: Duration,
69    /// Max Light slot timeout in time based on solana slot length and light
70    /// slot length.
71    pub timeout: Duration,
72}
73
74impl Default for RetryConfig {
75    fn default() -> Self {
76        RetryConfig {
77            max_retries: 10,
78            retry_delay: Duration::from_secs(1),
79            timeout: Duration::from_secs(60),
80        }
81    }
82}
83
84#[allow(dead_code)]
85pub struct LightClient {
86    pub client: RpcClient,
87    pub payer: Keypair,
88    pub retry_config: RetryConfig,
89    pub indexer: Option<PhotonIndexer>,
90    pub state_merkle_trees: Vec<TreeInfo>,
91}
92
93impl Debug for LightClient {
94    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
95        write!(f, "LightClient {{ client: {:?} }}", self.client.url())
96    }
97}
98
99impl LightClient {
100    pub async fn new_with_retry(
101        config: LightClientConfig,
102        retry_config: Option<RetryConfig>,
103    ) -> Result<Self, RpcError> {
104        let payer = Keypair::new();
105        let commitment_config = config
106            .commitment_config
107            .unwrap_or(CommitmentConfig::confirmed());
108        let client = RpcClient::new_with_commitment(config.url.to_string(), commitment_config);
109        let retry_config = retry_config.unwrap_or_default();
110
111        let indexer = config
112            .photon_url
113            .map(|path| PhotonIndexer::new(path, config.api_key));
114
115        let mut new = Self {
116            client,
117            payer,
118            retry_config,
119            indexer,
120            state_merkle_trees: Vec::new(),
121        };
122        if config.fetch_active_tree {
123            new.get_latest_active_state_trees().await?;
124        }
125        Ok(new)
126    }
127
128    pub fn add_indexer(&mut self, path: String, api_key: Option<String>) {
129        self.indexer = Some(PhotonIndexer::new(path, api_key));
130    }
131
132    /// Detects the network type based on the RPC URL
133    fn detect_network(&self) -> RpcUrl {
134        let url = self.client.url();
135
136        if url.contains("devnet") {
137            RpcUrl::Devnet
138        } else if url.contains("testnet") {
139            RpcUrl::Testnet
140        } else if url.contains("localhost") || url.contains("127.0.0.1") {
141            RpcUrl::Localnet
142        } else if url.contains("zk-testnet") {
143            RpcUrl::ZKTestnet
144        } else {
145            // Default to mainnet for production URLs and custom URLs
146            RpcUrl::Custom(url.to_string())
147        }
148    }
149
150    async fn retry<F, Fut, T>(&self, operation: F) -> Result<T, RpcError>
151    where
152        F: Fn() -> Fut,
153        Fut: std::future::Future<Output = Result<T, RpcError>>,
154    {
155        let mut attempts = 0;
156        let start_time = Instant::now();
157        loop {
158            match operation().await {
159                Ok(result) => return Ok(result),
160                Err(e) => {
161                    let retry = self.should_retry(&e);
162                    if retry {
163                        attempts += 1;
164                        if attempts >= self.retry_config.max_retries
165                            || start_time.elapsed() >= self.retry_config.timeout
166                        {
167                            return Err(e);
168                        }
169                        warn!(
170                            "Operation failed, retrying in {:?} (attempt {}/{}): {:?}",
171                            self.retry_config.retry_delay,
172                            attempts,
173                            self.retry_config.max_retries,
174                            e
175                        );
176                        sleep(self.retry_config.retry_delay).await;
177                    } else {
178                        return Err(e);
179                    }
180                }
181            }
182        }
183    }
184
185    async fn _create_and_send_transaction_with_batched_event(
186        &mut self,
187        instructions: &[Instruction],
188        payer: &Pubkey,
189        signers: &[&Keypair],
190    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
191        let latest_blockhash = self.client.get_latest_blockhash()?;
192
193        let mut instructions_vec = vec![
194            solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit(
195                1_000_000,
196            ),
197        ];
198        instructions_vec.extend_from_slice(instructions);
199
200        let transaction = Transaction::new_signed_with_payer(
201            instructions_vec.as_slice(),
202            Some(payer),
203            signers,
204            latest_blockhash,
205        );
206
207        let (signature, slot) = self
208            .process_transaction_with_context(transaction.clone())
209            .await?;
210
211        let mut vec = Vec::new();
212        let mut vec_accounts = Vec::new();
213        let mut program_ids = Vec::new();
214        instructions_vec.iter().for_each(|x| {
215            program_ids.push(light_compressed_account::Pubkey::new_from_array(
216                x.program_id.to_bytes(),
217            ));
218            vec.push(x.data.clone());
219            vec_accounts.push(
220                x.accounts
221                    .iter()
222                    .map(|x| light_compressed_account::Pubkey::new_from_array(x.pubkey.to_bytes()))
223                    .collect(),
224            );
225        });
226        {
227            let rpc_transaction_config = RpcTransactionConfig {
228                encoding: Some(UiTransactionEncoding::Base64),
229                commitment: Some(self.client.commitment()),
230                ..Default::default()
231            };
232            let transaction = self
233                .client
234                .get_transaction_with_config(&signature, rpc_transaction_config)
235                .map_err(|e| RpcError::CustomError(e.to_string()))?;
236            let decoded_transaction = transaction
237                .transaction
238                .transaction
239                .decode()
240                .clone()
241                .unwrap();
242            let account_keys = decoded_transaction.message.static_account_keys();
243            let meta = transaction.transaction.meta.as_ref().ok_or_else(|| {
244                RpcError::CustomError("Transaction missing metadata information".to_string())
245            })?;
246            if meta.status.is_err() {
247                return Err(RpcError::CustomError(
248                    "Transaction status indicates an error".to_string(),
249                ));
250            }
251
252            let inner_instructions = match &meta.inner_instructions {
253                OptionSerializer::Some(i) => i,
254                OptionSerializer::None => {
255                    return Err(RpcError::CustomError(
256                        "No inner instructions found".to_string(),
257                    ));
258                }
259                OptionSerializer::Skip => {
260                    return Err(RpcError::CustomError(
261                        "No inner instructions found".to_string(),
262                    ));
263                }
264            };
265
266            for ix in inner_instructions.iter() {
267                for ui_instruction in ix.instructions.iter() {
268                    match ui_instruction {
269                        UiInstruction::Compiled(ui_compiled_instruction) => {
270                            let accounts = &ui_compiled_instruction.accounts;
271                            let data = bs58::decode(&ui_compiled_instruction.data)
272                                .into_vec()
273                                .map_err(|_| {
274                                    RpcError::CustomError(
275                                        "Failed to decode instruction data".to_string(),
276                                    )
277                                })?;
278                            vec.push(data);
279                            program_ids.push(light_compressed_account::Pubkey::new_from_array(
280                                account_keys[ui_compiled_instruction.program_id_index as usize]
281                                    .to_bytes(),
282                            ));
283                            vec_accounts.push(
284                                accounts
285                                    .iter()
286                                    .map(|x| {
287                                        light_compressed_account::Pubkey::new_from_array(
288                                            account_keys[(*x) as usize].to_bytes(),
289                                        )
290                                    })
291                                    .collect(),
292                            );
293                        }
294                        UiInstruction::Parsed(_) => {
295                            println!("Parsed instructions are not implemented yet");
296                        }
297                    }
298                }
299            }
300        }
301        let parsed_event =
302            event_from_light_transaction(program_ids.as_slice(), vec.as_slice(), vec_accounts)
303                .map_err(|e| RpcError::CustomError(format!("Failed to parse event: {e:?}")))?;
304        let event = parsed_event.map(|e| (e, signature, slot));
305        Ok(event)
306    }
307
308    async fn _create_and_send_transaction_with_event<T>(
309        &mut self,
310        instructions: &[Instruction],
311        payer: &Pubkey,
312        signers: &[&Keypair],
313    ) -> Result<Option<(T, Signature, u64)>, RpcError>
314    where
315        T: BorshDeserialize + Send + Debug,
316    {
317        let latest_blockhash = self.client.get_latest_blockhash()?;
318
319        let mut instructions_vec = vec![
320            solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit(
321                1_000_000,
322            ),
323        ];
324        instructions_vec.extend_from_slice(instructions);
325
326        let transaction = Transaction::new_signed_with_payer(
327            instructions_vec.as_slice(),
328            Some(payer),
329            signers,
330            latest_blockhash,
331        );
332
333        let (signature, slot) = self
334            .process_transaction_with_context(transaction.clone())
335            .await?;
336
337        let mut parsed_event = None;
338        for instruction in &transaction.message.instructions {
339            let ix_data = instruction.data.clone();
340            match T::deserialize(&mut &instruction.data[..]) {
341                Ok(e) => {
342                    parsed_event = Some(e);
343                    break;
344                }
345                Err(e) => {
346                    warn!(
347                        "Failed to parse event: {:?}, type: {:?}, ix data: {:?}",
348                        e,
349                        std::any::type_name::<T>(),
350                        ix_data
351                    );
352                }
353            }
354        }
355
356        if parsed_event.is_none() {
357            parsed_event = self.parse_inner_instructions::<T>(signature).ok();
358        }
359
360        let result = parsed_event.map(|e| (e, signature, slot));
361        Ok(result)
362    }
363}
364
365impl LightClient {
366    #[allow(clippy::result_large_err)]
367    fn parse_inner_instructions<T: BorshDeserialize>(
368        &self,
369        signature: Signature,
370    ) -> Result<T, RpcError> {
371        let rpc_transaction_config = RpcTransactionConfig {
372            encoding: Some(UiTransactionEncoding::Base64),
373            commitment: Some(self.client.commitment()),
374            ..Default::default()
375        };
376        let transaction = self
377            .client
378            .get_transaction_with_config(&signature, rpc_transaction_config)
379            .map_err(|e| RpcError::CustomError(e.to_string()))?;
380        let meta = transaction.transaction.meta.as_ref().ok_or_else(|| {
381            RpcError::CustomError("Transaction missing metadata information".to_string())
382        })?;
383        if meta.status.is_err() {
384            return Err(RpcError::CustomError(
385                "Transaction status indicates an error".to_string(),
386            ));
387        }
388
389        let inner_instructions = match &meta.inner_instructions {
390            OptionSerializer::Some(i) => i,
391            OptionSerializer::None => {
392                return Err(RpcError::CustomError(
393                    "No inner instructions found".to_string(),
394                ));
395            }
396            OptionSerializer::Skip => {
397                return Err(RpcError::CustomError(
398                    "No inner instructions found".to_string(),
399                ));
400            }
401        };
402
403        for ix in inner_instructions.iter() {
404            for ui_instruction in ix.instructions.iter() {
405                match ui_instruction {
406                    UiInstruction::Compiled(ui_compiled_instruction) => {
407                        let data = bs58::decode(&ui_compiled_instruction.data)
408                            .into_vec()
409                            .map_err(|_| {
410                                RpcError::CustomError(
411                                    "Failed to decode instruction data".to_string(),
412                                )
413                            })?;
414
415                        match T::try_from_slice(data.as_slice()) {
416                            Ok(parsed_data) => return Ok(parsed_data),
417                            Err(e) => {
418                                warn!("Failed to parse inner instruction: {:?}", e);
419                            }
420                        }
421                    }
422                    UiInstruction::Parsed(_) => {
423                        println!("Parsed instructions are not implemented yet");
424                    }
425                }
426            }
427        }
428        Err(RpcError::CustomError(
429            "Failed to find any parseable inner instructions".to_string(),
430        ))
431    }
432}
433
434#[async_trait]
435impl Rpc for LightClient {
436    async fn new(config: LightClientConfig) -> Result<Self, RpcError>
437    where
438        Self: Sized,
439    {
440        Self::new_with_retry(config, None).await
441    }
442
443    fn get_payer(&self) -> &Keypair {
444        &self.payer
445    }
446
447    fn get_url(&self) -> String {
448        self.client.url()
449    }
450
451    async fn health(&self) -> Result<(), RpcError> {
452        self.retry(|| async { self.client.get_health().map_err(RpcError::from) })
453            .await
454    }
455
456    async fn get_program_accounts(
457        &self,
458        program_id: &Pubkey,
459    ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
460        self.retry(|| async {
461            self.client
462                .get_program_accounts(program_id)
463                .map_err(RpcError::from)
464        })
465        .await
466    }
467
468    async fn process_transaction(
469        &mut self,
470        transaction: Transaction,
471    ) -> Result<Signature, RpcError> {
472        self.retry(|| async {
473            self.client
474                .send_and_confirm_transaction(&transaction)
475                .map_err(RpcError::from)
476        })
477        .await
478    }
479
480    async fn process_transaction_with_context(
481        &mut self,
482        transaction: Transaction,
483    ) -> Result<(Signature, Slot), RpcError> {
484        self.retry(|| async {
485            let signature = self.client.send_and_confirm_transaction(&transaction)?;
486            let sig_info = self.client.get_signature_statuses(&[signature])?;
487            let slot = sig_info
488                .value
489                .first()
490                .and_then(|s| s.as_ref())
491                .map(|s| s.slot)
492                .ok_or_else(|| RpcError::CustomError("Failed to get slot".into()))?;
493            Ok((signature, slot))
494        })
495        .await
496    }
497
498    async fn confirm_transaction(&self, signature: Signature) -> Result<bool, RpcError> {
499        self.retry(|| async {
500            self.client
501                .confirm_transaction(&signature)
502                .map_err(RpcError::from)
503        })
504        .await
505    }
506
507    async fn get_account(&self, address: Pubkey) -> Result<Option<Account>, RpcError> {
508        self.retry(|| async {
509            self.client
510                .get_account_with_commitment(&address, self.client.commitment())
511                .map(|response| response.value)
512                .map_err(RpcError::from)
513        })
514        .await
515    }
516
517    async fn get_multiple_accounts(
518        &self,
519        addresses: &[Pubkey],
520    ) -> Result<Vec<Option<Account>>, RpcError> {
521        self.retry(|| async {
522            self.client
523                .get_multiple_accounts(addresses)
524                .map_err(RpcError::from)
525        })
526        .await
527    }
528
529    async fn get_minimum_balance_for_rent_exemption(
530        &self,
531        data_len: usize,
532    ) -> Result<u64, RpcError> {
533        self.retry(|| async {
534            self.client
535                .get_minimum_balance_for_rent_exemption(data_len)
536                .map_err(RpcError::from)
537        })
538        .await
539    }
540
541    async fn airdrop_lamports(
542        &mut self,
543        to: &Pubkey,
544        lamports: u64,
545    ) -> Result<Signature, RpcError> {
546        self.retry(|| async {
547            let signature = self
548                .client
549                .request_airdrop(to, lamports)
550                .map_err(RpcError::ClientError)?;
551            self.retry(|| async {
552                if self
553                    .client
554                    .confirm_transaction_with_commitment(&signature, self.client.commitment())?
555                    .value
556                {
557                    Ok(())
558                } else {
559                    Err(RpcError::CustomError("Airdrop not confirmed".into()))
560                }
561            })
562            .await?;
563
564            Ok(signature)
565        })
566        .await
567    }
568
569    async fn get_balance(&self, pubkey: &Pubkey) -> Result<u64, RpcError> {
570        self.retry(|| async { self.client.get_balance(pubkey).map_err(RpcError::from) })
571            .await
572    }
573
574    async fn get_latest_blockhash(&mut self) -> Result<(Hash, u64), RpcError> {
575        self.retry(|| async {
576            self.client
577                // Confirmed commitments land more reliably than finalized
578                // https://www.helius.dev/blog/how-to-deal-with-blockhash-errors-on-solana#how-to-deal-with-blockhash-errors
579                .get_latest_blockhash_with_commitment(CommitmentConfig::confirmed())
580                .map_err(RpcError::from)
581        })
582        .await
583    }
584
585    async fn get_slot(&self) -> Result<u64, RpcError> {
586        self.retry(|| async { self.client.get_slot().map_err(RpcError::from) })
587            .await
588    }
589
590    async fn send_transaction(&self, transaction: &Transaction) -> Result<Signature, RpcError> {
591        self.retry(|| async {
592            self.client
593                .send_transaction_with_config(
594                    transaction,
595                    RpcSendTransactionConfig {
596                        skip_preflight: true,
597                        max_retries: Some(self.retry_config.max_retries as usize),
598                        ..Default::default()
599                    },
600                )
601                .map_err(RpcError::from)
602        })
603        .await
604    }
605
606    async fn send_transaction_with_config(
607        &self,
608        transaction: &Transaction,
609        config: RpcSendTransactionConfig,
610    ) -> Result<Signature, RpcError> {
611        self.retry(|| async {
612            self.client
613                .send_transaction_with_config(transaction, config)
614                .map_err(RpcError::from)
615        })
616        .await
617    }
618
619    async fn get_transaction_slot(&self, signature: &Signature) -> Result<u64, RpcError> {
620        self.retry(|| async {
621            Ok(self
622                .client
623                .get_transaction_with_config(
624                    signature,
625                    RpcTransactionConfig {
626                        encoding: Some(UiTransactionEncoding::Base64),
627                        commitment: Some(self.client.commitment()),
628                        ..Default::default()
629                    },
630                )
631                .map_err(RpcError::from)?
632                .slot)
633        })
634        .await
635    }
636
637    async fn get_signature_statuses(
638        &self,
639        signatures: &[Signature],
640    ) -> Result<Vec<Option<TransactionStatus>>, RpcError> {
641        self.client
642            .get_signature_statuses(signatures)
643            .map(|response| response.value)
644            .map_err(RpcError::from)
645    }
646
647    async fn create_and_send_transaction_with_event<T>(
648        &mut self,
649        instructions: &[Instruction],
650        payer: &Pubkey,
651        signers: &[&Keypair],
652    ) -> Result<Option<(T, Signature, u64)>, RpcError>
653    where
654        T: BorshDeserialize + Send + Debug,
655    {
656        self._create_and_send_transaction_with_event::<T>(instructions, payer, signers)
657            .await
658    }
659
660    async fn create_and_send_transaction_with_public_event(
661        &mut self,
662        instructions: &[Instruction],
663        payer: &Pubkey,
664        signers: &[&Keypair],
665    ) -> Result<Option<(PublicTransactionEvent, Signature, Slot)>, RpcError> {
666        let parsed_event = self
667            ._create_and_send_transaction_with_batched_event(instructions, payer, signers)
668            .await?;
669
670        let event = parsed_event.map(|(e, signature, slot)| (e[0].event.clone(), signature, slot));
671        Ok(event)
672    }
673
674    async fn create_and_send_transaction_with_batched_event(
675        &mut self,
676        instructions: &[Instruction],
677        payer: &Pubkey,
678        signers: &[&Keypair],
679    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
680        self._create_and_send_transaction_with_batched_event(instructions, payer, signers)
681            .await
682    }
683
684    fn indexer(&self) -> Result<&impl Indexer, RpcError> {
685        self.indexer.as_ref().ok_or(RpcError::IndexerNotInitialized)
686    }
687
688    fn indexer_mut(&mut self) -> Result<&mut impl Indexer, RpcError> {
689        self.indexer.as_mut().ok_or(RpcError::IndexerNotInitialized)
690    }
691
692    /// Fetch the latest state tree addresses from the cluster.
693    async fn get_latest_active_state_trees(&mut self) -> Result<Vec<TreeInfo>, RpcError> {
694        let network = self.detect_network();
695
696        // Return default test values for localnet
697        if matches!(network, RpcUrl::Localnet) {
698            use light_compressed_account::TreeType;
699            use solana_pubkey::pubkey;
700
701            use crate::indexer::TreeInfo;
702
703            #[cfg(feature = "v2")]
704            let default_trees = vec![
705                TreeInfo {
706                    tree: pubkey!("bmt1LryLZUMmF7ZtqESaw7wifBXLfXHQYoE4GAmrahU"),
707                    queue: pubkey!("oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto"),
708                    cpi_context: Some(pubkey!("cpi15BoVPKgEPw5o8wc2T816GE7b378nMXnhH3Xbq4y")),
709                    next_tree_info: None,
710                    tree_type: TreeType::StateV2,
711                },
712                TreeInfo {
713                    tree: pubkey!("bmt2UxoBxB9xWev4BkLvkGdapsz6sZGkzViPNph7VFi"),
714                    queue: pubkey!("oq2UkeMsJLfXt2QHzim242SUi3nvjJs8Pn7Eac9H9vg"),
715                    cpi_context: Some(pubkey!("cpi2yGapXUR3As5SjnHBAVvmApNiLsbeZpF3euWnW6B")),
716                    next_tree_info: None,
717                    tree_type: TreeType::StateV2,
718                },
719                TreeInfo {
720                    tree: pubkey!("bmt3ccLd4bqSVZVeCJnH1F6C8jNygAhaDfxDwePyyGb"),
721                    queue: pubkey!("oq3AxjekBWgo64gpauB6QtuZNesuv19xrhaC1ZM1THQ"),
722                    cpi_context: Some(pubkey!("cpi3mbwMpSX8FAGMZVP85AwxqCaQMfEk9Em1v8QK9Rf")),
723                    next_tree_info: None,
724                    tree_type: TreeType::StateV2,
725                },
726                TreeInfo {
727                    tree: pubkey!("bmt4d3p1a4YQgk9PeZv5s4DBUmbF5NxqYpk9HGjQsd8"),
728                    queue: pubkey!("oq4ypwvVGzCUMoiKKHWh4S1SgZJ9vCvKpcz6RT6A8dq"),
729                    cpi_context: Some(pubkey!("cpi4yyPDc4bCgHAnsenunGA8Y77j3XEDyjgfyCKgcoc")),
730                    next_tree_info: None,
731                    tree_type: TreeType::StateV2,
732                },
733                TreeInfo {
734                    tree: pubkey!("bmt5yU97jC88YXTuSukYHa8Z5Bi2ZDUtmzfkDTA2mG2"),
735                    queue: pubkey!("oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P"),
736                    cpi_context: Some(pubkey!("cpi5ZTjdgYpZ1Xr7B1cMLLUE81oTtJbNNAyKary2nV6")),
737                    next_tree_info: None,
738                    tree_type: TreeType::StateV2,
739                },
740            ];
741
742            #[cfg(not(feature = "v2"))]
743            let default_trees = vec![TreeInfo {
744                tree: pubkey!("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT"),
745                queue: pubkey!("nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148"),
746                cpi_context: Some(pubkey!("cpi1uHzrEhBG733DoEJNgHCyRS3XmmyVNZx5fonubE4")),
747                next_tree_info: None,
748                tree_type: TreeType::StateV1,
749            }];
750
751            self.state_merkle_trees = default_trees.clone();
752            return Ok(default_trees);
753        }
754
755        let (mainnet_tables, devnet_tables) = default_state_tree_lookup_tables();
756
757        let lookup_tables = match network {
758            RpcUrl::Devnet | RpcUrl::Testnet | RpcUrl::ZKTestnet => &devnet_tables,
759            _ => &mainnet_tables, // Default to mainnet for production and custom URLs
760        };
761
762        let res = get_light_state_tree_infos(
763            self,
764            &lookup_tables[0].state_tree_lookup_table,
765            &lookup_tables[0].nullify_table,
766        )
767        .await?;
768        self.state_merkle_trees = res.clone();
769        Ok(res)
770    }
771
772    /// Fetch the latest state tree addresses from the cluster.
773    fn get_state_tree_infos(&self) -> Vec<TreeInfo> {
774        self.state_merkle_trees.to_vec()
775    }
776
777    /// Gets a random active state tree.
778    /// State trees are cached and have to be fetched or set.
779    /// Returns v1 state trees by default, v2 state trees when v2 feature is enabled.
780    fn get_random_state_tree_info(&self) -> Result<TreeInfo, RpcError> {
781        let mut rng = rand::thread_rng();
782
783        #[cfg(feature = "v2")]
784        let filtered_trees: Vec<TreeInfo> = self
785            .state_merkle_trees
786            .iter()
787            .filter(|tree| tree.tree_type == TreeType::StateV2)
788            .copied()
789            .collect();
790
791        #[cfg(not(feature = "v2"))]
792        let filtered_trees: Vec<TreeInfo> = self
793            .state_merkle_trees
794            .iter()
795            .filter(|tree| tree.tree_type == TreeType::StateV1)
796            .copied()
797            .collect();
798
799        select_state_tree_info(&mut rng, &filtered_trees)
800    }
801
802    /// Gets a random v1 state tree.
803    /// State trees are cached and have to be fetched or set.
804    fn get_random_state_tree_info_v1(&self) -> Result<TreeInfo, RpcError> {
805        let mut rng = rand::thread_rng();
806        let v1_trees: Vec<TreeInfo> = self
807            .state_merkle_trees
808            .iter()
809            .filter(|tree| tree.tree_type == TreeType::StateV1)
810            .copied()
811            .collect();
812        select_state_tree_info(&mut rng, &v1_trees)
813    }
814
815    fn get_address_tree_v1(&self) -> TreeInfo {
816        TreeInfo {
817            tree: pubkey!("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2"),
818            queue: pubkey!("aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F"),
819            cpi_context: None,
820            next_tree_info: None,
821            tree_type: TreeType::AddressV1,
822        }
823    }
824
825    fn get_address_tree_v2(&self) -> TreeInfo {
826        TreeInfo {
827            tree: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
828            queue: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
829            cpi_context: None,
830            next_tree_info: None,
831            tree_type: TreeType::AddressV2,
832        }
833    }
834}
835
836impl MerkleTreeExt for LightClient {}
837
838/// Selects a random state tree from the provided list.
839///
840/// This function should be used together with `get_state_tree_infos()` to first
841/// retrieve the list of state trees, then select one randomly.
842///
843/// # Arguments
844/// * `rng` - A mutable reference to a random number generator
845/// * `state_trees` - A slice of `TreeInfo` representing state trees
846///
847/// # Returns
848/// A randomly selected `TreeInfo` from the provided list, or an error if the list is empty
849///
850/// # Errors
851/// Returns `RpcError::NoStateTreesAvailable` if the provided slice is empty
852///
853/// # Example
854/// ```ignore
855/// use rand::thread_rng;
856/// let tree_infos = client.get_state_tree_infos();
857/// let mut rng = thread_rng();
858/// let selected_tree = select_state_tree_info(&mut rng, &tree_infos)?;
859/// ```
860pub fn select_state_tree_info<R: rand::Rng>(
861    rng: &mut R,
862    state_trees: &[TreeInfo],
863) -> Result<TreeInfo, RpcError> {
864    if state_trees.is_empty() {
865        return Err(RpcError::NoStateTreesAvailable);
866    }
867
868    Ok(state_trees[rng.gen_range(0..state_trees.len())])
869}