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
15/// Get the owners of the given addresses.
16pub 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
31/// Get the owner of the given address.
32pub 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/// Get the token details of the given address.
41#[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/// Token metadata definition.
55#[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
65/// Get the token details of the given address.
66pub 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}