light-token-interface 0.5.0

Light Protocol token instruction data types.
Documentation
use light_compressed_account::{instruction_data::compressed_proof::CompressedProof, Pubkey};
use light_compressible::compression_info::CompressionInfo;
use light_zero_copy::ZeroCopy;

use super::{
    CompressAndCloseMintAction, CpiContext, DecompressMintAction, MintToAction,
    MintToCompressedAction, RemoveMetadataKeyAction, UpdateAuthority,
    UpdateMetadataAuthorityAction, UpdateMetadataFieldAction,
};
use crate::{
    instructions::extensions::{ExtensionInstructionData, ZExtensionInstructionData},
    state::{AdditionalMetadata, BaseMint, ExtensionStruct, Mint, MintMetadata, TokenMetadata},
    AnchorDeserialize, AnchorSerialize, TokenError,
};

#[repr(C)]
#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopy)]
pub enum Action {
    /// Mint compressed tokens to compressed accounts.
    MintToCompressed(MintToCompressedAction),
    /// Update mint authority of a compressed mint account.
    UpdateMintAuthority(UpdateAuthority),
    /// Update freeze authority of a compressed mint account.
    UpdateFreezeAuthority(UpdateAuthority),
    /// Mint tokens from a compressed mint to a token solana account
    /// (tokens are not compressed but not spl tokens).
    MintTo(MintToAction),
    UpdateMetadataField(UpdateMetadataFieldAction),
    UpdateMetadataAuthority(UpdateMetadataAuthorityAction),
    RemoveMetadataKey(RemoveMetadataKeyAction),
    /// Decompress a compressed mint to a Mint Solana account.
    /// Creates a Mint PDA that becomes the source of truth.
    DecompressMint(DecompressMintAction),
    /// Compress and close a Mint Solana account. The compressed mint state is preserved.
    /// Permissionless - anyone can call if is_compressible() returns true (rent expired).
    CompressAndCloseMint(CompressAndCloseMintAction),
}

#[repr(C)]
#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopy)]
pub struct MintActionCompressedInstructionData {
    /// Only set if mint already exists
    pub leaf_index: u32,
    /// Only set if mint already exists
    pub prove_by_index: bool,
    /// If create mint, root index of address proof
    /// If mint already exists, root index of validity proof
    /// If proof by index not used.
    pub root_index: u16,
    /// Maximum lamports for rent and top-up combined. Transaction fails if exceeded. (u16::MAX = no limit, 0 = no top-ups allowed)
    pub max_top_up: u16,
    pub create_mint: Option<CreateMint>,
    pub actions: Vec<Action>,
    pub proof: Option<CompressedProof>,
    pub cpi_context: Option<CpiContext>,
    pub mint: Option<MintInstructionData>,
}

#[repr(C)]
#[derive(Debug, Clone, AnchorSerialize, Default, AnchorDeserialize, ZeroCopy)]
pub struct CreateMint {
    /// Placeholder to enable mints in multiple address trees.
    /// Currently set to 0.
    pub read_only_address_trees: [u8; 4],
    /// Placeholder to enable mints in multiple address trees.
    /// Currently set to 0.
    pub read_only_address_tree_root_indices: [u16; 4],
}

#[repr(C)]
#[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopy, PartialEq)]
pub struct MintWithContext {
    pub leaf_index: u32,
    pub prove_by_index: bool,
    pub root_index: u16,
    pub address: [u8; 32],
    pub mint: Option<MintInstructionData>,
}

#[repr(C)]
#[derive(Debug, PartialEq, Eq, Clone, AnchorSerialize, AnchorDeserialize, ZeroCopy)]
pub struct MintInstructionData {
    /// Total supply of tokens.
    pub supply: u64,
    /// Number of base 10 digits to the right of the decimal place.
    pub decimals: u8,
    /// Light Protocol-specific metadata
    pub metadata: MintMetadata,
    /// Optional authority used to mint new tokens. The mint authority may only
    /// be provided during mint creation. If no mint authority is present
    /// then the mint has a fixed supply and no further tokens may be
    /// minted.
    pub mint_authority: Option<Pubkey>,
    /// Optional authority to freeze token accounts.
    pub freeze_authority: Option<Pubkey>,
    /// Extensions for additional functionality
    pub extensions: Option<Vec<ExtensionInstructionData>>,
}

