use std::collections::HashMap;
use std::fmt::Debug;
use std::sync::Arc;
use crate::config::Config;
use crate::error::Result;
use crate::request_handler::RequestHandler;
use crate::types::inner::{RpcRequest, RpcResponse};
use crate::types::{
Asset, AssetList, AssetProof, EditionsList, GetAsset, GetAssetBatch, GetAssetProof, GetAssetProofBatch,
GetAssetSignatures, GetAssetsByAuthority, GetAssetsByCreator, GetAssetsByGroup, GetAssetsByOwner, GetNftEditions,
GetPriorityFeeEstimateRequest, GetPriorityFeeEstimateResponse, GetProgramAccountsV2Config,
GetProgramAccountsV2Request, GetProgramAccountsV2Response, GetTokenAccounts, GetTokenAccountsByOwnerV2Config,
GetTokenAccountsByOwnerV2Request, GetTokenAccountsByOwnerV2Response, GetTransactionsForAddressOptions,
GetTransactionsForAddressRequest, GetTransactionsForAddressResponse, GpaAccount, SearchAssets, TokenAccountRecord,
TokenAccountsList, TokenAccountsOwnerFilter, TransactionSignatureList,
};
use reqwest::{Client, Method, Url};
use serde::de::DeserializeOwned;
use serde::Serialize;
use solana_client::rpc_client::RpcClient as SolanaRpcClient;
use solana_commitment_config::CommitmentConfig;
pub struct RpcClient {
pub handler: RequestHandler,
pub config: Arc<Config>,
pub solana_client: Arc<SolanaRpcClient>,
}
impl RpcClient {
pub fn new(client: Arc<Client>, config: Arc<Config>) -> Result<Self> {
let handler: RequestHandler = RequestHandler::new(client)?;
let url: String = config.build_rpc_url();
let solana_client: Arc<SolanaRpcClient> = Arc::new(SolanaRpcClient::new(url));
Ok(RpcClient {
handler,
config,
solana_client,
})
}
pub fn new_with_commitment(client: Arc<Client>, config: Arc<Config>, commitment: CommitmentConfig) -> Result<Self> {
let handler: RequestHandler = RequestHandler::new(client)?;
let url: String = config.build_rpc_url();
let solana_client: Arc<SolanaRpcClient> = Arc::new(SolanaRpcClient::new_with_commitment(url, commitment));
Ok(RpcClient {
handler,
config,
solana_client,
})
}
pub async fn post_rpc_request<R, T>(&self, method: &str, request: R) -> Result<T>
where
R: Debug + Serialize + Send + Sync,
T: Debug + DeserializeOwned + Default,
{
let base_url: String = self.config.build_rpc_url();
let url: Url = Url::parse(&base_url).expect("Failed to parse URL");
let rpc_request: RpcRequest<R> = RpcRequest::new(method.to_string(), request);
let rpc_response: RpcResponse<T> = self.handler.send(Method::POST, url, Some(&rpc_request)).await?;
Ok(rpc_response.result)
}
pub async fn get_asset(&self, request: GetAsset) -> Result<Option<Asset>> {
self.post_rpc_request("getAsset", request).await
}
pub async fn get_asset_batch(&self, request: GetAssetBatch) -> Result<Vec<Option<Asset>>> {
self.post_rpc_request("getAssetBatch", request).await
}
pub async fn get_asset_proof(&self, request: GetAssetProof) -> Result<Option<AssetProof>> {
self.post_rpc_request("getAssetProof", request).await
}
pub async fn get_asset_proof_batch(
&self,
request: GetAssetProofBatch,
) -> Result<HashMap<String, Option<AssetProof>>> {
self.post_rpc_request("getAssetProofBatch", request).await
}
pub async fn get_assets_by_authority(&self, request: GetAssetsByAuthority) -> Result<AssetList> {
self.post_rpc_request("getAssetsByAuthority", request).await
}
pub async fn get_assets_by_creator(&self, request: GetAssetsByCreator) -> Result<AssetList> {
self.post_rpc_request("getAssetsByCreator", request).await
}
pub async fn get_assets_by_group(&self, request: GetAssetsByGroup) -> Result<AssetList> {
self.post_rpc_request("getAssetsByGroup", request).await
}
pub async fn get_assets_by_owner(&self, request: GetAssetsByOwner) -> Result<AssetList> {
self.post_rpc_request("getAssetsByOwner", request).await
}
pub async fn search_assets(&self, request: SearchAssets) -> Result<AssetList> {
self.post_rpc_request("searchAssets", request).await
}
pub async fn get_signatures_for_asset(&self, request: GetAssetSignatures) -> Result<TransactionSignatureList> {
self.post_rpc_request("getSignaturesForAsset", request).await
}
pub async fn get_token_accounts(&self, request: GetTokenAccounts) -> Result<TokenAccountsList> {
self.post_rpc_request("getTokenAccounts", request).await
}
pub async fn get_nft_editions(&self, request: GetNftEditions) -> Result<EditionsList> {
self.post_rpc_request("getNftEditions", request).await
}
pub async fn get_priority_fee_estimate(
&self,
request: GetPriorityFeeEstimateRequest,
) -> Result<GetPriorityFeeEstimateResponse> {
self.post_rpc_request("getPriorityFeeEstimate", vec![request]).await
}
pub async fn get_program_accounts_v2(
&self,
program_id: String,
config: GetProgramAccountsV2Config,
) -> Result<GetProgramAccountsV2Response> {
let params: GetProgramAccountsV2Request = (program_id, config);
self.post_rpc_request("getProgramAccountsV2", params).await
}
pub async fn get_token_accounts_by_owner_v2(
&self,
owner: String,
filter: TokenAccountsOwnerFilter,
config: GetTokenAccountsByOwnerV2Config,
) -> Result<GetTokenAccountsByOwnerV2Response> {
let params: GetTokenAccountsByOwnerV2Request = (owner, filter, config);
self.post_rpc_request("getTokenAccountsByOwnerV2", params).await
}
pub async fn get_all_program_accounts(
&self,
program_id: String,
mut config: GetProgramAccountsV2Config,
) -> Result<Vec<GpaAccount>> {
config.pagination_key = None;
if config.limit.is_none() {
config.limit = Some(10000);
}
let mut all_accounts: Vec<GpaAccount> = Vec::new();
loop {
let response: GetProgramAccountsV2Response =
self.get_program_accounts_v2(program_id.clone(), config.clone()).await?;
all_accounts.extend(response.accounts);
log::info!("Fetched {} accounts so far", all_accounts.len());
if let Some(key) = response.pagination_key {
config.pagination_key = Some(key);
} else {
break;
}
}
Ok(all_accounts)
}
pub async fn get_all_token_accounts_by_owner(
&self,
owner: String,
filter: TokenAccountsOwnerFilter,
mut config: GetTokenAccountsByOwnerV2Config,
) -> Result<Vec<TokenAccountRecord>> {
config.pagination_key = None;
if config.limit.is_none() {
config.limit = Some(10000);
}
let mut all_accounts: Vec<TokenAccountRecord> = Vec::new();
loop {
let response: GetTokenAccountsByOwnerV2Response = self
.get_token_accounts_by_owner_v2(owner.clone(), filter.clone(), config.clone())
.await?;
all_accounts.extend(response.value.accounts);
if let Some(key) = response.value.pagination_key {
config.pagination_key = Some(key);
} else {
break;
}
}
Ok(all_accounts)
}
pub async fn get_transactions_for_address(
&self,
address: String,
options: GetTransactionsForAddressOptions,
) -> Result<GetTransactionsForAddressResponse> {
let params: GetTransactionsForAddressRequest = (address, options);
self.post_rpc_request("getTransactionsForAddress", params).await
}
}