use {
crate::types::{AccountState, DecryptableBalance, EncryptedBalance, TransferFee},
solana_pubkey::Pubkey,
spl_pod::primitives::PodI64,
spl_token_2022::extension::{BaseStateWithExtensions as _, ExtensionType, StateWithExtensions},
spl_type_length_value::variable_len_pack::VariableLenPack,
std::collections::HashMap,
};
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, borsh::BorshSerialize, borsh::BorshDeserialize, PartialEq)]
pub enum Extension {
Uninitialized,
TransferFeeConfig {
transfer_fee_config_authority: Option<Pubkey>,
withdraw_withheld_authority: Option<Pubkey>,
withheld_amount: u64,
older_transfer_fee: TransferFee,
newer_transfer_fee: TransferFee,
},
TransferFeeAmount {
withheld_amount: u64,
},
MintCloseAuthority {
close_authority: Option<Pubkey>,
},
ConfidentialTransferMint {
authority: Option<Pubkey>,
auto_approve_new_accounts: bool,
auditor_elgamal_pubkey: Option<Pubkey>,
},
ConfidentialTransferAccount {
approved: bool,
elgamal_pubkey: Pubkey,
pending_balance_low: EncryptedBalance,
pending_balance_high: EncryptedBalance,
available_balance: EncryptedBalance,
decryptable_available_balance: DecryptableBalance,
allow_confidential_credits: bool,
allow_non_confidential_credits: bool,
pending_balance_credit_counter: u64,
maximum_pending_balance_credit_counter: u64,
expected_pending_balance_credit_counter: u64,
actual_pending_balance_credit_counter: u64,
},
DefaultAccountState {
state: AccountState,
},
ImmutableOwner {},
MemoTransfer {
require_incoming_transfer_memos: bool,
},
NonTransferable {},
InterestBearingConfig {
rate_authority: Option<Pubkey>,
initialization_timestamp: u64,
pre_update_average_rate: i16,
last_update_timestamp: u64,
current_rate: i16,
},
CpiGuard {
lock_cpi: bool,
},
PermanentDelegate {
delegate: Option<Pubkey>,
},
NonTransferableAccount {},
TransferHook {
authority: Option<Pubkey>,
program_id: Option<Pubkey>,
},
TransferHookAccount {
transferring: bool,
},
ConfidentialTransferFee {
authority: Option<Pubkey>,
elgamal_pubkey: Pubkey,
harvest_to_mint_enabled: bool,
withheld_amount: EncryptedBalance,
},
ConfidentialTransferFeeAmount {
withheld_amount: EncryptedBalance,
},
MetadataPointer {
authority: Option<Pubkey>,
metadata_address: Option<Pubkey>,
},
TokenMetadata {
update_authority: Option<Pubkey>,
mint: Pubkey,
name: String,
symbol: String,
uri: String,
additional_metadata: HashMap<String, String>,
},
GroupPointer {
authority: Option<Pubkey>,
group_address: Option<Pubkey>,
},
TokenGroup {
update_authority: Option<Pubkey>,
mint: Pubkey,
size: u64,
max_size: u64,
},
GroupMemberPointer {
authority: Option<Pubkey>,
member_address: Option<Pubkey>,
},
TokenGroupMember {
mint: Pubkey,
group: Pubkey,
member_number: u64,
},
ConfidentialMintBurn,
ScaledUiAmountConfig {
authority: Option<Pubkey>,
multiplier: f64,
new_multiplier_effective_timestamp: u64,
new_multiplier: f64,
},
PausableConfig {
authority: Option<Pubkey>,
paused: bool,
},
PausableAccount,
PermissionedBurn {
authority: Option<Pubkey>,
},
}
impl Extension {
pub fn from_mint_and_type(
state_with_extensions: &StateWithExtensions<'_, spl_token_2022::state::Mint>,
extension_type: &spl_token_2022::extension::ExtensionType,
) -> Option<Self> {
match extension_type {
ExtensionType::Uninitialized => None,
ExtensionType::TransferFeeConfig => {
state_with_extensions.get_extension::<spl_token_2022::extension::transfer_fee::TransferFeeConfig>().map(|extension| Extension::TransferFeeConfig {
transfer_fee_config_authority: extension.transfer_fee_config_authority.get(),
withdraw_withheld_authority: extension.withdraw_withheld_authority.get(),
withheld_amount: extension.withheld_amount.into(),
older_transfer_fee: TransferFee {
epoch: extension.older_transfer_fee.epoch.into(),
maximum_fee: extension.older_transfer_fee.maximum_fee.into(),
transfer_fee_basis_points: extension.older_transfer_fee.transfer_fee_basis_points.into(),
},
newer_transfer_fee: TransferFee {
epoch: extension.newer_transfer_fee.epoch.into(),
maximum_fee: extension.newer_transfer_fee.maximum_fee.into(),
transfer_fee_basis_points: extension.newer_transfer_fee.transfer_fee_basis_points.into(),
},
}).ok()
}
ExtensionType::MintCloseAuthority => {
state_with_extensions.get_extension::<spl_token_2022::extension::mint_close_authority::MintCloseAuthority>().map(|extension| Extension::MintCloseAuthority {
close_authority: extension.close_authority.get(),
}).ok()
}
ExtensionType::ConfidentialTransferMint => {
state_with_extensions.get_extension::<spl_token_2022::extension::confidential_transfer::ConfidentialTransferMint>().map(|extension| Some(Extension::ConfidentialTransferMint {
authority: extension.authority.into(),
auto_approve_new_accounts: extension.auto_approve_new_accounts.into(),
auditor_elgamal_pubkey: extension.auditor_elgamal_pubkey.get().map(|pubkey| Pubkey::new_from_array(pubkey.0)),
})).ok().flatten()
}
ExtensionType::DefaultAccountState => {
state_with_extensions.get_extension::<spl_token_2022::extension::default_account_state::DefaultAccountState>().map(|extension| Some(Extension::DefaultAccountState {
state: match extension.state {
0 => AccountState::Uninitialized,
1 => AccountState::Initialized,
2 => AccountState::Frozen,
_ => return None,
}
})).ok().flatten()
}
ExtensionType::NonTransferable => {
state_with_extensions.get_extension::<spl_token_2022::extension::non_transferable::NonTransferable>().map(|_extension| Extension::NonTransferable {}).ok()
}
ExtensionType::InterestBearingConfig => {
state_with_extensions.get_extension::<spl_token_2022::extension::interest_bearing_mint::InterestBearingConfig>().map(|extension| Extension::InterestBearingConfig {
rate_authority: extension.rate_authority.get(),
initialization_timestamp: unsafe { std::mem::transmute::<PodI64, u64>(extension.initialization_timestamp) },
pre_update_average_rate: extension.pre_update_average_rate.into(),
last_update_timestamp: unsafe { std::mem::transmute::<PodI64, u64>(extension.last_update_timestamp) },
current_rate: extension.current_rate.into(),
}).ok()
}
ExtensionType::PermanentDelegate => {
state_with_extensions.get_extension::<spl_token_2022::extension::permanent_delegate::PermanentDelegate>().map(|extension| Extension::PermanentDelegate {
delegate: extension.delegate.get(),
}).ok()
}
ExtensionType::TransferHook => {
state_with_extensions.get_extension::<spl_token_2022::extension::transfer_hook::TransferHook>().map(|extension| Extension::TransferHook {
authority: extension.authority.get(),
program_id: extension.program_id.get(),
}).ok()
}
ExtensionType::ConfidentialTransferFeeConfig => {
state_with_extensions.get_extension::<spl_token_2022::extension::confidential_transfer_fee::ConfidentialTransferFeeConfig>().map(|extension| Extension::ConfidentialTransferFee {
authority: extension.authority.into(),
harvest_to_mint_enabled: extension.harvest_to_mint_enabled.into(),
elgamal_pubkey: Pubkey::new_from_array(extension.withdraw_withheld_authority_elgamal_pubkey.0),
withheld_amount: EncryptedBalance(extension.withheld_amount.0),
}).ok()
}
ExtensionType::MetadataPointer => {
state_with_extensions.get_extension::<spl_token_2022::extension::metadata_pointer::MetadataPointer>().map(|extension| Extension::MetadataPointer {
authority: extension.authority.into(),
metadata_address: extension.metadata_address.into(),
}).ok()
}
ExtensionType::TokenMetadata => {
state_with_extensions.get_extension_bytes::<spl_token_metadata_interface::state::TokenMetadata>().map(|extension| spl_token_metadata_interface::state::TokenMetadata::unpack_from_slice(extension).ok().map(|extension| Extension::TokenMetadata {
update_authority: extension.update_authority.into(),
mint: extension.mint,
name: extension.name,
symbol: extension.symbol,
uri: extension.uri,
additional_metadata: extension.additional_metadata.iter().map(|(key, value)| (key.clone(), value.clone())).collect(),
})).ok().flatten()
},
ExtensionType::GroupPointer => {
state_with_extensions.get_extension::<spl_token_2022::extension::group_pointer::GroupPointer>().map(|extension| Extension::GroupPointer {
authority: extension.authority.into(),
group_address: extension.group_address.into(),
}).ok()
}
ExtensionType::TokenGroup => {
state_with_extensions.get_extension_bytes::<spl_token_group_interface::state::TokenGroup>().map(|extension| spl_pod::bytemuck::pod_from_bytes::<spl_token_group_interface::state::TokenGroup>(extension).ok().map(|extension| Extension::TokenGroup {
update_authority: extension.update_authority.into(),
mint: extension.mint,
size: extension.size.into(),
max_size: extension.max_size.into(),
})).ok().flatten()
},
ExtensionType::GroupMemberPointer => {
state_with_extensions.get_extension::<spl_token_2022::extension::group_member_pointer::GroupMemberPointer>().map(|extension| Extension::GroupMemberPointer {
authority: extension.authority.into(),
member_address: extension.member_address.into(),
}).ok()
}
ExtensionType::ConfidentialMintBurn => {
state_with_extensions.get_extension::<spl_token_2022::extension::confidential_mint_burn::ConfidentialMintBurn>().map(|_extension| Extension::ConfidentialMintBurn).ok()
}
ExtensionType::TokenGroupMember => {
state_with_extensions.get_extension_bytes::<spl_token_group_interface::state::TokenGroupMember>().map(|extension| spl_pod::bytemuck::pod_from_bytes::<spl_token_group_interface::state::TokenGroupMember>(extension).ok().map(|extension| Extension::TokenGroupMember {
mint: extension.mint,
group: extension.group,
member_number: extension.member_number.into(),
})).ok().flatten()
},
ExtensionType::ScaledUiAmount => {
state_with_extensions.get_extension::<spl_token_2022::extension::scaled_ui_amount::ScaledUiAmountConfig>().map(|extension| Extension::ScaledUiAmountConfig {
authority: extension.authority.get(),
multiplier: extension.multiplier.into(),
new_multiplier_effective_timestamp: u64::from_le_bytes(unsafe { std::mem::transmute::<PodI64, [u8; 8]>(extension.new_multiplier_effective_timestamp)}),
new_multiplier: extension.new_multiplier.into(),
}).ok()
}
ExtensionType::Pausable => {
state_with_extensions.get_extension::<spl_token_2022::extension::pausable::PausableConfig>().map(|extension| Extension::PausableConfig {
authority: extension.authority.into(),
paused: extension.paused.into(),
}).ok()
}
ExtensionType::PermissionedBurn => {
state_with_extensions.get_extension::<spl_token_2022::extension::permissioned_burn::PermissionedBurnConfig>().map(|extension| Extension::PermissionedBurn {
authority: extension.authority.into(),
}).ok()
}
_ => None,
}
}
pub fn from_account_and_type(
state_with_extensions: &StateWithExtensions<'_, spl_token_2022::state::Account>,
extension_type: &spl_token_2022::extension::ExtensionType,
) -> Option<Self> {
match extension_type {
ExtensionType::TransferFeeAmount => {
state_with_extensions.get_extension::<spl_token_2022::extension::transfer_fee::TransferFeeAmount>().map(|extension| Extension::TransferFeeAmount {
withheld_amount: extension.withheld_amount.into(),
}).ok()
}
ExtensionType::ConfidentialTransferAccount => {
state_with_extensions.get_extension::<spl_token_2022::extension::confidential_transfer::ConfidentialTransferAccount>().map(|extension| Extension::ConfidentialTransferAccount {
approved: extension.approved.into(),
elgamal_pubkey: Pubkey::new_from_array(extension.elgamal_pubkey.0),
pending_balance_low: EncryptedBalance(extension.pending_balance_lo.0),
pending_balance_high: EncryptedBalance(extension.pending_balance_hi.0),
available_balance: EncryptedBalance(extension.available_balance.0),
decryptable_available_balance: DecryptableBalance(extension.decryptable_available_balance.0),
allow_confidential_credits: extension.allow_confidential_credits.into(),
allow_non_confidential_credits: extension.allow_non_confidential_credits.into(),
pending_balance_credit_counter: extension.pending_balance_credit_counter.into(),
maximum_pending_balance_credit_counter: extension.maximum_pending_balance_credit_counter.into(),
expected_pending_balance_credit_counter: extension.expected_pending_balance_credit_counter.into(),
actual_pending_balance_credit_counter: extension.actual_pending_balance_credit_counter.into(),
}).ok()
}
ExtensionType::MemoTransfer => {
state_with_extensions.get_extension::<spl_token_2022::extension::memo_transfer::MemoTransfer>().map(|extension| Extension::MemoTransfer {
require_incoming_transfer_memos: extension.require_incoming_transfer_memos.into()
}).ok()
}
ExtensionType::NonTransferableAccount => {
state_with_extensions.get_extension::<spl_token_2022::extension::non_transferable::NonTransferableAccount>().map(|_extension| Extension::NonTransferableAccount {}).ok()
}
ExtensionType::TransferHookAccount => {
state_with_extensions.get_extension::<spl_token_2022::extension::transfer_hook::TransferHookAccount>().map(|extension| Extension::TransferHookAccount {
transferring: extension.transferring.into(),
}).ok()
}
ExtensionType::CpiGuard => {
state_with_extensions.get_extension::<spl_token_2022::extension::cpi_guard::CpiGuard>().map(|extension| Extension::CpiGuard {
lock_cpi: extension.lock_cpi.into(),
}).ok()
}
ExtensionType::ConfidentialTransferFeeAmount => {
state_with_extensions.get_extension::<spl_token_2022::extension::confidential_transfer_fee::ConfidentialTransferFeeAmount>().map(|extension| Extension::ConfidentialTransferFeeAmount {
withheld_amount: EncryptedBalance(extension.withheld_amount.0),
}).ok()
}
ExtensionType::PausableAccount => {
state_with_extensions.get_extension::<spl_token_2022::extension::pausable::PausableAccount>().map(|_extension| Extension::PausableAccount).ok()
}
_ => None,
}
}
}