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#[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 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 #[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 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 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 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 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 .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 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 async fn get_latest_active_state_trees(&mut self) -> Result<Vec<TreeInfo>, RpcError> {
1018 #[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 #[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 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 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 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
1132pub 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}