1use std::fmt::Debug;
2
3use async_trait::async_trait;
4use borsh::BorshDeserialize;
5use light_event::event::{BatchPublicTransactionEvent, PublicTransactionEvent};
6use solana_account::Account;
7use solana_clock::Slot;
8use solana_commitment_config::CommitmentConfig;
9use solana_hash::Hash;
10use solana_instruction::Instruction;
11use solana_keypair::Keypair;
12use solana_message::AddressLookupTableAccount;
13use solana_pubkey::Pubkey;
14use solana_rpc_client_api::config::RpcSendTransactionConfig;
15use solana_signature::Signature;
16use solana_transaction::Transaction;
17use solana_transaction_status_client_types::TransactionStatus;
18
19use super::client::RpcUrl;
20use crate::{
21 indexer::{Indexer, IndexerRpcConfig, Response, TreeInfo},
22 interface::{AccountInterface, AccountToFetch, MintInterface, TokenAccountInterface},
23 rpc::errors::RpcError,
24};
25
26#[derive(Debug, Clone)]
27pub struct LightClientConfig {
28 pub url: String,
29 pub commitment_config: Option<CommitmentConfig>,
30 pub photon_url: Option<String>,
33 pub fetch_active_tree: bool,
34}
35
36impl LightClientConfig {
37 pub fn new(url: String, photon_url: Option<String>) -> Self {
38 Self {
39 url,
40 photon_url,
41 commitment_config: Some(CommitmentConfig::confirmed()),
42 fetch_active_tree: true,
43 }
44 }
45 pub fn local_no_indexer() -> Self {
46 Self {
47 url: RpcUrl::Localnet.to_string(),
48 commitment_config: Some(CommitmentConfig::confirmed()),
49 photon_url: None,
50 fetch_active_tree: false,
51 }
52 }
53
54 pub fn local() -> Self {
55 Self {
56 url: RpcUrl::Localnet.to_string(),
57 commitment_config: Some(CommitmentConfig::processed()),
58 photon_url: Some("http://127.0.0.1:8784".to_string()),
59 fetch_active_tree: false,
60 }
61 }
62
63 pub fn devnet(photon_url: Option<String>) -> Self {
64 Self {
65 url: RpcUrl::Devnet.to_string(),
66 photon_url,
67 commitment_config: Some(CommitmentConfig::confirmed()),
68 fetch_active_tree: true,
69 }
70 }
71}
72
73#[async_trait]
74pub trait Rpc: Send + Sync + Debug + 'static {
75 async fn new(config: LightClientConfig) -> Result<Self, RpcError>
76 where
77 Self: Sized;
78
79 fn should_retry(&self, error: &RpcError) -> bool {
80 match error {
81 RpcError::ClientError(error) => error.kind.get_transaction_error().is_none(),
83 RpcError::SigningError(_) => false,
85 _ => true,
86 }
87 }
88
89 fn get_payer(&self) -> &Keypair;
90 fn get_url(&self) -> String;
91
92 async fn health(&self) -> Result<(), RpcError>;
93
94 async fn get_program_accounts(
95 &self,
96 program_id: &Pubkey,
97 ) -> Result<Vec<(Pubkey, Account)>, RpcError>;
98
99 async fn get_program_accounts_with_discriminator(
100 &self,
101 program_id: &Pubkey,
102 discriminator: &[u8],
103 ) -> Result<Vec<(Pubkey, Account)>, RpcError>;
104
105 async fn confirm_transaction(&self, signature: Signature) -> Result<bool, RpcError>;
108
109 async fn get_account(&self, address: Pubkey) -> Result<Option<Account>, RpcError>;
111
112 async fn get_multiple_accounts(
114 &self,
115 addresses: &[Pubkey],
116 ) -> Result<Vec<Option<Account>>, RpcError>;
117
118 async fn get_anchor_account<T: BorshDeserialize>(
121 &self,
122 pubkey: &Pubkey,
123 ) -> Result<Option<T>, RpcError> {
124 match self.get_account(*pubkey).await? {
125 Some(account) => {
126 let data = T::deserialize(&mut &account.data[8..]).map_err(RpcError::from)?;
127 Ok(Some(data))
128 }
129 None => Ok(None),
130 }
131 }
132
133 async fn get_minimum_balance_for_rent_exemption(
134 &self,
135 data_len: usize,
136 ) -> Result<u64, RpcError>;
137
138 async fn airdrop_lamports(&mut self, to: &Pubkey, lamports: u64)
139 -> Result<Signature, RpcError>;
140
141 async fn get_balance(&self, pubkey: &Pubkey) -> Result<u64, RpcError>;
142 async fn get_latest_blockhash(&mut self) -> Result<(Hash, u64), RpcError>;
143 async fn get_slot(&self) -> Result<u64, RpcError>;
144 async fn get_transaction_slot(&self, signature: &Signature) -> Result<u64, RpcError>;
145 async fn get_signature_statuses(
146 &self,
147 signatures: &[Signature],
148 ) -> Result<Vec<Option<TransactionStatus>>, RpcError>;
149
150 async fn send_transaction(&self, transaction: &Transaction) -> Result<Signature, RpcError>;
151
152 async fn send_transaction_with_config(
153 &self,
154 transaction: &Transaction,
155 config: RpcSendTransactionConfig,
156 ) -> Result<Signature, RpcError>;
157
158 async fn process_transaction(
159 &mut self,
160 transaction: Transaction,
161 ) -> Result<Signature, RpcError>;
162
163 async fn process_transaction_with_context(
164 &mut self,
165 transaction: Transaction,
166 ) -> Result<(Signature, Slot), RpcError>;
167
168 async fn create_and_send_transaction_with_event<T>(
169 &mut self,
170 instructions: &[Instruction],
171 authority: &Pubkey,
172 signers: &[&Keypair],
173 ) -> Result<Option<(T, Signature, Slot)>, RpcError>
174 where
175 T: BorshDeserialize + Send + Debug;
176
177 async fn create_and_send_transaction<'a>(
178 &'a mut self,
179 instructions: &'a [Instruction],
180 payer: &'a Pubkey,
181 signers: &'a [&'a Keypair],
182 ) -> Result<Signature, RpcError> {
183 let blockhash = self.get_latest_blockhash().await?.0;
184 let mut transaction = Transaction::new_with_payer(instructions, Some(payer));
185 transaction
186 .try_sign(signers, blockhash)
187 .map_err(|e| RpcError::SigningError(e.to_string()))?;
188 self.process_transaction(transaction).await
189 }
190
191 async fn create_and_send_versioned_transaction<'a>(
192 &'a mut self,
193 instructions: &'a [Instruction],
194 payer: &'a Pubkey,
195 signers: &'a [&'a Keypair],
196 address_lookup_tables: &'a [AddressLookupTableAccount],
197 ) -> Result<Signature, RpcError>;
198
199 async fn create_and_send_transaction_with_public_event(
200 &mut self,
201 instruction: &[Instruction],
202 payer: &Pubkey,
203 signers: &[&Keypair],
204 ) -> Result<Option<(PublicTransactionEvent, Signature, Slot)>, RpcError>;
205
206 async fn create_and_send_transaction_with_batched_event(
207 &mut self,
208 instruction: &[Instruction],
209 payer: &Pubkey,
210 signers: &[&Keypair],
211 ) -> Result<Option<(Vec<BatchPublicTransactionEvent>, Signature, Slot)>, RpcError>;
212
213 fn indexer(&self) -> Result<&impl Indexer, RpcError>;
214 fn indexer_mut(&mut self) -> Result<&mut impl Indexer, RpcError>;
215
216 async fn get_latest_active_state_trees(&mut self) -> Result<Vec<TreeInfo>, RpcError>;
218
219 fn get_state_tree_infos(&self) -> Vec<TreeInfo>;
222
223 fn get_random_state_tree_info(&self) -> Result<TreeInfo, RpcError>;
227
228 fn get_random_state_tree_info_v1(&self) -> Result<TreeInfo, RpcError>;
231
232 fn get_address_tree_v1(&self) -> TreeInfo;
233
234 fn get_address_tree_v2(&self) -> TreeInfo;
235
236 async fn get_account_interface(
244 &self,
245 address: &Pubkey,
246 config: Option<IndexerRpcConfig>,
247 ) -> Result<Response<Option<AccountInterface>>, RpcError>;
248
249 async fn get_token_account_interface(
251 &self,
252 address: &Pubkey,
253 config: Option<IndexerRpcConfig>,
254 ) -> Result<Response<Option<TokenAccountInterface>>, RpcError>;
255
256 async fn get_associated_token_account_interface(
258 &self,
259 owner: &Pubkey,
260 mint: &Pubkey,
261 config: Option<IndexerRpcConfig>,
262 ) -> Result<Response<Option<TokenAccountInterface>>, RpcError>;
263
264 async fn get_multiple_account_interfaces(
266 &self,
267 addresses: Vec<&Pubkey>,
268 config: Option<IndexerRpcConfig>,
269 ) -> Result<Response<Vec<Option<AccountInterface>>>, RpcError>;
270
271 async fn get_mint_interface(
278 &self,
279 address: &Pubkey,
280 config: Option<IndexerRpcConfig>,
281 ) -> Result<Response<Option<MintInterface>>, RpcError>;
282
283 async fn fetch_accounts(
291 &self,
292 accounts: &[AccountToFetch],
293 config: Option<IndexerRpcConfig>,
294 ) -> Result<Vec<AccountInterface>, RpcError> {
295 let mut results = Vec::with_capacity(accounts.len());
296 for account in accounts {
297 let interface = match account {
298 AccountToFetch::Pda { address, .. } => self
299 .get_account_interface(address, config.clone())
300 .await?
301 .value
302 .ok_or_else(|| {
303 RpcError::CustomError(format!("PDA account not found: {}", address))
304 })?,
305 AccountToFetch::Token { address } => {
306 let tai = self
307 .get_token_account_interface(address, config.clone())
308 .await?
309 .value
310 .ok_or_else(|| {
311 RpcError::CustomError(format!("Token account not found: {}", address))
312 })?;
313 tai.into()
314 }
315 AccountToFetch::Ata { wallet_owner, mint } => {
316 let tai = self
317 .get_associated_token_account_interface(wallet_owner, mint, config.clone())
318 .await?
319 .value
320 .ok_or_else(|| {
321 RpcError::CustomError(format!(
322 "ATA not found for owner {} mint {}",
323 wallet_owner, mint
324 ))
325 })?;
326 tai.into()
327 }
328 AccountToFetch::Mint { address } => {
329 let mi = self
330 .get_mint_interface(address, config.clone())
331 .await?
332 .value
333 .ok_or_else(|| {
334 RpcError::CustomError(format!("Mint not found: {}", address))
335 })?;
336 mi.into()
337 }
338 };
339 results.push(interface);
340 }
341 Ok(results)
342 }
343}