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
19 changes: 18 additions & 1 deletion canister/scripts/examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,21 @@ 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
dfx canister call sol_rpc getAccountInfo "$GET_ACCOUNT_INFO_PARAMS" $FLAGS --with-cycles "$CYCLES" || exit 1

GET_BALANCE_PARAMS="(
variant { Default = variant { Mainnet } },
opt record {
responseConsensus = opt variant {
Threshold = record { min = 2 : nat8; total = opt (3 : nat8) }
};
responseSizeEstimate = null;
},
record {
pubkey = \"EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v\";
commitment = null;
minContextSlot = null;
},
)"
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
27 changes: 27 additions & 0 deletions canister/sol_rpc_canister.did
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,29 @@ type MultiGetAccountInfoResult = variant {
Inconsistent : vec record { RpcSource; GetAccountInfoResult };
};

// The parameters for a Solana `getBalance` RPC method call.
type GetBalanceParams = record {
// Pubkey of 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;
// The minimum slot that the request can be evaluated at.
minContextSlot: opt nat64;
};

// Smallest denomination of SOL, the native token on Solana, i.e.
// 1_000_000_000 Lamports is 1 SOL
type Lamport = nat64;

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

// Represents the result of a call to the `getBalance` Solana RPC method.
type GetBalanceResult = variant { Ok : Lamport; Err : RpcError };

