1use std::ops::Deref;
2
3use anchor_client::{
4 anchor_lang::{AccountDeserialize, Discriminator},
5 solana_client::{
6 client_error::ClientError,
7 nonblocking::rpc_client::RpcClient,
8 rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig, RpcTokenAccountsFilter},
9 rpc_filter::{Memcmp, RpcFilterType},
10 rpc_request::{RpcError, RpcRequest, TokenAccountsFilter},
11 rpc_response::{Response, RpcKeyedAccount},
12 },
13 solana_sdk::{
14 account::Account, commitment_config::CommitmentConfig, pubkey::Pubkey, signer::Signer,
15 },
16};
17use gmsol_solana_utils::program::Program;
18use serde_json::json;
19use solana_account_decoder::{UiAccount, UiAccountEncoding};
20
21use crate::utils::WithContext;
22
23#[derive(Debug, Default)]
25pub struct ProgramAccountsConfigForRpc {
26 pub filters: Option<Vec<RpcFilterType>>,
28 pub account_config: RpcAccountInfoConfig,
30}
31
32pub async fn get_program_accounts_with_context(
37 client: &RpcClient,
38 program: &Pubkey,
39 mut config: ProgramAccountsConfigForRpc,
40) -> crate::Result<WithContext<Vec<(Pubkey, Account)>>> {
41 let commitment = config
42 .account_config
43 .commitment
44 .unwrap_or_else(|| client.commitment());
45 config.account_config.commitment = Some(commitment);
46 let config = RpcProgramAccountsConfig {
47 filters: config.filters,
48 account_config: config.account_config,
49 with_context: Some(true),
50 };
51 tracing::debug!(%program, ?config, "fetching program accounts");
52 let res = client
53 .send::<Response<Vec<RpcKeyedAccount>>>(
54 RpcRequest::GetProgramAccounts,
55 json!([program.to_string(), config]),
56 )
57 .await
58 .map_err(anchor_client::ClientError::from)?;
59 WithContext::from(res)
60 .map(|accounts| parse_keyed_accounts(accounts, RpcRequest::GetProgramAccounts))
61 .transpose()
62}
63
64pub async fn get_account_with_context(
68 client: &RpcClient,
69 address: &Pubkey,
70 mut config: RpcAccountInfoConfig,
71) -> crate::Result<WithContext<Option<Account>>> {
72 let commitment = config.commitment.unwrap_or_else(|| client.commitment());
73 config.commitment = Some(commitment);
74 tracing::debug!(%address, ?config, "fetching account");
75 let res = client
76 .send::<Response<Option<UiAccount>>>(
77 RpcRequest::GetAccountInfo,
78 json!([address.to_string(), config]),
79 )
80 .await
81 .map_err(anchor_client::ClientError::from)?;
82 Ok(WithContext::from(res).map(|value| value.and_then(|a| a.decode())))
83}
84
85#[derive(Debug, Default)]
87pub struct ProgramAccountsConfig {
88 pub skip_account_type_filter: bool,
90 pub commitment: Option<CommitmentConfig>,
92 pub min_context_slot: Option<u64>,
94}
95
96pub async fn accounts_lazy_with_context<
99 T: AccountDeserialize + Discriminator,
100 C: Deref<Target = impl Signer> + Clone,
101>(
102 program: &Program<C>,
103 filters: impl IntoIterator<Item = RpcFilterType>,
104 config: ProgramAccountsConfig,
105) -> crate::Result<WithContext<impl Iterator<Item = crate::Result<(Pubkey, T)>>>> {
106 let ProgramAccountsConfig {
107 skip_account_type_filter,
108 commitment,
109 min_context_slot,
110 } = config;
111 let filters = (!skip_account_type_filter)
112 .then(|| RpcFilterType::Memcmp(Memcmp::new_base58_encoded(0, &T::discriminator())))
113 .into_iter()
114 .chain(filters)
115 .collect::<Vec<_>>();
116 let config = ProgramAccountsConfigForRpc {
117 filters: (!filters.is_empty()).then_some(filters),
118 account_config: RpcAccountInfoConfig {
119 encoding: Some(UiAccountEncoding::Base64),
120 commitment,
121 min_context_slot,
122 ..Default::default()
123 },
124 };
125 let client = program.rpc();
126 let res = get_program_accounts_with_context(&client, program.id(), config).await?;
127 Ok(res.map(|accounts| {
128 accounts
129 .into_iter()
130 .map(|(key, account)| Ok((key, T::try_deserialize(&mut (&account.data as &[u8]))?)))
131 }))
132}
133
134pub async fn account_with_context<T: AccountDeserialize>(
138 client: &RpcClient,
139 address: &Pubkey,
140 config: RpcAccountInfoConfig,
141) -> crate::Result<WithContext<Option<T>>> {
142 let res = get_account_with_context(client, address, config).await?;
143 Ok(res
144 .map(|a| {
145 a.map(|account| T::try_deserialize(&mut (&account.data as &[u8])))
146 .transpose()
147 })
148 .transpose()?)
149}
150
151fn parse_keyed_accounts(
152 accounts: Vec<RpcKeyedAccount>,
153 request: RpcRequest,
154) -> crate::Result<Vec<(Pubkey, Account)>> {
155 let mut pubkey_accounts: Vec<(Pubkey, Account)> = Vec::with_capacity(accounts.len());
156 for RpcKeyedAccount { pubkey, account } in accounts.into_iter() {
157 let pubkey = pubkey
158 .parse()
159 .map_err(|_| {
160 ClientError::new_with_request(
161 RpcError::ParseError("Pubkey".to_string()).into(),
162 request,
163 )
164 })
165 .map_err(anchor_client::ClientError::from)?;
166 pubkey_accounts.push((
167 pubkey,
168 account
169 .decode()
170 .ok_or_else(|| {
171 ClientError::new_with_request(
172 RpcError::ParseError("Account from rpc".to_string()).into(),
173 request,
174 )
175 })
176 .map_err(anchor_client::ClientError::from)?,
177 ));
178 }
179 Ok(pubkey_accounts)
180}
181
182pub async fn get_token_accounts_by_owner_with_context(
184 client: &RpcClient,
185 owner: &Pubkey,
186 token_account_filter: TokenAccountsFilter,
187 mut config: RpcAccountInfoConfig,
188) -> crate::Result<WithContext<Vec<RpcKeyedAccount>>> {
189 let token_account_filter = match token_account_filter {
190 TokenAccountsFilter::Mint(mint) => RpcTokenAccountsFilter::Mint(mint.to_string()),
191 TokenAccountsFilter::ProgramId(program_id) => {
192 RpcTokenAccountsFilter::ProgramId(program_id.to_string())
193 }
194 };
195
196 if config.commitment.is_none() {
197 config.commitment = Some(client.commitment());
198 }
199
200 let res = client
201 .send::<Response<Vec<RpcKeyedAccount>>>(
202 RpcRequest::GetTokenAccountsByOwner,
203 json!([owner.to_string(), token_account_filter, config]),
204 )
205 .await
206 .map_err(anchor_client::ClientError::from)?;
207
208 Ok(WithContext::from(res))
209}
210
211#[cfg(test)]
212mod tests {
213 use std::sync::Arc;
214
215 use gmsol_solana_utils::cluster::Cluster;
216 use solana_sdk::signature::Keypair;
217 use spl_token::ID;
218
219 use super::*;
220
221 #[tokio::test]
222 async fn get_token_accounts_by_owner() -> crate::Result<()> {
223 let client = crate::Client::new(Cluster::Devnet, Arc::new(Keypair::new()))?;
224 let rpc = client.rpc();
225 let owner = "A1TMhSGzQxMr1TboBKtgixKz1sS6REASMxPo1qsyTSJd"
226 .parse()
227 .unwrap();
228 let accounts = get_token_accounts_by_owner_with_context(
229 rpc,
230 &owner,
231 TokenAccountsFilter::ProgramId(ID),
232 RpcAccountInfoConfig {
233 encoding: Some(UiAccountEncoding::Base64),
234 data_slice: None,
235 ..Default::default()
236 },
237 )
238 .await?;
239
240 assert!(!accounts.value().is_empty());
241
242 Ok(())
243 }
244}