Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions canister/scripts/examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ GET_ACCOUNT_INFO_PARAMS="(
CYCLES=$(dfx canister call sol_rpc getAccountInfoCyclesCost "$GET_ACCOUNT_INFO_PARAMS" $FLAGS --output json | jq '.Ok' --raw-output || exit 1)
dfx canister call sol_rpc getAccountInfo "$GET_ACCOUNT_INFO_PARAMS" $FLAGS --with-cycles "$CYCLES" || exit 1

# Get the USDC mint account balance on Mainnet with a 2-out-of-3 strategy
GET_BALANCE_PARAMS="(
variant { Default = variant { Mainnet } },
opt record {
Expand All @@ -102,3 +103,20 @@ GET_BALANCE_PARAMS="(
)"
CYCLES=$(dfx canister call sol_rpc getBalanceCyclesCost "$GET_BALANCE_PARAMS" $FLAGS --output json | jq '.Ok' --raw-output || exit 1)
dfx canister call sol_rpc getBalance "$GET_BALANCE_PARAMS" $FLAGS --with-cycles "$CYCLES" || exit 1

# Get the USDC issuer (Circle) token account balance on Mainnet with a 2-out-of-3 strategy
GET_TOKEN_ACCOUNT_BALANCE_PARAMS="(
variant { Default = variant { Mainnet } },
opt record {
responseConsensus = opt variant {
Threshold = record { min = 2 : nat8; total = opt (3 : nat8) }
};
responseSizeEstimate = null;
},
record {
pubkey = \"3emsAVdmGKERbHjmGfQ6oZ1e35dkf5iYcS6U4CPKFVaa\";
commitment = null;
},
)"
CYCLES=$(dfx canister call sol_rpc getTokenAccountBalanceCyclesCost "$GET_TOKEN_ACCOUNT_BALANCE_PARAMS" $FLAGS --output json | jq '.Ok' --raw-output || exit 1)
dfx canister call sol_rpc getTokenAccountBalance "$GET_TOKEN_ACCOUNT_BALANCE_PARAMS" $FLAGS --with-cycles "$CYCLES" || exit 1
28 changes: 25 additions & 3 deletions canister/sol_rpc_canister.did
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,23 @@ type MultiGetSlotResult = variant {
Inconsistent : vec record { RpcSource; GetSlotResult };
};

// The parameters for a Solana `getTokenAccountBalance` RPC method call.
type GetTokenAccountBalanceParams = record {
// Pubkey of token account to query, as base-58 encoded string.
pubkey: Pubkey;
// The commitment describes how finalized a block is at that point in time.
commitment: opt CommitmentLevel;
};

// Represents the result of a call to the `getTokenAccountBalance` Solana RPC method.
type GetTokenAccountBalanceResult = variant { Ok : TokenAmount; Err : RpcError };

// Represents an aggregated result from multiple RPC calls to the `getTokenAccountBalance` Solana RPC method.
type MultiGetTokenAccountBalanceResult = variant {
Consistent : GetTokenAccountBalanceResult;
Inconsistent : vec record { RpcSource; GetTokenAccountBalanceResult };
};

// Represents the result of a call to the `sendTransaction` Solana RPC method.
type SendTransactionResult = variant { Ok : Signature; Err : RpcError };

