// file: kb_pipeline/src/solana_token_2022_stateful.rs // version: 5 //! Contextual Token-2022 account-state validation and materialization routing. /// Optional external identities required to validate cross-account Token-2022 state. #[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)] pub struct Token2022StatefulContext { /// Expected group account for one TokenGroupMember extension. pub expected_group_address: std::option::Option, } /// Maximum complete Token-2022 account data accepted by one bounded RPC read. pub const MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES: usize = 65_536; /// One bounded Token-2022 account read request. #[derive(Clone, Debug, PartialEq)] pub struct Token2022StatefulReadRequest { /// Endpoint role used for the HTTP RPC request. pub query_role: std::string::String, /// Canonical account address. pub account: kb_model::Pubkey, /// Expected Token-2022 base-state category. pub kind: kb_decoder_spl_token_2022::state::Token2022StateKind, /// Optional minimum RPC context slot. pub min_context_slot: std::option::Option, /// Maximum decoded account bytes accepted from the endpoint. pub max_data_bytes: usize, /// Optional external identities needed by cross-account extensions. pub context: crate::solana_token_2022_stateful::Token2022StatefulContext, } /// One bounded RPC read and its contextually validated projections. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct Token2022StatefulReadResult { /// Commitment used for the RPC read. pub commitment: std::string::String, /// Context slot returned by the endpoint. pub context_slot: u64, /// Complete validated snapshot bundle. pub snapshot: crate::solana_token_2022_stateful::Token2022StatefulSnapshotBundle, } /// Reads, validates, parses, and routes one bounded Token-2022 account snapshot. pub async fn read_token_2022_stateful_snapshot( pool: &kb_rpc::HttpEndpointPool, request: &crate::solana_token_2022_stateful::Token2022StatefulReadRequest, ) -> kb_core::Result { if request.query_role.trim().is_empty() { return std::result::Result::Err(kb_core::Error::config( "Token-2022 stateful read query_role must not be empty", )); } if request.max_data_bytes == 0 || request.max_data_bytes > crate::solana_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES { return std::result::Result::Err(kb_core::Error::config(format!( "Token-2022 stateful read max_data_bytes must be between 1 and {}", crate::solana_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES ))); } let config = match kb_rpc::GetAccountInfoConfig::new_with_data( kb_rpc::RpcCommitmentLevel::Confirmed, request.min_context_slot, request.max_data_bytes, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let result = match pool .get_account_info_for_role(request.query_role.as_str(), &request.account, &config) .await { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return crate::solana_token_2022_stateful::materialize_token_2022_account_info_result( request, &result, ); } /// Validates one complete RPC account response before Token-2022 parsing and routing. pub fn materialize_token_2022_account_info_result( request: &crate::solana_token_2022_stateful::Token2022StatefulReadRequest, result: &kb_rpc::AccountInfoResult, ) -> kb_core::Result { if request.max_data_bytes == 0 || request.max_data_bytes > crate::solana_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES { return std::result::Result::Err(kb_core::Error::config(format!( "Token-2022 stateful read max_data_bytes must be between 1 and {}", crate::solana_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES ))); } if let std::option::Option::Some(min_context_slot) = request.min_context_slot { if result.context.slot < min_context_slot { return std::result::Result::Err(kb_core::Error::new( "token_2022_stateful_context_slot_too_old", format!( "Token-2022 account context slot {} is below requested minimum {min_context_slot}", result.context.slot ), )); } } let account = match result.account.as_ref() { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(kb_core::Error::new( "token_2022_stateful_account_missing", format!("Token-2022 account {} does not exist", request.account.0), )); }, }; if account.executable { return std::result::Result::Err(kb_core::Error::new( "token_2022_stateful_account_executable", format!("Token-2022 state account {} must not be executable", request.account.0), )); } if account.owner.0 != kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID { return std::result::Result::Err(kb_core::Error::new( "token_2022_stateful_owner_mismatch", format!( "Token-2022 state account {} owner must be {}, got {}", request.account.0, kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID, account.owner.0 ), )); } if account.space > request.max_data_bytes as u64 || account.data.len() > request.max_data_bytes { return std::result::Result::Err(kb_core::Error::new( "token_2022_stateful_account_too_large", format!( "Token-2022 account {} reports {} bytes and returned {} bytes above limit {}", request.account.0, account.space, account.data.len(), request.max_data_bytes ), )); } if account.space != account.data.len() as u64 { return std::result::Result::Err(kb_core::Error::new( "token_2022_stateful_account_data_incomplete", format!( "Token-2022 account {} reports {} bytes but returned {} decoded bytes", request.account.0, account.space, account.data.len() ), )); } let state = match kb_decoder_spl_token_2022::state::parse_token_2022_state( request.kind, account.data.as_slice(), ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(kb_core::Error::new( "token_2022_stateful_parse_failed", error, )); }, }; let snapshot = match crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context( request.account.0.as_str(), result.context.slot, &state, &request.context, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(kb_core::Error::new( "token_2022_stateful_projection_failed", error, )); }, }; return std::result::Result::Ok( crate::solana_token_2022_stateful::Token2022StatefulReadResult { commitment: "confirmed".to_string(), context_slot: result.context.slot, snapshot, }, ); } /// One contextually validated Token-2022 state snapshot and its owned projections. #[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] pub struct Token2022StatefulSnapshotBundle { /// Canonical account identity used for contextual checks and stable output keys. pub account_key: std::string::String, /// Context slot associated with the account read. pub slot: u64, /// Parsed base-state category. pub state_kind: std::string::String, /// Ordered published or future extension names retained by the parser. pub extension_names: std::vec::Vec, /// Processor-owned projections routed without duplicate ownership. pub outputs: std::vec::Vec, } /// Parse, contextually validate, and materialize one bounded Token-2022 account snapshot. pub fn materialize_token_2022_stateful_snapshot( account_key: &str, owner_program_id: &str, slot: u64, kind: kb_decoder_spl_token_2022::state::Token2022StateKind, data: &[u8], ) -> std::result::Result { if owner_program_id != kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID { return std::result::Result::Err(format!( "Token-2022 state snapshot owner must be {}, got {owner_program_id}", kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID )); } let state = match kb_decoder_spl_token_2022::state::parse_token_2022_state(kind, data) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context( account_key, slot, &state, &crate::solana_token_2022_stateful::Token2022StatefulContext::default(), ); } /// Contextually validate and materialize one already parsed Token-2022 account snapshot. pub fn materialize_parsed_token_2022_stateful_snapshot( account_key: &str, slot: u64, state: &kb_decoder_spl_token_2022::state::Token2022State, ) -> std::result::Result { return crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context( account_key, slot, state, &crate::solana_token_2022_stateful::Token2022StatefulContext::default(), ); } /// Contextually validate one parsed Token-2022 snapshot with external cross-account identities. pub fn materialize_parsed_token_2022_stateful_snapshot_with_context( account_key: &str, slot: u64, state: &kb_decoder_spl_token_2022::state::Token2022State, context: &crate::solana_token_2022_stateful::Token2022StatefulContext, ) -> std::result::Result { if account_key.trim().is_empty() { return std::result::Result::Err( "Token-2022 stateful snapshot requires a non-empty account key".to_string(), ); } let decoded = match bs58::decode(account_key).into_vec() { std::result::Result::Ok(value) => value, std::result::Result::Err(_) => { return std::result::Result::Err( "Token-2022 stateful snapshot account key must be valid base58".to_string(), ); }, }; if decoded.len() != 32 { return std::result::Result::Err(format!( "Token-2022 stateful snapshot account key must decode to 32 bytes, got {}", decoded.len() )); } match crate::solana_token_2022_stateful::validate_embedded_mint_identity(account_key, state) { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => return std::result::Result::Err(error), } match crate::solana_token_2022_stateful::validate_group_member_identity(state, context) { std::result::Result::Ok(()) => {}, std::result::Result::Err(error) => return std::result::Result::Err(error), } let account_output = match kb_materializer_token_accounts::materialize_token_2022_state_snapshot( account_key, slot, state, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let mut outputs = std::vec![account_output]; if state.kind == kb_decoder_spl_token_2022::state::Token2022StateKind::Mint { let metadata_outputs = match kb_materializer_metadata::materialize_token_2022_metadata_snapshot( account_key, slot, state, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; outputs.extend(metadata_outputs); } let fee_outputs = match kb_materializer_fees::materialize_token_2022_fee_state_snapshots( account_key, slot, state, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; outputs.extend(fee_outputs); let admin_outputs = match kb_materializer_admin::materialize_token_2022_admin_state_snapshots( account_key, slot, state, ) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; outputs.extend(admin_outputs); let state_kind = match state.kind { kb_decoder_spl_token_2022::state::Token2022StateKind::Mint => "mint", kb_decoder_spl_token_2022::state::Token2022StateKind::Account => "account", kb_decoder_spl_token_2022::state::Token2022StateKind::Multisig => "multisig", }; return std::result::Result::Ok( crate::solana_token_2022_stateful::Token2022StatefulSnapshotBundle { account_key: account_key.to_string(), slot, state_kind: state_kind.to_string(), extension_names: state .extensions .iter() .map(|entry| return entry.extension_name.to_string()) .collect(), outputs, }, ); } fn validate_embedded_mint_identity( account_key: &str, state: &kb_decoder_spl_token_2022::state::Token2022State, ) -> std::result::Result<(), String> { for entry in &state.extensions { if entry.extension_name != "token_metadata" && entry.extension_name != "token_group" && entry.extension_name != "token_group_member" { continue; } let embedded_mint = entry.value_fields.get("mint").and_then(serde_json::Value::as_str); let embedded_mint = match embedded_mint { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err(format!( "Token-2022 {} extension requires a structured mint field", entry.extension_name )); }, }; if embedded_mint != account_key { return std::result::Result::Err(format!( "Token-2022 {} mint {} does not match account {account_key}", entry.extension_name, embedded_mint )); } } return std::result::Result::Ok(()); } fn validate_group_member_identity( state: &kb_decoder_spl_token_2022::state::Token2022State, context: &crate::solana_token_2022_stateful::Token2022StatefulContext, ) -> std::result::Result<(), String> { for entry in &state.extensions { if entry.extension_name != "token_group_member" { continue; } let group = entry.value_fields.get("group").and_then(serde_json::Value::as_str); let group = match group { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( "Token-2022 token_group_member extension requires a structured group field" .to_string(), ); }, }; let expected = match context.expected_group_address.as_deref() { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( "Token-2022 token_group_member validation requires an expected group address" .to_string(), ); }, }; if group != expected { return std::result::Result::Err(format!( "Token-2022 token_group_member group {group} does not match expected group {expected}" )); } } return std::result::Result::Ok(()); } #[cfg(test)] mod tests { fn account_key(byte: u8) -> String { return bs58::encode([byte; 32]).into_string(); } fn mint_state(account_key: &str) -> kb_decoder_spl_token_2022::state::Token2022State { return kb_decoder_spl_token_2022::state::Token2022State { kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint, base_fields: serde_json::json!({"supply":"1","decimals":0,"initialized":true}), base_hex: "00".repeat(82), account_type: std::option::Option::Some(1), extensions: std::vec![ kb_decoder_spl_token_2022::state::Token2022TlvEntry { extension_type: 19, extension_name: "token_metadata", value_hex: "01".to_string(), value_fields: serde_json::json!({ "mint": account_key, "name": "Token", "symbol": "TOK", "uri": "https://example.invalid/token.json" }), }, kb_decoder_spl_token_2022::state::Token2022TlvEntry { extension_type: 20, extension_name: "token_group", value_hex: "02".to_string(), value_fields: serde_json::json!({ "mint": account_key, "size": "1", "maxSize": "10" }), }, ], }; } #[test] fn mint_snapshot_routes_account_and_metadata_outputs_once() { let account_key = account_key(7); let state = mint_state(account_key.as_str()); let bundle = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot( account_key.as_str(), 42, &state, ); assert_eq!( bundle.as_ref().map(|value| return value.outputs.len()), std::result::Result::Ok(3) ); assert_eq!( bundle.as_ref().map(|value| return value.state_kind.clone()), std::result::Result::Ok("mint".to_string()) ); assert_eq!( bundle.as_ref().map(|value| return value.extension_names.clone()), std::result::Result::Ok(std::vec![ "token_metadata".to_string(), "token_group".to_string() ]) ); } #[test] fn embedded_metadata_mint_must_match_the_account_identity() { let account_address = account_key(8); let mut state = mint_state(account_address.as_str()); state.extensions[0].value_fields["mint"] = serde_json::json!(account_key(9)); let result = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot( account_address.as_str(), 42, &state, ); assert!(result.is_err()); } #[test] fn owner_and_account_identity_fail_closed_before_projection() { let account_key = account_key(10); let data = [0u8; 82]; let wrong_owner = crate::solana_token_2022_stateful::materialize_token_2022_stateful_snapshot( account_key.as_str(), kb_program_ids::SPL_TOKEN_PROGRAM_ID, 1, kb_decoder_spl_token_2022::state::Token2022StateKind::Mint, &data, ); assert!(wrong_owner.is_err()); let state = mint_state(account_key.as_str()); let malformed_key = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot( "not-base58-0", 1, &state, ); assert!(malformed_key.is_err()); } #[test] fn account_snapshot_has_no_metadata_projection() { let account_key = account_key(11); let state = kb_decoder_spl_token_2022::state::Token2022State { kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Account, base_fields: serde_json::json!({"amount":"0","state":"initialized"}), base_hex: "00".repeat(165), account_type: std::option::Option::None, extensions: std::vec::Vec::new(), }; let bundle = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot( account_key.as_str(), 9, &state, ); assert_eq!( bundle.as_ref().map(|value| return value.outputs.len()), std::result::Result::Ok(1) ); } #[test] fn group_member_requires_and_matches_external_group_identity() { let account_address = account_key(12); let group_address = account_key(13); let state = kb_decoder_spl_token_2022::state::Token2022State { kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint, base_fields: serde_json::json!({"supply":"1","decimals":0,"initialized":true}), base_hex: "00".repeat(82), account_type: std::option::Option::Some(1), extensions: std::vec![kb_decoder_spl_token_2022::state::Token2022TlvEntry { extension_type: 23, extension_name: "token_group_member", value_hex: "03".to_string(), value_fields: serde_json::json!({ "mint": account_address, "group": group_address, "memberNumber": "1" }), }], }; let missing = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot( account_address.as_str(), 44, &state, ); assert!(missing.is_err()); let wrong_context = crate::solana_token_2022_stateful::Token2022StatefulContext { expected_group_address: std::option::Option::Some(account_key(14)), }; let wrong = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context( account_address.as_str(), 44, &state, &wrong_context, ); assert!(wrong.is_err()); let context = crate::solana_token_2022_stateful::Token2022StatefulContext { expected_group_address: std::option::Option::Some(group_address), }; let valid = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context( account_address.as_str(), 44, &state, &context, ); assert_eq!( valid.as_ref().map(|bundle| return bundle.outputs.len()), std::result::Result::Ok(2) ); } #[test] fn mint_and_account_fee_extensions_route_to_the_fee_owner_once() { let mint_address = account_key(15); let mint = kb_decoder_spl_token_2022::state::Token2022State { kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint, base_fields: serde_json::json!({"supply":"1","decimals":0,"initialized":true}), base_hex: "00".repeat(82), account_type: std::option::Option::Some(1), extensions: std::vec![kb_decoder_spl_token_2022::state::Token2022TlvEntry { extension_type: 1, extension_name: "transfer_fee_config", value_hex: "01".to_string(), value_fields: serde_json::json!({"withheldAmount":"7"}), }], }; let mint_bundle = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot( mint_address.as_str(), 50, &mint, ); assert_eq!( mint_bundle.as_ref().map(|bundle| return bundle.outputs.len()), std::result::Result::Ok(2) ); assert_eq!( mint_bundle .as_ref() .map(|bundle| return bundle.outputs[1].payload_json["provenance"]["processorName"] .clone()), std::result::Result::Ok(serde_json::json!("fees")) ); let account_address = account_key(16); let account = kb_decoder_spl_token_2022::state::Token2022State { kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Account, base_fields: serde_json::json!({"amount":"0","state":"initialized"}), base_hex: "00".repeat(165), account_type: std::option::Option::Some(2), extensions: std::vec![kb_decoder_spl_token_2022::state::Token2022TlvEntry { extension_type: 17, extension_name: "confidential_transfer_fee_amount", value_hex: "02".to_string(), value_fields: serde_json::json!({"withheldAmount":"ciphertext"}), }], }; let account_bundle = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot( account_address.as_str(), 51, &account, ); assert_eq!( account_bundle.as_ref().map(|bundle| return bundle.outputs.len()), std::result::Result::Ok(2) ); assert_eq!( account_bundle .as_ref() .map(|bundle| return bundle.outputs[1].payload_json["confidentialValuesDecrypted"] .clone()), std::result::Result::Ok(serde_json::json!(false)) ); } #[test] fn mint_admin_extensions_route_to_the_admin_owner_once() { let account_key = account_key(12); let mut state = mint_state(account_key.as_str()); state.extensions.push(kb_decoder_spl_token_2022::state::Token2022TlvEntry { extension_type: 6, extension_name: "default_account_state", value_hex: "02".to_string(), value_fields: serde_json::json!({"state": 2}), }); let bundle = crate::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot( account_key.as_str(), 17, &state, ); assert_eq!( bundle.as_ref().map(|value| return value.outputs.len()), std::result::Result::Ok(4) ); assert_eq!( bundle.as_ref().map(|value| { return value .outputs .iter() .filter(|output| { return output.payload_json["domain"] == serde_json::json!("token_2022_extension_admin_state"); }) .count(); }), std::result::Result::Ok(1) ); } #[test] fn complete_rpc_account_routes_only_after_owner_size_and_context_validation() { let account = crate::solana_token_2022_stateful::tests::account_key(21); let request = crate::solana_token_2022_stateful::Token2022StatefulReadRequest { query_role: "execution".to_string(), account: kb_model::Pubkey(account.clone()), kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint, min_context_slot: std::option::Option::Some(40), max_data_bytes: 82, context: crate::solana_token_2022_stateful::Token2022StatefulContext::default(), }; let result = kb_rpc::AccountInfoResult { context: kb_rpc::RpcResponseContext { slot: 41, api_version: std::option::Option::None, }, account: std::option::Option::Some(kb_rpc::AccountInfoValue { lamports: 1, owner: kb_model::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string()), executable: false, rent_epoch: 0, space: 82, data: std::vec![0; 82], }), }; let materialized = crate::solana_token_2022_stateful::materialize_token_2022_account_info_result( &request, &result, ); assert_eq!( materialized.as_ref().map(|value| return value.context_slot), std::result::Result::Ok(41) ); assert_eq!( materialized.as_ref().map(|value| return value.snapshot.outputs.len()), std::result::Result::Ok(1) ); } #[test] fn incomplete_foreign_executable_and_stale_rpc_accounts_fail_closed() { let account = crate::solana_token_2022_stateful::tests::account_key(22); let request = crate::solana_token_2022_stateful::Token2022StatefulReadRequest { query_role: "execution".to_string(), account: kb_model::Pubkey(account), kind: kb_decoder_spl_token_2022::state::Token2022StateKind::Mint, min_context_slot: std::option::Option::Some(50), max_data_bytes: 82, context: crate::solana_token_2022_stateful::Token2022StatefulContext::default(), }; let base = kb_rpc::AccountInfoValue { lamports: 1, owner: kb_model::ProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string()), executable: false, rent_epoch: 0, space: 82, data: std::vec![0; 82], }; let stale = kb_rpc::AccountInfoResult { context: kb_rpc::RpcResponseContext { slot: 49, api_version: std::option::Option::None, }, account: std::option::Option::Some(base.clone()), }; assert!( crate::solana_token_2022_stateful::materialize_token_2022_account_info_result( &request, &stale ) .is_err() ); let mut foreign = base.clone(); foreign.owner = kb_model::ProgramId(kb_program_ids::SPL_TOKEN_PROGRAM_ID.to_string()); let foreign = kb_rpc::AccountInfoResult { context: kb_rpc::RpcResponseContext { slot: 50, api_version: std::option::Option::None, }, account: std::option::Option::Some(foreign), }; assert!( crate::solana_token_2022_stateful::materialize_token_2022_account_info_result( &request, &foreign ) .is_err() ); let mut executable = base.clone(); executable.executable = true; let executable = kb_rpc::AccountInfoResult { context: kb_rpc::RpcResponseContext { slot: 50, api_version: std::option::Option::None, }, account: std::option::Option::Some(executable), }; assert!( crate::solana_token_2022_stateful::materialize_token_2022_account_info_result( &request, &executable ) .is_err() ); let mut incomplete = base; incomplete.data.pop(); let incomplete = kb_rpc::AccountInfoResult { context: kb_rpc::RpcResponseContext { slot: 50, api_version: std::option::Option::None, }, account: std::option::Option::Some(incomplete), }; assert!( crate::solana_token_2022_stateful::materialize_token_2022_account_info_result( &request, &incomplete ) .is_err() ); } }