// file: kb-onchain-transport/src/standard_methods.rs // version: 4 //! Canonical inventory of standard Solana HTTP and WebSocket JSON-RPC methods. /// Strongest implementation contract exposed for one standard RPC method. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize)] #[serde(rename_all = "snake_case")] pub enum StandardRpcContract { /// The crate exposes a dedicated request/result adapter in addition to raw JSON transport. TypedAdapter, /// The method is explicitly registered and callable through validated raw JSON transport. RawJson, } impl crate::StandardRpcContract { /// Returns the stable matrix code for this contract. pub const fn code(self) -> &'static str { return match self { Self::TypedAdapter => "typed_adapter", Self::RawJson => "raw_json", }; } } /// Local category used to group standard Solana HTTP methods. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize)] #[serde(rename_all = "snake_case")] pub enum StandardHttpCategory { /// Account state and account ownership reads. Accounts, /// SPL Token account and mint reads. Tokens, /// Transaction, signature, fee, simulation and submission methods. Transactions, /// Block, ledger and performance methods. Blocks, /// Cluster, node, epoch, slot and validator methods. Cluster, /// Inflation, supply and stake-economics methods. Economics, } impl crate::StandardHttpCategory { /// Returns the stable matrix code for this category. pub const fn code(self) -> &'static str { return match self { Self::Accounts => "accounts", Self::Tokens => "tokens", Self::Transactions => "transactions", Self::Blocks => "blocks", Self::Cluster => "cluster", Self::Economics => "economics", }; } } /// One standard Solana HTTP JSON-RPC method specification. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct StandardHttpMethodSpec { /// Exact JSON-RPC method name. pub method: &'static str, /// Local inventory category. pub category: crate::StandardHttpCategory, /// Strongest contract currently exposed by `kb-onchain-transport`. pub contract: crate::StandardRpcContract, } impl crate::StandardHttpMethodSpec { /// Returns the local HTTP routing class for this method. pub fn method_class(&self) -> crate::HttpMethodClass { return match self.method { "requestAirdrop" | "sendTransaction" => crate::HttpMethodClass::SendTransaction, "getBlock" | "getBlocks" | "getBlocksWithLimit" | "getProgramAccounts" | "getSignaturesForAddress" | "getTransaction" | "simulateTransaction" => crate::HttpMethodClass::HeavyRead, _ => crate::HttpMethodClass::GeneralRpc, }; } } /// Every standard Solana HTTP JSON-RPC method documented by the canonical RPC reference. pub const STANDARD_HTTP_METHODS: [crate::StandardHttpMethodSpec; 52] = [ crate::StandardHttpMethodSpec { method: "getAccountInfo", category: crate::StandardHttpCategory::Accounts, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getBalance", category: crate::StandardHttpCategory::Accounts, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getLargestAccounts", category: crate::StandardHttpCategory::Accounts, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getMinimumBalanceForRentExemption", category: crate::StandardHttpCategory::Accounts, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getMultipleAccounts", category: crate::StandardHttpCategory::Accounts, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getProgramAccounts", category: crate::StandardHttpCategory::Accounts, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getTokenAccountBalance", category: crate::StandardHttpCategory::Tokens, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getTokenAccountsByDelegate", category: crate::StandardHttpCategory::Tokens, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getTokenAccountsByOwner", category: crate::StandardHttpCategory::Tokens, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getTokenLargestAccounts", category: crate::StandardHttpCategory::Tokens, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getTokenSupply", category: crate::StandardHttpCategory::Tokens, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getFeeForMessage", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getLatestBlockhash", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getRecentPrioritizationFees", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getSignaturesForAddress", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getSignatureStatuses", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getTransaction", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getTransactionCount", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "isBlockhashValid", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "requestAirdrop", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "sendTransaction", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "simulateTransaction", category: crate::StandardHttpCategory::Transactions, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getBlock", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getBlockCommitment", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getBlockHeight", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getBlockProduction", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getBlocks", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getBlocksWithLimit", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getBlockTime", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getFirstAvailableBlock", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getRecentPerformanceSamples", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "minimumLedgerSlot", category: crate::StandardHttpCategory::Blocks, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getClusterNodes", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getEpochInfo", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getEpochSchedule", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getGenesisHash", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getHealth", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getHighestSnapshotSlot", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getIdentity", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getLeaderSchedule", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getMaxRetransmitSlot", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getMaxShredInsertSlot", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getSlot", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getSlotLeader", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getSlotLeaders", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getVersion", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getVoteAccounts", category: crate::StandardHttpCategory::Cluster, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getInflationGovernor", category: crate::StandardHttpCategory::Economics, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getInflationRate", category: crate::StandardHttpCategory::Economics, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getInflationReward", category: crate::StandardHttpCategory::Economics, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getStakeMinimumDelegation", category: crate::StandardHttpCategory::Economics, contract: crate::StandardRpcContract::TypedAdapter, }, crate::StandardHttpMethodSpec { method: "getSupply", category: crate::StandardHttpCategory::Economics, contract: crate::StandardRpcContract::TypedAdapter, }, ]; /// Returns the canonical specification for one exact standard HTTP method name. pub fn standard_http_method( method: &str, ) -> std::option::Option<&'static crate::StandardHttpMethodSpec> { return crate::STANDARD_HTTP_METHODS.iter().find(|entry| return entry.method == method); } /// Stability of one standard Solana WebSocket subscription surface. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize)] #[serde(rename_all = "snake_case")] pub enum StandardWsStability { /// Stable standard PubSub method. Stable, /// Method documented as unstable and potentially gated by validator flags. Unstable, } impl crate::StandardWsStability { /// Returns the stable matrix code for this stability class. pub const fn code(self) -> &'static str { return match self { Self::Stable => "stable", Self::Unstable => "unstable", }; } } /// One standard Solana WebSocket subscribe/unsubscribe pair. #[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct StandardWsSubscriptionSpec { /// Exact subscribe method name. pub subscribe_method: &'static str, /// Exact unsubscribe method name. pub unsubscribe_method: &'static str, /// Exact notification method name emitted by the server. pub notification_method: &'static str, /// Stability declared by the canonical Solana RPC documentation. pub stability: crate::StandardWsStability, /// Strongest typed request contract exposed by `kb-onchain-transport`. pub request_contract: crate::StandardRpcContract, /// Strongest typed notification contract exposed by `kb-onchain-transport`. pub notification_contract: crate::StandardRpcContract, /// Whether the pair is supported by the reusable persistent session runtime. pub persistent_runtime: bool, } /// Every standard Solana WebSocket subscription pair documented by the canonical RPC reference. pub const STANDARD_WS_SUBSCRIPTIONS: [crate::StandardWsSubscriptionSpec; 9] = [ crate::StandardWsSubscriptionSpec { subscribe_method: "accountSubscribe", unsubscribe_method: "accountUnsubscribe", notification_method: "accountNotification", stability: crate::StandardWsStability::Stable, request_contract: crate::StandardRpcContract::TypedAdapter, notification_contract: crate::StandardRpcContract::TypedAdapter, persistent_runtime: true, }, crate::StandardWsSubscriptionSpec { subscribe_method: "blockSubscribe", unsubscribe_method: "blockUnsubscribe", notification_method: "blockNotification", stability: crate::StandardWsStability::Unstable, request_contract: crate::StandardRpcContract::TypedAdapter, notification_contract: crate::StandardRpcContract::TypedAdapter, persistent_runtime: true, }, crate::StandardWsSubscriptionSpec { subscribe_method: "logsSubscribe", unsubscribe_method: "logsUnsubscribe", notification_method: "logsNotification", stability: crate::StandardWsStability::Stable, request_contract: crate::StandardRpcContract::TypedAdapter, notification_contract: crate::StandardRpcContract::TypedAdapter, persistent_runtime: true, }, crate::StandardWsSubscriptionSpec { subscribe_method: "programSubscribe", unsubscribe_method: "programUnsubscribe", notification_method: "programNotification", stability: crate::StandardWsStability::Stable, request_contract: crate::StandardRpcContract::TypedAdapter, notification_contract: crate::StandardRpcContract::TypedAdapter, persistent_runtime: true, }, crate::StandardWsSubscriptionSpec { subscribe_method: "rootSubscribe", unsubscribe_method: "rootUnsubscribe", notification_method: "rootNotification", stability: crate::StandardWsStability::Stable, request_contract: crate::StandardRpcContract::TypedAdapter, notification_contract: crate::StandardRpcContract::TypedAdapter, persistent_runtime: true, }, crate::StandardWsSubscriptionSpec { subscribe_method: "signatureSubscribe", unsubscribe_method: "signatureUnsubscribe", notification_method: "signatureNotification", stability: crate::StandardWsStability::Stable, request_contract: crate::StandardRpcContract::TypedAdapter, notification_contract: crate::StandardRpcContract::TypedAdapter, persistent_runtime: true, }, crate::StandardWsSubscriptionSpec { subscribe_method: "slotSubscribe", unsubscribe_method: "slotUnsubscribe", notification_method: "slotNotification", stability: crate::StandardWsStability::Stable, request_contract: crate::StandardRpcContract::TypedAdapter, notification_contract: crate::StandardRpcContract::TypedAdapter, persistent_runtime: true, }, crate::StandardWsSubscriptionSpec { subscribe_method: "slotsUpdatesSubscribe", unsubscribe_method: "slotsUpdatesUnsubscribe", notification_method: "slotsUpdatesNotification", stability: crate::StandardWsStability::Unstable, request_contract: crate::StandardRpcContract::TypedAdapter, notification_contract: crate::StandardRpcContract::TypedAdapter, persistent_runtime: true, }, crate::StandardWsSubscriptionSpec { subscribe_method: "voteSubscribe", unsubscribe_method: "voteUnsubscribe", notification_method: "voteNotification", stability: crate::StandardWsStability::Unstable, request_contract: crate::StandardRpcContract::TypedAdapter, notification_contract: crate::StandardRpcContract::TypedAdapter, persistent_runtime: true, }, ]; /// Returns the canonical subscription specification matching a subscribe or unsubscribe method. pub fn standard_ws_subscription( method: &str, ) -> std::option::Option<&'static crate::StandardWsSubscriptionSpec> { return crate::STANDARD_WS_SUBSCRIPTIONS.iter().find(|entry| { return entry.subscribe_method == method || entry.unsubscribe_method == method; }); } #[cfg(test)] mod tests { #[test] fn standard_http_inventory_is_exact_unique_and_classified() { assert_eq!(crate::STANDARD_HTTP_METHODS.len(), 52); let mut names = std::collections::BTreeSet::new(); let mut typed_count = 0_usize; for method in &crate::STANDARD_HTTP_METHODS { assert!(names.insert(method.method)); if method.contract == crate::StandardRpcContract::TypedAdapter { typed_count = typed_count.saturating_add(1); } assert_eq!( crate::standard_http_method(method.method), std::option::Option::Some(method) ); } assert_eq!(typed_count, 52); } #[test] fn configurable_request_registry_covers_the_former_thirty_eight_raw_methods() { fn method() -> &'static str { return ::METHOD; } let methods = [ method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), method::(), ]; assert_eq!(methods.len(), 38); let mut unique = std::collections::BTreeSet::new(); for method_name in methods { assert!(unique.insert(method_name)); let specification = match crate::standard_http_method(method_name) { std::option::Option::Some(specification) => specification, std::option::Option::None => panic!("typed method absent from registry"), }; assert_eq!(specification.contract, crate::StandardRpcContract::TypedAdapter); } } #[test] fn standard_ws_inventory_contains_nine_pairs_and_eighteen_unique_methods() { assert_eq!(crate::STANDARD_WS_SUBSCRIPTIONS.len(), 9); let mut names = std::collections::BTreeSet::new(); let mut unstable_count = 0_usize; for subscription in &crate::STANDARD_WS_SUBSCRIPTIONS { assert!(names.insert(subscription.subscribe_method)); assert!(names.insert(subscription.unsubscribe_method)); if subscription.stability == crate::StandardWsStability::Unstable { unstable_count = unstable_count.saturating_add(1); } assert_eq!(subscription.request_contract, crate::StandardRpcContract::TypedAdapter); assert_eq!( subscription.notification_contract, crate::StandardRpcContract::TypedAdapter ); assert!(subscription.persistent_runtime); assert_eq!( crate::standard_ws_subscription(subscription.subscribe_method), std::option::Option::Some(subscription) ); assert_eq!( crate::standard_ws_subscription(subscription.unsubscribe_method), std::option::Option::Some(subscription) ); } assert_eq!(names.len(), 18); assert_eq!(unstable_count, 3); } #[test] fn standard_rpc_matrix_matches_compiled_inventory() { let raw = include_str!("../../test-fixtures/contract-matrices/SOLANA_STANDARD_RPC_MATRIX.json"); let parsed = match serde_json::from_str::(raw) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("rpc matrix parsing failed: {error}"), }; let http_methods = match parsed.get("http_methods").and_then(serde_json::Value::as_array) { std::option::Option::Some(value) => value, std::option::Option::None => panic!("rpc matrix http_methods missing"), }; let ws_subscriptions = match parsed.get("ws_subscriptions").and_then(serde_json::Value::as_array) { std::option::Option::Some(value) => value, std::option::Option::None => panic!("rpc matrix ws_subscriptions missing"), }; assert_eq!(http_methods.len(), crate::STANDARD_HTTP_METHODS.len()); assert_eq!(ws_subscriptions.len(), crate::STANDARD_WS_SUBSCRIPTIONS.len()); for method in &crate::STANDARD_HTTP_METHODS { let matrix_entry = http_methods.iter().find(|entry| { return entry.get("method").and_then(serde_json::Value::as_str) == std::option::Option::Some(method.method); }); let matrix_entry = match matrix_entry { std::option::Option::Some(value) => value, std::option::Option::None => panic!("rpc matrix HTTP method missing"), }; assert_eq!( matrix_entry.get("category").and_then(serde_json::Value::as_str), std::option::Option::Some(method.category.code()) ); assert_eq!( matrix_entry.get("contract").and_then(serde_json::Value::as_str), std::option::Option::Some(method.contract.code()) ); } for subscription in &crate::STANDARD_WS_SUBSCRIPTIONS { let matrix_entry = ws_subscriptions.iter().find(|entry| { return entry.get("subscribe_method").and_then(serde_json::Value::as_str) == std::option::Option::Some(subscription.subscribe_method); }); let matrix_entry = match matrix_entry { std::option::Option::Some(value) => value, std::option::Option::None => panic!("rpc matrix WS subscription missing"), }; assert_eq!( matrix_entry.get("unsubscribe_method").and_then(serde_json::Value::as_str), std::option::Option::Some(subscription.unsubscribe_method) ); assert_eq!( matrix_entry.get("notification_method").and_then(serde_json::Value::as_str), std::option::Option::Some(subscription.notification_method) ); assert_eq!( matrix_entry.get("stability").and_then(serde_json::Value::as_str), std::option::Option::Some(subscription.stability.code()) ); assert_eq!( matrix_entry.get("request_contract").and_then(serde_json::Value::as_str), std::option::Option::Some(subscription.request_contract.code()) ); assert_eq!( matrix_entry.get("notification_contract").and_then(serde_json::Value::as_str), std::option::Option::Some(subscription.notification_contract.code()) ); assert_eq!( matrix_entry.get("persistent_runtime").and_then(serde_json::Value::as_bool), std::option::Option::Some(subscription.persistent_runtime) ); } } }