anchor_parser/client.rs
1//! Async RPC client helpers for fetching and deserializing on-chain accounts.
2//!
3//! This module is available when the `client` feature is enabled, which adds
4//! a dependency on [`solana-client`](https://docs.rs/solana-client).
5//!
6//! ```toml
7//! [dependencies]
8//! anchor-parser = { version = "0.1.3", features = ["client"] }
9//! ```
10//!
11//! # Examples
12//!
13//! ```ignore
14//! use anchor_parser::client;
15//! use my_program::accounts::MyAccount;
16//!
17//! let rpc = solana_client::nonblocking::rpc_client::RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
18//!
19//! // Fetch a single account
20//! let account = client::fetch_account::<MyAccount>(&rpc, &address).await?;
21//!
22//! // Fetch multiple accounts at once
23//! let accounts = client::fetch_accounts::<MyAccount>(&rpc, &[addr1, addr2]).await?;
24//! ```
25
26use solana_client::client_error::ClientError;
27use solana_client::nonblocking::rpc_client::RpcClient;
28use solana_sdk::pubkey::Pubkey;
29
30use crate::AccountDeserialize;
31
32/// Fetch a single account and deserialize it into type `T`.
33///
34/// Uses [`RpcClient::get_account`] under the hood, then delegates to
35/// [`AccountDeserialize::deserialize`] which checks the discriminator and
36/// borsh/bytemuck-deserializes the payload.
37///
38/// # Errors
39///
40/// Returns an error if the RPC call fails or if deserialization fails
41/// (wrong discriminator, data too short, etc.).
42///
43/// # Example
44///
45/// ```ignore
46/// use anchor_parser::client;
47/// use my_program::accounts::MyAccount;
48///
49/// let account = client::fetch_account::<MyAccount>(&rpc, &address).await?;
50/// ```
51pub async fn fetch_account<T: AccountDeserialize>(
52 client: &RpcClient,
53 address: &Pubkey,
54) -> Result<T, ClientError> {
55 let account = client.get_account(address).await?;
56 T::deserialize(&account.data).map_err(|e| e.into())
57}
58
59/// Fetch multiple accounts of the same type in a single RPC call.
60///
61/// Uses [`RpcClient::get_multiple_accounts`] under the hood. Returns `None`
62/// for addresses that don't exist on-chain or whose data fails deserialization.
63///
64/// # Errors
65///
66/// Returns an error if the RPC call itself fails. Individual deserialization
67/// failures are silently mapped to `None`.
68///
69/// # Example
70///
71/// ```ignore
72/// use anchor_parser::client;
73/// use my_program::accounts::MyAccount;
74///
75/// let results = client::fetch_accounts::<MyAccount>(&rpc, &[addr1, addr2]).await?;
76/// for (i, maybe_account) in results.iter().enumerate() {
77/// match maybe_account {
78/// Some(account) => println!("Account {i}: {account:?}"),
79/// None => println!("Account {i}: not found or invalid"),
80/// }
81/// }
82/// ```
83pub async fn fetch_accounts<T: AccountDeserialize>(
84 client: &RpcClient,
85 addresses: &[Pubkey],
86) -> Result<Vec<Option<T>>, ClientError> {
87 let accounts = client.get_multiple_accounts(addresses).await?;
88 Ok(accounts
89 .into_iter()
90 .map(|maybe_acc| maybe_acc.and_then(|acc| T::deserialize(&acc.data).ok()))
91 .collect())
92}