impl TryFrom<Mint> for MintInstructionData {
    type Error = TokenError;

    fn try_from(mint: Mint) -> Result<Self, Self::Error> {
        let extensions = match mint.extensions {
            Some(exts) if !exts.is_empty() => {
                let mut extension_list = Vec::with_capacity(exts.len());
                for ext in exts {
                    match ext {
                        ExtensionStruct::TokenMetadata(token_metadata) => {
                            extension_list.push(ExtensionInstructionData::TokenMetadata(
                                crate::instructions::extensions::token_metadata::TokenMetadataInstructionData {
                                    update_authority: if token_metadata.update_authority == [0u8;32] {None}else {Some(token_metadata.update_authority)},
                                    name: token_metadata.name,
                                    symbol: token_metadata.symbol,
                                    uri: token_metadata.uri,
                                    additional_metadata: Some(token_metadata.additional_metadata),
                                },
                            ));
                        }
                        _ => {
                            return Err(TokenError::UnsupportedExtension);
                        }
                    }
                }
                Some(extension_list)
            }
            _ => None,
        };

        Ok(Self {
            supply: mint.base.supply,
            decimals: mint.base.decimals,
            metadata: mint.metadata,
            mint_authority: mint.base.mint_authority,
            freeze_authority: mint.base.freeze_authority,
            extensions,
        })
    }
}

impl<'a> TryFrom<&ZMintInstructionData<'a>> for Mint {
    type Error = TokenError;

    fn try_from(instruction_data: &ZMintInstructionData<'a>) -> Result<Self, Self::Error> {
        let extensions = match &instruction_data.extensions {
            Some(exts) => {
                let converted_exts: Vec<_> = exts
                    .iter()
                    .map(|ext| match ext {
                        ZExtensionInstructionData::TokenMetadata(token_metadata_data) => {
                            Ok(ExtensionStruct::TokenMetadata(TokenMetadata {
                                update_authority: token_metadata_data
                                    .update_authority
                                    .map(|p| *p)
                                    .unwrap_or_else(|| Pubkey::from([0u8; 32])),
                                mint: instruction_data.metadata.mint, // Use the mint from metadata
                                name: token_metadata_data.name.to_vec(),
                                symbol: token_metadata_data.symbol.to_vec(),
                                uri: token_metadata_data.uri.to_vec(),
                                additional_metadata: token_metadata_data
                                    .additional_metadata
                                    .as_ref()
                                    .map(|ams| {
                                        ams.iter()
                                            .map(|am| AdditionalMetadata {
                                                key: am.key.to_vec(),
                                                value: am.value.to_vec(),
                                            })
                                            .collect()
                                    })
                                    .unwrap_or_else(Vec::new),
                            }))
                        }
                        _ => Err(TokenError::UnsupportedExtension),
                    })
                    .collect::<Result<Vec<_>, _>>()?;
                if converted_exts.is_empty() {
                    None
                } else {
                    Some(converted_exts)
                }
            }
            None => None,
        };

        Ok(Self {
            base: BaseMint {
                mint_authority: instruction_data.mint_authority.map(|p| *p),
                supply: instruction_data.supply.into(),
                decimals: instruction_data.decimals,
                is_initialized: true, // Always true for compressed mints
                freeze_authority: instruction_data.freeze_authority.map(|p| *p),
            },
            metadata: MintMetadata {
                version: instruction_data.metadata.version,
                mint_decompressed: instruction_data.metadata.mint_decompressed != 0,
                mint: instruction_data.metadata.mint,
                mint_signer: instruction_data.metadata.mint_signer,
                bump: instruction_data.metadata.bump,
            },
            reserved: [0u8; 16],
            account_type: crate::state::mint::ACCOUNT_TYPE_MINT,
            compression: CompressionInfo::default(),
            extensions,
        })
    }
}