Skip to main content

light_token_client/actions/
create_mint.rs

1//! Create mint action for Light Token.
2//!
3//! This action provides a clean interface for creating a new Light Token mint.
4
5use light_client::{
6    indexer::{AddressWithTree, Indexer},
7    rpc::{Rpc, RpcError},
8};
9use light_token::instruction::{
10    derive_mint_compressed_address, find_mint_address, CreateMint as CreateMintInstruction,
11    CreateMintParams as CreateMintInstructionParams,
12};
13use light_token_interface::{
14    instructions::extensions::{ExtensionInstructionData, TokenMetadataInstructionData},
15    state::AdditionalMetadata,
16};
17use solana_keypair::Keypair;
18use solana_pubkey::Pubkey;
19use solana_signature::Signature;
20use solana_signer::Signer;
21
22/// Token metadata for the mint.
23#[derive(Clone, Debug, Default)]
24pub struct TokenMetadata {
25    /// The longer name of the token.
26    pub name: String,
27    /// The shortened symbol for the token.
28    pub symbol: String,
29    /// The URI pointing to richer metadata.
30    pub uri: String,
31    /// Authority that can update the metadata.
32    pub update_authority: Option<Pubkey>,
33    /// Additional metadata as key-value pairs.
34    pub additional_metadata: Option<Vec<(String, String)>>,
35}
36
37/// Parameters for creating a new Light Token mint.
38///
39/// This creates both a compressed mint AND a decompressed Mint Solana account
40/// in a single instruction.
41///
42/// # Example
43/// ```ignore
44/// let (signature, mint) = CreateMint {
45///     decimals: 9,
46///     freeze_authority: Some(freeze_authority_pubkey),
47///     token_metadata: Some(TokenMetadata {
48///         name: "My Token".to_string(),
49///         symbol: "MTK".to_string(),
50///         uri: "https://example.com/metadata.json".to_string(),
51///         ..Default::default()
52///     }),
53///     seed: None, // auto-generate, or Some(keypair) for deterministic address
54/// }.execute(&mut rpc, &payer, &mint_authority).await?;
55/// ```
56#[derive(Default, Debug)]
57pub struct CreateMint {
58    /// Number of decimals for the token.
59    pub decimals: u8,
60    /// Optional authority that can freeze token accounts.
61    pub freeze_authority: Option<Pubkey>,
62    /// Optional token metadata (name, symbol, uri).
63    pub token_metadata: Option<TokenMetadata>,
64    /// Optional seed keypair for deterministic mint address.
65    /// If None, a new keypair is generated.
66    pub seed: Option<Keypair>,
67}
68
69impl CreateMint {
70    /// Execute the create_mint action via RPC.
71    ///
72    /// # Arguments
73    /// * `rpc` - RPC client that implements both `Rpc` and `Indexer` traits
74    /// * `payer` - Transaction fee payer keypair
75    /// * `mint_authority` - Authority that can mint new tokens
76    ///
77    /// # Returns
78    /// `Result<(Signature, Pubkey), RpcError>` - The transaction signature and mint public key
79    pub async fn execute<R: Rpc + Indexer>(
80        self,
81        rpc: &mut R,
82        payer: &Keypair,
83        mint_authority: &Keypair,
84    ) -> Result<(Signature, Pubkey), RpcError> {
85        let mint_seed = self.seed.unwrap_or_else(Keypair::new);
86        let address_tree = rpc.get_address_tree_v2();
87        let output_queue = rpc.get_random_state_tree_info()?.queue;
88
89        // Derive compression address
90        let compression_address =
91            derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree);
92
93        // Find mint PDA
94        let (mint, bump) = find_mint_address(&mint_seed.pubkey());
95
96        // Get validity proof for the address
97        let rpc_result = rpc
98            .get_validity_proof(
99                vec![],
100                vec![AddressWithTree {
101                    address: compression_address,
102                    tree: address_tree.tree,
103                }],
104                None,
105            )
106            .await
107            .map_err(|e| RpcError::CustomError(format!("Failed to get validity proof: {}", e)))?
108            .value;
109
110        // Build extensions if token metadata is provided
111        let extensions = self.token_metadata.map(|metadata| {
112            let additional_metadata = metadata.additional_metadata.map(|items| {
113                items
114                    .into_iter()
115                    .map(|(key, value)| AdditionalMetadata {
116                        key: key.into_bytes(),
117                        value: value.into_bytes(),
118                    })
119                    .collect()
120            });
121
122            vec![ExtensionInstructionData::TokenMetadata(
123                TokenMetadataInstructionData {
124                    update_authority: Some(
125                        metadata
126                            .update_authority
127                            .unwrap_or_else(|| mint_authority.pubkey())
128                            .to_bytes()
129                            .into(),
130                    ),
131                    name: metadata.name.into_bytes(),
132                    symbol: metadata.symbol.into_bytes(),
133                    uri: metadata.uri.into_bytes(),
134                    additional_metadata,
135                },
136            )]
137        });
138
139        // Build params
140        let params = CreateMintInstructionParams {
141            decimals: self.decimals,
142            address_merkle_tree_root_index: rpc_result.addresses[0].root_index,
143            mint_authority: mint_authority.pubkey(),
144            proof: rpc_result.proof.0.ok_or_else(|| {
145                RpcError::CustomError("Validity proof is required for create_mint".to_string())
146            })?,
147            compression_address,
148            mint,
149            bump,
150            freeze_authority: self.freeze_authority,
151            extensions,
152            rent_payment: 16,  // ~24 hours rent
153            write_top_up: 766, // ~3 hours per write
154        };
155
156        // Create instruction
157        let instruction = CreateMintInstruction::new(
158            params,
159            mint_seed.pubkey(),
160            payer.pubkey(),
161            address_tree.tree,
162            output_queue,
163        )
164        .instruction()
165        .map_err(|e| RpcError::CustomError(format!("Failed to create instruction: {}", e)))?;
166
167        // Build signers list
168        let mut signers: Vec<&Keypair> = vec![payer, &mint_seed];
169        if mint_authority.pubkey() != payer.pubkey() {
170            signers.push(mint_authority);
171        }
172
173        // Send transaction
174        let signature = rpc
175            .create_and_send_transaction(&[instruction], &payer.pubkey(), &signers)
176            .await?;
177
178        Ok((signature, mint))
179    }
180}