v0.2.3-pre.002

This commit is contained in:
2026-08-18 11:13:04 +02:00
parent d06284fca2
commit 1ccf11bcc1
15 changed files with 2130 additions and 12 deletions

View File

@@ -0,0 +1,36 @@
[
{
"slot": 430000123,
"blockTime": 1787000000,
"transaction": ["AQIDBAUGBwg=", "base64"],
"meta": {
"err": null,
"status": {"Ok": null},
"fee": 5000,
"preBalances": [10000],
"postBalances": [5000]
},
"version": 0,
"transactionIndex": 7
},
{
"slot": 430000100,
"blockTime": null,
"transaction": "3Bxs4NN8M2Yn4TLb",
"meta": null,
"version": "legacy",
"transactionIndex": null
},
{
"slot": 430000050,
"blockTime": null,
"transaction": {
"signatures": [],
"message": {
"accountKeys": [],
"recentBlockhash": "11111111111111111111111111111111",
"instructions": []
}
}
}
]

View File

@@ -0,0 +1,12 @@
[
"3Bxs4NN8M2Yn4TLb",
["AQIDBAUGBwg=", "base64"],
{
"signatures": ["5h6xBEauJ3PK6SWQkgLxHcQpWyRZyG7c4pUpK9fd2B2B7hRQ1S6F9J7BqvQPEmxQxK4DThfYF5f5f5f5f5f5f5f5"],
"message": {
"accountKeys": [],
"recentBlockhash": "11111111111111111111111111111111",
"instructions": []
}
}
]

View File

@@ -0,0 +1,4 @@
{
"blockhash": "11111111111111111111111111111111",
"lastValidBlockHeight": 430123456
}

View File

@@ -0,0 +1,4 @@
[
{"slot": 430000123, "prioritizationFee": 1000},
{"slot": 430000122, "prioritizationFee": 0}
]

View File

@@ -0,0 +1,19 @@
[
{
"signature": "5h6xBEauJ3PK6SWQkgLxHcQpWyRZyG7c4pUpK9fd2B2B7hRQ1S6F9J7BqvQPEmxQxK4DThfYF5f5f5f5f5f5f5f5",
"slot": 430000123,
"err": null,
"memo": "fixture memo",
"blockTime": 1787000000,
"confirmationStatus": "finalized",
"transactionIndex": 7
},
{
"signature": "4Nd1mC5QfVhNq8xqP7z8d7K6a5m4n3b2v1c9x8z7y6w5u4t3s2r1q9p8o7n6m5L4K3J2H1G9F8E7D6C5B4A3",
"slot": 430000100,
"err": {"InstructionError": [0, "Custom"]},
"memo": null,
"blockTime": null,
"confirmationStatus": "confirmed"
}
]

View File

@@ -0,0 +1,17 @@
[
null,
{
"slot": 430000123,
"confirmations": null,
"status": {"Ok": null},
"err": null,
"confirmationStatus": "finalized"
},
{
"slot": 430000120,
"confirmations": 3,
"status": {"Err": {"InstructionError": [0, "Custom"]}},
"err": {"InstructionError": [0, "Custom"]},
"confirmationStatus": "confirmed"
}
]

View File

