light_account/lib.rs
1//! # Light Accounts
2//!
3//! Rent-free Light Accounts and Light Token Accounts for Anchor programs.
4//!
5//! ## How It Works
6//!
7//! **Light Accounts (PDAs)**
8//! 1. Create a Solana PDA normally (Anchor `init`)
9//! 2. Add `#[light_account(init)]` - becomes a Light Account
10//! 3. Use it as normal Solana account
11//! 4. When rent runs out, account compresses (cold state)
12//! 5. State preserved on-chain, client loads when needed (hot state)
13//! 6. When account is hot, use it as normal Solana account
14//!
15//! **Light Token Accounts (associated token accounts, Vaults)**
16//! - Use `#[light_account(init, associated_token, ...)]` for associated token accounts
17//! - Use `#[light_account(init, token, ...)]` for program-owned vaults
18//! - Cold/hot lifecycle
19//!
20//! **Light Mints**
21//! - Created via `CreateMintsCpi`
22//! - Cold/hot lifecycle
23//!
24//! ## Quick Start
25//!
26//! ### 1. Program Setup
27//!
28//! ```rust,ignore
29//! use light_account::{derive_light_cpi_signer, light_program, CpiSigner};
30//!
31//! declare_id!("Your11111111111111111111111111111111111111");
32//!
33//! pub const LIGHT_CPI_SIGNER: CpiSigner =
34//! derive_light_cpi_signer!("Your11111111111111111111111111111111111111");
35//!
36//! #[light_program]
37//! #[program]
38//! pub mod my_program {
39//! // ...
40//! }
41//! ```
42//!
43//! ### 2. State Definition
44//!
45//! ```rust,ignore
46//! use light_account::{CompressionInfo, LightAccount};
47//!
48//! #[derive(Default, LightAccount)]
49//! #[account]
50//! pub struct UserRecord {
51//! pub compression_info: CompressionInfo, // Required field
52//! pub owner: Pubkey,
53//! pub data: u64,
54//! }
55//! ```
56//!
57//! ### 3. Accounts Struct
58//!
59//! ```rust,ignore
60//! use light_account::{CreateAccountsProof, LightAccounts};
61//!
62//! #[derive(AnchorSerialize, AnchorDeserialize)]
63//! pub struct CreateParams {
64//! pub create_accounts_proof: CreateAccountsProof,
65//! pub owner: Pubkey,
66//! }
67//!
68//! #[derive(Accounts, LightAccounts)]
69//! #[instruction(params: CreateParams)]
70//! pub struct CreateRecord<'info> {
71//! #[account(mut)]
72//! pub fee_payer: Signer<'info>,
73//!
74//! /// CHECK: Compression config
75//! pub compression_config: AccountInfo<'info>,
76//!
77//! /// CHECK: Rent sponsor
78//! #[account(mut)]
79//! pub pda_rent_sponsor: AccountInfo<'info>,
80//!
81//! #[account(init, payer = fee_payer, space = 8 + UserRecord::INIT_SPACE, seeds = [b"record", params.owner.as_ref()], bump)]
82//! #[light_account(init)]
83//! pub record: Account<'info, UserRecord>,
84//!
85//! pub system_program: Program<'info, System>,
86//! }
87//! ```
88//!
89//! ## Account Types
90//!
91//! ### 1. Light Account (PDA)
92//!
93//! ```rust,ignore
94//! #[account(init, payer = fee_payer, space = 8 + MyRecord::INIT_SPACE, seeds = [...], bump)]
95//! #[light_account(init)]
96//! pub record: Account<'info, MyRecord>,
97//! ```
98//!
99//! ### 2. Light Account (zero-copy)
100//!
101//! ```rust,ignore
102//! #[account(init, payer = fee_payer, space = 8 + size_of::<MyZcRecord>(), seeds = [...], bump)]
103//! #[light_account(init, zero_copy)]
104//! pub record: AccountLoader<'info, MyZcRecord>,
105//! ```
106//!
107//! ### 3. Light Token Account (vault)
108//!
109//! **With `init` (Anchor-created):**
110//! ```rust,ignore
111//! #[account(mut, seeds = [b"vault", mint.key().as_ref()], bump)]
112//! #[light_account(init, token::seeds = [b"vault", self.mint.key()], token::owner_seeds = [b"vault_authority"])]
113//! pub vault: UncheckedAccount<'info>,
114//! ```
115//!
116//! **Without `init` (manual creation via `CreateTokenAccountCpi`):**
117//! ```rust,ignore
118//! #[account(mut, seeds = [b"vault", mint.key().as_ref()], bump)]
119//! #[light_account(token::seeds = [b"vault", self.mint.key()], token::owner_seeds = [b"vault_authority"])]
120//! pub vault: UncheckedAccount<'info>,
121//! ```
122//!
123//! ### 4. Light Token Account (associated token account)
124//!
125//! **With `init` (Anchor-created):**
126//! ```rust,ignore
127//! #[account(mut)]
128//! #[light_account(init, associated_token::authority = owner, associated_token::mint = mint)]
129//! pub token_account: UncheckedAccount<'info>,
130//! ```
131//!
132//! **Without `init` (manual creation via `CreateTokenAtaCpi`):**
133//! ```rust,ignore
134//! #[account(mut)]
135//! #[light_account(associated_token::authority = owner, associated_token::mint = mint)]
136//! pub token_account: UncheckedAccount<'info>,
137//! ```
138//!
139//! ### 5. Light Mint
140//!
141//! ```rust,ignore
142//! #[account(mut)]
143//! #[light_account(init,
144//! mint::signer = mint_signer, // PDA that signs mint creation
145//! mint::authority = mint_authority, // Mint authority
146//! mint::decimals = 9, // Token decimals
147//! mint::seeds = &[SEED, self.key.as_ref()], // Seeds for mint PDA
148//! mint::bump = params.bump, // Bump seed
149//! // Optional: PDA authority
150//! mint::authority_seeds = &[b"authority"],
151//! mint::authority_bump = params.auth_bump,
152//! // Optional: Token metadata
153//! mint::name = params.name,
154//! mint::symbol = params.symbol,
155//! mint::uri = params.uri,
156//! mint::update_authority = update_auth,
157//! mint::additional_metadata = params.metadata
158//! )]
159//! pub mint: UncheckedAccount<'info>,
160//! ```
161//!
162//! ## Required Derives
163//!
164//! | Derive | Use |
165//! |--------|-----|
166//! | `LightAccount` | State structs (must have `compression_info: CompressionInfo`) |
167//! | `LightAccounts` | Accounts structs with `#[light_account(...)]` fields |
168//!
169//! ## Required Macros
170//!
171//! | Macro | Use |
172//! |-------|-----|
173//! | `#[light_program]` | Program module (before `#[program]`) |
174//! | `derive_light_cpi_signer!` | CPI signer PDA constant |
175//! | `derive_light_rent_sponsor_pda!` | Rent sponsor PDA (optional) |
176
177pub use solana_account_info::AccountInfo;
178
179// ===== TYPE ALIASES (structs generic over AI, specialized with AccountInfo) =====
180
181pub type CpiAccounts<'c, 'info> =
182 light_sdk_types::cpi_accounts::v2::CpiAccounts<'c, AccountInfo<'info>>;
183
184pub type CompressCtx<'a, 'info> =
185 light_sdk_types::interface::program::compression::processor::CompressCtx<
186 'a,
187 AccountInfo<'info>,
188 >;
189
190pub type CompressDispatchFn<'info> =
191 light_sdk_types::interface::program::compression::processor::CompressDispatchFn<
192 AccountInfo<'info>,
193 >;
194
195pub type DecompressCtx<'a, 'info> =
196 light_sdk_types::interface::program::decompression::processor::DecompressCtx<
197 'a,
198 AccountInfo<'info>,
199 >;
200
201pub type ValidatedPdaContext<'info> =
202 light_sdk_types::interface::program::validation::ValidatedPdaContext<AccountInfo<'info>>;
203
204#[cfg(not(target_os = "solana"))]
205pub type PackedAccounts =
206 light_sdk_types::pack_accounts::PackedAccounts<solana_instruction::AccountMeta>;
207
208// ===== RE-EXPORTED TRAITS (generic over AI, used with explicit AccountInfo in impls) =====
209
210pub use light_account_checks::close_account;
211#[cfg(feature = "token")]
212pub use light_compressed_account::instruction_data::compressed_proof::CompressedProof;
213// ===== RE-EXPORTED CONCRETE TRAITS (no AI parameter) =====
214pub use light_sdk_types::interface::account::compression_info::{
215 claim_completed_epoch_rent, CompressAs, CompressedAccountData, CompressedInitSpace,
216 CompressionInfo, CompressionInfoField, CompressionState, HasCompressionInfo, Space,
217 COMPRESSION_INFO_SIZE, OPTION_COMPRESSION_INFO_SPACE,
218};
219#[cfg(not(target_os = "solana"))]
220pub use light_sdk_types::interface::account::pack::Pack;
221// ===== TOKEN-GATED RE-EXPORTS =====
222#[cfg(feature = "token")]
223pub use light_sdk_types::interface::account::token_seeds::{
224 PackedTokenData, TokenDataWithPackedSeeds, TokenDataWithSeeds,
225};
226// create_accounts SDK function and parameter types
227pub use light_sdk_types::interface::accounts::create_accounts::{
228 create_accounts, AtaInitParam, CreateMintsInput, PdaInitParam, SharedAccounts, TokenInitParam,
229};
230// Mint creation CPI types and functions
231#[cfg(feature = "token")]
232pub use light_sdk_types::interface::cpi::create_mints::{
233 derive_mint_compressed_address as derive_mint_compressed_address_generic,
234 get_output_queue_next_index, CreateMints, CreateMintsCpi, CreateMintsParams,
235 CreateMintsStaticAccounts, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP,
236};
237// Token account/ATA creation CPI types and functions
238#[cfg(feature = "token")]
239pub use light_sdk_types::interface::cpi::create_token_accounts::{
240 derive_associated_token_account as derive_associated_token_account_generic,
241 CreateTokenAccountCpi, CreateTokenAccountRentFreeCpi, CreateTokenAtaCpi,
242 CreateTokenAtaCpiIdempotent, CreateTokenAtaRentFreeCpi,
243};
244// ===== RE-EXPORTED GENERIC FUNCTIONS (AI inferred from call-site args) =====
245pub use light_sdk_types::interface::cpi::invoke::invoke_light_system_program;
246#[cfg(feature = "token")]
247pub use light_sdk_types::interface::program::decompression::processor::process_decompress_accounts_idempotent;
248#[cfg(feature = "token")]
249pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression;
250#[cfg(feature = "token")]
251pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds};
252pub use light_sdk_types::interface::{
253 account::{
254 light_account::{AccountType, LightAccount},
255 pack::Unpack,
256 pda_seeds::{HasTokenVariant, PdaSeedDerivation},
257 },
258 accounts::{
259 finalize::{LightFinalize, LightPreInit},
260 init_compressed_account::{prepare_compressed_account_on_init, reimburse_rent},
261 },
262 cpi::{
263 account::CpiAccountsTrait,
264 invoke::{invoke_write_pdas_to_cpi_context, InvokeLightSystemProgram},
265 LightCpi,
266 },
267 create_accounts_proof::CreateAccountsProof,
268 program::{
269 compression::{
270 pda::prepare_account_for_compression,
271 processor::{process_compress_pda_accounts_idempotent, CompressAndCloseParams},
272 },
273 config::{
274 process_initialize_light_config_checked, process_update_light_config,
275 InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, LIGHT_CONFIG_SEED,
276 MAX_ADDRESS_TREES_PER_SPACE,
277 },
278 decompression::{
279 pda::prepare_account_for_decompression,
280 processor::{
281 process_decompress_pda_accounts_idempotent, DecompressIdempotentParams,
282 DecompressVariant,
283 },
284 },
285 validation::{
286 extract_tail_accounts, is_pda_initialized, should_skip_compression,
287 split_at_system_accounts_offset, validate_compress_accounts,
288 validate_decompress_accounts,
289 },
290 variant::{IntoVariant, LightAccountVariantTrait, PackedLightAccountVariantTrait},
291 },
292 rent,
293};
294#[cfg(feature = "token")]
295pub use light_token_interface::instructions::extensions::ExtensionInstructionData as TokenExtensionInstructionData;
296// Token-interface re-exports for macro-generated code
297#[cfg(feature = "token")]
298pub use light_token_interface::instructions::extensions::TokenMetadataInstructionData;
299#[cfg(feature = "token")]
300pub use light_token_interface::state::AdditionalMetadata;
301/// Re-export Token state struct for client-side use.
302#[cfg(feature = "token")]
303pub use light_token_interface::state::{AccountState, Token};
304
305/// Token sub-module for paths like `light_account::token::TokenDataWithSeeds`.
306#[cfg(feature = "token")]
307pub mod token {
308 pub use light_sdk_types::interface::{
309 account::token_seeds::{
310 ExtensionInstructionData, MultiInputTokenDataWithContext, PackedTokenData,
311 TokenDataWithPackedSeeds, TokenDataWithSeeds,
312 },
313 program::decompression::token::prepare_token_account_for_decompression,
314 };
315 pub use light_token_interface::state::{AccountState, Token};
316}
317
318/// Compression info sub-module for paths like `light_account::compression_info::CompressedInitSpace`.
319pub mod compression_info {
320 pub use light_sdk_types::interface::account::compression_info::*;
321}
322
323// ===== CPI / SDK-TYPES RE-EXPORTS =====
324
325pub use light_sdk_types::{
326 cpi_accounts::CpiAccountsConfig, cpi_context_write::CpiContextWriteAccounts,
327 interface::program::config::create::process_initialize_light_config,
328};
329
330/// Sub-module for generic `PackedAccounts<AM>` (not specialized to AccountMeta).
331#[cfg(not(target_os = "solana"))]
332pub mod interface {
333 pub mod instruction {
334 pub use light_sdk_types::pack_accounts::PackedAccounts;
335 }
336}
337
338/// Sub-module for account_meta types (e.g. `CompressedAccountMetaNoLamportsNoAddress`).
339pub mod account_meta {
340 pub use light_sdk_types::instruction::account_meta::*;
341}
342
343// ===== ACCOUNT-CHECKS RE-EXPORTS (used by macro-generated code) =====
344
345/// Re-export `light_account_checks` so consumers can use `light_account::light_account_checks::*`.
346pub extern crate light_account_checks;
347// ===== CONVENIENCE RE-EXPORTS =====
348pub use light_account_checks::{
349 discriminator::Discriminator as LightDiscriminator, packed_accounts, AccountInfoTrait,
350 AccountMetaTrait,
351};
352pub use light_compressed_account::instruction_data::compressed_proof::ValidityProof;
353pub use light_compressible::rent::RentConfig;
354pub use light_macros::{derive_light_cpi_signer, derive_light_cpi_signer_pda};
355pub use light_sdk_macros::{
356 // Attribute macros
357 account,
358 // Proc macros
359 derive_light_rent_sponsor,
360 derive_light_rent_sponsor_pda,
361 light_program,
362 // Derive macros
363 AnchorDiscriminator as Discriminator,
364 CompressAs,
365 HasCompressionInfo,
366 LightAccount,
367 LightAccounts,
368 LightDiscriminator,
369 LightHasher,
370 LightHasherSha,
371 LightProgram,
372};
373pub use light_sdk_types::{
374 constants,
375 constants::{CPI_AUTHORITY_PDA_SEED, RENT_SPONSOR_SEED},
376 error::LightSdkTypesError,
377 instruction::*,
378 interface::account::size::Size,
379 CpiSigner,
380};
381
382/// Hasher re-exports for macro-generated code paths like `light_account::hasher::DataHasher`.
383pub mod hasher {
384 pub use light_hasher::{errors::HasherError, DataHasher, Hasher};
385}
386
387/// Re-export LIGHT_TOKEN_PROGRAM_ID as Pubkey for Anchor's `#[account(address = ...)]`.
388pub const LIGHT_TOKEN_PROGRAM_ID: solana_pubkey::Pubkey =
389 solana_pubkey::Pubkey::new_from_array(constants::LIGHT_TOKEN_PROGRAM_ID);
390
391/// Default compressible config PDA for the Light Token Program.
392pub const LIGHT_TOKEN_CONFIG: solana_pubkey::Pubkey =
393 solana_pubkey::Pubkey::new_from_array(constants::LIGHT_TOKEN_CONFIG);
394
395/// Default rent sponsor PDA for the Light Token Program.
396pub const LIGHT_TOKEN_RENT_SPONSOR: solana_pubkey::Pubkey =
397 solana_pubkey::Pubkey::new_from_array(constants::LIGHT_TOKEN_RENT_SPONSOR);
398
399// ===== UTILITY FUNCTIONS =====
400
401/// Converts a [`LightSdkTypesError`] into an [`anchor_lang::error::Error`].
402///
403/// Use with `.map_err(light_err)` in Anchor instruction handlers to disambiguate
404/// the multiple `From` implementations on `LightSdkTypesError`.
405#[cfg(feature = "anchor")]
406pub fn light_err(e: LightSdkTypesError) -> anchor_lang::error::Error {
407 anchor_lang::error::Error::from(e)
408}
409
410/// Derives the rent sponsor PDA for a given program.
411///
412/// Seeds: `["rent_sponsor"]`
413pub fn derive_rent_sponsor_pda(program_id: &solana_pubkey::Pubkey) -> (solana_pubkey::Pubkey, u8) {
414 solana_pubkey::Pubkey::find_program_address(&[constants::RENT_SPONSOR_SEED], program_id)
415}
416
417/// Find the mint PDA address for a given mint seed.
418///
419/// Returns `([u8; 32], u8)` -- the PDA address and bump.
420#[cfg(feature = "token")]
421pub fn find_mint_address(mint_seed: &[u8; 32]) -> ([u8; 32], u8) {
422 light_sdk_types::interface::cpi::create_mints::find_mint_address::<AccountInfo<'static>>(
423 mint_seed,
424 )
425}
426
427/// Derive the compressed mint address from a mint seed and address tree pubkey.
428#[cfg(feature = "token")]
429pub fn derive_mint_compressed_address(
430 mint_seed: &[u8; 32],
431 address_tree_pubkey: &[u8; 32],
432) -> [u8; 32] {
433 derive_mint_compressed_address_generic::<AccountInfo<'static>>(mint_seed, address_tree_pubkey)
434}
435
436/// Derive the associated token account address for a given owner and mint.
437///
438/// Returns the ATA address.
439#[cfg(feature = "token")]
440pub fn derive_associated_token_account(
441 owner: &solana_pubkey::Pubkey,
442 mint: &solana_pubkey::Pubkey,
443) -> solana_pubkey::Pubkey {
444 let bytes = derive_associated_token_account_generic::<AccountInfo<'static>>(
445 &owner.to_bytes(),
446 &mint.to_bytes(),
447 );
448 solana_pubkey::Pubkey::from(bytes)
449}