Skip to main content

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_event::{
10    event::{BatchPublicTransactionEvent, PublicTransactionEvent},
11    parse::event_from_light_transaction,
12};
13use light_sdk_types::lca::TreeType;
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_message::{v0, AddressLookupTableAccount, VersionedMessage};
21use solana_pubkey::{pubkey, Pubkey};
22use solana_rpc_client::rpc_client::RpcClient;
23use solana_rpc_client_api::config::{RpcSendTransactionConfig, RpcTransactionConfig};
24use solana_signature::Signature;
25use solana_transaction::{versioned::VersionedTransaction, Transaction};
26use solana_transaction_status_client_types::{
27    option_serializer::OptionSerializer, TransactionStatus, UiInstruction, UiTransactionEncoding,
28};
29use tokio::time::{sleep, Instant};
30use tracing::warn;
31
32use super::LightClientConfig;
33#[cfg(not(feature = "v2"))]
34use crate::rpc::get_light_state_tree_infos::{
35    default_state_tree_lookup_tables, get_light_state_tree_infos,
36};
37use crate::{
38    indexer::{photon_indexer::PhotonIndexer, Indexer, TreeInfo},
39    rpc::{
40        errors::RpcError,
41        merkle_tree::MerkleTreeExt,
42        tx_v1::{build_v1_transaction, TransactionConfig},
43        Rpc,
44    },
45};
46
47/// V2 batched state trees.
48#[cfg(feature = "v2")]
49pub(crate) fn default_v2_state_trees() -> [TreeInfo; 5] {
50    [
51        TreeInfo {
52            tree: pubkey!("bmt1LryLZUMmF7ZtqESaw7wifBXLfXHQYoE4GAmrahU"),
53            queue: pubkey!("oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto"),
54            cpi_context: Some(pubkey!("cpi15BoVPKgEPw5o8wc2T816GE7b378nMXnhH3Xbq4y")),
55            next_tree_info: None,
56            tree_type: TreeType::StateV2,
57        },
58        TreeInfo {
59            tree: pubkey!("bmt2UxoBxB9xWev4BkLvkGdapsz6sZGkzViPNph7VFi"),
60            queue: pubkey!("oq2UkeMsJLfXt2QHzim242SUi3nvjJs8Pn7Eac9H9vg"),
61            cpi_context: Some(pubkey!("cpi2yGapXUR3As5SjnHBAVvmApNiLsbeZpF3euWnW6B")),
62            next_tree_info: None,
63            tree_type: TreeType::StateV2,
64        },
65        TreeInfo {
66            tree: pubkey!("bmt3ccLd4bqSVZVeCJnH1F6C8jNygAhaDfxDwePyyGb"),
67            queue: pubkey!("oq3AxjekBWgo64gpauB6QtuZNesuv19xrhaC1ZM1THQ"),
68            cpi_context: Some(pubkey!("cpi3mbwMpSX8FAGMZVP85AwxqCaQMfEk9Em1v8QK9Rf")),
69            next_tree_info: None,
70            tree_type: TreeType::StateV2,
71        },
72        TreeInfo {
73            tree: pubkey!("bmt4d3p1a4YQgk9PeZv5s4DBUmbF5NxqYpk9HGjQsd8"),
74            queue: pubkey!("oq4ypwvVGzCUMoiKKHWh4S1SgZJ9vCvKpcz6RT6A8dq"),
75            cpi_context: Some(pubkey!("cpi4yyPDc4bCgHAnsenunGA8Y77j3XEDyjgfyCKgcoc")),
76            next_tree_info: None,
77            tree_type: TreeType::StateV2,
78        },
79        TreeInfo {
80            tree: pubkey!("bmt5yU97jC88YXTuSukYHa8Z5Bi2ZDUtmzfkDTA2mG2"),
81            queue: pubkey!("oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P"),
82            cpi_context: Some(pubkey!("cpi5ZTjdgYpZ1Xr7B1cMLLUE81oTtJbNNAyKary2nV6")),
83            next_tree_info: None,
84            tree_type: TreeType::StateV2,
85        },
86    ]
87}
88
89pub enum RpcUrl {
90    Testnet,
91    Devnet,
92    Localnet,
93    ZKTestnet,
94    Custom(String),
95}
96
97impl Display for RpcUrl {
98    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
99        let str = match self {
100            RpcUrl::Testnet => "https://api.testnet.solana.com".to_string(),
101            RpcUrl::Devnet => "https://api.devnet.solana.com".to_string(),
102            RpcUrl::Localnet => "http://localhost:8899".to_string(),
103            RpcUrl::ZKTestnet => "https://zk-testnet.helius.dev:8899".to_string(),
104            RpcUrl::Custom(url) => url.clone(),
105        };
106        write!(f, "{}", str)
107    }
108}
109
110#[derive(Clone, Debug, Copy)]
111pub struct RetryConfig {
112    pub max_retries: u32,
113    pub retry_delay: Duration,
114    /// Max Light slot timeout in time based on solana slot length and light
115    /// slot length.
116    pub timeout: Duration,
117}
118
119impl Default for RetryConfig {
120    fn default() -> Self {
121        RetryConfig {
122            max_retries: 10,
123            retry_delay: Duration::from_secs(1),
124            timeout: Duration::from_secs(60),
125        }
126    }
127}
128
129#[allow(dead_code)]
130pub struct LightClient {
131    pub client: RpcClient,
132    pub payer: Keypair,
133    pub retry_config: RetryConfig,
134    pub indexer: Option<PhotonIndexer>,
135    pub state_merkle_trees: Vec<TreeInfo>,
136}
137
138impl Debug for LightClient {
139    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
140        write!(f, "LightClient {{ client: {:?} }}", self.client.url())
141    }
142}
143
144impl LightClient {
145    pub async fn new_with_retry(
146        config: LightClientConfig,
147        retry_config: Option<RetryConfig>,
148    ) -> Result<Self, RpcError> {
149        let payer = Keypair::new();
150        let commitment_config = config
151            .commitment_config
152            .unwrap_or(CommitmentConfig::confirmed());
153        let client = RpcClient::new_with_commitment(config.url.to_string(), commitment_config);
154        let retry_config = retry_config.unwrap_or_default();
155
156        let indexer = config.photon_url.map(PhotonIndexer::new);
157
158        let mut new = Self {
159            client,
160            payer,
161            retry_config,
162            indexer,
163            state_merkle_trees: Vec::new(),
164        };
165        if config.fetch_active_tree {
166            new.get_latest_active_state_trees().await?;
167        }
168        Ok(new)
169    }
170
171    pub fn add_indexer(&mut self, url: String) {
172        self.indexer = Some(PhotonIndexer::new(url));
173    }
174
175    /// Detects the network type based on the RPC URL. V1 only.
176    #[cfg(not(feature = "v2"))]
177    fn detect_network(&self) -> RpcUrl {
178        let url = self.client.url();
179
180        if url.contains("devnet") {
181            RpcUrl::Devnet
182        } else if url.contains("testnet") {
183            RpcUrl::Testnet
184        } else if url.contains("localhost") || url.contains("127.0.0.1") {
185            RpcUrl::Localnet
186        } else if url.contains("zk-testnet") {
187            RpcUrl::ZKTestnet
188        } else {
189            // Default to mainnet for production URLs and custom URLs
190            RpcUrl::Custom(url.to_string())
191        }
192    }
193
194    async fn retry<F, Fut, T>(&self, operation: F) -> Result<T, RpcError>
195    where
196        F: Fn() -> Fut,
197        Fut: std::future::Future<Output = Result<T, RpcError>>,
198    {
199        let mut attempts = 0;
200        let start_time = Instant::now();
201        loop {
202            match operation().await {
203                Ok(result) => return Ok(result),
204                Err(e) => {
205                    let retry = self.should_retry(&e);
206                    if retry {
207                        attempts += 1;
208                        if attempts >= self.retry_config.max_retries
209                            || start_time.elapsed() >= self.retry_config.timeout
210                        {
211                            return Err(e);
212                        }
213                        warn!(
214                            "Operation failed, retrying in {:?} (attempt {}/{}): {:?}",
215                            self.retry_config.retry_delay,
216                            attempts,
217                            self.retry_config.max_retries,
218                            e
219                        );
220                        sleep(self.retry_config.retry_delay).await;
221                    } else {
222                        return Err(e);
223                    }
224                }
225            }
226        }
227    }
228
229    async fn _create_and_send_transaction_with_batched_event(
230        &mut self,
231        instructions: &[Instruction],
232        payer: &Pubkey,
233        signers: &[&Keypair],
234    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
235        let latest_blockhash = self.client.get_latest_blockhash()?;
236
237        let mut instructions_vec = vec![
238            solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit(
239                1_000_000,
240            ),
241        ];
242        instructions_vec.extend_from_slice(instructions);
243
244        let transaction = Transaction::new_signed_with_payer(
245            instructions_vec.as_slice(),
246            Some(payer),
247            signers,
248            latest_blockhash,
249        );
250
251        let (signature, slot) = self.process_transaction_with_context(transaction).await?;
252
253        self.fetch_batched_events(instructions_vec.as_slice(), signature, slot)
254    }
255
256    async fn _create_and_send_v1_transaction_with_batched_event(
257        &mut self,
258        instructions: &[Instruction],
259        payer: &Pubkey,
260        signers: &[&Keypair],
261        config: TransactionConfig,
262    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
263        let latest_blockhash = self.client.get_latest_blockhash()?;
264        let transaction =
265            build_v1_transaction(instructions, payer, signers, latest_blockhash, config)?;
266
267        let (signature, slot) = self
268            .process_versioned_transaction_with_context(transaction)
269            .await?;
270
271        self.fetch_batched_events(instructions, signature, slot)
272    }
273
274    /// Fetches the confirmed transaction `signature` and parses Light events
275    /// from `instructions_vec` (the outer instructions as sent) and the inner
276    /// instructions reported by the RPC.
277    fn fetch_batched_events(
278        &self,
279        instructions_vec: &[Instruction],
280        signature: Signature,
281        slot: Slot,
282    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
283        let mut vec = Vec::new();
284        let mut vec_accounts = Vec::new();
285        let mut program_ids = Vec::new();
286        instructions_vec.iter().for_each(|x| {
287            program_ids.push(light_sdk_types::lca::Pubkey::new_from_array(
288                x.program_id.to_bytes(),
289            ));
290            vec.push(x.data.clone());
291            vec_accounts.push(
292                x.accounts
293                    .iter()
294                    .map(|x| light_sdk_types::lca::Pubkey::new_from_array(x.pubkey.to_bytes()))
295                    .collect(),
296            );
297        });
298        {
299            let rpc_transaction_config = RpcTransactionConfig {
300                encoding: Some(UiTransactionEncoding::Base64),
301                commitment: Some(self.client.commitment()),
302                max_supported_transaction_version: Some(1),
303            };
304            let transaction = self
305                .client
306                .get_transaction_with_config(&signature, rpc_transaction_config)
307                .map_err(|e| RpcError::CustomError(e.to_string()))?;
308            let decoded_transaction = transaction
309                .transaction
310                .transaction
311                .decode()
312                .clone()
313                .ok_or_else(|| {
314                    RpcError::CustomError(
315                        "Failed to decode transaction from RPC response".to_string(),
316                    )
317                })?;
318            let account_keys = decoded_transaction.message.static_account_keys();
319            let meta = transaction.transaction.meta.as_ref().ok_or_else(|| {
320                RpcError::CustomError("Transaction missing metadata information".to_string())
321            })?;
322            if meta.status.is_err() {
323                return Err(RpcError::CustomError(
324                    "Transaction status indicates an error".to_string(),
325                ));
326            }
327
328            let inner_instructions = match &meta.inner_instructions {
329                OptionSerializer::Some(i) => i,
330                OptionSerializer::None => {
331                    return Err(RpcError::CustomError(
332                        "No inner instructions found".to_string(),
333                    ));
334                }
335                OptionSerializer::Skip => {
336                    return Err(RpcError::CustomError(
337                        "No inner instructions found".to_string(),
338                    ));
339                }
340            };
341
342            for ix in inner_instructions.iter() {
343                for ui_instruction in ix.instructions.iter() {
344                    match ui_instruction {
345                        UiInstruction::Compiled(ui_compiled_instruction) => {
346                            let accounts = &ui_compiled_instruction.accounts;
347                            let data = bs58::decode(&ui_compiled_instruction.data)
348                                .into_vec()
349                                .map_err(|_| {
350                                    RpcError::CustomError(
351                                        "Failed to decode instruction data".to_string(),
352                                    )
353                                })?;
354                            vec.push(data);
355                            let program_id = account_keys
356                                .get(ui_compiled_instruction.program_id_index as usize)
357                                .ok_or_else(|| {
358                                    RpcError::CustomError(
359                                        "Instruction program id index out of bounds".to_string(),
360                                    )
361                                })?;
362                            program_ids.push(light_sdk_types::lca::Pubkey::new_from_array(
363                                program_id.to_bytes(),
364                            ));
365                            let instruction_accounts = accounts
366                                .iter()
367                                .map(|x| {
368                                    account_keys
369                                        .get(*x as usize)
370                                        .map(|key| {
371                                            light_sdk_types::lca::Pubkey::new_from_array(
372                                                key.to_bytes(),
373                                            )
374                                        })
375                                        .ok_or_else(|| {
376                                            RpcError::CustomError(
377                                                "Instruction account index out of bounds"
378                                                    .to_string(),
379                                            )
380                                        })
381                                })
382                                .collect::<Result<Vec<_>, RpcError>>()?;
383                            vec_accounts.push(instruction_accounts);
384                        }
385                        UiInstruction::Parsed(_) => {
386                            println!("Parsed instructions are not implemented yet");
387                        }
388                    }
389                }
390            }
391        }
392        let parsed_event =
393            event_from_light_transaction(program_ids.as_slice(), vec.as_slice(), vec_accounts)
394                .map_err(|e| RpcError::CustomError(format!("Failed to parse event: {e:?}")))?;
395        let event = parsed_event.map(|e| (e, signature, slot));
396        Ok(event)
397    }
398
399    async fn _create_and_send_transaction_with_event<T>(
400        &mut self,
401        instructions: &[Instruction],
402        payer: &Pubkey,
403        signers: &[&Keypair],
404    ) -> Result<Option<(T, Signature, u64)>, RpcError>
405    where
406        T: BorshDeserialize + Send + Debug,
407    {
408        let latest_blockhash = self.client.get_latest_blockhash()?;
409
410        let mut instructions_vec = vec![
411            solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit(
412                1_000_000,
413            ),
414        ];
415        instructions_vec.extend_from_slice(instructions);
416
417        let transaction = Transaction::new_signed_with_payer(
418            instructions_vec.as_slice(),
419            Some(payer),
420            signers,
421            latest_blockhash,
422        );
423
424        let (signature, slot) = self.process_transaction_with_context(transaction).await?;
425
426        self.parse_event_from_instructions::<T>(instructions_vec.as_slice(), signature, slot)
427    }
428
429    async fn _create_and_send_v1_transaction_with_event<T>(
430        &mut self,
431        instructions: &[Instruction],
432        payer: &Pubkey,
433        signers: &[&Keypair],
434        config: TransactionConfig,
435    ) -> Result<Option<(T, Signature, u64)>, RpcError>
436    where
437        T: BorshDeserialize + Send + Debug,
438    {
439        let latest_blockhash = self.client.get_latest_blockhash()?;
440        let transaction =
441            build_v1_transaction(instructions, payer, signers, latest_blockhash, config)?;
442
443        let (signature, slot) = self
444            .process_versioned_transaction_with_context(transaction)
445            .await?;
446
447        self.parse_event_from_instructions::<T>(instructions, signature, slot)
448    }
449
450    /// Tries to deserialize `T` from the data of each outer instruction and,
451    /// failing that, from the inner instructions of the confirmed transaction.
452    fn parse_event_from_instructions<T>(
453        &self,
454        instructions: &[Instruction],
455        signature: Signature,
456        slot: Slot,
457    ) -> Result<Option<(T, Signature, u64)>, RpcError>
458    where
459        T: BorshDeserialize + Send + Debug,
460    {
461        let mut parsed_event = None;
462        for instruction in instructions {
463            let ix_data = instruction.data.clone();
464            match T::deserialize(&mut &instruction.data[..]) {
465                Ok(e) => {
466                    parsed_event = Some(e);
467                    break;
468                }
469                Err(e) => {
470                    warn!(
471                        "Failed to parse event: {:?}, type: {:?}, ix data: {:?}",
472                        e,
473                        std::any::type_name::<T>(),
474                        ix_data
475                    );
476                }
477            }
478        }
479
480        if parsed_event.is_none() {
481            parsed_event = self.parse_inner_instructions::<T>(signature).ok();
482        }
483
484        let result = parsed_event.map(|e| (e, signature, slot));
485        Ok(result)
486    }
487}
488
489impl LightClient {
490    #[allow(clippy::result_large_err)]
491    fn parse_inner_instructions<T: BorshDeserialize>(
492        &self,
493        signature: Signature,
494    ) -> Result<T, RpcError> {
495        let rpc_transaction_config = RpcTransactionConfig {
496            encoding: Some(UiTransactionEncoding::Base64),
497            commitment: Some(self.client.commitment()),
498            max_supported_transaction_version: Some(1),
499        };
500        let transaction = self
501            .client
502            .get_transaction_with_config(&signature, rpc_transaction_config)
503            .map_err(|e| RpcError::CustomError(e.to_string()))?;
504        let meta = transaction.transaction.meta.as_ref().ok_or_else(|| {
505            RpcError::CustomError("Transaction missing metadata information".to_string())
506        })?;
507        if meta.status.is_err() {
508            return Err(RpcError::CustomError(
509                "Transaction status indicates an error".to_string(),
510            ));
511        }
512
513        let inner_instructions = match &meta.inner_instructions {
514            OptionSerializer::Some(i) => i,
515            OptionSerializer::None => {
516                return Err(RpcError::CustomError(
517                    "No inner instructions found".to_string(),
518                ));
519            }
520            OptionSerializer::Skip => {
521                return Err(RpcError::CustomError(
522                    "No inner instructions found".to_string(),
523                ));
524            }
525        };
526
527        for ix in inner_instructions.iter() {
528            for ui_instruction in ix.instructions.iter() {
529                match ui_instruction {
530                    UiInstruction::Compiled(ui_compiled_instruction) => {
531                        let data = bs58::decode(&ui_compiled_instruction.data)
532                            .into_vec()
533                            .map_err(|_| {
534                                RpcError::CustomError(
535                                    "Failed to decode instruction data".to_string(),
536                                )
537                            })?;
538
539                        match T::try_from_slice(data.as_slice()) {
540                            Ok(parsed_data) => return Ok(parsed_data),
541                            Err(e) => {
542                                warn!("Failed to parse inner instruction: {:?}", e);
543                            }
544                        }
545                    }
546                    UiInstruction::Parsed(_) => {
547                        println!("Parsed instructions are not implemented yet");
548                    }
549                }
550            }
551        }
552        Err(RpcError::CustomError(
553            "Failed to find any parseable inner instructions".to_string(),
554        ))
555    }
556
557    /// Instantly advances the validator to the given slot using surfpool's
558    /// `surfnet_timeTravel` RPC method. This is much faster than polling
559    /// `get_slot` in a loop and is intended for testing against surfpool.
560    ///
561    /// Returns the `EpochInfo` after the time travel, or an error if the
562    /// RPC call fails (e.g. when not running against surfpool).
563    pub async fn warp_to_slot(&self, slot: Slot) -> Result<serde_json::Value, RpcError> {
564        let url = self.client.url();
565        let body = serde_json::json!({
566            "jsonrpc": "2.0",
567            "id": 1,
568            "method": "surfnet_timeTravel",
569            "params": [{ "absoluteSlot": slot }]
570        });
571        let response = reqwest::Client::new()
572            .post(url)
573            .json(&body)
574            .send()
575            .await
576            .map_err(|e| RpcError::CustomError(format!("warp_to_slot failed: {e}")))?;
577        let result: serde_json::Value = response
578            .json()
579            .await
580            .map_err(|e| RpcError::CustomError(format!("warp_to_slot response error: {e}")))?;
581        Ok(result)
582    }
583}
584
585#[async_trait]
586impl Rpc for LightClient {
587    async fn new(config: LightClientConfig) -> Result<Self, RpcError>
588    where
589        Self: Sized,
590    {
591        Self::new_with_retry(config, None).await
592    }
593
594    fn get_payer(&self) -> &Keypair {
595        &self.payer
596    }
597
598    fn get_url(&self) -> String {
599        self.client.url()
600    }
601
602    async fn health(&self) -> Result<(), RpcError> {
603        self.retry(|| async { self.client.get_health().map_err(RpcError::from) })
604            .await
605    }
606
607    async fn get_program_accounts(
608        &self,
609        program_id: &Pubkey,
610    ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
611        self.retry(|| async {
612            self.client
613                .get_program_accounts(program_id)
614                .map_err(RpcError::from)
615        })
616        .await
617    }
618
619    async fn get_program_accounts_with_discriminator(
620        &self,
621        program_id: &Pubkey,
622        discriminator: &[u8],
623    ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
624        use solana_rpc_client_api::{
625            config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
626            filter::{Memcmp, RpcFilterType},
627        };
628
629        let discriminator = discriminator.to_vec();
630        self.retry(|| async {
631            let config = RpcProgramAccountsConfig {
632                filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
633                    0,
634                    &discriminator,
635                ))]),
636                account_config: RpcAccountInfoConfig {
637                    encoding: Some(solana_account_decoder_client_types::UiAccountEncoding::Base64),
638                    commitment: Some(self.client.commitment()),
639                    ..Default::default()
640                },
641                ..Default::default()
642            };
643            let ui_accounts = self
644                .client
645                .get_program_ui_accounts_with_config(program_id, config)
646                .map_err(RpcError::from)?;
647            ui_accounts
648                .into_iter()
649                .map(|(pubkey, ui_account)| {
650                    ui_account
651                        .to_account()
652                        .map(|account| (pubkey, account))
653                        .ok_or_else(|| {
654                            RpcError::CustomError(format!(
655                                "Failed to decode account {pubkey} from program accounts response"
656                            ))
657                        })
658                })
659                .collect::<Result<Vec<(Pubkey, Account)>, RpcError>>()
660        })
661        .await
662    }
663
664    async fn process_transaction(
665        &mut self,
666        transaction: Transaction,
667    ) -> Result<Signature, RpcError> {
668        self.retry(|| async {
669            self.client
670                .send_and_confirm_transaction(&transaction)
671                .map_err(RpcError::from)
672        })
673        .await
674    }
675
676    async fn process_versioned_transaction(
677        &mut self,
678        transaction: VersionedTransaction,
679    ) -> Result<Signature, RpcError> {
680        self.client
681            .send_and_confirm_transaction(&transaction)
682            .map_err(RpcError::from)
683    }
684
685    async fn process_transaction_with_context(
686        &mut self,
687        transaction: Transaction,
688    ) -> Result<(Signature, Slot), RpcError> {
689        self.retry(|| async {
690            let signature = self.client.send_and_confirm_transaction(&transaction)?;
691            let sig_info = self.client.get_signature_statuses(&[signature])?;
692            let slot = sig_info
693                .value
694                .first()
695                .and_then(|s| s.as_ref())
696                .map(|s| s.slot)
697                .ok_or_else(|| RpcError::CustomError("Failed to get slot".into()))?;
698            Ok((signature, slot))
699        })
700        .await
701    }
702
703    async fn confirm_transaction(&self, signature: Signature) -> Result<bool, RpcError> {
704        self.retry(|| async {
705            self.client
706                .confirm_transaction(&signature)
707                .map_err(RpcError::from)
708        })
709        .await
710    }
711
712    async fn get_account(&self, address: Pubkey) -> Result<Option<Account>, RpcError> {
713        self.retry(|| async {
714            self.client
715                .get_account_with_commitment(&address, self.client.commitment())
716                .map(|response| response.value)
717                .map_err(RpcError::from)
718        })
719        .await
720    }
721
722    async fn get_multiple_accounts(
723        &self,
724        addresses: &[Pubkey],
725    ) -> Result<Vec<Option<Account>>, RpcError> {
726        self.retry(|| async {
727            self.client
728                .get_multiple_accounts(addresses)
729                .map_err(RpcError::from)
730        })
731        .await
732    }
733
734    async fn get_minimum_balance_for_rent_exemption(
735        &self,
736        data_len: usize,
737    ) -> Result<u64, RpcError> {
738        self.retry(|| async {
739            self.client
740                .get_minimum_balance_for_rent_exemption(data_len)
741                .map_err(RpcError::from)
742        })
743        .await
744    }
745
746    async fn airdrop_lamports(
747        &mut self,
748        to: &Pubkey,
749        lamports: u64,
750    ) -> Result<Signature, RpcError> {
751        self.retry(|| async {
752            let signature = self
753                .client
754                .request_airdrop(to, lamports)
755                .map_err(RpcError::ClientError)?;
756            self.retry(|| async {
757                if self
758                    .client
759                    .confirm_transaction_with_commitment(&signature, self.client.commitment())?
760                    .value
761                {
762                    Ok(())
763                } else {
764                    Err(RpcError::CustomError("Airdrop not confirmed".into()))
765                }
766            })
767            .await?;
768
769            Ok(signature)
770        })
771        .await
772    }
773
774    async fn get_balance(&self, pubkey: &Pubkey) -> Result<u64, RpcError> {
775        self.retry(|| async { self.client.get_balance(pubkey).map_err(RpcError::from) })
776            .await
777    }
778
779    async fn get_latest_blockhash(&mut self) -> Result<(Hash, u64), RpcError> {
780        self.retry(|| async {
781            self.client
782                // Confirmed commitments land more reliably than finalized
783                // https://www.helius.dev/blog/how-to-deal-with-blockhash-errors-on-solana#how-to-deal-with-blockhash-errors
784                .get_latest_blockhash_with_commitment(CommitmentConfig::confirmed())
785                .map_err(RpcError::from)
786        })
787        .await
788    }
789
790    async fn get_block_height(&self) -> Result<u64, RpcError> {
791        self.retry(|| async { self.client.get_block_height().map_err(RpcError::from) })
792            .await
793    }
794
795    async fn get_slot(&self) -> Result<u64, RpcError> {
796        self.retry(|| async { self.client.get_slot().map_err(RpcError::from) })
797            .await
798    }
799
800    async fn send_transaction(&self, transaction: &Transaction) -> Result<Signature, RpcError> {
801        self.retry(|| async {
802            self.client
803                .send_transaction_with_config(
804                    transaction,
805                    RpcSendTransactionConfig {
806                        skip_preflight: true,
807                        max_retries: Some(self.retry_config.max_retries as usize),
808                        ..Default::default()
809                    },
810                )
811                .map_err(RpcError::from)
812        })
813        .await
814    }
815
816    async fn send_transaction_with_config(
817        &self,
818        transaction: &Transaction,
819        config: RpcSendTransactionConfig,
820    ) -> Result<Signature, RpcError> {
821        self.retry(|| async {
822            self.client
823                .send_transaction_with_config(transaction, config)
824                .map_err(RpcError::from)
825        })
826        .await
827    }
828
829    async fn send_versioned_transaction_with_config(
830        &self,
831        transaction: &VersionedTransaction,
832        config: RpcSendTransactionConfig,
833    ) -> Result<Signature, RpcError> {
834        self.retry(|| async {
835            self.client
836                .send_transaction_with_config(transaction, config)
837                .map_err(RpcError::from)
838        })
839        .await
840    }
841
842    async fn get_transaction_slot(&self, signature: &Signature) -> Result<u64, RpcError> {
843        self.retry(|| async {
844            Ok(self
845                .client
846                .get_transaction_with_config(
847                    signature,
848                    RpcTransactionConfig {
849                        encoding: Some(UiTransactionEncoding::Base64),
850                        commitment: Some(self.client.commitment()),
851                        max_supported_transaction_version: Some(1),
852                    },
853                )
854                .map_err(RpcError::from)?
855                .slot)
856        })
857        .await
858    }
859
860    async fn get_signature_statuses(
861        &self,
862        signatures: &[Signature],
863    ) -> Result<Vec<Option<TransactionStatus>>, RpcError> {
864        self.retry(|| async {
865            self.client
866                .get_signature_statuses(signatures)
867                .map(|response| response.value)
868                .map_err(RpcError::from)
869        })
870        .await
871    }
872
873    async fn create_and_send_transaction_with_event<T>(
874        &mut self,
875        instructions: &[Instruction],
876        payer: &Pubkey,
877        signers: &[&Keypair],
878    ) -> Result<Option<(T, Signature, u64)>, RpcError>
879    where
880        T: BorshDeserialize + Send + Debug,
881    {
882        self._create_and_send_transaction_with_event::<T>(instructions, payer, signers)
883            .await
884    }
885
886    async fn create_and_send_transaction_with_public_event(
887        &mut self,
888        instructions: &[Instruction],
889        payer: &Pubkey,
890        signers: &[&Keypair],
891    ) -> Result<Option<(PublicTransactionEvent, Signature, Slot)>, RpcError> {
892        let parsed_event = self
893            ._create_and_send_transaction_with_batched_event(instructions, payer, signers)
894            .await?;
895
896        let event = parsed_event.and_then(|(e, signature, slot)| {
897            e.first()
898                .map(|first| (first.event.clone(), signature, slot))
899        });
900        Ok(event)
901    }
902
903    async fn create_and_send_transaction_with_batched_event(
904        &mut self,
905        instructions: &[Instruction],
906        payer: &Pubkey,
907        signers: &[&Keypair],
908    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
909        self._create_and_send_transaction_with_batched_event(instructions, payer, signers)
910            .await
911    }
912
913    async fn create_and_send_v1_transaction_with_event<T>(
914        &mut self,
915        instructions: &[Instruction],
916        payer: &Pubkey,
917        signers: &[&Keypair],
918        config: TransactionConfig,
919    ) -> Result<Option<(T, Signature, Slot)>, RpcError>
920    where
921        T: BorshDeserialize + Send + Debug,
922    {
923        self._create_and_send_v1_transaction_with_event::<T>(instructions, payer, signers, config)
924            .await
925    }
926
927    async fn create_and_send_v1_transaction_with_batched_event(
928        &mut self,
929        instructions: &[Instruction],
930        payer: &Pubkey,
931        signers: &[&Keypair],
932        config: TransactionConfig,
933    ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
934        self._create_and_send_v1_transaction_with_batched_event(
935            instructions,
936            payer,
937            signers,
938            config,
939        )
940        .await
941    }
942
943    async fn process_versioned_transaction_with_context(
944        &mut self,
945        transaction: VersionedTransaction,
946    ) -> Result<(Signature, Slot), RpcError> {
947        self.retry(|| async {
948            let signature = self.client.send_and_confirm_transaction(&transaction)?;
949            let sig_info = self.client.get_signature_statuses(&[signature])?;
950            let slot = sig_info
951                .value
952                .first()
953                .and_then(|s| s.as_ref())
954                .map(|s| s.slot)
955                .ok_or_else(|| RpcError::CustomError("Failed to get slot".into()))?;
956            Ok((signature, slot))
957        })
958        .await
959    }
960
961    /// Creates and sends a versioned transaction with address lookup tables.
962    ///
963    /// `address_lookup_tables` must contain pre-fetched `AddressLookupTableAccount` values
964    /// loaded from the chain. Callers are responsible for resolving these accounts before
965    /// calling this method. Unresolved or missing lookup tables will cause compilation to fail.
966    ///
967    /// Returns `RpcError::TransactionBuildError` on message compilation failure,
968    /// `RpcError::SigningError` on signing failure.
969    async fn create_and_send_versioned_transaction<'a>(
970        &'a mut self,
971        instructions: &'a [Instruction],
972        payer: &'a Pubkey,
973        signers: &'a [&'a Keypair],
974        address_lookup_tables: &'a [AddressLookupTableAccount],
975    ) -> Result<Signature, RpcError> {
976        self.retry(|| async {
977            let (blockhash, _) = self
978                .client
979                .get_latest_blockhash_with_commitment(CommitmentConfig::confirmed())
980                .map_err(RpcError::from)?;
981
982            let message =
983                v0::Message::try_compile(payer, instructions, address_lookup_tables, blockhash)
984                    .map_err(|e| {
985                        RpcError::TransactionBuildError(format!(
986                            "Failed to compile v0 message: {}",
987                            e
988                        ))
989                    })?;
990
991            let versioned_message = VersionedMessage::V0(message);
992
993            let transaction = VersionedTransaction::try_new(versioned_message, signers)
994                .map_err(|e| RpcError::SigningError(e.to_string()))?;
995
996            self.client
997                .send_and_confirm_transaction(&transaction)
998                .map_err(RpcError::from)
999        })
1000        .await
1001    }
1002
1003    fn indexer(&self) -> Result<&impl Indexer, RpcError> {
1004        self.indexer.as_ref().ok_or(RpcError::IndexerNotInitialized)
1005    }
1006
1007    fn indexer_mut(&mut self) -> Result<&mut impl Indexer, RpcError> {
1008        self.indexer.as_mut().ok_or(RpcError::IndexerNotInitialized)
1009    }
1010
1011    /// Fetch the latest state tree addresses from the cluster.
1012    ///
1013    /// When the `v2` feature is enabled, returns the default V2
1014    /// batched state trees.
1015    /// When `v2` is disabled, uses V1 lookup-table resolution or
1016    /// localnet defaults.
1017    async fn get_latest_active_state_trees(&mut self) -> Result<Vec<TreeInfo>, RpcError> {
1018        // V2: the default batched state trees are the same on every network.
1019        #[cfg(feature = "v2")]
1020        {
1021            let trees = default_v2_state_trees().to_vec();
1022            self.state_merkle_trees = trees.clone();
1023            return Ok(trees);
1024        }
1025
1026        // V1 path: network-dependent resolution.
1027        #[cfg(not(feature = "v2"))]
1028        {
1029            let network = self.detect_network();
1030
1031            if matches!(network, RpcUrl::Localnet) {
1032                let default_trees = vec![TreeInfo {
1033                    tree: pubkey!("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT"),
1034                    queue: pubkey!("nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148"),
1035                    cpi_context: Some(pubkey!("cpi1uHzrEhBG733DoEJNgHCyRS3XmmyVNZx5fonubE4")),
1036                    next_tree_info: None,
1037                    tree_type: TreeType::StateV1,
1038                }];
1039                self.state_merkle_trees = default_trees.clone();
1040                return Ok(default_trees);
1041            }
1042
1043            let (mainnet_tables, devnet_tables) = default_state_tree_lookup_tables();
1044
1045            let lookup_tables = match network {
1046                RpcUrl::Devnet | RpcUrl::Testnet | RpcUrl::ZKTestnet => &devnet_tables,
1047                _ => &mainnet_tables,
1048            };
1049
1050            let res = get_light_state_tree_infos(
1051                self,
1052                &lookup_tables[0].state_tree_lookup_table,
1053                &lookup_tables[0].nullify_table,
1054            )
1055            .await?;
1056            self.state_merkle_trees = res.clone();
1057            Ok(res)
1058        }
1059    }
1060
1061    /// Returns list of state tree infos.
1062    fn get_state_tree_infos(&self) -> Vec<TreeInfo> {
1063        #[cfg(feature = "v2")]
1064        {
1065            default_v2_state_trees().to_vec()
1066        }
1067        #[cfg(not(feature = "v2"))]
1068        {
1069            self.state_merkle_trees.to_vec()
1070        }
1071    }
1072
1073    /// Gets a random active state tree.
1074    fn get_random_state_tree_info(&self) -> Result<TreeInfo, RpcError> {
1075        #[cfg(feature = "v2")]
1076        {
1077            use rand::Rng;
1078            let mut rng = rand::thread_rng();
1079            let trees = default_v2_state_trees();
1080            Ok(trees[rng.gen_range(0..trees.len())])
1081        }
1082
1083        #[cfg(not(feature = "v2"))]
1084        {
1085            let mut rng = rand::thread_rng();
1086            let filtered_trees: Vec<TreeInfo> = self
1087                .state_merkle_trees
1088                .iter()
1089                .filter(|tree| tree.tree_type == TreeType::StateV1)
1090                .copied()
1091                .collect();
1092            select_state_tree_info(&mut rng, &filtered_trees)
1093        }
1094    }
1095
1096    /// Gets a random v1 state tree.
1097    /// State trees are cached and have to be fetched or set.
1098    fn get_random_state_tree_info_v1(&self) -> Result<TreeInfo, RpcError> {
1099        let mut rng = rand::thread_rng();
1100        let v1_trees: Vec<TreeInfo> = self
1101            .state_merkle_trees
1102            .iter()
1103            .filter(|tree| tree.tree_type == TreeType::StateV1)
1104            .copied()
1105            .collect();
1106        select_state_tree_info(&mut rng, &v1_trees)
1107    }
1108
1109    fn get_address_tree_v1(&self) -> TreeInfo {
1110        TreeInfo {
1111            tree: pubkey!("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2"),
1112            queue: pubkey!("aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F"),
1113            cpi_context: None,
1114            next_tree_info: None,
1115            tree_type: TreeType::AddressV1,
1116        }
1117    }
1118
1119    fn get_address_tree_v2(&self) -> TreeInfo {
1120        TreeInfo {
1121            tree: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
1122            queue: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
1123            cpi_context: None,
1124            next_tree_info: None,
1125            tree_type: TreeType::AddressV2,
1126        }
1127    }
1128}
1129
1130impl MerkleTreeExt for LightClient {}
1131
1132/// Selects a random state tree from the provided list.
1133///
1134/// This function should be used together with `get_state_tree_infos()` to first
1135/// retrieve the list of state trees, then select one randomly.
1136///
1137/// # Arguments
1138/// * `rng` - A mutable reference to a random number generator
1139/// * `state_trees` - A slice of `TreeInfo` representing state trees
1140///
1141/// # Returns
1142/// A randomly selected `TreeInfo` from the provided list, or an error if the list is empty
1143///
1144/// # Errors
1145/// Returns `RpcError::NoStateTreesAvailable` if the provided slice is empty
1146///
1147/// # Example
1148/// ```ignore
1149/// use rand::thread_rng;
1150/// let tree_infos = client.get_state_tree_infos();
1151/// let mut rng = thread_rng();
1152/// let selected_tree = select_state_tree_info(&mut rng, &tree_infos)?;
1153/// ```
1154pub fn select_state_tree_info<R: rand::Rng>(
1155    rng: &mut R,
1156    state_trees: &[TreeInfo],
1157) -> Result<TreeInfo, RpcError> {
1158    if state_trees.is_empty() {
1159        return Err(RpcError::NoStateTreesAvailable);
1160    }
1161
1162    Ok(state_trees[rng.gen_range(0..state_trees.len())])
1163}