1use std::{
2 fmt::{Debug, Display, Formatter},
3 time::Duration,
4};
5
6use async_trait::async_trait;
7use borsh::BorshDeserialize;
8use bs58;
9use light_sdk_types::lca::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_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::{errors::RpcError, merkle_tree::MerkleTreeExt, Rpc},
40};
41
42#[cfg(feature = "v2")]
44pub(crate) fn default_v2_state_trees() -> [TreeInfo; 5] {
45 [
46 TreeInfo {
47 tree: pubkey!("bmt1LryLZUMmF7ZtqESaw7wifBXLfXHQYoE4GAmrahU"),
48 queue: pubkey!("oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto"),
49 cpi_context: Some(pubkey!("cpi15BoVPKgEPw5o8wc2T816GE7b378nMXnhH3Xbq4y")),
50 next_tree_info: None,
51 tree_type: TreeType::StateV2,
52 },
53 TreeInfo {
54 tree: pubkey!("bmt2UxoBxB9xWev4BkLvkGdapsz6sZGkzViPNph7VFi"),
55 queue: pubkey!("oq2UkeMsJLfXt2QHzim242SUi3nvjJs8Pn7Eac9H9vg"),
56 cpi_context: Some(pubkey!("cpi2yGapXUR3As5SjnHBAVvmApNiLsbeZpF3euWnW6B")),
57 next_tree_info: None,
58 tree_type: TreeType::StateV2,
59 },
60 TreeInfo {
61 tree: pubkey!("bmt3ccLd4bqSVZVeCJnH1F6C8jNygAhaDfxDwePyyGb"),
62 queue: pubkey!("oq3AxjekBWgo64gpauB6QtuZNesuv19xrhaC1ZM1THQ"),
63 cpi_context: Some(pubkey!("cpi3mbwMpSX8FAGMZVP85AwxqCaQMfEk9Em1v8QK9Rf")),
64 next_tree_info: None,
65 tree_type: TreeType::StateV2,
66 },
67 TreeInfo {
68 tree: pubkey!("bmt4d3p1a4YQgk9PeZv5s4DBUmbF5NxqYpk9HGjQsd8"),
69 queue: pubkey!("oq4ypwvVGzCUMoiKKHWh4S1SgZJ9vCvKpcz6RT6A8dq"),
70 cpi_context: Some(pubkey!("cpi4yyPDc4bCgHAnsenunGA8Y77j3XEDyjgfyCKgcoc")),
71 next_tree_info: None,
72 tree_type: TreeType::StateV2,
73 },
74 TreeInfo {
75 tree: pubkey!("bmt5yU97jC88YXTuSukYHa8Z5Bi2ZDUtmzfkDTA2mG2"),
76 queue: pubkey!("oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P"),
77 cpi_context: Some(pubkey!("cpi5ZTjdgYpZ1Xr7B1cMLLUE81oTtJbNNAyKary2nV6")),
78 next_tree_info: None,
79 tree_type: TreeType::StateV2,
80 },
81 ]
82}
83
84pub enum RpcUrl {
85 Testnet,
86 Devnet,
87 Localnet,
88 ZKTestnet,
89 Custom(String),
90}
91
92impl Display for RpcUrl {
93 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
94 let str = match self {
95 RpcUrl::Testnet => "https://api.testnet.solana.com".to_string(),
96 RpcUrl::Devnet => "https://api.devnet.solana.com".to_string(),
97 RpcUrl::Localnet => "http://localhost:8899".to_string(),
98 RpcUrl::ZKTestnet => "https://zk-testnet.helius.dev:8899".to_string(),
99 RpcUrl::Custom(url) => url.clone(),
100 };
101 write!(f, "{}", str)
102 }
103}
104
105#[derive(Clone, Debug, Copy)]
106pub struct RetryConfig {
107 pub max_retries: u32,
108 pub retry_delay: Duration,
109 pub timeout: Duration,
112}
113
114impl Default for RetryConfig {
115 fn default() -> Self {
116 RetryConfig {
117 max_retries: 10,
118 retry_delay: Duration::from_secs(1),
119 timeout: Duration::from_secs(60),
120 }
121 }
122}
123
124#[allow(dead_code)]
125pub struct LightClient {
126 pub client: RpcClient,
127 pub payer: Keypair,
128 pub retry_config: RetryConfig,
129 pub indexer: Option<PhotonIndexer>,
130 pub state_merkle_trees: Vec<TreeInfo>,
131}
132
133impl Debug for LightClient {
134 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
135 write!(f, "LightClient {{ client: {:?} }}", self.client.url())
136 }
137}
138
139impl LightClient {
140 pub async fn new_with_retry(
141 config: LightClientConfig,
142 retry_config: Option<RetryConfig>,
143 ) -> Result<Self, RpcError> {
144 let payer = Keypair::new();
145 let commitment_config = config
146 .commitment_config
147 .unwrap_or(CommitmentConfig::confirmed());
148 let client = RpcClient::new_with_commitment(config.url.to_string(), commitment_config);
149 let retry_config = retry_config.unwrap_or_default();
150
151 let indexer = config.photon_url.map(PhotonIndexer::new);
152
153 let mut new = Self {
154 client,
155 payer,
156 retry_config,
157 indexer,
158 state_merkle_trees: Vec::new(),
159 };
160 if config.fetch_active_tree {
161 new.get_latest_active_state_trees().await?;
162 }
163 Ok(new)
164 }
165
166 pub fn add_indexer(&mut self, url: String) {
167 self.indexer = Some(PhotonIndexer::new(url));
168 }
169
170 #[cfg(not(feature = "v2"))]
172 fn detect_network(&self) -> RpcUrl {
173 let url = self.client.url();
174
175 if url.contains("devnet") {
176 RpcUrl::Devnet
177 } else if url.contains("testnet") {
178 RpcUrl::Testnet
179 } else if url.contains("localhost") || url.contains("127.0.0.1") {
180 RpcUrl::Localnet
181 } else if url.contains("zk-testnet") {
182 RpcUrl::ZKTestnet
183 } else {
184 RpcUrl::Custom(url.to_string())
186 }
187 }
188
189 async fn retry<F, Fut, T>(&self, operation: F) -> Result<T, RpcError>
190 where
191 F: Fn() -> Fut,
192 Fut: std::future::Future<Output = Result<T, RpcError>>,
193 {
194 let mut attempts = 0;
195 let start_time = Instant::now();
196 loop {
197 match operation().await {
198 Ok(result) => return Ok(result),
199 Err(e) => {
200 let retry = self.should_retry(&e);
201 if retry {
202 attempts += 1;
203 if attempts >= self.retry_config.max_retries
204 || start_time.elapsed() >= self.retry_config.timeout
205 {
206 return Err(e);
207 }
208 warn!(
209 "Operation failed, retrying in {:?} (attempt {}/{}): {:?}",
210 self.retry_config.retry_delay,
211 attempts,
212 self.retry_config.max_retries,
213 e
214 );
215 sleep(self.retry_config.retry_delay).await;
216 } else {
217 return Err(e);
218 }
219 }
220 }
221 }
222 }
223
224 async fn _create_and_send_transaction_with_batched_event(
225 &mut self,
226 instructions: &[Instruction],
227 payer: &Pubkey,
228 signers: &[&Keypair],
229 ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
230 let latest_blockhash = self.client.get_latest_blockhash()?;
231
232 let mut instructions_vec = vec![
233 solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit(
234 1_000_000,
235 ),
236 ];
237 instructions_vec.extend_from_slice(instructions);
238
239 let transaction = Transaction::new_signed_with_payer(
240 instructions_vec.as_slice(),
241 Some(payer),
242 signers,
243 latest_blockhash,
244 );
245
246 let (signature, slot) = self
247 .process_transaction_with_context(transaction.clone())
248 .await?;
249
250 let mut vec = Vec::new();
251 let mut vec_accounts = Vec::new();
252 let mut program_ids = Vec::new();
253 instructions_vec.iter().for_each(|x| {
254 program_ids.push(light_sdk_types::lca::Pubkey::new_from_array(
255 x.program_id.to_bytes(),
256 ));
257 vec.push(x.data.clone());
258 vec_accounts.push(
259 x.accounts
260 .iter()
261 .map(|x| light_sdk_types::lca::Pubkey::new_from_array(x.pubkey.to_bytes()))
262 .collect(),
263 );
264 });
265 {
266 let rpc_transaction_config = RpcTransactionConfig {
267 encoding: Some(UiTransactionEncoding::Base64),
268 commitment: Some(self.client.commitment()),
269 ..Default::default()
270 };
271 let transaction = self
272 .client
273 .get_transaction_with_config(&signature, rpc_transaction_config)
274 .map_err(|e| RpcError::CustomError(e.to_string()))?;
275 let decoded_transaction = transaction
276 .transaction
277 .transaction
278 .decode()
279 .clone()
280 .ok_or_else(|| {
281 RpcError::CustomError(
282 "Failed to decode transaction from RPC response".to_string(),
283 )
284 })?;
285 let account_keys = decoded_transaction.message.static_account_keys();
286 let meta = transaction.transaction.meta.as_ref().ok_or_else(|| {
287 RpcError::CustomError("Transaction missing metadata information".to_string())
288 })?;
289 if meta.status.is_err() {
290 return Err(RpcError::CustomError(
291 "Transaction status indicates an error".to_string(),
292 ));
293 }
294
295 let inner_instructions = match &meta.inner_instructions {
296 OptionSerializer::Some(i) => i,
297 OptionSerializer::None => {
298 return Err(RpcError::CustomError(
299 "No inner instructions found".to_string(),
300 ));
301 }
302 OptionSerializer::Skip => {
303 return Err(RpcError::CustomError(
304 "No inner instructions found".to_string(),
305 ));
306 }
307 };
308
309 for ix in inner_instructions.iter() {
310 for ui_instruction in ix.instructions.iter() {
311 match ui_instruction {
312 UiInstruction::Compiled(ui_compiled_instruction) => {
313 let accounts = &ui_compiled_instruction.accounts;
314 let data = bs58::decode(&ui_compiled_instruction.data)
315 .into_vec()
316 .map_err(|_| {
317 RpcError::CustomError(
318 "Failed to decode instruction data".to_string(),
319 )
320 })?;
321 vec.push(data);
322 program_ids.push(light_sdk_types::lca::Pubkey::new_from_array(
323 account_keys[ui_compiled_instruction.program_id_index as usize]
324 .to_bytes(),
325 ));
326 vec_accounts.push(
327 accounts
328 .iter()
329 .map(|x| {
330 light_sdk_types::lca::Pubkey::new_from_array(
331 account_keys[(*x) as usize].to_bytes(),
332 )
333 })
334 .collect(),
335 );
336 }
337 UiInstruction::Parsed(_) => {
338 println!("Parsed instructions are not implemented yet");
339 }
340 }
341 }
342 }
343 }
344 let parsed_event =
345 event_from_light_transaction(program_ids.as_slice(), vec.as_slice(), vec_accounts)
346 .map_err(|e| RpcError::CustomError(format!("Failed to parse event: {e:?}")))?;
347 let event = parsed_event.map(|e| (e, signature, slot));
348 Ok(event)
349 }
350
351 async fn _create_and_send_transaction_with_event<T>(
352 &mut self,
353 instructions: &[Instruction],
354 payer: &Pubkey,
355 signers: &[&Keypair],
356 ) -> Result<Option<(T, Signature, u64)>, RpcError>
357 where
358 T: BorshDeserialize + Send + Debug,
359 {
360 let latest_blockhash = self.client.get_latest_blockhash()?;
361
362 let mut instructions_vec = vec![
363 solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit(
364 1_000_000,
365 ),
366 ];
367 instructions_vec.extend_from_slice(instructions);
368
369 let transaction = Transaction::new_signed_with_payer(
370 instructions_vec.as_slice(),
371 Some(payer),
372 signers,
373 latest_blockhash,
374 );
375
376 let (signature, slot) = self
377 .process_transaction_with_context(transaction.clone())
378 .await?;
379
380 let mut parsed_event = None;
381 for instruction in &transaction.message.instructions {
382 let ix_data = instruction.data.clone();
383 match T::deserialize(&mut &instruction.data[..]) {
384 Ok(e) => {
385 parsed_event = Some(e);
386 break;
387 }
388 Err(e) => {
389 warn!(
390 "Failed to parse event: {:?}, type: {:?}, ix data: {:?}",
391 e,
392 std::any::type_name::<T>(),
393 ix_data
394 );
395 }
396 }
397 }
398
399 if parsed_event.is_none() {
400 parsed_event = self.parse_inner_instructions::<T>(signature).ok();
401 }
402
403 let result = parsed_event.map(|e| (e, signature, slot));
404 Ok(result)
405 }
406}
407
408impl LightClient {
409 #[allow(clippy::result_large_err)]
410 fn parse_inner_instructions<T: BorshDeserialize>(
411 &self,
412 signature: Signature,
413 ) -> Result<T, RpcError> {
414 let rpc_transaction_config = RpcTransactionConfig {
415 encoding: Some(UiTransactionEncoding::Base64),
416 commitment: Some(self.client.commitment()),
417 ..Default::default()
418 };
419 let transaction = self
420 .client
421 .get_transaction_with_config(&signature, rpc_transaction_config)
422 .map_err(|e| RpcError::CustomError(e.to_string()))?;
423 let meta = transaction.transaction.meta.as_ref().ok_or_else(|| {
424 RpcError::CustomError("Transaction missing metadata information".to_string())
425 })?;
426 if meta.status.is_err() {
427 return Err(RpcError::CustomError(
428 "Transaction status indicates an error".to_string(),
429 ));
430 }
431
432 let inner_instructions = match &meta.inner_instructions {
433 OptionSerializer::Some(i) => i,
434 OptionSerializer::None => {
435 return Err(RpcError::CustomError(
436 "No inner instructions found".to_string(),
437 ));
438 }
439 OptionSerializer::Skip => {
440 return Err(RpcError::CustomError(
441 "No inner instructions found".to_string(),
442 ));
443 }
444 };
445
446 for ix in inner_instructions.iter() {
447 for ui_instruction in ix.instructions.iter() {
448 match ui_instruction {
449 UiInstruction::Compiled(ui_compiled_instruction) => {
450 let data = bs58::decode(&ui_compiled_instruction.data)
451 .into_vec()
452 .map_err(|_| {
453 RpcError::CustomError(
454 "Failed to decode instruction data".to_string(),
455 )
456 })?;
457
458 match T::try_from_slice(data.as_slice()) {
459 Ok(parsed_data) => return Ok(parsed_data),
460 Err(e) => {
461 warn!("Failed to parse inner instruction: {:?}", e);
462 }
463 }
464 }
465 UiInstruction::Parsed(_) => {
466 println!("Parsed instructions are not implemented yet");
467 }
468 }
469 }
470 }
471 Err(RpcError::CustomError(
472 "Failed to find any parseable inner instructions".to_string(),
473 ))
474 }
475
476 pub async fn warp_to_slot(&self, slot: Slot) -> Result<serde_json::Value, RpcError> {
483 let url = self.client.url();
484 let body = serde_json::json!({
485 "jsonrpc": "2.0",
486 "id": 1,
487 "method": "surfnet_timeTravel",
488 "params": [{ "absoluteSlot": slot }]
489 });
490 let response = reqwest::Client::new()
491 .post(url)
492 .json(&body)
493 .send()
494 .await
495 .map_err(|e| RpcError::CustomError(format!("warp_to_slot failed: {e}")))?;
496 let result: serde_json::Value = response
497 .json()
498 .await
499 .map_err(|e| RpcError::CustomError(format!("warp_to_slot response error: {e}")))?;
500 Ok(result)
501 }
502}
503
504#[async_trait]
505impl Rpc for LightClient {
506 async fn new(config: LightClientConfig) -> Result<Self, RpcError>
507 where
508 Self: Sized,
509 {
510 Self::new_with_retry(config, None).await
511 }
512
513 fn get_payer(&self) -> &Keypair {
514 &self.payer
515 }
516
517 fn get_url(&self) -> String {
518 self.client.url()
519 }
520
521 async fn health(&self) -> Result<(), RpcError> {
522 self.retry(|| async { self.client.get_health().map_err(RpcError::from) })
523 .await
524 }
525
526 async fn get_program_accounts(
527 &self,
528 program_id: &Pubkey,
529 ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
530 self.retry(|| async {
531 self.client
532 .get_program_accounts(program_id)
533 .map_err(RpcError::from)
534 })
535 .await
536 }
537
538 async fn get_program_accounts_with_discriminator(
539 &self,
540 program_id: &Pubkey,
541 discriminator: &[u8],
542 ) -> Result<Vec<(Pubkey, Account)>, RpcError> {
543 use solana_rpc_client_api::{
544 config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
545 filter::{Memcmp, RpcFilterType},
546 };
547
548 let discriminator = discriminator.to_vec();
549 self.retry(|| async {
550 let config = RpcProgramAccountsConfig {
551 filters: Some(vec![RpcFilterType::Memcmp(Memcmp::new_base58_encoded(
552 0,
553 &discriminator,
554 ))]),
555 account_config: RpcAccountInfoConfig {
556 encoding: Some(solana_account_decoder_client_types::UiAccountEncoding::Base64),
557 commitment: Some(self.client.commitment()),
558 ..Default::default()
559 },
560 ..Default::default()
561 };
562 let ui_accounts = self
563 .client
564 .get_program_ui_accounts_with_config(program_id, config)
565 .map_err(RpcError::from)?;
566 ui_accounts
567 .into_iter()
568 .map(|(pubkey, ui_account)| {
569 ui_account
570 .to_account()
571 .map(|account| (pubkey, account))
572 .ok_or_else(|| {
573 RpcError::CustomError(format!(
574 "Failed to decode account {pubkey} from program accounts response"
575 ))
576 })
577 })
578 .collect::<Result<Vec<(Pubkey, Account)>, RpcError>>()
579 })
580 .await
581 }
582
583 async fn process_transaction(
584 &mut self,
585 transaction: Transaction,
586 ) -> Result<Signature, RpcError> {
587 self.retry(|| async {
588 self.client
589 .send_and_confirm_transaction(&transaction)
590 .map_err(RpcError::from)
591 })
592 .await
593 }
594
595 async fn process_versioned_transaction(
596 &mut self,
597 transaction: VersionedTransaction,
598 ) -> Result<Signature, RpcError> {
599 self.client
600 .send_and_confirm_transaction(&transaction)
601 .map_err(RpcError::from)
602 }
603
604 async fn process_transaction_with_context(
605 &mut self,
606 transaction: Transaction,
607 ) -> Result<(Signature, Slot), RpcError> {
608 self.retry(|| async {
609 let signature = self.client.send_and_confirm_transaction(&transaction)?;
610 let sig_info = self.client.get_signature_statuses(&[signature])?;
611 let slot = sig_info
612 .value
613 .first()
614 .and_then(|s| s.as_ref())
615 .map(|s| s.slot)
616 .ok_or_else(|| RpcError::CustomError("Failed to get slot".into()))?;
617 Ok((signature, slot))
618 })
619 .await
620 }
621
622 async fn confirm_transaction(&self, signature: Signature) -> Result<bool, RpcError> {
623 self.retry(|| async {
624 self.client
625 .confirm_transaction(&signature)
626 .map_err(RpcError::from)
627 })
628 .await
629 }
630
631 async fn get_account(&self, address: Pubkey) -> Result<Option<Account>, RpcError> {
632 self.retry(|| async {
633 self.client
634 .get_account_with_commitment(&address, self.client.commitment())
635 .map(|response| response.value)
636 .map_err(RpcError::from)
637 })
638 .await
639 }
640
641 async fn get_multiple_accounts(
642 &self,
643 addresses: &[Pubkey],
644 ) -> Result<Vec<Option<Account>>, RpcError> {
645 self.retry(|| async {
646 self.client
647 .get_multiple_accounts(addresses)
648 .map_err(RpcError::from)
649 })
650 .await
651 }
652
653 async fn get_minimum_balance_for_rent_exemption(
654 &self,
655 data_len: usize,
656 ) -> Result<u64, RpcError> {
657 self.retry(|| async {
658 self.client
659 .get_minimum_balance_for_rent_exemption(data_len)
660 .map_err(RpcError::from)
661 })
662 .await
663 }
664
665 async fn airdrop_lamports(
666 &mut self,
667 to: &Pubkey,
668 lamports: u64,
669 ) -> Result<Signature, RpcError> {
670 self.retry(|| async {
671 let signature = self
672 .client
673 .request_airdrop(to, lamports)
674 .map_err(RpcError::ClientError)?;
675 self.retry(|| async {
676 if self
677 .client
678 .confirm_transaction_with_commitment(&signature, self.client.commitment())?
679 .value
680 {
681 Ok(())
682 } else {
683 Err(RpcError::CustomError("Airdrop not confirmed".into()))
684 }
685 })
686 .await?;
687
688 Ok(signature)
689 })
690 .await
691 }
692
693 async fn get_balance(&self, pubkey: &Pubkey) -> Result<u64, RpcError> {
694 self.retry(|| async { self.client.get_balance(pubkey).map_err(RpcError::from) })
695 .await
696 }
697
698 async fn get_latest_blockhash(&mut self) -> Result<(Hash, u64), RpcError> {
699 self.retry(|| async {
700 self.client
701 .get_latest_blockhash_with_commitment(CommitmentConfig::confirmed())
704 .map_err(RpcError::from)
705 })
706 .await
707 }
708
709 async fn get_block_height(&self) -> Result<u64, RpcError> {
710 self.retry(|| async { self.client.get_block_height().map_err(RpcError::from) })
711 .await
712 }
713
714 async fn get_slot(&self) -> Result<u64, RpcError> {
715 self.retry(|| async { self.client.get_slot().map_err(RpcError::from) })
716 .await
717 }
718
719 async fn send_transaction(&self, transaction: &Transaction) -> Result<Signature, RpcError> {
720 self.retry(|| async {
721 self.client
722 .send_transaction_with_config(
723 transaction,
724 RpcSendTransactionConfig {
725 skip_preflight: true,
726 max_retries: Some(self.retry_config.max_retries as usize),
727 ..Default::default()
728 },
729 )
730 .map_err(RpcError::from)
731 })
732 .await
733 }
734
735 async fn send_transaction_with_config(
736 &self,
737 transaction: &Transaction,
738 config: RpcSendTransactionConfig,
739 ) -> Result<Signature, RpcError> {
740 self.retry(|| async {
741 self.client
742 .send_transaction_with_config(transaction, config)
743 .map_err(RpcError::from)
744 })
745 .await
746 }
747
748 async fn send_versioned_transaction_with_config(
749 &self,
750 transaction: &VersionedTransaction,
751 config: RpcSendTransactionConfig,
752 ) -> Result<Signature, RpcError> {
753 self.retry(|| async {
754 self.client
755 .send_transaction_with_config(transaction, config)
756 .map_err(RpcError::from)
757 })
758 .await
759 }
760
761 async fn get_transaction_slot(&self, signature: &Signature) -> Result<u64, RpcError> {
762 self.retry(|| async {
763 Ok(self
764 .client
765 .get_transaction_with_config(
766 signature,
767 RpcTransactionConfig {
768 encoding: Some(UiTransactionEncoding::Base64),
769 commitment: Some(self.client.commitment()),
770 ..Default::default()
771 },
772 )
773 .map_err(RpcError::from)?
774 .slot)
775 })
776 .await
777 }
778
779 async fn get_signature_statuses(
780 &self,
781 signatures: &[Signature],
782 ) -> Result<Vec<Option<TransactionStatus>>, RpcError> {
783 self.retry(|| async {
784 self.client
785 .get_signature_statuses(signatures)
786 .map(|response| response.value)
787 .map_err(RpcError::from)
788 })
789 .await
790 }
791
792 async fn create_and_send_transaction_with_event<T>(
793 &mut self,
794 instructions: &[Instruction],
795 payer: &Pubkey,
796 signers: &[&Keypair],
797 ) -> Result<Option<(T, Signature, u64)>, RpcError>
798 where
799 T: BorshDeserialize + Send + Debug,
800 {
801 self._create_and_send_transaction_with_event::<T>(instructions, payer, signers)
802 .await
803 }
804
805 async fn create_and_send_transaction_with_public_event(
806 &mut self,
807 instructions: &[Instruction],
808 payer: &Pubkey,
809 signers: &[&Keypair],
810 ) -> Result<Option<(PublicTransactionEvent, Signature, Slot)>, RpcError> {
811 let parsed_event = self
812 ._create_and_send_transaction_with_batched_event(instructions, payer, signers)
813 .await?;
814
815 let event = parsed_event.map(|(e, signature, slot)| (e[0].event.clone(), signature, slot));
816 Ok(event)
817 }
818
819 async fn create_and_send_transaction_with_batched_event(
820 &mut self,
821 instructions: &[Instruction],
822 payer: &Pubkey,
823 signers: &[&Keypair],
824 ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError> {
825 self._create_and_send_transaction_with_batched_event(instructions, payer, signers)
826 .await
827 }
828
829 async fn create_and_send_versioned_transaction<'a>(
838 &'a mut self,
839 instructions: &'a [Instruction],
840 payer: &'a Pubkey,
841 signers: &'a [&'a Keypair],
842 address_lookup_tables: &'a [AddressLookupTableAccount],
843 ) -> Result<Signature, RpcError> {
844 self.retry(|| async {
845 let (blockhash, _) = self
846 .client
847 .get_latest_blockhash_with_commitment(CommitmentConfig::confirmed())
848 .map_err(RpcError::from)?;
849
850 let message =
851 v0::Message::try_compile(payer, instructions, address_lookup_tables, blockhash)
852 .map_err(|e| {
853 RpcError::TransactionBuildError(format!(
854 "Failed to compile v0 message: {}",
855 e
856 ))
857 })?;
858
859 let versioned_message = VersionedMessage::V0(message);
860
861 let transaction = VersionedTransaction::try_new(versioned_message, signers)
862 .map_err(|e| RpcError::SigningError(e.to_string()))?;
863
864 self.client
865 .send_and_confirm_transaction(&transaction)
866 .map_err(RpcError::from)
867 })
868 .await
869 }
870
871 fn indexer(&self) -> Result<&impl Indexer, RpcError> {
872 self.indexer.as_ref().ok_or(RpcError::IndexerNotInitialized)
873 }
874
875 fn indexer_mut(&mut self) -> Result<&mut impl Indexer, RpcError> {
876 self.indexer.as_mut().ok_or(RpcError::IndexerNotInitialized)
877 }
878
879 async fn get_latest_active_state_trees(&mut self) -> Result<Vec<TreeInfo>, RpcError> {
886 #[cfg(feature = "v2")]
888 {
889 let trees = default_v2_state_trees().to_vec();
890 self.state_merkle_trees = trees.clone();
891 return Ok(trees);
892 }
893
894 #[cfg(not(feature = "v2"))]
896 {
897 let network = self.detect_network();
898
899 if matches!(network, RpcUrl::Localnet) {
900 let default_trees = vec![TreeInfo {
901 tree: pubkey!("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT"),
902 queue: pubkey!("nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148"),
903 cpi_context: Some(pubkey!("cpi1uHzrEhBG733DoEJNgHCyRS3XmmyVNZx5fonubE4")),
904 next_tree_info: None,
905 tree_type: TreeType::StateV1,
906 }];
907 self.state_merkle_trees = default_trees.clone();
908 return Ok(default_trees);
909 }
910
911 let (mainnet_tables, devnet_tables) = default_state_tree_lookup_tables();
912
913 let lookup_tables = match network {
914 RpcUrl::Devnet | RpcUrl::Testnet | RpcUrl::ZKTestnet => &devnet_tables,
915 _ => &mainnet_tables,
916 };
917
918 let res = get_light_state_tree_infos(
919 self,
920 &lookup_tables[0].state_tree_lookup_table,
921 &lookup_tables[0].nullify_table,
922 )
923 .await?;
924 self.state_merkle_trees = res.clone();
925 Ok(res)
926 }
927 }
928
929 fn get_state_tree_infos(&self) -> Vec<TreeInfo> {
931 #[cfg(feature = "v2")]
932 {
933 default_v2_state_trees().to_vec()
934 }
935 #[cfg(not(feature = "v2"))]
936 {
937 self.state_merkle_trees.to_vec()
938 }
939 }
940
941 fn get_random_state_tree_info(&self) -> Result<TreeInfo, RpcError> {
943 #[cfg(feature = "v2")]
944 {
945 use rand::Rng;
946 let mut rng = rand::thread_rng();
947 let trees = default_v2_state_trees();
948 Ok(trees[rng.gen_range(0..trees.len())])
949 }
950
951 #[cfg(not(feature = "v2"))]
952 {
953 let mut rng = rand::thread_rng();
954 let filtered_trees: Vec<TreeInfo> = self
955 .state_merkle_trees
956 .iter()
957 .filter(|tree| tree.tree_type == TreeType::StateV1)
958 .copied()
959 .collect();
960 select_state_tree_info(&mut rng, &filtered_trees)
961 }
962 }
963
964 fn get_random_state_tree_info_v1(&self) -> Result<TreeInfo, RpcError> {
967 let mut rng = rand::thread_rng();
968 let v1_trees: Vec<TreeInfo> = self
969 .state_merkle_trees
970 .iter()
971 .filter(|tree| tree.tree_type == TreeType::StateV1)
972 .copied()
973 .collect();
974 select_state_tree_info(&mut rng, &v1_trees)
975 }
976
977 fn get_address_tree_v1(&self) -> TreeInfo {
978 TreeInfo {
979 tree: pubkey!("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2"),
980 queue: pubkey!("aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F"),
981 cpi_context: None,
982 next_tree_info: None,
983 tree_type: TreeType::AddressV1,
984 }
985 }
986
987 fn get_address_tree_v2(&self) -> TreeInfo {
988 TreeInfo {
989 tree: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
990 queue: pubkey!("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx"),
991 cpi_context: None,
992 next_tree_info: None,
993 tree_type: TreeType::AddressV2,
994 }
995 }
996
997}
998
999impl MerkleTreeExt for LightClient {}
1000
1001pub fn select_state_tree_info<R: rand::Rng>(
1024 rng: &mut R,
1025 state_trees: &[TreeInfo],
1026) -> Result<TreeInfo, RpcError> {
1027 if state_trees.is_empty() {
1028 return Err(RpcError::NoStateTreesAvailable);
1029 }
1030
1031 Ok(state_trees[rng.gen_range(0..state_trees.len())])
1032}