@@ -0,0 +1,35 @@
{
"err": null,
"logs": ["Program 11111111111111111111111111111111 invoke [1]", "Program 11111111111111111111111111111111 success"],
"accounts": [
{
"lamports": 1000,
"data": ["", "base64"],
"owner": "11111111111111111111111111111111",
"executable": false,
"rentEpoch": 0,
"space": 0
},
null
],
"unitsConsumed": 150,
"loadedAccountsDataSize": 64,
"returnData": {
"programId": "11111111111111111111111111111111",
"data": ["AQI=", "base64"]
},
"innerInstructions": [],
"replacementBlockhash": {
"blockhash": "ComputeBudget111111111111111111111111111111",
"lastValidBlockHeight": 430123999
},
"fee": 5000,
"preBalances": [10000, 0],
"postBalances": [5000, 5000],
"preTokenBalances": [],
"postTokenBalances": [],
"loadedAddresses": {
"writable": [],
"readonly": []
}
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 11
// version: 12
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
@@ -10,7 +10,8 @@
//! independent from `ksp-config-lib`, Store and Program layers. `ksp-config-lib` now constructs these public settings through its one-way Config ->
//! Transport adapter without creating a reverse dependency. Logical endpoint clients, priority-aware pools, bounded admission limits and retry/no-resend policy
//! are available. The four typed Solana HTTP foundation canaries plus all 22 typed `0.2.2` Accounts, Tokens and Cluster wrappers execute real JSON-RPC
//! requests through the shared transport path while the `0.2.3` and `0.2.4` audited families remain staged.
//! requests through the shared transport path. `0.2.3` now exposes its shared Transaction wire/config primitives while its 11 typed wrappers remain staged;
//! the `0.2.4` family remains fully staged.
mod client;
mod constants;
@@ -25,6 +26,7 @@ mod rpc_cluster;
mod rpc_common;
mod rpc_method;
mod rpc_tokens;
mod rpc_transactions;
mod settings;
pub(crate) use self::constants::TRACING_TARGET;
@@ -197,6 +199,44 @@ pub use self::rpc_tokens::SolanaTokenAccountBalance;
pub use self::rpc_tokens::SolanaTokenAccountSelector;
/// Token amount returned by Solana HTTP token RPC methods.
pub use self::rpc_tokens::SolanaTokenAmount;
/// Confirmed transaction result returned by `getTransaction` when the RPC result is non-null.
pub use self::rpc_transactions::SolanaConfirmedTransaction;
/// Wire-preserving transaction payload returned by `getTransaction`.
pub use self::rpc_transactions::SolanaEncodedTransaction;
/// Modern configuration object accepted by `getTransaction`.
pub use self::rpc_transactions::SolanaGetTransactionConfig;
/// Blockhash information returned by `getLatestBlockhash` and optionally by simulation.
pub use self::rpc_transactions::SolanaLatestBlockhash;
/// One recent prioritization-fee sample returned by `getRecentPrioritizationFees`.
pub use self::rpc_transactions::SolanaPrioritizationFee;
/// Configuration accepted by `requestAirdrop`.
pub use self::rpc_transactions::SolanaRequestAirdropConfig;
/// Configuration accepted by `sendTransaction` without changing KSP transport retry semantics.
pub use self::rpc_transactions::SolanaSendTransactionConfig;
/// One ordered signature record returned by `getSignaturesForAddress`.
pub use self::rpc_transactions::SolanaSignatureInfo;
/// One non-null position returned by `getSignatureStatuses`.
pub use self::rpc_transactions::SolanaSignatureStatus;
/// Optional historical-search configuration accepted by `getSignatureStatuses`.
pub use self::rpc_transactions::SolanaSignatureStatusesConfig;
/// Pagination and context configuration accepted by `getSignaturesForAddress`.
pub use self::rpc_transactions::SolanaSignaturesForAddressConfig;
/// Configuration accepted by `simulateTransaction`.
pub use self::rpc_transactions::SolanaSimulateTransactionConfig;
/// Rich result payload returned inside the contextual `simulateTransaction` response.
pub use self::rpc_transactions::SolanaSimulateTransactionResult;
/// Account-return configuration nested under `simulateTransaction`.
pub use self::rpc_transactions::SolanaSimulationAccountsConfig;
/// Binary encoding accepted for serialized transaction input payloads.
pub use self::rpc_transactions::SolanaTransactionBinaryEncoding;
/// Confirmation state reported for a signature or transaction status.
pub use self::rpc_transactions::SolanaTransactionConfirmationStatus;
/// Encoding accepted by `getTransaction`, including its retained legacy `binary` alias.
pub use self::rpc_transactions::SolanaTransactionEncoding;
/// Transaction version reported by `getTransaction` when the version field is present.
pub use self::rpc_transactions::SolanaTransactionVersion;
/// Three-state wire field used when Solana distinguishes omission from an explicit JSON `null`.
pub use self::rpc_transactions::SolanaWireField;
/// Open cluster or network descriptor used by HTTP endpoint settings.
pub use self::settings::HttpClusterName;
/// Runtime settings for one role declared by an HTTP endpoint.

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs
// version: 9
// version: 10
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
@@ -243,3 +243,30 @@ fn public_pre_006_remaining_cluster_wrappers_are_available_from_crate_root() {
let request = ksp_onchain_transport_lib::SolanaLeaderScheduleRequest::default();
assert!(matches!(request, ksp_onchain_transport_lib::SolanaLeaderScheduleRequest::CurrentEpoch(std::option::Option::None)));
}
#[test]
fn public_transaction_pre_002_shared_types_are_constructible_from_crate_root() {
let get_transaction = ksp_onchain_transport_lib::SolanaGetTransactionConfig::new(
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Confirmed),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionEncoding::JsonParsed),
std::option::Option::Some(0),
);
assert_eq!(get_transaction.max_supported_transaction_version(), std::option::Option::Some(0));
let send = ksp_onchain_transport_lib::SolanaSendTransactionConfig::new(
std::option::Option::Some(false),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Processed),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaTransactionBinaryEncoding::Base64),
std::option::Option::Some(3),
std::option::Option::None,
);
assert_eq!(send.max_retries(), std::option::Option::Some(3));
let pagination = ksp_onchain_transport_lib::SolanaSignaturesForAddressConfig::new(
std::option::Option::Some("before".to_owned()),
std::option::Option::None,
std::option::Option::Some(10),
std::option::Option::Some(ksp_onchain_transport_lib::SolanaCommitment::Finalized),
std::option::Option::None,
);
assert_eq!(pagination.limit(), std::option::Option::Some(10));
assert!(ksp_onchain_transport_lib::SolanaWireField::<u64>::Omitted.is_omitted());
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 6
// version: 7
//! Release-level completeness canaries for the `0.2.1` HTTP foundation contract.
@@ -194,3 +194,50 @@ fn release_pre_007_v0_2_2_exact_set_is_complete_without_advancing_future_release
assert_eq!(future_transactions, 11);
assert_eq!(future_blocks_economics, 15);
}
#[test]
fn release_v0_2_3_descriptor_set_and_safety_classes_are_exact_before_wrapper_completion() {
let mut expected = std::vec![
"getFeeForMessage",
"getLatestBlockhash",
"getRecentPrioritizationFees",
"getSignaturesForAddress",
"getSignatureStatuses",
"getTransaction",
"getTransactionCount",
"isBlockhashValid",
"requestAirdrop",
"sendTransaction",
"simulateTransaction",
];
let mut actual = std::vec::Vec::new();
let mut reads = 0_usize;
let mut writes = 0_usize;
let mut simulations = 0_usize;
for descriptor in ksp_onchain_transport_lib::current_http_rpc_methods() {
if descriptor.coverage_release() != ksp_onchain_transport_lib::HttpRpcCoverageRelease::V0_2_3 {
continue;
}
actual.push(descriptor.method());
match descriptor.operation_kind() {
ksp_onchain_transport_lib::RpcOperationKind::Read => {
reads += 1;
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
},
ksp_onchain_transport_lib::RpcOperationKind::WriteSubmission => {
writes += 1;
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::NeverAfterDispatch);
},
ksp_onchain_transport_lib::RpcOperationKind::Simulation => {
simulations += 1;
assert_eq!(descriptor.transport_retry_class(), ksp_onchain_transport_lib::TransportRetryClass::RetrySafe);
},
}
}
actual.sort_unstable();
expected.sort_unstable();
assert_eq!(actual, expected);
assert_eq!(reads, 8);
assert_eq!(writes, 2);
assert_eq!(simulations, 1);
}