Expand Down Expand Up @@ -663,11 +680,11 @@ service : (InstallArgs,) -> {
// The caller is the controller or a principal specified in `InstallArgs::manage_api_keys`.
updateApiKeys : (vec record { SupportedProvider; opt text }) -> ();

// Call the Solana `getAccountInfo` RPC method and return the resulting slot.
// Call the Solana `getAccountInfo` RPC method and return the resulting info.
getAccountInfo : (RpcSources, opt RpcConfig, GetAccountInfoParams) -> (MultiGetAccountInfoResult);
getAccountInfoCyclesCost : (RpcSources, opt RpcConfig, GetAccountInfoParams) -> (RequestCostResult) query;

// Call the Solana `getBalance` RPC method and return the resulting block.
// Call the Solana `getBalance` RPC method and return the resulting balance.
getBalance : (RpcSources, opt RpcConfig, GetBalanceParams) -> (MultiGetBalanceResult);
getBalanceCyclesCost : (RpcSources, opt RpcConfig, GetBalanceParams) -> (RequestCostResult) query;

Expand All @@ -679,7 +696,12 @@ service : (InstallArgs,) -> {
getSlot : (RpcSources, opt GetSlotRpcConfig, opt GetSlotParams) -> (MultiGetSlotResult);
getSlotCyclesCost : (RpcSources, opt GetSlotRpcConfig, opt GetSlotParams) -> (RequestCostResult) query;

// Call the Solana `getTransaction` RPC method and return the resulting slot.
// Call the Solana `getTokenAccountBalance` RPC method and return the resulting balance.
// If the account does not exist, this method will return a JSON-RPC error.
getTokenAccountBalance : (RpcSources, opt RpcConfig, GetTokenAccountBalanceParams) -> (MultiGetTokenAccountBalanceResult);
getTokenAccountBalanceCyclesCost : (RpcSources, opt RpcConfig, GetTokenAccountBalanceParams) -> (RequestCostResult) query;

// Call the Solana `getTransaction` RPC method and return the resulting transaction.
getTransaction : (RpcSources, opt RpcConfig, GetTransactionParams) -> (MultiGetTransactionResult);
getTransactionCyclesCost : (RpcSources, opt RpcConfig, GetTransactionParams) -> (RequestCostResult) query;

Expand Down
33 changes: 30 additions & 3 deletions canister/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use sol_rpc_canister::{
};
use sol_rpc_types::{
AccountInfo, ConfirmedBlock, GetAccountInfoParams, GetBalanceParams, GetBlockParams,
GetSlotParams, GetSlotRpcConfig, GetTransactionParams, Lamport, MultiRpcResult, RpcAccess,
RpcConfig, RpcResult, RpcSources, SendTransactionParams, Signature, Slot, SupportedRpcProvider,
SupportedRpcProviderId, TransactionInfo,
GetSlotParams, GetSlotRpcConfig, GetTokenAccountBalanceParams, GetTransactionParams, Lamport,
MultiRpcResult, RpcAccess, RpcConfig, RpcResult, RpcSources, SendTransactionParams, Signature,
Slot, SupportedRpcProvider, SupportedRpcProviderId, TokenAmount, TransactionInfo,
};
use std::str::FromStr;

Expand Down Expand Up @@ -188,6 +188,33 @@ async fn get_slot_cycles_cost(
.await
}

#[update(name = "getTokenAccountBalance")]
#[candid_method(rename = "getTokenAccountBalance")]
async fn get_token_account_balance(
source: RpcSources,
config: Option<RpcConfig>,
params: GetTokenAccountBalanceParams,
) -> MultiRpcResult<TokenAmount> {
let request =
MultiRpcRequest::get_token_account_balance(source, config.unwrap_or_default(), params);
send_multi(request).await.into()
}

#[query(name = "getTokenAccountBalanceCyclesCost")]
#[candid_method(query, rename = "getTokenAccountBalanceCyclesCost")]
async fn get_token_account_balance_cycles_cost(
source: RpcSources,
config: Option<RpcConfig>,
params: GetTokenAccountBalanceParams,
) -> RpcResult<u128> {
if read_state(State::is_demo_mode_active) {
return Ok(0);
}
MultiRpcRequest::get_token_account_balance(source, config.unwrap_or_default(), params)?
.cycles_cost()
.await
}

#[update(name = "getTransaction")]
#[candid_method(rename = "getTransaction")]
async fn get_transaction(
Expand Down
30 changes: 30 additions & 0 deletions canister/src/rpc_client/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,36 @@ pub struct GetBlockConfig {
pub max_supported_transaction_version: Option<u8>,
}

#[skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(into = "(String, Option<GetTokenAccountBalanceConfig>)")]
pub struct GetTokenAccountBalanceParams(String, Option<GetTokenAccountBalanceConfig>);

#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GetTokenAccountBalanceConfig {
pub commitment: Option<CommitmentLevel>,
}

impl From<sol_rpc_types::GetTokenAccountBalanceParams> for GetTokenAccountBalanceParams {
fn from(params: sol_rpc_types::GetTokenAccountBalanceParams) -> Self {
Self(
params.pubkey,
params
.commitment
.map(|commitment| GetTokenAccountBalanceConfig {
commitment: Some(commitment),
}),
)
}
}

impl From<GetTokenAccountBalanceParams> for (String, Option<GetTokenAccountBalanceConfig>) {
fn from(value: GetTokenAccountBalanceParams) -> Self {
(value.0, value.1)
}
}

#[skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(into = "(String, Option<GetTransactionConfig>)")]
Expand Down
27 changes: 27 additions & 0 deletions canister/src/rpc_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,33 @@ impl GetSlotRequest {
}
}

pub type GetTokenAccountBalanceRequest = MultiRpcRequest<
json::GetTokenAccountBalanceParams,
solana_account_decoder_client_types::token::UiTokenAmount,
>;

impl GetTokenAccountBalanceRequest {
pub fn get_token_account_balance<Params: Into<json::GetTokenAccountBalanceParams>>(
rpc_sources: RpcSources,
config: RpcConfig,
params: Params,
) -> Result<Self, ProviderError> {
let consensus_strategy = config.response_consensus.unwrap_or_default();
let providers = Providers::new(rpc_sources, consensus_strategy.clone())?;
let max_response_bytes = config
.response_size_estimate
.unwrap_or(1024 + HEADER_SIZE_LIMIT);

Ok(MultiRpcRequest::new(
providers,
JsonRpcRequest::new("getTokenAccountBalance", params.into()),
max_response_bytes,
ResponseTransform::GetTokenAccountBalance,
ReductionStrategy::from(consensus_strategy),
))
}
}

pub type GetTransactionRequest = MultiRpcRequest<
json::GetTransactionParams,
Option<solana_transaction_status_client_types::EncodedConfirmedTransactionWithStatusMeta>,
Expand Down
9 changes: 7 additions & 2 deletions canister/src/rpc_client/sol_rpc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ pub enum ResponseTransform {
#[n(3)]
GetSlot(#[n(0)] RoundingError),
#[n(4)]
GetTransaction,
GetTokenAccountBalance,
#[n(5)]
SendTransaction,
GetTransaction,
#[n(6)]
SendTransaction,
#[n(7)]
Raw,
}

Expand Down Expand Up @@ -76,6 +78,9 @@ impl ResponseTransform {
value => Some(value),
});
}
Self::GetTokenAccountBalance => {
canonicalize_response::<Value, Value>(body_bytes, |result| result["value"].clone());
}
Self::SendTransaction => {
canonicalize_response::<String, String>(body_bytes, std::convert::identity);
}
Expand Down
63 changes: 46 additions & 17 deletions canister/src/rpc_client/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,18 @@ use crate::rpc_client::{
use serde::Serialize;
use serde_json::json;
use sol_rpc_types::{
CommitmentLevel, DataSlice, GetAccountInfoEncoding, GetAccountInfoParams,
CommitmentLevel, DataSlice, GetAccountInfoEncoding, GetAccountInfoParams, GetBalanceParams,
GetBlockCommitmentLevel, GetBlockParams, GetSlotParams, GetSlotRpcConfig,
GetTransactionEncoding, GetTransactionParams, RpcConfig, RpcSources, SendTransactionEncoding,
SendTransactionParams, SolanaCluster, TransactionDetails,
GetTokenAccountBalanceParams, GetTransactionEncoding, GetTransactionParams, RpcConfig,
RpcSources, SendTransactionEncoding, SendTransactionParams, SolanaCluster, TransactionDetails,
};

mod request_serialization_tests {
use super::*;
use sol_rpc_types::GetBalanceParams;

#[test]
fn should_serialize_get_account_info_request() {
assert_serialized(
assert_params_eq(
GetAccountInfoRequest::get_account_info(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand All @@ -26,7 +25,7 @@ mod request_serialization_tests {
.unwrap(),
json!(["11111111111111111111111111111111", null]),
);
assert_serialized(
assert_params_eq(
GetAccountInfoRequest::get_account_info(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand Down Expand Up @@ -55,7 +54,7 @@ mod request_serialization_tests {

#[test]
fn should_serialize_get_slot_request() {
assert_serialized(
assert_params_eq(
GetSlotRequest::get_slot(
RpcSources::Default(SolanaCluster::Mainnet),
GetSlotRpcConfig::default(),
Expand All @@ -64,7 +63,7 @@ mod request_serialization_tests {
.unwrap(),
json!([null]),
);
assert_serialized(
assert_params_eq(
GetSlotRequest::get_slot(
RpcSources::Default(SolanaCluster::Mainnet),
GetSlotRpcConfig::default(),
Expand All @@ -86,7 +85,7 @@ mod request_serialization_tests {
#[test]
fn should_serialize_get_transaction_request() {
let signature = solana_signature::Signature::default().to_string();
assert_serialized(
assert_params_eq(
GetTransactionRequest::get_transaction(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand All @@ -95,7 +94,7 @@ mod request_serialization_tests {
.unwrap(),
json!([signature, null]),
);
assert_serialized(
assert_params_eq(
GetTransactionRequest::get_transaction(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand All @@ -121,7 +120,7 @@ mod request_serialization_tests {
#[test]
fn should_serialize_get_balance_request() {
let pubkey = solana_pubkey::Pubkey::default();
assert_serialized(
assert_params_eq(
MultiRpcRequest::get_balance(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand All @@ -131,7 +130,7 @@ mod request_serialization_tests {
json!([pubkey.to_string(), null]),
);

assert_serialized(
assert_params_eq(
MultiRpcRequest::get_balance(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand All @@ -154,9 +153,39 @@ mod request_serialization_tests {
);
}

#[test]
fn should_serialize_get_token_account_balance_request() {
let pubkey = solana_pubkey::Pubkey::default();
assert_params_eq(
MultiRpcRequest::get_token_account_balance(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
GetTokenAccountBalanceParams::from(pubkey),
)
.unwrap(),
json!([pubkey.to_string(), null]),
);

assert_params_eq(
MultiRpcRequest::get_token_account_balance(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
GetTokenAccountBalanceParams {
pubkey: pubkey.to_string(),
commitment: Some(CommitmentLevel::Confirmed),
},
)
.unwrap(),
json!([
pubkey.to_string(),
{"commitment": "confirmed"}
]),
);
}

#[test]
fn should_serialize_get_block_request() {
assert_serialized(
assert_params_eq(
GetBlockRequest::get_block(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand All @@ -168,7 +197,7 @@ mod request_serialization_tests {
{"rewards": false, "transactionDetails": "none"}
]),
);
assert_serialized(
assert_params_eq(
GetBlockRequest::get_block(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand All @@ -195,7 +224,7 @@ mod request_serialization_tests {
#[test]
fn should_serialize_send_transaction_request() {
let transaction = "4F9ksKhLSgn9e7ugVnAmRpRXL9kjke4TT96FNDxMiUNc5KVDz8p1yuv";
assert_serialized(
assert_params_eq(
SendTransactionRequest::send_transaction(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand All @@ -215,7 +244,7 @@ mod request_serialization_tests {
params.skip_preflight = Some(true);
params.preflight_commitment = Some(CommitmentLevel::Processed);
params.min_context_slot = Some(456);
assert_serialized(
assert_params_eq(
SendTransactionRequest::send_transaction(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
Expand All @@ -235,7 +264,7 @@ mod request_serialization_tests {
);
}

fn assert_serialized<Params: Serialize, Output>(
fn assert_params_eq<Params: Serialize, Output>(
request: MultiRpcRequest<Params, Output>,
serialized: serde_json::Value,
) {
Expand Down
Loading