v0.2.2-pre.004

This commit is contained in:
2026-08-18 08:09:20 +02:00
parent c4636ac8b9
commit 1d1bc6a4d6
16 changed files with 814 additions and 16 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 9
// version: 10
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
@@ -9,8 +9,8 @@
//! This crate owns runtime HTTP transport settings, Solana HTTP JSON-RPC envelopes and the audited standard method registry. It deliberately remains
//! independent from `ksp-config-lib`, Store and Program layers. `ksp-config-lib` now constructs these public settings through its one-way Config ->
//! Transport adapter without creating a reverse dependency. Logical endpoint clients, priority-aware pools, bounded admission limits and retry/no-resend policy
//! are available. The four typed Solana HTTP foundation canaries and the `0.2.2` Accounts wrappers execute real JSON-RPC requests through the shared
//! transport path while the remaining audited methods stay staged by subsequent `0.2.x` prereleases.
//! are available. The four typed Solana HTTP foundation canaries plus the `0.2.2` Accounts and Tokens wrappers execute real JSON-RPC requests through
//! the shared transport path while the remaining audited methods stay staged by subsequent `0.2.x` prereleases.
mod client;
mod constants;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_accounts.rs
// version: 3
// version: 4
/// Account-data encoding accepted by Solana HTTP account methods.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
@@ -116,7 +116,7 @@ impl SolanaAccountInfoConfig {
return self.context.min_context_slot();
}
fn is_empty(&self) -> bool {
pub(crate) fn is_empty(&self) -> bool {
return self.encoding.is_none() && self.data_slice.is_none() && self.commitment().is_none() && self.min_context_slot().is_none();
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/rpc_tokens.rs
// version: 2
// version: 3
/// Exclusive selector accepted by token-account list RPC methods.
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -13,7 +13,6 @@ pub enum SolanaTokenAccountSelector {
impl SolanaTokenAccountSelector {
/// Serializes the exclusive selector to the Solana JSON-RPC wire object.
#[must_use]
#[cfg(test)]
pub(crate) fn to_json_value(&self) -> serde_json::Value {
return match self {
Self::Mint(pubkey) => serde_json::json!({"mint": pubkey.to_string()}),
@@ -57,7 +56,6 @@ impl SolanaTokenAmount {
}
/// Decodes one token amount from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireTokenAmount>(method, value);
return match decoded {
@@ -93,7 +91,6 @@ impl SolanaTokenAccountBalance {
}
/// Decodes one token-account balance entry from the Solana JSON wire shape.
#[cfg(test)]
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
let decoded = crate::decode_wire_json::<WireTokenAccountBalance>(method, value);
let wire = match decoded {
@@ -115,7 +112,6 @@ impl SolanaTokenAccountBalance {
}
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireTokenAmount {
amount: std::string::String,
@@ -126,7 +122,6 @@ struct WireTokenAmount {
ui_amount_string: std::string::String,
}
#[cfg(test)]
#[derive(serde::Deserialize)]
struct WireTokenAccountBalance {
address: std::string::String,
@@ -138,6 +133,222 @@ struct WireTokenAccountBalance {
ui_amount_string: std::string::String,
}
#[derive(serde::Deserialize)]
struct WireRpcResponse<T> {
context: serde_json::Value,
value: T,
}
impl crate::HttpTransportPool {
/// Executes typed `getTokenAccountBalance` through the common KSP HTTP transport path.
pub async fn get_token_account_balance(
&self,
role: &crate::HttpRoleName,
token_account: &ksp_core_lib::Pubkey,
config: std::option::Option<&crate::SolanaCommitmentConfig>,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaTokenAmount>> {
let method_result = token_descriptor("getTokenAccountBalance");
let method = match method_result {
std::result::Result::Ok(method) => method,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut params = std::vec![serde_json::Value::String(token_account.to_string())];
push_commitment_config(&mut params, config);
let result = self.execute_standard_rpc(role, method, params).await;
let value = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return decode_token_amount_response("getTokenAccountBalance", value);
}
/// Executes typed `getTokenAccountsByDelegate` through the common KSP HTTP transport path.
pub async fn get_token_accounts_by_delegate(
&self,
role: &crate::HttpRoleName,
delegate: &ksp_core_lib::Pubkey,
selector: &crate::SolanaTokenAccountSelector,
config: std::option::Option<&crate::SolanaAccountInfoConfig>,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaKeyedAccount>>> {
return self.get_token_accounts_list("getTokenAccountsByDelegate", role, delegate, selector, config).await;
}
/// Executes typed `getTokenAccountsByOwner` through the common KSP HTTP transport path.
pub async fn get_token_accounts_by_owner(
&self,
role: &crate::HttpRoleName,
owner: &ksp_core_lib::Pubkey,
selector: &crate::SolanaTokenAccountSelector,
config: std::option::Option<&crate::SolanaAccountInfoConfig>,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaKeyedAccount>>> {
return self.get_token_accounts_list("getTokenAccountsByOwner", role, owner, selector, config).await;
}
/// Executes typed `getTokenLargestAccounts` through the common KSP HTTP transport path.
pub async fn get_token_largest_accounts(
&self,
role: &crate::HttpRoleName,
mint: &ksp_core_lib::Pubkey,
config: std::option::Option<&crate::SolanaCommitmentConfig>,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaTokenAccountBalance>>> {
let method_result = token_descriptor("getTokenLargestAccounts");
let method = match method_result {
std::result::Result::Ok(method) => method,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut params = std::vec![serde_json::Value::String(mint.to_string())];
push_commitment_config(&mut params, config);
let result = self.execute_standard_rpc(role, method, params).await;
let value = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return decode_token_account_balances_response("getTokenLargestAccounts", value);
}
/// Executes typed `getTokenSupply` through the common KSP HTTP transport path.
pub async fn get_token_supply(
&self,
role: &crate::HttpRoleName,
mint: &ksp_core_lib::Pubkey,
config: std::option::Option<&crate::SolanaCommitmentConfig>,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaTokenAmount>> {
let method_result = token_descriptor("getTokenSupply");
let method = match method_result {
std::result::Result::Ok(method) => method,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut params = std::vec![serde_json::Value::String(mint.to_string())];
push_commitment_config(&mut params, config);
let result = self.execute_standard_rpc(role, method, params).await;
let value = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return decode_token_amount_response("getTokenSupply", value);
}
async fn get_token_accounts_list(
&self,
method_name: &'static str,
role: &crate::HttpRoleName,
address: &ksp_core_lib::Pubkey,
selector: &crate::SolanaTokenAccountSelector,
config: std::option::Option<&crate::SolanaAccountInfoConfig>,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaKeyedAccount>>> {
let method_result = token_descriptor(method_name);
let method = match method_result {
std::result::Result::Ok(method) => method,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut params = std::vec![serde_json::Value::String(address.to_string()), selector.to_json_value()];
if let std::option::Option::Some(config) = config
&& !config.is_empty()
{
params.push(config.to_json_value());
}
let result = self.execute_standard_rpc(role, method, params).await;
let value = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return decode_keyed_accounts_response(method_name, value);
}
}
fn token_descriptor(method: &str) -> ksp_core_lib::Result<&'static crate::HttpRpcMethodDescriptor> {
let descriptor = crate::find_http_rpc_method(method);
return match descriptor {
std::option::Option::Some(descriptor)
if descriptor.category() == crate::HttpRpcCategory::Tokens && descriptor.coverage_release() == crate::HttpRpcCoverageRelease::V0_2_2 =>
{
std::result::Result::Ok(descriptor)
},
_ => std::result::Result::Err(
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Tokens descriptor is missing from the audited 0.2.2 registry")
.with_context("rpc_method", method),
),
};
}
fn push_commitment_config(params: &mut std::vec::Vec<serde_json::Value>, config: std::option::Option<&crate::SolanaCommitmentConfig>) {
if let std::option::Option::Some(config) = config
&& config.commitment().is_some()
{
params.push(config.to_json_value());
}
return;
}
fn decode_token_amount_response(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaTokenAmount>> {
let decoded = crate::decode_wire_json::<WireRpcResponse<serde_json::Value>>(method, value);
let wire = match decoded {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
let context = match context {
std::result::Result::Ok(context) => context,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let amount = crate::SolanaTokenAmount::decode_wire(method, wire.value);
let amount = match amount {
std::result::Result::Ok(amount) => amount,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::SolanaRpcResponse::new(context, amount));
}
fn decode_keyed_accounts_response(
method: &str,
value: serde_json::Value,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaKeyedAccount>>> {
let decoded = crate::decode_wire_json::<WireRpcResponse<std::vec::Vec<serde_json::Value>>>(method, value);
let wire = match decoded {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
let context = match context {
std::result::Result::Ok(context) => context,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut accounts = std::vec::Vec::with_capacity(wire.value.len());
for value in wire.value {
let account = crate::SolanaKeyedAccount::decode_wire(method, value);
match account {
std::result::Result::Ok(account) => accounts.push(account),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return std::result::Result::Ok(crate::SolanaRpcResponse::new(context, accounts));
}
fn decode_token_account_balances_response(
method: &str,
value: serde_json::Value,
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<crate::SolanaTokenAccountBalance>>> {
let decoded = crate::decode_wire_json::<WireRpcResponse<std::vec::Vec<serde_json::Value>>>(method, value);
let wire = match decoded {
std::result::Result::Ok(wire) => wire,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let context = crate::SolanaRpcContext::decode_wire(method, wire.context);
let context = match context {
std::result::Result::Ok(context) => context,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut accounts = std::vec::Vec::with_capacity(wire.value.len());
for value in wire.value {
let account = crate::SolanaTokenAccountBalance::decode_wire(method, value);
match account {
std::result::Result::Ok(account) => accounts.push(account),
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
}
return std::result::Result::Ok(crate::SolanaRpcResponse::new(context, accounts));
}
#[cfg(test)]
#[path = "../unit_tests/rpc_tokens.rs"]
mod tests;