diff --git a/Cargo.lock b/Cargo.lock index a8558aa59a0aa1..390f569ba7d214 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5655,6 +5655,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "solana-account-decoder-client-types", "solana-config-program", "solana-sdk", "spl-pod", @@ -5666,6 +5667,20 @@ dependencies = [ "zstd", ] +[[package]] +name = "solana-account-decoder-client-types" +version = "2.1.0" +dependencies = [ + "base64 0.22.1", + "bs58", + "serde", + "serde_derive", + "serde_json", + "solana-account", + "solana-pubkey", + "zstd", +] + [[package]] name = "solana-account-info" version = "2.1.0" @@ -7590,6 +7605,7 @@ dependencies = [ "serde_derive", "serde_json", "solana-account-decoder", + "solana-account-decoder-client-types", "solana-rpc-client-api", "solana-sdk", "solana-transaction-status-client-types", @@ -7613,7 +7629,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "solana-account-decoder", + "solana-account-decoder-client-types", "solana-inline-spl", "solana-sdk", "solana-transaction-status-client-types", @@ -8428,7 +8444,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "solana-account-decoder", + "solana-account-decoder-client-types", "solana-sdk", "solana-signature", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 921996e4a27749..36217c51ad2161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ split-debuginfo = "packed" [workspace] members = [ "account-decoder", + "account-decoder-client-types", "accounts-bench", "accounts-cluster-bench", "accounts-db", @@ -367,6 +368,7 @@ socket2 = "0.5.7" soketto = "0.7" solana-account = { path = "sdk/account", version = "=2.1.0" } solana-account-decoder = { path = "account-decoder", version = "=2.1.0" } +solana-account-decoder-client-types = { path = "account-decoder-client-types", version = "=2.1.0" } solana-account-info = { path = "sdk/account-info", version = "=2.1.0" } solana-accounts-db = { path = "accounts-db", version = "=2.1.0" } solana-address-lookup-table-program = { path = "programs/address-lookup-table", version = "=2.1.0" } diff --git a/account-decoder-client-types/Cargo.toml b/account-decoder-client-types/Cargo.toml new file mode 100644 index 00000000000000..8f53e2afd1a44c --- /dev/null +++ b/account-decoder-client-types/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "solana-account-decoder-client-types" +description = "Core RPC client types for solana-account-decoder" +documentation = "https://docs.rs/solana-account-decoder-client-types" +version = { workspace = true } +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } + +[dependencies] +base64 = { workspace = true } +bs58 = { workspace = true } +serde = { workspace = true } +serde_derive = { workspace = true } +serde_json = { workspace = true } +solana-account = { workspace = true } +solana-pubkey = { workspace = true } +zstd = { workspace = true, optional = true } + +[features] +zstd = ["dep:zstd"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +all-features = true +rustdoc-args = ["--cfg=docsrs"] diff --git a/account-decoder-client-types/src/lib.rs b/account-decoder-client-types/src/lib.rs new file mode 100644 index 00000000000000..d501f45631830f --- /dev/null +++ b/account-decoder-client-types/src/lib.rs @@ -0,0 +1,99 @@ +//! Core RPC client types for solana-account-decoder +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#[cfg(feature = "zstd")] +use std::io::Read; +use { + base64::{prelude::BASE64_STANDARD, Engine}, + core::str::FromStr, + serde_derive::{Deserialize, Serialize}, + serde_json::Value, + solana_account::WritableAccount, + solana_pubkey::Pubkey, +}; +pub mod token; + +/// A duplicate representation of an Account for pretty JSON serialization +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiAccount { + pub lamports: u64, + pub data: UiAccountData, + pub owner: String, + pub executable: bool, + pub rent_epoch: u64, + pub space: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", untagged)] +pub enum UiAccountData { + LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility + Json(ParsedAccount), + Binary(String, UiAccountEncoding), +} + +impl UiAccountData { + /// Returns decoded account data in binary format if possible + pub fn decode(&self) -> Option> { + match self { + UiAccountData::Json(_) => None, + UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(), + UiAccountData::Binary(blob, encoding) => match encoding { + UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(), + UiAccountEncoding::Base64 => BASE64_STANDARD.decode(blob).ok(), + #[cfg(feature = "zstd")] + UiAccountEncoding::Base64Zstd => { + BASE64_STANDARD.decode(blob).ok().and_then(|zstd_data| { + let mut data = vec![]; + zstd::stream::read::Decoder::new(zstd_data.as_slice()) + .and_then(|mut reader| reader.read_to_end(&mut data)) + .map(|_| data) + .ok() + }) + } + #[cfg(not(feature = "zstd"))] + UiAccountEncoding::Base64Zstd => None, + UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None, + }, + } + } +} + +#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[serde(rename_all = "camelCase")] +pub enum UiAccountEncoding { + Binary, // Legacy. Retained for RPC backwards compatibility + Base58, + Base64, + JsonParsed, + #[serde(rename = "base64+zstd")] + Base64Zstd, +} + +impl UiAccount { + pub fn decode(&self) -> Option { + let data = self.data.decode()?; + Some(T::create( + self.lamports, + data, + Pubkey::from_str(&self.owner).ok()?, + self.executable, + self.rent_epoch, + )) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct ParsedAccount { + pub program: String, + pub parsed: Value, + pub space: u64, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct UiDataSliceConfig { + pub offset: usize, + pub length: usize, +} diff --git a/account-decoder-client-types/src/token.rs b/account-decoder-client-types/src/token.rs new file mode 100644 index 00000000000000..49518b1f3a4765 --- /dev/null +++ b/account-decoder-client-types/src/token.rs @@ -0,0 +1,310 @@ +use { + core::str::FromStr, + serde_derive::{Deserialize, Serialize}, +}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct UiTokenAmount { + pub ui_amount: Option, + pub decimals: u8, + pub amount: String, + pub ui_amount_string: String, +} + +impl UiTokenAmount { + pub fn real_number_string(&self) -> String { + real_number_string( + u64::from_str(&self.amount).unwrap_or_default(), + self.decimals, + ) + } + + pub fn real_number_string_trimmed(&self) -> String { + if !self.ui_amount_string.is_empty() { + self.ui_amount_string.clone() + } else { + real_number_string_trimmed( + u64::from_str(&self.amount).unwrap_or_default(), + self.decimals, + ) + } + } +} + +#[allow(clippy::arithmetic_side_effects)] +pub fn real_number_string(amount: u64, decimals: u8) -> String { + let decimals = decimals as usize; + if decimals > 0 { + // Left-pad zeros to decimals + 1, so we at least have an integer zero + let mut s = format!("{:01$}", amount, decimals + 1); + // Add the decimal point (Sorry, "," locales!) + s.insert(s.len() - decimals, '.'); + s + } else { + amount.to_string() + } +} + +pub fn real_number_string_trimmed(amount: u64, decimals: u8) -> String { + let mut s = real_number_string(amount, decimals); + if decimals > 0 { + let zeros_trimmed = s.trim_end_matches('0'); + s = zeros_trimmed.trim_end_matches('.').to_string(); + } + s +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase", tag = "type", content = "info")] +#[allow(clippy::large_enum_variant)] +pub enum TokenAccountType { + Account(UiTokenAccount), + Mint(UiMint), + Multisig(UiMultisig), +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct UiTokenAccount { + pub mint: String, + pub owner: String, + pub token_amount: UiTokenAmount, + #[serde(skip_serializing_if = "Option::is_none")] + pub delegate: Option, + pub state: UiAccountState, + pub is_native: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub rent_exempt_reserve: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub delegated_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub close_authority: Option, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub extensions: Vec, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum UiAccountState { + Uninitialized, + Initialized, + Frozen, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase", tag = "extension", content = "state")] +pub enum UiExtension { + Uninitialized, + TransferFeeConfig(UiTransferFeeConfig), + TransferFeeAmount(UiTransferFeeAmount), + MintCloseAuthority(UiMintCloseAuthority), + ConfidentialTransferMint(UiConfidentialTransferMint), + ConfidentialTransferAccount(UiConfidentialTransferAccount), + DefaultAccountState(UiDefaultAccountState), + ImmutableOwner, + MemoTransfer(UiMemoTransfer), + NonTransferable, + InterestBearingConfig(UiInterestBearingConfig), + CpiGuard(UiCpiGuard), + PermanentDelegate(UiPermanentDelegate), + NonTransferableAccount, + ConfidentialTransferFeeConfig(UiConfidentialTransferFeeConfig), + ConfidentialTransferFeeAmount(UiConfidentialTransferFeeAmount), + TransferHook(UiTransferHook), + TransferHookAccount(UiTransferHookAccount), + MetadataPointer(UiMetadataPointer), + TokenMetadata(UiTokenMetadata), + GroupPointer(UiGroupPointer), + GroupMemberPointer(UiGroupMemberPointer), + TokenGroup(UiTokenGroup), + TokenGroupMember(UiTokenGroupMember), + UnparseableExtension, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTokenGroupMember { + pub mint: String, + pub group: String, + pub member_number: u32, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTokenGroup { + pub update_authority: Option, + pub mint: String, + pub size: u32, + pub max_size: u32, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiMetadataPointer { + pub authority: Option, + pub metadata_address: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTransferHook { + pub authority: Option, + pub program_id: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTransferHookAccount { + pub transferring: bool, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiConfidentialTransferMint { + pub authority: Option, + pub auto_approve_new_accounts: bool, + pub auditor_elgamal_pubkey: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiConfidentialTransferFeeAmount { + pub withheld_amount: String, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiConfidentialTransferFeeConfig { + pub authority: Option, + pub withdraw_withheld_authority_elgamal_pubkey: Option, + pub harvest_to_mint_enabled: bool, + pub withheld_amount: String, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiPermanentDelegate { + pub delegate: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiCpiGuard { + pub lock_cpi: bool, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiInterestBearingConfig { + pub rate_authority: Option, + pub initialization_timestamp: i64, + pub pre_update_average_rate: i16, + pub last_update_timestamp: i64, + pub current_rate: i16, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiMemoTransfer { + pub require_incoming_transfer_memos: bool, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiDefaultAccountState { + pub account_state: UiAccountState, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiConfidentialTransferAccount { + pub approved: bool, + pub elgamal_pubkey: String, + pub pending_balance_lo: String, + pub pending_balance_hi: String, + pub available_balance: String, + pub decryptable_available_balance: String, + pub allow_confidential_credits: bool, + pub allow_non_confidential_credits: bool, + pub pending_balance_credit_counter: u64, + pub maximum_pending_balance_credit_counter: u64, + pub expected_pending_balance_credit_counter: u64, + pub actual_pending_balance_credit_counter: u64, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiMintCloseAuthority { + pub close_authority: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTransferFeeAmount { + pub withheld_amount: u64, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTransferFeeConfig { + pub transfer_fee_config_authority: Option, + pub withdraw_withheld_authority: Option, + pub withheld_amount: u64, + pub older_transfer_fee: UiTransferFee, + pub newer_transfer_fee: UiTransferFee, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTransferFee { + pub epoch: u64, + pub maximum_fee: u64, + pub transfer_fee_basis_points: u16, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiGroupMemberPointer { + pub authority: Option, + pub member_address: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiGroupPointer { + pub authority: Option, + pub group_address: Option, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiTokenMetadata { + pub update_authority: Option, + pub mint: String, + pub name: String, + pub symbol: String, + pub uri: String, + pub additional_metadata: Vec<(String, String)>, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiMint { + pub mint_authority: Option, + pub supply: String, + pub decimals: u8, + pub is_initialized: bool, + pub freeze_authority: Option, + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub extensions: Vec, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct UiMultisig { + pub num_required_signers: u8, + pub num_valid_signers: u8, + pub is_initialized: bool, + pub signers: Vec, +} diff --git a/account-decoder/Cargo.toml b/account-decoder/Cargo.toml index 71674b67ddd23b..d49d1a69fc3f4f 100644 --- a/account-decoder/Cargo.toml +++ b/account-decoder/Cargo.toml @@ -19,6 +19,7 @@ lazy_static = { workspace = true } serde = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } +solana-account-decoder-client-types = { workspace = true, features = ["zstd"] } solana-config-program = { workspace = true } solana-sdk = { workspace = true } spl-token = { workspace = true, features = ["no-entrypoint"] } diff --git a/account-decoder/src/lib.rs b/account-decoder/src/lib.rs index 2f9e00ce5ea625..9dd11fcbada666 100644 --- a/account-decoder/src/lib.rs +++ b/account-decoder/src/lib.rs @@ -17,161 +17,86 @@ pub mod parse_token_extension; pub mod parse_vote; pub mod validator_info; +pub use solana_account_decoder_client_types::{ + UiAccount, UiAccountData, UiAccountEncoding, UiDataSliceConfig, +}; use { - crate::parse_account_data::{parse_account_data_v2, AccountAdditionalDataV2, ParsedAccount}, + crate::parse_account_data::{parse_account_data_v2, AccountAdditionalDataV2}, base64::{prelude::BASE64_STANDARD, Engine}, - solana_sdk::{ - account::{ReadableAccount, WritableAccount}, - clock::Epoch, - fee_calculator::FeeCalculator, - pubkey::Pubkey, - }, - std::{ - io::{Read, Write}, - str::FromStr, - }, + solana_sdk::{account::ReadableAccount, fee_calculator::FeeCalculator, pubkey::Pubkey}, + std::io::Write, }; pub type StringAmount = String; pub type StringDecimals = String; pub const MAX_BASE58_BYTES: usize = 128; -/// A duplicate representation of an Account for pretty JSON serialization -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiAccount { - pub lamports: u64, - pub data: UiAccountData, - pub owner: String, - pub executable: bool, - pub rent_epoch: Epoch, - pub space: Option, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -#[serde(rename_all = "camelCase", untagged)] -pub enum UiAccountData { - LegacyBinary(String), // Legacy. Retained for RPC backwards compatibility - Json(ParsedAccount), - Binary(String, UiAccountEncoding), -} - -impl UiAccountData { - /// Returns decoded account data in binary format if possible - pub fn decode(&self) -> Option> { - match self { - UiAccountData::Json(_) => None, - UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(), - UiAccountData::Binary(blob, encoding) => match encoding { - UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(), - UiAccountEncoding::Base64 => BASE64_STANDARD.decode(blob).ok(), - UiAccountEncoding::Base64Zstd => { - BASE64_STANDARD.decode(blob).ok().and_then(|zstd_data| { - let mut data = vec![]; - zstd::stream::read::Decoder::new(zstd_data.as_slice()) - .and_then(|mut reader| reader.read_to_end(&mut data)) - .map(|_| data) - .ok() - }) - } - UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None, - }, - } +fn encode_bs58( + account: &T, + data_slice_config: Option, +) -> String { + let slice = slice_data(account.data(), data_slice_config); + if slice.len() <= MAX_BASE58_BYTES { + bs58::encode(slice).into_string() + } else { + "error: data too large for bs58 encoding".to_string() } } -#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)] -#[serde(rename_all = "camelCase")] -pub enum UiAccountEncoding { - Binary, // Legacy. Retained for RPC backwards compatibility - Base58, - Base64, - JsonParsed, - #[serde(rename = "base64+zstd")] - Base64Zstd, -} - -impl UiAccount { - fn encode_bs58( - account: &T, - data_slice_config: Option, - ) -> String { - let slice = slice_data(account.data(), data_slice_config); - if slice.len() <= MAX_BASE58_BYTES { - bs58::encode(slice).into_string() - } else { - "error: data too large for bs58 encoding".to_string() +pub fn encode_ui_account( + pubkey: &Pubkey, + account: &T, + encoding: UiAccountEncoding, + additional_data: Option, + data_slice_config: Option, +) -> UiAccount { + let space = account.data().len(); + let data = match encoding { + UiAccountEncoding::Binary => { + let data = encode_bs58(account, data_slice_config); + UiAccountData::LegacyBinary(data) } - } - - pub fn encode( - pubkey: &Pubkey, - account: &T, - encoding: UiAccountEncoding, - additional_data: Option, - data_slice_config: Option, - ) -> Self { - let space = account.data().len(); - let data = match encoding { - UiAccountEncoding::Binary => { - let data = Self::encode_bs58(account, data_slice_config); - UiAccountData::LegacyBinary(data) - } - UiAccountEncoding::Base58 => { - let data = Self::encode_bs58(account, data_slice_config); - UiAccountData::Binary(data, encoding) - } - UiAccountEncoding::Base64 => UiAccountData::Binary( - BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)), - encoding, - ), - UiAccountEncoding::Base64Zstd => { - let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap(); - match encoder - .write_all(slice_data(account.data(), data_slice_config)) - .and_then(|()| encoder.finish()) - { - Ok(zstd_data) => { - UiAccountData::Binary(BASE64_STANDARD.encode(zstd_data), encoding) - } - Err(_) => UiAccountData::Binary( - BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)), - UiAccountEncoding::Base64, - ), - } + UiAccountEncoding::Base58 => { + let data = encode_bs58(account, data_slice_config); + UiAccountData::Binary(data, encoding) + } + UiAccountEncoding::Base64 => UiAccountData::Binary( + BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)), + encoding, + ), + UiAccountEncoding::Base64Zstd => { + let mut encoder = zstd::stream::write::Encoder::new(Vec::new(), 0).unwrap(); + match encoder + .write_all(slice_data(account.data(), data_slice_config)) + .and_then(|()| encoder.finish()) + { + Ok(zstd_data) => UiAccountData::Binary(BASE64_STANDARD.encode(zstd_data), encoding), + Err(_) => UiAccountData::Binary( + BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)), + UiAccountEncoding::Base64, + ), } - UiAccountEncoding::JsonParsed => { - if let Ok(parsed_data) = - parse_account_data_v2(pubkey, account.owner(), account.data(), additional_data) - { - UiAccountData::Json(parsed_data) - } else { - UiAccountData::Binary( - BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)), - UiAccountEncoding::Base64, - ) - } + } + UiAccountEncoding::JsonParsed => { + if let Ok(parsed_data) = + parse_account_data_v2(pubkey, account.owner(), account.data(), additional_data) + { + UiAccountData::Json(parsed_data) + } else { + UiAccountData::Binary( + BASE64_STANDARD.encode(slice_data(account.data(), data_slice_config)), + UiAccountEncoding::Base64, + ) } - }; - UiAccount { - lamports: account.lamports(), - data, - owner: account.owner().to_string(), - executable: account.executable(), - rent_epoch: account.rent_epoch(), - space: Some(space as u64), } - } - - pub fn decode(&self) -> Option { - let data = self.data.decode()?; - Some(T::create( - self.lamports, - data, - Pubkey::from_str(&self.owner).ok()?, - self.executable, - self.rent_epoch, - )) + }; + UiAccount { + lamports: account.lamports(), + data, + owner: account.owner().to_string(), + executable: account.executable(), + rent_epoch: account.rent_epoch(), + space: Some(space as u64), } } @@ -197,13 +122,6 @@ impl Default for UiFeeCalculator { } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct UiDataSliceConfig { - pub offset: usize, - pub length: usize, -} - fn slice_data(data: &[u8], data_slice_config: Option) -> &[u8] { if let Some(UiDataSliceConfig { offset, length }) = data_slice_config { if offset >= data.len() { @@ -264,13 +182,13 @@ mod test { // Whole account assert_eq!( - UiAccount::encode_bs58(&account, None), + encode_bs58(&account, None), "error: data too large for bs58 encoding" ); // Slice of account that's still too large assert_eq!( - UiAccount::encode_bs58( + encode_bs58( &account, Some(UiDataSliceConfig { length: MAX_BASE58_BYTES + 1, @@ -282,7 +200,7 @@ mod test { // Slice of account that fits inside `MAX_BASE58_BYTES` assert_ne!( - UiAccount::encode_bs58( + encode_bs58( &account, Some(UiDataSliceConfig { length: MAX_BASE58_BYTES, @@ -294,7 +212,7 @@ mod test { // Slice of account that's too large, but whose intersection with the account still fits assert_ne!( - UiAccount::encode_bs58( + encode_bs58( &account, Some(UiDataSliceConfig { length: MAX_BASE58_BYTES + 1, @@ -307,7 +225,7 @@ mod test { #[test] fn test_base64_zstd() { - let encoded_account = UiAccount::encode( + let encoded_account = encode_ui_account( &Pubkey::default(), &AccountSharedData::from(Account { data: vec![0; 1024], diff --git a/account-decoder/src/parse_account_data.rs b/account-decoder/src/parse_account_data.rs index 68cbfeedb54302..cd13a50e94f07f 100644 --- a/account-decoder/src/parse_account_data.rs +++ b/account-decoder/src/parse_account_data.rs @@ -1,3 +1,4 @@ +pub use solana_account_decoder_client_types::ParsedAccount; use { crate::{ parse_address_lookup_table::parse_address_lookup_table, @@ -6,7 +7,6 @@ use { parse_token::parse_token_v2, parse_vote::parse_vote, }, inflector::Inflector, - serde_json::Value, solana_sdk::{ address_lookup_table, clock::UnixTimestamp, instruction::InstructionError, pubkey::Pubkey, stake, system_program, sysvar, vote, @@ -63,14 +63,6 @@ pub enum ParseAccountError { SerdeJsonError(#[from] serde_json::error::Error), } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct ParsedAccount { - pub program: String, - pub parsed: Value, - pub space: u64, -} - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum ParsableAccount { diff --git a/account-decoder/src/parse_token.rs b/account-decoder/src/parse_token.rs index 878d738fe03367..ef08667c40b4fd 100644 --- a/account-decoder/src/parse_token.rs +++ b/account-decoder/src/parse_token.rs @@ -1,8 +1,11 @@ +pub use solana_account_decoder_client_types::token::{ + real_number_string, real_number_string_trimmed, TokenAccountType, UiAccountState, UiMint, + UiMultisig, UiTokenAccount, UiTokenAmount, +}; use { crate::{ parse_account_data::{ParsableAccount, ParseAccountError, SplTokenAdditionalData}, - parse_token_extension::{parse_extension, UiExtension}, - StringAmount, StringDecimals, + parse_token_extension::parse_extension, }, solana_sdk::pubkey::Pubkey, spl_token_2022::{ @@ -58,7 +61,7 @@ pub fn parse_token_v2( COption::Some(pubkey) => Some(pubkey.to_string()), COption::None => None, }, - state: account.base.state.into(), + state: convert_account_state(account.base.state), is_native: account.base.is_native(), rent_exempt_reserve: match account.base.is_native { COption::Some(reserve) => { @@ -128,101 +131,11 @@ pub fn parse_token_v2( } } -#[derive(Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase", tag = "type", content = "info")] -#[allow(clippy::large_enum_variant)] -pub enum TokenAccountType { - Account(UiTokenAccount), - Mint(UiMint), - Multisig(UiMultisig), -} - -#[derive(Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct UiTokenAccount { - pub mint: String, - pub owner: String, - pub token_amount: UiTokenAmount, - #[serde(skip_serializing_if = "Option::is_none")] - pub delegate: Option, - pub state: UiAccountState, - pub is_native: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub rent_exempt_reserve: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub delegated_amount: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub close_authority: Option, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub extensions: Vec, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub enum UiAccountState { - Uninitialized, - Initialized, - Frozen, -} - -impl From for UiAccountState { - fn from(state: AccountState) -> Self { - match state { - AccountState::Uninitialized => UiAccountState::Uninitialized, - AccountState::Initialized => UiAccountState::Initialized, - AccountState::Frozen => UiAccountState::Frozen, - } - } -} - -pub fn real_number_string(amount: u64, decimals: u8) -> StringDecimals { - let decimals = decimals as usize; - if decimals > 0 { - // Left-pad zeros to decimals + 1, so we at least have an integer zero - let mut s = format!("{:01$}", amount, decimals + 1); - // Add the decimal point (Sorry, "," locales!) - s.insert(s.len() - decimals, '.'); - s - } else { - amount.to_string() - } -} - -pub fn real_number_string_trimmed(amount: u64, decimals: u8) -> StringDecimals { - let mut s = real_number_string(amount, decimals); - if decimals > 0 { - let zeros_trimmed = s.trim_end_matches('0'); - s = zeros_trimmed.trim_end_matches('.').to_string(); - } - s -} - -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct UiTokenAmount { - pub ui_amount: Option, - pub decimals: u8, - pub amount: StringAmount, - pub ui_amount_string: StringDecimals, -} - -impl UiTokenAmount { - pub fn real_number_string(&self) -> String { - real_number_string( - u64::from_str(&self.amount).unwrap_or_default(), - self.decimals, - ) - } - - pub fn real_number_string_trimmed(&self) -> String { - if !self.ui_amount_string.is_empty() { - self.ui_amount_string.clone() - } else { - real_number_string_trimmed( - u64::from_str(&self.amount).unwrap_or_default(), - self.decimals, - ) - } +pub fn convert_account_state(state: AccountState) -> UiAccountState { + match state { + AccountState::Uninitialized => UiAccountState::Uninitialized, + AccountState::Initialized => UiAccountState::Initialized, + AccountState::Frozen => UiAccountState::Frozen, } } @@ -261,27 +174,6 @@ pub fn token_amount_to_ui_amount_v2( } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiMint { - pub mint_authority: Option, - pub supply: StringAmount, - pub decimals: u8, - pub is_initialized: bool, - pub freeze_authority: Option, - #[serde(skip_serializing_if = "Vec::is_empty", default)] - pub extensions: Vec, -} - -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiMultisig { - pub num_required_signers: u8, - pub num_valid_signers: u8, - pub is_initialized: bool, - pub signers: Vec, -} - pub fn get_token_account_mint(data: &[u8]) -> Option { Account::valid_account_data(data) .then(|| Pubkey::try_from(data.get(..32)?).ok()) @@ -293,6 +185,7 @@ mod test { use { super::*, crate::parse_token_extension::{UiMemoTransfer, UiMintCloseAuthority}, + solana_account_decoder_client_types::token::UiExtension, spl_pod::optional_keys::OptionalNonZeroPubkey, spl_token_2022::extension::{ immutable_owner::ImmutableOwner, interest_bearing_mint::InterestBearingConfig, diff --git a/account-decoder/src/parse_token_extension.rs b/account-decoder/src/parse_token_extension.rs index 76700a44018498..8ab6644980c4e3 100644 --- a/account-decoder/src/parse_token_extension.rs +++ b/account-decoder/src/parse_token_extension.rs @@ -1,5 +1,13 @@ +pub use solana_account_decoder_client_types::token::{ + UiConfidentialTransferAccount, UiConfidentialTransferFeeAmount, + UiConfidentialTransferFeeConfig, UiConfidentialTransferMint, UiCpiGuard, UiDefaultAccountState, + UiExtension, UiGroupMemberPointer, UiGroupPointer, UiInterestBearingConfig, UiMemoTransfer, + UiMetadataPointer, UiMintCloseAuthority, UiPermanentDelegate, UiTokenGroup, UiTokenGroupMember, + UiTokenMetadata, UiTransferFee, UiTransferFeeAmount, UiTransferFeeConfig, UiTransferHook, + UiTransferHookAccount, +}; use { - crate::parse_token::UiAccountState, + crate::parse_token::convert_account_state, solana_sdk::{clock::UnixTimestamp, program_pack::Pack}, spl_token_2022::{ extension::{self, BaseState, BaseStateWithExtensions, ExtensionType, StateWithExtensions}, @@ -10,36 +18,6 @@ use { spl_token_metadata_interface::state::TokenMetadata, }; -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase", tag = "extension", content = "state")] -pub enum UiExtension { - Uninitialized, - TransferFeeConfig(UiTransferFeeConfig), - TransferFeeAmount(UiTransferFeeAmount), - MintCloseAuthority(UiMintCloseAuthority), - ConfidentialTransferMint(UiConfidentialTransferMint), - ConfidentialTransferAccount(UiConfidentialTransferAccount), - DefaultAccountState(UiDefaultAccountState), - ImmutableOwner, - MemoTransfer(UiMemoTransfer), - NonTransferable, - InterestBearingConfig(UiInterestBearingConfig), - CpiGuard(UiCpiGuard), - PermanentDelegate(UiPermanentDelegate), - NonTransferableAccount, - ConfidentialTransferFeeConfig(UiConfidentialTransferFeeConfig), - ConfidentialTransferFeeAmount(UiConfidentialTransferFeeAmount), - TransferHook(UiTransferHook), - TransferHookAccount(UiTransferHookAccount), - MetadataPointer(UiMetadataPointer), - TokenMetadata(UiTokenMetadata), - GroupPointer(UiGroupPointer), - GroupMemberPointer(UiGroupMemberPointer), - TokenGroup(UiTokenGroup), - TokenGroupMember(UiTokenGroupMember), - UnparseableExtension, -} - pub fn parse_extension( extension_type: &ExtensionType, account: &StateWithExtensions, @@ -48,532 +26,356 @@ pub fn parse_extension( ExtensionType::Uninitialized => UiExtension::Uninitialized, ExtensionType::TransferFeeConfig => account .get_extension::() - .map(|&extension| UiExtension::TransferFeeConfig(extension.into())) + .map(|&extension| { + UiExtension::TransferFeeConfig(convert_transfer_fee_config(extension)) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::TransferFeeAmount => account .get_extension::() - .map(|&extension| UiExtension::TransferFeeAmount(extension.into())) + .map(|&extension| { + UiExtension::TransferFeeAmount(convert_transfer_fee_amount(extension)) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::MintCloseAuthority => account .get_extension::() - .map(|&extension| UiExtension::MintCloseAuthority(extension.into())) + .map(|&extension| { + UiExtension::MintCloseAuthority(convert_mint_close_authority(extension)) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::ConfidentialTransferMint => account .get_extension::() - .map(|&extension| UiExtension::ConfidentialTransferMint(extension.into())) + .map(|&extension| { + UiExtension::ConfidentialTransferMint(convert_confidential_transfer_mint(extension)) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::ConfidentialTransferFeeConfig => account .get_extension::() - .map(|&extension| UiExtension::ConfidentialTransferFeeConfig(extension.into())) + .map(|&extension| { + UiExtension::ConfidentialTransferFeeConfig( + convert_confidential_transfer_fee_config(extension), + ) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::ConfidentialTransferAccount => account .get_extension::() - .map(|&extension| UiExtension::ConfidentialTransferAccount(extension.into())) + .map(|&extension| { + UiExtension::ConfidentialTransferAccount(convert_confidential_transfer_account( + extension, + )) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::ConfidentialTransferFeeAmount => account .get_extension::() - .map(|&extension| UiExtension::ConfidentialTransferFeeAmount(extension.into())) + .map(|&extension| { + UiExtension::ConfidentialTransferFeeAmount( + convert_confidential_transfer_fee_amount(extension), + ) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::DefaultAccountState => account .get_extension::() - .map(|&extension| UiExtension::DefaultAccountState(extension.into())) + .map(|&extension| { + UiExtension::DefaultAccountState(convert_default_account_state(extension)) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::ImmutableOwner => UiExtension::ImmutableOwner, ExtensionType::MemoTransfer => account .get_extension::() - .map(|&extension| UiExtension::MemoTransfer(extension.into())) + .map(|&extension| UiExtension::MemoTransfer(convert_memo_transfer(extension))) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::NonTransferable => UiExtension::NonTransferable, ExtensionType::InterestBearingConfig => account .get_extension::() - .map(|&extension| UiExtension::InterestBearingConfig(extension.into())) + .map(|&extension| { + UiExtension::InterestBearingConfig(convert_interest_bearing_config(extension)) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::CpiGuard => account .get_extension::() - .map(|&extension| UiExtension::CpiGuard(extension.into())) + .map(|&extension| UiExtension::CpiGuard(convert_cpi_guard(extension))) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::PermanentDelegate => account .get_extension::() - .map(|&extension| UiExtension::PermanentDelegate(extension.into())) + .map(|&extension| UiExtension::PermanentDelegate(convert_permanent_delegate(extension))) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::NonTransferableAccount => UiExtension::NonTransferableAccount, ExtensionType::MetadataPointer => account .get_extension::() - .map(|&extension| UiExtension::MetadataPointer(extension.into())) + .map(|&extension| UiExtension::MetadataPointer(convert_metadata_pointer(extension))) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::TokenMetadata => account .get_variable_len_extension::() - .map(|extension| UiExtension::TokenMetadata(extension.into())) + .map(|extension| UiExtension::TokenMetadata(convert_token_metadata(extension))) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::TransferHook => account .get_extension::() - .map(|&extension| UiExtension::TransferHook(extension.into())) + .map(|&extension| UiExtension::TransferHook(convert_transfer_hook(extension))) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::TransferHookAccount => account .get_extension::() - .map(|&extension| UiExtension::TransferHookAccount(extension.into())) + .map(|&extension| { + UiExtension::TransferHookAccount(convert_transfer_hook_account(extension)) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::GroupPointer => account .get_extension::() - .map(|&extension| UiExtension::GroupPointer(extension.into())) + .map(|&extension| UiExtension::GroupPointer(convert_group_pointer(extension))) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::GroupMemberPointer => account .get_extension::() - .map(|&extension| UiExtension::GroupMemberPointer(extension.into())) + .map(|&extension| { + UiExtension::GroupMemberPointer(convert_group_member_pointer(extension)) + }) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::TokenGroup => account .get_extension::() - .map(|&extension| UiExtension::TokenGroup(extension.into())) + .map(|&extension| UiExtension::TokenGroup(convert_token_group(extension))) .unwrap_or(UiExtension::UnparseableExtension), ExtensionType::TokenGroupMember => account .get_extension::() - .map(|&extension| UiExtension::TokenGroupMember(extension.into())) + .map(|&extension| UiExtension::TokenGroupMember(convert_token_group_member(extension))) .unwrap_or(UiExtension::UnparseableExtension), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiTransferFee { - pub epoch: u64, - pub maximum_fee: u64, - pub transfer_fee_basis_points: u16, -} - -impl From for UiTransferFee { - fn from(transfer_fee: extension::transfer_fee::TransferFee) -> Self { - Self { - epoch: u64::from(transfer_fee.epoch), - maximum_fee: u64::from(transfer_fee.maximum_fee), - transfer_fee_basis_points: u16::from(transfer_fee.transfer_fee_basis_points), - } +fn convert_transfer_fee(transfer_fee: extension::transfer_fee::TransferFee) -> UiTransferFee { + UiTransferFee { + epoch: u64::from(transfer_fee.epoch), + maximum_fee: u64::from(transfer_fee.maximum_fee), + transfer_fee_basis_points: u16::from(transfer_fee.transfer_fee_basis_points), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiTransferFeeConfig { - pub transfer_fee_config_authority: Option, - pub withdraw_withheld_authority: Option, - pub withheld_amount: u64, - pub older_transfer_fee: UiTransferFee, - pub newer_transfer_fee: UiTransferFee, -} - -impl From for UiTransferFeeConfig { - fn from(transfer_fee_config: extension::transfer_fee::TransferFeeConfig) -> Self { - let transfer_fee_config_authority: Option = - transfer_fee_config.transfer_fee_config_authority.into(); - let withdraw_withheld_authority: Option = - transfer_fee_config.withdraw_withheld_authority.into(); - - Self { - transfer_fee_config_authority: transfer_fee_config_authority - .map(|pubkey| pubkey.to_string()), - withdraw_withheld_authority: withdraw_withheld_authority - .map(|pubkey| pubkey.to_string()), - withheld_amount: u64::from(transfer_fee_config.withheld_amount), - older_transfer_fee: transfer_fee_config.older_transfer_fee.into(), - newer_transfer_fee: transfer_fee_config.newer_transfer_fee.into(), - } +fn convert_transfer_fee_config( + transfer_fee_config: extension::transfer_fee::TransferFeeConfig, +) -> UiTransferFeeConfig { + let transfer_fee_config_authority: Option = + transfer_fee_config.transfer_fee_config_authority.into(); + let withdraw_withheld_authority: Option = + transfer_fee_config.withdraw_withheld_authority.into(); + + UiTransferFeeConfig { + transfer_fee_config_authority: transfer_fee_config_authority + .map(|pubkey| pubkey.to_string()), + withdraw_withheld_authority: withdraw_withheld_authority.map(|pubkey| pubkey.to_string()), + withheld_amount: u64::from(transfer_fee_config.withheld_amount), + older_transfer_fee: convert_transfer_fee(transfer_fee_config.older_transfer_fee), + newer_transfer_fee: convert_transfer_fee(transfer_fee_config.newer_transfer_fee), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiTransferFeeAmount { - pub withheld_amount: u64, -} - -impl From for UiTransferFeeAmount { - fn from(transfer_fee_amount: extension::transfer_fee::TransferFeeAmount) -> Self { - Self { - withheld_amount: u64::from(transfer_fee_amount.withheld_amount), - } +fn convert_transfer_fee_amount( + transfer_fee_amount: extension::transfer_fee::TransferFeeAmount, +) -> UiTransferFeeAmount { + UiTransferFeeAmount { + withheld_amount: u64::from(transfer_fee_amount.withheld_amount), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiMintCloseAuthority { - pub close_authority: Option, -} - -impl From for UiMintCloseAuthority { - fn from(mint_close_authority: extension::mint_close_authority::MintCloseAuthority) -> Self { - let authority: Option = mint_close_authority.close_authority.into(); - Self { - close_authority: authority.map(|pubkey| pubkey.to_string()), - } +fn convert_mint_close_authority( + mint_close_authority: extension::mint_close_authority::MintCloseAuthority, +) -> UiMintCloseAuthority { + let authority: Option = mint_close_authority.close_authority.into(); + UiMintCloseAuthority { + close_authority: authority.map(|pubkey| pubkey.to_string()), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiDefaultAccountState { - pub account_state: UiAccountState, -} - -impl From for UiDefaultAccountState { - fn from(default_account_state: extension::default_account_state::DefaultAccountState) -> Self { - let account_state = - spl_token_2022::state::AccountState::try_from(default_account_state.state) - .unwrap_or_default(); - Self { - account_state: account_state.into(), - } +fn convert_default_account_state( + default_account_state: extension::default_account_state::DefaultAccountState, +) -> UiDefaultAccountState { + let account_state = spl_token_2022::state::AccountState::try_from(default_account_state.state) + .unwrap_or_default(); + UiDefaultAccountState { + account_state: convert_account_state(account_state), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiMemoTransfer { - pub require_incoming_transfer_memos: bool, -} - -impl From for UiMemoTransfer { - fn from(memo_transfer: extension::memo_transfer::MemoTransfer) -> Self { - Self { - require_incoming_transfer_memos: memo_transfer.require_incoming_transfer_memos.into(), - } +fn convert_memo_transfer(memo_transfer: extension::memo_transfer::MemoTransfer) -> UiMemoTransfer { + UiMemoTransfer { + require_incoming_transfer_memos: memo_transfer.require_incoming_transfer_memos.into(), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiInterestBearingConfig { - pub rate_authority: Option, - pub initialization_timestamp: UnixTimestamp, - pub pre_update_average_rate: i16, - pub last_update_timestamp: UnixTimestamp, - pub current_rate: i16, -} - -impl From for UiInterestBearingConfig { - fn from( - interest_bearing_config: extension::interest_bearing_mint::InterestBearingConfig, - ) -> Self { - let rate_authority: Option = interest_bearing_config.rate_authority.into(); +fn convert_interest_bearing_config( + interest_bearing_config: extension::interest_bearing_mint::InterestBearingConfig, +) -> UiInterestBearingConfig { + let rate_authority: Option = interest_bearing_config.rate_authority.into(); - Self { - rate_authority: rate_authority.map(|pubkey| pubkey.to_string()), - initialization_timestamp: UnixTimestamp::from( - interest_bearing_config.initialization_timestamp, - ), - pre_update_average_rate: i16::from(interest_bearing_config.pre_update_average_rate), - last_update_timestamp: UnixTimestamp::from( - interest_bearing_config.last_update_timestamp, - ), - current_rate: i16::from(interest_bearing_config.current_rate), - } + UiInterestBearingConfig { + rate_authority: rate_authority.map(|pubkey| pubkey.to_string()), + initialization_timestamp: UnixTimestamp::from( + interest_bearing_config.initialization_timestamp, + ), + pre_update_average_rate: i16::from(interest_bearing_config.pre_update_average_rate), + last_update_timestamp: UnixTimestamp::from(interest_bearing_config.last_update_timestamp), + current_rate: i16::from(interest_bearing_config.current_rate), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiCpiGuard { - pub lock_cpi: bool, -} - -impl From for UiCpiGuard { - fn from(cpi_guard: extension::cpi_guard::CpiGuard) -> Self { - Self { - lock_cpi: cpi_guard.lock_cpi.into(), - } +fn convert_cpi_guard(cpi_guard: extension::cpi_guard::CpiGuard) -> UiCpiGuard { + UiCpiGuard { + lock_cpi: cpi_guard.lock_cpi.into(), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiPermanentDelegate { - pub delegate: Option, -} - -impl From for UiPermanentDelegate { - fn from(permanent_delegate: extension::permanent_delegate::PermanentDelegate) -> Self { - let delegate: Option = permanent_delegate.delegate.into(); - Self { - delegate: delegate.map(|pubkey| pubkey.to_string()), - } +fn convert_permanent_delegate( + permanent_delegate: extension::permanent_delegate::PermanentDelegate, +) -> UiPermanentDelegate { + let delegate: Option = permanent_delegate.delegate.into(); + UiPermanentDelegate { + delegate: delegate.map(|pubkey| pubkey.to_string()), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiConfidentialTransferMint { - pub authority: Option, - pub auto_approve_new_accounts: bool, - pub auditor_elgamal_pubkey: Option, -} - -impl From - for UiConfidentialTransferMint -{ - fn from( - confidential_transfer_mint: extension::confidential_transfer::ConfidentialTransferMint, - ) -> Self { - let authority: Option = confidential_transfer_mint.authority.into(); - let auditor_elgamal_pubkey: Option = - confidential_transfer_mint.auditor_elgamal_pubkey.into(); - Self { - authority: authority.map(|pubkey| pubkey.to_string()), - auto_approve_new_accounts: confidential_transfer_mint.auto_approve_new_accounts.into(), - auditor_elgamal_pubkey: auditor_elgamal_pubkey.map(|pubkey| pubkey.to_string()), - } +pub fn convert_confidential_transfer_mint( + confidential_transfer_mint: extension::confidential_transfer::ConfidentialTransferMint, +) -> UiConfidentialTransferMint { + let authority: Option = confidential_transfer_mint.authority.into(); + let auditor_elgamal_pubkey: Option = + confidential_transfer_mint.auditor_elgamal_pubkey.into(); + UiConfidentialTransferMint { + authority: authority.map(|pubkey| pubkey.to_string()), + auto_approve_new_accounts: confidential_transfer_mint.auto_approve_new_accounts.into(), + auditor_elgamal_pubkey: auditor_elgamal_pubkey.map(|pubkey| pubkey.to_string()), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiConfidentialTransferFeeConfig { - pub authority: Option, - pub withdraw_withheld_authority_elgamal_pubkey: Option, - pub harvest_to_mint_enabled: bool, - pub withheld_amount: String, -} - -impl From - for UiConfidentialTransferFeeConfig -{ - fn from( - confidential_transfer_fee_config: extension::confidential_transfer_fee::ConfidentialTransferFeeConfig, - ) -> Self { - let authority: Option = confidential_transfer_fee_config.authority.into(); - let withdraw_withheld_authority_elgamal_pubkey: Option = - confidential_transfer_fee_config - .withdraw_withheld_authority_elgamal_pubkey - .into(); - Self { - authority: authority.map(|pubkey| pubkey.to_string()), - withdraw_withheld_authority_elgamal_pubkey: withdraw_withheld_authority_elgamal_pubkey - .map(|pubkey| pubkey.to_string()), - harvest_to_mint_enabled: confidential_transfer_fee_config - .harvest_to_mint_enabled - .into(), - withheld_amount: format!("{}", confidential_transfer_fee_config.withheld_amount), - } +pub fn convert_confidential_transfer_fee_config( + confidential_transfer_fee_config: extension::confidential_transfer_fee::ConfidentialTransferFeeConfig, +) -> UiConfidentialTransferFeeConfig { + let authority: Option = confidential_transfer_fee_config.authority.into(); + let withdraw_withheld_authority_elgamal_pubkey: Option = + confidential_transfer_fee_config + .withdraw_withheld_authority_elgamal_pubkey + .into(); + UiConfidentialTransferFeeConfig { + authority: authority.map(|pubkey| pubkey.to_string()), + withdraw_withheld_authority_elgamal_pubkey: withdraw_withheld_authority_elgamal_pubkey + .map(|pubkey| pubkey.to_string()), + harvest_to_mint_enabled: confidential_transfer_fee_config + .harvest_to_mint_enabled + .into(), + withheld_amount: format!("{}", confidential_transfer_fee_config.withheld_amount), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiConfidentialTransferAccount { - pub approved: bool, - pub elgamal_pubkey: String, - pub pending_balance_lo: String, - pub pending_balance_hi: String, - pub available_balance: String, - pub decryptable_available_balance: String, - pub allow_confidential_credits: bool, - pub allow_non_confidential_credits: bool, - pub pending_balance_credit_counter: u64, - pub maximum_pending_balance_credit_counter: u64, - pub expected_pending_balance_credit_counter: u64, - pub actual_pending_balance_credit_counter: u64, -} - -impl From - for UiConfidentialTransferAccount -{ - fn from( - confidential_transfer_account: extension::confidential_transfer::ConfidentialTransferAccount, - ) -> Self { - Self { - approved: confidential_transfer_account.approved.into(), - elgamal_pubkey: format!("{}", confidential_transfer_account.elgamal_pubkey), - pending_balance_lo: format!("{}", confidential_transfer_account.pending_balance_lo), - pending_balance_hi: format!("{}", confidential_transfer_account.pending_balance_hi), - available_balance: format!("{}", confidential_transfer_account.available_balance), - decryptable_available_balance: format!( - "{}", - confidential_transfer_account.decryptable_available_balance - ), - allow_confidential_credits: confidential_transfer_account - .allow_confidential_credits - .into(), - allow_non_confidential_credits: confidential_transfer_account - .allow_non_confidential_credits - .into(), - pending_balance_credit_counter: confidential_transfer_account - .pending_balance_credit_counter - .into(), - maximum_pending_balance_credit_counter: confidential_transfer_account - .maximum_pending_balance_credit_counter - .into(), - expected_pending_balance_credit_counter: confidential_transfer_account - .expected_pending_balance_credit_counter - .into(), - actual_pending_balance_credit_counter: confidential_transfer_account - .actual_pending_balance_credit_counter - .into(), - } +fn convert_confidential_transfer_account( + confidential_transfer_account: extension::confidential_transfer::ConfidentialTransferAccount, +) -> UiConfidentialTransferAccount { + UiConfidentialTransferAccount { + approved: confidential_transfer_account.approved.into(), + elgamal_pubkey: format!("{}", confidential_transfer_account.elgamal_pubkey), + pending_balance_lo: format!("{}", confidential_transfer_account.pending_balance_lo), + pending_balance_hi: format!("{}", confidential_transfer_account.pending_balance_hi), + available_balance: format!("{}", confidential_transfer_account.available_balance), + decryptable_available_balance: format!( + "{}", + confidential_transfer_account.decryptable_available_balance + ), + allow_confidential_credits: confidential_transfer_account + .allow_confidential_credits + .into(), + allow_non_confidential_credits: confidential_transfer_account + .allow_non_confidential_credits + .into(), + pending_balance_credit_counter: confidential_transfer_account + .pending_balance_credit_counter + .into(), + maximum_pending_balance_credit_counter: confidential_transfer_account + .maximum_pending_balance_credit_counter + .into(), + expected_pending_balance_credit_counter: confidential_transfer_account + .expected_pending_balance_credit_counter + .into(), + actual_pending_balance_credit_counter: confidential_transfer_account + .actual_pending_balance_credit_counter + .into(), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiConfidentialTransferFeeAmount { - pub withheld_amount: String, -} - -impl From - for UiConfidentialTransferFeeAmount -{ - fn from( - confidential_transfer_fee_amount: extension::confidential_transfer_fee::ConfidentialTransferFeeAmount, - ) -> Self { - Self { - withheld_amount: format!("{}", confidential_transfer_fee_amount.withheld_amount), - } +fn convert_confidential_transfer_fee_amount( + confidential_transfer_fee_amount: extension::confidential_transfer_fee::ConfidentialTransferFeeAmount, +) -> UiConfidentialTransferFeeAmount { + UiConfidentialTransferFeeAmount { + withheld_amount: format!("{}", confidential_transfer_fee_amount.withheld_amount), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiMetadataPointer { - pub authority: Option, - pub metadata_address: Option, -} - -impl From for UiMetadataPointer { - fn from(metadata_pointer: extension::metadata_pointer::MetadataPointer) -> Self { - let authority: Option = metadata_pointer.authority.into(); - let metadata_address: Option = metadata_pointer.metadata_address.into(); - Self { - authority: authority.map(|pubkey| pubkey.to_string()), - metadata_address: metadata_address.map(|pubkey| pubkey.to_string()), - } +fn convert_metadata_pointer( + metadata_pointer: extension::metadata_pointer::MetadataPointer, +) -> UiMetadataPointer { + let authority: Option = metadata_pointer.authority.into(); + let metadata_address: Option = metadata_pointer.metadata_address.into(); + UiMetadataPointer { + authority: authority.map(|pubkey| pubkey.to_string()), + metadata_address: metadata_address.map(|pubkey| pubkey.to_string()), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiTokenMetadata { - pub update_authority: Option, - pub mint: String, - pub name: String, - pub symbol: String, - pub uri: String, - pub additional_metadata: Vec<(String, String)>, -} - -impl From for UiTokenMetadata { - fn from(token_metadata: TokenMetadata) -> Self { - let update_authority: Option = token_metadata.update_authority.into(); - Self { - update_authority: update_authority.map(|pubkey| pubkey.to_string()), - mint: token_metadata.mint.to_string(), - name: token_metadata.name, - symbol: token_metadata.symbol, - uri: token_metadata.uri, - additional_metadata: token_metadata.additional_metadata, - } +fn convert_token_metadata(token_metadata: TokenMetadata) -> UiTokenMetadata { + let update_authority: Option = token_metadata.update_authority.into(); + UiTokenMetadata { + update_authority: update_authority.map(|pubkey| pubkey.to_string()), + mint: token_metadata.mint.to_string(), + name: token_metadata.name, + symbol: token_metadata.symbol, + uri: token_metadata.uri, + additional_metadata: token_metadata.additional_metadata, } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiTransferHook { - pub authority: Option, - pub program_id: Option, -} - -impl From for UiTransferHook { - fn from(transfer_hook: extension::transfer_hook::TransferHook) -> Self { - let authority: Option = transfer_hook.authority.into(); - let program_id: Option = transfer_hook.program_id.into(); - Self { - authority: authority.map(|pubkey| pubkey.to_string()), - program_id: program_id.map(|pubkey| pubkey.to_string()), - } +fn convert_transfer_hook(transfer_hook: extension::transfer_hook::TransferHook) -> UiTransferHook { + let authority: Option = transfer_hook.authority.into(); + let program_id: Option = transfer_hook.program_id.into(); + UiTransferHook { + authority: authority.map(|pubkey| pubkey.to_string()), + program_id: program_id.map(|pubkey| pubkey.to_string()), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiTransferHookAccount { - pub transferring: bool, -} - -impl From for UiTransferHookAccount { - fn from(transfer_hook: extension::transfer_hook::TransferHookAccount) -> Self { - Self { - transferring: transfer_hook.transferring.into(), - } +fn convert_transfer_hook_account( + transfer_hook: extension::transfer_hook::TransferHookAccount, +) -> UiTransferHookAccount { + UiTransferHookAccount { + transferring: transfer_hook.transferring.into(), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiGroupPointer { - pub authority: Option, - pub group_address: Option, -} - -impl From for UiGroupPointer { - fn from(group_pointer: extension::group_pointer::GroupPointer) -> Self { - let authority: Option = group_pointer.authority.into(); - let group_address: Option = group_pointer.group_address.into(); - Self { - authority: authority.map(|pubkey| pubkey.to_string()), - group_address: group_address.map(|pubkey| pubkey.to_string()), - } +fn convert_group_pointer(group_pointer: extension::group_pointer::GroupPointer) -> UiGroupPointer { + let authority: Option = group_pointer.authority.into(); + let group_address: Option = group_pointer.group_address.into(); + UiGroupPointer { + authority: authority.map(|pubkey| pubkey.to_string()), + group_address: group_address.map(|pubkey| pubkey.to_string()), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiGroupMemberPointer { - pub authority: Option, - pub member_address: Option, -} - -impl From for UiGroupMemberPointer { - fn from(member_pointer: extension::group_member_pointer::GroupMemberPointer) -> Self { - let authority: Option = member_pointer.authority.into(); - let member_address: Option = member_pointer.member_address.into(); - Self { - authority: authority.map(|pubkey| pubkey.to_string()), - member_address: member_address.map(|pubkey| pubkey.to_string()), - } +fn convert_group_member_pointer( + member_pointer: extension::group_member_pointer::GroupMemberPointer, +) -> UiGroupMemberPointer { + let authority: Option = member_pointer.authority.into(); + let member_address: Option = member_pointer.member_address.into(); + UiGroupMemberPointer { + authority: authority.map(|pubkey| pubkey.to_string()), + member_address: member_address.map(|pubkey| pubkey.to_string()), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiTokenGroup { - pub update_authority: Option, - pub mint: String, - pub size: u32, - pub max_size: u32, -} - -impl From for UiTokenGroup { - fn from(token_group: TokenGroup) -> Self { - let update_authority: Option = token_group.update_authority.into(); - Self { - update_authority: update_authority.map(|pubkey| pubkey.to_string()), - mint: token_group.mint.to_string(), - size: token_group.size.into(), - max_size: token_group.max_size.into(), - } +fn convert_token_group(token_group: TokenGroup) -> UiTokenGroup { + let update_authority: Option = token_group.update_authority.into(); + UiTokenGroup { + update_authority: update_authority.map(|pubkey| pubkey.to_string()), + mint: token_group.mint.to_string(), + size: token_group.size.into(), + max_size: token_group.max_size.into(), } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] -#[serde(rename_all = "camelCase")] -pub struct UiTokenGroupMember { - pub mint: String, - pub group: String, - pub member_number: u32, -} - -impl From for UiTokenGroupMember { - fn from(member: TokenGroupMember) -> Self { - Self { - mint: member.mint.to_string(), - group: member.group.to_string(), - member_number: member.member_number.into(), - } +fn convert_token_group_member(member: TokenGroupMember) -> UiTokenGroupMember { + UiTokenGroupMember { + mint: member.mint.to_string(), + group: member.group.to_string(), + member_number: member.member_number.into(), } } diff --git a/cli-output/src/cli_output.rs b/cli-output/src/cli_output.rs index 1474c758fd8c8e..4930e141543294 100644 --- a/cli-output/src/cli_output.rs +++ b/cli-output/src/cli_output.rs @@ -17,8 +17,8 @@ use { serde::{Deserialize, Serialize}, serde_json::{Map, Value}, solana_account_decoder::{ - parse_account_data::AccountAdditionalDataV2, parse_token::UiTokenAccount, UiAccount, - UiAccountEncoding, UiDataSliceConfig, + encode_ui_account, parse_account_data::AccountAdditionalDataV2, + parse_token::UiTokenAccount, UiAccountEncoding, UiDataSliceConfig, }, solana_clap_utils::keypair::SignOnly, solana_rpc_client_api::response::{ @@ -201,7 +201,7 @@ impl CliAccount { Self { keyed_account: RpcKeyedAccount { pubkey: address.to_string(), - account: UiAccount::encode( + account: encode_ui_account( address, account, data_encoding, diff --git a/ledger-tool/src/output.rs b/ledger-tool/src/output.rs index 4739a0a22b86aa..2f9254df7b2f2d 100644 --- a/ledger-tool/src/output.rs +++ b/ledger-tool/src/output.rs @@ -6,7 +6,7 @@ use { chrono::{Local, TimeZone}, serde::ser::{Impossible, SerializeSeq, SerializeStruct, Serializer}, serde_derive::{Deserialize, Serialize}, - solana_account_decoder::{UiAccount, UiAccountData, UiAccountEncoding}, + solana_account_decoder::{encode_ui_account, UiAccountData, UiAccountEncoding}, solana_accounts_db::accounts_index::ScanConfig, solana_cli_output::{ display::writeln_transaction, CliAccount, CliAccountNewConfig, OutputFormat, QuietDisplay, @@ -950,7 +950,7 @@ pub fn output_account( println!(" rent_epoch: {}", account.rent_epoch()); println!(" data_len: {}", account.data().len()); if print_account_data { - let account_data = UiAccount::encode(pubkey, account, encoding, None, None).data; + let account_data = encode_ui_account(pubkey, account, encoding, None, None).data; match account_data { UiAccountData::Binary(data, data_encoding) => { println!(" data: '{data}'"); diff --git a/programs/sbf/Cargo.lock b/programs/sbf/Cargo.lock index 70d1b420f6297b..36eeaff07d87db 100644 --- a/programs/sbf/Cargo.lock +++ b/programs/sbf/Cargo.lock @@ -4713,6 +4713,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "solana-account-decoder-client-types", "solana-config-program", "solana-sdk", "spl-token", @@ -4723,6 +4724,20 @@ dependencies = [ "zstd", ] +[[package]] +name = "solana-account-decoder-client-types" +version = "2.1.0" +dependencies = [ + "base64 0.22.1", + "bs58", + "serde", + "serde_derive", + "serde_json", + "solana-account", + "solana-pubkey", + "zstd", +] + [[package]] name = "solana-account-info" version = "2.1.0" @@ -5950,7 +5965,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "solana-account-decoder", + "solana-account-decoder-client-types", "solana-rpc-client-api", "solana-sdk", "solana-transaction-status-client-types", @@ -5973,7 +5988,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "solana-account-decoder", + "solana-account-decoder-client-types", "solana-inline-spl", "solana-sdk", "solana-transaction-status-client-types", @@ -6989,7 +7004,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "solana-account-decoder", + "solana-account-decoder-client-types", "solana-sdk", "solana-signature", "thiserror", diff --git a/rpc-client-api/Cargo.toml b/rpc-client-api/Cargo.toml index d0eb73608dea5e..e1eee85110178e 100644 --- a/rpc-client-api/Cargo.toml +++ b/rpc-client-api/Cargo.toml @@ -20,7 +20,7 @@ semver = { workspace = true } serde = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } -solana-account-decoder = { workspace = true } +solana-account-decoder-client-types = { workspace = true } solana-inline-spl = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status-client-types = { workspace = true } diff --git a/rpc-client-api/src/config.rs b/rpc-client-api/src/config.rs index 9eea7fb6508514..57a8c163e400c0 100644 --- a/rpc-client-api/src/config.rs +++ b/rpc-client-api/src/config.rs @@ -1,6 +1,6 @@ use { crate::filter::RpcFilterType, - solana_account_decoder::{UiAccountEncoding, UiDataSliceConfig}, + solana_account_decoder_client_types::{UiAccountEncoding, UiDataSliceConfig}, solana_sdk::{ clock::{Epoch, Slot}, commitment_config::{CommitmentConfig, CommitmentLevel}, diff --git a/rpc-client-api/src/response.rs b/rpc-client-api/src/response.rs index ee38c15d921f52..c9e6c5e5b99ad9 100644 --- a/rpc-client-api/src/response.rs +++ b/rpc-client-api/src/response.rs @@ -1,7 +1,7 @@ use { crate::client_error, serde::{Deserialize, Deserializer, Serialize, Serializer}, - solana_account_decoder::{parse_token::UiTokenAmount, UiAccount}, + solana_account_decoder_client_types::{token::UiTokenAmount, UiAccount}, solana_sdk::{ clock::{Epoch, Slot, UnixTimestamp}, fee_calculator::{FeeCalculator, FeeRateGovernor}, diff --git a/rpc-client-nonce-utils/src/blockhash_query.rs b/rpc-client-nonce-utils/src/blockhash_query.rs index 179ec378f4fecc..2cdf166e8ab037 100644 --- a/rpc-client-nonce-utils/src/blockhash_query.rs +++ b/rpc-client-nonce-utils/src/blockhash_query.rs @@ -114,7 +114,7 @@ mod tests { super::*, crate::blockhash_query, serde_json::{self, json}, - solana_account_decoder::{UiAccount, UiAccountEncoding}, + solana_account_decoder::{encode_ui_account, UiAccountEncoding}, solana_rpc_client_api::{ request::RpcRequest, response::{Response, RpcBlockhash, RpcResponseContext}, @@ -360,7 +360,7 @@ mod tests { ) .unwrap(); let nonce_pubkey = Pubkey::from([4u8; 32]); - let rpc_nonce_account = UiAccount::encode( + let rpc_nonce_account = encode_ui_account( &nonce_pubkey, &nonce_account, UiAccountEncoding::Base64, diff --git a/rpc-client-nonce-utils/src/nonblocking/blockhash_query.rs b/rpc-client-nonce-utils/src/nonblocking/blockhash_query.rs index f31313f69c93bb..bd644c96208f99 100644 --- a/rpc-client-nonce-utils/src/nonblocking/blockhash_query.rs +++ b/rpc-client-nonce-utils/src/nonblocking/blockhash_query.rs @@ -122,7 +122,7 @@ mod tests { super::*, crate::nonblocking::blockhash_query, serde_json::{self, json}, - solana_account_decoder::{UiAccount, UiAccountEncoding}, + solana_account_decoder::{encode_ui_account, UiAccountEncoding}, solana_rpc_client_api::{ request::RpcRequest, response::{Response, RpcBlockhash, RpcResponseContext}, @@ -381,7 +381,7 @@ mod tests { ) .unwrap(); let nonce_pubkey = Pubkey::from([4u8; 32]); - let rpc_nonce_account = UiAccount::encode( + let rpc_nonce_account = encode_ui_account( &nonce_pubkey, &nonce_account, UiAccountEncoding::Base64, diff --git a/rpc-client/Cargo.toml b/rpc-client/Cargo.toml index 3970f9c2b945b6..a795c6ce0ce8d7 100644 --- a/rpc-client/Cargo.toml +++ b/rpc-client/Cargo.toml @@ -22,7 +22,7 @@ semver = { workspace = true } serde = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } -solana-account-decoder = { workspace = true } +solana-account-decoder-client-types = { workspace = true, features = ["zstd"] } solana-rpc-client-api = { workspace = true } solana-sdk = { workspace = true } solana-transaction-status-client-types = { workspace = true } @@ -36,6 +36,7 @@ crossbeam-channel = { workspace = true } futures = { workspace = true } jsonrpc-core = { workspace = true } jsonrpc-http-server = { workspace = true } +solana-account-decoder = { workspace = true } [features] default = ["spinner"] diff --git a/rpc-client/src/mock_sender.rs b/rpc-client/src/mock_sender.rs index b355c1e0be684f..92e384ecc89a94 100644 --- a/rpc-client/src/mock_sender.rs +++ b/rpc-client/src/mock_sender.rs @@ -5,7 +5,7 @@ use { async_trait::async_trait, base64::{prelude::BASE64_STANDARD, Engine}, serde_json::{json, Number, Value}, - solana_account_decoder::{UiAccount, UiAccountEncoding}, + solana_account_decoder_client_types::{UiAccount, UiAccountData, UiAccountEncoding}, solana_rpc_client_api::{ client_error::Result, config::RpcBlockProductionConfig, @@ -20,7 +20,6 @@ use { }, }, solana_sdk::{ - account::Account, clock::{Slot, UnixTimestamp}, epoch_info::EpochInfo, instruction::InstructionError, @@ -426,23 +425,10 @@ impl RpcSender for MockSender { })?, "getProgramAccounts" => { let pubkey = Pubkey::from_str(PUBKEY).unwrap(); - let account = Account { - lamports: 1_000_000, - data: vec![], - owner: pubkey, - executable: false, - rent_epoch: 0, - }; serde_json::to_value(vec![ RpcKeyedAccount { pubkey: PUBKEY.to_string(), - account: UiAccount::encode( - &pubkey, - &account, - UiAccountEncoding::Base64, - None, - None, - ) + account: mock_encoded_account(&pubkey) } ])? }, @@ -455,3 +441,33 @@ impl RpcSender for MockSender { format!("MockSender: {}", self.url) } } + +pub(crate) fn mock_encoded_account(pubkey: &Pubkey) -> UiAccount { + UiAccount { + lamports: 1_000_000, + data: UiAccountData::Binary("".to_string(), UiAccountEncoding::Base64), + owner: pubkey.to_string(), + executable: false, + rent_epoch: 0, + space: Some(0), + } +} + +#[cfg(test)] +mod tests { + use {super::*, solana_account_decoder::encode_ui_account, solana_sdk::account::Account}; + + #[test] + fn test_mock_encoded_account() { + let pubkey = Pubkey::from_str(PUBKEY).unwrap(); + let account = Account { + lamports: 1_000_000, + data: vec![], + owner: pubkey, + executable: false, + rent_epoch: 0, + }; + let expected = encode_ui_account(&pubkey, &account, UiAccountEncoding::Base64, None, None); + assert_eq!(expected, mock_encoded_account(&pubkey)); + } +} diff --git a/rpc-client/src/nonblocking/rpc_client.rs b/rpc-client/src/nonblocking/rpc_client.rs index 8c580b58efb716..8d2b858190834f 100644 --- a/rpc-client/src/nonblocking/rpc_client.rs +++ b/rpc-client/src/nonblocking/rpc_client.rs @@ -12,7 +12,7 @@ use {crate::spinner, solana_sdk::clock::MAX_HASH_AGE_IN_SECONDS, std::cmp::min}; use { crate::{ http_sender::HttpSender, - mock_sender::MockSender, + mock_sender::{mock_encoded_account, MockSender}, rpc_client::{ GetConfirmedSignaturesForAddress2Config, RpcClientConfig, SerializableMessage, SerializableTransaction, @@ -23,8 +23,8 @@ use { bincode::serialize, log::*, serde_json::{json, Value}, - solana_account_decoder::{ - parse_token::{TokenAccountType, UiTokenAccount, UiTokenAmount}, + solana_account_decoder_client_types::{ + token::{TokenAccountType, UiTokenAccount, UiTokenAmount}, UiAccount, UiAccountData, UiAccountEncoding, }, solana_rpc_client_api::{ @@ -3454,7 +3454,7 @@ impl RpcClient { /// # pubkey::Pubkey, /// # commitment_config::CommitmentConfig, /// # }; - /// # use solana_account_decoder::UiAccountEncoding; + /// # use solana_account_decoder_client_types::UiAccountEncoding; /// # use std::str::FromStr; /// # futures::executor::block_on(async { /// # let mocks = rpc_client::create_rpc_client_mocks(); @@ -3674,7 +3674,7 @@ impl RpcClient { /// # signer::keypair::Keypair, /// # commitment_config::CommitmentConfig, /// # }; - /// # use solana_account_decoder::UiAccountEncoding; + /// # use solana_account_decoder_client_types::UiAccountEncoding; /// # futures::executor::block_on(async { /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); /// # let alice = Keypair::new(); @@ -3948,7 +3948,7 @@ impl RpcClient { /// # signer::keypair::Keypair, /// # commitment_config::CommitmentConfig, /// # }; - /// # use solana_account_decoder::{UiDataSliceConfig, UiAccountEncoding}; + /// # use solana_account_decoder_client_types::{UiDataSliceConfig, UiAccountEncoding}; /// # futures::executor::block_on(async { /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); /// # let alice = Keypair::new(); @@ -4716,14 +4716,7 @@ pub fn create_rpc_client_mocks() -> crate::mock_sender::Mocks { }, value: { let pubkey = Pubkey::from_str("BgvYtJEfmZYdVKiptmMjxGzv8iQoo4MWjsP3QsTkhhxa").unwrap(); - let account = Account { - lamports: 1_000_000, - data: vec![], - owner: pubkey, - executable: false, - rent_epoch: 0, - }; - UiAccount::encode(&pubkey, &account, UiAccountEncoding::Base64, None, None) + mock_encoded_account(&pubkey) }, }) .unwrap(); diff --git a/rpc-client/src/rpc_client.rs b/rpc-client/src/rpc_client.rs index baf9ba92eca42d..c2a214fe7dde67 100644 --- a/rpc-client/src/rpc_client.rs +++ b/rpc-client/src/rpc_client.rs @@ -13,16 +13,13 @@ pub use crate::mock_sender::Mocks; use { crate::{ http_sender::HttpSender, - mock_sender::MockSender, + mock_sender::{mock_encoded_account, MockSender}, nonblocking::{self, rpc_client::get_rpc_request_str}, rpc_sender::*, }, serde::Serialize, serde_json::Value, - solana_account_decoder::{ - parse_token::{UiTokenAccount, UiTokenAmount}, - UiAccount, UiAccountEncoding, - }, + solana_account_decoder_client_types::token::{UiTokenAccount, UiTokenAmount}, solana_rpc_client_api::{ client_error::{Error as ClientError, ErrorKind, Result as ClientResult}, config::{RpcAccountInfoConfig, *}, @@ -2967,7 +2964,7 @@ impl RpcClient { /// # pubkey::Pubkey, /// # commitment_config::CommitmentConfig, /// # }; - /// # use solana_account_decoder::UiAccountEncoding; + /// # use solana_account_decoder_client_types::UiAccountEncoding; /// # use std::str::FromStr; /// # let mocks = rpc_client::create_rpc_client_mocks(); /// # let rpc_client = RpcClient::new_mock_with_mocks("succeeds".to_string(), mocks); @@ -3128,7 +3125,7 @@ impl RpcClient { /// # signer::keypair::Keypair, /// # commitment_config::CommitmentConfig, /// # }; - /// # use solana_account_decoder::UiAccountEncoding; + /// # use solana_account_decoder_client_types::UiAccountEncoding; /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); /// # let alice = Keypair::new(); /// # let bob = Keypair::new(); @@ -3331,7 +3328,7 @@ impl RpcClient { /// # signer::keypair::Keypair, /// # commitment_config::CommitmentConfig, /// # }; - /// # use solana_account_decoder::{UiDataSliceConfig, UiAccountEncoding}; + /// # use solana_account_decoder_client_types::{UiDataSliceConfig, UiAccountEncoding}; /// # let rpc_client = RpcClient::new_mock("succeeds".to_string()); /// # let alice = Keypair::new(); /// # let base64_bytes = "\ @@ -3730,14 +3727,7 @@ pub fn create_rpc_client_mocks() -> crate::mock_sender::Mocks { }, value: { let pubkey = Pubkey::from_str("BgvYtJEfmZYdVKiptmMjxGzv8iQoo4MWjsP3QsTkhhxa").unwrap(); - let account = Account { - lamports: 1_000_000, - data: vec![], - owner: pubkey, - executable: false, - rent_epoch: 0, - }; - UiAccount::encode(&pubkey, &account, UiAccountEncoding::Base64, None, None) + mock_encoded_account(&pubkey) }, }) .unwrap(); @@ -3757,6 +3747,8 @@ mod tests { jsonrpc_core::{futures::prelude::*, Error, IoHandler, Params}, jsonrpc_http_server::{AccessControlAllowOrigin, DomainsValidation, ServerBuilder}, serde_json::{json, Number}, + solana_account_decoder::encode_ui_account, + solana_account_decoder_client_types::UiAccountEncoding, solana_rpc_client_api::client_error::ErrorKind, solana_sdk::{ instruction::InstructionError, @@ -3995,7 +3987,7 @@ mod tests { }; let keyed_account = RpcKeyedAccount { pubkey: pubkey.to_string(), - account: UiAccount::encode(&pubkey, &account, UiAccountEncoding::Base64, None, None), + account: encode_ui_account(&pubkey, &account, UiAccountEncoding::Base64, None, None), }; let expected_result = vec![(pubkey, account)]; // Test: without context diff --git a/rpc/src/parsed_token_accounts.rs b/rpc/src/parsed_token_accounts.rs index 8f7d02d73eff03..77461eeb79762a 100644 --- a/rpc/src/parsed_token_accounts.rs +++ b/rpc/src/parsed_token_accounts.rs @@ -2,6 +2,7 @@ use { crate::rpc::account_resolver, jsonrpc_core::{Error, Result}, solana_account_decoder::{ + encode_ui_account, parse_account_data::{AccountAdditionalDataV2, SplTokenAdditionalData}, parse_token::get_token_account_mint, UiAccount, UiAccountData, UiAccountEncoding, @@ -42,7 +43,7 @@ pub fn get_parsed_token_account( spl_token_additional_data: Some(data), }); - UiAccount::encode( + encode_ui_account( pubkey, &account, UiAccountEncoding::JsonParsed, @@ -71,7 +72,7 @@ where }) }); - let maybe_encoded_account = UiAccount::encode( + let maybe_encoded_account = encode_ui_account( &pubkey, &account, UiAccountEncoding::JsonParsed, diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index 9f6de648a40e30..3f0da1debfa5d8 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -11,6 +11,7 @@ use { jsonrpc_core::{futures::future, types::error, BoxFuture, Error, Metadata, Result}, jsonrpc_derive::rpc, solana_account_decoder::{ + encode_ui_account, parse_account_data::SplTokenAdditionalData, parse_token::{is_known_spl_token_id, token_amount_to_ui_amount_v2, UiTokenAmount}, UiAccount, UiAccountEncoding, UiDataSliceConfig, MAX_BASE58_BYTES, @@ -2380,7 +2381,7 @@ fn encode_account( data: None, }) } else { - Ok(UiAccount::encode( + Ok(encode_ui_account( pubkey, account, encoding, None, data_slice, )) } @@ -5498,7 +5499,7 @@ pub mod tests { let result: Vec = parse_success_result(rpc.handle_request_sync(request)); let expected_value = vec![RpcKeyedAccount { pubkey: new_program_account_key.to_string(), - account: UiAccount::encode( + account: encode_ui_account( &new_program_account_key, &new_program_account, UiAccountEncoding::Binary, diff --git a/rpc/src/rpc_subscriptions.rs b/rpc/src/rpc_subscriptions.rs index dfa82c87a00588..5aefa09c5b6603 100644 --- a/rpc/src/rpc_subscriptions.rs +++ b/rpc/src/rpc_subscriptions.rs @@ -17,7 +17,9 @@ use { itertools::Either, rayon::prelude::*, serde::Serialize, - solana_account_decoder::{parse_token::is_known_spl_token_id, UiAccount, UiAccountEncoding}, + solana_account_decoder::{ + encode_ui_account, parse_token::is_known_spl_token_id, UiAccount, UiAccountEncoding, + }, solana_ledger::{blockstore::Blockstore, get_tmp_ledger_path}, solana_measure::measure::Measure, solana_rpc_client_api::response::{ @@ -385,7 +387,7 @@ fn filter_account_result( { get_parsed_token_account(&bank, ¶ms.pubkey, account, None) } else { - UiAccount::encode(¶ms.pubkey, &account, params.encoding, None, None) + encode_ui_account(¶ms.pubkey, &account, params.encoding, None, None) } }); (account, last_modified_slot) @@ -428,7 +430,7 @@ fn filter_program_results( } else { let accounts = keyed_accounts.map(move |(pubkey, account)| RpcKeyedAccount { pubkey: pubkey.to_string(), - account: UiAccount::encode(&pubkey, &account, encoding, None, None), + account: encode_ui_account(&pubkey, &account, encoding, None, None), }); Either::Right(accounts) }; diff --git a/svm/examples/json-rpc/server/src/rpc_process.rs b/svm/examples/json-rpc/server/src/rpc_process.rs index ed239323b462b4..7cb910c876d9ad 100644 --- a/svm/examples/json-rpc/server/src/rpc_process.rs +++ b/svm/examples/json-rpc/server/src/rpc_process.rs @@ -10,6 +10,7 @@ use { log::*, serde_json, solana_account_decoder::{ + encode_ui_account, parse_account_data::{AccountAdditionalDataV2, SplTokenAdditionalData}, parse_token::{get_token_account_mint, is_known_spl_token_id}, UiAccount, UiAccountEncoding, UiDataSliceConfig, MAX_BASE58_BYTES, @@ -514,7 +515,7 @@ impl JsonRpcRequestProcessor { spl_token_additional_data: Some(data), }); - UiAccount::encode( + encode_ui_account( pubkey, &account, UiAccountEncoding::JsonParsed, @@ -900,7 +901,7 @@ fn encode_account( data: None, }) } else { - Ok(UiAccount::encode( + Ok(encode_ui_account( pubkey, account, encoding, None, data_slice, )) } diff --git a/transaction-status-client-types/Cargo.toml b/transaction-status-client-types/Cargo.toml index 4298bf10b68a00..cdf4d29a4ab94e 100644 --- a/transaction-status-client-types/Cargo.toml +++ b/transaction-status-client-types/Cargo.toml @@ -16,7 +16,7 @@ bs58 = { workspace = true } serde = { workspace = true } serde_derive = { workspace = true } serde_json = { workspace = true } -solana-account-decoder = { workspace = true } +solana-account-decoder-client-types = { workspace = true } solana-sdk = { workspace = true } solana-signature = { workspace = true, default-features = false } thiserror = { workspace = true } diff --git a/transaction-status-client-types/src/lib.rs b/transaction-status-client-types/src/lib.rs index ee471e9ae914b1..a1284efb07b0de 100644 --- a/transaction-status-client-types/src/lib.rs +++ b/transaction-status-client-types/src/lib.rs @@ -5,7 +5,7 @@ use { core::fmt, serde_derive::{Deserialize, Serialize}, serde_json::Value, - solana_account_decoder::parse_token::UiTokenAmount, + solana_account_decoder_client_types::token::UiTokenAmount, solana_sdk::{ commitment_config::CommitmentConfig, instruction::CompiledInstruction, diff --git a/transaction-status/src/parse_token.rs b/transaction-status/src/parse_token.rs index bc4e57fc467af7..4bc5ea14cbf41a 100644 --- a/transaction-status/src/parse_token.rs +++ b/transaction-status/src/parse_token.rs @@ -11,8 +11,7 @@ use { }, serde_json::{json, Map, Value}, solana_account_decoder::{ - parse_account_data::SplTokenAdditionalData, - parse_token::{token_amount_to_ui_amount_v2, UiAccountState}, + parse_account_data::SplTokenAdditionalData, parse_token::token_amount_to_ui_amount_v2, }, solana_sdk::{instruction::CompiledInstruction, message::AccountKeys}, spl_token_2022::{ diff --git a/transaction-status/src/parse_token/extension/confidential_transfer.rs b/transaction-status/src/parse_token/extension/confidential_transfer.rs index f490e6417d920d..78b1c229c34638 100644 --- a/transaction-status/src/parse_token/extension/confidential_transfer.rs +++ b/transaction-status/src/parse_token/extension/confidential_transfer.rs @@ -1,6 +1,6 @@ use { super::*, - solana_account_decoder::parse_token_extension::UiConfidentialTransferMint, + solana_account_decoder::parse_token_extension::convert_confidential_transfer_mint, spl_token_2022::{ extension::confidential_transfer::{instruction::*, ConfidentialTransferMint}, instruction::{decode_instruction_data, decode_instruction_type}, @@ -21,8 +21,8 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction( *decode_instruction_data(instruction_data).map_err(|_| { ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) })?; - let confidential_transfer_mint: UiConfidentialTransferMint = - confidential_transfer_mint.into(); + let confidential_transfer_mint = + convert_confidential_transfer_mint(confidential_transfer_mint); let mut value = json!({ "mint": account_keys[account_indexes[0] as usize].to_string(), }); @@ -39,8 +39,8 @@ pub(in crate::parse_token) fn parse_confidential_transfer_instruction( *decode_instruction_data(instruction_data).map_err(|_| { ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) })?; - let confidential_transfer_mint: UiConfidentialTransferMint = - confidential_transfer_mint.into(); + let confidential_transfer_mint = + convert_confidential_transfer_mint(confidential_transfer_mint); let mut value = json!({ "mint": account_keys[account_indexes[0] as usize].to_string(), "confidentialTransferMintAuthority": account_keys[account_indexes[1] as usize].to_string(), diff --git a/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs b/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs index f35fad62c095fe..9a6856835c864b 100644 --- a/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs +++ b/transaction-status/src/parse_token/extension/confidential_transfer_fee.rs @@ -1,6 +1,6 @@ use { super::*, - solana_account_decoder::parse_token_extension::UiConfidentialTransferFeeConfig, + solana_account_decoder::parse_token_extension::convert_confidential_transfer_fee_config, spl_token_2022::{ extension::confidential_transfer_fee::{instruction::*, ConfidentialTransferFeeConfig}, instruction::{decode_instruction_data, decode_instruction_type}, @@ -21,8 +21,8 @@ pub(in crate::parse_token) fn parse_confidential_transfer_fee_instruction( *decode_instruction_data(instruction_data).map_err(|_| { ParseInstructionError::InstructionNotParsable(ParsableProgram::SplToken) })?; - let confidential_transfer_mint: UiConfidentialTransferFeeConfig = - confidential_transfer_mint.into(); + let confidential_transfer_mint = + convert_confidential_transfer_fee_config(confidential_transfer_mint); let mut value = json!({ "mint": account_keys[account_indexes[0] as usize].to_string(), }); diff --git a/transaction-status/src/parse_token/extension/default_account_state.rs b/transaction-status/src/parse_token/extension/default_account_state.rs index aa7a3da01bb2b2..a5f8a7b016a599 100644 --- a/transaction-status/src/parse_token/extension/default_account_state.rs +++ b/transaction-status/src/parse_token/extension/default_account_state.rs @@ -1,5 +1,6 @@ use { super::*, + solana_account_decoder::parse_token::convert_account_state, spl_token_2022::extension::default_account_state::instruction::{ decode_instruction, DefaultAccountStateInstruction, }, @@ -22,7 +23,7 @@ pub(in crate::parse_token) fn parse_default_account_state_instruction( instruction_type: format!("initialize{instruction_type}"), info: json!({ "mint": account_keys[account_indexes[0] as usize].to_string(), - "accountState": UiAccountState::from(account_state), + "accountState": convert_account_state(account_state), }), }) } @@ -30,7 +31,7 @@ pub(in crate::parse_token) fn parse_default_account_state_instruction( check_num_token_accounts(account_indexes, 2)?; let mut value = json!({ "mint": account_keys[account_indexes[0] as usize].to_string(), - "accountState": UiAccountState::from(account_state), + "accountState": convert_account_state(account_state), }); let map = value.as_object_mut().unwrap(); parse_signers(