diff --git a/token/cli/src/main.rs b/token/cli/src/main.rs index 7d5e585c909..8dbb690a81c 100644 --- a/token/cli/src/main.rs +++ b/token/cli/src/main.rs @@ -2162,6 +2162,9 @@ async fn command_display(config: &Config<'_>, address: Pubkey) -> CommandResult let token_data = parse_token(&account_data.data, decimals); + // remove on next `solana-account-decoder` upgrade + let ui_confidential_transfer_extension = has_confidential_transfer(&account_data.data); + match token_data { Ok(TokenAccountType::Account(account)) => { let mint_address = Pubkey::from_str(&account.mint)?; @@ -2189,6 +2192,7 @@ async fn command_display(config: &Config<'_>, address: Pubkey) -> CommandResult epoch: epoch_info.epoch, program_id: config.program_id.to_string(), mint, + ui_confidential_transfer_extension, }; Ok(config.output_format.formatted_string(&cli_output)) diff --git a/token/cli/src/output.rs b/token/cli/src/output.rs index 6bb0799992d..3192e644982 100644 --- a/token/cli/src/output.rs +++ b/token/cli/src/output.rs @@ -9,6 +9,15 @@ use solana_account_decoder::{ }, }; use solana_cli_output::{display::writeln_name_value, OutputFormat, QuietDisplay, VerboseDisplay}; +use spl_token_2022::{ + extension::{ + confidential_transfer::ConfidentialTransferMint, BaseStateWithExtensions, + StateWithExtensions, + }, + solana_program::pubkey::Pubkey, + solana_zk_token_sdk::zk_token_elgamal::pod::ElGamalPubkey, + state::Mint, +}; use std::fmt::{self, Display}; pub(crate) trait Output: Serialize + fmt::Display + QuietDisplay + VerboseDisplay {} @@ -250,7 +259,7 @@ impl fmt::Display for CliTokenAccount { if !self.account.extensions.is_empty() { writeln!(f, "{}", style("Extensions:").bold())?; for extension in &self.account.extensions { - display_ui_extension(f, 0, extension)?; + display_ui_extension(f, 0, extension, None)?; } } @@ -288,6 +297,7 @@ pub(crate) struct CliMint { pub(crate) epoch: u64, #[serde(flatten)] pub(crate) mint: UiMint, + pub(crate) ui_confidential_transfer_extension: Option, } impl QuietDisplay for CliMint {} @@ -319,7 +329,12 @@ impl fmt::Display for CliMint { if !self.mint.extensions.is_empty() { writeln!(f, "{}", style("Extensions").bold())?; for extension in &self.mint.extensions { - display_ui_extension(f, self.epoch, extension)?; + display_ui_extension( + f, + self.epoch, + extension, + self.ui_confidential_transfer_extension.as_ref(), + )?; } } @@ -547,6 +562,7 @@ fn display_ui_extension( f: &mut fmt::Formatter, epoch: u64, ui_extension: &UiExtension, + ui_confidential_transfer_extension: Option<&UiConfidentialTransferExtension>, ) -> fmt::Result { match ui_extension { UiExtension::TransferFeeConfig(UiTransferFeeConfig { @@ -683,21 +699,47 @@ fn display_ui_extension( } // ExtensionType::Uninitialized is a hack to ensure a mint/account is never the same length as a multisig UiExtension::Uninitialized => Ok(()), - UiExtension::ConfidentialTransferMint(_) => writeln_name_value( - f, - " Unparseable extension:", - "ConfidentialTransferMint is not presently supported", - ), UiExtension::ConfidentialTransferAccount(_) => writeln_name_value( f, - " Unparseable extension:", + " Confidential transfer:", "ConfidentialTransferAccount is not presently supported", ), - _ => writeln_name_value( - f, - " Unparseable extension:", - "Consider upgrading to a newer version of spl-token", - ), + _ => { + if let Some(ui_confidential_transfer_extension) = ui_confidential_transfer_extension { + match ui_confidential_transfer_extension { + UiConfidentialTransferExtension::ConfidentialTransferMint(ui_mint) => { + writeln!(f, " {}", style("Confidential transfer:").bold())?; + writeln!( + f, + " {}: {}", + style("Account approve policy").bold(), + if ui_mint.auto_approve_new_accounts { + "auto" + } else { + "manual" + }, + )?; + writeln!( + f, + " {}: {}", + style("Audit key:").bold(), + if let Some(auditor_pubkey) = ui_mint.auditor_encryption_pubkey.as_ref() + { + auditor_pubkey + } else { + "audits are not enabled" + } + ) + } + } + } else { + writeln_name_value( + f, + " Unparseable extension:", + "Consider upgrading to a newer version of spl-token", + ) + } + } } } @@ -708,3 +750,39 @@ fn flattened( let flattened: Vec<_> = vec.iter().flatten().collect(); flattened.serialize(serializer) } + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub(crate) enum UiConfidentialTransferExtension { + ConfidentialTransferMint(UiConfidentialTransferMint), + // TODO: add `ConfidentialTransferAccount` +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub(crate) struct UiConfidentialTransferMint { + pub authority: Option, + pub auto_approve_new_accounts: bool, + pub auditor_encryption_pubkey: Option, +} + +pub(crate) fn has_confidential_transfer(data: &[u8]) -> Option { + if let Ok(mint) = StateWithExtensions::::unpack(data) { + if let Some(confidential_transfer_mint) = + mint.get_extension::().ok() + { + let authority: Option = confidential_transfer_mint.authority.into(); + let auditor_encryption_pubkey: Option = + confidential_transfer_mint.auditor_elgamal_pubkey.into(); + return Some(UiConfidentialTransferExtension::ConfidentialTransferMint( + UiConfidentialTransferMint { + authority: authority.map(|pubkey| pubkey.to_string()), + auto_approve_new_accounts: confidential_transfer_mint + .auto_approve_new_accounts + .into(), + auditor_encryption_pubkey: auditor_encryption_pubkey + .map(|pubkey| pubkey.to_string()), + }, + )); + } + } + None +}