1808 lines
76 KiB
Rust
1808 lines
76 KiB
Rust
// file: crates/ksp-onchain-transport-lib/src/rpc_transactions.rs
|
|
// version: 7
|
|
|
|
/// Binary encoding accepted for serialized transaction input payloads.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum SolanaTransactionBinaryEncoding {
|
|
/// Base58 text encoding.
|
|
Base58,
|
|
/// Base64 text encoding.
|
|
Base64,
|
|
}
|
|
|
|
impl SolanaTransactionBinaryEncoding {
|
|
/// Returns the Solana JSON-RPC encoding string.
|
|
#[must_use]
|
|
pub const fn as_str(self) -> &'static str {
|
|
return match self {
|
|
Self::Base58 => "base58",
|
|
Self::Base64 => "base64",
|
|
};
|
|
}
|
|
|
|
fn from_wire(value: &str) -> std::option::Option<Self> {
|
|
return match value {
|
|
"base58" => std::option::Option::Some(Self::Base58),
|
|
"base64" => std::option::Option::Some(Self::Base64),
|
|
_ => std::option::Option::None,
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Encoding accepted by `getTransaction`, including its retained legacy `binary` alias.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum SolanaTransactionEncoding {
|
|
/// Legacy base58-compatible `binary` alias retained by Solana RPC for backwards compatibility.
|
|
Binary,
|
|
/// Base58 encoded transaction bytes.
|
|
Base58,
|
|
/// Base64 encoded transaction bytes.
|
|
Base64,
|
|
/// Raw JSON transaction representation.
|
|
Json,
|
|
/// Parsed JSON transaction representation.
|
|
JsonParsed,
|
|
}
|
|
|
|
impl SolanaTransactionEncoding {
|
|
/// Returns the Solana JSON-RPC encoding string.
|
|
#[must_use]
|
|
pub const fn as_str(self) -> &'static str {
|
|
return match self {
|
|
Self::Binary => "binary",
|
|
Self::Base58 => "base58",
|
|
Self::Base64 => "base64",
|
|
Self::Json => "json",
|
|
Self::JsonParsed => "jsonParsed",
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Three-state wire field used when Solana distinguishes omission from an explicit JSON `null`.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum SolanaWireField<T> {
|
|
/// The field was not present in the decoded wire object.
|
|
Omitted,
|
|
/// The field was explicitly present with JSON `null`.
|
|
Null,
|
|
/// The field was present with a concrete value.
|
|
Value(T),
|
|
}
|
|
|
|
impl<T> SolanaWireField<T> {
|
|
/// Returns whether the field was omitted from the wire object.
|
|
#[must_use]
|
|
pub const fn is_omitted(&self) -> bool {
|
|
return match self {
|
|
Self::Omitted => true,
|
|
Self::Null | Self::Value(_) => false,
|
|
};
|
|
}
|
|
|
|
/// Returns whether the field was explicitly JSON `null`.
|
|
#[must_use]
|
|
pub const fn is_null(&self) -> bool {
|
|
return match self {
|
|
Self::Null => true,
|
|
Self::Omitted | Self::Value(_) => false,
|
|
};
|
|
}
|
|
|
|
/// Returns the concrete value when the field was present and non-null.
|
|
#[must_use]
|
|
pub const fn value(&self) -> std::option::Option<&T> {
|
|
return match self {
|
|
Self::Value(value) => std::option::Option::Some(value),
|
|
Self::Omitted | Self::Null => std::option::Option::None,
|
|
};
|
|
}
|
|
}
|
|
|
|
impl<T> std::default::Default for SolanaWireField<T> {
|
|
fn default() -> Self {
|
|
return Self::Omitted;
|
|
}
|
|
}
|
|
|
|
impl<'de, T> serde::Deserialize<'de> for SolanaWireField<T>
|
|
where
|
|
T: serde::Deserialize<'de>,
|
|
{
|
|
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let decoded = <std::option::Option<T> as serde::Deserialize>::deserialize(deserializer);
|
|
return match decoded {
|
|
std::result::Result::Ok(std::option::Option::Some(value)) => std::result::Result::Ok(Self::Value(value)),
|
|
std::result::Result::Ok(std::option::Option::None) => std::result::Result::Ok(Self::Null),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Modern configuration object accepted by `getTransaction`.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
pub struct SolanaGetTransactionConfig {
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
encoding: std::option::Option<crate::SolanaTransactionEncoding>,
|
|
max_supported_transaction_version: std::option::Option<u8>,
|
|
}
|
|
|
|
impl SolanaGetTransactionConfig {
|
|
/// Creates a modern `getTransaction` configuration.
|
|
#[must_use]
|
|
pub const fn new(
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
encoding: std::option::Option<crate::SolanaTransactionEncoding>,
|
|
max_supported_transaction_version: std::option::Option<u8>,
|
|
) -> Self {
|
|
return Self { commitment, encoding, max_supported_transaction_version };
|
|
}
|
|
|
|
/// Returns the optional commitment level.
|
|
#[must_use]
|
|
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
|
return self.commitment;
|
|
}
|
|
|
|
/// Returns the optional transaction response encoding.
|
|
#[must_use]
|
|
pub const fn encoding(&self) -> std::option::Option<crate::SolanaTransactionEncoding> {
|
|
return self.encoding;
|
|
}
|
|
|
|
/// Returns the highest transaction version the caller declares it can consume.
|
|
#[must_use]
|
|
pub const fn max_supported_transaction_version(&self) -> std::option::Option<u8> {
|
|
return self.max_supported_transaction_version;
|
|
}
|
|
|
|
/// Returns whether the modern config would serialize to an empty object.
|
|
#[cfg(test)]
|
|
pub(crate) const fn is_empty(&self) -> bool {
|
|
return self.commitment.is_none() && self.encoding.is_none() && self.max_supported_transaction_version.is_none();
|
|
}
|
|
|
|
/// Serializes the modern config to its Solana JSON-RPC wire object.
|
|
#[must_use]
|
|
pub(crate) fn to_json_value(self) -> serde_json::Value {
|
|
let mut object = serde_json::Map::new();
|
|
if let std::option::Option::Some(commitment) = self.commitment {
|
|
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
|
|
}
|
|
if let std::option::Option::Some(encoding) = self.encoding {
|
|
object.insert("encoding".to_owned(), serde_json::Value::String(encoding.as_str().to_owned()));
|
|
}
|
|
if let std::option::Option::Some(version) = self.max_supported_transaction_version {
|
|
object.insert("maxSupportedTransactionVersion".to_owned(), serde_json::Value::Number(version.into()));
|
|
}
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Pagination and context configuration accepted by `getSignaturesForAddress`.
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
pub struct SolanaSignaturesForAddressConfig {
|
|
before: std::option::Option<std::string::String>,
|
|
until: std::option::Option<std::string::String>,
|
|
limit: std::option::Option<usize>,
|
|
context: crate::SolanaContextConfig,
|
|
}
|
|
|
|
impl SolanaSignaturesForAddressConfig {
|
|
/// Creates an address-signature pagination configuration.
|
|
#[must_use]
|
|
pub fn new(
|
|
before: std::option::Option<std::string::String>,
|
|
until: std::option::Option<std::string::String>,
|
|
limit: std::option::Option<usize>,
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
min_context_slot: std::option::Option<u64>,
|
|
) -> Self {
|
|
return Self { before, until, limit, context: crate::SolanaContextConfig::new(commitment, min_context_slot) };
|
|
}
|
|
|
|
/// Returns the exclusive pagination signature preceding the requested page.
|
|
#[must_use]
|
|
pub fn before(&self) -> std::option::Option<&str> {
|
|
return self.before.as_deref();
|
|
}
|
|
|
|
/// Returns the exclusive pagination signature terminating the requested range.
|
|
#[must_use]
|
|
pub fn until(&self) -> std::option::Option<&str> {
|
|
return self.until.as_deref();
|
|
}
|
|
|
|
/// Returns the optional requested page size.
|
|
#[must_use]
|
|
pub const fn limit(&self) -> std::option::Option<usize> {
|
|
return self.limit;
|
|
}
|
|
|
|
/// Returns the optional commitment level.
|
|
#[must_use]
|
|
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
|
return self.context.commitment();
|
|
}
|
|
|
|
/// Returns the optional minimum context slot.
|
|
#[must_use]
|
|
pub const fn min_context_slot(&self) -> std::option::Option<u64> {
|
|
return self.context.min_context_slot();
|
|
}
|
|
|
|
/// Returns whether the pagination config would serialize to an empty object.
|
|
pub(crate) fn is_empty(&self) -> bool {
|
|
return self.before.is_none() && self.until.is_none() && self.limit.is_none() && self.commitment().is_none() && self.min_context_slot().is_none();
|
|
}
|
|
|
|
/// Serializes this pagination config to the Solana JSON-RPC wire object.
|
|
#[must_use]
|
|
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
|
let context = self.context.to_json_value();
|
|
let mut object = match context {
|
|
serde_json::Value::Object(object) => object,
|
|
_ => serde_json::Map::new(),
|
|
};
|
|
if let std::option::Option::Some(before) = self.before.as_ref() {
|
|
object.insert("before".to_owned(), serde_json::Value::String(before.clone()));
|
|
}
|
|
if let std::option::Option::Some(until) = self.until.as_ref() {
|
|
object.insert("until".to_owned(), serde_json::Value::String(until.clone()));
|
|
}
|
|
if let std::option::Option::Some(limit) = self.limit {
|
|
object.insert("limit".to_owned(), serde_json::json!(limit));
|
|
}
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Optional historical-search configuration accepted by `getSignatureStatuses`.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
pub struct SolanaSignatureStatusesConfig {
|
|
search_transaction_history: std::option::Option<bool>,
|
|
}
|
|
|
|
impl SolanaSignatureStatusesConfig {
|
|
/// Creates a signature-status configuration.
|
|
#[must_use]
|
|
pub const fn new(search_transaction_history: std::option::Option<bool>) -> Self {
|
|
return Self { search_transaction_history };
|
|
}
|
|
|
|
/// Returns whether full transaction history should be searched when explicitly configured.
|
|
#[must_use]
|
|
pub const fn search_transaction_history(&self) -> std::option::Option<bool> {
|
|
return self.search_transaction_history;
|
|
}
|
|
|
|
/// Returns whether the status config would serialize to an empty object.
|
|
pub(crate) const fn is_empty(&self) -> bool {
|
|
return self.search_transaction_history.is_none();
|
|
}
|
|
|
|
/// Serializes this config to the Solana JSON-RPC wire object.
|
|
#[must_use]
|
|
pub(crate) fn to_json_value(self) -> serde_json::Value {
|
|
let mut object = serde_json::Map::new();
|
|
if let std::option::Option::Some(value) = self.search_transaction_history {
|
|
object.insert("searchTransactionHistory".to_owned(), serde_json::Value::Bool(value));
|
|
}
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Configuration accepted by `requestAirdrop`.
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
pub struct SolanaRequestAirdropConfig {
|
|
recent_blockhash: std::option::Option<std::string::String>,
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
}
|
|
|
|
impl SolanaRequestAirdropConfig {
|
|
/// Creates an airdrop request configuration.
|
|
#[must_use]
|
|
pub fn new(recent_blockhash: std::option::Option<std::string::String>, commitment: std::option::Option<crate::SolanaCommitment>) -> Self {
|
|
return Self { recent_blockhash, commitment };
|
|
}
|
|
|
|
/// Returns the optional recent blockhash supplied to the faucet RPC.
|
|
#[must_use]
|
|
pub fn recent_blockhash(&self) -> std::option::Option<&str> {
|
|
return self.recent_blockhash.as_deref();
|
|
}
|
|
|
|
/// Returns the optional commitment level.
|
|
#[must_use]
|
|
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
|
return self.commitment;
|
|
}
|
|
|
|
/// Returns whether the airdrop config would serialize to an empty object.
|
|
pub(crate) fn is_empty(&self) -> bool {
|
|
return self.recent_blockhash.is_none() && self.commitment.is_none();
|
|
}
|
|
|
|
/// Serializes this config to the Solana JSON-RPC wire object.
|
|
#[must_use]
|
|
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
|
let mut object = serde_json::Map::new();
|
|
if let std::option::Option::Some(blockhash) = self.recent_blockhash.as_ref() {
|
|
object.insert("recentBlockhash".to_owned(), serde_json::Value::String(blockhash.clone()));
|
|
}
|
|
if let std::option::Option::Some(commitment) = self.commitment {
|
|
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
|
|
}
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Configuration accepted by `sendTransaction` without changing KSP transport retry semantics.
|
|
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
|
pub struct SolanaSendTransactionConfig {
|
|
skip_preflight: std::option::Option<bool>,
|
|
preflight_commitment: std::option::Option<crate::SolanaCommitment>,
|
|
encoding: std::option::Option<crate::SolanaTransactionBinaryEncoding>,
|
|
max_retries: std::option::Option<usize>,
|
|
min_context_slot: std::option::Option<u64>,
|
|
}
|
|
|
|
impl SolanaSendTransactionConfig {
|
|
/// Creates a serialized-transaction submission configuration.
|
|
#[must_use]
|
|
pub const fn new(
|
|
skip_preflight: std::option::Option<bool>,
|
|
preflight_commitment: std::option::Option<crate::SolanaCommitment>,
|
|
encoding: std::option::Option<crate::SolanaTransactionBinaryEncoding>,
|
|
max_retries: std::option::Option<usize>,
|
|
min_context_slot: std::option::Option<u64>,
|
|
) -> Self {
|
|
return Self { skip_preflight, preflight_commitment, encoding, max_retries, min_context_slot };
|
|
}
|
|
|
|
/// Returns the optional preflight-skip flag.
|
|
#[must_use]
|
|
pub const fn skip_preflight(&self) -> std::option::Option<bool> {
|
|
return self.skip_preflight;
|
|
}
|
|
|
|
/// Returns the optional preflight commitment.
|
|
#[must_use]
|
|
pub const fn preflight_commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
|
return self.preflight_commitment;
|
|
}
|
|
|
|
/// Returns the optional serialized transaction encoding.
|
|
#[must_use]
|
|
pub const fn encoding(&self) -> std::option::Option<crate::SolanaTransactionBinaryEncoding> {
|
|
return self.encoding;
|
|
}
|
|
|
|
/// Returns the optional node-side retransmission limit.
|
|
#[must_use]
|
|
pub const fn max_retries(&self) -> std::option::Option<usize> {
|
|
return self.max_retries;
|
|
}
|
|
|
|
/// Returns the optional minimum context slot.
|
|
#[must_use]
|
|
pub const fn min_context_slot(&self) -> std::option::Option<u64> {
|
|
return self.min_context_slot;
|
|
}
|
|
|
|
/// Returns whether the send config would serialize to an empty object.
|
|
pub(crate) const fn is_empty(&self) -> bool {
|
|
return self.skip_preflight.is_none()
|
|
&& self.preflight_commitment.is_none()
|
|
&& self.encoding.is_none()
|
|
&& self.max_retries.is_none()
|
|
&& self.min_context_slot.is_none();
|
|
}
|
|
|
|
/// Serializes this config to the Solana JSON-RPC wire object.
|
|
#[must_use]
|
|
pub(crate) fn to_json_value(self) -> serde_json::Value {
|
|
let mut object = serde_json::Map::new();
|
|
if let std::option::Option::Some(value) = self.skip_preflight {
|
|
object.insert("skipPreflight".to_owned(), serde_json::Value::Bool(value));
|
|
}
|
|
if let std::option::Option::Some(commitment) = self.preflight_commitment {
|
|
object.insert("preflightCommitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
|
|
}
|
|
if let std::option::Option::Some(encoding) = self.encoding {
|
|
object.insert("encoding".to_owned(), serde_json::Value::String(encoding.as_str().to_owned()));
|
|
}
|
|
if let std::option::Option::Some(max_retries) = self.max_retries {
|
|
object.insert("maxRetries".to_owned(), serde_json::json!(max_retries));
|
|
}
|
|
if let std::option::Option::Some(min_context_slot) = self.min_context_slot {
|
|
object.insert("minContextSlot".to_owned(), serde_json::Value::Number(min_context_slot.into()));
|
|
}
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Account-return configuration nested under `simulateTransaction`.
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
pub struct SolanaSimulationAccountsConfig {
|
|
encoding: std::option::Option<crate::SolanaAccountEncoding>,
|
|
addresses: std::vec::Vec<ksp_core_lib::Pubkey>,
|
|
}
|
|
|
|
impl SolanaSimulationAccountsConfig {
|
|
/// Creates a simulation account-return configuration.
|
|
#[must_use]
|
|
pub fn new(encoding: std::option::Option<crate::SolanaAccountEncoding>, addresses: std::vec::Vec<ksp_core_lib::Pubkey>) -> Self {
|
|
return Self { encoding, addresses };
|
|
}
|
|
|
|
/// Returns the optional account-data encoding.
|
|
#[must_use]
|
|
pub const fn encoding(&self) -> std::option::Option<crate::SolanaAccountEncoding> {
|
|
return self.encoding;
|
|
}
|
|
|
|
/// Returns the requested account addresses in caller order.
|
|
#[must_use]
|
|
pub fn addresses(&self) -> &[ksp_core_lib::Pubkey] {
|
|
return self.addresses.as_slice();
|
|
}
|
|
|
|
/// Serializes the nested account config to the Solana JSON-RPC wire object.
|
|
#[must_use]
|
|
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
|
let mut object = serde_json::Map::new();
|
|
if let std::option::Option::Some(encoding) = self.encoding {
|
|
object.insert("encoding".to_owned(), serde_json::Value::String(encoding.as_str().to_owned()));
|
|
}
|
|
let mut addresses = std::vec::Vec::with_capacity(self.addresses.len());
|
|
for address in &self.addresses {
|
|
addresses.push(serde_json::Value::String(address.to_string()));
|
|
}
|
|
object.insert("addresses".to_owned(), serde_json::Value::Array(addresses));
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Configuration accepted by `simulateTransaction`.
|
|
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
pub struct SolanaSimulateTransactionConfig {
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
encoding: std::option::Option<crate::SolanaTransactionBinaryEncoding>,
|
|
replace_recent_blockhash: std::option::Option<bool>,
|
|
sig_verify: std::option::Option<bool>,
|
|
min_context_slot: std::option::Option<u64>,
|
|
inner_instructions: std::option::Option<bool>,
|
|
accounts: std::option::Option<crate::SolanaSimulationAccountsConfig>,
|
|
}
|
|
|
|
impl SolanaSimulateTransactionConfig {
|
|
/// Creates a simulation configuration while leaving deterministic validation to the simulation wrapper.
|
|
#[must_use]
|
|
pub fn new(
|
|
commitment: std::option::Option<crate::SolanaCommitment>,
|
|
encoding: std::option::Option<crate::SolanaTransactionBinaryEncoding>,
|
|
replace_recent_blockhash: std::option::Option<bool>,
|
|
sig_verify: std::option::Option<bool>,
|
|
min_context_slot: std::option::Option<u64>,
|
|
inner_instructions: std::option::Option<bool>,
|
|
accounts: std::option::Option<crate::SolanaSimulationAccountsConfig>,
|
|
) -> Self {
|
|
return Self {
|
|
commitment,
|
|
encoding,
|
|
replace_recent_blockhash,
|
|
sig_verify,
|
|
min_context_slot,
|
|
inner_instructions,
|
|
accounts,
|
|
};
|
|
}
|
|
|
|
/// Returns the optional simulation commitment.
|
|
#[must_use]
|
|
pub const fn commitment(&self) -> std::option::Option<crate::SolanaCommitment> {
|
|
return self.commitment;
|
|
}
|
|
|
|
/// Returns the optional serialized transaction encoding.
|
|
#[must_use]
|
|
pub const fn encoding(&self) -> std::option::Option<crate::SolanaTransactionBinaryEncoding> {
|
|
return self.encoding;
|
|
}
|
|
|
|
/// Returns the optional recent-blockhash replacement flag.
|
|
#[must_use]
|
|
pub const fn replace_recent_blockhash(&self) -> std::option::Option<bool> {
|
|
return self.replace_recent_blockhash;
|
|
}
|
|
|
|
/// Returns the optional signature-verification flag.
|
|
#[must_use]
|
|
pub const fn sig_verify(&self) -> std::option::Option<bool> {
|
|
return self.sig_verify;
|
|
}
|
|
|
|
/// Returns the optional minimum context slot.
|
|
#[must_use]
|
|
pub const fn min_context_slot(&self) -> std::option::Option<u64> {
|
|
return self.min_context_slot;
|
|
}
|
|
|
|
/// Returns the optional inner-instruction recording flag.
|
|
#[must_use]
|
|
pub const fn inner_instructions(&self) -> std::option::Option<bool> {
|
|
return self.inner_instructions;
|
|
}
|
|
|
|
/// Returns the optional post-simulation account-return configuration.
|
|
#[must_use]
|
|
pub const fn accounts(&self) -> std::option::Option<&crate::SolanaSimulationAccountsConfig> {
|
|
return self.accounts.as_ref();
|
|
}
|
|
|
|
/// Returns whether the simulation config would serialize to an empty object.
|
|
pub(crate) fn is_empty(&self) -> bool {
|
|
return self.commitment.is_none()
|
|
&& self.encoding.is_none()
|
|
&& self.replace_recent_blockhash.is_none()
|
|
&& self.sig_verify.is_none()
|
|
&& self.min_context_slot.is_none()
|
|
&& self.inner_instructions.is_none()
|
|
&& self.accounts.is_none();
|
|
}
|
|
|
|
/// Serializes this config to the Solana JSON-RPC wire object.
|
|
#[must_use]
|
|
pub(crate) fn to_json_value(&self) -> serde_json::Value {
|
|
let mut object = serde_json::Map::new();
|
|
if let std::option::Option::Some(commitment) = self.commitment {
|
|
object.insert("commitment".to_owned(), serde_json::Value::String(commitment.as_str().to_owned()));
|
|
}
|
|
if let std::option::Option::Some(encoding) = self.encoding {
|
|
object.insert("encoding".to_owned(), serde_json::Value::String(encoding.as_str().to_owned()));
|
|
}
|
|
if let std::option::Option::Some(value) = self.replace_recent_blockhash {
|
|
object.insert("replaceRecentBlockhash".to_owned(), serde_json::Value::Bool(value));
|
|
}
|
|
if let std::option::Option::Some(value) = self.sig_verify {
|
|
object.insert("sigVerify".to_owned(), serde_json::Value::Bool(value));
|
|
}
|
|
if let std::option::Option::Some(min_context_slot) = self.min_context_slot {
|
|
object.insert("minContextSlot".to_owned(), serde_json::Value::Number(min_context_slot.into()));
|
|
}
|
|
if let std::option::Option::Some(value) = self.inner_instructions {
|
|
object.insert("innerInstructions".to_owned(), serde_json::Value::Bool(value));
|
|
}
|
|
if let std::option::Option::Some(accounts) = self.accounts.as_ref() {
|
|
object.insert("accounts".to_owned(), accounts.to_json_value());
|
|
}
|
|
return serde_json::Value::Object(object);
|
|
}
|
|
}
|
|
|
|
/// Blockhash information returned by `getLatestBlockhash` and optionally by simulation.
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub struct SolanaLatestBlockhash {
|
|
blockhash: std::string::String,
|
|
last_valid_block_height: u64,
|
|
}
|
|
|
|
impl SolanaLatestBlockhash {
|
|
/// Returns the base58 blockhash string without locally decoding it.
|
|
#[must_use]
|
|
pub fn blockhash(&self) -> &str {
|
|
return self.blockhash.as_str();
|
|
}
|
|
|
|
/// Returns the last valid block height for this blockhash.
|
|
#[must_use]
|
|
pub const fn last_valid_block_height(&self) -> u64 {
|
|
return self.last_valid_block_height;
|
|
}
|
|
|
|
/// Decodes a latest-blockhash object from the Solana JSON wire shape.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireLatestBlockhash>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if wire.blockhash.is_empty() {
|
|
return std::result::Result::Err(invalid_transaction_wire(method, "blockhash", "latest blockhash must not be empty"));
|
|
}
|
|
return std::result::Result::Ok(Self { blockhash: wire.blockhash, last_valid_block_height: wire.last_valid_block_height });
|
|
}
|
|
}
|
|
|
|
/// One recent prioritization-fee sample returned by `getRecentPrioritizationFees`.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub struct SolanaPrioritizationFee {
|
|
slot: u64,
|
|
prioritization_fee: u64,
|
|
}
|
|
|
|
impl SolanaPrioritizationFee {
|
|
/// Returns the sample slot.
|
|
#[must_use]
|
|
pub const fn slot(&self) -> u64 {
|
|
return self.slot;
|
|
}
|
|
|
|
/// Returns the minimum prioritization fee in micro-lamports reported for the sample.
|
|
#[must_use]
|
|
pub const fn prioritization_fee(&self) -> u64 {
|
|
return self.prioritization_fee;
|
|
}
|
|
|
|
/// Decodes one prioritization-fee sample from the Solana JSON wire shape.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WirePrioritizationFee>(method, value);
|
|
return match decoded {
|
|
std::result::Result::Ok(wire) => std::result::Result::Ok(Self { slot: wire.slot, prioritization_fee: wire.prioritization_fee }),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Confirmation state reported for a signature or transaction status.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum SolanaTransactionConfirmationStatus {
|
|
/// The transaction has been processed by a node.
|
|
Processed,
|
|
/// The transaction has reached confirmed commitment.
|
|
Confirmed,
|
|
/// The transaction has reached finalized commitment.
|
|
Finalized,
|
|
}
|
|
|
|
/// One ordered signature record returned by `getSignaturesForAddress`.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SolanaSignatureInfo {
|
|
signature: std::string::String,
|
|
slot: u64,
|
|
err: std::option::Option<serde_json::Value>,
|
|
memo: std::option::Option<std::string::String>,
|
|
block_time: std::option::Option<i64>,
|
|
confirmation_status: std::option::Option<crate::SolanaTransactionConfirmationStatus>,
|
|
transaction_index: std::option::Option<u32>,
|
|
}
|
|
|
|
impl SolanaSignatureInfo {
|
|
/// Returns the base58 transaction signature string without locally decoding it.
|
|
#[must_use]
|
|
pub fn signature(&self) -> &str {
|
|
return self.signature.as_str();
|
|
}
|
|
|
|
/// Returns the transaction slot.
|
|
#[must_use]
|
|
pub const fn slot(&self) -> u64 {
|
|
return self.slot;
|
|
}
|
|
|
|
/// Returns the nullable transaction error wire value.
|
|
#[must_use]
|
|
pub const fn err(&self) -> std::option::Option<&serde_json::Value> {
|
|
return self.err.as_ref();
|
|
}
|
|
|
|
/// Returns the nullable memo text.
|
|
#[must_use]
|
|
pub fn memo(&self) -> std::option::Option<&str> {
|
|
return self.memo.as_deref();
|
|
}
|
|
|
|
/// Returns the nullable block time as a Unix timestamp.
|
|
#[must_use]
|
|
pub const fn block_time(&self) -> std::option::Option<i64> {
|
|
return self.block_time;
|
|
}
|
|
|
|
/// Returns the optional confirmation status.
|
|
#[must_use]
|
|
pub const fn confirmation_status(&self) -> std::option::Option<crate::SolanaTransactionConfirmationStatus> {
|
|
return self.confirmation_status;
|
|
}
|
|
|
|
/// Returns the optional transaction index exposed by current Agave runtimes.
|
|
#[must_use]
|
|
pub const fn transaction_index(&self) -> std::option::Option<u32> {
|
|
return self.transaction_index;
|
|
}
|
|
|
|
/// Decodes one signature record from the Solana JSON wire shape.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireSignatureInfo>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let status = decode_confirmation_status(method, "confirmationStatus", wire.confirmation_status);
|
|
let confirmation_status = match status {
|
|
std::result::Result::Ok(status) => status,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(Self {
|
|
signature: wire.signature,
|
|
slot: wire.slot,
|
|
err: wire.err,
|
|
memo: wire.memo,
|
|
block_time: wire.block_time,
|
|
confirmation_status,
|
|
transaction_index: wire.transaction_index,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// One non-null position returned by `getSignatureStatuses`.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SolanaSignatureStatus {
|
|
slot: u64,
|
|
confirmations: std::option::Option<u64>,
|
|
status: serde_json::Value,
|
|
err: std::option::Option<serde_json::Value>,
|
|
confirmation_status: std::option::Option<crate::SolanaTransactionConfirmationStatus>,
|
|
}
|
|
|
|
impl SolanaSignatureStatus {
|
|
/// Returns the slot at which the signature status was observed.
|
|
#[must_use]
|
|
pub const fn slot(&self) -> u64 {
|
|
return self.slot;
|
|
}
|
|
|
|
/// Returns remaining confirmations, or `None` when the transaction is rooted.
|
|
#[must_use]
|
|
pub const fn confirmations(&self) -> std::option::Option<u64> {
|
|
return self.confirmations;
|
|
}
|
|
|
|
/// Returns the legacy `status` result field losslessly.
|
|
#[must_use]
|
|
pub const fn status(&self) -> &serde_json::Value {
|
|
return &self.status;
|
|
}
|
|
|
|
/// Returns the nullable transaction error wire value.
|
|
#[must_use]
|
|
pub const fn err(&self) -> std::option::Option<&serde_json::Value> {
|
|
return self.err.as_ref();
|
|
}
|
|
|
|
/// Returns the optional confirmation status.
|
|
#[must_use]
|
|
pub const fn confirmation_status(&self) -> std::option::Option<crate::SolanaTransactionConfirmationStatus> {
|
|
return self.confirmation_status;
|
|
}
|
|
|
|
/// Decodes one present signature status from the Solana JSON wire shape.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireSignatureStatus>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let status = decode_confirmation_status(method, "confirmationStatus", wire.confirmation_status);
|
|
let confirmation_status = match status {
|
|
std::result::Result::Ok(status) => status,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(Self {
|
|
slot: wire.slot,
|
|
confirmations: wire.confirmations,
|
|
status: wire.status,
|
|
err: wire.err,
|
|
confirmation_status,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Wire-preserving transaction payload returned by `getTransaction`.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum SolanaEncodedTransaction {
|
|
/// Legacy single-string base58 form retained for RPC backwards compatibility.
|
|
LegacyBinary(std::string::String),
|
|
/// Explicit encoded tuple `[data, "base58" | "base64"]`.
|
|
Binary {
|
|
/// Encoded transaction bytes.
|
|
data: std::string::String,
|
|
/// Explicit binary encoding label.
|
|
encoding: crate::SolanaTransactionBinaryEncoding,
|
|
},
|
|
/// JSON or `jsonParsed` object preserved without Program-specific decoding.
|
|
Json(serde_json::Value),
|
|
}
|
|
|
|
impl SolanaEncodedTransaction {
|
|
/// Decodes the untagged transaction wire union without decoding transaction bytes.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
return match value {
|
|
serde_json::Value::String(data) => std::result::Result::Ok(Self::LegacyBinary(data)),
|
|
serde_json::Value::Object(object) => std::result::Result::Ok(Self::Json(serde_json::Value::Object(object))),
|
|
serde_json::Value::Array(values) => decode_binary_transaction_tuple(method, values),
|
|
_ => std::result::Result::Err(invalid_transaction_wire(method, "transaction", "transaction payload has an unsupported JSON shape")),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Transaction version reported by `getTransaction` when the version field is present.
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
pub enum SolanaTransactionVersion {
|
|
/// Legacy transaction without a versioned message prefix.
|
|
Legacy,
|
|
/// Numeric versioned-transaction identifier.
|
|
Number(u8),
|
|
}
|
|
|
|
impl SolanaTransactionVersion {
|
|
fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
return match value {
|
|
serde_json::Value::String(value) if value == "legacy" => std::result::Result::Ok(Self::Legacy),
|
|
serde_json::Value::Number(value) => {
|
|
let number = value.as_u64();
|
|
let number = match number {
|
|
std::option::Option::Some(number) => number,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(invalid_transaction_wire(
|
|
method,
|
|
"version",
|
|
"transaction version must be an unsigned integer or legacy",
|
|
));
|
|
},
|
|
};
|
|
let converted = u8::try_from(number);
|
|
return match converted {
|
|
std::result::Result::Ok(number) => std::result::Result::Ok(Self::Number(number)),
|
|
std::result::Result::Err(_) => {
|
|
std::result::Result::Err(invalid_transaction_wire(method, "version", "transaction version is outside the supported wire integer range"))
|
|
},
|
|
};
|
|
},
|
|
_ => std::result::Result::Err(invalid_transaction_wire(method, "version", "transaction version must be an unsigned integer or legacy")),
|
|
};
|
|
}
|
|
}
|
|
|
|
/// Confirmed transaction result returned by `getTransaction` when the RPC result is non-null.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SolanaConfirmedTransaction {
|
|
slot: u64,
|
|
block_time: std::option::Option<i64>,
|
|
transaction: crate::SolanaEncodedTransaction,
|
|
meta: crate::SolanaWireField<serde_json::Value>,
|
|
version: crate::SolanaWireField<crate::SolanaTransactionVersion>,
|
|
transaction_index: crate::SolanaWireField<u32>,
|
|
}
|
|
|
|
impl SolanaConfirmedTransaction {
|
|
/// Returns the containing slot.
|
|
#[must_use]
|
|
pub const fn slot(&self) -> u64 {
|
|
return self.slot;
|
|
}
|
|
|
|
/// Returns the nullable block time as a Unix timestamp.
|
|
#[must_use]
|
|
pub const fn block_time(&self) -> std::option::Option<i64> {
|
|
return self.block_time;
|
|
}
|
|
|
|
/// Returns the wire-preserving encoded/JSON transaction payload.
|
|
#[must_use]
|
|
pub const fn transaction(&self) -> &crate::SolanaEncodedTransaction {
|
|
return &self.transaction;
|
|
}
|
|
|
|
/// Returns the transaction metadata while preserving omitted/null/present states.
|
|
#[must_use]
|
|
pub const fn meta(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
|
return &self.meta;
|
|
}
|
|
|
|
/// Returns the transaction version while preserving omitted/null/present states.
|
|
#[must_use]
|
|
pub const fn version(&self) -> &crate::SolanaWireField<crate::SolanaTransactionVersion> {
|
|
return &self.version;
|
|
}
|
|
|
|
/// Returns the current optional transaction index while preserving omitted/null/present states.
|
|
#[must_use]
|
|
pub const fn transaction_index(&self) -> &crate::SolanaWireField<u32> {
|
|
return &self.transaction_index;
|
|
}
|
|
|
|
/// Decodes one non-null confirmed transaction without decoding Program-specific transaction internals.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireConfirmedTransaction>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let transaction = crate::SolanaEncodedTransaction::decode_wire(method, wire.transaction);
|
|
let transaction = match transaction {
|
|
std::result::Result::Ok(transaction) => transaction,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let version = decode_transaction_version_field(method, wire.version);
|
|
let version = match version {
|
|
std::result::Result::Ok(version) => version,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(Self {
|
|
slot: wire.slot,
|
|
block_time: wire.block_time,
|
|
transaction,
|
|
meta: wire.meta,
|
|
version,
|
|
transaction_index: wire.transaction_index,
|
|
});
|
|
}
|
|
}
|
|
|
|
/// Rich result payload returned inside the contextual `simulateTransaction` response.
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub struct SolanaSimulateTransactionResult {
|
|
err: crate::SolanaWireField<serde_json::Value>,
|
|
logs: crate::SolanaWireField<std::vec::Vec<std::string::String>>,
|
|
accounts: crate::SolanaWireField<std::vec::Vec<std::option::Option<crate::SolanaAccount>>>,
|
|
units_consumed: crate::SolanaWireField<u64>,
|
|
loaded_accounts_data_size: crate::SolanaWireField<u32>,
|
|
return_data: crate::SolanaWireField<serde_json::Value>,
|
|
inner_instructions: crate::SolanaWireField<serde_json::Value>,
|
|
replacement_blockhash: crate::SolanaWireField<crate::SolanaLatestBlockhash>,
|
|
fee: crate::SolanaWireField<u64>,
|
|
pre_balances: crate::SolanaWireField<std::vec::Vec<u64>>,
|
|
post_balances: crate::SolanaWireField<std::vec::Vec<u64>>,
|
|
pre_token_balances: crate::SolanaWireField<serde_json::Value>,
|
|
post_token_balances: crate::SolanaWireField<serde_json::Value>,
|
|
loaded_addresses: crate::SolanaWireField<serde_json::Value>,
|
|
}
|
|
|
|
impl SolanaSimulateTransactionResult {
|
|
/// Returns the simulation error while preserving omission versus explicit `null`.
|
|
#[must_use]
|
|
pub const fn err(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
|
return &self.err;
|
|
}
|
|
|
|
/// Returns simulation logs while preserving omission versus explicit `null`.
|
|
#[must_use]
|
|
pub const fn logs(&self) -> &crate::SolanaWireField<std::vec::Vec<std::string::String>> {
|
|
return &self.logs;
|
|
}
|
|
|
|
/// Returns requested post-simulation accounts with positional nulls preserved.
|
|
#[must_use]
|
|
pub const fn accounts(&self) -> &crate::SolanaWireField<std::vec::Vec<std::option::Option<crate::SolanaAccount>>> {
|
|
return &self.accounts;
|
|
}
|
|
|
|
/// Returns compute units consumed when the runtime reports them.
|
|
#[must_use]
|
|
pub const fn units_consumed(&self) -> &crate::SolanaWireField<u64> {
|
|
return &self.units_consumed;
|
|
}
|
|
|
|
/// Returns loaded-account data size when the runtime reports it.
|
|
#[must_use]
|
|
pub const fn loaded_accounts_data_size(&self) -> &crate::SolanaWireField<u32> {
|
|
return &self.loaded_accounts_data_size;
|
|
}
|
|
|
|
/// Returns Program return data losslessly at the transport boundary.
|
|
#[must_use]
|
|
pub const fn return_data(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
|
return &self.return_data;
|
|
}
|
|
|
|
/// Returns inner instructions losslessly without Program-specific decoding.
|
|
#[must_use]
|
|
pub const fn inner_instructions(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
|
return &self.inner_instructions;
|
|
}
|
|
|
|
/// Returns a replacement blockhash when the runtime produced one.
|
|
#[must_use]
|
|
pub const fn replacement_blockhash(&self) -> &crate::SolanaWireField<crate::SolanaLatestBlockhash> {
|
|
return &self.replacement_blockhash;
|
|
}
|
|
|
|
/// Returns the simulated fee when reported.
|
|
#[must_use]
|
|
pub const fn fee(&self) -> &crate::SolanaWireField<u64> {
|
|
return &self.fee;
|
|
}
|
|
|
|
/// Returns pre-simulation lamport balances when reported.
|
|
#[must_use]
|
|
pub const fn pre_balances(&self) -> &crate::SolanaWireField<std::vec::Vec<u64>> {
|
|
return &self.pre_balances;
|
|
}
|
|
|
|
/// Returns post-simulation lamport balances when reported.
|
|
#[must_use]
|
|
pub const fn post_balances(&self) -> &crate::SolanaWireField<std::vec::Vec<u64>> {
|
|
return &self.post_balances;
|
|
}
|
|
|
|
/// Returns pre-simulation token balances losslessly without SPL decoding.
|
|
#[must_use]
|
|
pub const fn pre_token_balances(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
|
return &self.pre_token_balances;
|
|
}
|
|
|
|
/// Returns post-simulation token balances losslessly without SPL decoding.
|
|
#[must_use]
|
|
pub const fn post_token_balances(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
|
return &self.post_token_balances;
|
|
}
|
|
|
|
/// Returns loaded-address metadata losslessly without transaction-message decoding.
|
|
#[must_use]
|
|
pub const fn loaded_addresses(&self) -> &crate::SolanaWireField<serde_json::Value> {
|
|
return &self.loaded_addresses;
|
|
}
|
|
|
|
/// Decodes a simulation result while preserving optional/nullable current Agave fields.
|
|
pub(crate) fn decode_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<Self> {
|
|
let decoded = crate::decode_wire_json::<WireSimulateTransactionResult>(method, value);
|
|
let wire = match decoded {
|
|
std::result::Result::Ok(wire) => wire,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let accounts = decode_simulation_accounts_field(method, wire.accounts);
|
|
let accounts = match accounts {
|
|
std::result::Result::Ok(accounts) => accounts,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let replacement_blockhash = decode_replacement_blockhash_field(method, wire.replacement_blockhash);
|
|
let replacement_blockhash = match replacement_blockhash {
|
|
std::result::Result::Ok(blockhash) => blockhash,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return std::result::Result::Ok(Self {
|
|
err: wire.err,
|
|
logs: wire.logs,
|
|
accounts,
|
|
units_consumed: wire.units_consumed,
|
|
loaded_accounts_data_size: wire.loaded_accounts_data_size,
|
|
return_data: wire.return_data,
|
|
inner_instructions: wire.inner_instructions,
|
|
replacement_blockhash,
|
|
fee: wire.fee,
|
|
pre_balances: wire.pre_balances,
|
|
post_balances: wire.post_balances,
|
|
pre_token_balances: wire.pre_token_balances,
|
|
post_token_balances: wire.post_token_balances,
|
|
loaded_addresses: wire.loaded_addresses,
|
|
});
|
|
}
|
|
}
|
|
|
|
const MAX_RECENT_PRIORITIZATION_FEE_ACCOUNTS: usize = 128;
|
|
const MAX_SIGNATURES_FOR_ADDRESS_LIMIT: usize = 1_000;
|
|
const MAX_SIGNATURE_STATUSES: usize = 256;
|
|
|
|
impl crate::HttpTransportPool {
|
|
/// Executes typed `getFeeForMessage` through the common KSP HTTP transport path.
|
|
pub async fn get_fee_for_message(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
message_base64: &str,
|
|
config: std::option::Option<&crate::SolanaContextConfig>,
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::option::Option<u64>>> {
|
|
let mut params = std::vec![serde_json::Value::String(message_base64.to_owned())];
|
|
push_transaction_context_config(&mut params, config);
|
|
let value = self.execute_transaction_rpc("getFeeForMessage", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let contextual = decode_transaction_contextual_wire("getFeeForMessage", value);
|
|
let (context, value) = match contextual {
|
|
std::result::Result::Ok(contextual) => contextual,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let fee = crate::decode_wire_json::<std::option::Option<u64>>("getFeeForMessage", value);
|
|
return match fee {
|
|
std::result::Result::Ok(fee) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, fee)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `getLatestBlockhash` through the common KSP HTTP transport path.
|
|
pub async fn get_latest_blockhash(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
config: std::option::Option<&crate::SolanaContextConfig>,
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaLatestBlockhash>> {
|
|
let mut params = std::vec::Vec::new();
|
|
push_transaction_context_config(&mut params, config);
|
|
let value = self.execute_transaction_rpc("getLatestBlockhash", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let contextual = decode_transaction_contextual_wire("getLatestBlockhash", value);
|
|
let (context, value) = match contextual {
|
|
std::result::Result::Ok(contextual) => contextual,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let blockhash = crate::SolanaLatestBlockhash::decode_wire("getLatestBlockhash", value);
|
|
return match blockhash {
|
|
std::result::Result::Ok(blockhash) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, blockhash)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `getRecentPrioritizationFees` through the common KSP HTTP transport path.
|
|
pub async fn get_recent_prioritization_fees(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
writable_accounts: std::option::Option<&[ksp_core_lib::Pubkey]>,
|
|
) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaPrioritizationFee>> {
|
|
if let std::option::Option::Some(writable_accounts) = writable_accounts
|
|
&& writable_accounts.len() > MAX_RECENT_PRIORITIZATION_FEE_ACCOUNTS
|
|
{
|
|
return invalid_transaction_parameters(
|
|
"getRecentPrioritizationFees",
|
|
"getRecentPrioritizationFees accepts at most 128 account addresses",
|
|
"account_count",
|
|
writable_accounts.len(),
|
|
);
|
|
}
|
|
let mut params = std::vec::Vec::new();
|
|
if let std::option::Option::Some(writable_accounts) = writable_accounts {
|
|
let mut addresses = std::vec::Vec::with_capacity(writable_accounts.len());
|
|
for account in writable_accounts {
|
|
addresses.push(serde_json::Value::String(account.to_string()));
|
|
}
|
|
params.push(serde_json::Value::Array(addresses));
|
|
}
|
|
let value = self.execute_transaction_rpc("getRecentPrioritizationFees", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return decode_prioritization_fees("getRecentPrioritizationFees", value);
|
|
}
|
|
|
|
/// Executes typed `getSignaturesForAddress` through the common KSP HTTP transport path.
|
|
pub async fn get_signatures_for_address(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
address: &ksp_core_lib::Pubkey,
|
|
config: std::option::Option<&crate::SolanaSignaturesForAddressConfig>,
|
|
) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaSignatureInfo>> {
|
|
if let std::option::Option::Some(limit) = config.and_then(crate::SolanaSignaturesForAddressConfig::limit)
|
|
&& (limit == 0 || limit > MAX_SIGNATURES_FOR_ADDRESS_LIMIT)
|
|
{
|
|
return invalid_transaction_parameters("getSignaturesForAddress", "getSignaturesForAddress limit must be between 1 and 1000", "limit", limit);
|
|
}
|
|
let mut params = std::vec![serde_json::Value::String(address.to_string())];
|
|
if let std::option::Option::Some(config) = config
|
|
&& !config.is_empty()
|
|
{
|
|
params.push(config.to_json_value());
|
|
}
|
|
let value = self.execute_transaction_rpc("getSignaturesForAddress", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return decode_signature_infos("getSignaturesForAddress", value);
|
|
}
|
|
|
|
/// Executes typed `getSignatureStatuses` through the common KSP HTTP transport path.
|
|
pub async fn get_signature_statuses(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
signatures: &[std::string::String],
|
|
config: std::option::Option<&crate::SolanaSignatureStatusesConfig>,
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<std::vec::Vec<std::option::Option<crate::SolanaSignatureStatus>>>> {
|
|
if signatures.len() > MAX_SIGNATURE_STATUSES {
|
|
return invalid_transaction_parameters(
|
|
"getSignatureStatuses",
|
|
"getSignatureStatuses accepts at most 256 signatures",
|
|
"signature_count",
|
|
signatures.len(),
|
|
);
|
|
}
|
|
let mut signature_values = std::vec::Vec::with_capacity(signatures.len());
|
|
for signature in signatures {
|
|
signature_values.push(serde_json::Value::String(signature.clone()));
|
|
}
|
|
let mut params = std::vec![serde_json::Value::Array(signature_values)];
|
|
if let std::option::Option::Some(config) = config
|
|
&& !config.is_empty()
|
|
{
|
|
params.push((*config).to_json_value());
|
|
}
|
|
let value = self.execute_transaction_rpc("getSignatureStatuses", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let contextual = decode_transaction_contextual_wire("getSignatureStatuses", value);
|
|
let (context, value) = match contextual {
|
|
std::result::Result::Ok(contextual) => contextual,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let statuses = decode_signature_statuses("getSignatureStatuses", value, signatures.len());
|
|
return match statuses {
|
|
std::result::Result::Ok(statuses) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, statuses)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes the current object-form `getTransaction` request through the common KSP HTTP transport path.
|
|
///
|
|
/// `None` omits the optional second parameter. `Some(config)` sends the modern object form exactly, including an empty `{}` object when the
|
|
/// caller explicitly supplies an empty modern configuration object.
|
|
pub async fn get_transaction(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
signature: &str,
|
|
config: std::option::Option<&crate::SolanaGetTransactionConfig>,
|
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedTransaction>> {
|
|
if let std::option::Option::Some(config) = config
|
|
&& config.commitment() == std::option::Option::Some(crate::SolanaCommitment::Processed)
|
|
{
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(
|
|
crate::ERROR_CODE_INVALID_RPC_PARAMETERS,
|
|
"getTransaction commitment must be confirmed or finalized when explicitly provided",
|
|
)
|
|
.with_context("rpc_method", "getTransaction")
|
|
.with_context("commitment", "processed"),
|
|
);
|
|
}
|
|
let mut params = std::vec![serde_json::Value::String(signature.to_owned())];
|
|
if let std::option::Option::Some(config) = config {
|
|
params.push((*config).to_json_value());
|
|
}
|
|
return self.execute_get_transaction(role, params).await;
|
|
}
|
|
|
|
/// Executes the deprecated bare-encoding `getTransaction` request form retained by Solana RPC for backwards compatibility.
|
|
#[deprecated(note = "use HttpTransportPool::get_transaction with SolanaGetTransactionConfig; the bare encoding request form is deprecated")]
|
|
pub async fn get_transaction_legacy(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
signature: &str,
|
|
encoding: crate::SolanaTransactionEncoding,
|
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedTransaction>> {
|
|
ksp_logging_lib::warn!(
|
|
target: crate::TRACING_TARGET,
|
|
rpc_method = "getTransaction",
|
|
request_form = "bare_encoding",
|
|
encoding = encoding.as_str(),
|
|
"deprecated Solana HTTP RPC request form used"
|
|
);
|
|
let params = std::vec![serde_json::Value::String(signature.to_owned()), serde_json::Value::String(encoding.as_str().to_owned()),];
|
|
return self.execute_get_transaction(role, params).await;
|
|
}
|
|
|
|
async fn execute_get_transaction(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
params: std::vec::Vec<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaConfirmedTransaction>> {
|
|
let value = self.execute_transaction_rpc("getTransaction", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if value.is_null() {
|
|
return std::result::Result::Ok(std::option::Option::None);
|
|
}
|
|
let transaction = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", value);
|
|
return match transaction {
|
|
std::result::Result::Ok(transaction) => std::result::Result::Ok(std::option::Option::Some(transaction)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `requestAirdrop` through the common KSP HTTP transport path.
|
|
///
|
|
/// The RPC creates and submits a faucet transaction, so its audited descriptor is `WriteSubmission / NeverAfterDispatch`. The optional
|
|
/// `recentBlockhash` field is retained from the targeted Agave runtime even though the public Solana page currently documents only `commitment`.
|
|
pub async fn request_airdrop(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
recipient: &ksp_core_lib::Pubkey,
|
|
lamports: u64,
|
|
config: std::option::Option<&crate::SolanaRequestAirdropConfig>,
|
|
) -> ksp_core_lib::Result<std::string::String> {
|
|
let mut params = std::vec![serde_json::Value::String(recipient.to_string()), serde_json::json!(lamports)];
|
|
if let std::option::Option::Some(config) = config
|
|
&& !config.is_empty()
|
|
{
|
|
params.push(config.to_json_value());
|
|
}
|
|
let value = self.execute_transaction_rpc("requestAirdrop", role, params).await;
|
|
return match value {
|
|
std::result::Result::Ok(value) => crate::decode_wire_json::<std::string::String>("requestAirdrop", value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `sendTransaction` through the common KSP HTTP transport path.
|
|
///
|
|
/// Transport forwards an already serialized and signed transaction without decoding or modifying it. `config.maxRetries` controls node-side
|
|
/// retransmission only; KSP's HTTP retry policy remains governed by the central `WriteSubmission / NeverAfterDispatch` descriptor.
|
|
pub async fn send_transaction(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
transaction: &str,
|
|
config: std::option::Option<&crate::SolanaSendTransactionConfig>,
|
|
) -> ksp_core_lib::Result<std::string::String> {
|
|
let mut params = std::vec![serde_json::Value::String(transaction.to_owned())];
|
|
if let std::option::Option::Some(config) = config
|
|
&& !config.is_empty()
|
|
{
|
|
params.push((*config).to_json_value());
|
|
}
|
|
let value = self.execute_transaction_rpc("sendTransaction", role, params).await;
|
|
return match value {
|
|
std::result::Result::Ok(value) => crate::decode_wire_json::<std::string::String>("sendTransaction", value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `simulateTransaction` through the common KSP HTTP transport path.
|
|
///
|
|
/// Transport forwards the already encoded transaction as opaque text. The wrapper enforces only deterministic RPC invariants that do not require
|
|
/// decoding transaction bytes: `sigVerify` cannot be combined with `replaceRecentBlockhash`, and simulation account-return encoding cannot use the
|
|
/// legacy `binary` / `base58` account encodings rejected by the targeted Agave runtime.
|
|
pub async fn simulate_transaction(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
transaction: &str,
|
|
config: std::option::Option<&crate::SolanaSimulateTransactionConfig>,
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<crate::SolanaSimulateTransactionResult>> {
|
|
if let std::option::Option::Some(config) = config {
|
|
if config.sig_verify() == std::option::Option::Some(true) && config.replace_recent_blockhash() == std::option::Option::Some(true) {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(
|
|
crate::ERROR_CODE_INVALID_RPC_PARAMETERS,
|
|
"simulateTransaction sigVerify may not be used with replaceRecentBlockhash",
|
|
)
|
|
.with_context("rpc_method", "simulateTransaction"),
|
|
);
|
|
}
|
|
if let std::option::Option::Some(accounts) = config.accounts()
|
|
&& let std::option::Option::Some(encoding) = accounts.encoding()
|
|
&& (encoding == crate::SolanaAccountEncoding::Binary || encoding == crate::SolanaAccountEncoding::Base58)
|
|
{
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(
|
|
crate::ERROR_CODE_INVALID_RPC_PARAMETERS,
|
|
"simulateTransaction account-return encoding must be base64, base64+zstd, or jsonParsed",
|
|
)
|
|
.with_context("rpc_method", "simulateTransaction")
|
|
.with_context("accounts_encoding", encoding.as_str()),
|
|
);
|
|
}
|
|
}
|
|
|
|
let mut params = std::vec![serde_json::Value::String(transaction.to_owned())];
|
|
if let std::option::Option::Some(config) = config
|
|
&& !config.is_empty()
|
|
{
|
|
params.push(config.to_json_value());
|
|
}
|
|
let value = self.execute_transaction_rpc("simulateTransaction", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let contextual = decode_transaction_contextual_wire("simulateTransaction", value);
|
|
let (context, value) = match contextual {
|
|
std::result::Result::Ok(contextual) => contextual,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let result = crate::SolanaSimulateTransactionResult::decode_wire("simulateTransaction", value);
|
|
return match result {
|
|
std::result::Result::Ok(result) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, result)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `getTransactionCount` through the common KSP HTTP transport path.
|
|
pub async fn get_transaction_count(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
config: std::option::Option<&crate::SolanaContextConfig>,
|
|
) -> ksp_core_lib::Result<u64> {
|
|
let mut params = std::vec::Vec::new();
|
|
push_transaction_context_config(&mut params, config);
|
|
let value = self.execute_transaction_rpc("getTransactionCount", role, params).await;
|
|
return match value {
|
|
std::result::Result::Ok(value) => crate::decode_wire_json::<u64>("getTransactionCount", value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
/// Executes typed `isBlockhashValid` through the common KSP HTTP transport path.
|
|
pub async fn is_blockhash_valid(
|
|
&self,
|
|
role: &crate::HttpRoleName,
|
|
blockhash: &str,
|
|
config: std::option::Option<&crate::SolanaContextConfig>,
|
|
) -> ksp_core_lib::Result<crate::SolanaRpcResponse<bool>> {
|
|
let mut params = std::vec![serde_json::Value::String(blockhash.to_owned())];
|
|
push_transaction_context_config(&mut params, config);
|
|
let value = self.execute_transaction_rpc("isBlockhashValid", role, params).await;
|
|
let value = match value {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let contextual = decode_transaction_contextual_wire("isBlockhashValid", value);
|
|
let (context, value) = match contextual {
|
|
std::result::Result::Ok(contextual) => contextual,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let valid = crate::decode_wire_json::<bool>("isBlockhashValid", value);
|
|
return match valid {
|
|
std::result::Result::Ok(valid) => std::result::Result::Ok(crate::SolanaRpcResponse::new(context, valid)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
async fn execute_transaction_rpc(
|
|
&self,
|
|
method_name: &'static str,
|
|
role: &crate::HttpRoleName,
|
|
params: std::vec::Vec<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<serde_json::Value> {
|
|
let method = transaction_descriptor(method_name);
|
|
let method = match method {
|
|
std::result::Result::Ok(method) => method,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
return self.execute_standard_rpc(role, method, params).await;
|
|
}
|
|
}
|
|
|
|
fn push_transaction_context_config(params: &mut std::vec::Vec<serde_json::Value>, config: std::option::Option<&crate::SolanaContextConfig>) {
|
|
if let std::option::Option::Some(config) = config
|
|
&& (config.commitment().is_some() || config.min_context_slot().is_some())
|
|
{
|
|
params.push((*config).to_json_value());
|
|
}
|
|
return;
|
|
}
|
|
|
|
fn decode_transaction_contextual_wire(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<(crate::SolanaRpcContext, serde_json::Value)> {
|
|
let decoded = crate::decode_wire_json::<WireTransactionRpcResponse>(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);
|
|
return match context {
|
|
std::result::Result::Ok(context) => std::result::Result::Ok((context, wire.value)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
fn decode_prioritization_fees(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaPrioritizationFee>> {
|
|
let decoded = crate::decode_wire_json::<std::vec::Vec<serde_json::Value>>(method, value);
|
|
let values = match decoded {
|
|
std::result::Result::Ok(values) => values,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let mut fees = std::vec::Vec::with_capacity(values.len());
|
|
for value in values {
|
|
let fee = crate::SolanaPrioritizationFee::decode_wire(method, value);
|
|
match fee {
|
|
std::result::Result::Ok(fee) => fees.push(fee),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
}
|
|
return std::result::Result::Ok(fees);
|
|
}
|
|
|
|
fn decode_signature_infos(method: &str, value: serde_json::Value) -> ksp_core_lib::Result<std::vec::Vec<crate::SolanaSignatureInfo>> {
|
|
let decoded = crate::decode_wire_json::<std::vec::Vec<serde_json::Value>>(method, value);
|
|
let values = match decoded {
|
|
std::result::Result::Ok(values) => values,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let mut infos = std::vec::Vec::with_capacity(values.len());
|
|
for value in values {
|
|
let info = crate::SolanaSignatureInfo::decode_wire(method, value);
|
|
match info {
|
|
std::result::Result::Ok(info) => infos.push(info),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
}
|
|
return std::result::Result::Ok(infos);
|
|
}
|
|
|
|
fn decode_signature_statuses(
|
|
method: &str,
|
|
value: serde_json::Value,
|
|
expected_count: usize,
|
|
) -> ksp_core_lib::Result<std::vec::Vec<std::option::Option<crate::SolanaSignatureStatus>>> {
|
|
let decoded = crate::decode_wire_json::<std::vec::Vec<std::option::Option<serde_json::Value>>>(method, value);
|
|
let values = match decoded {
|
|
std::result::Result::Ok(values) => values,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
if values.len() != expected_count {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "getSignatureStatuses result count does not match the requested signature count")
|
|
.with_context("rpc_method", method)
|
|
.with_context("expected_count", expected_count.to_string())
|
|
.with_context("actual_count", values.len().to_string()),
|
|
);
|
|
}
|
|
let mut statuses = std::vec::Vec::with_capacity(values.len());
|
|
for value in values {
|
|
let value = match value {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
statuses.push(std::option::Option::None);
|
|
continue;
|
|
},
|
|
};
|
|
let status = crate::SolanaSignatureStatus::decode_wire(method, value);
|
|
match status {
|
|
std::result::Result::Ok(status) => statuses.push(std::option::Option::Some(status)),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
}
|
|
return std::result::Result::Ok(statuses);
|
|
}
|
|
|
|
fn invalid_transaction_parameters<T>(method: &str, message: &str, field: &'static str, value: usize) -> ksp_core_lib::Result<T> {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RPC_PARAMETERS, message)
|
|
.with_context("rpc_method", method)
|
|
.with_context(field, value.to_string()),
|
|
);
|
|
}
|
|
|
|
fn transaction_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::Transactions && descriptor.coverage_release() == crate::HttpRpcCoverageRelease::V0_2_3 =>
|
|
{
|
|
std::result::Result::Ok(descriptor)
|
|
},
|
|
_ => std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, "typed Transaction descriptor is missing or misclassified in the audited registry")
|
|
.with_context("rpc_method", method),
|
|
),
|
|
};
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
struct WireTransactionRpcResponse {
|
|
context: serde_json::Value,
|
|
value: serde_json::Value,
|
|
}
|
|
|
|
fn invalid_transaction_wire(method: &str, field: &str, message: &'static str) -> ksp_core_lib::Error {
|
|
return ksp_core_lib::Error::new(crate::ERROR_CODE_INVALID_RESPONSE, message).with_context("rpc_method", method).with_context("field", field);
|
|
}
|
|
|
|
fn decode_confirmation_status(
|
|
method: &str,
|
|
field: &str,
|
|
value: std::option::Option<std::string::String>,
|
|
) -> ksp_core_lib::Result<std::option::Option<crate::SolanaTransactionConfirmationStatus>> {
|
|
let value = match value {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
|
};
|
|
let status = match value.as_str() {
|
|
"processed" => crate::SolanaTransactionConfirmationStatus::Processed,
|
|
"confirmed" => crate::SolanaTransactionConfirmationStatus::Confirmed,
|
|
"finalized" => crate::SolanaTransactionConfirmationStatus::Finalized,
|
|
_ => return std::result::Result::Err(invalid_transaction_wire(method, field, "transaction confirmation status is unknown")),
|
|
};
|
|
return std::result::Result::Ok(std::option::Option::Some(status));
|
|
}
|
|
|
|
fn decode_binary_transaction_tuple(method: &str, values: std::vec::Vec<serde_json::Value>) -> ksp_core_lib::Result<crate::SolanaEncodedTransaction> {
|
|
if values.len() != 2 {
|
|
return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple must contain data and encoding"));
|
|
}
|
|
let mut values = values.into_iter();
|
|
let data_value = match values.next() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple is missing data"));
|
|
},
|
|
};
|
|
let encoding_value = match values.next() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple is missing encoding"));
|
|
},
|
|
};
|
|
let data = match data_value.as_str() {
|
|
std::option::Option::Some(value) => value.to_owned(),
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple data must be a string"));
|
|
},
|
|
};
|
|
let encoding = match encoding_value.as_str() {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple encoding must be a string"));
|
|
},
|
|
};
|
|
let encoding = crate::SolanaTransactionBinaryEncoding::from_wire(encoding);
|
|
let encoding = match encoding {
|
|
std::option::Option::Some(encoding) => encoding,
|
|
std::option::Option::None => {
|
|
return std::result::Result::Err(invalid_transaction_wire(method, "transaction", "encoded transaction tuple uses an unsupported binary encoding"));
|
|
},
|
|
};
|
|
return std::result::Result::Ok(crate::SolanaEncodedTransaction::Binary { data, encoding });
|
|
}
|
|
|
|
fn decode_transaction_version_field(
|
|
method: &str,
|
|
field: crate::SolanaWireField<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<crate::SolanaWireField<crate::SolanaTransactionVersion>> {
|
|
return match field {
|
|
crate::SolanaWireField::Omitted => std::result::Result::Ok(crate::SolanaWireField::Omitted),
|
|
crate::SolanaWireField::Null => std::result::Result::Ok(crate::SolanaWireField::Null),
|
|
crate::SolanaWireField::Value(value) => {
|
|
let version = crate::SolanaTransactionVersion::decode_wire(method, value);
|
|
match version {
|
|
std::result::Result::Ok(version) => std::result::Result::Ok(crate::SolanaWireField::Value(version)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
fn decode_simulation_accounts_field(
|
|
method: &str,
|
|
field: crate::SolanaWireField<std::vec::Vec<std::option::Option<serde_json::Value>>>,
|
|
) -> ksp_core_lib::Result<crate::SolanaWireField<std::vec::Vec<std::option::Option<crate::SolanaAccount>>>> {
|
|
let values = match field {
|
|
crate::SolanaWireField::Omitted => return std::result::Result::Ok(crate::SolanaWireField::Omitted),
|
|
crate::SolanaWireField::Null => return std::result::Result::Ok(crate::SolanaWireField::Null),
|
|
crate::SolanaWireField::Value(values) => values,
|
|
};
|
|
let mut accounts = std::vec::Vec::with_capacity(values.len());
|
|
for value in values {
|
|
let value = match value {
|
|
std::option::Option::Some(value) => value,
|
|
std::option::Option::None => {
|
|
accounts.push(std::option::Option::None);
|
|
continue;
|
|
},
|
|
};
|
|
let account = crate::SolanaAccount::decode_wire(method, value);
|
|
match account {
|
|
std::result::Result::Ok(account) => accounts.push(std::option::Option::Some(account)),
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
}
|
|
}
|
|
return std::result::Result::Ok(crate::SolanaWireField::Value(accounts));
|
|
}
|
|
|
|
fn decode_replacement_blockhash_field(
|
|
method: &str,
|
|
field: crate::SolanaWireField<serde_json::Value>,
|
|
) -> ksp_core_lib::Result<crate::SolanaWireField<crate::SolanaLatestBlockhash>> {
|
|
let value = match field {
|
|
crate::SolanaWireField::Omitted => return std::result::Result::Ok(crate::SolanaWireField::Omitted),
|
|
crate::SolanaWireField::Null => return std::result::Result::Ok(crate::SolanaWireField::Null),
|
|
crate::SolanaWireField::Value(value) => value,
|
|
};
|
|
let blockhash = crate::SolanaLatestBlockhash::decode_wire(method, value);
|
|
return match blockhash {
|
|
std::result::Result::Ok(blockhash) => std::result::Result::Ok(crate::SolanaWireField::Value(blockhash)),
|
|
std::result::Result::Err(error) => std::result::Result::Err(error),
|
|
};
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireLatestBlockhash {
|
|
blockhash: std::string::String,
|
|
last_valid_block_height: u64,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WirePrioritizationFee {
|
|
slot: u64,
|
|
prioritization_fee: u64,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireSignatureInfo {
|
|
signature: std::string::String,
|
|
slot: u64,
|
|
#[serde(default)]
|
|
err: std::option::Option<serde_json::Value>,
|
|
#[serde(default)]
|
|
memo: std::option::Option<std::string::String>,
|
|
#[serde(default)]
|
|
block_time: std::option::Option<i64>,
|
|
#[serde(default)]
|
|
confirmation_status: std::option::Option<std::string::String>,
|
|
#[serde(default)]
|
|
transaction_index: std::option::Option<u32>,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireSignatureStatus {
|
|
slot: u64,
|
|
#[serde(default)]
|
|
confirmations: std::option::Option<u64>,
|
|
status: serde_json::Value,
|
|
#[serde(default)]
|
|
err: std::option::Option<serde_json::Value>,
|
|
#[serde(default)]
|
|
confirmation_status: std::option::Option<std::string::String>,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireConfirmedTransaction {
|
|
slot: u64,
|
|
transaction: serde_json::Value,
|
|
#[serde(default)]
|
|
meta: crate::SolanaWireField<serde_json::Value>,
|
|
#[serde(default)]
|
|
block_time: std::option::Option<i64>,
|
|
#[serde(default)]
|
|
version: crate::SolanaWireField<serde_json::Value>,
|
|
#[serde(default)]
|
|
transaction_index: crate::SolanaWireField<u32>,
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct WireSimulateTransactionResult {
|
|
#[serde(default)]
|
|
err: crate::SolanaWireField<serde_json::Value>,
|
|
#[serde(default)]
|
|
logs: crate::SolanaWireField<std::vec::Vec<std::string::String>>,
|
|
#[serde(default)]
|
|
accounts: crate::SolanaWireField<std::vec::Vec<std::option::Option<serde_json::Value>>>,
|
|
#[serde(default)]
|
|
units_consumed: crate::SolanaWireField<u64>,
|
|
#[serde(default)]
|
|
loaded_accounts_data_size: crate::SolanaWireField<u32>,
|
|
#[serde(default)]
|
|
return_data: crate::SolanaWireField<serde_json::Value>,
|
|
#[serde(default)]
|
|
inner_instructions: crate::SolanaWireField<serde_json::Value>,
|
|
#[serde(default)]
|
|
replacement_blockhash: crate::SolanaWireField<serde_json::Value>,
|
|
#[serde(default)]
|
|
fee: crate::SolanaWireField<u64>,
|
|
#[serde(default)]
|
|
pre_balances: crate::SolanaWireField<std::vec::Vec<u64>>,
|
|
#[serde(default)]
|
|
post_balances: crate::SolanaWireField<std::vec::Vec<u64>>,
|
|
#[serde(default)]
|
|
pre_token_balances: crate::SolanaWireField<serde_json::Value>,
|
|
#[serde(default)]
|
|
post_token_balances: crate::SolanaWireField<serde_json::Value>,
|
|
#[serde(default)]
|
|
loaded_addresses: crate::SolanaWireField<serde_json::Value>,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "../unit_tests/rpc_transactions.rs"]
|
|
mod tests;
|