#![forbid(unsafe_code)]
#![forbid(missing_docs)]
#[cfg(feature = "ed25519")]
pub mod ed25519;
#[cfg(not(target_arch = "wasm32"))]
pub mod fixtures;
pub mod nonce;
mod request;
use crate::request::{
GetAccountInfoRequest, GetAccountInfoRequestBuilder, GetBalanceRequest,
GetBalanceRequestBuilder, GetBlockRequest, GetBlockRequestBuilder,
GetRecentPrioritizationFeesRequest, GetRecentPrioritizationFeesRequestBuilder,
GetSignatureStatusesRequest, GetSignatureStatusesRequestBuilder,
GetSignaturesForAddressRequest, GetSignaturesForAddressRequestBuilder, GetSlotRequest,
GetSlotRequestBuilder, GetTokenAccountBalanceRequest, GetTokenAccountBalanceRequestBuilder,
GetTransactionRequest, GetTransactionRequestBuilder, JsonRequest, JsonRequestBuilder,
SendTransactionRequest, SendTransactionRequestBuilder,
};
use candid::{CandidType, Principal};
pub use ic_canister_runtime::IcError;
use ic_canister_runtime::{IcRuntime, Runtime};
pub use request::{
DefaultRequestCycles, GetRecentBlockError, GetRecentBlockRequestBuilder, Request,
RequestBuilder, SolRpcConfig, SolRpcEndpoint, SolRpcRequest,
};
use serde::de::DeserializeOwned;
use sol_rpc_types::{
CommitmentLevel, ConsensusStrategy, GetAccountInfoParams, GetBalanceParams, GetBlockParams,
GetRecentPrioritizationFeesParams, GetSignatureStatusesParams, GetSignaturesForAddressParams,
GetTokenAccountBalanceParams, GetTransactionParams, Pubkey, RpcConfig, RpcResult, RpcSources,
SendTransactionParams, SolanaCluster, SupportedRpcProvider, SupportedRpcProviderId,
};
use std::{fmt::Debug, sync::Arc};
pub const SOL_RPC_CANISTER: Principal = Principal::from_slice(&[0, 0, 0, 0, 2, 48, 4, 68, 1, 1]);
#[derive(Debug)]
pub struct SolRpcClient<R> {
config: Arc<ClientConfig<R>>,
}
impl<R> Clone for SolRpcClient<R> {
fn clone(&self) -> Self {
Self {
config: self.config.clone(),
}
}
}
impl<R> SolRpcClient<R> {
pub fn builder(runtime: R, sol_rpc_canister: Principal) -> ClientBuilder<R> {
ClientBuilder::new(runtime, sol_rpc_canister)
}
pub fn runtime(&self) -> &R {
&self.config.runtime
}
}
impl SolRpcClient<IcRuntime> {
pub fn builder_for_ic() -> ClientBuilder<IcRuntime> {
ClientBuilder::new(IcRuntime::new(), SOL_RPC_CANISTER)
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct ClientConfig<R> {
runtime: R,
sol_rpc_canister: Principal,
rpc_config: Option<RpcConfig>,
default_commitment_level: Option<CommitmentLevel>,
rpc_sources: RpcSources,
}
#[must_use]
pub struct ClientBuilder<R> {
config: ClientConfig<R>,
}
impl<R> ClientBuilder<R> {
fn new(runtime: R, sol_rpc_canister: Principal) -> Self {
Self {
config: ClientConfig {
runtime,
sol_rpc_canister,
rpc_config: None,
default_commitment_level: None,
rpc_sources: RpcSources::Default(SolanaCluster::Mainnet),
},
}
}
pub fn with_runtime<S, F: FnOnce(R) -> S>(self, other_runtime: F) -> ClientBuilder<S> {
ClientBuilder {
config: ClientConfig {
runtime: other_runtime(self.config.runtime),
sol_rpc_canister: self.config.sol_rpc_canister,
rpc_config: self.config.rpc_config,
default_commitment_level: self.config.default_commitment_level,
rpc_sources: self.config.rpc_sources,
},
}
}
pub fn with_rpc_sources(mut self, rpc_sources: RpcSources) -> Self {
self.config.rpc_sources = rpc_sources;
self
}
pub fn with_rpc_config(mut self, rpc_config: RpcConfig) -> Self {
self.config.rpc_config = Some(rpc_config);
self
}
pub fn with_consensus_strategy(mut self, consensus_strategy: ConsensusStrategy) -> Self {
self.config.rpc_config = Some(RpcConfig {
response_consensus: Some(consensus_strategy),
..self.config.rpc_config.unwrap_or_default()
});
self
}
pub fn with_response_size_estimate(mut self, response_size_estimate: u64) -> Self {
self.config.rpc_config = Some(RpcConfig {
response_size_estimate: Some(response_size_estimate),
..self.config.rpc_config.unwrap_or_default()
});
self
}
pub fn with_default_commitment_level(mut self, commitment_level: CommitmentLevel) -> Self {
self.config.default_commitment_level = Some(commitment_level);
self
}
pub fn build(self) -> SolRpcClient<R> {
SolRpcClient {
config: Arc::new(self.config),
}
}
}
impl<R> SolRpcClient<R> {
pub fn get_account_info(
&self,
params: impl Into<GetAccountInfoParams>,
) -> GetAccountInfoRequestBuilder<R> {
RequestBuilder::new(self.clone(), GetAccountInfoRequest::new(params.into()))
}
pub fn get_balance(&self, params: impl Into<GetBalanceParams>) -> GetBalanceRequestBuilder<R> {
RequestBuilder::new(self.clone(), GetBalanceRequest::new(params.into()))
}
pub fn get_block(&self, params: impl Into<GetBlockParams>) -> GetBlockRequestBuilder<R> {
RequestBuilder::new(self.clone(), GetBlockRequest::new(params.into()))
}
pub fn get_token_account_balance(
&self,
params: impl Into<GetTokenAccountBalanceParams>,
) -> GetTokenAccountBalanceRequestBuilder<R> {
RequestBuilder::new(
self.clone(),
GetTokenAccountBalanceRequest::new(params.into()),
)
}
pub fn get_recent_prioritization_fees<'a, I>(
&self,
addresses: I,
) -> RpcResult<GetRecentPrioritizationFeesRequestBuilder<R>>
where
I: IntoIterator<Item = &'a solana_pubkey::Pubkey>,
{
let params = GetRecentPrioritizationFeesParams::try_from(
addresses.into_iter().map(Pubkey::from).collect::<Vec<_>>(),
)?;
Ok(RequestBuilder::new(
self.clone(),
GetRecentPrioritizationFeesRequest::from(params),
))
}
pub fn get_signatures_for_address(
&self,
params: impl Into<GetSignaturesForAddressParams>,
) -> GetSignaturesForAddressRequestBuilder<R> {
RequestBuilder::new(
self.clone(),
GetSignaturesForAddressRequest::from(params.into()),
)
}
pub fn get_signature_statuses<'a, I>(
&self,
signatures: I,
) -> RpcResult<GetSignatureStatusesRequestBuilder<R>>
where
I: IntoIterator<Item = &'a solana_signature::Signature>,
{
let signatures = signatures.into_iter().collect::<Vec<_>>();
Ok(RequestBuilder::new(
self.clone(),
GetSignatureStatusesRequest::from(GetSignatureStatusesParams::try_from(signatures)?),
))
}
pub fn get_slot(&self) -> GetSlotRequestBuilder<R> {
RequestBuilder::new(self.clone(), GetSlotRequest::default())
}
pub fn get_transaction(
&self,
params: impl Into<GetTransactionParams>,
) -> GetTransactionRequestBuilder<R> {
RequestBuilder::new(self.clone(), GetTransactionRequest::new(params.into()))
}
pub fn send_transaction<T>(&self, params: T) -> SendTransactionRequestBuilder<R>
where
T: TryInto<SendTransactionParams>,
<T as TryInto<SendTransactionParams>>::Error: Debug,
{
let params = params
.try_into()
.expect("Unable to build request parameters");
RequestBuilder::new(self.clone(), SendTransactionRequest::new(params))
}
pub fn json_request(&self, json_request: serde_json::Value) -> JsonRequestBuilder<R> {
RequestBuilder::new(
self.clone(),
JsonRequest::try_from(json_request).expect("Client error: invalid JSON request"),
)
}
}
impl<R: Runtime> SolRpcClient<R> {
pub async fn get_providers(&self) -> Vec<(SupportedRpcProviderId, SupportedRpcProvider)> {
self.config
.runtime
.query_call(self.config.sol_rpc_canister, "getProviders", ())
.await
.unwrap()
}
pub async fn update_api_keys(&self, api_keys: &[(SupportedRpcProviderId, Option<String>)]) {
self.config
.runtime
.update_call(
self.config.sol_rpc_canister,
"updateApiKeys",
(api_keys.to_vec(),),
0,
)
.await
.unwrap()
}
pub fn get_recent_block(&self) -> GetRecentBlockRequestBuilder<R> {
GetRecentBlockRequestBuilder::new(self.clone())
}
async fn try_execute_request<Config, Params, CandidOutput, Output>(
&self,
request: Request<Config, Params, CandidOutput, Output>,
cycles: u128,
) -> Result<Output, IcError>
where
Config: CandidType + Send,
Params: CandidType + Send,
CandidOutput: Into<Output> + CandidType + DeserializeOwned,
{
self.config
.runtime
.update_call::<(RpcSources, Option<Config>, Params), CandidOutput>(
self.config.sol_rpc_canister,
request.endpoint.rpc_method(),
(request.rpc_sources, request.rpc_config, request.params),
cycles,
)
.await
.map(Into::into)
}
async fn execute_cycles_cost_request<Config, Params, CandidOutput, Output>(
&self,
request: Request<Config, Params, CandidOutput, Output>,
) -> Output
where
Config: CandidType + Send,
Params: CandidType + Send,
CandidOutput: Into<Output> + CandidType + DeserializeOwned,
{
self.config
.runtime
.query_call::<(RpcSources, Option<Config>, Params), CandidOutput>(
self.config.sol_rpc_canister,
request.endpoint.cycles_cost_method(),
(request.rpc_sources, request.rpc_config, request.params),
)
.await
.unwrap_or_else(|e| {
panic!(
"Client error: failed to call `{}`: {e:?}",
request.endpoint.cycles_cost_method()
)
})
.into()
}
}