1use async_trait::async_trait;
2use borsh::BorshDeserialize as _;
3use light_compressed_account::address::derive_address;
4use light_token::instruction::derive_token_ata;
5use light_token_interface::{state::Mint, MINT_ADDRESS_TREE};
6use solana_pubkey::Pubkey;
7
8use super::{AccountInterface, AccountToFetch, MintInterface, MintState, TokenAccountInterface};
9use crate::{
10 indexer::{GetCompressedTokenAccountsByOwnerOrDelegateOptions, Indexer},
11 rpc::{Rpc, RpcError},
12};
13
14fn indexer_err(e: impl std::fmt::Display) -> RpcError {
15 RpcError::CustomError(format!("IndexerError: {}", e))
16}
17
18#[async_trait]
20pub trait AccountInterfaceExt: Rpc + Indexer {
21 async fn get_mint_interface(&self, address: &Pubkey) -> Result<MintInterface, RpcError>;
25
26 async fn get_account_interface(
30 &self,
31 address: &Pubkey,
32 program_id: &Pubkey,
33 ) -> Result<AccountInterface, RpcError>;
34
35 async fn get_token_account_interface(
39 &self,
40 address: &Pubkey,
41 ) -> Result<TokenAccountInterface, RpcError>;
42
43 async fn get_ata_interface(
47 &self,
48 owner: &Pubkey,
49 mint: &Pubkey,
50 ) -> Result<TokenAccountInterface, RpcError>;
51
52 async fn get_multiple_account_interfaces(
56 &self,
57 accounts: &[AccountToFetch],
58 ) -> Result<Vec<AccountInterface>, RpcError>;
59}
60
61#[async_trait]
63impl<T: Rpc + Indexer> AccountInterfaceExt for T {
64 async fn get_mint_interface(&self, address: &Pubkey) -> Result<MintInterface, RpcError> {
65 let address_tree = Pubkey::new_from_array(MINT_ADDRESS_TREE);
66 let compressed_address = derive_address(
67 &address.to_bytes(),
68 &address_tree.to_bytes(),
69 &light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
70 );
71
72 if let Some(account) = self.get_account(*address).await? {
74 if account.lamports > 0 {
75 return Ok(MintInterface {
76 mint: *address,
77 address_tree,
78 compressed_address,
79 state: MintState::Hot { account },
80 });
81 }
82 }
83
84 let result = self
86 .get_compressed_account(compressed_address, None)
87 .await
88 .map_err(indexer_err)?;
89
90 if let Some(compressed) = result.value {
91 if let Some(data) = compressed.data.as_ref() {
92 if !data.data.is_empty() {
93 let mint_data = Mint::try_from_slice(&data.data)
94 .map_err(|e| RpcError::CustomError(format!("mint parse error: {}", e)))?;
95 return Ok(MintInterface {
96 mint: *address,
97 address_tree,
98 compressed_address,
99 state: MintState::Cold {
100 compressed,
101 mint_data,
102 },
103 });
104 }
105 }
106 }
107
108 Ok(MintInterface {
109 mint: *address,
110 address_tree,
111 compressed_address,
112 state: MintState::None,
113 })
114 }
115
116 async fn get_account_interface(
117 &self,
118 address: &Pubkey,
119 program_id: &Pubkey,
120 ) -> Result<AccountInterface, RpcError> {
121 let address_tree = self.get_address_tree_v2().tree;
122 let compressed_address = derive_address(
123 &address.to_bytes(),
124 &address_tree.to_bytes(),
125 &program_id.to_bytes(),
126 );
127
128 if let Some(account) = self.get_account(*address).await? {
130 if account.lamports > 0 {
131 return Ok(AccountInterface::hot(*address, account));
132 }
133 }
134
135 let result = self
137 .get_compressed_account(compressed_address, None)
138 .await
139 .map_err(indexer_err)?;
140
141 if let Some(compressed) = result.value {
142 if compressed.data.as_ref().is_some_and(|d| !d.data.is_empty()) {
143 return Ok(AccountInterface::cold(*address, compressed, *program_id));
144 }
145 }
146
147 let account = solana_account::Account {
149 lamports: 0,
150 data: vec![],
151 owner: *program_id,
152 executable: false,
153 rent_epoch: 0,
154 };
155 Ok(AccountInterface::hot(*address, account))
156 }
157
158 async fn get_token_account_interface(
159 &self,
160 address: &Pubkey,
161 ) -> Result<TokenAccountInterface, RpcError> {
162 use light_sdk::constants::LIGHT_TOKEN_PROGRAM_ID;
163
164 if let Some(account) = self.get_account(*address).await? {
166 if account.lamports > 0 {
167 return TokenAccountInterface::hot(*address, account)
168 .map_err(|e| RpcError::CustomError(format!("parse error: {}", e)));
169 }
170 }
171
172 let result = self
174 .get_compressed_token_accounts_by_owner(address, None, None)
175 .await
176 .map_err(indexer_err)?;
177
178 if let Some(compressed) = result.value.items.into_iter().next() {
179 return Ok(TokenAccountInterface::cold(
180 *address,
181 compressed,
182 *address, LIGHT_TOKEN_PROGRAM_ID.into(),
184 ));
185 }
186
187 Err(RpcError::CustomError(format!(
188 "token account not found: {}",
189 address
190 )))
191 }
192
193 async fn get_ata_interface(
194 &self,
195 owner: &Pubkey,
196 mint: &Pubkey,
197 ) -> Result<TokenAccountInterface, RpcError> {
198 use light_sdk::constants::LIGHT_TOKEN_PROGRAM_ID;
199
200 let (ata, _bump) = derive_token_ata(owner, mint);
201
202 if let Some(account) = self.get_account(ata).await? {
204 if account.lamports > 0 {
205 return TokenAccountInterface::hot(ata, account)
206 .map_err(|e| RpcError::CustomError(format!("parse error: {}", e)));
207 }
208 }
209
210 let options = Some(GetCompressedTokenAccountsByOwnerOrDelegateOptions::new(
212 Some(*mint),
213 ));
214 let result = self
215 .get_compressed_token_accounts_by_owner(&ata, options, None)
216 .await
217 .map_err(indexer_err)?;
218
219 if let Some(compressed) = result.value.items.into_iter().next() {
220 return Ok(TokenAccountInterface::cold(
221 ata,
222 compressed,
223 *owner, LIGHT_TOKEN_PROGRAM_ID.into(),
225 ));
226 }
227
228 Err(RpcError::CustomError(format!(
229 "ATA not found: owner={} mint={}",
230 owner, mint
231 )))
232 }
233
234 async fn get_multiple_account_interfaces(
235 &self,
236 accounts: &[AccountToFetch],
237 ) -> Result<Vec<AccountInterface>, RpcError> {
238 let mut result = Vec::with_capacity(accounts.len());
240
241 for account in accounts {
242 let iface = match account {
243 AccountToFetch::Pda {
244 address,
245 program_id,
246 } => self.get_account_interface(address, program_id).await?,
247 AccountToFetch::Token { address } => {
248 let token_iface = self.get_token_account_interface(address).await?;
249 AccountInterface {
250 key: token_iface.key,
251 account: token_iface.account,
252 cold: token_iface.cold,
253 }
254 }
255 AccountToFetch::Ata { wallet_owner, mint } => {
256 let token_iface = self.get_ata_interface(wallet_owner, mint).await?;
257 AccountInterface {
258 key: token_iface.key,
259 account: token_iface.account,
260 cold: token_iface.cold,
261 }
262 }
263 AccountToFetch::Mint { address } => {
264 let mint_iface = self.get_mint_interface(address).await?;
265 match mint_iface.state {
266 MintState::Hot { account } => AccountInterface {
267 key: mint_iface.mint,
268 account,
269 cold: None,
270 },
271 MintState::Cold { compressed, .. } => {
272 let owner = compressed.owner;
273 AccountInterface::cold(mint_iface.mint, compressed, owner)
274 }
275 MintState::None => AccountInterface {
276 key: mint_iface.mint,
277 account: Default::default(),
278 cold: None,
279 },
280 }
281 }
282 };
283 result.push(iface);
284 }
285
286 Ok(result)
287 }
288}