Files
khadhroony-bot3/kb-onchain-transport/src/get_transaction.rs
2026-07-28 18:41:30 +02:00

731 lines
28 KiB
Rust

// file: kb-onchain-transport/src/get_transaction.rs
// version: 10
//! Standard Solana `getTransaction` adapter for the canonical transaction contract.
use base64::Engine; // rust-rules: trait-import
/// Common adapter contract for transport-specific transaction payloads.
pub trait CanonicalTransactionAdapter {
/// Source payload type accepted by the adapter.
type Source;
/// Adapts one source payload into an optional canonical transaction.
fn adapt(
&self,
source: &Self::Source,
) -> kb_core::Result<std::option::Option<kb_lib::MdCanonicalTransaction>>;
}
/// Configuration for a standard Solana `getTransaction` request.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GetTransactionConfig {
/// Requested commitment, restricted to `confirmed` or `finalized`.
pub commitment: std::string::String,
/// Maximum supported numbered transaction version.
pub max_supported_transaction_version: u8,
}
impl GetTransactionConfig {
/// Creates and validates a `getTransaction` request configuration.
pub fn new(
commitment: impl std::convert::Into<std::string::String>,
max_supported_transaction_version: u8,
) -> kb_core::Result<Self> {
let commitment_value = commitment.into();
if commitment_value != "confirmed" && commitment_value != "finalized" {
return std::result::Result::Err(kb_core::Error::config(
"getTransaction commitment must be 'confirmed' or 'finalized'",
));
}
return std::result::Result::Ok(Self {
commitment: commitment_value,
max_supported_transaction_version,
});
}
/// Creates the default confirmed configuration supporting transaction version zero.
pub fn confirmed_v0() -> Self {
return Self {
commitment: "confirmed".to_string(),
max_supported_transaction_version: 0,
};
}
fn request_params(&self, signature: &str) -> kb_core::Result<std::vec::Vec<serde_json::Value>> {
let signature_result =
crate::validate_transaction_signature_text(signature, "getTransaction signature");
if let std::result::Result::Err(error) = signature_result {
return std::result::Result::Err(error);
}
return std::result::Result::Ok(std::vec![
serde_json::Value::String(signature.to_string()),
serde_json::json!({
"commitment": self.commitment.clone(),
"encoding": "json",
"maxSupportedTransactionVersion": self.max_supported_transaction_version
}),
]);
}
}
/// Source payload and canonical result returned by one `getTransaction` request.
#[derive(Clone, Debug, PartialEq)]
pub struct GetTransactionAcquisition {
/// Source-specific JSON result retained only for immediate metrics and hashing.
pub source_json: serde_json::Value,
/// Canonical source-independent transaction when the RPC result is not null.
pub canonical_transaction: std::option::Option<kb_lib::MdCanonicalTransaction>,
}
/// Adapter from the standard Solana `getTransaction` JSON result to the canonical model.
#[derive(Clone, Copy, Debug, Default)]
pub struct GetTransactionAdapter;
impl crate::CanonicalTransactionAdapter for crate::GetTransactionAdapter {
type Source = serde_json::Value;
fn adapt(
&self,
source: &Self::Source,
) -> kb_core::Result<std::option::Option<kb_lib::MdCanonicalTransaction>> {
return adapt_get_transaction_result(source);
}
}
impl crate::HttpClient {
/// Fetches one source payload and its canonical transaction adaptation.
pub async fn get_transaction_acquisition(
&self,
signature: &str,
config: &crate::GetTransactionConfig,
) -> kb_core::Result<crate::GetTransactionAcquisition> {
let params_result = config.request_params(signature);
let params = match params_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let result = self.execute_json_rpc_result_raw("getTransaction".to_string(), params).await;
let value = match result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let adapter = crate::GetTransactionAdapter;
let canonical_result = crate::CanonicalTransactionAdapter::adapt(&adapter, &value);
let canonical_transaction = match canonical_result {
std::result::Result::Ok(canonical) => canonical,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::GetTransactionAcquisition {
source_json: value,
canonical_transaction,
});
}
/// Fetches and adapts one standard Solana transaction into the canonical model.
pub async fn get_transaction_canonical(
&self,
signature: &str,
config: &crate::GetTransactionConfig,
) -> kb_core::Result<std::option::Option<kb_lib::MdCanonicalTransaction>> {
let acquisition_result = self.get_transaction_acquisition(signature, config).await;
let acquisition = match acquisition_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(acquisition.canonical_transaction);
}
}
/// Adapts a raw standard Solana `getTransaction` result value.
pub fn adapt_get_transaction_result(
source: &serde_json::Value,
) -> kb_core::Result<std::option::Option<kb_lib::MdCanonicalTransaction>> {
if source.is_null() {
return std::result::Result::Ok(std::option::Option::None);
}
let parse_result = serde_json::from_value::<RpcGetTransactionResult>(source.clone());
let parsed = match parse_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::json(format!(
"cannot parse getTransaction result: {error}"
)));
},
};
let canonical_result = adapt_parsed_transaction(parsed);
return match canonical_result {
std::result::Result::Ok(transaction) => {
std::result::Result::Ok(std::option::Option::Some(transaction))
},
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcGetTransactionResult {
slot: u64,
#[serde(default)]
block_time: std::option::Option<i64>,
#[serde(default)]
meta: std::option::Option<RpcTransactionMeta>,
transaction: RpcJsonTransaction,
#[serde(default)]
version: std::option::Option<RpcTransactionVersion>,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(untagged)]
enum RpcTransactionVersion {
Legacy(std::string::String),
Number(u8),
}
#[derive(Clone, Debug, serde::Deserialize)]
struct RpcJsonTransaction {
signatures: std::vec::Vec<std::string::String>,
message: RpcRawMessage,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcRawMessage {
header: RpcMessageHeader,
account_keys: std::vec::Vec<std::string::String>,
recent_blockhash: std::string::String,
instructions: std::vec::Vec<RpcCompiledInstruction>,
#[serde(default)]
address_table_lookups: std::vec::Vec<RpcAddressTableLookup>,
}
#[derive(Clone, Copy, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcMessageHeader {
num_required_signatures: u8,
num_readonly_signed_accounts: u8,
num_readonly_unsigned_accounts: u8,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcCompiledInstruction {
program_id_index: u16,
accounts: std::vec::Vec<u16>,
data: std::string::String,
#[serde(default)]
stack_height: std::option::Option<u32>,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcAddressTableLookup {
account_key: std::string::String,
writable_indexes: std::vec::Vec<u8>,
readonly_indexes: std::vec::Vec<u8>,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcTransactionMeta {
#[serde(default)]
err: std::option::Option<serde_json::Value>,
fee: u64,
pre_balances: std::vec::Vec<u64>,
post_balances: std::vec::Vec<u64>,
#[serde(default)]
inner_instructions: std::option::Option<std::vec::Vec<RpcInnerInstructionGroup>>,
#[serde(default)]
log_messages: std::option::Option<std::vec::Vec<std::string::String>>,
#[serde(default)]
pre_token_balances: std::option::Option<std::vec::Vec<RpcTokenBalance>>,
#[serde(default)]
post_token_balances: std::option::Option<std::vec::Vec<RpcTokenBalance>>,
#[serde(default)]
rewards: std::option::Option<std::vec::Vec<RpcReward>>,
#[serde(default)]
loaded_addresses: std::option::Option<RpcLoadedAddresses>,
#[serde(default)]
return_data: std::option::Option<RpcReturnData>,
#[serde(default)]
compute_units_consumed: std::option::Option<u64>,
#[serde(default)]
cost_units: std::option::Option<u64>,
}
#[derive(Clone, Debug, serde::Deserialize)]
struct RpcInnerInstructionGroup {
index: u16,
instructions: std::vec::Vec<RpcCompiledInstruction>,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcTokenBalance {
account_index: u16,
mint: std::string::String,
#[serde(default)]
owner: std::option::Option<std::string::String>,
#[serde(default)]
program_id: std::option::Option<std::string::String>,
ui_token_amount: RpcUiTokenAmount,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcUiTokenAmount {
amount: std::string::String,
decimals: u8,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcReward {
pubkey: std::string::String,
lamports: i64,
post_balance: u64,
#[serde(default)]
reward_type: std::option::Option<std::string::String>,
#[serde(default)]
commission: std::option::Option<u8>,
}
#[derive(Clone, Debug, Default, serde::Deserialize)]
struct RpcLoadedAddresses {
#[serde(default)]
writable: std::vec::Vec<std::string::String>,
#[serde(default)]
readonly: std::vec::Vec<std::string::String>,
}
#[derive(Clone, Debug, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
struct RpcReturnData {
program_id: std::string::String,
data: std::vec::Vec<std::string::String>,
}
fn adapt_parsed_transaction(
parsed: RpcGetTransactionResult,
) -> kb_core::Result<kb_lib::MdCanonicalTransaction> {
let primary_signature = match parsed.transaction.signatures.first() {
std::option::Option::Some(value) => value.clone(),
std::option::Option::None => {
return std::result::Result::Err(kb_core::Error::json(
"getTransaction result contains no signatures",
));
},
};
let version_result = adapt_version(parsed.version);
let version = match version_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let loaded_addresses = match &parsed.meta {
std::option::Option::Some(metadata) => {
adapt_loaded_addresses(metadata.loaded_addresses.clone())
},
std::option::Option::None => kb_lib::MdCanonicalLoadedAddresses::default(),
};
let instructions_result = adapt_instructions(parsed.transaction.message.instructions);
let instructions = match instructions_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let metadata_result = adapt_metadata(parsed.meta);
let metadata = match metadata_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let transaction = kb_lib::MdCanonicalTransaction {
format_version: kb_lib::MD_CANONICAL_TRANSACTION_FORMAT_VERSION,
primary_signature,
slot: parsed.slot,
block_time: parsed.block_time,
version,
signatures: parsed.transaction.signatures,
message: kb_lib::MdCanonicalTransactionMessage {
header: kb_lib::MdCanonicalMessageHeader {
num_required_signatures: parsed.transaction.message.header.num_required_signatures,
num_readonly_signed_accounts: parsed
.transaction
.message
.header
.num_readonly_signed_accounts,
num_readonly_unsigned_accounts: parsed
.transaction
.message
.header
.num_readonly_unsigned_accounts,
},
static_account_keys: parsed.transaction.message.account_keys,
recent_blockhash: parsed.transaction.message.recent_blockhash,
instructions,
address_table_lookups: parsed
.transaction
.message
.address_table_lookups
.into_iter()
.map(|lookup| {
return kb_lib::MdCanonicalAddressTableLookup {
account_key: lookup.account_key,
writable_indexes: lookup.writable_indexes,
readonly_indexes: lookup.readonly_indexes,
};
})
.collect(),
loaded_addresses,
},
metadata,
};
let validation_result = transaction.validate();
if let std::result::Result::Err(error) = validation_result {
return std::result::Result::Err(error);
}
return std::result::Result::Ok(transaction);
}
fn adapt_version(
version: std::option::Option<RpcTransactionVersion>,
) -> kb_core::Result<kb_lib::MdCanonicalTransactionVersion> {
return match version {
std::option::Option::Some(RpcTransactionVersion::Legacy(value)) => {
if value != "legacy" {
return std::result::Result::Err(kb_core::Error::json(format!(
"unsupported textual transaction version: {value}"
)));
}
std::result::Result::Ok(kb_lib::MdCanonicalTransactionVersion::Legacy)
},
std::option::Option::Some(RpcTransactionVersion::Number(value)) => {
std::result::Result::Ok(kb_lib::MdCanonicalTransactionVersion::Number(value))
},
std::option::Option::None => {
std::result::Result::Ok(kb_lib::MdCanonicalTransactionVersion::Legacy)
},
};
}
fn adapt_instructions(
instructions: std::vec::Vec<RpcCompiledInstruction>,
) -> kb_core::Result<std::vec::Vec<kb_lib::MdCanonicalCompiledInstruction>> {
let mut adapted = std::vec::Vec::with_capacity(instructions.len());
for instruction in instructions {
let instruction_result = adapt_instruction(instruction);
let canonical = match instruction_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
adapted.push(canonical);
}
return std::result::Result::Ok(adapted);
}
fn adapt_instruction(
instruction: RpcCompiledInstruction,
) -> kb_core::Result<kb_lib::MdCanonicalCompiledInstruction> {
let decode_result = bs58::decode(instruction.data.as_str()).into_vec();
let bytes = match decode_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::json(format!(
"getTransaction instruction data is not valid base58: {error}"
)));
},
};
let data_base64 = base64::engine::general_purpose::STANDARD.encode(bytes);
return std::result::Result::Ok(kb_lib::MdCanonicalCompiledInstruction {
program_id_index: instruction.program_id_index,
account_indexes: instruction.accounts,
data_base64,
stack_height: instruction.stack_height,
});
}
fn adapt_loaded_addresses(
loaded_addresses: std::option::Option<RpcLoadedAddresses>,
) -> kb_lib::MdCanonicalLoadedAddresses {
return match loaded_addresses {
std::option::Option::Some(value) => kb_lib::MdCanonicalLoadedAddresses {
writable: value.writable,
readonly: value.readonly,
},
std::option::Option::None => kb_lib::MdCanonicalLoadedAddresses::default(),
};
}
fn adapt_metadata(
metadata: std::option::Option<RpcTransactionMeta>,
) -> kb_core::Result<std::option::Option<kb_lib::MdCanonicalTransactionMetadata>> {
let source = match metadata {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
};
let inner_result = adapt_inner_instructions(source.inner_instructions.unwrap_or_default());
let inner_instructions = match inner_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let return_result = adapt_return_data(source.return_data);
let return_data = match return_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let pre_token_result = adapt_token_balances(source.pre_token_balances.unwrap_or_default());
let pre_token_balances = match pre_token_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let post_token_result = adapt_token_balances(source.post_token_balances.unwrap_or_default());
let post_token_balances = match post_token_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let status = if source.err.is_some() {
kb_lib::MdCanonicalTransactionStatus::Failed
} else {
kb_lib::MdCanonicalTransactionStatus::Success
};
return std::result::Result::Ok(std::option::Option::Some(
kb_lib::MdCanonicalTransactionMetadata {
status,
error: source.err,
fee: source.fee,
pre_balances: source.pre_balances,
post_balances: source.post_balances,
inner_instructions,
log_messages: source.log_messages.unwrap_or_default(),
pre_token_balances,
post_token_balances,
rewards: source
.rewards
.unwrap_or_default()
.into_iter()
.map(|reward| {
return kb_lib::MdCanonicalReward {
pubkey: reward.pubkey,
lamports: reward.lamports,
post_balance: reward.post_balance,
reward_type: reward.reward_type,
commission: reward.commission,
};
})
.collect(),
return_data,
compute_units_consumed: source.compute_units_consumed,
cost_units: source.cost_units,
},
));
}
fn adapt_inner_instructions(
groups: std::vec::Vec<RpcInnerInstructionGroup>,
) -> kb_core::Result<std::vec::Vec<kb_lib::MdCanonicalInnerInstructionGroup>> {
let mut adapted = std::vec::Vec::with_capacity(groups.len());
for group in groups {
let instructions_result = adapt_instructions(group.instructions);
let instructions = match instructions_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
adapted.push(kb_lib::MdCanonicalInnerInstructionGroup {
parent_instruction_index: group.index,
instructions,
});
}
return std::result::Result::Ok(adapted);
}
fn adapt_token_balances(
balances: std::vec::Vec<RpcTokenBalance>,
) -> kb_core::Result<std::vec::Vec<kb_lib::MdCanonicalTokenBalance>> {
let mut adapted = std::vec::Vec::with_capacity(balances.len());
for balance in balances {
let balance_result = kb_lib::MdCanonicalTokenBalance::new(
balance.account_index,
balance.mint,
balance.owner,
balance.program_id,
balance.ui_token_amount.amount,
balance.ui_token_amount.decimals,
);
let canonical = match balance_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
adapted.push(canonical);
}
return std::result::Result::Ok(adapted);
}
fn adapt_return_data(
return_data: std::option::Option<RpcReturnData>,
) -> kb_core::Result<std::option::Option<kb_lib::MdCanonicalReturnData>> {
let source = match return_data {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
};
if source.data.len() != 2 {
return std::result::Result::Err(kb_core::Error::json(
"getTransaction return data must contain payload and encoding",
));
}
if source.data[1] != "base64" {
return std::result::Result::Err(kb_core::Error::json(format!(
"unsupported getTransaction return data encoding: {}",
source.data[1]
)));
}
return std::result::Result::Ok(std::option::Option::Some(kb_lib::MdCanonicalReturnData {
program_id: source.program_id,
data_base64: source.data[0].clone(),
}));
}
#[cfg(test)]
mod tests {
fn fixture(name: &str) -> serde_json::Value {
let text = match name {
"legacy_success" => {
include_str!("../tests/fixtures/get_transaction_legacy_success.json")
},
"legacy_failure" => {
include_str!("../tests/fixtures/get_transaction_legacy_failure.json")
},
"v0_alt" => {
include_str!("../tests/fixtures/get_transaction_v0_alt_cpi_token_2022.json")
},
"optional_absent" => {
include_str!("../tests/fixtures/get_transaction_optional_absent.json")
},
"equivalent_a" => include_str!("../tests/fixtures/get_transaction_equivalent_a.json"),
"equivalent_b" => include_str!("../tests/fixtures/get_transaction_equivalent_b.json"),
_ => panic!("unknown fixture: {name}"),
};
let parse_result = serde_json::from_str(text);
match parse_result {
std::result::Result::Ok(value) => return value,
std::result::Result::Err(error) => panic!("fixture parse failed: {error}"),
}
}
fn adapt_fixture(name: &str) -> kb_lib::MdCanonicalTransaction {
let value = fixture(name);
let result = crate::adapt_get_transaction_result(&value);
match result {
std::result::Result::Ok(std::option::Option::Some(transaction)) => return transaction,
std::result::Result::Ok(std::option::Option::None) => {
panic!("fixture unexpectedly adapted to none");
},
std::result::Result::Err(error) => panic!("fixture adaptation failed: {error}"),
}
}
#[test]
fn null_result_adapts_to_none() {
let result = crate::adapt_get_transaction_result(&serde_json::Value::Null);
assert_eq!(result, std::result::Result::Ok(std::option::Option::None));
}
#[test]
fn legacy_success_preserves_token_balances_and_return_data() {
let transaction = adapt_fixture("legacy_success");
assert_eq!(transaction.version, kb_lib::MdCanonicalTransactionVersion::Legacy);
assert_eq!(transaction.block_time, std::option::Option::Some(1_700_000_001));
let metadata = match transaction.metadata {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("metadata missing"),
};
assert_eq!(metadata.status, kb_lib::MdCanonicalTransactionStatus::Success);
assert_eq!(transaction.message.instructions[0].data_base64, "AQ==");
assert_eq!(metadata.post_token_balances.len(), 1);
assert_eq!(metadata.post_token_balances[0].amount, "12345678901234567890");
assert_eq!(metadata.post_token_balances[0].decimal_amount, "12345678901.234567890");
assert!(metadata.return_data.is_some());
assert_eq!(metadata.inner_instructions.len(), 1);
}
#[test]
fn legacy_failure_preserves_error() {
let transaction = adapt_fixture("legacy_failure");
let metadata = match transaction.metadata {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("metadata missing"),
};
assert_eq!(metadata.status, kb_lib::MdCanonicalTransactionStatus::Failed);
assert!(metadata.error.is_some());
}
#[test]
fn version_zero_preserves_alt_cpi_and_token_2022() {
let transaction = adapt_fixture("v0_alt");
assert_eq!(transaction.version, kb_lib::MdCanonicalTransactionVersion::Number(0));
assert_eq!(transaction.message.address_table_lookups.len(), 1);
assert_eq!(transaction.message.loaded_addresses.writable.len(), 1);
let metadata = match transaction.metadata {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("metadata missing"),
};
assert_eq!(metadata.inner_instructions.len(), 1);
assert_eq!(
metadata.post_token_balances[0].program_id.as_deref(),
std::option::Option::Some("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb")
);
}
#[test]
fn optional_fields_and_block_time_can_be_absent() {
let transaction = adapt_fixture("optional_absent");
assert_eq!(transaction.block_time, std::option::Option::None);
let metadata = match transaction.metadata {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("metadata missing"),
};
assert!(metadata.log_messages.is_empty());
assert!(metadata.rewards.is_empty());
assert!(metadata.return_data.is_none());
}
#[test]
fn equivalent_provider_shapes_produce_identical_hashes() {
let first = adapt_fixture("equivalent_a");
let second = adapt_fixture("equivalent_b");
let first_hash = match first.canonical_json_hash() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("first hash failed: {error}"),
};
let second_hash = match second.canonical_json_hash() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("second hash failed: {error}"),
};
assert_eq!(first_hash, second_hash);
}
#[test]
fn request_config_rejects_public_key_length_signature() {
let config = crate::GetTransactionConfig::confirmed_v0();
let result = config.request_params("3Bxs4NN8M2Yn4TLb7gR6Xy7n2D1Q8VjQqWcnpX8C6pQw");
assert!(result.is_err());
}
#[test]
fn request_config_rejects_processed_commitment() {
let result = crate::GetTransactionConfig::new("processed", 0);
assert!(result.is_err());
}
#[test]
fn request_config_uses_json_encoding_and_version_zero() {
let config = crate::GetTransactionConfig::confirmed_v0();
let params_result = config.request_params(
"2Ana1pUpv2ZbMVkwF5FXapYeBEjdxDatLn7nvJkhgTSXbs59SyZSx866bXirPgj8QQVB57uxHJBG1YFvkRbFj4T",
);
let params = match params_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("request params failed: {error}"),
};
assert_eq!(params[1]["encoding"], serde_json::Value::String("json".to_string()));
assert_eq!(params[1]["maxSupportedTransactionVersion"], serde_json::Value::from(0));
}
}