View File

@@ -0,0 +1,286 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/rpc_transactions.rs
// version: 1
#[test]
fn transaction_encoding_strings_match_current_and_legacy_wire_labels() {
assert_eq!(crate::SolanaTransactionBinaryEncoding::Base58.as_str(), "base58");
assert_eq!(crate::SolanaTransactionBinaryEncoding::Base64.as_str(), "base64");
assert_eq!(crate::SolanaTransactionEncoding::Binary.as_str(), "binary");
assert_eq!(crate::SolanaTransactionEncoding::Base58.as_str(), "base58");
assert_eq!(crate::SolanaTransactionEncoding::Base64.as_str(), "base64");
assert_eq!(crate::SolanaTransactionEncoding::Json.as_str(), "json");
assert_eq!(crate::SolanaTransactionEncoding::JsonParsed.as_str(), "jsonParsed");
}
#[test]
fn wire_field_preserves_omitted_null_and_value_states() {
#[derive(serde::Deserialize)]
struct Holder {
#[serde(default)]
field: crate::SolanaWireField<u64>,
}
let omitted = serde_json::from_value::<Holder>(serde_json::json!({})).expect("omitted fixture must decode");
let null = serde_json::from_value::<Holder>(serde_json::json!({"field": null})).expect("null fixture must decode");
let value = serde_json::from_value::<Holder>(serde_json::json!({"field": 7})).expect("value fixture must decode");
assert!(omitted.field.is_omitted());
assert!(!omitted.field.is_null());
assert_eq!(omitted.field.value(), std::option::Option::None);
assert!(null.field.is_null());
assert!(!null.field.is_omitted());
assert_eq!(null.field.value(), std::option::Option::None);
assert_eq!(value.field.value(), std::option::Option::Some(&7));
}
#[test]
fn get_transaction_config_serializes_modern_object_without_legacy_overload() {
let config = crate::SolanaGetTransactionConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
std::option::Option::Some(crate::SolanaTransactionEncoding::JsonParsed),
std::option::Option::Some(0),
);
assert!(!config.is_empty());
assert_eq!(config.to_json_value(), serde_json::json!({"commitment":"confirmed","encoding":"jsonParsed","maxSupportedTransactionVersion":0}));
assert!(crate::SolanaGetTransactionConfig::default().is_empty());
assert_eq!(crate::SolanaGetTransactionConfig::default().to_json_value(), serde_json::json!({}));
}
#[test]
fn signatures_for_address_config_preserves_pagination_and_context_order_independently() {
let config = crate::SolanaSignaturesForAddressConfig::new(
std::option::Option::Some("before-signature".to_owned()),
std::option::Option::Some("until-signature".to_owned()),
std::option::Option::Some(25),
std::option::Option::Some(crate::SolanaCommitment::Finalized),
std::option::Option::Some(430_000_000),
);
assert_eq!(config.before(), std::option::Option::Some("before-signature"));
assert_eq!(config.until(), std::option::Option::Some("until-signature"));
assert_eq!(config.limit(), std::option::Option::Some(25));
assert_eq!(config.commitment(), std::option::Option::Some(crate::SolanaCommitment::Finalized));
assert_eq!(config.min_context_slot(), std::option::Option::Some(430_000_000));
assert_eq!(
config.to_json_value(),
serde_json::json!({
"before":"before-signature",
"until":"until-signature",
"limit":25,
"commitment":"finalized",
"minContextSlot":430000000
})
);
assert!(crate::SolanaSignaturesForAddressConfig::default().is_empty());
}
#[test]
fn signature_status_and_airdrop_configs_preserve_explicit_options() {
let status = crate::SolanaSignatureStatusesConfig::new(std::option::Option::Some(false));
assert_eq!(status.search_transaction_history(), std::option::Option::Some(false));
assert_eq!(status.to_json_value(), serde_json::json!({"searchTransactionHistory":false}));
let airdrop = crate::SolanaRequestAirdropConfig::new(
std::option::Option::Some("recent-blockhash".to_owned()),
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
);
assert_eq!(airdrop.recent_blockhash(), std::option::Option::Some("recent-blockhash"));
assert_eq!(airdrop.commitment(), std::option::Option::Some(crate::SolanaCommitment::Confirmed));
assert_eq!(airdrop.to_json_value(), serde_json::json!({"recentBlockhash":"recent-blockhash","commitment":"confirmed"}));
}
#[test]
fn send_transaction_config_keeps_node_retries_distinct_from_transport_policy() {
let config = crate::SolanaSendTransactionConfig::new(
std::option::Option::Some(true),
std::option::Option::Some(crate::SolanaCommitment::Processed),
std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64),
std::option::Option::Some(4),
std::option::Option::Some(430_000_000),
);
assert_eq!(config.skip_preflight(), std::option::Option::Some(true));
assert_eq!(config.max_retries(), std::option::Option::Some(4));
assert_eq!(
config.to_json_value(),
serde_json::json!({
"skipPreflight":true,
"preflightCommitment":"processed",
"encoding":"base64",
"maxRetries":4,
"minContextSlot":430000000
})
);
assert!(crate::SolanaSendTransactionConfig::default().is_empty());
}
#[test]
fn simulate_transaction_config_serializes_accounts_and_flags_without_decoding_transaction() {
let address = "11111111111111111111111111111111".parse::<ksp_core_lib::Pubkey>().expect("fixture pubkey must parse");
let accounts = crate::SolanaSimulationAccountsConfig::new(std::option::Option::Some(crate::SolanaAccountEncoding::Base64), std::vec![address]);
let config = crate::SolanaSimulateTransactionConfig::new(
std::option::Option::Some(crate::SolanaCommitment::Confirmed),
std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64),
std::option::Option::Some(true),
std::option::Option::Some(false),
std::option::Option::Some(430_000_000),
std::option::Option::Some(true),
std::option::Option::Some(accounts),
);
assert_eq!(config.encoding(), std::option::Option::Some(crate::SolanaTransactionBinaryEncoding::Base64));
assert_eq!(config.replace_recent_blockhash(), std::option::Option::Some(true));
assert_eq!(config.sig_verify(), std::option::Option::Some(false));
assert_eq!(config.accounts().expect("accounts config must be present").addresses(), &[address]);
assert_eq!(
config.to_json_value(),
serde_json::json!({
"commitment":"confirmed",
"encoding":"base64",
"replaceRecentBlockhash":true,
"sigVerify":false,
"minContextSlot":430000000,
"innerInstructions":true,
"accounts":{"encoding":"base64","addresses":["11111111111111111111111111111111"]}
})
);
assert!(crate::SolanaSimulateTransactionConfig::default().is_empty());
}
#[test]
fn latest_blockhash_fixture_decodes_shared_result() {
let value =
serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/latest_blockhash.v4_2_1.json")).expect("latest blockhash fixture must decode");
let blockhash = crate::SolanaLatestBlockhash::decode_wire("getLatestBlockhash", value).expect("latest blockhash must decode");
assert_eq!(blockhash.blockhash(), "11111111111111111111111111111111");
assert_eq!(blockhash.last_valid_block_height(), 430_123_456);
}
#[test]
fn latest_blockhash_rejects_empty_wire_value() {
let result = crate::SolanaLatestBlockhash::decode_wire("getLatestBlockhash", serde_json::json!({"blockhash":"","lastValidBlockHeight":430123456}));
assert_eq!(result.expect_err("empty blockhash must fail closed").code(), crate::ERROR_CODE_INVALID_RESPONSE);
}
#[test]
fn prioritization_fee_fixture_preserves_server_order() {
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/prioritization_fee.v4_2_1.json"))
.expect("prioritization fee fixture must decode");
let first = crate::SolanaPrioritizationFee::decode_wire("getRecentPrioritizationFees", values[0].clone()).expect("first fee must decode");
let second = crate::SolanaPrioritizationFee::decode_wire("getRecentPrioritizationFees", values[1].clone()).expect("second fee must decode");
assert_eq!(first.slot(), 430_000_123);
assert_eq!(first.prioritization_fee(), 1_000);
assert_eq!(second.slot(), 430_000_122);
assert_eq!(second.prioritization_fee(), 0);
}
#[test]
fn signature_info_fixture_accepts_current_transaction_index_and_older_omission() {
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/signature_info.v4_2_1.json"))
.expect("signature info fixture must decode");
let current = crate::SolanaSignatureInfo::decode_wire("getSignaturesForAddress", values[0].clone()).expect("current signature info must decode");
let older = crate::SolanaSignatureInfo::decode_wire("getSignaturesForAddress", values[1].clone()).expect("older signature info must decode");
assert_eq!(current.slot(), 430_000_123);
assert_eq!(current.memo(), std::option::Option::Some("fixture memo"));
assert_eq!(current.transaction_index(), std::option::Option::Some(7));
assert_eq!(current.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Finalized));
assert_eq!(older.transaction_index(), std::option::Option::None);
assert!(older.err().is_some());
assert_eq!(older.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Confirmed));
}
#[test]
fn signature_status_fixture_preserves_position_null_and_legacy_status_value() {
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/signature_status.variants.json"))
.expect("signature status fixture must decode");
assert!(values[0].is_null());
let rooted = crate::SolanaSignatureStatus::decode_wire("getSignatureStatuses", values[1].clone()).expect("rooted signature status must decode");
let errored = crate::SolanaSignatureStatus::decode_wire("getSignatureStatuses", values[2].clone()).expect("errored signature status must decode");
assert_eq!(rooted.confirmations(), std::option::Option::None);
assert_eq!(rooted.status(), &serde_json::json!({"Ok":null}));
assert_eq!(rooted.confirmation_status(), std::option::Option::Some(crate::SolanaTransactionConfirmationStatus::Finalized));
assert_eq!(errored.confirmations(), std::option::Option::Some(3));
assert!(errored.err().is_some());
}
#[test]
fn unknown_confirmation_status_is_rejected_as_invalid_response() {
let result = crate::SolanaSignatureStatus::decode_wire(
"getSignatureStatuses",
serde_json::json!({"slot":1,"confirmations":0,"status":{"Ok":null},"err":null,"confirmationStatus":"future-status"}),
);
let error = result.expect_err("unknown confirmation status must fail closed");
assert_eq!(error.code(), crate::ERROR_CODE_INVALID_RESPONSE);
}
#[test]
fn encoded_transaction_fixture_preserves_legacy_binary_tuple_and_json_shapes() {
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/encoded_transaction.variants.json"))
.expect("encoded transaction fixture must decode");
let legacy = crate::SolanaEncodedTransaction::decode_wire("getTransaction", values[0].clone()).expect("legacy transaction must decode");
let binary = crate::SolanaEncodedTransaction::decode_wire("getTransaction", values[1].clone()).expect("binary transaction must decode");
let json = crate::SolanaEncodedTransaction::decode_wire("getTransaction", values[2].clone()).expect("JSON transaction must decode");
assert!(matches!(legacy, crate::SolanaEncodedTransaction::LegacyBinary(_)));
assert!(matches!(binary, crate::SolanaEncodedTransaction::Binary { encoding: crate::SolanaTransactionBinaryEncoding::Base64, .. }));
assert!(matches!(json, crate::SolanaEncodedTransaction::Json(_)));
let invalid = crate::SolanaEncodedTransaction::decode_wire("getTransaction", serde_json::json!(["payload", "json"]));
assert_eq!(invalid.expect_err("non-binary tuple encoding must fail").code(), crate::ERROR_CODE_INVALID_RESPONSE);
}
#[test]
fn confirmed_transaction_fixture_preserves_meta_version_and_transaction_index_states() {
let values = serde_json::from_str::<std::vec::Vec<serde_json::Value>>(include_str!("../fixtures/http/confirmed_transaction.variants.json"))
.expect("confirmed transaction fixture must decode");
let current = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", values[0].clone()).expect("current transaction must decode");
let legacy = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", values[1].clone()).expect("legacy transaction must decode");
let omitted = crate::SolanaConfirmedTransaction::decode_wire("getTransaction", values[2].clone()).expect("omitted-field transaction must decode");
assert_eq!(current.slot(), 430_000_123);
assert_eq!(current.version().value(), std::option::Option::Some(&crate::SolanaTransactionVersion::Number(0)));
assert_eq!(current.transaction_index().value(), std::option::Option::Some(&7));
assert!(current.meta().value().is_some());
assert_eq!(legacy.version().value(), std::option::Option::Some(&crate::SolanaTransactionVersion::Legacy));
assert!(legacy.meta().is_null());
assert!(legacy.transaction_index().is_null());
assert!(omitted.meta().is_omitted());
assert!(omitted.version().is_omitted());
assert!(omitted.transaction_index().is_omitted());
}
#[test]
fn confirmed_transaction_rejects_unknown_version_shape() {
let result = crate::SolanaConfirmedTransaction::decode_wire(
"getTransaction",
serde_json::json!({"slot":1,"blockTime":null,"transaction":"legacy","meta":null,"version":"v0"}),
);
assert_eq!(result.expect_err("unknown version label must fail").code(), crate::ERROR_CODE_INVALID_RESPONSE);
}
#[test]
fn simulation_result_fixture_preserves_rich_optional_fields_and_positional_null_accounts() {
let value = serde_json::from_str::<serde_json::Value>(include_str!("../fixtures/http/simulate_transaction_result.v4_2_1.json"))
.expect("simulation result fixture must decode");
let result = crate::SolanaSimulateTransactionResult::decode_wire("simulateTransaction", value).expect("simulation result must decode");
assert!(result.err().is_null());
assert_eq!(result.logs().value().expect("logs must be present").len(), 2);
let accounts = result.accounts().value().expect("accounts must be present");
assert_eq!(accounts.len(), 2);
assert_eq!(accounts[0].as_ref().expect("first account must be present").lamports(), 1_000);
assert!(accounts[1].is_none());
assert_eq!(result.units_consumed().value(), std::option::Option::Some(&150));
assert_eq!(result.loaded_accounts_data_size().value(), std::option::Option::Some(&64));
assert_eq!(result.fee().value(), std::option::Option::Some(&5_000));
let replacement = result.replacement_blockhash().value().expect("replacement blockhash must be present");
assert_eq!(replacement.blockhash(), "ComputeBudget111111111111111111111111111111");
assert!(result.return_data().value().is_some());
assert!(result.inner_instructions().value().is_some());
assert!(result.pre_token_balances().value().is_some());
assert!(result.post_token_balances().value().is_some());
assert!(result.loaded_addresses().value().is_some());
}
#[test]
fn simulation_result_distinguishes_omitted_from_explicit_null_fields() {
let result = crate::SolanaSimulateTransactionResult::decode_wire("simulateTransaction", serde_json::json!({"err":null,"logs":null,"unitsConsumed":1}))
.expect("partial simulation result must decode");
assert!(result.err().is_null());
assert!(result.logs().is_null());
assert_eq!(result.units_consumed().value(), std::option::Option::Some(&1));
assert!(result.accounts().is_omitted());
assert!(result.replacement_blockhash().is_omitted());
assert!(result.loaded_addresses().is_omitted());
}