// The parameters for a Solana `getBlock` RPC method call.
// TODO XC-342: Add `rewards`, `encoding` and `transactionDetails` fields.
type GetBlockParams = record {
Expand Down Expand Up @@ -613,6 +636,10 @@ service : (InstallArgs,) -> {
getAccountInfo : (RpcSources, opt RpcConfig, GetAccountInfoParams) -> (MultiGetAccountInfoResult);
getAccountInfoCyclesCost : (RpcSources, opt RpcConfig, GetAccountInfoParams) -> (RequestCostResult) query;

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

// Call the Solana `getBlock` RPC method and return the resulting block.
getBlock : (RpcSources, opt RpcConfig, GetBlockParams) -> (MultiGetBlockResult);
getBlockCyclesCost : (RpcSources, opt RpcConfig, GetBlockParams) -> (RequestCostResult) query;
Expand Down
32 changes: 29 additions & 3 deletions canister/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ use sol_rpc_canister::{
rpc_client::MultiRpcRequest,
};
use sol_rpc_types::{
AccountInfo, ConfirmedBlock, GetAccountInfoParams, GetBlockParams, GetSlotParams,
GetSlotRpcConfig, GetTransactionParams, MultiRpcResult, RpcAccess, RpcConfig, RpcResult,
RpcSources, SendTransactionParams, Signature, Slot, SupportedRpcProvider,
AccountInfo, ConfirmedBlock, GetAccountInfoParams, GetBalanceParams, GetBlockParams,
GetSlotParams, GetSlotRpcConfig, GetTransactionParams, Lamport, MultiRpcResult, RpcAccess,
RpcConfig, RpcResult, RpcSources, SendTransactionParams, Signature, Slot, SupportedRpcProvider,
SupportedRpcProviderId, TransactionInfo,
};
use std::str::FromStr;
Expand Down Expand Up @@ -101,6 +101,32 @@ async fn get_account_info_cycles_cost(
.await
}

#[update(name = "getBalance")]
#[candid_method(rename = "getBalance")]
async fn get_balance(
source: RpcSources,
config: Option<RpcConfig>,
params: GetBalanceParams,
) -> MultiRpcResult<Lamport> {
let request = MultiRpcRequest::get_balance(source, config.unwrap_or_default(), params);
send_multi(request).await
}

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

#[update(name = "getBlock")]
#[candid_method(rename = "getBlock")]
async fn get_block(
Expand Down
39 changes: 39 additions & 0 deletions canister/src/rpc_client/json/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,45 @@ pub struct GetAccountInfoConfig {
pub min_context_slot: Option<u64>,
}

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

#[skip_serializing_none]
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct GetBalanceConfig {
pub commitment: Option<CommitmentLevel>,
#[serde(rename = "minContextSlot")]
pub min_context_slot: Option<u64>,
}

impl From<sol_rpc_types::GetBalanceParams> for GetBalanceParams {
fn from(
sol_rpc_types::GetBalanceParams {
pubkey,
commitment,
min_context_slot,
}: sol_rpc_types::GetBalanceParams,
) -> Self {
let config = if commitment.is_some() || min_context_slot.is_some() {
Some(GetBalanceConfig {
commitment,
min_context_slot,
})
} else {
None
};
GetBalanceParams(pubkey, config)
}
}

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

#[skip_serializing_none]
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(into = "(Slot, Option<GetBlockConfig>)")]
Expand Down
28 changes: 26 additions & 2 deletions canister/src/rpc_client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ use ic_cdk::api::management_canister::http_request::{
};
use serde::{de::DeserializeOwned, Serialize};
use sol_rpc_types::{
ConsensusStrategy, GetSlotRpcConfig, ProviderError, RpcConfig, RpcError, RpcResult, RpcSource,
RpcSources, Signature, TransactionDetails,
ConsensusStrategy, GetSlotRpcConfig, Lamport, ProviderError, RpcConfig, RpcError, RpcResult,
RpcSource, RpcSources, Signature, TransactionDetails,
};
use solana_clock::Slot;
use std::{fmt::Debug, marker::PhantomData};
Expand Down Expand Up @@ -110,6 +110,30 @@ impl GetAccountInfoRequest {
}
}

pub type GetBalanceRequest = MultiRpcRequest<json::GetBalanceParams, Lamport>;

impl GetBalanceRequest {
pub fn get_balance<Params: Into<json::GetBalanceParams>>(
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(256 + HEADER_SIZE_LIMIT);

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

pub type GetBlockRequest = MultiRpcRequest<
json::GetBlockParams,
Option<solana_transaction_status_client_types::UiConfirmedBlock>,
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 @@ -22,9 +22,11 @@ pub enum ResponseTransform {
#[n(0)]
GetAccountInfo,
#[n(1)]
GetBlock,
GetBalance,
#[n(2)]
GetSlot(#[n(3)] RoundingError),
GetBlock,
#[n(3)]
GetSlot(#[n(0)] RoundingError),
#[n(4)]
GetTransaction,
#[n(5)]
Expand Down Expand Up @@ -56,6 +58,9 @@ impl ResponseTransform {
}
});
}
Self::GetBalance => {
canonicalize_response::<Value, Value>(body_bytes, |result| result["value"].clone());
}
Self::GetBlock => {
canonicalize_response::<Value, Option<Value>>(body_bytes, |result| match result {
Value::Null => None,
Expand Down
35 changes: 34 additions & 1 deletion canister/src/rpc_client/sol_rpc/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,39 @@ mod normalization_tests {
assert_normalized(&ResponseTransform::GetTransaction, "null", Value::Null);
}

#[test]
fn should_normalize_get_balance_response() {
assert_normalized(
&ResponseTransform::GetAccountInfo,
r#"{ "context": { "slot": 334035824, "apiVersion": "2.1.9" }, "value": 0 }"#,
json!(0),
);

assert_normalized(
&ResponseTransform::GetAccountInfo,
r#"{ "context": { "slot": 334035824, "apiVersion": "2.1.9" }, "value": 1000000 }"#,
json!(1000000),
);

assert_normalized_equal(
&ResponseTransform::GetBalance,
r#"{
"context": {
"slot": 334036571,
"apiVersion": "2.1.9"
},
"value": 1000000
}"#,
r#"{
"context": {
"slot": 334036572,
"apiVersion": "2.1.9"
},
"value": 1000000
}"#,
);
}

fn assert_normalized(transform: &ResponseTransform, result: &str, expected: Value) {
let expected_response = to_vec(&JsonRpcResponse::from_ok(Id::Number(1), expected)).unwrap();
let normalized_response = normalize_result(transform, result);
Expand All @@ -246,7 +279,7 @@ mod normalization_tests {
normalized_response,
"expected {:?}, actual: {:?}",
from_slice::<Value>(&expected_response),
expected_response,
from_slice::<Value>(&normalized_response),
);
}

Expand Down
37 changes: 37 additions & 0 deletions canister/src/rpc_client/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use sol_rpc_types::{

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

#[test]
fn should_serialize_get_account_info_request() {
Expand Down Expand Up @@ -117,6 +118,42 @@ mod request_serialization_tests {
);
}

#[test]
fn should_serialize_get_balance_request() {
let pubkey = solana_pubkey::Pubkey::default();
assert_serialized(
MultiRpcRequest::get_balance(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
GetBalanceParams::from(pubkey),
)
.unwrap(),
json!([pubkey.to_string(), null]),
);

assert_serialized(
MultiRpcRequest::get_balance(
RpcSources::Default(SolanaCluster::Mainnet),
RpcConfig::default(),
GetBalanceParams {
pubkey: pubkey.to_string(),
commitment: Some(CommitmentLevel::Confirmed),
min_context_slot: Some(42),
},
)
.unwrap(),
json!(
[
pubkey.to_string(),
{
"commitment": "confirmed",
"minContextSlot": 42
}
]
),
);
}

#[test]
fn should_serialize_get_block_request() {
assert_serialized(
Expand Down
23 changes: 5 additions & 18 deletions examples/basic_solana/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
use base64::{prelude::BASE64_STANDARD, Engine};
use candid::{CandidType, Deserialize, Nat, Principal};
use ic_cdk::{init, post_upgrade, update};
use num::{BigUint, ToPrimitive};
use num::ToPrimitive;
use serde_json::json;
use sol_rpc_client::{IcRuntime, SolRpcClient};
use sol_rpc_types::{
Expand Down Expand Up @@ -62,28 +62,15 @@ pub async fn associated_token_account(owner: Option<Principal>, mint_account: St
#[update]
pub async fn get_balance(account: Option<String>) -> Nat {
let account = account.unwrap_or(solana_account(None).await);

// TODO XC-346: use `getBalance` method from client
let response = client()
.json_request(json!({
"jsonrpc": "2.0",
"id": 1,
"method": "getBalance",
"params": [ account ]
}))
let public_key = Pubkey::from_str(&account).unwrap();
let balance = client()
.get_balance(public_key)
.send()
.await
.expect_consistent()
.expect("Call to `getBalance` failed");

// The response to a successful `getBalance` call has the following format:
// { "id": "[ID]", "jsonrpc": "2.0", "result": { "context": { "slot": [SLOT] } }, "value": [BALANCE] }, }
let balance = serde_json::to_value(response)
.expect("`getBalance` response is not a valid JSON")["result"]["value"]
.as_u64()
.unwrap();

Nat(BigUint::from(balance))
Nat::from(balance)
}

#[update]
Expand Down
Loading