light_token/compressed_token/v2/create_compressed_mint/
instruction.rs

1use light_compressed_account::instruction_data::{
2    compressed_proof::CompressedProof, traits::LightInstructionData,
3};
4use light_token_interface::{
5    self,
6    instructions::{
7        extensions::ExtensionInstructionData,
8        mint_action::{CpiContext, MintInstructionData},
9    },
10    COMPRESSED_MINT_SEED,
11};
12use solana_instruction::Instruction;
13use solana_msg::msg;
14use solana_pubkey::Pubkey;
15
16use crate::{
17    compressed_token::mint_action::{
18        get_mint_action_instruction_account_metas_cpi_write, MintActionMetaConfig,
19        MintActionMetaConfigCpiWrite,
20    },
21    error::{Result, TokenSdkError},
22    AnchorDeserialize, AnchorSerialize,
23};
24
25/// Input struct for creating a compressed mint instruction
26#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize)]
27pub struct CreateMintInputs {
28    pub decimals: u8,
29    pub mint_authority: Pubkey,
30    pub freeze_authority: Option<Pubkey>,
31    pub proof: CompressedProof,
32    pub address_merkle_tree_root_index: u16,
33    pub mint_signer: Pubkey,
34    pub payer: Pubkey,
35    pub address_tree_pubkey: Pubkey,
36    pub output_queue: Pubkey,
37    pub extensions: Option<Vec<ExtensionInstructionData>>,
38    pub version: u8,
39}
40
41/// Creates a compressed mint instruction (wrapper around mint_action)
42pub fn create_compressed_mint_cpi(
43    input: CreateMintInputs,
44    cpi_context: Option<CpiContext>,
45    cpi_context_pubkey: Option<Pubkey>,
46) -> Result<Instruction> {
47    let (mint_pda, bump) = find_mint_address(&input.mint_signer);
48    let compressed_mint_instruction_data = MintInstructionData {
49        supply: 0,
50        decimals: input.decimals,
51        metadata: light_token_interface::state::MintMetadata {
52            version: input.version,
53            mint: mint_pda.to_bytes().into(),
54            mint_decompressed: false,
55            mint_signer: input.mint_signer.to_bytes(),
56            bump,
57        },
58        mint_authority: Some(input.mint_authority.to_bytes().into()),
59        freeze_authority: input.freeze_authority.map(|auth| auth.to_bytes().into()),
60        extensions: input.extensions,
61    };
62
63    let mut instruction_data = light_token_interface::instructions::mint_action::MintActionCompressedInstructionData::new_mint(
64        input.address_merkle_tree_root_index,
65        input.proof,
66        compressed_mint_instruction_data,
67    );
68
69    if let Some(ctx) = cpi_context {
70        instruction_data = instruction_data.with_cpi_context(ctx);
71    }
72
73    let meta_config = if cpi_context_pubkey.is_some() {
74        MintActionMetaConfig::new_cpi_context(
75            &instruction_data,
76            input.payer,
77            input.mint_authority,
78            cpi_context_pubkey.unwrap(),
79        )?
80    } else {
81        MintActionMetaConfig::new_create_mint(
82            input.payer,
83            input.mint_authority,
84            input.mint_signer,
85            input.address_tree_pubkey,
86            input.output_queue,
87        )
88    };
89
90    let account_metas = meta_config.to_account_metas();
91
92    let data = instruction_data
93        .data()
94        .map_err(|_| TokenSdkError::SerializationError)?;
95
96    Ok(Instruction {
97        program_id: solana_pubkey::Pubkey::new_from_array(
98            light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
99        ),
100        accounts: account_metas,
101        data,
102    })
103}
104
105#[derive(Debug, Clone, AnchorDeserialize, AnchorSerialize)]
106pub struct CreateMintInputsCpiWrite {
107    pub decimals: u8,
108    pub mint_authority: Pubkey,
109    pub freeze_authority: Option<Pubkey>,
110    pub address_merkle_tree_root_index: u16,
111    pub mint_signer: Pubkey,
112    pub payer: Pubkey,
113    pub mint_address: [u8; 32],
114    pub cpi_context: CpiContext,
115    pub cpi_context_pubkey: Pubkey,
116    pub extensions: Option<Vec<ExtensionInstructionData>>,
117    pub version: u8,
118}
119
120pub fn create_compressed_mint_cpi_write(input: CreateMintInputsCpiWrite) -> Result<Instruction> {
121    if !input.cpi_context.first_set_context && !input.cpi_context.set_context {
122        msg!(
123            "Invalid CPI context first cpi set or set context must be true {:?}",
124            input.cpi_context
125        );
126        return Err(TokenSdkError::InvalidAccountData);
127    }
128
129    let (mint_pda, bump) = find_mint_address(&input.mint_signer);
130    let compressed_mint_instruction_data = MintInstructionData {
131        supply: 0,
132        decimals: input.decimals,
133        metadata: light_token_interface::state::MintMetadata {
134            version: input.version,
135            mint: mint_pda.to_bytes().into(),
136            mint_decompressed: false,
137            mint_signer: input.mint_signer.to_bytes(),
138            bump,
139        },
140        mint_authority: Some(input.mint_authority.to_bytes().into()),
141        freeze_authority: input.freeze_authority.map(|auth| auth.to_bytes().into()),
142        extensions: input.extensions,
143    };
144
145    let instruction_data = light_token_interface::instructions::mint_action::MintActionCompressedInstructionData::new_mint_write_to_cpi_context(
146        input.address_merkle_tree_root_index,
147        compressed_mint_instruction_data,
148        input.cpi_context,
149    );
150
151    let meta_config = MintActionMetaConfigCpiWrite {
152        fee_payer: input.payer,
153        mint_signer: Some(input.mint_signer),
154        authority: input.mint_authority,
155        cpi_context: input.cpi_context_pubkey,
156    };
157
158    let account_metas = get_mint_action_instruction_account_metas_cpi_write(meta_config);
159
160    let data = instruction_data
161        .data()
162        .map_err(|_| TokenSdkError::SerializationError)?;
163
164    Ok(Instruction {
165        program_id: solana_pubkey::Pubkey::new_from_array(
166            light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
167        ),
168        accounts: account_metas,
169        data,
170    })
171}
172
173/// Creates a compressed mint instruction with automatic mint address derivation
174pub fn create_compressed_mint(input: CreateMintInputs) -> Result<Instruction> {
175    create_compressed_mint_cpi(input, None, None)
176}
177
178/// Derives the compressed mint address from the mint seed and address tree
179pub fn derive_mint_compressed_address(
180    mint_seed: &Pubkey,
181    address_tree_pubkey: &Pubkey,
182) -> [u8; 32] {
183    light_compressed_account::address::derive_address(
184        &find_mint_address(mint_seed).0.to_bytes(),
185        &address_tree_pubkey.to_bytes(),
186        &light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
187    )
188}
189
190pub fn derive_mint_from_spl_mint(mint: &Pubkey, address_tree_pubkey: &Pubkey) -> [u8; 32] {
191    light_compressed_account::address::derive_address(
192        &mint.to_bytes(),
193        &address_tree_pubkey.to_bytes(),
194        &light_token_interface::LIGHT_TOKEN_PROGRAM_ID,
195    )
196}
197
198pub fn find_mint_address(mint_seed: &Pubkey) -> (Pubkey, u8) {
199    Pubkey::find_program_address(
200        &[COMPRESSED_MINT_SEED, mint_seed.as_ref()],
201        &Pubkey::new_from_array(light_token_interface::LIGHT_TOKEN_PROGRAM_ID),
202    )
203}