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