Skip to main content

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, associated_token::bump = params.bump)]
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// Mint creation CPI types and functions
227#[cfg(feature = "token")]
228pub use light_sdk_types::interface::cpi::create_mints::{
229    derive_mint_compressed_address as derive_mint_compressed_address_generic,
230    get_output_queue_next_index, CreateMints, CreateMintsCpi, CreateMintsParams,
231    CreateMintsStaticAccounts, SingleMintParams, DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP,
232};
233// Token account/ATA creation CPI types and functions
234#[cfg(feature = "token")]
235pub use light_sdk_types::interface::cpi::create_token_accounts::{
236    derive_associated_token_account as derive_associated_token_account_generic,
237    CreateTokenAccountCpi, CreateTokenAccountRentFreeCpi, CreateTokenAtaCpi,
238    CreateTokenAtaCpiIdempotent, CreateTokenAtaRentFreeCpi,
239};
240// ===== RE-EXPORTED GENERIC FUNCTIONS (AI inferred from call-site args) =====
241pub use light_sdk_types::interface::cpi::invoke::invoke_light_system_program;
242#[cfg(feature = "token")]
243pub use light_sdk_types::interface::program::decompression::processor::process_decompress_accounts_idempotent;
244#[cfg(feature = "token")]
245pub use light_sdk_types::interface::program::decompression::token::prepare_token_account_for_decompression;
246#[cfg(feature = "token")]
247pub use light_sdk_types::interface::program::variant::{PackedTokenSeeds, UnpackedTokenSeeds};
248pub use light_sdk_types::interface::{
249    account::{
250        light_account::{AccountType, LightAccount},
251        pack::Unpack,
252        pda_seeds::{HasTokenVariant, PdaSeedDerivation},
253    },
254    accounts::{
255        finalize::{LightFinalize, LightPreInit},
256        init_compressed_account::{prepare_compressed_account_on_init, reimburse_rent},
257    },
258    cpi::{
259        account::CpiAccountsTrait,
260        invoke::{invoke_write_pdas_to_cpi_context, InvokeLightSystemProgram},
261        LightCpi,
262    },
263    create_accounts_proof::CreateAccountsProof,
264    program::{
265        compression::{
266            pda::prepare_account_for_compression,
267            processor::{process_compress_pda_accounts_idempotent, CompressAndCloseParams},
268        },
269        config::{
270            process_initialize_light_config_checked, process_update_light_config,
271            InitializeLightConfigParams, LightConfig, UpdateLightConfigParams, LIGHT_CONFIG_SEED,
272            MAX_ADDRESS_TREES_PER_SPACE,
273        },
274        decompression::{
275            pda::prepare_account_for_decompression,
276            processor::{
277                process_decompress_pda_accounts_idempotent, DecompressIdempotentParams,
278                DecompressVariant,
279            },
280        },
281        validation::{
282            extract_tail_accounts, is_pda_initialized, should_skip_compression,
283            split_at_system_accounts_offset, validate_compress_accounts,
284            validate_decompress_accounts,
285        },
286        variant::{IntoVariant, LightAccountVariantTrait, PackedLightAccountVariantTrait},
287    },
288    rent,
289};
290#[cfg(feature = "token")]
291pub use light_token_interface::instructions::extensions::ExtensionInstructionData as TokenExtensionInstructionData;
292// Token-interface re-exports for macro-generated code
293#[cfg(feature = "token")]
294pub use light_token_interface::instructions::extensions::TokenMetadataInstructionData;
295#[cfg(feature = "token")]
296pub use light_token_interface::state::AdditionalMetadata;
297/// Re-export Token state struct for client-side use.
298#[cfg(feature = "token")]
299pub use light_token_interface::state::{AccountState, Token};
300
301/// Token sub-module for paths like `light_account::token::TokenDataWithSeeds`.
302#[cfg(feature = "token")]
303pub mod token {
304    pub use light_sdk_types::interface::{
305        account::token_seeds::{
306            ExtensionInstructionData, MultiInputTokenDataWithContext, PackedTokenData,
307            TokenDataWithPackedSeeds, TokenDataWithSeeds,
308        },
309        program::decompression::token::prepare_token_account_for_decompression,
310    };
311    pub use light_token_interface::state::{AccountState, Token};
312}
313
314/// Compression info sub-module for paths like `light_account::compression_info::CompressedInitSpace`.
315pub mod compression_info {
316    pub use light_sdk_types::interface::account::compression_info::*;
317}
318
319// ===== CPI / SDK-TYPES RE-EXPORTS =====
320
321pub use light_sdk_types::{
322    cpi_accounts::CpiAccountsConfig, cpi_context_write::CpiContextWriteAccounts,
323    interface::program::config::create::process_initialize_light_config,
324};
325
326/// Sub-module for generic `PackedAccounts<AM>` (not specialized to AccountMeta).
327#[cfg(not(target_os = "solana"))]
328pub mod interface {
329    pub mod instruction {
330        pub use light_sdk_types::pack_accounts::PackedAccounts;
331    }
332}
333
334/// Sub-module for account_meta types (e.g. `CompressedAccountMetaNoLamportsNoAddress`).
335pub mod account_meta {
336    pub use light_sdk_types::instruction::account_meta::*;
337}
338
339// ===== ACCOUNT-CHECKS RE-EXPORTS (used by macro-generated code) =====
340
341/// Re-export `light_account_checks` so consumers can use `light_account::light_account_checks::*`.
342pub extern crate light_account_checks;
343// ===== CONVENIENCE RE-EXPORTS =====
344pub use light_account_checks::{
345    discriminator::Discriminator as LightDiscriminator, packed_accounts, AccountInfoTrait,
346    AccountMetaTrait,
347};
348pub use light_compressed_account::instruction_data::compressed_proof::ValidityProof;
349pub use light_compressible::rent::RentConfig;
350pub use light_macros::{derive_light_cpi_signer, derive_light_cpi_signer_pda};
351pub use light_sdk_macros::{
352    // Attribute macros
353    account,
354    // Proc macros
355    derive_light_rent_sponsor,
356    derive_light_rent_sponsor_pda,
357    light_program,
358    // Derive macros
359    AnchorDiscriminator as Discriminator,
360    CompressAs,
361    HasCompressionInfo,
362    LightAccount,
363    LightAccounts,
364    LightDiscriminator,
365    LightHasher,
366    LightHasherSha,
367    LightProgram,
368};
369pub use light_sdk_types::{
370    constants,
371    constants::{CPI_AUTHORITY_PDA_SEED, RENT_SPONSOR_SEED},
372    error::LightSdkTypesError,
373    instruction::*,
374    interface::account::size::Size,
375    CpiSigner,
376};
377
378/// Hasher re-exports for macro-generated code paths like `light_account::hasher::DataHasher`.
379pub mod hasher {
380    pub use light_hasher::{errors::HasherError, DataHasher, Hasher};
381}
382
383/// Re-export LIGHT_TOKEN_PROGRAM_ID as Pubkey for Anchor's `#[account(address = ...)]`.
384pub const LIGHT_TOKEN_PROGRAM_ID: solana_pubkey::Pubkey =
385    solana_pubkey::Pubkey::new_from_array(constants::LIGHT_TOKEN_PROGRAM_ID);
386
387/// Default compressible config PDA for the Light Token Program.
388pub const LIGHT_TOKEN_CONFIG: solana_pubkey::Pubkey =
389    solana_pubkey::Pubkey::new_from_array(constants::LIGHT_TOKEN_CONFIG);
390
391/// Default rent sponsor PDA for the Light Token Program.
392pub const LIGHT_TOKEN_RENT_SPONSOR: solana_pubkey::Pubkey =
393    solana_pubkey::Pubkey::new_from_array(constants::LIGHT_TOKEN_RENT_SPONSOR);
394
395// ===== UTILITY FUNCTIONS =====
396
397/// Converts a [`LightSdkTypesError`] into an [`anchor_lang::error::Error`].
398///
399/// Use with `.map_err(light_err)` in Anchor instruction handlers to disambiguate
400/// the multiple `From` implementations on `LightSdkTypesError`.
401#[cfg(feature = "anchor")]
402pub fn light_err(e: LightSdkTypesError) -> anchor_lang::error::Error {
403    anchor_lang::error::Error::from(e)
404}
405
406/// Derives the rent sponsor PDA for a given program.
407///
408/// Seeds: `["rent_sponsor"]`
409pub fn derive_rent_sponsor_pda(program_id: &solana_pubkey::Pubkey) -> (solana_pubkey::Pubkey, u8) {
410    solana_pubkey::Pubkey::find_program_address(&[constants::RENT_SPONSOR_SEED], program_id)
411}
412
413/// Find the mint PDA address for a given mint seed.
414///
415/// Returns `([u8; 32], u8)` -- the PDA address and bump.
416#[cfg(feature = "token")]
417pub fn find_mint_address(mint_seed: &[u8; 32]) -> ([u8; 32], u8) {
418    light_sdk_types::interface::cpi::create_mints::find_mint_address::<AccountInfo<'static>>(
419        mint_seed,
420    )
421}
422
423/// Derive the compressed mint address from a mint seed and address tree pubkey.
424#[cfg(feature = "token")]
425pub fn derive_mint_compressed_address(
426    mint_seed: &[u8; 32],
427    address_tree_pubkey: &[u8; 32],
428) -> [u8; 32] {
429    derive_mint_compressed_address_generic::<AccountInfo<'static>>(mint_seed, address_tree_pubkey)
430}
431
432/// Derive the associated token account address for a given owner and mint.
433///
434/// Returns `(Pubkey, u8)` -- the ATA address and bump seed.
435#[cfg(feature = "token")]
436pub fn derive_associated_token_account(
437    owner: &solana_pubkey::Pubkey,
438    mint: &solana_pubkey::Pubkey,
439) -> (solana_pubkey::Pubkey, u8) {
440    let (bytes, bump) = derive_associated_token_account_generic::<AccountInfo<'static>>(
441        &owner.to_bytes(),
442        &mint.to_bytes(),
443    );
444    (solana_pubkey::Pubkey::from(bytes), bump)
445}