1use anchor_lang::prelude::*;
2
3pub mod constants;
4pub mod process_compress_spl_token_account;
5pub mod process_mint;
6pub mod process_transfer;
7use process_compress_spl_token_account::process_compress_spl_token_account;
8pub mod spl_compression;
9pub use process_mint::*;
10pub mod token_data;
11pub use token_data::TokenData;
12pub mod delegation;
13pub mod freeze;
14pub mod instructions;
15pub use instructions::*;
16pub mod burn;
17pub use burn::*;
18pub mod batch_compress;
19use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext;
20
21use crate::process_transfer::CompressedTokenInstructionDataTransfer;
22declare_id!("cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m");
23
24#[cfg(not(feature = "no-entrypoint"))]
25solana_security_txt::security_txt! {
26 name: "light-compressed-token",
27 project_url: "lightprotocol.com",
28 contacts: "email:security@lightprotocol.com",
29 policy: "https://github.com/Lightprotocol/light-protocol/blob/main/SECURITY.md",
30 source_code: "https://github.com/Lightprotocol/light-protocol"
31}
32
33#[program]
34pub mod light_compressed_token {
35
36 use constants::{NOT_FROZEN, NUM_MAX_POOL_ACCOUNTS};
37 use light_zero_copy::borsh::Deserialize;
38 use spl_compression::check_spl_token_pool_derivation_with_index;
39
40 use super::*;
41
42 pub fn create_token_pool<'info>(
47 ctx: Context<'_, '_, '_, 'info, CreateTokenPoolInstruction<'info>>,
48 ) -> Result<()> {
49 create_token_pool::assert_mint_extensions(
50 &ctx.accounts.mint.to_account_info().try_borrow_data()?,
51 )
52 }
53
54 pub fn add_token_pool<'info>(
57 ctx: Context<'_, '_, '_, 'info, AddTokenPoolInstruction<'info>>,
58 token_pool_index: u8,
59 ) -> Result<()> {
60 if token_pool_index >= NUM_MAX_POOL_ACCOUNTS {
61 return err!(ErrorCode::InvalidTokenPoolBump);
62 }
63 check_spl_token_pool_derivation_with_index(
65 &ctx.accounts.mint.key().to_bytes(),
66 &ctx.accounts.existing_token_pool_pda.key(),
67 &[token_pool_index.saturating_sub(1)],
68 )
69 }
70
71 pub fn mint_to<'info>(
79 ctx: Context<'_, '_, '_, 'info, MintToInstruction<'info>>,
80 public_keys: Vec<Pubkey>,
81 amounts: Vec<u64>,
82 lamports: Option<u64>,
83 ) -> Result<()> {
84 process_mint_to_or_compress::<MINT_TO>(
85 ctx,
86 public_keys.as_slice(),
87 amounts.as_slice(),
88 lamports,
89 None,
90 None,
91 )
92 }
93
94 pub fn batch_compress<'info>(
96 ctx: Context<'_, '_, '_, 'info, MintToInstruction<'info>>,
97 inputs: Vec<u8>,
98 ) -> Result<()> {
99 let (inputs, _) = batch_compress::BatchCompressInstructionData::zero_copy_at(&inputs)
100 .map_err(ProgramError::from)?;
101 if inputs.amounts.is_some() && inputs.amount.is_some() {
102 return Err(crate::ErrorCode::AmountsAndAmountProvided.into());
103 }
104 let amounts = if let Some(amount) = inputs.amount {
105 vec![*amount; inputs.pubkeys.len()]
106 } else if let Some(amounts) = inputs.amounts {
107 amounts.to_vec()
108 } else {
109 return Err(crate::ErrorCode::NoAmount.into());
110 };
111
112 process_mint_to_or_compress::<COMPRESS>(
113 ctx,
114 inputs.pubkeys.as_slice(),
115 amounts.as_slice(),
116 inputs.lamports.map(|x| (*x).into()),
117 Some(inputs.index),
118 Some(inputs.bump),
119 )
120 }
121
122 pub fn compress_spl_token_account<'info>(
126 ctx: Context<'_, '_, '_, 'info, TransferInstruction<'info>>,
127 owner: Pubkey,
128 remaining_amount: Option<u64>,
129 cpi_context: Option<CompressedCpiContext>,
130 ) -> Result<()> {
131 process_compress_spl_token_account(ctx, owner, remaining_amount, cpi_context)
132 }
133
134 pub fn transfer<'info>(
143 ctx: Context<'_, '_, '_, 'info, TransferInstruction<'info>>,
144 inputs: Vec<u8>,
145 ) -> Result<()> {
146 let mut inputs = inputs;
147 inputs.extend_from_slice(&[0u8; 1]);
149 let inputs: CompressedTokenInstructionDataTransfer =
150 CompressedTokenInstructionDataTransfer::deserialize(&mut inputs.as_slice())?;
151 process_transfer::process_transfer(ctx, inputs)
152 }
153
154 pub fn approve<'info>(
161 ctx: Context<'_, '_, '_, 'info, GenericInstruction<'info>>,
162 inputs: Vec<u8>,
163 ) -> Result<()> {
164 delegation::process_approve(ctx, inputs)
165 }
166
167 pub fn revoke<'info>(
170 ctx: Context<'_, '_, '_, 'info, GenericInstruction<'info>>,
171 inputs: Vec<u8>,
172 ) -> Result<()> {
173 delegation::process_revoke(ctx, inputs)
174 }
175
176 pub fn freeze<'info>(
179 ctx: Context<'_, '_, '_, 'info, FreezeInstruction<'info>>,
180 inputs: Vec<u8>,
181 ) -> Result<()> {
182 freeze::process_freeze_or_thaw::<NOT_FROZEN, true>(ctx, inputs)
184 }
185
186 pub fn thaw<'info>(
189 ctx: Context<'_, '_, '_, 'info, FreezeInstruction<'info>>,
190 inputs: Vec<u8>,
191 ) -> Result<()> {
192 freeze::process_freeze_or_thaw::<true, NOT_FROZEN>(ctx, inputs)
194 }
195
196 pub fn burn<'info>(
200 ctx: Context<'_, '_, '_, 'info, BurnInstruction<'info>>,
201 inputs: Vec<u8>,
202 ) -> Result<()> {
203 burn::process_burn(ctx, inputs)
204 }
205
206 #[cfg(feature = "idl-build")]
210 pub fn stub_idl_build<'info>(
211 _ctx: Context<'_, '_, '_, 'info, TransferInstruction<'info>>,
212 _inputs1: CompressedTokenInstructionDataTransfer,
213 _inputs2: TokenData,
214 ) -> Result<()> {
215 Err(ErrorCode::InstructionNotCallable.into())
216 }
217}
218
219#[error_code]
220pub enum ErrorCode {
221 #[msg("public keys and amounts must be of same length")]
222 PublicKeyAmountMissmatch,
223 #[msg("ComputeInputSumFailed")]
224 ComputeInputSumFailed,
225 #[msg("ComputeOutputSumFailed")]
226 ComputeOutputSumFailed,
227 #[msg("ComputeCompressSumFailed")]
228 ComputeCompressSumFailed,
229 #[msg("ComputeDecompressSumFailed")]
230 ComputeDecompressSumFailed,
231 #[msg("SumCheckFailed")]
232 SumCheckFailed,
233 #[msg("DecompressRecipientUndefinedForDecompress")]
234 DecompressRecipientUndefinedForDecompress,
235 #[msg("CompressedPdaUndefinedForDecompress")]
236 CompressedPdaUndefinedForDecompress,
237 #[msg("DeCompressAmountUndefinedForDecompress")]
238 DeCompressAmountUndefinedForDecompress,
239 #[msg("CompressedPdaUndefinedForCompress")]
240 CompressedPdaUndefinedForCompress,
241 #[msg("DeCompressAmountUndefinedForCompress")]
242 DeCompressAmountUndefinedForCompress,
243 #[msg("DelegateSignerCheckFailed")]
244 DelegateSignerCheckFailed,
245 #[msg("Minted amount greater than u64::MAX")]
246 MintTooLarge,
247 #[msg("SplTokenSupplyMismatch")]
248 SplTokenSupplyMismatch,
249 #[msg("HeapMemoryCheckFailed")]
250 HeapMemoryCheckFailed,
251 #[msg("The instruction is not callable")]
252 InstructionNotCallable,
253 #[msg("ArithmeticUnderflow")]
254 ArithmeticUnderflow,
255 #[msg("HashToFieldError")]
256 HashToFieldError,
257 #[msg("Expected the authority to be also a mint authority")]
258 InvalidAuthorityMint,
259 #[msg("Provided authority is not the freeze authority")]
260 InvalidFreezeAuthority,
261 InvalidDelegateIndex,
262 TokenPoolPdaUndefined,
263 #[msg("Compress or decompress recipient is the same account as the token pool pda.")]
264 IsTokenPoolPda,
265 InvalidTokenPoolPda,
266 NoInputTokenAccountsProvided,
267 NoInputsProvided,
268 MintHasNoFreezeAuthority,
269 MintWithInvalidExtension,
270 #[msg("The token account balance is less than the remaining amount.")]
271 InsufficientTokenAccountBalance,
272 #[msg("Max number of token pools reached.")]
273 InvalidTokenPoolBump,
274 FailedToDecompress,
275 FailedToBurnSplTokensFromTokenPool,
276 NoMatchingBumpFound,
277 NoAmount,
278 AmountsAndAmountProvided,
279}