lumos_svm_lib/
solana_utils.rs
1use solana_account_decoder::parse_token::{
2 TokenAccountType,
3 is_known_spl_token_id,
4 parse_token_v3,
5};
6use solana_account_decoder::parse_token_extension::{
7 UiExtension,
8 UiMetadataPointer,
9 UiTokenMetadata,
10};
11use solana_client::rpc_client::RpcClient;
12use solana_sdk::pubkey::Pubkey;
13use std::str::FromStr as _;
14
15pub async fn get_owners<T: AsRef<str>>(rpc_endpoint: &str, addresses: &[T]) -> anyhow::Result<Vec<String>> {
17 let mut owners = Vec::with_capacity(addresses.len());
18
19 for address in addresses {
20 match get_owner(rpc_endpoint, address.as_ref()).await {
21 Ok(owner) => {
22 owners.push(owner);
23 },
24 Err(_) => continue,
25 }
26 }
27
28 Ok(owners)
29}
30
31pub async fn get_owner(rpc_endpoint: &str, address: &str) -> anyhow::Result<String> {
33 let client = RpcClient::new(rpc_endpoint);
34 let pubkey = Pubkey::from_str(address).map_err(|e| anyhow::anyhow!("Invalid address format: {}", e))?;
35
36 let account = client.get_account(&pubkey)?;
37 Ok(account.owner.to_string())
38}
39
40#[derive(Debug)]
42pub struct TokenDetails {
43 pub owner: String,
44 pub mint_authority: Option<String>,
45 pub freeze_authority: Option<String>,
46 pub update_authority: Option<String>,
47 pub decimals: u8,
48 pub supply: String,
49 pub is_initialized: bool,
50 pub extensions: bool,
51 pub metadata: Option<TokenMetadata>,
52}
53
54#[derive(Debug)]
56pub struct TokenMetadata {
57 pub authority: Option<String>,
58 pub metadata_address: Option<String>,
59 pub update_authority: Option<String>,
60 pub name: Option<String>,
61 pub symbol: Option<String>,
62 pub uri: Option<String>,
63}
64
65pub async fn get_token_details(rpc_endpoint: &str, address: &str) -> anyhow::Result<TokenDetails> {
67 let client = RpcClient::new(rpc_endpoint);
68 let pubkey =
69 Pubkey::from_str(address).map_err(|e| anyhow::anyhow!("Invalid mint address format: {}", e))?;
70
71 let account = client
72 .get_account_with_commitment(&pubkey, client.commitment())?
73 .value
74 .ok_or_else(|| anyhow::anyhow!("Account not found"))?;
75
76 if !is_known_spl_token_id(&account.owner) {
77 return Err(anyhow::anyhow!("Not a token mint account"));
78 }
79
80 if let Ok(token_mint) = parse_token_v3(&account.data, None) {
81 match token_mint {
82 TokenAccountType::Mint(mint) => {
83 let extensions = !mint.extensions.is_empty();
84 let mut metadata = None;
85 if extensions {
86 for ext in mint.extensions.iter() {
87 match ext {
88 UiExtension::MetadataPointer(UiMetadataPointer {
89 authority,
90 metadata_address,
91 }) => {
92 metadata = Some(TokenMetadata {
93 authority: authority.clone(),
94 metadata_address: metadata_address.clone(),
95 update_authority: None,
96 name: None,
97 symbol: None,
98 uri: None,
99 });
100 },
101 UiExtension::TokenMetadata(UiTokenMetadata {
102 update_authority,
103 name,
104 symbol,
105 uri,
106 ..
107 }) => {
108 if let Some(meta) = metadata.as_mut() {
109 meta.update_authority = update_authority.clone();
110 meta.name = Some(name.clone());
111 meta.symbol = Some(symbol.clone());
112 meta.uri = Some(uri.clone());
113 } else {
114 metadata = Some(TokenMetadata {
115 authority: None,
116 metadata_address: None,
117 update_authority: update_authority.clone(),
118 name: Some(name.clone()),
119 symbol: Some(symbol.clone()),
120 uri: Some(uri.clone()),
121 });
122 }
123 },
124 _ => continue,
125 }
126 }
127 }
128
129 Ok(TokenDetails {
130 owner: account.owner.to_string(),
131 mint_authority: mint.mint_authority,
132 freeze_authority: mint.freeze_authority,
133 update_authority: None,
134 decimals: mint.decimals,
135 supply: mint.supply,
136 is_initialized: mint.is_initialized,
137 extensions,
138 metadata,
139 })
140 },
141 _ => Err(anyhow::anyhow!("Not a mint account")),
142 }
143 } else {
144 Err(anyhow::anyhow!("Failed to parse token mint account"))
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use tokio::runtime::Runtime;
152
153 #[test]
154 fn test_get_owners() {
155 let rt = Runtime::new().unwrap();
156 rt.block_on(async {
157 let rpc_endpoint = "https://eclipse.lgns.net/";
158 let accounts = [
159 "GU7NS9xCwgNPiAdJ69iusFrRfawjDDPjeMBovhV1d4kn",
160 "AKEWE7Bgh87GPp171b4cJPSSZfmZwQ3KaqYqXoKLNAEE",
161 ];
162
163 let owners = get_owners(rpc_endpoint, &accounts).await.unwrap();
164 assert_eq!(owners.len(), accounts.len());
165 });
166 }
167}