light-token 0.4.0

SDK for Light Tokens
Documentation
use light_compressed_account::instruction_data::{
    compressed_proof::CompressedProof, traits::LightInstructionData,
};
use light_token_interface::{
    self,
    instructions::{
        extensions::ExtensionInstructionData,
        mint_action::{CpiContext, MintInstructionData},
    },
    COMPRESSED_MINT_SEED,
};
use solana_instruction::Instruction;
use solana_msg::msg;
use solana_pubkey::Pubkey;

use crate::{
    compressed_token::mint_action::{
        get_mint_action_instruction_account_metas_cpi_write, MintActionMetaConfig,
        MintActionMetaConfigCpiWrite,
    },
    error::{Result, TokenSdkError},
    AnchorDeserialize, AnchorSerialize,
};

/// Input struct for creating a compressed mint instruction
#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize)]
pub struct CreateMintInputs {
    pub decimals: u8,
    pub mint_authority: Pubkey,
    pub freeze_authority: Option<Pubkey>,
    pub proof: CompressedProof,
    pub address_merkle_tree_root_index: u16,
    pub mint_signer: Pubkey,
    pub payer: Pubkey,
    pub address_tree_pubkey: Pubkey,
    pub output_queue: Pubkey,
    pub extensions: Option<Vec<ExtensionInstructionData>>,
    pub version: u8,
}

/// Creates a compressed mint instruction (wrapper around mint_action)
pub fn create_compressed_mint_cpi(
    input: CreateMintInputs,
    cpi_context: Option<CpiContext>,
    cpi_context_pubkey: Option<Pubkey>,
) -> Result<Instruction> {
    let (mint_pda, bump) = find_mint_address(&input.mint_signer);
    let compressed_mint_instruction_data = MintInstructionData {
        supply: 0,
        decimals: input.decimals,
        metadata: light_token_interface::state::MintMetadata {
            version: input.version,
            mint: mint_pda.to_bytes().into(),
            mint_decompressed: false,
            mint_signer: input.mint_signer.to_bytes(),
            bump,
        },
        mint_authority: Some(input.mint_authority.to_bytes().into()),
        freeze_authority: input.freeze_authority.map(|auth| auth.to_bytes().into()),
        extensions: input.extensions,
    };

    let mut instruction_data = light_token_interface::instructions::mint_action::MintActionCompressedInstructionData::new_mint(
        input.address_merkle_tree_root_index,
        input.proof,
        compressed_mint_instruction_data,
    );

    if let Some(ctx) = cpi_context {
        instruction_data = instruction_data.with_cpi_context(ctx);
    }

    let meta_config = if cpi_context_pubkey.is_some() {
        MintActionMetaConfig::new_cpi_context(
            &instruction_data,
            input.payer,
            input.mint_authority,
            cpi_context_pubkey.unwrap(),
        )?
    } else {
        MintActionMetaConfig::new_create_mint(
            input.payer,
            input.mint_authority,
            input.mint_signer,
            input.address_tree_pubkey,
            input.output_queue,
        )
    };

    let account_metas = meta_config.to_account_metas();

    let data = instruction_data
        .data()
        .map_err(|_| TokenSdkError::SerializationError)?;

    Ok(Instruction {
        program_id: solana_pubkey::Pubkey::new_from_array(
            light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
        ),
        accounts: account_metas,
        data,
    })
}

#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize)]
pub struct CreateMintInputsCpiWrite {
    pub decimals: u8,
    pub mint_authority: Pubkey,
    pub freeze_authority: Option<Pubkey>,
    pub address_merkle_tree_root_index: u16,
    pub mint_signer: Pubkey,
    pub payer: Pubkey,
    pub mint_address: [u8; 32],
    pub cpi_context: CpiContext,
    pub cpi_context_pubkey: Pubkey,
    pub extensions: Option<Vec<ExtensionInstructionData>>,
    pub version: u8,
}

pub fn create_compressed_mint_cpi_write(input: CreateMintInputsCpiWrite) -> Result<Instruction> {
    if !input.cpi_context.first_set_context && !input.cpi_context.set_context {
        msg!(
            "Invalid CPI context first cpi set or set context must be true {:?}",
            input.cpi_context
        );
        return Err(TokenSdkError::InvalidAccountData);
    }

    let (mint_pda, bump) = find_mint_address(&input.mint_signer);
    let compressed_mint_instruction_data = MintInstructionData {
        supply: 0,
        decimals: input.decimals,
        metadata: light_token_interface::state::MintMetadata {
            version: input.version,
            mint: mint_pda.to_bytes().into(),
            mint_decompressed: false,
            mint_signer: input.mint_signer.to_bytes(),
            bump,
        },
        mint_authority: Some(input.mint_authority.to_bytes().into()),
        freeze_authority: input.freeze_authority.map(|auth| auth.to_bytes().into()),
        extensions: input.extensions,
    };

    let instruction_data = light_token_interface::instructions::mint_action::MintActionCompressedInstructionData::new_mint_write_to_cpi_context(
        input.address_merkle_tree_root_index,
        compressed_mint_instruction_data,
        input.cpi_context,
    );

    let meta_config = MintActionMetaConfigCpiWrite {
        fee_payer: input.payer,
        mint_signer: Some(input.mint_signer),
        authority: input.mint_authority,
        cpi_context: input.cpi_context_pubkey,
    };

    let account_metas = get_mint_action_instruction_account_metas_cpi_write(meta_config);

    let data = instruction_data
        .data()
        .map_err(|_| TokenSdkError::SerializationError)?;

    Ok(Instruction {
        program_id: solana_pubkey::Pubkey::new_from_array(
            light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
        ),
        accounts: account_metas,
        data,
    })
}

/// Creates a compressed mint instruction with automatic mint address derivation
pub fn create_compressed_mint(input: CreateMintInputs) -> Result<Instruction> {
    create_compressed_mint_cpi(input, None, None)
}

/// Derives the compressed mint address from the mint seed and address tree
pub fn derive_mint_compressed_address(
    mint_seed: &Pubkey,
    address_tree_pubkey: &Pubkey,
) -> [u8; 32] {
    light_compressed_account::address::derive_address(
        &find_mint_address(mint_seed).0.to_bytes(),
        &address_tree_pubkey.to_bytes(),
        &light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
    )
}

pub fn derive_mint_from_spl_mint(mint: &Pubkey, address_tree_pubkey: &Pubkey) -> [u8; 32] {
    light_compressed_account::address::derive_address(
        &mint.to_bytes(),
        &address_tree_pubkey.to_bytes(),
        &light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
    )
}

pub fn find_mint_address(mint_seed: &Pubkey) -> (Pubkey, u8) {
    Pubkey::find_program_address(
        &[COMPRESSED_MINT_SEED, mint_seed.as_ref()],
        &Pubkey::new_from_array(light_token_interface::LIGHT_TOKEN_PROGRAM_ID),
    )
}