From 2395ef487b9eee64157518da05c1090a6e922556 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 26 Jan 2026 23:37:34 +0000 Subject: [PATCH 01/21] add rust client, programs, anchor --- .mintignore | 1 + .mintlifyignore | 1 + light-token/anchor.mdx | 33 + light-token/cookbook/approve-revoke.mdx | 280 +++++++ light-token/cookbook/burn.mdx | 157 ++++ light-token/cookbook/close-token-account.mdx | 355 ++------- light-token/cookbook/create-ata.mdx | 456 +++-------- light-token/cookbook/create-mint.mdx | 714 ++++-------------- light-token/cookbook/create-token-account.mdx | 385 +++------- light-token/cookbook/freeze-thaw.mdx | 266 +++++++ light-token/cookbook/load-ata.mdx | 8 +- light-token/cookbook/mint-to.mdx | 676 ++++------------- light-token/cookbook/transfer-checked.mdx | 150 ++++ light-token/cookbook/transfer-interface.mdx | 515 +++---------- light-token/cookbook/update-metadata.mdx | 0 light-token/cookbook/wrap-unwrap.mdx | 69 +- light-token/{cookbook => }/extensions.mdx | 0 light-token/toolkits/for-streaming-mints.mdx | 21 +- resources/sdks/instruction-decoder.mdx | 68 ++ resources/sdks/program-development.mdx | 1 + scripts/copy-light-token-snippets.sh | 2 +- scripts/copy-program-snippets.sh | 213 ++++++ scripts/copy-rust-snippets.sh | 164 ++++ ...ight-token-create-accounts-list-client.mdx | 2 +- .../light-token-create-accounts-list.mdx | 2 +- .../light-token-create-ata-accounts-list.mdx | 2 +- .../code-samples/code-compare-snippets.jsx | 303 ++++++++ .../rust-client/approve-action.mdx | 35 + .../rust-client/approve-full.mdx | 35 + .../rust-client/approve-instruction.mdx | 39 + .../rust-client/revoke-action.mdx | 31 + .../rust-client/revoke-full.mdx | 30 + .../rust-client/revoke-instruction.mdx | 34 + .../approve/anchor-program/full-example.mdx | 87 +++ .../approve/native-program/full-example.mdx | 138 ++++ .../burn-checked/rust-client/instruction.mdx | 44 ++ .../burn/anchor-program/full-example.mdx | 87 +++ .../burn/native-program/full-example.mdx | 168 +++++ .../burn/rust-client/instruction.mdx | 41 + .../anchor-program/full-example.mdx | 85 +++ .../native-program/full-example.mdx | 116 +++ .../rust-client/instruction.mdx | 28 + .../create-ata/anchor-macro/full-example.mdx | 212 ++++++ .../anchor-program/full-example.mdx | 172 +++++ .../light-token/create-ata/instruction.mdx | 6 +- .../native-program/full-example.mdx | 172 +++++ .../create-ata/rust-client/action.mdx | 33 + .../create-ata/rust-client/idempotent.mdx | 11 + .../create-ata/rust-client/instruction.mdx | 31 + .../create-mint/anchor-macro/full-example.mdx | 185 +++++ .../anchor-program/full-example.mdx | 229 ++++++ .../light-token/create-mint/instruction.mdx | 6 +- .../native-program/full-example.mdx | 513 +++++++++++++ .../create-mint/rust-client/action.mdx | 36 + .../create-mint/rust-client/instruction.mdx | 96 +++ .../anchor-macro/full-example.mdx | 240 ++++++ .../anchor-program/full-example.mdx | 160 ++++ .../native-program/full-example.mdx | 170 +++++ .../rust-client/instruction.mdx | 36 + .../freeze-thaw/rust-client/freeze-full.mdx | 32 + .../rust-client/freeze-instruction.mdx | 37 + .../freeze-thaw/rust-client/thaw-full.mdx | 32 + .../rust-client/thaw-instruction.mdx | 36 + .../freeze/anchor-program/full-example.mdx | 77 ++ .../freeze/native-program/full-example.mdx | 118 +++ .../light-token/load-ata/instruction.mdx | 2 +- .../native-program/full-example.mdx | 140 ++++ .../mint-to/anchor-program/full-example.mdx | 84 +++ .../light-token/mint-to/instruction.mdx | 6 +- .../mint-to/native-program/full-example.mdx | 187 +++++ .../mint-to/rust-client/action.mdx | 47 ++ .../mint-to/rust-client/instruction.mdx | 41 + .../revoke/anchor-program/full-example.mdx | 95 +++ .../revoke/native-program/full-example.mdx | 107 +++ .../thaw/anchor-program/full-example.mdx | 91 +++ .../thaw/native-program/full-example.mdx | 111 +++ .../anchor-program/full-example.mdx | 107 +++ .../native-program/full-example.mdx | 177 +++++ .../transfer-checked/rust-client/action.mdx | 52 ++ .../rust-client/instruction.mdx | 62 ++ .../anchor-program/full-example.mdx | 106 +++ .../transfer-interface/instruction.mdx | 6 +- .../native-program/full-example.mdx | 185 +++++ .../transfer-interface/rust-client/action.mdx | 50 ++ .../rust-client/instruction.mdx | 74 ++ .../light-token/unwrap/instruction.mdx | 2 +- .../light-token/unwrap/rust-client/action.mdx | 40 + .../light-token/wrap/instruction.mdx | 2 +- .../light-token/wrap/rust-client/action.mdx | 39 + snippets/jsx/code-compare.jsx | 125 +-- .../setup/compressed-pdas-program-setup.mdx | 4 +- snippets/setup/rust-install-dependencies.mdx | 8 +- snippets/versions/rust-deps-0.16.0.mdx | 5 - snippets/versions/rust-deps-0.19.0.mdx | 5 + .../versions/rust-deps-token-sdk-0.16.0.mdx | 8 - .../versions/rust-deps-token-sdk-0.19.0.mdx | 5 + 96 files changed, 7893 insertions(+), 2562 deletions(-) create mode 100644 .mintignore create mode 100644 .mintlifyignore create mode 100644 light-token/anchor.mdx create mode 100644 light-token/cookbook/approve-revoke.mdx create mode 100644 light-token/cookbook/burn.mdx create mode 100644 light-token/cookbook/freeze-thaw.mdx create mode 100644 light-token/cookbook/transfer-checked.mdx delete mode 100644 light-token/cookbook/update-metadata.mdx rename light-token/{cookbook => }/extensions.mdx (100%) create mode 100644 resources/sdks/instruction-decoder.mdx create mode 100755 scripts/copy-program-snippets.sh create mode 100755 scripts/copy-rust-snippets.sh create mode 100644 snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx create mode 100644 snippets/code-snippets/light-token/approve-revoke/rust-client/approve-full.mdx create mode 100644 snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx create mode 100644 snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx create mode 100644 snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-full.mdx create mode 100644 snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx create mode 100644 snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/approve/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/burn-checked/rust-client/instruction.mdx create mode 100644 snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/burn/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/burn/rust-client/instruction.mdx create mode 100644 snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/rust-client/action.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/rust-client/idempotent.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/rust-client/action.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx create mode 100644 snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx create mode 100644 snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-full.mdx create mode 100644 snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx create mode 100644 snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-full.mdx create mode 100644 snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx create mode 100644 snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/freeze/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/mint-to/rust-client/action.mdx create mode 100644 snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx create mode 100644 snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/revoke/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/thaw/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx create mode 100644 snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx create mode 100644 snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx create mode 100644 snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx create mode 100644 snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx create mode 100644 snippets/code-snippets/light-token/unwrap/rust-client/action.mdx create mode 100644 snippets/code-snippets/light-token/wrap/rust-client/action.mdx delete mode 100644 snippets/versions/rust-deps-0.16.0.mdx create mode 100644 snippets/versions/rust-deps-0.19.0.mdx delete mode 100644 snippets/versions/rust-deps-token-sdk-0.16.0.mdx create mode 100644 snippets/versions/rust-deps-token-sdk-0.19.0.mdx diff --git a/.mintignore b/.mintignore new file mode 100644 index 00000000..3c3629e6 --- /dev/null +++ b/.mintignore @@ -0,0 +1 @@ +node_modules diff --git a/.mintlifyignore b/.mintlifyignore new file mode 100644 index 00000000..c2658d7d --- /dev/null +++ b/.mintlifyignore @@ -0,0 +1 @@ +node_modules/ diff --git a/light-token/anchor.mdx b/light-token/anchor.mdx new file mode 100644 index 00000000..fa4772fe --- /dev/null +++ b/light-token/anchor.mdx @@ -0,0 +1,33 @@ +--- +title: Anchor +description: Using Anchor with light-token. +keywords: ["anchor, solana anchor, rust, light protocol anchor, light token anchor, token solana anchor, spl token anchor"] +--- + +The Light Token uses standard Anchor and a custom Light Account macro to reduce boilerplate code. Main macros include: + +* declare_id: Specifies the program's on-chain address +* #[program]: Specifies the module containing the program’s instruction logic +* #[derive(Accounts)]: Applied to structs to indicate a list of accounts required by an instruction +* #[account]: Applied to structs to create custom account types for the program + + +The #[derive(Accounts)] macro is applied to a struct +to specify the accounts that must be provided when an instruction is invoked. + +```rust +use light_sdk::compressible::CompressionInfo; +use light_sdk_macros::LightAccount; + +#[derive(Default, Debug, InitSpace, LightAccount)] +#[account] +pub struct PoolState { + /// Add this: + pub compression_info: Option, + + /// Your existing fields + /// ... +} +``` + +The LightAccounts trait complements the standard accounts trait \ No newline at end of file diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx new file mode 100644 index 00000000..b6eedafa --- /dev/null +++ b/light-token/cookbook/approve-revoke.mdx @@ -0,0 +1,280 @@ +--- +title: Approve and Revoke Delegates +sidebarTitle: Approve / Revoke +description: Rust client guide to approve and revoke delegates for light-token accounts. Includes step-by-step implementation and full code examples. +keywords: ["approve delegate solana", "revoke delegate solana", "token delegation"] +--- + +--- + +import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; +import { CodeCompare } from "/snippets/jsx/code-compare.jsx"; +import { + splApproveRustCode, + lightApproveRustCode, + splRevokeRustCode, + lightRevokeRustCode, +} from "/snippets/code-samples/code-compare-snippets.jsx"; +import ApproveActionCode from "/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx"; +import ApproveInstructionCode from "/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx"; +import RevokeActionCode from "/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx"; +import RevokeInstructionCode from "/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx"; +import ApproveAnchorProgramCode from "/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx"; +import ApproveNativeProgramCode from "/snippets/code-snippets/light-token/approve/native-program/full-example.mdx"; +import RevokeAnchorProgramCode from "/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx"; +import RevokeNativeProgramCode from "/snippets/code-snippets/light-token/revoke/native-program/full-example.mdx"; + +1. Approve grants a delegate permission to transfer up to a specified amount of tokens from your account. + * Each token account can have only one delegate at a time. + * Any new approval overwrites the previous one. +2. Revoke removes all delegate permissions from a light-token account. +3. Only the token account owner can approve or revoke delegates. + + + + + + + + + + + + + + + + + + + +### Prerequisites + + + + + + +### Approve or revoke delegates + + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Find [a full code example at the end](#full-code-example). + + + + + + + +### Build Account Infos and CPI the Light Token Program + +1. Define the light-token account, delegate, and amount to approve +2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. + + + + +```rust +use light_token::instruction::ApproveCpi; + +ApproveCpi { + token_account: token_account.clone(), + delegate: delegate.clone(), + owner: owner.clone(), + system_program: system_program.clone(), + amount, +} +.invoke()?; +``` + + + + +```rust +use light_token::instruction::ApproveCpi; + +let approve_cpi = ApproveCpi { + token_account: token_account.clone(), + delegate: delegate.clone(), + owner: owner.clone(), + system_program: system_program.clone(), + amount, +}; + +let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]]; +approve_cpi.invoke_signed(&[signer_seeds])?; +``` + + + + + + + + + + + + +### Build Account Infos and CPI the Light Token Program + +1. Define the light-token account to revoke delegation from +2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. + + + + +```rust +use light_token::instruction::RevokeCpi; + +RevokeCpi { + token_account: token_account.clone(), + owner: owner.clone(), + system_program: system_program.clone(), +} +.invoke()?; +``` + + + + +```rust +use light_token::instruction::RevokeCpi; + +let revoke_cpi = RevokeCpi { + token_account: token_account.clone(), + owner: owner.clone(), + system_program: system_program.clone(), +}; + +let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]]; +revoke_cpi.invoke_signed(&[signer_seeds])?; +``` + + + + + + + + + +# Full Code Example + + + + + + + + + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/approve). + + + + + + + + + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + + + + + + + + + + + + + + + + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/revoke). + + + + + + + + + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + + + + + + + + + + + + + + +# Next Steps + + diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx new file mode 100644 index 00000000..8b4f7fe9 --- /dev/null +++ b/light-token/cookbook/burn.mdx @@ -0,0 +1,157 @@ +--- +title: Burn Light Tokens +sidebarTitle: Burn +description: Rust client guide to burn light-tokens. Includes step-by-step implementation and full code examples. +keywords: ["burn tokens on solana", "destroy tokens solana", "reduce token supply"] +--- + +--- + +import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; +import { CodeCompare } from "/snippets/jsx/code-compare.jsx"; +import { + splBurnRustCode, + lightBurnRustCode, +} from "/snippets/code-samples/code-compare-snippets.jsx"; +import RustInstructionCode from "/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx"; +import BurnCheckedRustInstructionCode from "/snippets/code-snippets/light-token/burn-checked/rust-client/instruction.mdx"; +import AnchorProgramCode from "/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx"; +import NativeProgramCode from "/snippets/code-snippets/light-token/burn/native-program/full-example.mdx"; + +1. Burn permanently destroys tokens by reducing the balance in a token account. +2. Burned tokens are removed from circulation and decreases the supply tracked on the mint account. +3. Only the token account owner (or approved delegate) can burn tokens. + + + + +Compare to SPL: + + + + + +### Prerequisites + + + + + + +### Burn Light-Tokens + + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + + + + + + + + + + + + + + +Find [a full code example at the end](#full-code-example). + + + + + +### Build Account Infos and CPI the Light Token Program + +1. Use `invoke` when the authority is an external signer +2. Use `invoke_signed` when the authority is a PDA + + + + +```rust +use light_token::instruction::BurnCpi; + +BurnCpi { + source: source.clone(), + mint: mint.clone(), + amount, + authority: authority.clone(), + system_program: system_program.clone(), + fee_payer: None, + max_top_up: None, +} +.invoke() +``` + + + + +```rust +use light_token::instruction::BurnCpi; + +BurnCpi { + source: source.clone(), + mint: mint.clone(), + amount, + authority: authority.clone(), + system_program: system_program.clone(), + fee_payer: None, + max_top_up: None, +} +.invoke_signed(&[signer_seeds]) +``` + + + + + + + +# Full Code Example + + + + + + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/burn). + + + + + + + + + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + + + + + + + + + + + +# Next Steps + + diff --git a/light-token/cookbook/close-token-account.mdx b/light-token/cookbook/close-token-account.mdx index bcc10901..9eaca143 100644 --- a/light-token/cookbook/close-token-account.mdx +++ b/light-token/cookbook/close-token-account.mdx @@ -10,30 +10,38 @@ keywords: ["close token account on solana", "reclaim rent on solana"] import CloseAccountInfosAccountsList from "/snippets/accounts-list/close-account-infos-accounts-list.mdx"; import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; import ClientCustomRentConfig from "/snippets/light-token-guides/client-custom-rent-config.mdx"; +import { CodeCompare } from "/snippets/jsx/code-compare.jsx"; +import { + splCloseAccountRustCode, + lightCloseAccountRustCode, +} from "/snippets/code-samples/code-compare-snippets.jsx"; +import RustInstructionCode from "/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx"; +import AnchorProgramCode from "/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx"; +import NativeProgramCode from "/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx"; 1. Closing a light-token account transfers remaining lamports to a destination account and the rent sponsor can reclaim sponsored rent. -2. Light token accounts can be closed - - by the account owner at any time. - - by the `compression_authority` - when the account becomes compressible. The account is compressed and closed - it can be reinstated with the same state (decompressed). +2. Light token accounts can be closed by the owner. -## Get Started + +The `compression_authority` +closes the account and preserves the balance as compressed token account when the account becomes compressible. +The account is reinstated in flight with the same state the next time it is accessed. + -1. The example creates a light-token account and mint. -2. Build the instruction with `CloseTokenAccount`: +Use `CloseTokenAccount` to close an empty light-token account. -```rust -let close_instruction = CloseTokenAccount::new( - LIGHT_TOKEN_PROGRAM_ID, - account.pubkey(), - payer.pubkey(), // Destination for remaining lamports - owner, -) -.instruction() -``` +Compare to SPL: + + @@ -46,192 +54,22 @@ let close_instruction = CloseTokenAccount::new( ### Close light-token Account -```rust -use borsh::BorshDeserialize; -use light_client::indexer::{AddressWithTree, Indexer}; -use light_client::rpc::{LightClient, LightClientConfig, Rpc}; -use light_token_sdk::token::{ - CloseTokenAccount, CreateCMint, CreateCMintParams, CreateTokenAccount, LIGHT_TOKEN_PROGRAM_ID, -}; -use light_token_interface::state::Token; -use serde_json; -use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; -use std::convert::TryFrom; -use std::env; -use std::fs; - -#[tokio::test(flavor = "multi_thread")] -async fn test_close_token_account() { - dotenvy::dotenv().ok(); - - let keypair_path = env::var("KEYPAIR_PATH") - .unwrap_or_else(|_| format!("{}/.config/solana/id.json", env::var("HOME").unwrap())); - let payer = load_keypair(&keypair_path).expect("Failed to load keypair"); - - let api_key = env::var("api_key") // Set api_key in your .env - .expect("api_key environment variable must be set"); - - let config = LightClientConfig::devnet( - Some("https://devnet.helius-rpc.com".to_string()), - Some(api_key), - ); - let mut rpc = LightClient::new_with_retry(config, None) - .await - .expect("Failed to initialize LightClient"); - - // Step 1: Create compressed mint (prerequisite) - let (mint, _compression_address) = create_compressed_mint(&mut rpc, &payer, 9).await; - - // Step 2: Create token account with 0 balance - let account = Keypair::new(); - let owner = payer.pubkey(); - - let create_instruction = - CreateTokenAccount::new(payer.pubkey(), account.pubkey(), mint, owner) - .instruction() - .unwrap(); - - rpc.create_and_send_transaction(&[create_instruction], &payer.pubkey(), &[&payer, &account]) - .await - .unwrap(); - - // Step 3: Verify account exists before closing - let account_before_close = rpc.get_account(account.pubkey()).await.unwrap(); - assert!( - account_before_close.is_some(), - "Account should exist before closing" - ); - - let token_state = - Token::deserialize(&mut &account_before_close.unwrap().data[..]).unwrap(); - assert_eq!(token_state.amount, 0, "Account balance must be 0 to close"); - - // Step 4: Build close instruction using SDK builder - let close_instruction = CloseTokenAccount::new( - LIGHT_TOKEN_PROGRAM_ID, - account.pubkey(), - payer.pubkey(), // Destination for remaining lamports - owner, - ) - .instruction() - .unwrap(); - - // Step 5: Send close transaction - rpc.create_and_send_transaction(&[close_instruction], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Step 6: Verify account is closed - let account_after_close = rpc.get_account(account.pubkey()).await.unwrap(); - assert!( - account_after_close.is_none(), - "Account should be closed and no longer exist" - ); -} - -pub async fn create_compressed_mint( - rpc: &mut R, - payer: &Keypair, - decimals: u8, -) -> (Pubkey, [u8; 32]) { - let mint_signer = Keypair::new(); - let address_tree = rpc.get_address_tree_v2(); - - // Fetch active state trees for devnet - let _ = rpc.get_latest_active_state_trees().await; - let output_pubkey = match rpc - .get_random_state_tree_info() - .ok() - .or_else(|| rpc.get_random_state_tree_info_v1().ok()) - { - Some(info) => info - .get_output_pubkey() - .expect("Invalid state tree type for output"), - None => { - let queues = rpc - .indexer_mut() - .expect("IndexerNotInitialized") - .get_queue_info(None) - .await - .expect("Failed to fetch queue info") - .value - .queues; - queues - .get(0) - .map(|q| q.queue) - .expect("NoStateTreesAvailable: no active state trees returned") - } - }; - - // Derive compression address - let compression_address = light_token_sdk::token::derive_cmint_compressed_address( - &mint_signer.pubkey(), - &address_tree.tree, - ); - - let mint_pda = light_token_sdk::token::find_cmint_address(&mint_signer.pubkey()).0; - - // Get validity proof for the address - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - // Build params - let params = CreateCMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority: payer.pubkey(), - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint: mint_pda, - freeze_authority: None, - extensions: None, - }; - - // Create instruction - let create_cmint = CreateCMint::new( - params, - mint_signer.pubkey(), - payer.pubkey(), - address_tree.tree, - output_pubkey, - ); - let instruction = create_cmint.instruction().unwrap(); - - // Send transaction - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_signer]) - .await - .unwrap(); - - (mint_pda, compression_address) -} + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + -fn load_keypair(path: &str) -> Result> { - let path = if path.starts_with("~") { - path.replace("~", &env::var("HOME").unwrap_or_default()) - } else { - path.to_string() - }; - let file = fs::read_to_string(&path)?; - let bytes: Vec = serde_json::from_str(&file)?; - Ok(Keypair::try_from(&bytes[..])?) -} -``` + + + + + - + Find [a full code example at the end](#full-code-example). @@ -239,43 +77,41 @@ Find [a full code example at the end](#full-code-example). -### Build Account Infos and CPI the light token program +### Build Account Infos and CPI the Light Token Program -1. Define the light-token account to close and where remaining lamports should go -2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. +1. Define the account to close and destination for remaining lamports +2. Use `invoke` or `invoke_signed` when a CPI requires a PDA signer. - + ```rust -use light_token_sdk::token::CloseTokenAccountCpi; +use light_token::instruction::CloseAccountCpi; -CloseTokenAccountCpi { +CloseAccountCpi { token_program: token_program.clone(), account: account.clone(), destination: destination.clone(), owner: owner.clone(), - rent_sponsor: Some(rent_sponsor.clone()), + rent_sponsor: rent_sponsor.clone(), } .invoke()?; ``` - + ```rust -use light_token_sdk::token::CloseTokenAccountCpi; +use light_token::instruction::CloseAccountCpi; -let close_account_cpi = CloseTokenAccountCpi { +CloseAccountCpi { token_program: token_program.clone(), account: account.clone(), destination: destination.clone(), owner: owner.clone(), - rent_sponsor: Some(rent_sponsor.clone()), -}; - -let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]]; -close_account_cpi.invoke_signed(&[signer_seeds])?; + rent_sponsor: rent_sponsor.clone(), +} +.invoke_signed(&[signer_seeds])?; ``` @@ -290,88 +126,29 @@ close_account_cpi.invoke_signed(&[signer_seeds])?; # Full Code Example + + + - Find the source code - [here](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-tests/sdk-light-token-test/src/close.rs). + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/close). -```rust expandable -use light_token_sdk::token::CloseTokenAccountCpi; -use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; - -use crate::{ID, TOKEN_ACCOUNT_SEED}; - -/// Handler for closing a compressed token account (invoke) -/// -/// Account order: -/// - accounts[0]: token_program (light token program) -/// - accounts[1]: account to close (writable) -/// - accounts[2]: destination for lamports (writable) -/// - accounts[3]: owner/authority (signer) -/// - accounts[4]: rent_sponsor (optional, writable) -pub fn process_close_account_invoke(accounts: &[AccountInfo]) -> Result<(), ProgramError> { - if accounts.len() < 4 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - let rent_sponsor = if accounts.len() > 4 { - Some(accounts[4].clone()) - } else { - None - }; - - CloseTokenAccountCpi { - token_program: accounts[0].clone(), - account: accounts[1].clone(), - destination: accounts[2].clone(), - owner: accounts[3].clone(), - rent_sponsor, - } - .invoke()?; - - Ok(()) -} + -/// Handler for closing a PDA-owned compressed token account (invoke_signed) -/// -/// Account order: -/// - accounts[0]: token_program (light token program) -/// - accounts[1]: account to close (writable) -/// - accounts[2]: destination for lamports (writable) -/// - accounts[3]: PDA owner/authority (not signer, program signs) -/// - accounts[4]: rent_sponsor (optional, writable) -pub fn process_close_account_invoke_signed(accounts: &[AccountInfo]) -> Result<(), ProgramError> { - if accounts.len() < 4 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Derive the PDA for the authority - let (pda, bump) = Pubkey::find_program_address(&[TOKEN_ACCOUNT_SEED], &ID); - - // Verify the authority account is the PDA we expect - if &pda != accounts[3].key { - return Err(ProgramError::InvalidSeeds); - } - - let rent_sponsor = if accounts.len() > 4 { - Some(accounts[4].clone()) - } else { - None - }; - - let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]]; - CloseTokenAccountCpi { - token_program: accounts[0].clone(), - account: accounts[1].clone(), - destination: accounts[2].clone(), - owner: accounts[3].clone(), - rent_sponsor, - } - .invoke_signed(&[signer_seeds])?; - - Ok(()) -} -``` + + + + + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + + + + + + diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index 1ce4c393..b21a99a0 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -8,34 +8,32 @@ keywords: ["associated token account on solana", "create ata for solana tokens", --- import TokenCreateATAAccountsList from "/snippets/accounts-list/light-token-create-ata-accounts-list.mdx"; -import CompressibleVsSolanaRent from "/snippets/compressible-vs-solana-rent.mdx"; import TokenConfigureRent from "/snippets/light-token-configure-rent.mdx"; -import AtaIntro from "/snippets/light-token-guides/cata-intro.mdx"; import CompressibleRentExplained from "/snippets/compressible-rent-explained.mdx"; import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; -import TokenTsClientPrerequisites from "/snippets/light-token-guides/light-token-ts-client-prerequisites.mdx"; -import ClientCustomRentConfig from "/snippets/light-token-guides/client-custom-rent-config.mdx"; import { CodeCompare } from "/snippets/jsx/code-compare.jsx"; import FullSetup from "/snippets/setup/full-setup.mdx"; import { - splCreateAtaCode, - lightCreateAtaCode, + splCreateAtaCode, + lightCreateAtaCode, + splCreateAtaRustCode, + lightCreateAtaRustCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import ActionCode from "/snippets/code-snippets/light-token/create-ata/action.mdx"; import InstructionCode from "/snippets/code-snippets/light-token/create-ata/instruction.mdx"; +import RustActionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx"; +import RustInstructionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx"; +import AnchorProgramCode from "/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx"; +import AnchorMacroCode from "/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx"; +import NativeProgramCode from "/snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx"; -1. Associated light-token accounts are Solana accounts that hold token balances of light, SPL, or Token 2022 mints. -2. The address for light-ATAs is deterministically derived with the owner's address, Light Token Program ID, and mint address. -3. Associated light-token accounts implement a default rent config: - 1. At account creation, you pay ~17,208 lamports for 24h of rent
and compression incentive (the rent-exemption is sponsored by the protocol) - 2. Transfers keep the account funded with rent for 3h via top-ups. The transaction payer tops up 776 lamports when the account's rent is below 3h. +1. Associated light-token accounts can hold token balances of light, SPL, or Token 2022 mints. +2. Light-ATAs are on-chain accounts like SPL ATA's, but the light token program sponsors the rent-exemption cost for you. -## Get Started - @@ -45,10 +43,10 @@ The `createAtaInterface` function creates an associated light-token account in a Compare to SPL: @@ -75,57 +73,21 @@ Compare to SPL:
-## Custom Rent Config - - -The SDK uses the protocol's rent sponsor and config PDA by default. To pass a custom config explicitly: - -```typescript -import { createAssociatedTokenAccountInterfaceInstruction } from "@lightprotocol/compressed-token"; - -const ix = createAssociatedTokenAccountInterfaceInstruction( - payer.publicKey, - associatedToken, - owner.publicKey, - mint, - undefined, // token program id, defaults to light-token - undefined, // associated token program id, defaults to light-token - { - compressibleConfig: { - tokenAccountVersion: 3, - rentPayment: 16, - compressionOnly: 1, - writeTopUp: 766, - compressToAccountPubkey: null, - }, - configAccount: new PublicKey("ACXg8a7VaqecBWrSbdu73W4Pg9gsqXJ3EXAqkHyhvVXg"), // light token default config pda - rentPayerPda: new PublicKey("r18WwUxfG8kQ69bQPAB2jV6zGNKy3GosFGctjQoV4ti"), // light token default rent sponsor pda - }, -); -``` - - -
-1. The example creates a test light-mint. You can use existing light-mints, SPL or Token 2022 mints as well. -2. Derive the address from mint and owner pubkey. -3. Build the instruction with `CreateAssociatedTokenAccount`. It automatically includes the default rent config: - -```rust -use light_token_sdk::token::CreateAssociatedTokenAccount; +`CreateAssociatedTokenAccount` creates an on-chain ATA to store token balances of light, SPL, or Token 2022 mints. -let instruction = CreateAssociatedTokenAccount::new( - payer.pubkey(), - owner, - mint, -) -.instruction()?; -``` +Compare to SPL: -4. Send transaction & verify light-ATA creation with `get_account`. + @@ -140,235 +102,88 @@ let instruction = CreateAssociatedTokenAccount::new( ### Create ATA -```rust -use borsh::BorshDeserialize; -use light_client::indexer::{AddressWithTree, Indexer}; -use light_client::rpc::{LightClient, LightClientConfig, Rpc}; -use light_token_sdk::token::{ - derive_token_ata, CreateAssociatedTokenAccount, CreateCMint, - CreateCMintParams, -}; -use light_token_interface::state::Token; -use serde_json; -use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; -use std::convert::TryFrom; -use std::env; -use std::fs; - -#[tokio::test(flavor = "multi_thread")] -async fn test_create_associated_token_account() { - dotenvy::dotenv().ok(); - - let keypair_path = env::var("KEYPAIR_PATH") - .unwrap_or_else(|_| format!("{}/.config/solana/id.json", env::var("HOME").unwrap())); - let payer = load_keypair(&keypair_path).expect("Failed to load keypair"); - - let api_key = env::var("api_key") - .expect("api_key environment variable must be set"); - - let config = LightClientConfig::devnet( - Some("https://devnet.helius-rpc.com".to_string()), - Some(api_key), - ); - let mut rpc = LightClient::new_with_retry(config, None) - .await - .expect("Failed to initialize LightClient"); - - // Step 1: Create compressed mint (prerequisite) - let (mint, _compression_address) = create_compressed_mint(&mut rpc, &payer, 9).await; - - // Step 2: Define owner and derive ATA address - let owner = payer.pubkey(); - let (ata_address, _bump) = derive_token_ata(&owner, &mint); - - // Step 3: Build instruction using SDK builder - let instruction = CreateAssociatedTokenAccount::new( - payer.pubkey(), - owner, - mint, - ) - .instruction() - .unwrap(); - - // Step 4: Send transaction (only payer signs, no account keypair needed) - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Step 5: Verify light-ATA creation - let account_data = rpc.get_account(ata_address).await.unwrap().unwrap(); - let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); - - assert_eq!(token_state.mint, mint.to_bytes(), "Mint should match"); - assert_eq!(token_state.owner, owner.to_bytes(), "Owner should match"); - assert_eq!(token_state.amount, 0, "Initial amount should be 0"); -} - -pub async fn create_compressed_mint( - rpc: &mut R, - payer: &Keypair, - decimals: u8, -) -> (Pubkey, [u8; 32]) { - let mint_signer = Keypair::new(); - let address_tree = rpc.get_address_tree_v2(); - - let _ = rpc.get_latest_active_state_trees().await; - let output_pubkey = match rpc - .get_random_state_tree_info() - .ok() - .or_else(|| rpc.get_random_state_tree_info_v1().ok()) - { - Some(info) => info - .get_output_pubkey() - .expect("Invalid state tree type for output"), - None => { - let queues = rpc - .indexer_mut() - .expect("IndexerNotInitialized") - .get_queue_info(None) - .await - .expect("Failed to fetch queue info") - .value - .queues; - queues - .get(0) - .map(|q| q.queue) - .expect("NoStateTreesAvailable") - } - }; - - // Derive compression address - let compression_address = light_token_sdk::token::derive_cmint_compressed_address( - &mint_signer.pubkey(), - &address_tree.tree, - ); - - let mint_pda = - light_token_sdk::token::find_cmint_address(&mint_signer.pubkey()).0; - - // Get validity proof for the address - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - // Build params - let params = CreateCMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority: payer.pubkey(), - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint: mint_pda, - freeze_authority: None, - extensions: None, - }; - - // Create instruction - let create_cmint = CreateCMint::new( - params, - mint_signer.pubkey(), - payer.pubkey(), - address_tree.tree, - output_pubkey, - ); - let instruction = create_cmint.instruction().unwrap(); - - // Send transaction - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_signer]) - .await - .unwrap(); - - (mint_pda, compression_address) -} + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + -fn load_keypair(path: &str) -> Result> { - let path = if path.starts_with("~") { - path.replace("~", &env::var("HOME").unwrap_or_default()) - } else { - path.to_string() - }; - let file = fs::read_to_string(&path)?; - let bytes: Vec = serde_json::from_str(&file)?; - Ok(Keypair::try_from(&bytes[..])?) -} -``` + + + + + + + + - + Find [a full code example at the end](#full-code-example). - - -### Define Rent Config Accounts - - - - ### Build Account Infos and CPI the Light Token Program -1. Pass the required accounts that include the rent config. -2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. - - The light-ATA address is derived from `[owner, light_token_program_id, mint]`. - Unlike light-token accounts, owner and mint are passed as accounts, not in - instruction data. - +1. Pass ATA accounts and call `.rent_free()` with rent config accounts. +2. Use `invoke` or `invoke_signed`: + - When the `payer` is an external wallet, use `invoke`. + - When the `payer` is a PDA, use `invoke_signed` with its seeds. + + + The light-ATA address is derived from `[owner, light_token_program_id, mint]`. + Unlike light-token accounts, owner and mint are passed as accounts, not in + instruction data. + ```rust -use light_token_sdk::token::CreateAssociatedTokenAccountCpi; +use light_token::instruction::CreateAssociatedAccountCpi; -CreateAssociatedTokenAccountCpi { +CreateAssociatedAccountCpi { + payer: payer.clone(), owner: owner.clone(), mint: mint.clone(), - payer: payer.clone(), - associated_token_account: associated_token_account.clone(), - system_program: system_program.clone(), - bump: data.bump, - compressible: Some(compressible_params), - idempotent: false, + ata: associated_token_account.clone(), + bump, } -.invoke()?; +.rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), +) +.invoke() ``` - + ```rust -use light_token_sdk::token::CreateAssociatedTokenAccountCpi; +use light_token::instruction::CreateAssociatedAccountCpi; -let signer_seeds: &[&[u8]] = &[ATA_SEED, &[bump]]; -CreateAssociatedTokenAccountCpi { +let signer_seeds: &[&[u8]] = &[ATA_SEED, &[authority_bump]]; + +CreateAssociatedAccountCpi { + payer: payer.clone(), owner: owner.clone(), mint: mint.clone(), - payer: payer.clone(), - associated_token_account: associated_token_account.clone(), - system_program: system_program.clone(), - bump: data.bump, - compressible: Some(compressible_params), - idempotent: false, + ata: associated_token_account.clone(), + bump, } -.invoke_signed(&[signer_seeds])?; +.rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), +) +.invoke_signed(&[signer_seeds]) ``` @@ -379,113 +194,40 @@ CreateAssociatedTokenAccountCpi { # Full Code Example + + + - Find the source code - [here](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-tests/sdk-light-token-test/src/create_ata.rs). + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-ata). -```rust expandable -use borsh::{BorshDeserialize, BorshSerialize}; -use light_token_sdk::token::{CompressibleParamsCpi, CreateAssociatedTokenAccountCpi}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; + -use crate::{ATA_SEED, ID}; + -/// Instruction data for create ATA V2 (owner/mint as accounts) -#[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct CreateAta2Data { - pub bump: u8, - pub pre_pay_num_epochs: u8, - pub lamports_per_write: u32, -} + -/// Handler for creating ATA using V2 variant (invoke) -/// -/// Account order: -/// - accounts[0]: owner (readonly) -/// - accounts[1]: mint (readonly) -/// - accounts[2]: payer (signer, writable) -/// - accounts[3]: associated_token_account (writable) -/// - accounts[4]: system_program -/// - accounts[5]: compressible_config -/// - accounts[6]: rent_sponsor (writable) -pub fn process_create_ata2_invoke( - accounts: &[AccountInfo], - data: CreateAta2Data, -) -> Result<(), ProgramError> { - if accounts.len() < 7 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - let compressible_params = CompressibleParamsCpi::new( - accounts[5].clone(), - accounts[6].clone(), - accounts[4].clone(), - ); - - CreateAssociatedTokenAccountCpi { - owner: accounts[0].clone(), - mint: accounts[1].clone(), - payer: accounts[2].clone(), - associated_token_account: accounts[3].clone(), - system_program: accounts[4].clone(), - bump: data.bump, - compressible: Some(compressible_params), - idempotent: false, - } - .invoke()?; - - Ok(()) -} + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + -/// Handler for creating ATA using V2 variant with PDA ownership (invoke_signed) -/// -/// Account order: -/// - accounts[0]: owner (PDA, readonly) -/// - accounts[1]: mint (readonly) -/// - accounts[2]: payer (PDA, writable, not signer - program signs) -/// - accounts[3]: associated_token_account (writable) -/// - accounts[4]: system_program -/// - accounts[5]: compressible_config -/// - accounts[6]: rent_sponsor (writable) -pub fn process_create_ata2_invoke_signed( - accounts: &[AccountInfo], - data: CreateAta2Data, -) -> Result<(), ProgramError> { - if accounts.len() < 7 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Derive the PDA that will act as payer - let (pda, bump) = Pubkey::find_program_address(&[ATA_SEED], &ID); - - // Verify the payer is the PDA - if &pda != accounts[2].key { - return Err(ProgramError::InvalidSeeds); - } - - let compressible_params = CompressibleParamsCpi::new( - accounts[5].clone(), - accounts[6].clone(), - accounts[4].clone(), - ); - - let signer_seeds: &[&[u8]] = &[ATA_SEED, &[bump]]; - CreateAssociatedTokenAccountCpi { - owner: accounts[0].clone(), - mint: accounts[1].clone(), - payer: accounts[2].clone(), // PDA - associated_token_account: accounts[3].clone(), - system_program: accounts[4].clone(), - bump: data.bump, - compressible: Some(compressible_params), - idempotent: false, - } - .invoke_signed(&[signer_seeds])?; - - Ok(()) -} -``` + + + + + + + + + + + View the full example: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/macro-basics/create-ata-macro). + + + diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx index c47c1ebd..4ea9b453 100644 --- a/light-token/cookbook/create-mint.mdx +++ b/light-token/cookbook/create-mint.mdx @@ -1,5 +1,5 @@ --- -title: Create Mint Account with Token Metadata +title: Create Mint Account wih Token Metadata sidebarTitle: Create Mint Account description: Program and client guides to create a mint with token metadata. Includes step-by-step implementation and full code examples. keywords: ["create mint account on solana", "rent free mint on solana", "rent free token mints on solana", "spl create mint alternative"] @@ -16,18 +16,25 @@ import { CodeCompare } from "/snippets/jsx/code-compare.jsx"; import { splCreateMintCode, lightCreateMintCode, + splCreateMintRustCode, + lightCreateMintRustCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import ActionCode from "/snippets/code-snippets/light-token/create-mint/action.mdx"; import InstructionCode from "/snippets/code-snippets/light-token/create-mint/instruction.mdx"; -import SplActionCode from "/snippets/code-snippets/light-token/create-spl-mint/action.mdx"; -import SplInstructionCode from "/snippets/code-snippets/light-token/create-spl-mint/instruction.mdx"; -import TokenPoolActionCode from "/snippets/code-snippets/light-token/create-token-pool/action.mdx"; -import TokenPoolInstructionCode from "/snippets/code-snippets/light-token/create-token-pool/instruction.mdx"; +import RustActionCode from "/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx"; +import RustInstructionCode from "/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx"; +import NativeProgramCode from "/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx"; +import AnchorProgramCode from "/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx"; +import AnchorMacroCode from "/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx"; +import CompressibleRentExplained from "/snippets/compressible-rent-explained.mdx"; 1. Mint accounts uniquely represent a token on Solana and store its global metadata. 2. Light mints are on-chain accounts like SPL mints, but the light token program sponsors the rent-exemption cost for you. -## Get Started + + + + @@ -43,10 +50,10 @@ You can use the same interface regardless of mint type. Compare to SPL: @@ -78,56 +85,22 @@ Compare to SPL: -## Create SPL Mint with Token Pool - -Pass `TOKEN_PROGRAM_ID` to create a standard SPL mint with a token pool in one transaction. This enables compression for the mint. - - - - - - - - - - -## Enable Existing SPL Mints with Light ATAs - -Register a token pool for an existing SPL mint to enable compression. No mint authority required. - - - - - - - - - - -The example creates a light-mint with token metadata. - -1. Derive the mint address from the mint signer and address tree -2. Fetch a validity proof from your RPC that proves the address does not exist yet. +`CreateMint` creates an on-chain mint account that can optionally include token metadata. +The instruction creates under the hood a compressed mint address for when the mint is inactive. -3. Configure mint and your token metadata (name, symbol, URI, additional metadata) -4. Build the instruction with `CreateCMint::new()` and send the transaction. +Compare to SPL: -```rust -use light_token_sdk::token::CreateCMint; - -let create_cmint = CreateCMint::new( - params, - mint_signer.pubkey(), - payer.pubkey(), - address_tree.tree, - output_queue, -); -let instruction = create_cmint.instruction()?; -``` + @@ -140,164 +113,25 @@ let instruction = create_cmint.instruction()?; ### Create Mint with Token Metadata -```rust -use light_client::indexer::{AddressWithTree, Indexer}; -use light_client::rpc::{LightClient, LightClientConfig, Rpc}; -use light_token_sdk::token::{CreateCMint, CreateCMintParams}; -use light_token_interface::instructions::extensions::token_metadata::TokenMetadataInstructionData; -use light_token_interface::instructions::extensions::ExtensionInstructionData; -use light_token_interface::state::AdditionalMetadata; -use serde_json; -use solana_sdk::{bs58, pubkey::Pubkey, signature::Keypair, signer::Signer}; -use std::convert::TryFrom; -use std::env; -use std::fs; - -#[tokio::test(flavor = "multi_thread")] -async fn test_create_compressed_mint_with_metadata() { - dotenvy::dotenv().ok(); - - let keypair_path = env::var("KEYPAIR_PATH") - .unwrap_or_else(|_| format!("{}/.config/solana/id.json", env::var("HOME").unwrap())); - let payer = load_keypair(&keypair_path).expect("Failed to load keypair"); - - let api_key = env::var("api_key") // Set api_key in your .env - .expect("api_key environment variable must be set"); - - let config = LightClientConfig::devnet( - Some("https://devnet.helius-rpc.com".to_string()), - Some(api_key), - ); - let mut rpc = LightClient::new_with_retry(config, None) - .await - .expect("Failed to initialize LightClient"); - - // Create compressed mint with metadata - let (mint_pda, compression_address) = create_compressed_mint(&mut rpc, &payer, 9).await; - - println!("\n=== Created Compressed Mint ==="); - println!("Mint PDA: {}", mint_pda); - println!("Compression Address: {}", bs58::encode(compression_address).into_string()); - println!("Decimals: 9"); - println!("Name: Example Token"); - println!("Symbol: EXT"); - println!("URI: https://example.com/metadata.json"); -} - -pub async fn create_compressed_mint( - rpc: &mut R, - payer: &Keypair, - decimals: u8, -) -> (Pubkey, [u8; 32]) { - let mint_signer = Keypair::new(); - let address_tree = rpc.get_address_tree_v2(); - - // Fetch active state trees for devnet - let _ = rpc.get_latest_active_state_trees().await; - let output_pubkey = match rpc - .get_random_state_tree_info() - .ok() - .or_else(|| rpc.get_random_state_tree_info_v1().ok()) - { - Some(info) => info - .get_output_pubkey() - .expect("Invalid state tree type for output"), - None => { - let queues = rpc - .indexer_mut() - .expect("IndexerNotInitialized") - .get_queue_info(None) - .await - .expect("Failed to fetch queue info") - .value - .queues; - queues - .get(0) - .map(|q| q.queue) - .expect("NoStateTreesAvailable: no active state trees returned") - } - }; - - // Derive compression address - let compression_address = light_token_sdk::token::derive_cmint_compressed_address( - &mint_signer.pubkey(), - &address_tree.tree, - ); - - let mint_pda = light_token_sdk::token::find_cmint_address(&mint_signer.pubkey()).0; - - // Get validity proof for the address - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - // Build params with token metadata - let params = CreateCMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority: payer.pubkey(), - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint: mint_pda, - freeze_authority: None, - extensions: Some(vec![ExtensionInstructionData::TokenMetadata( - TokenMetadataInstructionData { - update_authority: Some(payer.pubkey().to_bytes().into()), - name: b"Example Token".to_vec(), - symbol: b"EXT".to_vec(), - uri: b"https://example.com/metadata.json".to_vec(), - additional_metadata: Some(vec![AdditionalMetadata { - key: b"type".to_vec(), - value: b"compressed".to_vec(), - }]), - }, - )]), - }; - - // Create instruction - let create_cmint = CreateCMint::new( - params, - mint_signer.pubkey(), - payer.pubkey(), - address_tree.tree, - output_pubkey, - ); - let instruction = create_cmint.instruction().unwrap(); - - // Send transaction - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_signer]) - .await - .unwrap(); - - (mint_pda, compression_address) -} - -fn load_keypair(path: &str) -> Result> { - let path = if path.starts_with("~") { - path.replace("~", &env::var("HOME").unwrap_or_default()) - } else { - path.to_string() - }; - let file = fs::read_to_string(&path)?; - let bytes: Vec = serde_json::from_str(&file)?; - Ok(Keypair::try_from(&bytes[..])?) -} -``` + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + + + + + + + + + + - + Find [a full code example at the end](#full-code-example). @@ -308,28 +142,19 @@ Find [a full code example at the end](#full-code-example). ### Configure Token Metadata ```rust -use light_token_interface::{ - instructions::extensions::{ - token_metadata::TokenMetadataInstructionData, - ExtensionInstructionData, - }, - state::AdditionalMetadata, +use light_token_interface::instructions::extensions::{ + token_metadata::TokenMetadataInstructionData, ExtensionInstructionData, }; -let token_metadata = ExtensionInstructionData::TokenMetadata( +let extensions = Some(vec![ExtensionInstructionData::TokenMetadata( TokenMetadataInstructionData { - update_authority: Some(authority.to_bytes().into()), - name: b"My Token".to_vec(), - symbol: b"MTK".to_vec(), + update_authority: Some(authority.key.to_bytes().into()), + name: b"Example Token".to_vec(), + symbol: b"EXT".to_vec(), uri: b"https://example.com/metadata.json".to_vec(), - additional_metadata: Some(vec![ - AdditionalMetadata { - key: b"category".to_vec(), - value: b"utility".to_vec(), - }, - ]), + additional_metadata: None, }, -); +)]); ``` @@ -344,28 +169,31 @@ let token_metadata = ExtensionInstructionData::TokenMetadata( ### Configure Mint -Set `decimals`, `mint_authority`, `freeze_authority`, and pass the `token_metadata` from the previous step. +Configure mint parameters including `decimals`, authorities, rent settings, and pass `extensions` from the previous step. ```rust -use light_token_sdk::token::CreateCMintParams; - -let params = CreateCMintParams { - decimals: data.decimals, - address_merkle_tree_root_index: data.address_merkle_tree_root_index, - mint_authority: data.mint_authority, - proof: data.proof, - compression_address: data.compression_address, - mint: data.mint, - freeze_authority: data.freeze_authority, - extensions: data.extensions, +use light_token::instruction::CreateMintParams; + +let params = CreateMintParams { + decimals, + address_merkle_tree_root_index, + mint_authority: *ctx.accounts.authority.key, + proof, + compression_address, + mint, + bump, + freeze_authority, + extensions, + rent_payment: rent_payment.unwrap_or(16), // ~24 hours rent + write_top_up: write_top_up.unwrap_or(766), // ~3 hours rent }; ``` - The client passes a validity proof that proves the light-mint address does not - exist in the address tree where it will be stored. You can safely ignore - `compression_address` and `address_merkle_tree_root_index`. The client passes - these for proof verification. + The address of the mint account is stored in an address Merkle tree + , which is maintained by the protocol. + The client passes a validity proof that proves the mint address does not + exist yet. @@ -373,34 +201,32 @@ let params = CreateCMintParams { ### System Accounts -Include system accounts such as the Light System Program required to interact with compressed state. -The client includes them in the instruction. +Include system accounts such as the Light System Program + to verify the proof and write the mint address to the address tree. ```rust -use light_token_sdk::token::SystemAccountInfos; +use light_token::instruction::SystemAccountInfos; let system_accounts = SystemAccountInfos { - light_system_program: light_system_program.clone(), - cpi_authority_pda: cpi_authority_pda.clone(), - registered_program_pda: registered_program_pda.clone(), - account_compression_authority: account_compression_authority.clone(), - account_compression_program: account_compression_program.clone(), - system_program: system_program.clone(), + light_system_program: ctx.accounts.light_system_program.to_account_info(), + cpi_authority_pda: ctx.accounts.cpi_authority_pda.to_account_info(), + registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(), + account_compression_authority: ctx.accounts.account_compression_authority.to_account_info(), + account_compression_program: ctx.accounts.account_compression_program.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), }; ``` -### Build Account Infos and CPI the light token program +### Build Account Infos and CPI the Light Token Program -1. Pass the required accounts -2. Include `params` and `system_accounts` from the previous steps -3. Use `invoke` or `invoke_signed`: +Use `invoke` or `invoke_signed`: - When `mint_seed` is an external keypair, use `invoke`. - When `mint_seed` is a PDA, use `invoke_signed` with its seeds. - When both `mint_seed` and `authority` are PDAs, use `invoke_signed` with both seeds. @@ -409,43 +235,45 @@ let system_accounts = SystemAccountInfos { ```rust -use light_token_sdk::token::CreateCMintCpi; - -CreateCMintCpi { - mint_seed: mint_seed.clone(), - authority: authority.clone(), - payer: payer.clone(), - address_tree: address_tree.clone(), - output_queue: output_queue.clone(), +use light_token::instruction::CreateMintCpi; + +CreateMintCpi::new( + mint_seed.clone(), + authority.clone(), + payer.clone(), + address_tree.clone(), // stores address + output_queue.clone(), // stores account when inactive + compressible_config.clone(), // rent settings + mint.clone(), + rent_sponsor.clone(), system_accounts, - cpi_context: None, - cpi_context_account: None, params, -} -.invoke()?; +) +.invoke() ``` - + ```rust -use light_token_sdk::token::CreateCMintCpi; - -let account_infos = CreateCMintCpi { - mint_seed: mint_seed.clone(), - authority: authority.clone(), - payer: payer.clone(), - address_tree: address_tree.clone(), - output_queue: output_queue.clone(), - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, -}; +use light_token::instruction::CreateMintCpi; let signer_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[bump]]; -account_infos.invoke_signed(&[signer_seeds])?; + +CreateMintCpi::new( + mint_seed.clone(), + authority.clone(), + payer.clone(), + address_tree.clone(), + output_queue.clone(), + compressible_config.clone(), + mint.clone(), + rent_sponsor.clone(), + system_accounts, + params, +) +.invoke_signed(&[signer_seeds]) ``` @@ -453,23 +281,24 @@ account_infos.invoke_signed(&[signer_seeds])?; ```rust -use light_token_sdk::token::CreateCMintCpi; - -let account_infos = CreateCMintCpi { - mint_seed: mint_seed.clone(), - authority: authority.clone(), - payer: payer.clone(), - address_tree: address_tree.clone(), - output_queue: output_queue.clone(), - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, -}; +use light_token::instruction::CreateMintCpi; let mint_seed_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[mint_seed_bump]]; let authority_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[authority_bump]]; -account_infos.invoke_signed(&[mint_seed_seeds, authority_seeds])?; + +CreateMintCpi::new( + mint_seed.clone(), + authority.clone(), + payer.clone(), + address_tree.clone(), + output_queue.clone(), + compressible_config.clone(), + mint.clone(), + rent_sponsor.clone(), + system_accounts, + params, +) +.invoke_signed(&[mint_seed_seeds, authority_seeds]) ``` @@ -482,275 +311,40 @@ account_infos.invoke_signed(&[mint_seed_seeds, authority_seeds])?; # Full Code Example + + + - Find the source code - [here](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-tests/sdk-light-token-test/src/create_cmint.rs). + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-mint). -```rust expandable -use borsh::{BorshDeserialize, BorshSerialize}; -use light_token_sdk::{ - token::{ - CreateCMintCpi, CreateCMintParams, ExtensionInstructionData, SystemAccountInfos, - }, - CompressedProof, -}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; - -use crate::ID; - -/// PDA seed for mint signer in invoke_signed variant -pub const MINT_SIGNER_SEED: &[u8] = b"mint_signer"; - -/// Instruction data for create compressed mint -#[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct CreateCmintData { - pub decimals: u8, - pub address_merkle_tree_root_index: u16, - pub mint_authority: Pubkey, - pub proof: CompressedProof, - pub compression_address: [u8; 32], - pub mint: Pubkey, - pub freeze_authority: Option, - pub extensions: Option>, -} - -/// Handler for creating a compressed mint (invoke) -/// -/// Uses the CreateCMintCpi builder pattern. This demonstrates how to: -/// 1. Build the CreateCMintParams struct from instruction data -/// 2. Build the CreateCMintCpi with accounts -/// 3. Call invoke() which handles instruction building and CPI -/// -/// Account order: -/// - accounts[0]: compressed_token_program (for CPI) -/// - accounts[1]: light_system_program -/// - accounts[2]: mint_seed (signer) -/// - accounts[3]: payer (signer, also authority) -/// - accounts[4]: payer again (fee_payer in SDK) -/// - accounts[5]: cpi_authority_pda -/// - accounts[6]: registered_program_pda -/// - accounts[7]: account_compression_authority -/// - accounts[8]: account_compression_program -/// - accounts[9]: system_program -/// - accounts[10]: output_queue -/// - accounts[11]: address_tree -/// - accounts[12] (optional): cpi_context_account -pub fn process_create_cmint( - accounts: &[AccountInfo], - data: CreateCmintData, -) -> Result<(), ProgramError> { - if accounts.len() < 12 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Build the params - let params = CreateCMintParams { - decimals: data.decimals, - address_merkle_tree_root_index: data.address_merkle_tree_root_index, - mint_authority: data.mint_authority, - proof: data.proof, - compression_address: data.compression_address, - mint: data.mint, - freeze_authority: data.freeze_authority, - extensions: data.extensions, - }; - - // Build system accounts struct - let system_accounts = SystemAccountInfos { - light_system_program: accounts[1].clone(), - cpi_authority_pda: accounts[5].clone(), - registered_program_pda: accounts[6].clone(), - account_compression_authority: accounts[7].clone(), - account_compression_program: accounts[8].clone(), - system_program: accounts[9].clone(), - }; - - // Build the account infos struct - // In this case, payer == authority (accounts[3]) - CreateCMintCpi { - mint_seed: accounts[2].clone(), - authority: accounts[3].clone(), - payer: accounts[3].clone(), - address_tree: accounts[11].clone(), - output_queue: accounts[10].clone(), - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, - } - .invoke()?; - - Ok(()) -} - -/// Handler for creating a compressed mint with PDA mint seed (invoke_signed) -/// -/// Uses the CreateCMintCpi builder pattern with invoke_signed. -/// The mint_seed is a PDA derived from this program. -/// -/// Account order: -/// - accounts[0]: compressed_token_program (for CPI) -/// - accounts[1]: light_system_program -/// - accounts[2]: mint_seed (PDA, not signer - program signs) -/// - accounts[3]: payer (signer, also authority) -/// - accounts[4]: payer again (fee_payer in SDK) -/// - accounts[5]: cpi_authority_pda -/// - accounts[6]: registered_program_pda -/// - accounts[7]: account_compression_authority -/// - accounts[8]: account_compression_program -/// - accounts[9]: system_program -/// - accounts[10]: output_queue -/// - accounts[11]: address_tree -/// - accounts[12] (optional): cpi_context_account -pub fn process_create_cmint_invoke_signed( - accounts: &[AccountInfo], - data: CreateCmintData, -) -> Result<(), ProgramError> { - if accounts.len() < 12 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Derive the PDA for the mint seed - let (pda, bump) = Pubkey::find_program_address(&[MINT_SIGNER_SEED], &ID); - - // Verify the mint_seed account is the PDA we expect - if &pda != accounts[2].key { - return Err(ProgramError::InvalidSeeds); - } - - // Build the params - let params = CreateCMintParams { - decimals: data.decimals, - address_merkle_tree_root_index: data.address_merkle_tree_root_index, - mint_authority: data.mint_authority, - proof: data.proof, - compression_address: data.compression_address, - mint: data.mint, - freeze_authority: data.freeze_authority, - extensions: data.extensions, - }; - - // Build system accounts struct - let system_accounts = SystemAccountInfos { - light_system_program: accounts[1].clone(), - cpi_authority_pda: accounts[5].clone(), - registered_program_pda: accounts[6].clone(), - account_compression_authority: accounts[7].clone(), - account_compression_program: accounts[8].clone(), - system_program: accounts[9].clone(), - }; - - // Build the account infos struct - // In this case, payer == authority (accounts[3]) - let account_infos = CreateCMintCpi { - mint_seed: accounts[2].clone(), - authority: accounts[3].clone(), - payer: accounts[3].clone(), - address_tree: accounts[11].clone(), - output_queue: accounts[10].clone(), - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, - }; - - // Invoke with PDA signing - let signer_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[bump]]; - account_infos.invoke_signed(&[signer_seeds])?; - - Ok(()) -} - -/// Handler for creating a compressed mint with PDA mint seed AND PDA authority (invoke_signed) -/// -/// Uses the SDK's CreateCMintCpi with separate authority and payer accounts. -/// Both mint_seed and authority are PDAs signed by this program. -/// -/// Account order: -/// - accounts[0]: compressed_token_program (for CPI) -/// - accounts[1]: light_system_program -/// - accounts[2]: mint_seed (PDA from MINT_SIGNER_SEED, not signer - program signs) -/// - accounts[3]: authority (PDA from MINT_AUTHORITY_SEED, not signer - program signs) -/// - accounts[4]: fee_payer (signer) -/// - accounts[5]: cpi_authority_pda -/// - accounts[6]: registered_program_pda -/// - accounts[7]: account_compression_authority -/// - accounts[8]: account_compression_program -/// - accounts[9]: system_program -/// - accounts[10]: output_queue -/// - accounts[11]: address_tree -/// - accounts[12] (optional): cpi_context_account -pub fn process_create_cmint_with_pda_authority( - accounts: &[AccountInfo], - data: CreateCmintData, -) -> Result<(), ProgramError> { - use crate::mint_to::MINT_AUTHORITY_SEED; - - if accounts.len() < 12 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Derive the PDA for the mint seed - let (mint_seed_pda, mint_seed_bump) = - Pubkey::find_program_address(&[MINT_SIGNER_SEED], &ID); - - // Derive the PDA for the authority - let (authority_pda, authority_bump) = Pubkey::find_program_address(&[MINT_AUTHORITY_SEED], &ID); - - // Verify the mint_seed account is the PDA we expect - if &mint_seed_pda != accounts[2].key { - return Err(ProgramError::InvalidSeeds); - } - - // Verify the authority account is the PDA we expect - if &authority_pda != accounts[3].key { - return Err(ProgramError::InvalidSeeds); - } - - // Build the params - authority is the PDA - let params = CreateCMintParams { - decimals: data.decimals, - address_merkle_tree_root_index: data.address_merkle_tree_root_index, - mint_authority: authority_pda, // Use the derived PDA as authority - proof: data.proof, - compression_address: data.compression_address, - mint: data.mint, - freeze_authority: data.freeze_authority, - extensions: data.extensions, - }; - - // Build system accounts struct - let system_accounts = SystemAccountInfos { - light_system_program: accounts[1].clone(), - cpi_authority_pda: accounts[5].clone(), - registered_program_pda: accounts[6].clone(), - account_compression_authority: accounts[7].clone(), - account_compression_program: accounts[8].clone(), - system_program: accounts[9].clone(), - }; - - // Build the account infos struct using SDK - let account_infos = CreateCMintCpi { - mint_seed: accounts[2].clone(), - authority: accounts[3].clone(), - payer: accounts[4].clone(), - address_tree: accounts[11].clone(), - output_queue: accounts[10].clone(), - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, - }; - - // Invoke with both PDAs signing - let mint_seed_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[mint_seed_bump]]; - let authority_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[authority_bump]]; - account_infos.invoke_signed(&[mint_seed_seeds, authority_seeds])?; - - Ok(()) -} -``` + + + + + + + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + + + + + + + + + + + + + View the full example: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/macro-basics/create-mint-macro). + + + diff --git a/light-token/cookbook/create-token-account.mdx b/light-token/cookbook/create-token-account.mdx index 0d16594f..1d14961c 100644 --- a/light-token/cookbook/create-token-account.mdx +++ b/light-token/cookbook/create-token-account.mdx @@ -8,41 +8,39 @@ keywords: ["token account on solana", "rent free token account for apps", "rent --- import TokenCreateAccountsList from "/snippets/accounts-list/light-token-create-accounts-list.mdx"; -import TokenConfigureRent from "/snippets/light-token-configure-rent.mdx"; import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; import CompressibleRentExplained from "/snippets/compressible-rent-explained.mdx"; +import { CodeCompare } from "/snippets/jsx/code-compare.jsx"; +import { + splCreateTokenAccountRustCode, + lightCreateTokenAccountRustCode, +} from "/snippets/code-samples/code-compare-snippets.jsx"; +import RustInstructionCode from "/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx"; +import AnchorProgramCode from "/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx"; +import AnchorMacroCode from "/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx"; +import NativeProgramCode from "/snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx"; 1. Light token accounts are Solana accounts that hold token balances of light, SPL, or Token 2022 mints. -2. Light token accounts implement a default rent config: - 1. At account creation, you pay ~17,208 lamports for 24h of rent
and compression incentive (the rent-exemption is sponsored by the protocol) - 2. Transfers keep the account funded with rent for 3h via top-ups. The transaction payer tops up 776 lamports when the account's rent is below 3h. +2. Light token accounts are on-chain accounts like SPL ATA’s, but the light token program sponsors the rent-exemption cost for you. -## Get Started - -1. The example creates a test mint for light-tokens. You can use existing light, SPL or Token 2022 mints as well. -2. Build the instruction with `CreateTokenAccount`. It automatically includes the default rent config. - -```rust -use light_token_sdk::token::{CreateTokenAccount}; - -let instruction = CreateTokenAccount::new( - payer.pubkey(), - account.pubkey(), - mint, - owner, -) -.instruction()?; -``` +`CreateTokenAccount` creates an on-chain token account to store token balances of light, SPL, or Token 2022 mints. -3. Send transaction & verify light-token account creation with `get_account`. +Compare to SPL: + @@ -55,167 +53,23 @@ let instruction = CreateTokenAccount::new( ### Create Token Account -```rust -use borsh::BorshDeserialize; -use light_client::indexer::{AddressWithTree, Indexer}; -use light_client::rpc::{LightClient, LightClientConfig, Rpc}; -use light_token_sdk::token::{CreateCMint, CreateCMintParams, CreateTokenAccount}; -use light_token_interface::state::Token; -use serde_json; -use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; -use std::convert::TryFrom; -use std::env; -use std::fs; - -#[tokio::test(flavor = "multi_thread")] -async fn test_create_token_account() { - dotenvy::dotenv().ok(); - - let keypair_path = env::var("KEYPAIR_PATH") - .unwrap_or_else(|_| format!("{}/.config/solana/id.json", env::var("HOME").unwrap())); - let payer = load_keypair(&keypair_path).expect("Failed to load keypair"); - - let api_key = env::var("api_key") // Set api_key in your .env - .expect("api_key environment variable must be set"); - - let config = LightClientConfig::devnet( - Some("https://devnet.helius-rpc.com".to_string()), - Some(api_key), - ); - let mut rpc = LightClient::new_with_retry(config, None) - .await - .expect("Failed to initialize LightClient"); - - // Step 1: Create compressed mint (prerequisite) - let (mint, _compression_address) = create_compressed_mint(&mut rpc, &payer, 9).await; - - // Step 2: Generate new keypair for the cToken account - let account = Keypair::new(); - let owner = payer.pubkey(); - - // Step 3: Build instruction using SDK builder - let instruction = CreateTokenAccount::new(payer.pubkey(), account.pubkey(), mint, owner) - .instruction() - .unwrap(); - - // Step 4: Send transaction (account keypair must sign) - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &account]) - .await - .unwrap(); - - // Step 5: Verify account creation - let account_data = rpc.get_account(account.pubkey()).await.unwrap().unwrap(); - let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); - - assert_eq!(token_state.mint, mint.to_bytes(), "Mint should match"); - assert_eq!(token_state.owner, owner.to_bytes(), "Owner should match"); - assert_eq!(token_state.amount, 0, "Initial amount should be 0"); -} - -pub async fn create_compressed_mint( - rpc: &mut R, - payer: &Keypair, - decimals: u8, -) -> (Pubkey, [u8; 32]) { - let mint_signer = Keypair::new(); - let address_tree = rpc.get_address_tree_v2(); - - // Fetch active state trees for devnet - let _ = rpc.get_latest_active_state_trees().await; - let output_pubkey = match rpc - .get_random_state_tree_info() - .ok() - .or_else(|| rpc.get_random_state_tree_info_v1().ok()) - { - Some(info) => info - .get_output_pubkey() - .expect("Invalid state tree type for output"), - None => { - let queues = rpc - .indexer_mut() - .expect("IndexerNotInitialized") - .get_queue_info(None) - .await - .expect("Failed to fetch queue info") - .value - .queues; - queues - .get(0) - .map(|q| q.queue) - .expect("NoStateTreesAvailable: no active state trees returned") - } - }; - - // Derive compression address - let compression_address = light_token_sdk::token::derive_cmint_compressed_address( - &mint_signer.pubkey(), - &address_tree.tree, - ); - - let mint_pda = light_token_sdk::token::find_cmint_address(&mint_signer.pubkey()).0; - - // Get validity proof for the address - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - // Build params - let params = CreateCMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority: payer.pubkey(), - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint: mint_pda, - freeze_authority: None, - extensions: None, - }; - - // Create instruction - let create_cmint = CreateCMint::new( - params, - mint_signer.pubkey(), - payer.pubkey(), - address_tree.tree, - output_pubkey, - ); - let instruction = create_cmint.instruction().unwrap(); - - // Send transaction - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_signer]) - .await - .unwrap(); - - (mint_pda, compression_address) -} + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + -fn load_keypair(path: &str) -> Result> { - let path = if path.starts_with("~") { - path.replace("~", &env::var("HOME").unwrap_or_default()) - } else { - path.to_string() - }; - let file = fs::read_to_string(&path)?; - let bytes: Vec = serde_json::from_str(&file)?; - Ok(Keypair::try_from(&bytes[..])?) -} -``` + + + + + - + Find [a full code example at the end](#full-code-example). @@ -223,53 +77,57 @@ Find [a full code example at the end](#full-code-example). -### Configure Rent - - - - - - -### Build Account Infos and CPI +### Build Account Infos and CPI the Light Token Program -1. Pass the required accounts -2. Include rent config from `compressible_params` -3. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. +1. Pass token account fields and call `.rent_free()` with rent config accounts. +2. Use `invoke` or `invoke_signed`: + - When `account` is an external keypair, use `invoke`. + - When `account` is a PDA, use `invoke_signed` with its seeds. ```rust -use light_token_sdk::token::CreateTokenAccountCpi; +use light_token::instruction::CreateTokenAccountCpi; CreateTokenAccountCpi { payer: payer.clone(), account: account.clone(), mint: mint.clone(), - owner: data.owner, - compressible: Some(compressible_params), + owner, } -.invoke()?; +.rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + token_program.key, // light token program +) +.invoke() ``` - + ```rust -use light_token_sdk::token::CreateTokenAccountCpi; +use light_token::instruction::CreateTokenAccountCpi; -let account_cpi = CreateTokenAccountCpi { +let signer_seeds = authority_seeds!(authority_bump); + +CreateTokenAccountCpi { payer: payer.clone(), account: account.clone(), mint: mint.clone(), - owner: data.owner, - compressible: Some(compressible_params), -}; - -let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]]; -account_cpi.invoke_signed(&[signer_seeds])?; + owner, +} +.rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + program_id, +) +.invoke_signed(signer_seeds) ``` @@ -280,115 +138,40 @@ account_cpi.invoke_signed(&[signer_seeds])?; # Full Code Example + + + - Find the source code - [here](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-tests/sdk-light-token-test/src/create_token_account.rs). + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-token-account). -```rust expandable -use borsh::{BorshDeserialize, BorshSerialize}; -use light_token_sdk::token::{CompressibleParamsCpi, CreateTokenAccountCpi}; -use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; + -use crate::{ID, TOKEN_ACCOUNT_SEED}; + -/// Instruction data for create token account -#[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct CreateTokenAccountData { - pub owner: Pubkey, - pub pre_pay_num_epochs: u8, - pub lamports_per_write: u32, -} + -/// Handler for creating a compressible token account (invoke) -/// -/// Uses the builder pattern from the token module. This demonstrates how to: -/// 1. Build the account infos struct with compressible params -/// 2. Call the invoke() method which handles instruction building and CPI -/// -/// Account order: -/// - accounts[0]: payer (signer) -/// - accounts[1]: account to create (signer) -/// - accounts[2]: mint -/// - accounts[3]: compressible_config -/// - accounts[4]: system_program -/// - accounts[5]: rent_sponsor -pub fn process_create_token_account_invoke( - accounts: &[AccountInfo], - data: CreateTokenAccountData, -) -> Result<(), ProgramError> { - if accounts.len() < 6 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Build the compressible params using constructor - let compressible_params = CompressibleParamsCpi::new( - accounts[3].clone(), - accounts[5].clone(), - accounts[4].clone(), - ); - - // Build the account infos struct - CreateTokenAccountCpi { - payer: accounts[0].clone(), - account: accounts[1].clone(), - mint: accounts[2].clone(), - owner: data.owner, - compressible: Some(compressible_params), - } - .invoke()?; - - Ok(()) -} + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + -/// Handler for creating a compressible token account with PDA ownership (invoke_signed) -/// -/// Account order: -/// - accounts[0]: payer (signer) -/// - accounts[1]: account to create (PDA, will be derived and verified) -/// - accounts[2]: mint -/// - accounts[3]: compressible_config -/// - accounts[4]: system_program -/// - accounts[5]: rent_sponsor -pub fn process_create_token_account_invoke_signed( - accounts: &[AccountInfo], - data: CreateTokenAccountData, -) -> Result<(), ProgramError> { - if accounts.len() < 6 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Derive the PDA for the token account - let (pda, bump) = Pubkey::find_program_address(&[TOKEN_ACCOUNT_SEED], &ID); - - // Verify the account to create is the PDA - if &pda != accounts[1].key { - return Err(ProgramError::InvalidSeeds); - } - - // Build the compressible params using constructor - let compressible_params = CompressibleParamsCpi::new( - accounts[3].clone(), - accounts[5].clone(), - accounts[4].clone(), - ); - - // Build the account infos struct - let account_cpi = CreateTokenAccountCpi { - payer: accounts[0].clone(), - account: accounts[1].clone(), - mint: accounts[2].clone(), - owner: data.owner, - compressible: Some(compressible_params), - }; - - // Invoke with PDA signing - let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]]; - account_cpi.invoke_signed(&[signer_seeds])?; - - Ok(()) -} -``` + + + + + + + + + + + View the full example: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/macro-basics/create-token-account-macro). + + + diff --git a/light-token/cookbook/freeze-thaw.mdx b/light-token/cookbook/freeze-thaw.mdx new file mode 100644 index 00000000..1a73f0f1 --- /dev/null +++ b/light-token/cookbook/freeze-thaw.mdx @@ -0,0 +1,266 @@ +--- +title: Freeze and Thaw Light Token Accounts +sidebarTitle: Freeze / Thaw +description: Rust client guide to freeze and thaw light-token accounts. Includes step-by-step implementation and full code examples. +keywords: ["freeze token account solana", "thaw token account solana", "token account management"] +--- + +--- + +import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; +import { CodeCompare } from "/snippets/jsx/code-compare.jsx"; +import { + splFreezeRustCode, + lightFreezeRustCode, + splThawRustCode, + lightThawRustCode, +} from "/snippets/code-samples/code-compare-snippets.jsx"; +import FreezeInstructionCode from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx"; +import ThawInstructionCode from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx"; +import FreezeAnchorProgramCode from "/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx"; +import FreezeNativeProgramCode from "/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx"; +import ThawAnchorProgramCode from "/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx"; +import ThawNativeProgramCode from "/snippets/code-snippets/light-token/thaw/native-program/full-example.mdx"; + +1. Freeze prevents all transfers or token burns from a specific light-token account. +2. Once frozen, the account cannot send tokens, receive tokens, or be closed until it is thawed. +3. Thaw re-enables transfers on a frozen light-token account. +4. Only the freeze authority (set at mint creation) can freeze or thaw accounts. +5. If the freeze authority is revoked (set to null) on the mint account, tokens can never be frozen. + + + + + + + + + + + + + + + + + + + +### Prerequisites + + + + + + +### Freeze or thaw light-token accounts + + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + + + + + + + + + + + + + + + + + + + + + + + + + + +Find [a full code example at the end](#full-code-example). + + + + + + + +### Build Account Infos and CPI the Light Token Program + +Define the light-token account to freeze, the mint it belongs to, and the freeze authority. Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. + + + + +```rust +use light_token::instruction::FreezeCpi; + +FreezeCpi { + token_account: ctx.accounts.token_account.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + freeze_authority: ctx.accounts.freeze_authority.to_account_info(), +} +.invoke()?; +``` + + + + +```rust +use light_token::instruction::FreezeCpi; + +let signer_seeds = authority_seeds!(bump); + +FreezeCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), +} +.invoke_signed(&[signer_seeds]) +``` + + + + + + + + + + + + +### Build Account Infos and CPI the Light Token Program + +Define the frozen light-token account to thaw, the mint it belongs to, and the freeze authority. Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. + + + + +```rust +use light_token::instruction::ThawCpi; + +ThawCpi { + token_account: ctx.accounts.token_account.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + freeze_authority: ctx.accounts.freeze_authority.to_account_info(), +} +.invoke()?; +``` + + + + +```rust +use light_token::instruction::ThawCpi; + +let signer_seeds = authority_seeds!(bump); + +ThawCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), +} +.invoke_signed(&[signer_seeds]) +``` + + + + + + + + + +# Full Code Example + + + + + + + + + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/freeze). + + + + + + + + + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + + + + + + + + + + + + + + + + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/thaw). + + + + + + + + + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + + + + + + + + + + + + + + +# Next Steps + + diff --git a/light-token/cookbook/load-ata.mdx b/light-token/cookbook/load-ata.mdx index 17eced75..042e1c20 100644 --- a/light-token/cookbook/load-ata.mdx +++ b/light-token/cookbook/load-ata.mdx @@ -1,7 +1,7 @@ --- title: Load Token Balances to Light ATA sidebarTitle: Load ATA -description: Unify token balances from compressed tokens (cold), SPL, and Token-2022 to one light ATA. +description: Unify token balances from compressed tokens (cold light-tokens), SPL, and Token-2022 to one light ATA. keywords: ["load ata on solana", "get token balance for wallets"] --- @@ -12,7 +12,7 @@ import ActionCode from "/snippets/code-snippets/light-token/load-ata/action.mdx" import InstructionCode from "/snippets/code-snippets/light-token/load-ata/instruction.mdx"; 1. `loadAta` unifies tokens from multiple sources to a single ATA: - * Compressed tokens (cold) -> Decompresses -> light ATA + * Compressed tokens (cold light-tokens) -> Decompresses -> light ATA * SPL balance (if wrap=true) -> Wraps -> light ATA * T22 balance (if wrap=true) -> Wraps -> light ATA @@ -24,11 +24,9 @@ import InstructionCode from "/snippets/code-snippets/light-token/load-ata/instru Find the source code [here](https://github.com/Lightprotocol/light-protocol/blob/0c4e2417b2df2d564721b89e18d1aad3665120e7/js/compressed-token/src/v3/actions/load-ata.ts). -## Get Started - -### Load Compressed Tokens to Hot Balance +### Unify Tokens to Light-ATA Balance diff --git a/light-token/cookbook/mint-to.mdx b/light-token/cookbook/mint-to.mdx index 19ac18f3..ee77a805 100644 --- a/light-token/cookbook/mint-to.mdx +++ b/light-token/cookbook/mint-to.mdx @@ -15,15 +15,20 @@ import FullSetup from "/snippets/setup/full-setup.mdx"; import { splMintToCode, lightMintToCode, + splMintToRustCode, + lightMintToRustCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import ActionCode from "/snippets/code-snippets/light-token/mint-to/action.mdx"; import InstructionCode from "/snippets/code-snippets/light-token/mint-to/instruction.mdx"; +import RustActionCode from "/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx"; +import RustInstructionCode from "/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx"; +import AnchorProgramCode from "/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx"; +import NativeProgramCode from "/snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx"; +import NativeMintToCheckedCode from "/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx"; 1. Mint To creates new tokens of a mint and deposits them to a specified token account. 2. Before we can mint any tokens, we need an initialized mint account (SPL, Token-2022 or Light) for which we hold the mint authority. -## Get Started - @@ -35,10 +40,10 @@ The function auto-detects the token program (SPL, Token-2022, or Light) from the Compare to SPL: @@ -68,26 +73,17 @@ Compare to SPL: -The example mints light-tokens to existing light-token accounts. +Use `MintTo` to mint tokens to a light-token account. -1. Prerequisite: The example creates a test light-mint and destination light-token account. -2. Get light-mint account infos and prove it exists with a validity proof.. -3. Set the amount of tokens you will mint and the mint authority. Only the mint authority can mint new light-tokens. -4. Build the instruction with `MintTo::new()` and send the transaction. +Compare to SPL: -```rust -use light_token_sdk::token::MintTo; - -let instruction = MintTo::new( - params, - payer.pubkey(), - state_tree, - output_queue, - input_queue, - vec![recipient_account.pubkey()], -) -.instruction()?; -``` + @@ -100,565 +96,193 @@ let instruction = MintTo::new( ### Mint to Light Token Accounts -```rust -use borsh::BorshDeserialize; -use light_client::indexer::{AddressWithTree, Indexer}; -use light_client::rpc::{LightClient, LightClientConfig, Rpc}; -use light_token_sdk::token::{ - CreateCMint, CreateCMintParams, CreateTokenAccount, MintTo, MintToParams, -}; -use light_token_interface::instructions::extensions::token_metadata::TokenMetadataInstructionData; -use solana_sdk::compute_budget::ComputeBudgetInstruction; -use light_token_interface::instructions::extensions::ExtensionInstructionData; -use light_token_interface::instructions::mint_action::CompressedMintWithContext; -use light_token_interface::state::{AdditionalMetadata, Token, CompressedMint}; -use serde_json; -use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer}; -use std::convert::TryFrom; -use std::env; -use std::fs; - -#[tokio::test(flavor = "multi_thread")] -async fn test_mint_to_token() { - dotenvy::dotenv().ok(); - - let keypair_path = env::var("KEYPAIR_PATH") - .unwrap_or_else(|_| format!("{}/.config/solana/id.json", env::var("HOME").unwrap())); - let payer = load_keypair(&keypair_path).expect("Failed to load keypair"); - let mint_authority = payer.pubkey(); - - let api_key = env::var("api_key") // Set api_key in your .env - .expect("api_key environment variable must be set"); - - let config = LightClientConfig::devnet( - Some("https://devnet.helius-rpc.com".to_string()), - Some(api_key), - ); - let mut rpc = LightClient::new_with_retry(config, None) - .await - .expect("Failed to initialize LightClient"); - - // Step 1: Create compressed mint with metadata - let (mint, compression_address) = create_compressed_mint(&mut rpc, &payer, 9).await; - - // Step 2: Create token account - let token_account = Keypair::new(); - let owner = payer.pubkey(); - let create_account_ix = - CreateTokenAccount::new(payer.pubkey(), token_account.pubkey(), mint, owner) - .instruction() - .unwrap(); - - rpc.create_and_send_transaction( - &[create_account_ix], - &payer.pubkey(), - &[&payer, &token_account], - ) - .await - .unwrap(); - - // Step 3: Get compressed mint account to build CompressedMintWithContext - let compressed_mint_account = rpc - .get_compressed_account(compression_address, None) - .await - .unwrap() - .value - .expect("Compressed mint should exist"); - - // Step 4: Get validity proof for the mint operation - let rpc_result = rpc - .get_validity_proof(vec![compressed_mint_account.hash], vec![], None) - .await - .unwrap() - .value; - - // Step 5: Deserialize compressed mint data - let compressed_mint = CompressedMint::deserialize( - &mut compressed_mint_account.data.unwrap().data.as_slice(), - ) - .unwrap(); - - // Step 6: Build CompressedMintWithContext - let compressed_mint_with_context = CompressedMintWithContext { - address: compression_address, - leaf_index: compressed_mint_account.leaf_index, - prove_by_index: false, - root_index: rpc_result.accounts[0] - .root_index - .root_index() - .unwrap_or_default(), - mint: compressed_mint.try_into().unwrap(), - }; - - let amount = 1_000_000_000u64; // 1 token with 9 decimals - - // Step 7: Get active output queue for devnet - let _ = rpc.get_latest_active_state_trees().await; - let output_queue = match rpc - .get_random_state_tree_info() - .ok() - .or_else(|| rpc.get_random_state_tree_info_v1().ok()) - { - Some(info) => info - .get_output_pubkey() - .expect("Invalid state tree type for output"), - None => { - let queues = rpc - .indexer_mut() - .expect("IndexerNotInitialized") - .get_queue_info(None) - .await - .expect("Failed to fetch queue info") - .value - .queues; - queues - .get(0) - .map(|q| q.queue) - .expect("NoStateTreesAvailable: no active state trees returned") - } - }; - - // Step 8: Build mint params - let params = MintToParams::new( - compressed_mint_with_context, - amount, - mint_authority, - rpc_result.proof, - ); - - // Step 9: Build instruction using SDK builder - let instruction = MintTo::new( - params, - payer.pubkey(), - compressed_mint_account.tree_info.tree, - compressed_mint_account.tree_info.queue, - output_queue, - vec![token_account.pubkey()], - ) - .instruction() - .unwrap(); - - // Step 10: Send transaction - let compute_unit_ix = ComputeBudgetInstruction::set_compute_unit_limit(300_000); - rpc.create_and_send_transaction(&[compute_unit_ix, instruction], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Step 11: Verify tokens were minted - let token_account_data = rpc - .get_account(token_account.pubkey()) - .await - .unwrap() - .unwrap(); - - let token_state = Token::deserialize(&mut &token_account_data.data[..]).unwrap(); - assert_eq!(token_state.amount, amount, "Token amount should match"); - assert_eq!(token_state.mint, mint.to_bytes(), "Mint should match"); - assert_eq!(token_state.owner, owner.to_bytes(), "Owner should match"); -} - -pub async fn create_compressed_mint( - rpc: &mut R, - payer: &Keypair, - decimals: u8, -) -> (Pubkey, [u8; 32]) { - let mint_signer = Keypair::new(); - let address_tree = rpc.get_address_tree_v2(); - - // Fetch active state trees for devnet - let _ = rpc.get_latest_active_state_trees().await; - let output_pubkey = match rpc - .get_random_state_tree_info() - .ok() - .or_else(|| rpc.get_random_state_tree_info_v1().ok()) - { - Some(info) => info - .get_output_pubkey() - .expect("Invalid state tree type for output"), - None => { - let queues = rpc - .indexer_mut() - .expect("IndexerNotInitialized") - .get_queue_info(None) - .await - .expect("Failed to fetch queue info") - .value - .queues; - queues - .get(0) - .map(|q| q.queue) - .expect("NoStateTreesAvailable: no active state trees returned") - } - }; - - // Derive compression address - let compression_address = light_token_sdk::token::derive_cmint_compressed_address( - &mint_signer.pubkey(), - &address_tree.tree, - ); - - let mint_pda = light_token_sdk::token::find_cmint_address(&mint_signer.pubkey()).0; - - // Get validity proof for the address - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - // Build params with token metadata - let params = CreateCMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority: payer.pubkey(), - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint: mint_pda, - freeze_authority: None, - extensions: Some(vec![ExtensionInstructionData::TokenMetadata( - TokenMetadataInstructionData { - update_authority: Some(payer.pubkey().to_bytes().into()), - name: b"Example Token".to_vec(), - symbol: b"EXT".to_vec(), - uri: b"https://example.com/metadata.json".to_vec(), - additional_metadata: Some(vec![AdditionalMetadata { - key: b"type".to_vec(), - value: b"compressed".to_vec(), - }]), - }, - )]), - }; - - // Create instruction - let create_cmint = CreateCMint::new( - params, - mint_signer.pubkey(), - payer.pubkey(), - address_tree.tree, - output_pubkey, - ); - let instruction = create_cmint.instruction().unwrap(); - - // Send transaction - rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_signer]) - .await - .unwrap(); - - (mint_pda, compression_address) -} + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + -fn load_keypair(path: &str) -> Result> { - let path = if path.starts_with("~") { - path.replace("~", &env::var("HOME").unwrap_or_default()) - } else { - path.to_string() - }; - let file = fs::read_to_string(&path)?; - let bytes: Vec = serde_json::from_str(&file)?; - Ok(Keypair::try_from(&bytes[..])?) -} -``` + + + + + + + + - + Find [a full code example at the end](#full-code-example). + -### Configure Mint Parameters +### Build Account Infos and CPI the Light Token Program -Include your mint, the amount of tokens to be minted and the pubkey of the mint authority. -The client passes a validity proof that proves the light-mint exists. +Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. -```rust -use light_token_sdk::token::MintToParams; - -let params = MintToParams::new( - data.compressed_mint_inputs, - data.amount, - data.mint_authority, - data.proof, -); -``` + + - +```rust +use light_token::instruction::MintToCpi; - +MintToCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, + authority: authority.clone(), + system_program: system_program.clone(), + fee_payer: None, + max_top_up: None, +} +.invoke() +``` -### System Accounts + + -Compressed accounts like light-mints require system accounts like the Light System Program account for interactions and proof verification. -The client includes them in the instruction. +```rust +use light_token::instruction::MintToCpi; - - - +let signer_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[bump]]; -```rust -use light_token_sdk::token::SystemAccountInfos; - -let system_accounts = SystemAccountInfos { - light_system_program: light_system_program.clone(), - cpi_authority_pda: cpi_authority_pda.clone(), - registered_program_pda: registered_program_pda.clone(), - account_compression_authority: account_compression_authority.clone(), - account_compression_program: account_compression_program.clone(), +MintToCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, + authority: authority.clone(), system_program: system_program.clone(), -}; + fee_payer: None, + max_top_up: None, +} +.invoke_signed(&[signer_seeds]) ``` + + + + + `fee_payer` and `max_top_up` are optional fields to customize rent top-ups. + Set to `None` to use defaults. + + + + +# Full Code Example + + + + + + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/mint-to). + + + + + + + + + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + + + + + + + + + + + + +MintToChecked validates that the decimals parameter matches the mint's decimals. +Find [a full code example at the end](#full-code-example-1). + + + -### Build Account Infos and CPI +### Build Account Infos and CPI the Light Token Program -1. Pass the required accounts, including the destination light-token accounts. -2. Include `params` and `system_accounts` from the previous steps -3. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. +Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. - + ```rust -use light_token_sdk::token::MintToCpi; +use light_token::instruction::MintToCheckedCpi; -MintToCpi { +MintToCheckedCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, + decimals, authority: authority.clone(), - payer: payer.clone(), - state_tree: state_tree.clone(), - input_queue: input_queue.clone(), - output_queue: output_queue.clone(), - token_accounts, - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, + system_program: system_program.clone(), + fee_payer: None, + max_top_up: None, } -.invoke()?; +.invoke() ``` - + ```rust -use light_token_sdk::token::MintToCpi; - -let account_infos = MintToCpi { - authority: authority.clone(), - payer: payer.clone(), - state_tree: state_tree.clone(), - input_queue: input_queue.clone(), - output_queue: output_queue.clone(), - token_accounts, - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, -}; +use light_token::instruction::MintToCheckedCpi; let signer_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[bump]]; -account_infos.invoke_signed(&[signer_seeds])?; + +MintToCheckedCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, + decimals, + authority: authority.clone(), + system_program: system_program.clone(), + fee_payer: None, + max_top_up: None, +} +.invoke_signed(&[signer_seeds]) ``` - - - + + `fee_payer` and `max_top_up` are optional fields to customize rent top-ups. + Set to `None` to use defaults. + # Full Code Example + + + - Find the source code - [here](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-tests/sdk-light-token-test/src/ctoken_mint_to.rs). + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). -```rust expandable -use borsh::{BorshDeserialize, BorshSerialize}; -use light_token_interface::instructions::mint_action::CompressedMintWithContext; -use light_token_sdk::token::{MintToCpi, MintToParams, SystemAccountInfos}; -use light_sdk::instruction::ValidityProof; -use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; - -use crate::ID; - -/// PDA seed for mint authority in invoke_signed variant -pub const MINT_AUTHORITY_SEED: &[u8] = b"mint_authority"; - -/// Instruction data for mint_to -#[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct MintToData { - pub compressed_mint_inputs: CompressedMintWithContext, - pub amount: u64, - pub mint_authority: Pubkey, - pub proof: ValidityProof, -} + -/// Handler for minting tokens to compressed token accounts -/// -/// Uses the MintToCpi builder pattern. This demonstrates how to: -/// 1. Build MintToParams using the constructor -/// 2. Build MintToCpi with accounts and params -/// 3. Call invoke() which handles instruction building and CPI -/// -/// Account order (all accounts from SDK-generated instruction): -/// - accounts[0]: compressed_token_program (for CPI) -/// - accounts[1]: light_system_program -/// - accounts[2]: authority (mint_authority) -/// - accounts[3]: fee_payer -/// - accounts[4]: cpi_authority_pda -/// - accounts[5]: registered_program_pda -/// - accounts[6]: account_compression_authority -/// - accounts[7]: account_compression_program -/// - accounts[8]: system_program -/// - accounts[9]: output_queue -/// - accounts[10]: state_tree -/// - accounts[11]: input_queue -/// - accounts[12..]: token_accounts (variable length - destination accounts) -pub fn process_mint_to( - accounts: &[AccountInfo], - data: MintToData, -) -> Result<(), ProgramError> { - if accounts.len() < 13 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Build params using the constructor - let params = MintToParams::new( - data.compressed_mint_inputs, - data.amount, - data.mint_authority, - data.proof, - ); - - // Build system accounts struct - let system_accounts = SystemAccountInfos { - light_system_program: accounts[1].clone(), - cpi_authority_pda: accounts[4].clone(), - registered_program_pda: accounts[5].clone(), - account_compression_authority: accounts[6].clone(), - account_compression_program: accounts[7].clone(), - system_program: accounts[8].clone(), - }; - - // Collect token accounts from remaining accounts (index 12 onwards) - let token_accounts: Vec = accounts[12..].to_vec(); - - // Build the account infos struct and invoke - // SDK account order: output_queue (9), tree (10), input_queue (11), token_accounts (12+) - // In this case, payer == authority (accounts[3]) - MintToCpi { - authority: accounts[2].clone(), // authority from SDK accounts - payer: accounts[3].clone(), // fee_payer from SDK accounts - state_tree: accounts[10].clone(), // tree at index 10 - input_queue: accounts[11].clone(), // input_queue at index 11 - output_queue: accounts[9].clone(), // output_queue at index 9 - token_accounts, - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, - } - .invoke()?; - - Ok(()) -} - -/// Handler for minting tokens with PDA mint authority (invoke_signed) -/// -/// Uses the MintToCpi builder pattern with invoke_signed. -/// The mint authority is a PDA derived from this program. -/// -/// Account order (all accounts from SDK-generated instruction): -/// - accounts[0]: compressed_token_program (for CPI) -/// - accounts[1]: light_system_program -/// - accounts[2]: authority (PDA mint_authority, not signer - program signs) -/// - accounts[3]: fee_payer -/// - accounts[4]: cpi_authority_pda -/// - accounts[5]: registered_program_pda -/// - accounts[6]: account_compression_authority -/// - accounts[7]: account_compression_program -/// - accounts[8]: system_program -/// - accounts[9]: output_queue -/// - accounts[10]: state_tree -/// - accounts[11]: input_queue -/// - accounts[12..]: token_accounts (variable length - destination accounts) -pub fn process_mint_to_invoke_signed( - accounts: &[AccountInfo], - data: MintToData, -) -> Result<(), ProgramError> { - if accounts.len() < 13 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Derive the PDA for the mint authority - let (pda, bump) = Pubkey::find_program_address(&[MINT_AUTHORITY_SEED], &ID); - - // Verify the authority account is the PDA we expect - if &pda != accounts[2].key { - return Err(ProgramError::InvalidSeeds); - } - - // Build params using the constructor - let params = MintToParams::new( - data.compressed_mint_inputs, - data.amount, - data.mint_authority, - data.proof, - ); - - // Build system accounts struct - let system_accounts = SystemAccountInfos { - light_system_program: accounts[1].clone(), - cpi_authority_pda: accounts[4].clone(), - registered_program_pda: accounts[5].clone(), - account_compression_authority: accounts[6].clone(), - account_compression_program: accounts[7].clone(), - system_program: accounts[8].clone(), - }; - - // Collect token accounts from remaining accounts (index 12 onwards) - let token_accounts: Vec = accounts[12..].to_vec(); - - // Build the account infos struct - // authority is the PDA (accounts[2]) - let account_infos = MintToCpi { - authority: accounts[2].clone(), // authority PDA - payer: accounts[3].clone(), // fee_payer from SDK accounts - state_tree: accounts[10].clone(), // tree at index 10 - input_queue: accounts[11].clone(), // input_queue at index 11 - output_queue: accounts[9].clone(), // output_queue at index 9 - token_accounts, - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, - }; - - // Invoke with PDA signing - let signer_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[bump]]; - account_infos.invoke_signed(&[signer_seeds])?; - - Ok(()) -} -``` + + diff --git a/light-token/cookbook/transfer-checked.mdx b/light-token/cookbook/transfer-checked.mdx new file mode 100644 index 00000000..3c73fde3 --- /dev/null +++ b/light-token/cookbook/transfer-checked.mdx @@ -0,0 +1,150 @@ +--- +title: Transfer Checked +sidebarTitle: Transfer Checked +description: Rust client guide to transfer light-tokens with decimal validation. Includes step-by-step implementation and full code examples. +keywords: ["transfer tokens solana", "checked transfer", "decimal validation"] +--- + +--- + +import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; +import RustActionCode from "/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx"; +import RustInstructionCode from "/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx"; +import NativeProgramCode from "/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx"; +import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx"; + +1. TransferChecked validates that the decimals parameter matches the mint's decimals. +2. Use for Light→Light transfers when you need decimal verification. +3. For transfers involving SPL or Token-2022 accounts, use [Transfer Interface](/light-token/cookbook/transfer-interface) instead. + + + + + + +### Prerequisites + + + + + + +### Transfer with Decimal Check + + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + + + + + + + + + + + + + + + + + + +Find [a full code example at the end](#full-code-example). + + + + +### Transfer Checked with CPI + + + + +```rust +use light_token::instruction::TransferCheckedCpi; + +TransferCheckedCpi { + source: source.clone(), + mint: mint.clone(), + destination: destination.clone(), + amount, + decimals, + authority: authority.clone(), + system_program: system_program.clone(), + max_top_up: None, + fee_payer: None, +} +.invoke()?; +``` + + + + +```rust +use light_token::instruction::TransferCheckedCpi; + +let signer_seeds = authority_seeds!(bump); + +TransferCheckedCpi { + source: source.clone(), + mint: mint.clone(), + destination: destination.clone(), + amount, + decimals, + authority: authority.clone(), + system_program: system_program.clone(), + max_top_up: None, + fee_payer: None, +} +.invoke_signed(&[signer_seeds])?; +``` + + + + + + + +# Full Code Example + + + + + + View the full example: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/transfer-checked). + + + + + + + + + + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + + + + + + + + + + + +# Next Steps + +{" "} + + diff --git a/light-token/cookbook/transfer-interface.mdx b/light-token/cookbook/transfer-interface.mdx index 8ddce1f0..71b9beab 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -6,8 +6,6 @@ keywords: ["transfer tokens on solana", "token transfer on solana", "interface m --- -import TransferInterfaceAccountsListCtoken1 from "/snippets/accounts-list/transfer-interface-accounts-list-light-token-1.mdx"; -import TransferInterfaceAccountsListSpl2 from "/snippets/accounts-list/transfer-interface-accounts-list-spl-2.mdx"; import TransferInterfaceIntro from "/snippets/light-token-guides/transfer-interface-intro.mdx"; import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; import TokenTsClientPrerequisites from "/snippets/light-token-guides/light-token-ts-client-prerequisites.mdx"; @@ -17,9 +15,15 @@ import FullSetup from "/snippets/setup/full-setup.mdx"; import { splTransferCode, lightTransferCode, + splTransferRustCode, + lightTransferRustCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import ActionCode from "/snippets/code-snippets/light-token/transfer-interface/action.mdx"; import InstructionCode from "/snippets/code-snippets/light-token/transfer-interface/instruction.mdx"; +import RustActionCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx"; +import RustInstructionCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx"; +import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx"; +import NativeProgramCode from "/snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx"; @@ -54,8 +58,6 @@ import InstructionCode from "/snippets/code-snippets/light-token/transfer-interf
-## Get Started - @@ -65,10 +67,10 @@ The `transferInterface` function transfers tokens between token accounts (SPL, T Compare to SPL: @@ -99,11 +101,15 @@ Compare to SPL: -The example transfers SPL token -> light-token and light-token -> light-token: +Use the unified `TransferInterface` to transfer tokens between token accounts (SPL, Token-2022, or Light) in a single call. -1. Create SPL mint, SPL token accounts, and mint SPL tokens -2. Send SPL tokens to light-token account to mint light-tokens. -3. Transfer light-tokens to another light-token account. + @@ -114,235 +120,33 @@ The example transfers SPL token -> light-token and light-token -> light-token: -### Transfer Interface +### Transfer Interface + +The example transfers +1. SPL token -> light-token, +2. light-token -> light-token, and +3. light-token -> SPL token. + + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + + + + + + + + + + + -```rust -use anchor_spl::token::{spl_token, Mint}; -use light_client::rpc::{LightClient, LightClientConfig, Rpc}; -use light_token_sdk::{ - token::{ - derive_token_ata, CreateAssociatedTokenAccount, - Transfer, TransferFromSpl, - }, - spl_interface::{find_spl_interface_pda_with_index, CreateSplInterfacePda}, -}; -use serde_json; -use solana_sdk::compute_budget::ComputeBudgetInstruction; -use solana_sdk::program_pack::Pack; -use solana_sdk::{signature::Keypair, signer::Signer}; -use spl_token_2022::pod::PodAccount; -use std::convert::TryFrom; -use std::env; -use std::fs; - -/// Test SPL → light-token → light-token -// with ATA creation + transfer in one transaction -#[tokio::test(flavor = "multi_thread")] -async fn test_spl_to_token_to_token() { - dotenvy::dotenv().ok(); - - let keypair_path = env::var("KEYPAIR_PATH") - .unwrap_or_else(|_| format!("{}/.config/solana/id.json", env::var("HOME").unwrap())); - let payer = load_keypair(&keypair_path).expect("Failed to load keypair"); - let api_key = env::var("api_key") // Set api_key in your .env - .expect("api_key environment variable must be set"); - - let config = LightClientConfig::devnet( - Some("https://devnet.helius-rpc.com".to_string()), - Some(api_key), - ); - let mut rpc = LightClient::new_with_retry(config, None) - .await - .expect("Failed to initialize LightClient"); - - // 2. Create SPL mint - let mint_keypair = Keypair::new(); - let mint = mint_keypair.pubkey(); - let decimals = 2u8; - - let mint_rent = rpc - .get_minimum_balance_for_rent_exemption(Mint::LEN) - .await - .unwrap(); - - let create_mint_account_ix = solana_sdk::system_instruction::create_account( - &payer.pubkey(), - &mint, - mint_rent, - Mint::LEN as u64, - &spl_token::ID, - ); - - let initialize_mint_ix = spl_token::instruction::initialize_mint( - &spl_token::ID, - &mint, - &payer.pubkey(), - None, - decimals, - ) - .unwrap(); - - rpc.create_and_send_transaction( - &[create_mint_account_ix, initialize_mint_ix], - &payer.pubkey(), - &[&payer, &mint_keypair], - ) - .await - .unwrap(); - - // 3. Create SPL interface PDA - let create_spl_interface_pda_ix = - CreateSplInterfacePda::new(payer.pubkey(), mint, anchor_spl::token::ID).instruction(); - - rpc.create_and_send_transaction(&[create_spl_interface_pda_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let mint_amount = 10_000u64; - let spl_to_token_amount = 7_000u64; - let token_transfer_amount = 3_000u64; - - // 4. Create SPL token account - let spl_token_account_keypair = Keypair::new(); - let token_account_rent = rpc - .get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN) - .await - .unwrap(); - let create_token_account_ix = solana_sdk::system_instruction::create_account( - &payer.pubkey(), - &spl_token_account_keypair.pubkey(), - token_account_rent, - spl_token::state::Account::LEN as u64, - &spl_token::ID, - ); - let init_token_account_ix = spl_token::instruction::initialize_account( - &spl_token::ID, - &spl_token_account_keypair.pubkey(), - &mint, - &payer.pubkey(), - ) - .unwrap(); - rpc.create_and_send_transaction( - &[create_token_account_ix, init_token_account_ix], - &payer.pubkey(), - &[&spl_token_account_keypair, &payer], - ) - .await - .unwrap(); - - // 5. Mint SPL tokens to the SPL account - let mint_to_ix = spl_token::instruction::mint_to( - &spl_token::ID, - &mint, - &spl_token_account_keypair.pubkey(), - &payer.pubkey(), - &[&payer.pubkey()], - mint_amount, - ) - .unwrap(); - rpc.create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Verify SPL account has tokens - let spl_account_data = rpc - .get_account(spl_token_account_keypair.pubkey()) - .await - .unwrap() - .unwrap(); - let spl_account = - spl_pod::bytemuck::pod_from_bytes::(&spl_account_data.data).unwrap(); - let initial_spl_balance: u64 = spl_account.amount.into(); - assert_eq!(initial_spl_balance, mint_amount); - - // 6. Create sender's token ATA - let (sender_token_ata, _bump) = derive_token_ata(&payer.pubkey(), &mint); - let create_ata_instruction = - CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint) - .instruction() - .unwrap(); - - rpc.create_and_send_transaction(&[create_ata_instruction], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Verify sender's token ATA was created - let token_account_data = rpc.get_account(sender_token_ata).await.unwrap().unwrap(); - assert!( - !token_account_data.data.is_empty(), - "Sender token ATA should exist" - ); - - // 7. Transfer SPL tokens to sender's token account - let (spl_interface_pda, spl_interface_pda_bump) = find_spl_interface_pda_with_index(&mint, 0); - - let spl_to_token_ix = TransferFromSpl { - amount: spl_to_token_amount, - spl_interface_pda_bump, - source_spl_token_account: spl_token_account_keypair.pubkey(), - destination_token_account: sender_token_ata, - authority: payer.pubkey(), - mint, - payer: payer.pubkey(), - spl_interface_pda, - spl_token_program: anchor_spl::token::ID, - } - .instruction() - .unwrap(); - - rpc.create_and_send_transaction(&[spl_to_token_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // 8. Create recipient ATA + transfer token→token in one transaction - let recipient = Keypair::new(); - let (recipient_token_ata, _) = derive_token_ata(&recipient.pubkey(), &mint); - - let create_recipient_ata_ix = CreateAssociatedTokenAccount::new( - payer.pubkey(), - recipient.pubkey(), - mint, - ) - .instruction() - .unwrap(); - - let token_transfer_ix = Transfer { - source: sender_token_ata, - destination: recipient_token_ata, - amount: token_transfer_amount, - authority: payer.pubkey(), - max_top_up: None, - } - .instruction() - .unwrap(); - - let compute_unit_ix = ComputeBudgetInstruction::set_compute_unit_limit(10_000); - rpc.create_and_send_transaction( - &[compute_unit_ix, create_recipient_ata_ix, token_transfer_ix], - &payer.pubkey(), - &[&payer], - ) - .await - .unwrap(); -} - -fn load_keypair(path: &str) -> Result> { - let path = if path.starts_with("~") { - path.replace("~", &env::var("HOME").unwrap_or_default()) - } else { - path.to_string() - }; - let file = fs::read_to_string(&path)?; - let bytes: Vec = serde_json::from_str(&file)?; - Ok(Keypair::try_from(&bytes[..])?) -} -``` - - + Find [a full code example at the end](#full-code-example). @@ -350,220 +154,81 @@ Find [a full code example at the end](#full-code-example). -### Light Token Transfer Interface +### Transfer Interface CPI -Define the number of light-tokens / SPL tokens to transfer +The `TransferInterfaceCpi` transfers tokens between token accounts (SPL, Token-2022, or light-token). -- from which SPL or light-token account, and -- to which SPL or light-token account. + + ```rust -use light_token_sdk::token::TransferInterfaceCpi; +use light_token::instruction::TransferInterfaceCpi; -let mut transfer = TransferInterfaceCpi::new( - data.amount, - source_account.clone(), - destination_account.clone(), +TransferInterfaceCpi::new( + amount, + decimals, + source.clone(), + destination.clone(), authority.clone(), payer.clone(), - compressed_token_program_authority.clone(), -); + light_token_authority.clone(), + system_program.clone(), +) +.invoke()?; ``` - - - - - - - - -### SPL Transfer Interface (Optional) - -The SPL transfer interface is only needed for SPL ↔ light-token transfers. + + ```rust -transfer = transfer.with_spl_interface( - Some(mint.clone()), - Some(spl_token_program.clone()), - Some(spl_interface_pda.clone()), - data.spl_interface_pda_bump, -)?; -``` +use light_token::instruction::TransferInterfaceCpi; -SPL ↔ light-token transfers require a `spl_interface_pda`: +let signer_seeds = authority_seeds!(bump); -- **SPL → light-token**: SPL tokens are locked by the light token program in the PDA, light-tokens are minted to light-token accounts -- **light-token → SPL**: light-tokens are burned, SPL tokens transferred to SPL token accounts +TransferInterfaceCpi::new( + amount, + decimals, + source.clone(), + destination.clone(), + authority.clone(), + payer.clone(), + ctoken_authority.clone(), + system_program.clone(), +) +.invoke_signed(&[signer_seeds])?; +``` -The interface PDA is derived from the `mint` pubkey and pool seed. - - - + +
+ - - -### CPI - -CPI the Light Token program to execute the transfer. -Use `invoke()`, or `invoke_signed()` when a CPI requires a PDA signer. +# Full Code Example - - -```rust -transfer.invoke()?; -``` - - - + -```rust -let authority_seeds: &[&[u8]] = &[TRANSFER_INTERFACE_AUTHORITY_SEED, &[authority_bump]]; -transfer.invoke_signed(&[authority_seeds])?; -``` + + View the full example with shared test utilities: + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/transfer-interface). + - - + - - +
-# Full Code Example + - Find the source code - [here](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-tests/sdk-light-token-test/src/transfer_interface.rs). + View the full example with shared test utilities: + [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). -```rust expandable -use borsh::{BorshDeserialize, BorshSerialize}; -use light_token_sdk::token::TransferInterfaceCpi; -use solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; - -use crate::ID; - -/// PDA seed for authority in invoke_signed variants -pub const TRANSFER_INTERFACE_AUTHORITY_SEED: &[u8] = b"transfer_interface_authority"; - -/// Instruction data for TransferInterfaceCpi -#[derive(BorshSerialize, BorshDeserialize, Debug)] -pub struct TransferInterfaceData { - pub amount: u64, - /// Required for SPL<->Token transfers, None for Token->Token - pub token_pool_pda_bump: Option, -} - -/// Handler for TransferInterfaceCpi (invoke) -/// -/// This unified interface automatically detects account types and routes to: -/// - Token -> Token transfer -/// - Token -> SPL transfer -/// - SPL -> Token transfer -/// -/// Account order: -/// - accounts[0]: compressed_token_program (for CPI) -/// - accounts[1]: source_account (SPL or light-token) -/// - accounts[2]: destination_account (SPL or light-token) -/// - accounts[3]: authority (signer) -/// - accounts[4]: payer (signer) -/// - accounts[5]: compressed_token_program_authority -/// For SPL bridge (optional, required for SPL<->Token): -/// - accounts[6]: mint -/// - accounts[7]: token_pool_pda -/// - accounts[8]: spl_token_program -pub fn process_transfer_interface_invoke( - accounts: &[AccountInfo], - data: TransferInterfaceData, -) -> Result<(), ProgramError> { - if accounts.len() < 6 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - let mut transfer = TransferInterfaceCpi::new( - data.amount, - accounts[1].clone(), // source_account - accounts[2].clone(), // destination_account - accounts[3].clone(), // authority - accounts[4].clone(), // payer - accounts[5].clone(), // compressed_token_program_authority - ); - - // Add SPL bridge config if provided - if accounts.len() >= 9 && data.token_pool_pda_bump.is_some() { - transfer = transfer.with_spl_interface( - Some(accounts[6].clone()), // mint - Some(accounts[8].clone()), // spl_token_program - Some(accounts[7].clone()), // token_pool_pda - data.token_pool_pda_bump, - )?; - } - - transfer.invoke()?; - - Ok(()) -} - -/// Handler for TransferInterfaceCpi with PDA authority (invoke_signed) -/// -/// The authority is a PDA derived from TRANSFER_INTERFACE_AUTHORITY_SEED. -/// -/// Account order: -/// - accounts[0]: compressed_token_program (for CPI) -/// - accounts[1]: source_account (SPL or light-token) -/// - accounts[2]: destination_account (SPL or light-token) -/// - accounts[3]: authority (PDA, not signer - program signs) -/// - accounts[4]: payer (signer) -/// - accounts[5]: compressed_token_program_authority -/// For SPL bridge (optional, required for SPL<->Token): -/// - accounts[6]: mint -/// - accounts[7]: token_pool_pda -/// - accounts[8]: spl_token_program -pub fn process_transfer_interface_invoke_signed( - accounts: &[AccountInfo], - data: TransferInterfaceData, -) -> Result<(), ProgramError> { - if accounts.len() < 6 { - return Err(ProgramError::NotEnoughAccountKeys); - } - - // Derive the PDA for the authority - let (authority_pda, authority_bump) = - Pubkey::find_program_address(&[TRANSFER_INTERFACE_AUTHORITY_SEED], &ID); - - // Verify the authority account is the PDA we expect - if &authority_pda != accounts[3].key { - return Err(ProgramError::InvalidSeeds); - } - - let mut transfer = TransferInterfaceCpi::new( - data.amount, - accounts[1].clone(), // source_account - accounts[2].clone(), // destination_account - accounts[3].clone(), // authority (PDA) - accounts[4].clone(), // payer - accounts[5].clone(), // compressed_token_program_authority - ); - - // Add SPL bridge config if provided - if accounts.len() >= 9 && data.token_pool_pda_bump.is_some() { - transfer = transfer.with_spl_interface( - Some(accounts[6].clone()), // mint - Some(accounts[8].clone()), // spl_token_program - Some(accounts[7].clone()), // token_pool_pda - data.token_pool_pda_bump, - )?; - } - - // Invoke with PDA signing - let authority_seeds: &[&[u8]] = &[TRANSFER_INTERFACE_AUTHORITY_SEED, &[authority_bump]]; - transfer.invoke_signed(&[authority_seeds])?; - - Ok(()) -} -``` + + + +
@@ -574,9 +239,9 @@ pub fn process_transfer_interface_invoke_signed( {" "} diff --git a/light-token/cookbook/update-metadata.mdx b/light-token/cookbook/update-metadata.mdx deleted file mode 100644 index e69de29b..00000000 diff --git a/light-token/cookbook/wrap-unwrap.mdx b/light-token/cookbook/wrap-unwrap.mdx index 59a50eeb..2158ae0d 100644 --- a/light-token/cookbook/wrap-unwrap.mdx +++ b/light-token/cookbook/wrap-unwrap.mdx @@ -12,6 +12,9 @@ import WrapActionCode from "/snippets/code-snippets/light-token/wrap/action.mdx" import WrapInstructionCode from "/snippets/code-snippets/light-token/wrap/instruction.mdx"; import UnwrapActionCode from "/snippets/code-snippets/light-token/unwrap/action.mdx"; import UnwrapInstructionCode from "/snippets/code-snippets/light-token/unwrap/instruction.mdx"; +import WrapRustActionCode from "/snippets/code-snippets/light-token/wrap/rust-client/action.mdx"; +import UnwrapRustActionCode from "/snippets/code-snippets/light-token/unwrap/rust-client/action.mdx"; +import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; - **Wrap**: Move tokens from SPL/T22 account → light-token ATA (hot balance) - **Unwrap**: Move tokens from light-token ATA (hot balance) → SPL/T22 account @@ -23,7 +26,8 @@ import UnwrapInstructionCode from "/snippets/code-snippets/light-token/unwrap/in [unwrap.ts](https://github.com/Lightprotocol/light-protocol/blob/0c4e2417b2df2d564721b89e18d1aad3665120e7/js/compressed-token/src/v3/actions/unwrap.ts) -## Get Started + + @@ -68,3 +72,66 @@ import UnwrapInstructionCode from "/snippets/code-snippets/light-token/unwrap/in + + + + + + + + + +### Prerequisites + + + + + + +### Wrap SPL tokens to light-token ATA + + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + + + + + + + + + + + + + + + +### Prerequisites + + + + + + +### Unwrap light-tokens to SPL account + + + Find the full example including shared test utilities in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + + + + + + + + + + + + + + + diff --git a/light-token/cookbook/extensions.mdx b/light-token/extensions.mdx similarity index 100% rename from light-token/cookbook/extensions.mdx rename to light-token/extensions.mdx diff --git a/light-token/toolkits/for-streaming-mints.mdx b/light-token/toolkits/for-streaming-mints.mdx index 7d96a5c1..f9a7b1d5 100644 --- a/light-token/toolkits/for-streaming-mints.mdx +++ b/light-token/toolkits/for-streaming-mints.mdx @@ -22,9 +22,9 @@ Find devnet examples [here](https://github.com/Lightprotocol/examples-light-toke helius-laserstream = "0.1" tokio = { version = "1", features = ["full"] } futures = "0.3" -light-event = "0.3" -light-compressed-account = "0.8" -light-token-interface = "0.2" +light-event = "0.2" +light-compressed-account = "0.7" +light-token-interface = "0.1" borsh = "0.10" bs58 = "0.5" ``` @@ -33,7 +33,7 @@ bs58 = "0.5" use futures::StreamExt; use helius_laserstream::{subscribe, LaserstreamConfig}; use light_event::parse::event_from_light_transaction; -use light_token_interface::state::mint::Mint; +use light_token_interface::state::Mint; use light_token_interface::state::extensions::ExtensionStruct; use borsh::BorshDeserialize; @@ -202,16 +202,13 @@ if let Some((name, symbol, uri)) = extract_metadata(&mint) { pub struct Mint { pub base: BaseMint, pub metadata: MintMetadata, - /// Reserved bytes (16 bytes) for T22 layout compatibility. pub reserved: [u8; 16], - /// Account type discriminator at byte offset 165 (1 = Mint, 2 = Account) pub account_type: u8, - /// Compression info embedded directly in the mint pub compression: CompressionInfo, pub extensions: Option>, } -/// SPL compatible basemint structure (82 bytes) +/// SPL-compatible base mint structure #[repr(C)] pub struct BaseMint { pub mint_authority: Option, @@ -221,19 +218,13 @@ pub struct BaseMint { pub freeze_authority: Option, } -/// Light Protocol-specific metadata for compressed mints. -/// Total size: 67 bytes +/// Light Protocol metadata for compressed mints (67 bytes) #[repr(C)] pub struct MintMetadata { - /// Version for upgradability pub version: u8, - /// Whether the compressed mint has been decompressed to a Mint Solana account. pub mint_decompressed: bool, - /// PDA derived from mint_signer pub mint: Pubkey, - /// Signer pubkey used to derive the mint PDA pub mint_signer: [u8; 32], - /// Bump seed from mint PDA derivation pub bump: u8, } ``` diff --git a/resources/sdks/instruction-decoder.mdx b/resources/sdks/instruction-decoder.mdx new file mode 100644 index 00000000..b387417e --- /dev/null +++ b/resources/sdks/instruction-decoder.mdx @@ -0,0 +1,68 @@ +--- +title: Instruction Decoder +description: Decode instruction data for test logging +--- + +## Overview + +The instruction decoder crate decodes raw instruction data into named fields for logging during tests. Off-chain only. + +## Installation + +```toml +[dependencies] +light-instruction-decoder = "0.1.0" +``` + +## Usage + +### Derive macro for instruction enums + +```rust +use light_instruction_decoder_derive::InstructionDecoder; + +#[derive(InstructionDecoder)] +#[instruction_decoder( + program_id = "MyProgram11111111111111111111111111111111111", + program_name = "My Program" +)] +pub enum MyInstruction { + #[instruction_decoder(accounts = CreateRecord)] + CreateRecord, +} +``` + +### Attribute macro for Anchor programs + +```rust +use light_instruction_decoder_derive::instruction_decoder; + +#[instruction_decoder] +#[program] +pub mod my_program { + pub fn create_record(ctx: Context) -> Result<()> { + // ... + } +} +``` + +### Register with test config + +```rust +let config = ProgramTestConfig::new() + .add_decoder(MyProgramInstructionDecoder::default()); +``` + +## Configuration + +| Attribute | Description | +|-----------|-------------| +| `program_id` | Program address | +| `program_name` | Display name (optional) | +| `discriminator_size` | 1, 4, or 8 bytes (default: 8) | +| `accounts` | Struct to extract account names from | +| `params` | Struct for parameter deserialization | + +## API reference + +See [docs.rs/light-instruction-decoder](https://docs.rs/light-instruction-decoder) \ No newline at end of file diff --git a/resources/sdks/program-development.mdx b/resources/sdks/program-development.mdx index 7ae55b87..fa4d876d 100644 --- a/resources/sdks/program-development.mdx +++ b/resources/sdks/program-development.mdx @@ -11,6 +11,7 @@ ZK Compression's Rust crates are published to [crates.io](https://docs.rs/releas - [`light-sdk`](https://github.com/Lightprotocol/light-protocol/tree/main/sdk-libs/sdk) — For Anchor and native programs. Includes CPI utilities, compressed account abstractions similar to anchor Account, and metadata structs for CPIs to the Light System program. - [`light-sdk-pinocchio`](https://github.com/Lightprotocol/light-protocol/tree/main/sdk-libs/sdk-pinocchio) — For Pinocchio programs. Pinocchio-optimized SDK with compressed account abstractions and CPI utilities. +- [`light-instruction-decoder`](/resources/sdks/instruction-decoder) — Decodes instruction data into named fields for test logging. Off-chain only. ## Light Programs Overview diff --git a/scripts/copy-light-token-snippets.sh b/scripts/copy-light-token-snippets.sh index 862a7069..e607a3fe 100755 --- a/scripts/copy-light-token-snippets.sh +++ b/scripts/copy-light-token-snippets.sh @@ -3,7 +3,7 @@ # Script to copy TypeScript code from examples-light-token to docs/snippets/code-snippets/light-token # Wraps each file in typescript markdown code blocks -EXAMPLES="/home/tilo/Workspace/examples-light-token/cookbook" +EXAMPLES="/home/tilo/Workspace/examples-light-token/typescript-client" SNIPPETS_DIR="/home/tilo/Workspace/docs/snippets/code-snippets/light-token" # Recipes to process (matching directory names) diff --git a/scripts/copy-program-snippets.sh b/scripts/copy-program-snippets.sh new file mode 100755 index 00000000..a88cf560 --- /dev/null +++ b/scripts/copy-program-snippets.sh @@ -0,0 +1,213 @@ +#!/bin/bash + +# Script to copy program code from example repos to docs snippets +# Creates CodeGroup MDX files with lib.rs/instruction.rs and test.rs combined + +SNIPPETS_DIR="/home/tilo/Workspace/docs/snippets/code-snippets/light-token" + +# ============================================================================= +# NATIVE PROGRAMS +# ============================================================================= + +NATIVE_EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token-native/program-examples/native-rust" + +# Recipes to process (output-name:source_name:test_name) +# test_name is optional, defaults to source_name if not provided +NATIVE_RECIPES=( + "create-mint:create_mint" + "mint-to:mint_to" + "mint-to-checked:mint_to_checked" + "create-ata:create_ata" + "create-token-account:create_token_account" + "close-token-account:close" + "transfer-interface:transfer_interface:transfer" + "transfer-checked:transfer_checked" + "approve:approve" + "revoke:revoke" + "burn:burn" + "freeze:freeze" + "thaw:thaw" +) + +echo "=== Processing Native program files ===" +echo "" + +for mapping in "${NATIVE_RECIPES[@]}"; do + # Parse the mapping (output:source:test) + IFS=':' read -r output_name source_name test_name <<< "$mapping" + # Default test_name to source_name if not provided + test_name="${test_name:-$source_name}" + + echo "Processing: $output_name (source: $source_name, test: $test_name)" + + output_dir="$SNIPPETS_DIR/$output_name/native-program" + mkdir -p "$output_dir" + + instruction_file="$NATIVE_EXAMPLES_DIR/program/src/instructions/$source_name.rs" + test_file="$NATIVE_EXAMPLES_DIR/program/tests/$test_name.rs" + + # Check source files exist + if [ ! -f "$instruction_file" ]; then + echo " WARNING: Not found - $instruction_file" + continue + fi + + if [ ! -f "$test_file" ]; then + echo " WARNING: Not found - $test_file" + continue + fi + + # Create CodeGroup MDX with both files + output_file="$output_dir/full-example.mdx" + + { + echo '' + echo '```rust instruction.rs' + cat "$instruction_file" + echo '```' + echo '' + echo '```rust test.rs' + cat "$test_file" + echo '```' + echo '' + } > "$output_file" + + echo " Created: $output_file" +done + +# ============================================================================= +# ANCHOR PROGRAMS +# ============================================================================= + +ANCHOR_EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token/program-examples/anchor/programs-sdk" + +# Anchor recipes (output-name:anchor-dir-name) +# Some have different directory names (e.g., close-token-account uses 'close' dir) +ANCHOR_RECIPES=( + "create-mint:create-mint" + "mint-to:mint-to" + "create-ata:create-ata" + "create-token-account:create-token-account" + "close-token-account:close" + "transfer-interface:transfer-interface" + "approve:approve" + "revoke:revoke" + "burn:burn" + "freeze:freeze" + "thaw:thaw" + "transfer-checked:transfer-checked" +) + +echo "" +echo "=== Processing Anchor program files ===" +echo "" + +for mapping in "${ANCHOR_RECIPES[@]}"; do + IFS=':' read -r output_name anchor_dir <<< "$mapping" + + echo "Processing: $output_name (dir: $anchor_dir)" + + output_dir="$SNIPPETS_DIR/$output_name/anchor-program" + mkdir -p "$output_dir" + + lib_file="$ANCHOR_EXAMPLES_DIR/$anchor_dir/src/lib.rs" + test_file="$ANCHOR_EXAMPLES_DIR/$anchor_dir/tests/test.rs" + + # Check lib file exists (required) + if [ ! -f "$lib_file" ]; then + echo " WARNING: Not found - $lib_file" + continue + fi + + # Create CodeGroup MDX + output_file="$output_dir/full-example.mdx" + + if [ -f "$test_file" ]; then + # Both lib.rs and test.rs + { + echo '' + echo '```rust lib.rs' + cat "$lib_file" + echo '```' + echo '' + echo '```rust test.rs' + cat "$test_file" + echo '```' + echo '' + } > "$output_file" + else + # Only lib.rs (no test file) + { + echo '```rust lib.rs' + cat "$lib_file" + echo '```' + } > "$output_file" + echo " Note: No test file found, using lib.rs only" + fi + + echo " Created: $output_file" +done + +# ============================================================================= +# ANCHOR MACROS +# ============================================================================= + +ANCHOR_MACROS_DIR="/home/tilo/Workspace/examples-light-token/program-examples/anchor/macro-basics" + +ANCHOR_MACRO_RECIPES=( + "create-mint:create-mint-macro" + "create-ata:create-ata-macro" + "create-token-account:create-token-account-macro" +) + +echo "" +echo "=== Processing Anchor Macro program files ===" +echo "" + +for mapping in "${ANCHOR_MACRO_RECIPES[@]}"; do + IFS=':' read -r output_name macro_dir <<< "$mapping" + + echo "Processing: $output_name (dir: $macro_dir)" + + output_dir="$SNIPPETS_DIR/$output_name/anchor-macro" + mkdir -p "$output_dir" + + lib_file="$ANCHOR_MACROS_DIR/$macro_dir/src/lib.rs" + test_file="$ANCHOR_MACROS_DIR/$macro_dir/tests/test.rs" + + if [ ! -f "$lib_file" ]; then + echo " WARNING: Not found - $lib_file" + continue + fi + + output_file="$output_dir/full-example.mdx" + + if [ -f "$test_file" ]; then + { + echo '' + echo '```rust lib.rs' + cat "$lib_file" + echo '```' + echo '' + echo '```rust test.rs' + cat "$test_file" + echo '```' + echo '' + } > "$output_file" + else + { + echo '```rust lib.rs' + cat "$lib_file" + echo '```' + } > "$output_file" + fi + + echo " Created: $output_file" +done + +echo "" +echo "Done! Created program snippets in: $SNIPPETS_DIR" +echo "" +echo "Files created:" +find "$SNIPPETS_DIR" -path "*-program/*.mdx" -type f | sort +find "$SNIPPETS_DIR" -path "*-macro/*.mdx" -type f | sort diff --git a/scripts/copy-rust-snippets.sh b/scripts/copy-rust-snippets.sh new file mode 100755 index 00000000..05e5c8a7 --- /dev/null +++ b/scripts/copy-rust-snippets.sh @@ -0,0 +1,164 @@ +#!/bin/bash + +# Script to copy Rust client code from examples-light-token-rust-client to docs snippets +# Creates action.mdx and instruction.mdx files wrapped in rust code blocks + +EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token-rust-client/rust-client" +SNIPPETS_DIR="/home/tilo/Workspace/docs/snippets/code-snippets/light-token" + +# Full recipes (action + instruction in same directory) +FULL_RECIPES=("create-mint" "create-ata" "mint-to" "transfer-interface" "transfer-checked") + +# Action-only recipes (action file only) +ACTION_ONLY=("wrap" "unwrap") + +# Instruction-only recipes with name mapping (target:source) +# Format: "output-dir:source-filename" +INSTRUCTION_ONLY=( + "burn:burn" + "close-token-account:close" + "create-token-account:create_token_account" +) + +# Function to wrap Rust code in markdown +wrap_rust() { + local input_file="$1" + local output_file="$2" + echo '```rust' > "$output_file" + cat "$input_file" >> "$output_file" + echo '```' >> "$output_file" + echo "Created: $output_file" +} + +# Convert kebab-case to snake_case +kebab_to_snake() { + echo "$1" | tr '-' '_' +} + +echo "=== Processing Rust client files ===" +echo "" + +# Process full recipes (action + instruction) +echo "--- Full recipes (action + instruction) ---" +for recipe in "${FULL_RECIPES[@]}"; do + rust_name=$(kebab_to_snake "$recipe") + echo "Processing: $recipe (source: $rust_name.rs)" + + output_dir="$SNIPPETS_DIR/$recipe/rust-client" + mkdir -p "$output_dir" + + # Action file + action_file="$EXAMPLES_DIR/actions/$rust_name.rs" + if [ -f "$action_file" ]; then + wrap_rust "$action_file" "$output_dir/action.mdx" + else + echo " WARNING: Not found - $action_file" + fi + + # Instruction file + instruction_file="$EXAMPLES_DIR/instructions/$rust_name.rs" + if [ -f "$instruction_file" ]; then + wrap_rust "$instruction_file" "$output_dir/instruction.mdx" + else + echo " WARNING: Not found - $instruction_file" + fi +done + +echo "" +echo "--- Action-only recipes ---" +for recipe in "${ACTION_ONLY[@]}"; do + rust_name=$(kebab_to_snake "$recipe") + echo "Processing: $recipe (source: $rust_name.rs)" + + output_dir="$SNIPPETS_DIR/$recipe/rust-client" + mkdir -p "$output_dir" + + # Action file only + action_file="$EXAMPLES_DIR/actions/$rust_name.rs" + if [ -f "$action_file" ]; then + wrap_rust "$action_file" "$output_dir/action.mdx" + else + echo " WARNING: Not found - $action_file" + fi +done + +echo "" +echo "--- Instruction-only recipes ---" +for mapping in "${INSTRUCTION_ONLY[@]}"; do + output_name="${mapping%%:*}" + source_name="${mapping##*:}" + echo "Processing: $output_name (source: $source_name.rs)" + + output_dir="$SNIPPETS_DIR/$output_name/rust-client" + mkdir -p "$output_dir" + + # Instruction file only + instruction_file="$EXAMPLES_DIR/instructions/$source_name.rs" + if [ -f "$instruction_file" ]; then + wrap_rust "$instruction_file" "$output_dir/instruction.mdx" + else + echo " WARNING: Not found - $instruction_file" + fi +done + +echo "" +echo "--- Freeze/Thaw recipes ---" +output_dir="$SNIPPETS_DIR/freeze-thaw/rust-client" +mkdir -p "$output_dir" + +freeze_file="$EXAMPLES_DIR/instructions/freeze.rs" +if [ -f "$freeze_file" ]; then + wrap_rust "$freeze_file" "$output_dir/freeze-instruction.mdx" +else + echo " WARNING: Not found - $freeze_file" +fi + +thaw_file="$EXAMPLES_DIR/instructions/thaw.rs" +if [ -f "$thaw_file" ]; then + wrap_rust "$thaw_file" "$output_dir/thaw-instruction.mdx" +else + echo " WARNING: Not found - $thaw_file" +fi + +echo "" +echo "--- Approve/Revoke recipes ---" +output_dir="$SNIPPETS_DIR/approve-revoke/rust-client" +mkdir -p "$output_dir" + +# Approve action +approve_action="$EXAMPLES_DIR/actions/approve.rs" +if [ -f "$approve_action" ]; then + wrap_rust "$approve_action" "$output_dir/approve-action.mdx" +else + echo " WARNING: Not found - $approve_action" +fi + +# Approve instruction +approve_instruction="$EXAMPLES_DIR/instructions/approve.rs" +if [ -f "$approve_instruction" ]; then + wrap_rust "$approve_instruction" "$output_dir/approve-instruction.mdx" +else + echo " WARNING: Not found - $approve_instruction" +fi + +# Revoke action +revoke_action="$EXAMPLES_DIR/actions/revoke.rs" +if [ -f "$revoke_action" ]; then + wrap_rust "$revoke_action" "$output_dir/revoke-action.mdx" +else + echo " WARNING: Not found - $revoke_action" +fi + +# Revoke instruction +revoke_instruction="$EXAMPLES_DIR/instructions/revoke.rs" +if [ -f "$revoke_instruction" ]; then + wrap_rust "$revoke_instruction" "$output_dir/revoke-instruction.mdx" +else + echo " WARNING: Not found - $revoke_instruction" +fi + +echo "" +echo "Done! Created Rust snippets in: $SNIPPETS_DIR" +echo "" +echo "Files created:" +find "$SNIPPETS_DIR" -path "*/rust-client/*.mdx" -type f | sort diff --git a/snippets/accounts-list/light-token-create-accounts-list-client.mdx b/snippets/accounts-list/light-token-create-accounts-list-client.mdx index ee97c25f..c85d9193 100644 --- a/snippets/accounts-list/light-token-create-accounts-list-client.mdx +++ b/snippets/accounts-list/light-token-create-accounts-list-client.mdx @@ -89,7 +89,7 @@ mutable - - light token program PDA that fronts rent exemption at creation. + - light token program PDA that pays rent exemption at creation.
- Claims rent when account compresses. diff --git a/snippets/accounts-list/light-token-create-accounts-list.mdx b/snippets/accounts-list/light-token-create-accounts-list.mdx index 024f2a0c..611fcf8b 100644 --- a/snippets/accounts-list/light-token-create-accounts-list.mdx +++ b/snippets/accounts-list/light-token-create-accounts-list.mdx @@ -16,7 +16,7 @@ signer, mutable - Pays initial rent per epoch, transaction fee and compression incentive.
- - Does NOT pay rent exemption (fronted by `rent_sponsor`). + - Does NOT pay rent exemption (paid by the light token program, `rent_sponsor`). diff --git a/snippets/accounts-list/light-token-create-ata-accounts-list.mdx b/snippets/accounts-list/light-token-create-ata-accounts-list.mdx index 46c8e291..d29eea47 100644 --- a/snippets/accounts-list/light-token-create-ata-accounts-list.mdx +++ b/snippets/accounts-list/light-token-create-ata-accounts-list.mdx @@ -32,7 +32,7 @@ signer, mutable - Pays initial rent per epoch, transaction fee and compression incentive.
- - Does NOT pay rent exemption (fronted by `rent_sponsor`). + - Does NOT pay rent exemption (paid by the light token program, `rent_sponsor`). diff --git a/snippets/code-samples/code-compare-snippets.jsx b/snippets/code-samples/code-compare-snippets.jsx index 9fc0e28e..d68db6c0 100644 --- a/snippets/code-samples/code-compare-snippets.jsx +++ b/snippets/code-samples/code-compare-snippets.jsx @@ -113,6 +113,176 @@ export const lightTransferCode = [ ");", ].join("\n"); +// === TRANSFER (RUST) === +export const splTransferRustCode = [ + "// SPL transfer", + "use spl_token::instruction::transfer;", + "", + "let ix = transfer(", + " &spl_token::id(),", + " &source,", + " &destination,", + " &authority,", + " &[],", + " amount,", + ")?;", +].join("\n"); + +export const lightTransferRustCode = [ + "// light-token transfer", + "use light_token_sdk::token::TransferInterface;", + "", + "let ix = TransferInterface {", + " source,", + " destination,", + " amount,", + " decimals,", + " authority: payer.pubkey(),", + " payer: payer.pubkey(),", + " spl_interface: None,", + " max_top_up: None,", + " source_owner: LIGHT_TOKEN_PROGRAM_ID,", + " destination_owner: LIGHT_TOKEN_PROGRAM_ID,", + "}", + ".instruction()?;", +].join("\n"); + +// === CREATE ATA (RUST) === +export const splCreateAtaRustCode = [ + "// SPL create ATA", + "use spl_associated_token_account::instruction::create_associated_token_account;", + "", + "let ix = create_associated_token_account(", + " &payer.pubkey(),", + " &owner.pubkey(),", + " &mint,", + " &spl_token::id(),", + ");", +].join("\n"); + +export const lightCreateAtaRustCode = [ + "// light-token create ATA", + "use light_token_sdk::token::CreateAssociatedTokenAccount;", + "", + "let ix = CreateAssociatedTokenAccount::new(", + " payer.pubkey(),", + " owner.pubkey(),", + " mint,", + ")", + ".instruction()?;", +].join("\n"); + +// === CREATE MINT (RUST) === +export const splCreateMintRustCode = [ + "// SPL create mint", + "use spl_token::instruction::initialize_mint;", + "", + "let ix = initialize_mint(", + " &spl_token::id(),", + " &mint.pubkey(),", + " &mint_authority,", + " Some(&freeze_authority),", + " decimals,", + ")?;", +].join("\n"); + +export const lightCreateMintRustCode = [ + "// light-token create mint", + "use light_token_sdk::token::CreateMint;", + "", + "let ix = CreateMint::new(", + " // includes decimals, mint_authority, freeze_authority, extensions, rent config", + " params,", + " mint_seed.pubkey(),", + " payer.pubkey(),", + " address_tree.tree,", + " output_queue,", + ")", + ".instruction()?;", +].join("\n"); + +// === MINT TO (RUST) === +export const splMintToRustCode = [ + "// SPL mint to", + "use spl_token::instruction::mint_to;", + "", + "let ix = mint_to(", + " &spl_token::id(),", + " &mint,", + " &destination,", + " &mint_authority,", + " &[],", + " amount,", + ")?;", +].join("\n"); + +export const lightMintToRustCode = [ + "// light-token mint to", + "use light_token_sdk::token::MintTo;", + "", + "let ix = MintTo {", + " mint,", + " destination,", + " amount,", + " authority: payer.pubkey(),", + " max_top_up: None,", + "}", + ".instruction()?;", +].join("\n"); + +// === CREATE TOKEN ACCOUNT (RUST) === +export const splCreateTokenAccountRustCode = [ + "// SPL create token account", + "use spl_token::instruction::initialize_account;", + "", + "let ix = initialize_account(", + " &spl_token::id(),", + " &account,", + " &mint,", + " &owner,", + ")?;", +].join("\n"); + +export const lightCreateTokenAccountRustCode = [ + "// light-token create token account", + "use light_token_sdk::token::CreateTokenAccount;", + "", + "let ix = CreateTokenAccount::new(", + " payer.pubkey(),", + " account.pubkey(),", + " mint,", + " owner,", + ")", + ".instruction()?;", +].join("\n"); + +// === CLOSE TOKEN ACCOUNT (RUST) === +export const splCloseAccountRustCode = [ + "// SPL close account", + "use spl_token::instruction::close_account;", + "", + "let ix = close_account(", + " &spl_token::id(),", + " &account,", + " &destination,", + " &owner,", + " &[],", + ")?;", +].join("\n"); + +export const lightCloseAccountRustCode = [ + "// light-token close account", + "use light_token_sdk::token::{CloseAccount, LIGHT_TOKEN_PROGRAM_ID};", + "", + "let ix = CloseAccount::new(", + " LIGHT_TOKEN_PROGRAM_ID,", + " account,", + " destination,", + " owner,", + ")", + ".instruction()?;", +].join("\n"); + // === BLOG - CREATE ATA (different comments) === export const blogSplCreateAtaCode = [ "// Create SPL token account", @@ -133,3 +303,136 @@ export const blogLightCreateAtaCode = [ " mint", ");", ].join("\n"); + +// === BURN (RUST) === +export const splBurnRustCode = [ + "// SPL burn", + "use spl_token::instruction::burn;", + "", + "let ix = burn(", + " &spl_token::id(),", + " &source,", + " &mint,", + " &authority,", + " &[],", + " amount,", + ")?;", +].join("\n"); + +export const lightBurnRustCode = [ + "// light-token burn", + "use light_token_sdk::token::Burn;", + "", + "let ix = Burn {", + " source,", + " mint,", + " amount,", + " authority: payer.pubkey(),", + " max_top_up: None,", + "}", + ".instruction()?;", +].join("\n"); + +// === FREEZE (RUST) === +export const splFreezeRustCode = [ + "// SPL freeze", + "use spl_token::instruction::freeze_account;", + "", + "let ix = freeze_account(", + " &spl_token::id(),", + " &account,", + " &mint,", + " &freeze_authority,", + " &[],", + ")?;", +].join("\n"); + +export const lightFreezeRustCode = [ + "// light-token freeze", + "use light_token_sdk::token::Freeze;", + "", + "let ix = Freeze {", + " token_account: ata,", + " mint,", + " freeze_authority: payer.pubkey(),", + "}", + ".instruction()?;", +].join("\n"); + +// === THAW (RUST) === +export const splThawRustCode = [ + "// SPL thaw", + "use spl_token::instruction::thaw_account;", + "", + "let ix = thaw_account(", + " &spl_token::id(),", + " &account,", + " &mint,", + " &freeze_authority,", + " &[],", + ")?;", +].join("\n"); + +export const lightThawRustCode = [ + "// light-token thaw", + "use light_token_sdk::token::Thaw;", + "", + "let ix = Thaw {", + " token_account: ata,", + " mint,", + " freeze_authority: payer.pubkey(),", + "}", + ".instruction()?;", +].join("\n"); + +// === APPROVE (RUST) === +export const splApproveRustCode = [ + "// SPL approve", + "use spl_token::instruction::approve;", + "", + "let ix = approve(", + " &spl_token::id(),", + " &source,", + " &delegate,", + " &owner,", + " &[],", + " amount,", + ")?;", +].join("\n"); + +export const lightApproveRustCode = [ + "// light-token approve", + "use light_token_sdk::token::Approve;", + "", + "let ix = Approve {", + " token_account: ata,", + " delegate: delegate.pubkey(),", + " owner: payer.pubkey(),", + " amount,", + "}", + ".instruction()?;", +].join("\n"); + +// === REVOKE (RUST) === +export const splRevokeRustCode = [ + "// SPL revoke", + "use spl_token::instruction::revoke;", + "", + "let ix = revoke(", + " &spl_token::id(),", + " &source,", + " &owner,", + " &[],", + ")?;", +].join("\n"); + +export const lightRevokeRustCode = [ + "// light-token revoke", + "use light_token_sdk::token::Revoke;", + "", + "let ix = Revoke {", + " token_account: ata,", + " owner: payer.pubkey(),", + "}", + ".instruction()?;", +].join("\n"); diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx new file mode 100644 index 00000000..5d56d8df --- /dev/null +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx @@ -0,0 +1,35 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_client::actions::Approve; +use rust_client::{setup, SetupContext}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + ata, + .. + } = setup().await; + + let delegate = Keypair::new(); + + let sig = Approve { + token_account: ata, + delegate: delegate.pubkey(), + amount: 500_000, + owner: Some(payer.pubkey()), + } + .execute(&mut rpc, &payer) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Delegate: {:?} Tx: {sig}", token.delegate); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-full.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-full.mdx new file mode 100644 index 00000000..e157cb8e --- /dev/null +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-full.mdx @@ -0,0 +1,35 @@ +```rust +mod shared; + +use light_client::rpc::Rpc; +use light_token_sdk::token::Approve; +use shared::SetupContext; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn approve_delegate() { + // Setup creates mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + ata, + .. + } = shared::setup().await; + + let delegate = Keypair::new(); + let delegate_amount = 500_000u64; + + let approve_ix = Approve { + token_account: ata, + delegate: delegate.pubkey(), + owner: payer.pubkey(), + amount: delegate_amount, + } + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[approve_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx new file mode 100644 index 00000000..07b994ed --- /dev/null +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx @@ -0,0 +1,39 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token::instruction::Approve; +use rust_client::{setup, SetupContext}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + ata, + .. + } = setup().await; + + let delegate = Keypair::new(); + let delegate_amount = 500_000u64; + + let approve_ix = Approve { + token_account: ata, + delegate: delegate.pubkey(), + owner: payer.pubkey(), + amount: delegate_amount, + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[approve_ix], &payer.pubkey(), &[&payer]) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Delegate: {:?} Tx: {sig}", token.delegate); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx new file mode 100644 index 00000000..5f449840 --- /dev/null +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx @@ -0,0 +1,31 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_client::actions::Revoke; +use rust_client::{setup, SetupContext}; +use solana_sdk::signer::Signer; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint and ATA with approved delegate + let SetupContext { + mut rpc, + payer, + ata, + .. + } = setup().await; + + let sig = Revoke { + token_account: ata, + owner: Some(payer.pubkey()), + } + .execute(&mut rpc, &payer) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Delegate: {:?} Tx: {sig}", token.delegate); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-full.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-full.mdx new file mode 100644 index 00000000..5a15cc0f --- /dev/null +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-full.mdx @@ -0,0 +1,30 @@ +```rust +mod shared; + +use light_client::rpc::Rpc; +use light_token_sdk::token::Revoke; +use shared::SetupContext; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn revoke_delegation() { + // Setup creates mint, ATA with tokens, and approves delegate + let SetupContext { + mut rpc, + payer, + ata, + .. + } = shared::setup().await; + + let revoke_ix = Revoke { + token_account: ata, + owner: payer.pubkey(), + } + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[revoke_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx new file mode 100644 index 00000000..cc71b60c --- /dev/null +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx @@ -0,0 +1,34 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token::instruction::Revoke; +use rust_client::{setup, SetupContext}; +use solana_sdk::signer::Signer; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint, ATA with tokens, and approves delegate + let SetupContext { + mut rpc, + payer, + ata, + .. + } = setup().await; + + let revoke_ix = Revoke { + token_account: ata, + owner: payer.pubkey(), + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[revoke_ix], &payer.pubkey(), &[&payer]) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Delegate: {:?} Tx: {sig}", token.delegate); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx new file mode 100644 index 00000000..821555fa --- /dev/null +++ b/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx @@ -0,0 +1,87 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::ApproveCpi; + +declare_id!("37XmzKqSG2VD1ZBvzyfbt1HN1mT1bqVAmfzX2ziB3KT1"); + +#[program] +pub mod light_token_anchor_approve { + use super::*; + + pub fn approve(ctx: Context, amount: u64) -> Result<()> { + ApproveCpi { + token_account: ctx.accounts.token_account.to_account_info(), + delegate: ctx.accounts.delegate.to_account_info(), + owner: ctx.accounts.owner.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + amount, + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct ApproveAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub token_account: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub delegate: AccountInfo<'info>, + pub owner: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_program_test::Rpc; +use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; +use light_token_anchor_approve::{instruction::Approve, ID}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signature::Keypair, + signer::Signer, + system_program, +}; +use test_utils::{mint_tokens, setup_test_env}; + +#[tokio::test] +async fn test_approve() { + let mut env = setup_test_env("light_token_anchor_approve", ID).await; + + // Mint tokens first + let mint_amount = 1_000_000u64; + mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, mint_amount).await; + + // Call the anchor program to approve delegate + let delegate = Keypair::new(); + let approve_amount = 500_000u64; + + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(env.ata, false), + AccountMeta::new_readonly(delegate.pubkey(), false), + AccountMeta::new_readonly(env.payer.pubkey(), true), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: Approve { amount: approve_amount }.data(), + }; + + let sig = env + .rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/approve/native-program/full-example.mdx b/snippets/code-snippets/light-token/approve/native-program/full-example.mdx new file mode 100644 index 00000000..8a3e3f1e --- /dev/null +++ b/snippets/code-snippets/light-token/approve/native-program/full-example.mdx @@ -0,0 +1,138 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::ApproveCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn approve_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [token_account, delegate, owner, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let amount = u64::from_le_bytes( + data.try_into() + .map_err(|_| ProgramError::InvalidInstructionData)?, + ); + + // Approve delegate to transfer tokens on behalf of owner + ApproveCpi { + token_account: token_account.clone(), + delegate: delegate.clone(), + owner: owner.clone(), + system_program: system_program.clone(), + amount, + } + .invoke() +} + +pub fn approve_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [token_account, delegate, owner, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 9 { + return Err(ProgramError::InvalidInstructionData); + } + + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let bump = data[8]; + let signer_seeds = authority_seeds!(bump); + + ApproveCpi { + token_account: token_account.clone(), + delegate: delegate.clone(), + owner: owner.clone(), + system_program: system_program.clone(), + amount, + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_interface::state::Token; +use shared::{ + build_approve_cpi_ix, build_approve_signed_cpi_ix, create_test_rpc, + get_authority_pda, setup, setup_empty_ata, setup_pda_owned_ata, + SetupContext, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn approve_cpi() { + // Setup: create mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + ata, + .. + } = setup().await; + + let delegate = Keypair::new(); + let delegate_amount = 500_000u64; + + let ix = build_approve_cpi_ix( + ata, + delegate.pubkey(), + payer.pubkey(), + delegate_amount, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // Verify delegate is set + let account_data = rpc.get_account(ata).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + assert_eq!(token_state.delegate, Some(delegate.pubkey().into())); + assert_eq!(token_state.delegated_amount, delegate_amount); +} + +#[tokio::test(flavor = "multi_thread")] +async fn approve_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_owner, bump) = get_authority_pda(); + + let (_mint, ata) = + setup_pda_owned_ata(&mut rpc, &payer, pda_owner, 1_000_000).await; + + let delegate = Keypair::new(); + let delegate_amount = 500_000u64; + + let ix = build_approve_signed_cpi_ix( + ata, + delegate.pubkey(), + pda_owner, + delegate_amount, + bump, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // Verify delegate is set + let account_data = rpc.get_account(ata).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + assert_eq!(token_state.delegate, Some(delegate.pubkey().into())); + assert_eq!(token_state.delegated_amount, delegate_amount); +} +``` + diff --git a/snippets/code-snippets/light-token/burn-checked/rust-client/instruction.mdx b/snippets/code-snippets/light-token/burn-checked/rust-client/instruction.mdx new file mode 100644 index 00000000..9801f7e5 --- /dev/null +++ b/snippets/code-snippets/light-token/burn-checked/rust-client/instruction.mdx @@ -0,0 +1,44 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token::instruction::BurnChecked; +use rust_client::{setup, SetupContext}; +use solana_sdk::signer::Signer; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + mint, + ata, + decimals, + .. + } = setup().await; + + let burn_amount = 400_000u64; + + // BurnChecked validates that decimals match the mint + let burn_ix = BurnChecked { + source: ata, + mint, + amount: burn_amount, + decimals, + authority: payer.pubkey(), + max_top_up: None, + fee_payer: None, + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[burn_ix], &payer.pubkey(), &[&payer]) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx new file mode 100644 index 00000000..8efebd61 --- /dev/null +++ b/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx @@ -0,0 +1,87 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::BurnCpi; + +declare_id!("2TXVn8AqjfyeJvmFBD3kHJmh6fWkC4HNB5T76BmLKV5c"); + +#[program] +pub mod light_token_anchor_burn { + use super::*; + + pub fn burn(ctx: Context, amount: u64) -> Result<()> { + BurnCpi { + source: ctx.accounts.source.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + amount, + authority: ctx.accounts.authority.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + max_top_up: None, + fee_payer: None, + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct BurnAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub source: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub mint: AccountInfo<'info>, + pub authority: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_program_test::Rpc; +use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; +use light_token_anchor_burn::{instruction::Burn, ID}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signer::Signer, + system_program, +}; +use test_utils::{mint_tokens, setup_test_env}; + +#[tokio::test] +async fn test_burn() { + let mut env = setup_test_env("light_token_anchor_burn", ID).await; + + // Mint tokens first + let mint_amount = 1_000_000u64; + mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, mint_amount).await; + + // Call the anchor program to burn tokens + let burn_amount = 250_000u64; + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(env.ata, false), + AccountMeta::new(env.mint_pda, false), + AccountMeta::new_readonly(env.payer.pubkey(), true), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: Burn { amount: burn_amount }.data(), + }; + + let sig = env + .rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/burn/native-program/full-example.mdx b/snippets/code-snippets/light-token/burn/native-program/full-example.mdx new file mode 100644 index 00000000..995c0d77 --- /dev/null +++ b/snippets/code-snippets/light-token/burn/native-program/full-example.mdx @@ -0,0 +1,168 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::BurnCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn burn_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [source, mint, authority, system_program, _token_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let amount = u64::from_le_bytes( + data.try_into() + .map_err(|_| ProgramError::InvalidInstructionData)?, + ); + + // Burn tokens from source account, reducing total supply + BurnCpi { + source: source.clone(), + mint: mint.clone(), + amount, + authority: authority.clone(), + system_program: system_program.clone(), + fee_payer: None, + max_top_up: None, + } + .invoke() +} + +pub fn burn_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [source, mint, authority, system_program, _token_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 9 { + return Err(ProgramError::InvalidInstructionData); + } + + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let bump = data[8]; + let signer_seeds = authority_seeds!(bump); + + BurnCpi { + source: source.clone(), + mint: mint.clone(), + amount, + authority: authority.clone(), + system_program: system_program.clone(), + fee_payer: None, + max_top_up: None, + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_interface::state::Token; +use shared::{ + build_burn_cpi_ix, build_burn_signed_cpi_ix, create_test_rpc, + get_authority_pda, setup, setup_pda_owned_ata, SetupContext, +}; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn burn_cpi() { + // Setup: create mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup().await; + + let initial_amount = 1_000_000u64; + let burn_amount = 300_000u64; + + // Get balance before burn + let account_data = rpc.get_account(ata).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + let balance_before = token_state.amount; + assert_eq!( + balance_before, initial_amount, + "Initial balance should match" + ); + + let ix = build_burn_cpi_ix(ata, mint, payer.pubkey(), burn_amount); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // Verify balance decreased + let account_data = rpc.get_account(ata).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + let balance_after = token_state.amount; + assert_eq!( + balance_after, + initial_amount - burn_amount, + "Balance should decrease by burn amount" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn burn_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_owner, bump) = get_authority_pda(); + let initial_amount = 1_000_000u64; + let burn_amount = 300_000u64; + + let (mint, ata) = + setup_pda_owned_ata(&mut rpc, &payer, pda_owner, initial_amount).await; + + let ix = build_burn_signed_cpi_ix(ata, mint, pda_owner, burn_amount, bump); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // Verify balance decreased + let account_data = rpc.get_account(ata).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + let balance_after = token_state.amount; + assert_eq!( + balance_after, + initial_amount - burn_amount, + "Balance should decrease by burn amount" + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn burn_fails_with_insufficient_balance() { + // Setup: create mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup().await; + + let initial_amount = 1_000_000u64; + let burn_amount = initial_amount + 1; // More than balance + + let ix = build_burn_cpi_ix(ata, mint, payer.pubkey(), burn_amount); + + let result = rpc + .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await; + assert!( + result.is_err(), + "Burn with insufficient balance should fail" + ); +} +``` + diff --git a/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx b/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx new file mode 100644 index 00000000..0f1d0f23 --- /dev/null +++ b/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx @@ -0,0 +1,41 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token::instruction::Burn; +use rust_client::{setup, SetupContext}; +use solana_sdk::signer::Signer; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup().await; + + let burn_amount = 400_000u64; + + let burn_ix = Burn { + source: ata, + mint, + amount: burn_amount, + authority: payer.pubkey(), + max_top_up: None, + fee_payer: None, + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[burn_ix], &payer.pubkey(), &[&payer]) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx new file mode 100644 index 00000000..333ece13 --- /dev/null +++ b/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx @@ -0,0 +1,85 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::CloseAccountCpi; + +declare_id!("GXLCuNhnkRVp596eCdbNsZ9ua1ePbKbb344VKS7V3zQQ"); + +#[program] +pub mod light_token_anchor_close { + use super::*; + + pub fn close_account(ctx: Context) -> Result<()> { + CloseAccountCpi { + token_program: ctx.accounts.light_token_program.to_account_info(), + account: ctx.accounts.account.to_account_info(), + destination: ctx.accounts.destination.to_account_info(), + owner: ctx.accounts.owner.to_account_info(), + rent_sponsor: ctx.accounts.rent_sponsor.to_account_info(), + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct CloseAccountAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub account: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub destination: AccountInfo<'info>, + pub owner: Signer<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub rent_sponsor: AccountInfo<'info>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_program_test::Rpc; +use light_token::instruction::{rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID}; +use light_token_anchor_close::{instruction::CloseAccount, ID}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signer::Signer, +}; +use test_utils::setup_test_env; + +#[tokio::test] +async fn test_close() { + let mut env = setup_test_env("light_token_anchor_close", ID).await; + + // ATA must be empty to close (no mint_tokens call). + + // Call the anchor program to close account + let rent_sponsor = rent_sponsor_pda(); + + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(env.ata, false), + AccountMeta::new(env.payer.pubkey(), false), + AccountMeta::new_readonly(env.payer.pubkey(), true), + AccountMeta::new(rent_sponsor, false), + ], + data: CloseAccount {}.data(), + }; + + let sig = env + .rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx b/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx new file mode 100644 index 00000000..8dcc78f4 --- /dev/null +++ b/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx @@ -0,0 +1,116 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::CloseAccountCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn close_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [account, destination, owner, rent_sponsor, token_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Close token account. Must be empty (balance = 0) + CloseAccountCpi { + token_program: token_program.clone(), + account: account.clone(), + destination: destination.clone(), + owner: owner.clone(), + rent_sponsor: rent_sponsor.clone(), + } + .invoke() +} + +pub fn close_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [account, destination, owner, rent_sponsor, token_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + + let bump = data[0]; + let signer_seeds = authority_seeds!(bump); + + CloseAccountCpi { + token_program: token_program.clone(), + account: account.clone(), + destination: destination.clone(), + owner: owner.clone(), + rent_sponsor: rent_sponsor.clone(), + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use light_client::rpc::Rpc; +use light_token::instruction::rent_sponsor_pda; +use shared::{ + build_close_cpi_ix, build_close_signed_cpi_ix, create_test_rpc, + get_authority_pda, setup_empty_ata, setup_pda_owned_ata, SetupContext, +}; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn close_cpi() { + // Setup: create mint and empty ATA (must be empty to close) + let SetupContext { + mut rpc, + payer, + ata, + .. + } = setup_empty_ata().await; + + let ix = build_close_cpi_ix( + ata, + payer.pubkey(), + payer.pubkey(), + rent_sponsor_pda(), + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let account = rpc.get_account(ata).await.unwrap(); + assert!(account.is_none()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn close_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_owner, bump) = get_authority_pda(); + + let (_mint, ata) = + setup_pda_owned_ata(&mut rpc, &payer, pda_owner, 0).await; + + let ix = build_close_signed_cpi_ix( + ata, + payer.pubkey(), + pda_owner, + rent_sponsor_pda(), + bump, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let account = rpc.get_account(ata).await.unwrap(); + assert!(account.is_none()); +} +``` + diff --git a/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx b/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx new file mode 100644 index 00000000..21a034e2 --- /dev/null +++ b/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx @@ -0,0 +1,28 @@ +```rust +use light_client::rpc::Rpc; +use light_token::instruction::{CloseAccount, LIGHT_TOKEN_PROGRAM_ID}; +use rust_client::{setup_empty_ata, SetupContext}; +use solana_sdk::signer::Signer; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint and empty ATA (must be empty to close). + let SetupContext { + mut rpc, + payer, + ata, + .. + } = setup_empty_ata().await; + let close_ix = CloseAccount::new(LIGHT_TOKEN_PROGRAM_ID, ata, payer.pubkey(), payer.pubkey()) + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[close_ix], &payer.pubkey(), &[&payer]) + .await?; + + let account = rpc.get_account(ata).await?; + println!("Closed: {} Tx: {sig}", account.is_none()); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx new file mode 100644 index 00000000..84aa2351 --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx @@ -0,0 +1,212 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_compressible::CreateAccountsProof; +use light_sdk::derive_light_cpi_signer; +use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::{CpiSigner, LIGHT_TOKEN_PROGRAM_ID}; +use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR}; + +declare_id!("8yizawASBjGGCPnqtMsvBscwPUgDuNjJA93QBdm3PZY8"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("8yizawASBjGGCPnqtMsvBscwPUgDuNjJA93QBdm3PZY8"); + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateAtaParams { + pub create_accounts_proof: CreateAccountsProof, + pub ata_bump: u8, +} + +#[light_program] +#[program] +pub mod light_token_anchor_create_ata_macro { + use super::*; + + #[allow(unused_variables)] + pub fn create_ata<'info>( + ctx: Context<'_, '_, '_, 'info, CreateAta<'info>>, + params: CreateAtaParams, + ) -> Result<()> { + Ok(()) + } +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateAtaParams)] +pub struct CreateAta<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + /// CHECK: Validated by light-token CPI + pub mint: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub owner: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut)] + #[light_account(init, associated_token::authority = owner, associated_token::mint = mint, associated_token::bump = params.ata_bump)] + pub ata: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + #[account(address = COMPRESSIBLE_CONFIG_V1)] + pub light_token_compressible_config: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub light_token_rent_sponsor: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(address = LIGHT_TOKEN_PROGRAM_ID.into())] + pub light_token_program: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::indexer::{AddressWithTree, Indexer}; +use light_client::interface::{get_create_accounts_proof, InitializeRentFreeConfig}; +use light_program_test::program_test::{setup_mock_program_data, LightProgramTest}; +use light_program_test::{ProgramTestConfig, Rpc}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{ + CreateMint, CreateMintParams, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR, + derive_mint_compressed_address, derive_token_ata, find_mint_address, +}; +use light_token_anchor_create_ata_macro::{CreateAtaParams, ID}; +use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer, system_program}; + +async fn setup_create_mint( + rpc: &mut (impl Rpc + Indexer), + payer: &Keypair, + mint_authority: solana_sdk::pubkey::Pubkey, + decimals: u8, +) -> (solana_sdk::pubkey::Pubkey, Keypair) { + let mint_seed = Keypair::new(); + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); + let (mint, bump) = find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority, + proof: rpc_result.proof.0.unwrap(), + compression_address, + mint, + bump, + freeze_authority: None, + extensions: None, + rent_payment: 16, + write_top_up: 766, + }; + + let ix = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer, &mint_seed]) + .await + .unwrap(); + + (mint, mint_seed) +} + +#[tokio::test] +async fn test_create_ata() { + let config = + ProgramTestConfig::new_v2(true, Some(vec![("light_token_anchor_create_ata_macro", ID)])) + .with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &ID); + + let (init_config_ix, _) = InitializeRentFreeConfig::new( + &ID, + &payer.pubkey(), + &program_data_pda, + RENT_SPONSOR, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let (mint_pda, _) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + let (ata, ata_bump) = derive_token_ata(&payer.pubkey(), &mint_pda); + + let proof_result = get_create_accounts_proof(&rpc, &ID, vec![]).await.unwrap(); + + let accounts = light_token_anchor_create_ata_macro::accounts::CreateAta { + payer: payer.pubkey(), + mint: mint_pda, + owner: payer.pubkey(), + ata, + light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, + light_token_rent_sponsor: RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: system_program::ID, + }; + + let instruction_data = light_token_anchor_create_ata_macro::instruction::CreateAta { + params: CreateAtaParams { + create_accounts_proof: proof_result.create_accounts_proof, + ata_bump, + }, + }; + + let ix = Instruction { + program_id: ID, + accounts: [accounts.to_account_metas(None), proof_result.remaining_accounts].concat(), + data: instruction_data.data(), + }; + + let sig = rpc + .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); + + let ata_account = rpc.get_account(ata).await.unwrap().unwrap(); + + use light_token_interface::state::Token; + let token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]).unwrap(); + + assert_eq!(token.owner, payer.pubkey().to_bytes()); + assert_eq!(token.mint, mint_pda.to_bytes()); + assert_eq!(token.amount, 0); +} +``` + diff --git a/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx new file mode 100644 index 00000000..ea1e4613 --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx @@ -0,0 +1,172 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::CreateAssociatedAccountCpi; + +declare_id!("35MukgdfpNUbPMhTmEk63ECV8vjgpNVFRH9nP8ovMN58"); + +#[program] +pub mod light_token_anchor_create_ata { + use super::*; + + pub fn create_ata(ctx: Context, bump: u8, idempotent: bool) -> Result<()> { + let cpi = CreateAssociatedAccountCpi { + payer: ctx.accounts.payer.to_account_info(), + owner: ctx.accounts.owner.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + ata: ctx.accounts.associated_token_account.to_account_info(), + bump, + }; + + if idempotent { + cpi.idempotent().rent_free( + ctx.accounts.compressible_config.to_account_info(), + ctx.accounts.rent_sponsor.to_account_info(), + ctx.accounts.system_program.to_account_info(), + ) + } else { + cpi.rent_free( + ctx.accounts.compressible_config.to_account_info(), + ctx.accounts.rent_sponsor.to_account_info(), + ctx.accounts.system_program.to_account_info(), + ) + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct CreateAtaAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub owner: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub mint: AccountInfo<'info>, + #[account(mut)] + pub payer: Signer<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub associated_token_account: AccountInfo<'info>, + pub system_program: Program<'info, System>, + /// CHECK: Validated by light-token CPI + pub compressible_config: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub rent_sponsor: AccountInfo<'info>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_client::indexer::AddressWithTree; +use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; +use light_token_anchor_create_ata::{instruction::CreateAta, ID}; +use light_token::instruction::{ + CreateMint, CreateMintParams, config_pda, derive_mint_compressed_address, derive_token_ata, + find_mint_address, rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID, +}; +use anchor_lang::system_program; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signature::Keypair, + signer::Signer, +}; + +#[tokio::test] +async fn test_create_ata() { + let config = + ProgramTestConfig::new_v2(true, Some(vec![("light_token_anchor_create_ata", ID)])); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let mint_seed = Keypair::new(); + let mint_authority = payer.pubkey(); + let decimals = 9u8; + + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); + let (mint_pda, bump) = find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority, + proof: rpc_result.proof.0.unwrap(), + compression_address, + mint: mint_pda, + bump, + freeze_authority: None, + extensions: None, + rent_payment: 16, // ~24 hours rent + write_top_up: 766, // ~3 hours rent per write + }; + + let create_mint_ix = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[create_mint_ix], &payer.pubkey(), &[&payer, &mint_seed]) + .await + .unwrap(); + + // You can use light, spl, t22 mints to create a light token ATA. + // Derive ATA address and bump + let (ata, ata_bump) = derive_token_ata(&payer.pubkey(), &mint_pda); + + // Call the anchor program to create ATA + let compressible_config = config_pda(); + let rent_sponsor = rent_sponsor_pda(); + + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new_readonly(payer.pubkey(), false), + AccountMeta::new_readonly(mint_pda, false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(ata, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(compressible_config, false), + AccountMeta::new(rent_sponsor, false), + ], + data: CreateAta { + bump: ata_bump, + idempotent: false, + } + .data(), + }; + + let sig = rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/create-ata/instruction.mdx b/snippets/code-snippets/light-token/create-ata/instruction.mdx index c1219adc..55dea6d5 100644 --- a/snippets/code-snippets/light-token/create-ata/instruction.mdx +++ b/snippets/code-snippets/light-token/create-ata/instruction.mdx @@ -15,10 +15,10 @@ import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; -const rpc = createRpc(RPC_URL); +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); // localnet: -// const rpc = createRpc(); +const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( diff --git a/snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx b/snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx new file mode 100644 index 00000000..668c2657 --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx @@ -0,0 +1,172 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::CreateAssociatedAccountCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn create_ata_invoke( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [owner, mint, payer, associated_token_account, system_program, compressible_config, rent_sponsor, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + + let bump = data[0]; + let idempotent = data.get(1).copied().unwrap_or(0) != 0; + + // Create associated token account. Works with light, spl, t22 mints + let cpi = CreateAssociatedAccountCpi { + payer: payer.clone(), + owner: owner.clone(), + mint: mint.clone(), + ata: associated_token_account.clone(), + bump, + }; + + if idempotent { + cpi.idempotent().rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + ) + } else { + cpi.rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + ) + } + .invoke() +} + +pub fn create_ata_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [owner, mint, payer, associated_token_account, system_program, compressible_config, rent_sponsor, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 3 { + return Err(ProgramError::InvalidInstructionData); + } + + let bump = data[0]; + let idempotent = data[1] != 0; + let authority_bump = data[2]; + let signer_seeds = authority_seeds!(authority_bump); + + let cpi = CreateAssociatedAccountCpi { + payer: payer.clone(), + owner: owner.clone(), + mint: mint.clone(), + ata: associated_token_account.clone(), + bump, + }; + + if idempotent { + cpi.idempotent().rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + ) + } else { + cpi.rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + ) + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use light_client::rpc::Rpc; +use light_token::instruction::derive_token_ata; +use shared::{ + build_create_ata_cpi_ix, build_create_ata_signed_cpi_ix, create_test_rpc, + get_authority_pda, setup_mint_with_tokens, +}; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn create_ata_cpi() { + // Works with light, spl, or t22 mints + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (mint, _) = setup_mint_with_tokens( + &mut rpc, + &payer, + payer.pubkey(), + None, + 9, + vec![], + ) + .await; + + let owner = payer.pubkey(); + let (ata, bump) = derive_token_ata(&owner, &mint); + + let ix = build_create_ata_cpi_ix(owner, mint, payer.pubkey(), ata, bump); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let account = rpc.get_account(ata).await.unwrap(); + assert!(account.is_some()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn create_ata_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_owner, authority_bump) = get_authority_pda(); + + let (mint, _) = setup_mint_with_tokens( + &mut rpc, + &payer, + payer.pubkey(), + None, + 9, + vec![], + ) + .await; + + let (ata, ata_bump) = derive_token_ata(&pda_owner, &mint); + + let ix = build_create_ata_signed_cpi_ix( + pda_owner, + mint, + payer.pubkey(), + ata, + ata_bump, + authority_bump, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let account = rpc.get_account(ata).await.unwrap(); + assert!(account.is_some()); +} +``` + diff --git a/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx b/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx new file mode 100644 index 00000000..e7b61cfb --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx @@ -0,0 +1,33 @@ +```rust +use light_token_client::actions::{CreateAta, CreateMint}; +use rust_client::setup_rpc_and_payer; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let (mut rpc, payer) = setup_rpc_and_payer().await; + let mint_seed = Keypair::new(); + + // Create mint + let mint_result = CreateMint { + decimals: 9, + freeze_authority: None, + token_metadata: None, + } + .execute(&mut rpc, &payer, &mint_seed) + .await?; + + // Create ATA + let ata = CreateAta { + mint: mint_result.mint, + owner: payer.pubkey(), + idempotent: true, + } + .execute(&mut rpc, &payer) + .await?; + + println!("ATA: {ata}"); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/create-ata/rust-client/idempotent.mdx b/snippets/code-snippets/light-token/create-ata/rust-client/idempotent.mdx new file mode 100644 index 00000000..5f4fa5fd --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/rust-client/idempotent.mdx @@ -0,0 +1,11 @@ +```rust +use light_token_sdk::token::CreateAssociatedTokenAccount; + +let create_ata_ix = CreateAssociatedTokenAccount::new( + payer.pubkey(), + payer.pubkey(), + mint, +) +.idempotent() +.instruction()?; +``` diff --git a/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx b/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx new file mode 100644 index 00000000..097b0d33 --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx @@ -0,0 +1,31 @@ +```rust +use light_client::rpc::Rpc; +use light_token::instruction::{get_associated_token_address, CreateAssociatedTokenAccount}; +use rust_client::{setup_spl_mint_context, SplMintContext}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // You can use light, spl, t22 mints to create a light token ATA. + let SplMintContext { + mut rpc, + payer, + mint, + } = setup_spl_mint_context().await; + + let owner = Keypair::new(); + + let create_ata_ix = + CreateAssociatedTokenAccount::new(payer.pubkey(), owner.pubkey(), mint).instruction()?; + + let sig = rpc + .create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[&payer]) + .await?; + + let ata = get_associated_token_address(&owner.pubkey(), &mint); + let data = rpc.get_account(ata).await?; + println!("ATA: {ata} exists: {} Tx: {sig}", data.is_some()); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx new file mode 100644 index 00000000..c97eec30 --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx @@ -0,0 +1,185 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_compressible::CreateAccountsProof; +use light_sdk::derive_light_cpi_signer; +use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::CpiSigner; + +declare_id!("JD5N2ZwvCpdcJguF2zRHgpPdrBXf3R9kmhFC7aLALB6j"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("JD5N2ZwvCpdcJguF2zRHgpPdrBXf3R9kmhFC7aLALB6j"); + +pub const MINT_SIGNER_SEED: &[u8] = b"mint_signer"; + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateMintParams { + pub create_accounts_proof: CreateAccountsProof, + pub mint_signer_bump: u8, +} + +#[light_program] +#[program] +pub mod light_token_anchor_create_mint_macro { + use super::*; + + #[allow(unused_variables)] + pub fn create_mint<'info>( + ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>, + params: CreateMintParams, + ) -> Result<()> { + Ok(()) + } +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateMintParams)] +pub struct CreateMint<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// CHECK: Used for mint_signer PDA derivation only + pub authority: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account( + seeds = [MINT_SIGNER_SEED, authority.key().as_ref()], + bump, + )] + pub mint_signer: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut)] + #[light_account(init, + mint::signer = mint_signer, + mint::authority = fee_payer, + mint::decimals = 9, + mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()], + mint::bump = params.mint_signer_bump + )] + pub mint: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + pub compression_config: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_compressible_config: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub rent_sponsor: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_program: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_cpi_authority: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{ + get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, +}; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest}, + ProgramTestConfig, Rpc, +}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{find_mint_address, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR}; +use light_token_anchor_create_mint_macro::{CreateMintParams, MINT_SIGNER_SEED, ID}; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer}; + +#[tokio::test] +async fn test_create_mint() { + let config = ProgramTestConfig::new_v2( + true, + Some(vec![("light_token_anchor_create_mint_macro", ID)]), + ) + .with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &ID); + + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &ID, + &payer.pubkey(), + &program_data_pda, + RENT_SPONSOR, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let authority = Keypair::new(); + + let (mint_signer_pda, mint_signer_bump) = + Pubkey::find_program_address(&[MINT_SIGNER_SEED, authority.pubkey().as_ref()], &ID); + + let (mint_pda, _) = find_mint_address(&mint_signer_pda); + + let proof_result = get_create_accounts_proof( + &rpc, + &ID, + vec![CreateAccountsProofInput::mint(mint_signer_pda)], + ) + .await + .unwrap(); + + let accounts = light_token_anchor_create_mint_macro::accounts::CreateMint { + fee_payer: payer.pubkey(), + authority: authority.pubkey(), + mint_signer: mint_signer_pda, + mint: mint_pda, + compression_config: config_pda, + light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, + rent_sponsor: RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = light_token_anchor_create_mint_macro::instruction::CreateMint { + params: CreateMintParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_bump, + }, + }; + + let ix = Instruction { + program_id: ID, + accounts: [accounts.to_account_metas(None), proof_result.remaining_accounts].concat(), + data: instruction_data.data(), + }; + + let sig = rpc + .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); + + let mint_account = rpc.get_account(mint_pda).await.unwrap().unwrap(); + + use light_token_interface::state::Mint; + let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..]).unwrap(); + + assert_eq!(mint.base.decimals, 9); + assert_eq!( + mint.base.mint_authority, + Some(payer.pubkey().to_bytes().into()) + ); +} +``` + diff --git a/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx new file mode 100644 index 00000000..b4364199 --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx @@ -0,0 +1,229 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::{CreateMintCpi, CreateMintParams, SystemAccountInfos}; +use light_token::{CompressedProof, ExtensionInstructionData, TokenMetadataInstructionData}; + +declare_id!("A1rJEoepgKYWZYZ8KVFpxgeeRGwBrU7xk8S39srjVkUX"); + +/// Token metadata parameters for creating a mint with metadata. +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct TokenMetadataParams { + pub name: Vec, + pub symbol: Vec, + pub uri: Vec, + pub update_authority: Option, +} + +#[program] +pub mod light_token_anchor_create_mint { + use super::*; + + pub fn create_mint( + ctx: Context, + decimals: u8, + address_merkle_tree_root_index: u16, + compression_address: [u8; 32], + proof: CompressedProof, + freeze_authority: Option, + bump: u8, + rent_payment: Option, + write_top_up: Option, + metadata: Option, + ) -> Result<()> { + let mint = light_token::instruction::find_mint_address(ctx.accounts.mint_seed.key).0; + + let extensions = metadata.map(|m| { + vec![ExtensionInstructionData::TokenMetadata( + TokenMetadataInstructionData { + update_authority: m + .update_authority + .map(|p| p.to_bytes().into()), + name: m.name, + symbol: m.symbol, + uri: m.uri, + additional_metadata: None, + }, + )] + }); + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index, + mint_authority: *ctx.accounts.authority.key, + proof, + compression_address, + mint, + bump, + freeze_authority, + extensions, + rent_payment: rent_payment.unwrap_or(16), // Default: ~24 hours + write_top_up: write_top_up.unwrap_or(766), // Default: ~3 hours per write + }; + + let system_accounts = SystemAccountInfos { + light_system_program: ctx.accounts.light_system_program.to_account_info(), + cpi_authority_pda: ctx.accounts.cpi_authority_pda.to_account_info(), + registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(), + account_compression_authority: ctx.accounts.account_compression_authority.to_account_info(), + account_compression_program: ctx.accounts.account_compression_program.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + }; + + CreateMintCpi { + mint_seed: ctx.accounts.mint_seed.to_account_info(), + authority: ctx.accounts.authority.to_account_info(), + payer: ctx.accounts.payer.to_account_info(), + address_tree: ctx.accounts.address_tree.to_account_info(), + output_queue: ctx.accounts.output_queue.to_account_info(), + compressible_config: ctx.accounts.compressible_config.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + rent_sponsor: ctx.accounts.rent_sponsor.to_account_info(), + system_accounts, + cpi_context: None, + cpi_context_account: None, + params, + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct CreateMintAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + pub mint_seed: Signer<'info>, + /// CHECK: Validated by light-token CPI + pub authority: AccountInfo<'info>, + #[account(mut)] + pub payer: Signer<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub address_tree: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub output_queue: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub light_system_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub cpi_authority_pda: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub registered_program_pda: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub account_compression_authority: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub account_compression_program: AccountInfo<'info>, + pub system_program: Program<'info, System>, + /// CHECK: Validated by light-token CPI - use light_token::token::config_pda() + pub compressible_config: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI - derived from find_mint_address(mint_seed) + #[account(mut)] + pub mint: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI - use light_token::token::rent_sponsor_pda() + #[account(mut)] + pub rent_sponsor: AccountInfo<'info>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; +use light_token_anchor_create_mint::{instruction::CreateMint, ID}; +use light_token::instruction::{ + config_pda, derive_mint_compressed_address, find_mint_address, rent_sponsor_pda, + SystemAccounts, LIGHT_TOKEN_PROGRAM_ID, +}; +use anchor_lang::system_program; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signature::Keypair, + signer::Signer, +}; + +#[tokio::test] +async fn test_create_mint() { + let config = + ProgramTestConfig::new_v2(true, Some(vec![("light_token_anchor_create_mint", ID)])); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let mint_seed = Keypair::new(); + let mint_authority = payer.pubkey(); + let decimals = 9u8; + + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); + + let (mint_pda, bump) = find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![light_client::indexer::AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let system_accounts = SystemAccounts::default(); + + // Call the anchor program to create mint + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(mint_seed.pubkey(), true), + AccountMeta::new_readonly(mint_authority, false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(address_tree.tree, false), + AccountMeta::new(output_queue, false), + AccountMeta::new_readonly(system_accounts.light_system_program, false), + AccountMeta::new_readonly(system_accounts.cpi_authority_pda, false), + AccountMeta::new_readonly(system_accounts.registered_program_pda, false), + AccountMeta::new_readonly(system_accounts.account_compression_authority, false), + AccountMeta::new_readonly(system_accounts.account_compression_program, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(config_pda(), false), + AccountMeta::new(mint_pda, false), + AccountMeta::new(rent_sponsor_pda(), false), + ], + data: CreateMint { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + compression_address: compression_address.into(), + proof: rpc_result.proof.0.unwrap(), + freeze_authority: None, + bump, + rent_payment: Some(16), // ~24 hours rent + write_top_up: Some(766), // ~3 hours rent per write + metadata: None, + } + .data(), + }; + + let sig = rpc + .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer, &mint_seed]) + .await + .unwrap(); + + let compressed_account = rpc + .get_compressed_account(compression_address, None) + .await + .unwrap() + .value; + + assert!(compressed_account.is_some(), "Light-mint should exist"); + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/create-mint/instruction.mdx b/snippets/code-snippets/light-token/create-mint/instruction.mdx index 753db086..000a6cfe 100644 --- a/snippets/code-snippets/light-token/create-mint/instruction.mdx +++ b/snippets/code-snippets/light-token/create-mint/instruction.mdx @@ -30,10 +30,10 @@ function findMintAddress(mintSigner: PublicKey): [PublicKey, number] { } // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; -const rpc = createRpc(RPC_URL); +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); // localnet: -// const rpc = createRpc(); +const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( diff --git a/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx b/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx new file mode 100644 index 00000000..50514950 --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx @@ -0,0 +1,513 @@ + +```rust instruction.rs +use super::authority_seeds; +use borsh::BorshDeserialize; +use light_compressible::CreateAccountsProof; +use light_token::instruction::{ + CreateMintCpi, CreateMintParams, SystemAccountInfos, +}; +use light_token_interface::instructions::extensions::{ + token_metadata::TokenMetadataInstructionData, ExtensionInstructionData, +}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, +}; + +#[derive(BorshDeserialize)] +struct CreateMintData { + decimals: u8, + mint_authority: Pubkey, + create_accounts_proof: CreateAccountsProof, + compression_address: [u8; 32], + mint: Pubkey, + bump: u8, + freeze_authority: Option, + rent_payment: u8, + write_top_up: u32, + metadata_name: Option>, + metadata_symbol: Option>, + metadata_uri: Option>, +} + +pub fn create_mint_invoke( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [ + mint_seed, + authority, + payer, + address_tree, + output_queue, + compressible_config, + mint, + rent_sponsor, + light_system_program, + cpi_authority_pda, + registered_program_pda, + account_compression_authority, + account_compression_program, + system_program, + _token_program, + ] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let ix_data = CreateMintData::deserialize(&mut &data[..]) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + // Build token metadata extension if metadata fields are provided + let extensions = match ( + &ix_data.metadata_name, + &ix_data.metadata_symbol, + &ix_data.metadata_uri, + ) { + (Some(name), Some(symbol), Some(uri)) => { + Some(vec![ExtensionInstructionData::TokenMetadata( + TokenMetadataInstructionData { + update_authority: Some( + ix_data.mint_authority.to_bytes().into(), + ), + name: name.clone(), + symbol: symbol.clone(), + uri: uri.clone(), + additional_metadata: None, + }, + )]) + } + _ => None, + }; + + // Create mint. rent_payment: ~24h rent/unit, write_top_up: ~3h rent/write + let params = CreateMintParams { + decimals: ix_data.decimals, + address_merkle_tree_root_index: ix_data + .create_accounts_proof + .address_tree_info + .root_index, + mint_authority: ix_data.mint_authority, + proof: ix_data.create_accounts_proof.proof.0.unwrap_or_default(), + compression_address: ix_data.compression_address, + mint: ix_data.mint, + bump: ix_data.bump, + freeze_authority: ix_data.freeze_authority, + extensions, + rent_payment: ix_data.rent_payment, + write_top_up: ix_data.write_top_up, + }; + + let system_accounts = SystemAccountInfos { + light_system_program: light_system_program.clone(), + cpi_authority_pda: cpi_authority_pda.clone(), + registered_program_pda: registered_program_pda.clone(), + account_compression_authority: account_compression_authority.clone(), + account_compression_program: account_compression_program.clone(), + system_program: system_program.clone(), + }; + + CreateMintCpi::new( + mint_seed.clone(), + authority.clone(), + payer.clone(), + address_tree.clone(), + output_queue.clone(), + compressible_config.clone(), + mint.clone(), + rent_sponsor.clone(), + system_accounts, + params, + ) + .invoke() +} + +#[derive(BorshDeserialize)] +struct CreateMintSignedData { + decimals: u8, + create_accounts_proof: CreateAccountsProof, + compression_address: [u8; 32], + mint: Pubkey, + bump: u8, + freeze_authority: Option, + rent_payment: u8, + write_top_up: u32, + authority_bump: u8, + metadata_name: Option>, + metadata_symbol: Option>, + metadata_uri: Option>, +} + +pub fn create_mint_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [ + mint_seed, + authority, + payer, + address_tree, + output_queue, + compressible_config, + mint, + rent_sponsor, + light_system_program, + cpi_authority_pda, + registered_program_pda, + account_compression_authority, + account_compression_program, + system_program, + _token_program, + ] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let ix_data = CreateMintSignedData::deserialize(&mut &data[..]) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + let signer_seeds = authority_seeds!(ix_data.authority_bump); + + // Build token metadata extension if metadata fields are provided + let extensions = match ( + &ix_data.metadata_name, + &ix_data.metadata_symbol, + &ix_data.metadata_uri, + ) { + (Some(name), Some(symbol), Some(uri)) => { + Some(vec![ExtensionInstructionData::TokenMetadata( + TokenMetadataInstructionData { + update_authority: Some(authority.key.to_bytes().into()), + name: name.clone(), + symbol: symbol.clone(), + uri: uri.clone(), + additional_metadata: None, + }, + )]) + } + _ => None, + }; + + let params = CreateMintParams { + decimals: ix_data.decimals, + address_merkle_tree_root_index: ix_data + .create_accounts_proof + .address_tree_info + .root_index, + mint_authority: *authority.key, + proof: ix_data.create_accounts_proof.proof.0.unwrap_or_default(), + compression_address: ix_data.compression_address, + mint: ix_data.mint, + bump: ix_data.bump, + freeze_authority: ix_data.freeze_authority, + extensions, + rent_payment: ix_data.rent_payment, + write_top_up: ix_data.write_top_up, + }; + + let system_accounts = SystemAccountInfos { + light_system_program: light_system_program.clone(), + cpi_authority_pda: cpi_authority_pda.clone(), + registered_program_pda: registered_program_pda.clone(), + account_compression_authority: account_compression_authority.clone(), + account_compression_program: account_compression_program.clone(), + system_program: system_program.clone(), + }; + + CreateMintCpi::new( + mint_seed.clone(), + authority.clone(), + payer.clone(), + address_tree.clone(), + output_queue.clone(), + compressible_config.clone(), + mint.clone(), + rent_sponsor.clone(), + system_accounts, + params, + ) + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use borsh::BorshSerialize; +use light_client::{ + indexer::{AddressWithTree, Indexer}, + rpc::Rpc, +}; +use light_compressible::CreateAccountsProof; +use light_compressed_account::instruction_data::{ + compressed_proof::ValidityProof, data::PackedAddressTreeInfo, +}; +use light_token::instruction::{ + config_pda, derive_mint_compressed_address, find_mint_address, + rent_sponsor_pda, SystemAccounts, LIGHT_TOKEN_PROGRAM_ID, +}; +use shared::{create_test_rpc, get_authority_pda, PROGRAM_ID}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, +}; + +#[tokio::test(flavor = "multi_thread")] +async fn create_mint_cpi() { + let mut rpc = create_test_rpc().await; + + let payer = rpc.get_payer().insecure_clone(); + let mint_seed = Keypair::new(); + let decimals = 9u8; + + // Get address tree to store compressed address for when mint turns inactive + // We must create a compressed address at creation to ensure the mint does not exist yet + let address_tree = rpc.get_address_tree_v2(); + // Get state tree to store mint when inactive + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + // Derive mint addresses + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); + let (mint, bump) = find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + // Build instruction data + #[derive(BorshSerialize)] + struct CreateMintDataTest { + decimals: u8, + mint_authority: Pubkey, + create_accounts_proof: CreateAccountsProof, + compression_address: [u8; 32], + mint: Pubkey, + bump: u8, + freeze_authority: Option, + rent_payment: u8, + write_top_up: u32, + metadata_name: Option>, + metadata_symbol: Option>, + metadata_uri: Option>, + } + + let create_accounts_proof = CreateAccountsProof { + proof: ValidityProof(rpc_result.proof.0), + address_tree_info: PackedAddressTreeInfo { + address_merkle_tree_pubkey_index: 0, + address_queue_pubkey_index: 0, + root_index: rpc_result.addresses[0].root_index, + }, + output_state_tree_index: 0, + state_tree_index: None, + }; + + let test_data = CreateMintDataTest { + decimals, + mint_authority: payer.pubkey(), + create_accounts_proof, + compression_address, + mint, + bump, + freeze_authority: None, + rent_payment: 16, + write_top_up: 766, + metadata_name: Some(b"Example Token".to_vec()), + metadata_symbol: Some(b"EXT".to_vec()), + metadata_uri: Some(b"https://example.com/metadata.json".to_vec()), + }; + + let mut data = vec![0u8]; + data.extend(test_data.try_to_vec().unwrap()); + + let system_accounts = SystemAccounts::default(); + + // Build and send instruction (mint_seed must sign) + let ix = Instruction { + program_id: PROGRAM_ID, + accounts: vec![ + AccountMeta::new(mint_seed.pubkey(), true), + AccountMeta::new_readonly(payer.pubkey(), true), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(address_tree.tree, false), + AccountMeta::new(output_queue, false), + AccountMeta::new_readonly(config_pda(), false), + AccountMeta::new(mint, false), + AccountMeta::new(rent_sponsor_pda(), false), + AccountMeta::new_readonly( + system_accounts.light_system_program, + false, + ), + AccountMeta::new_readonly(system_accounts.cpi_authority_pda, false), + AccountMeta::new_readonly( + system_accounts.registered_program_pda, + false, + ), + AccountMeta::new_readonly( + system_accounts.account_compression_authority, + false, + ), + AccountMeta::new_readonly( + system_accounts.account_compression_program, + false, + ), + AccountMeta::new_readonly(system_accounts.system_program, false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + ], + data, + }; + + rpc.create_and_send_transaction( + &[ix], + &payer.pubkey(), + &[&payer, &mint_seed], + ) + .await + .unwrap(); + + let mint_account = rpc.get_account(mint).await.unwrap(); + assert!(mint_account.is_some()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn create_mint_signed_cpi() { + let mut rpc = create_test_rpc().await; + + let payer = rpc.get_payer().insecure_clone(); + let mint_seed = Keypair::new(); + let decimals = 9u8; + + let (pda_authority, authority_bump) = get_authority_pda(); + + // Get address tree to store compressed address for when mint turns inactive + // We must create a compressed address at creation to ensure the mint does not exist yet + let address_tree = rpc.get_address_tree_v2(); + // Get state tree to store mint when inactive + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + // Derive mint addresses + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); + let (mint, bump) = find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + // Build instruction data + #[derive(BorshSerialize)] + struct CreateMintSignedDataTest { + decimals: u8, + create_accounts_proof: CreateAccountsProof, + compression_address: [u8; 32], + mint: Pubkey, + bump: u8, + freeze_authority: Option, + rent_payment: u8, + write_top_up: u32, + authority_bump: u8, + metadata_name: Option>, + metadata_symbol: Option>, + metadata_uri: Option>, + } + + let create_accounts_proof = CreateAccountsProof { + proof: ValidityProof(rpc_result.proof.0), + address_tree_info: PackedAddressTreeInfo { + address_merkle_tree_pubkey_index: 0, + address_queue_pubkey_index: 0, + root_index: rpc_result.addresses[0].root_index, + }, + output_state_tree_index: 0, + state_tree_index: None, + }; + + let test_data = CreateMintSignedDataTest { + decimals, + create_accounts_proof, + compression_address, + mint, + bump, + freeze_authority: None, + rent_payment: 16, + write_top_up: 766, + authority_bump, + metadata_name: Some(b"Example Token".to_vec()), + metadata_symbol: Some(b"EXT".to_vec()), + metadata_uri: Some(b"https://example.com/metadata.json".to_vec()), + }; + + let mut data = vec![19u8]; + data.extend(test_data.try_to_vec().unwrap()); + + let system_accounts = SystemAccounts::default(); + + // Build and send instruction (mint_seed must sign) + let ix = Instruction { + program_id: PROGRAM_ID, + accounts: vec![ + AccountMeta::new(mint_seed.pubkey(), true), + AccountMeta::new(pda_authority, false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(address_tree.tree, false), + AccountMeta::new(output_queue, false), + AccountMeta::new_readonly(config_pda(), false), + AccountMeta::new(mint, false), + AccountMeta::new(rent_sponsor_pda(), false), + AccountMeta::new_readonly( + system_accounts.light_system_program, + false, + ), + AccountMeta::new_readonly(system_accounts.cpi_authority_pda, false), + AccountMeta::new_readonly( + system_accounts.registered_program_pda, + false, + ), + AccountMeta::new_readonly( + system_accounts.account_compression_authority, + false, + ), + AccountMeta::new_readonly( + system_accounts.account_compression_program, + false, + ), + AccountMeta::new_readonly(system_accounts.system_program, false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + ], + data, + }; + + rpc.create_and_send_transaction( + &[ix], + &payer.pubkey(), + &[&payer, &mint_seed], + ) + .await + .unwrap(); + + let mint_account = rpc.get_account(mint).await.unwrap(); + assert!(mint_account.is_some()); +} +``` + diff --git a/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx b/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx new file mode 100644 index 00000000..ae48bd3f --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx @@ -0,0 +1,36 @@ +```rust +use light_client::rpc::Rpc; +use light_token_client::actions::{CreateMint, TokenMetadata}; +use rust_client::setup_rpc_and_payer; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let (mut rpc, payer) = setup_rpc_and_payer().await; + let mint_seed = Keypair::new(); + + let result = CreateMint { + decimals: 9, + freeze_authority: None, + token_metadata: Some(TokenMetadata { + name: "Example Token".to_string(), + symbol: "EXT".to_string(), + uri: "https://example.com/metadata.json".to_string(), + update_authority: Some(payer.pubkey()), + additional_metadata: Some(vec![("type".to_string(), "example".to_string())]), + }), + } + .execute(&mut rpc, &payer, &mint_seed) + .await?; + + let data = rpc.get_account(result.mint).await?; + println!( + "Mint: {} exists: {} Tx: {}", + result.mint, + data.is_some(), + result.signature + ); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx b/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx new file mode 100644 index 00000000..e149c64e --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx @@ -0,0 +1,96 @@ +```rust +use light_client::{ + indexer::{AddressWithTree, Indexer}, + rpc::Rpc, +}; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token::instruction::{ + derive_mint_compressed_address, find_mint_address, CreateMint, CreateMintParams, +}; +use light_token_interface::{ + instructions::extensions::{ + token_metadata::TokenMetadataInstructionData, ExtensionInstructionData, + }, + state::AdditionalMetadata, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(false, None)).await?; + + let payer = rpc.get_payer().insecure_clone(); + let mint_seed = Keypair::new(); + let decimals = 9u8; + + // Get address tree to store compressed address for when mint turns inactive + // We must create a compressed address at creation to ensure the mint does not exist yet + let address_tree = rpc.get_address_tree_v2(); + // Get state tree to store mint when inactive + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + // Derive mint addresses + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); + let mint = find_mint_address(&mint_seed.pubkey()).0; // on-chain Mint PDA + + // Fetch validity proof to proof address does not exist yet + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + // Build CreateMintParams with token metadata extension + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, // stores mint compressed address + mint_authority: payer.pubkey(), + proof: rpc_result.proof.0.unwrap(), + compression_address, // address for compression when mint turns inactive + mint, + bump: find_mint_address(&mint_seed.pubkey()).1, + freeze_authority: None, + extensions: Some(vec![ExtensionInstructionData::TokenMetadata( + TokenMetadataInstructionData { + update_authority: Some(payer.pubkey().to_bytes().into()), + name: b"Example Token".to_vec(), + symbol: b"EXT".to_vec(), + uri: b"https://example.com/metadata.json".to_vec(), + additional_metadata: Some(vec![AdditionalMetadata { + key: b"type".to_vec(), + value: b"example".to_vec(), + }]), + }, + )]), + rent_payment: 16, // ~24 hours rent + write_top_up: 766, // ~3 hours rent per write + }; + + // Build and send instruction (mint_seed must sign) + let create_mint_ix = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ) + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[create_mint_ix], &payer.pubkey(), &[&payer, &mint_seed]) + .await?; + + let data = rpc.get_account(mint).await?; + println!("Mint: {} exists: {} Tx: {sig}", mint, data.is_some()); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx new file mode 100644 index 00000000..b895364c --- /dev/null +++ b/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx @@ -0,0 +1,240 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_compressible::CreateAccountsProof; +use light_sdk::derive_light_cpi_signer; +use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::CpiSigner; +use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR}; + +declare_id!("FCczCsMBwaqUSqunWPGQjNP55EeqYrkSwYdLmztgmvKn"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("FCczCsMBwaqUSqunWPGQjNP55EeqYrkSwYdLmztgmvKn"); + +pub const TOKEN_AUTH_SEED: &[u8] = b"token_auth"; +pub const TOKEN_ACCOUNT_SEED: &[u8] = b"token_account"; + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateTokenAccountParams { + pub create_accounts_proof: CreateAccountsProof, + pub account_bump: u8, +} + +#[light_program] +#[program] +pub mod light_token_anchor_create_token_account_macro { + use super::*; + + #[allow(unused_variables)] + pub fn create_token_account<'info>( + ctx: Context<'_, '_, '_, 'info, CreateTokenAccountAccounts<'info>>, + params: CreateTokenAccountParams, + ) -> Result<()> { + Ok(()) + } +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateTokenAccountParams)] +pub struct CreateTokenAccountAccounts<'info> { + #[account(mut)] + pub payer: Signer<'info>, + + /// CHECK: Validated by light-token CPI + pub mint: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account( + seeds = [TOKEN_AUTH_SEED], + bump, + )] + pub token_authority: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + #[account( + mut, + seeds = [TOKEN_ACCOUNT_SEED, mint.key().as_ref()], + bump, + )] + #[light_account(init, token, + token::authority = [TOKEN_ACCOUNT_SEED, self.mint.key()], + token::mint = mint, + token::owner = token_authority, + token::bump = params.account_bump + )] + pub account: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + #[account(address = COMPRESSIBLE_CONFIG_V1)] + pub light_token_compressible_config: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub light_token_rent_sponsor: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_cpi_authority: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_program: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::indexer::AddressWithTree; +use light_client::interface::{get_create_accounts_proof, InitializeRentFreeConfig}; +use light_program_test::program_test::{setup_mock_program_data, LightProgramTest}; +use light_program_test::{Indexer, ProgramTestConfig, Rpc}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{ + CreateMint, CreateMintParams, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR, + derive_mint_compressed_address, find_mint_address, +}; +use light_token_anchor_create_token_account_macro::{ + CreateTokenAccountParams, TOKEN_AUTH_SEED, TOKEN_ACCOUNT_SEED, ID, +}; +use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer}; + +async fn setup_create_mint( + rpc: &mut (impl Rpc + Indexer), + payer: &Keypair, + mint_authority: Pubkey, + decimals: u8, +) -> (Pubkey, Keypair) { + let mint_seed = Keypair::new(); + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); + let (mint, bump) = find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority, + proof: rpc_result.proof.0.unwrap(), + compression_address, + mint, + bump, + freeze_authority: None, + extensions: None, + rent_payment: 16, + write_top_up: 766, + }; + + let ix = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer, &mint_seed]) + .await + .unwrap(); + + (mint, mint_seed) +} + +#[tokio::test] +async fn test_create_token_account() { + let config = ProgramTestConfig::new_v2( + true, + Some(vec![("light_token_anchor_create_token_account_macro", ID)]), + ) + .with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &ID); + + let (init_config_ix, _) = InitializeRentFreeConfig::new( + &ID, + &payer.pubkey(), + &program_data_pda, + RENT_SPONSOR, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let (mint_pda, _) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + + let (token_authority, _) = Pubkey::find_program_address(&[TOKEN_AUTH_SEED], &ID); + let (account_pda, account_bump) = + Pubkey::find_program_address(&[TOKEN_ACCOUNT_SEED, mint_pda.as_ref()], &ID); + + let proof_result = get_create_accounts_proof(&rpc, &ID, vec![]).await.unwrap(); + + let accounts = light_token_anchor_create_token_account_macro::accounts::CreateTokenAccountAccounts { + payer: payer.pubkey(), + mint: mint_pda, + token_authority, + account: account_pda, + light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, + light_token_rent_sponsor: RENT_SPONSOR, + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = + light_token_anchor_create_token_account_macro::instruction::CreateTokenAccount { + params: CreateTokenAccountParams { + create_accounts_proof: proof_result.create_accounts_proof, + account_bump, + }, + }; + + let ix = Instruction { + program_id: ID, + accounts: [accounts.to_account_metas(None), proof_result.remaining_accounts].concat(), + data: instruction_data.data(), + }; + + let sig = rpc + .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); + + let token_account = rpc.get_account(account_pda).await.unwrap().unwrap(); + + use light_token_interface::state::Token; + let token: Token = + borsh::BorshDeserialize::deserialize(&mut &token_account.data[..]).unwrap(); + + assert_eq!(token.owner, token_authority.to_bytes()); + assert_eq!(token.mint, mint_pda.to_bytes()); + assert_eq!(token.amount, 0); +} +``` + diff --git a/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx new file mode 100644 index 00000000..b1d74ffc --- /dev/null +++ b/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx @@ -0,0 +1,160 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::CreateTokenAccountCpi; + +declare_id!("zXK1CnWj4WFfFHCArxxr4sh3Qqx2p3oui8ahqpjArgS"); + +#[program] +pub mod light_token_anchor_create_token_account { + use super::*; + + pub fn create_token_account(ctx: Context, owner: Pubkey) -> Result<()> { + CreateTokenAccountCpi { + payer: ctx.accounts.payer.to_account_info(), + account: ctx.accounts.account.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + owner, + } + .rent_free( + ctx.accounts.compressible_config.to_account_info(), + ctx.accounts.rent_sponsor.to_account_info(), + ctx.accounts.system_program.to_account_info(), + &ctx.accounts.light_token_program.key(), + ) + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct CreateTokenAccountAccounts<'info> { + #[account(mut)] + pub payer: Signer<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub account: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub mint: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub compressible_config: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub rent_sponsor: AccountInfo<'info>, + pub system_program: Program<'info, System>, + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_client::indexer::AddressWithTree; +use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; +use light_token_anchor_create_token_account::{instruction::CreateTokenAccount, ID}; +use light_token::instruction::{ + CreateMint, CreateMintParams, config_pda, derive_mint_compressed_address, + find_mint_address, rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID, +}; +use anchor_lang::system_program; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, +}; + +#[tokio::test] +async fn test_create_token_account() { + let config = ProgramTestConfig::new_v2( + true, + Some(vec![("light_token_anchor_create_token_account", ID)]), + ); + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + // Create a mint first + let mint_seed = Keypair::new(); + let mint_authority = payer.pubkey(); + let decimals = 9u8; + + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); + let (mint_pda, bump) = find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority, + proof: rpc_result.proof.0.unwrap(), + compression_address, + mint: mint_pda, + bump, + freeze_authority: None, + extensions: None, + rent_payment: 16, // ~24 hours rent + write_top_up: 766, // ~3 hours rent per write + }; + + let create_mint_ix = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[create_mint_ix], &payer.pubkey(), &[&payer, &mint_seed]) + .await + .unwrap(); + + // You can use light, spl, t22 mints to create a light token account. + // Create a token account + let token_account = Keypair::new(); + let owner = payer.pubkey(); + let compressible_config = config_pda(); + let rent_sponsor = rent_sponsor_pda(); + + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(token_account.pubkey(), true), + AccountMeta::new_readonly(mint_pda, false), + AccountMeta::new_readonly(compressible_config, false), + AccountMeta::new(rent_sponsor, false), + AccountMeta::new_readonly(system_program::ID, false), + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + ], + data: CreateTokenAccount { owner }.data(), + }; + + let sig = rpc + .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer, &token_account]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx new file mode 100644 index 00000000..fabd355f --- /dev/null +++ b/snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx @@ -0,0 +1,170 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::CreateTokenAccountCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, +}; + +/// Account order: +/// - accounts[0]: payer (signer, mut) +/// - accounts[1]: account (signer for invoke, PDA for invoke_signed, mut) +/// - accounts[2]: mint (readonly) +/// - accounts[3]: compressible_config (readonly) +/// - accounts[4]: system_program (readonly) +/// - accounts[5]: rent_sponsor (mut) +/// - accounts[6]: light_token_program (readonly) +pub fn create_token_account_invoke( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [payer, account, mint, compressible_config, system_program, rent_sponsor, token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 32 { + return Err(ProgramError::InvalidInstructionData); + } + + let owner = Pubkey::try_from(&data[0..32]) + .map_err(|_| ProgramError::InvalidInstructionData)?; + + // Create token account. Works with light, spl, t22 mints + CreateTokenAccountCpi { + payer: payer.clone(), + account: account.clone(), + mint: mint.clone(), + owner, + } + .rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + token_program.key, + ) + .invoke() +} + +pub fn create_token_account_invoke_signed( + program_id: &Pubkey, + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [payer, account, mint, compressible_config, system_program, rent_sponsor, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 33 { + return Err(ProgramError::InvalidInstructionData); + } + + let owner = Pubkey::try_from(&data[0..32]) + .map_err(|_| ProgramError::InvalidInstructionData)?; + let authority_bump = data[32]; + let signer_seeds = authority_seeds!(authority_bump); + + CreateTokenAccountCpi { + payer: payer.clone(), + account: account.clone(), + mint: mint.clone(), + owner, + } + .rent_free( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + program_id, + ) + .invoke_signed(signer_seeds) +} +``` + +```rust test.rs +mod shared; + +use light_client::rpc::Rpc; +use shared::{ + build_create_token_account_cpi_ix, + build_create_token_account_signed_cpi_ix, create_test_rpc, + get_authority_pda, setup_mint_with_tokens, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn create_token_account_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (mint, _) = setup_mint_with_tokens( + &mut rpc, + &payer, + payer.pubkey(), + None, + 9, + vec![], + ) + .await; + + let token_account = Keypair::new(); + let owner = payer.pubkey(); + + let ix = build_create_token_account_cpi_ix( + payer.pubkey(), + token_account.pubkey(), + mint, + owner, + ); + + rpc.create_and_send_transaction( + &[ix], + &payer.pubkey(), + &[&payer, &token_account], + ) + .await + .unwrap(); + + let account = rpc.get_account(token_account.pubkey()).await.unwrap(); + assert!(account.is_some()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn create_token_account_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_account, authority_bump) = get_authority_pda(); + + let (mint, _) = setup_mint_with_tokens( + &mut rpc, + &payer, + payer.pubkey(), + None, + 9, + vec![], + ) + .await; + + let owner = payer.pubkey(); + + let ix = build_create_token_account_signed_cpi_ix( + payer.pubkey(), + pda_account, + mint, + owner, + authority_bump, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let account = rpc.get_account(pda_account).await.unwrap(); + assert!(account.is_some()); +} +``` + diff --git a/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx b/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx new file mode 100644 index 00000000..db612b8c --- /dev/null +++ b/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx @@ -0,0 +1,36 @@ +```rust +use light_client::rpc::Rpc; +use light_token::instruction::CreateTokenAccount; +use rust_client::{setup_spl_mint_context, SplMintContext}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint + // You can use light, spl, t22 mints to create a light token account. + let SplMintContext { + mut rpc, + payer, + mint, + } = setup_spl_mint_context().await; + + let account = Keypair::new(); + + let create_account_ix = + CreateTokenAccount::new(payer.pubkey(), account.pubkey(), mint, payer.pubkey()) + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[create_account_ix], &payer.pubkey(), &[&payer, &account]) + .await?; + + let data = rpc.get_account(account.pubkey()).await?; + println!( + "Account: {} exists: {} Tx: {sig}", + account.pubkey(), + data.is_some() + ); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-full.mdx b/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-full.mdx new file mode 100644 index 00000000..40ede2f4 --- /dev/null +++ b/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-full.mdx @@ -0,0 +1,32 @@ +```rust +mod shared; + +use light_client::rpc::Rpc; +use light_token_sdk::token::Freeze; +use shared::SetupContext; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn test_freeze() { + // Setup creates mint, ATA with tokens, and approves delegate + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = shared::setup().await; + + let freeze_ix = Freeze { + token_account: ata, + mint, + freeze_authority: payer.pubkey(), + } + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[freeze_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` diff --git a/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx b/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx new file mode 100644 index 00000000..e7d719fe --- /dev/null +++ b/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx @@ -0,0 +1,37 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token::instruction::Freeze; +use rust_client::{setup, SetupContext}; +use solana_sdk::signer::Signer; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint, ATA with tokens, and approves delegate + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup().await; + + // freeze_authority must match what was set during mint creation. + let freeze_ix = Freeze { + token_account: ata, + mint, + freeze_authority: payer.pubkey(), + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[freeze_ix], &payer.pubkey(), &[&payer]) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("State: {:?} Tx: {sig}", token.state); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-full.mdx b/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-full.mdx new file mode 100644 index 00000000..60864340 --- /dev/null +++ b/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-full.mdx @@ -0,0 +1,32 @@ +```rust +mod shared; + +use light_client::rpc::Rpc; +use light_token_sdk::token::Thaw; +use shared::SetupContext; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn thaw() { + // Setup creates mint, ATA with tokens, and freezes account + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = shared::setup_frozen().await; + + let thaw_ix = Thaw { + token_account: ata, + mint, + freeze_authority: payer.pubkey(), + } + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[thaw_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` diff --git a/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx b/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx new file mode 100644 index 00000000..3e31699a --- /dev/null +++ b/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx @@ -0,0 +1,36 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token::instruction::Thaw; +use rust_client::{setup_frozen, SetupContext}; +use solana_sdk::signer::Signer; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint, ATA with tokens, and freezes account + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup_frozen().await; + + let thaw_ix = Thaw { + token_account: ata, + mint, + freeze_authority: payer.pubkey(), + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[thaw_ix], &payer.pubkey(), &[&payer]) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("State: {:?} Tx: {sig}", token.state); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx new file mode 100644 index 00000000..ab67fd63 --- /dev/null +++ b/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx @@ -0,0 +1,77 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::FreezeCpi; + +declare_id!("JBMzMJX4sqCQfNVbosP2oqP1KZ5ZDWiwYTrupk687qXZ"); + +#[program] +pub mod light_token_anchor_freeze { + use super::*; + + pub fn freeze(ctx: Context) -> Result<()> { + FreezeCpi { + token_account: ctx.accounts.token_account.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + freeze_authority: ctx.accounts.freeze_authority.to_account_info(), + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct FreezeAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub token_account: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub mint: AccountInfo<'info>, + pub freeze_authority: Signer<'info>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_program_test::Rpc; +use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; +use light_token_anchor_freeze::{instruction::Freeze, ID}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signer::Signer, +}; +use test_utils::{mint_tokens, setup_test_env_with_freeze}; + +#[tokio::test] +async fn test_freeze() { + let mut env = setup_test_env_with_freeze("light_token_anchor_freeze", ID).await; + + // Mint tokens first + mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, 1_000_000).await; + + // Call the anchor program to freeze account + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(env.ata, false), + AccountMeta::new_readonly(env.mint_pda, false), + AccountMeta::new_readonly(env.freeze_authority, true), + ], + data: Freeze {}.data(), + }; + + let sig = env + .rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx b/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx new file mode 100644 index 00000000..50b5c377 --- /dev/null +++ b/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx @@ -0,0 +1,118 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::FreezeCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn freeze_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Freeze token account. freeze_authority must match mint creation + FreezeCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), + } + .invoke() +} + +pub fn freeze_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + + let bump = data[0]; + let signer_seeds = authority_seeds!(bump); + + FreezeCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_interface::state::Token; +use shared::{ + build_freeze_cpi_ix, build_freeze_signed_cpi_ix, create_test_rpc, + get_authority_pda, setup, setup_mint_with_tokens, SetupContext, +}; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn freeze_cpi() { + // Setup: create mint and ATA with freeze authority + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup().await; + + let ix = build_freeze_cpi_ix(ata, mint, payer.pubkey()); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // Verify account is frozen + let account_data = rpc.get_account(ata).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + assert!(token_state.is_frozen(), "Account should be frozen"); +} + +#[tokio::test(flavor = "multi_thread")] +async fn freeze_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_authority, bump) = get_authority_pda(); + let initial_amount = 1_000_000u64; + + // Create mint with PDA as freeze authority and mint tokens to payer + let (mint, atas) = setup_mint_with_tokens( + &mut rpc, + &payer, + payer.pubkey(), + Some(pda_authority), + 9, + vec![(initial_amount, payer.pubkey())], + ) + .await; + + let ata = atas[0]; + + let ix = build_freeze_signed_cpi_ix(ata, mint, pda_authority, bump); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // Verify account is frozen + let account_data = rpc.get_account(ata).await.unwrap().unwrap(); + let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); + assert!(token_state.is_frozen(), "Account should be frozen"); +} +``` + diff --git a/snippets/code-snippets/light-token/load-ata/instruction.mdx b/snippets/code-snippets/light-token/load-ata/instruction.mdx index 8a8b05fc..f79158d5 100644 --- a/snippets/code-snippets/light-token/load-ata/instruction.mdx +++ b/snippets/code-snippets/light-token/load-ata/instruction.mdx @@ -17,7 +17,7 @@ import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; const rpc = createRpc(RPC_URL); // localnet: // const rpc = createRpc(); diff --git a/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx new file mode 100644 index 00000000..8c824529 --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx @@ -0,0 +1,140 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::MintToCheckedCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn mint_to_checked_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [mint, destination, authority, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 9 { + return Err(ProgramError::InvalidInstructionData); + } + + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + + // MintToChecked validates decimals match the mint + MintToCheckedCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, + decimals, + authority: authority.clone(), + system_program: system_program.clone(), + max_top_up: None, + fee_payer: None, + } + .invoke() +} + +pub fn mint_to_checked_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [mint, destination, authority, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 10 { + return Err(ProgramError::InvalidInstructionData); + } + + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + let bump = data[9]; + let signer_seeds = authority_seeds!(bump); + + MintToCheckedCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, + decimals, + authority: authority.clone(), + system_program: system_program.clone(), + max_top_up: None, + fee_payer: None, + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use light_client::rpc::Rpc; +use shared::{ + build_mint_to_checked_cpi_ix, build_mint_to_checked_signed_cpi_ix, + create_test_rpc, get_authority_pda, setup_mint_with_pda_authority, + setup_mint_with_tokens, create_ata, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn mint_to_checked_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (mint, atas) = setup_mint_with_tokens( + &mut rpc, + &payer, + payer.pubkey(), + None, + 9, + vec![(0, payer.pubkey())], + ) + .await; + + let mint_amount = 1_000_000u64; + + let ix = build_mint_to_checked_cpi_ix( + mint, + atas[0], + payer.pubkey(), + mint_amount, + 9, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn mint_to_checked_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_authority, bump) = get_authority_pda(); + + let mint = setup_mint_with_pda_authority(&mut rpc, &payer, pda_authority, 9).await; + + let recipient = Keypair::new(); + let recipient_ata = create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; + + let mint_amount = 1_000_000u64; + + let ix = build_mint_to_checked_signed_cpi_ix( + mint, + recipient_ata, + pda_authority, + mint_amount, + 9, + bump, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` + diff --git a/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx new file mode 100644 index 00000000..e715a575 --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx @@ -0,0 +1,84 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::MintToCpi; + +declare_id!("8bXEVmHLtAVqDLJp1dYWAZ61WQmqQKoTQ8LpPbRoUDCp"); + +#[program] +pub mod light_token_anchor_mint_to { + use super::*; + + pub fn mint_to(ctx: Context, amount: u64) -> Result<()> { + MintToCpi { + mint: ctx.accounts.mint.to_account_info(), + destination: ctx.accounts.destination.to_account_info(), + amount, + authority: ctx.accounts.authority.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + max_top_up: None, + fee_payer: None, + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct MintToAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub mint: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub destination: AccountInfo<'info>, + pub authority: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_program_test::Rpc; +use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; +use light_token_anchor_mint_to::{instruction::MintTo, ID}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signer::Signer, + system_program, +}; +use test_utils::setup_test_env; + +#[tokio::test(flavor = "multi_thread")] +async fn test_mint_to() { + let mut env = setup_test_env("light_token_anchor_mint_to", ID).await; + + // No mint_tokens call - the test IS minting tokens via CPI. + let amount = 1_000_000u64; + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(env.mint_pda, false), + AccountMeta::new(env.ata, false), + AccountMeta::new_readonly(env.payer.pubkey(), true), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: MintTo { amount }.data(), + }; + + env.rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + // Verify the account exists and has data + let ata_data = env.rpc.get_account(env.ata).await.unwrap().unwrap(); + assert!(!ata_data.data.is_empty(), "ATA account should have data"); +} +``` + diff --git a/snippets/code-snippets/light-token/mint-to/instruction.mdx b/snippets/code-snippets/light-token/mint-to/instruction.mdx index c2efd0bb..1f904571 100644 --- a/snippets/code-snippets/light-token/mint-to/instruction.mdx +++ b/snippets/code-snippets/light-token/mint-to/instruction.mdx @@ -13,10 +13,10 @@ import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; -const rpc = createRpc(RPC_URL); +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); // localnet: -// const rpc = createRpc(); +const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( diff --git a/snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx new file mode 100644 index 00000000..53a52aa5 --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx @@ -0,0 +1,187 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::MintToCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn mint_to_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [mint, destination, authority, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let amount = u64::from_le_bytes( + data.try_into() + .map_err(|_| ProgramError::InvalidInstructionData)?, + ); + + // Mint tokens to destination account + MintToCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, + authority: authority.clone(), + system_program: system_program.clone(), + fee_payer: None, + max_top_up: None, + } + .invoke() +} + +pub fn mint_to_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [mint, destination, authority, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 9 { + return Err(ProgramError::InvalidInstructionData); + } + + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let bump = data[8]; + let signer_seeds = authority_seeds!(bump); + + MintToCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, + authority: authority.clone(), + system_program: system_program.clone(), + fee_payer: None, + max_top_up: None, + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use light_client::rpc::Rpc; +use light_token::instruction::derive_token_ata; +use shared::{ + build_create_ata_signed_cpi_ix, build_mint_to_cpi_ix, + build_mint_to_signed_cpi_ix, create_ata, create_test_rpc, + get_authority_pda, setup_empty_ata, setup_mint_with_pda_authority, + SetupContext, +}; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn mint_to_cpi() { + // Setup: create mint and empty ATA + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup_empty_ata().await; + + let amount = 1_000_000u64; + + let ix = build_mint_to_cpi_ix(mint, ata, payer.pubkey(), amount); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} + +/// Mints tokens via CPI with PDA authority. +#[tokio::test(flavor = "multi_thread")] +async fn mint_to_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_authority, authority_bump) = get_authority_pda(); + let decimals = 9u8; + + // Create mint with PDA authority + let mint = setup_mint_with_pda_authority( + &mut rpc, + &payer, + pda_authority, + decimals, + ) + .await; + + // Create ATA for the payer + let ata = create_ata(&mut rpc, &payer, payer.pubkey(), mint).await; + + // Mint tokens using signed CPI + let amount = 1_000_000u64; + let mint_to_ix = build_mint_to_signed_cpi_ix( + mint, + ata, + pda_authority, + amount, + authority_bump, + ); + + rpc.create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} + +/// Mints tokens using CPI-style ATA creation. +#[tokio::test(flavor = "multi_thread")] +async fn mint_to_signed_cpi_with_ata_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_authority, authority_bump) = get_authority_pda(); + let decimals = 9u8; + + // Create mint with PDA authority + let mint = setup_mint_with_pda_authority( + &mut rpc, + &payer, + pda_authority, + decimals, + ) + .await; + + // Create ATA using the signed CPI instruction + let (ata, ata_bump) = derive_token_ata(&payer.pubkey(), &mint); + let create_ata_ix = build_create_ata_signed_cpi_ix( + payer.pubkey(), + mint, + payer.pubkey(), + ata, + ata_bump, + authority_bump, + ); + + rpc.create_and_send_transaction( + &[create_ata_ix], + &payer.pubkey(), + &[&payer], + ) + .await + .unwrap(); + + // Mint tokens using signed CPI + let amount = 1_000_000u64; + let mint_to_ix = build_mint_to_signed_cpi_ix( + mint, + ata, + pda_authority, + amount, + authority_bump, + ); + + rpc.create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` + diff --git a/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx b/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx new file mode 100644 index 00000000..86abae0b --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx @@ -0,0 +1,47 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_client::actions::{CreateAta, CreateMint, MintTo}; +use rust_client::setup_rpc_and_payer; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let (mut rpc, payer) = setup_rpc_and_payer().await; + let mint_seed = Keypair::new(); + + // Create mint + let mint_result = CreateMint { + decimals: 9, + freeze_authority: None, + token_metadata: None, + } + .execute(&mut rpc, &payer, &mint_seed) + .await?; + + // Create ATA + let ata = CreateAta { + mint: mint_result.mint, + owner: payer.pubkey(), + idempotent: true, + } + .execute(&mut rpc, &payer) + .await?; + + // Mint tokens + let sig = MintTo { + mint: mint_result.mint, + destination: ata, + amount: 1_000_000, + max_top_up: None, + } + .execute(&mut rpc, &payer, &mint_seed) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx b/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx new file mode 100644 index 00000000..4b2ef381 --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx @@ -0,0 +1,41 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token::instruction::MintTo; +use rust_client::{setup_empty_ata, SetupContext}; +use solana_sdk::signer::Signer; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint and empty ATA + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup_empty_ata().await; + + let mint_amount = 1_000_000_000u64; + + let mint_to_ix = MintTo { + mint, + destination: ata, + amount: mint_amount, + authority: payer.pubkey(), + max_top_up: None, + fee_payer: None, + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[&payer]) + .await?; + + let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx new file mode 100644 index 00000000..84dd14ac --- /dev/null +++ b/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx @@ -0,0 +1,95 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::RevokeCpi; + +declare_id!("G3ph4MK5qaSdxYnfxToETg31AHEMMqVhPuMRgBhk38tQ"); + +#[program] +pub mod light_token_anchor_revoke { + use super::*; + + pub fn revoke(ctx: Context) -> Result<()> { + RevokeCpi { + token_account: ctx.accounts.token_account.to_account_info(), + owner: ctx.accounts.owner.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct RevokeAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub token_account: AccountInfo<'info>, + pub owner: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_program_test::Rpc; +use light_token::instruction::{Approve, LIGHT_TOKEN_PROGRAM_ID}; +use light_token_anchor_revoke::{instruction::Revoke, ID}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signature::Keypair, + signer::Signer, + system_program, +}; +use test_utils::{mint_tokens, setup_test_env}; + +#[tokio::test] +async fn test_revoke() { + let mut env = setup_test_env("light_token_anchor_revoke", ID).await; + + // Mint tokens first + let mint_amount = 1_000_000u64; + mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, mint_amount).await; + + // Approve delegate first using SDK + let delegate = Keypair::new(); + let approve_ix = Approve { + token_account: env.ata, + delegate: delegate.pubkey(), + owner: env.payer.pubkey(), + amount: 500_000, + } + .instruction() + .unwrap(); + + env.rpc + .create_and_send_transaction(&[approve_ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + // Call the anchor program to revoke delegation + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(env.ata, false), + AccountMeta::new_readonly(env.payer.pubkey(), true), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: Revoke {}.data(), + }; + + let sig = env + .rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/revoke/native-program/full-example.mdx b/snippets/code-snippets/light-token/revoke/native-program/full-example.mdx new file mode 100644 index 00000000..574e340d --- /dev/null +++ b/snippets/code-snippets/light-token/revoke/native-program/full-example.mdx @@ -0,0 +1,107 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::RevokeCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn revoke_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_account, owner, system_program, _token_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Revoke delegate authority from token account + RevokeCpi { + token_account: token_account.clone(), + owner: owner.clone(), + system_program: system_program.clone(), + } + .invoke() +} + +pub fn revoke_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [token_account, owner, system_program, _token_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + + let bump = data[0]; + let signer_seeds = authority_seeds!(bump); + + RevokeCpi { + token_account: token_account.clone(), + owner: owner.clone(), + system_program: system_program.clone(), + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use light_client::rpc::Rpc; +use shared::{ + build_revoke_cpi_ix, build_revoke_signed_cpi_ix, create_test_rpc, + get_authority_pda, setup, setup_pda_owned_ata, SetupContext, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn revoke_cpi() { + // Setup: create mint, ATA with tokens, and approve delegate + let SetupContext { + mut rpc, + payer, + ata, + .. + } = setup().await; + + let ix = build_revoke_cpi_ix(ata, payer.pubkey()); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn revoke_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_owner, bump) = get_authority_pda(); + + let (_mint, ata) = + setup_pda_owned_ata(&mut rpc, &payer, pda_owner, 1_000_000).await; + + let delegate = Keypair::new(); + let approve_ix = shared::build_approve_signed_cpi_ix( + ata, + delegate.pubkey(), + pda_owner, + 500_000, + bump, + ); + + rpc.create_and_send_transaction(&[approve_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let ix = build_revoke_signed_cpi_ix(ata, pda_owner, bump); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` + diff --git a/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx new file mode 100644 index 00000000..036d5335 --- /dev/null +++ b/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx @@ -0,0 +1,91 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::ThawCpi; + +declare_id!("7j94EF5hSkDLf7R26bjrd8Qc6s3oLAQpcKiF3re8JYw9"); + +#[program] +pub mod light_token_anchor_thaw { + use super::*; + + pub fn thaw(ctx: Context) -> Result<()> { + ThawCpi { + token_account: ctx.accounts.token_account.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + freeze_authority: ctx.accounts.freeze_authority.to_account_info(), + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct ThawAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub token_account: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub mint: AccountInfo<'info>, + pub freeze_authority: Signer<'info>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_program_test::Rpc; +use light_token::instruction::{Freeze, LIGHT_TOKEN_PROGRAM_ID}; +use light_token_anchor_thaw::{instruction::Thaw, ID}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signer::Signer, +}; +use test_utils::{mint_tokens, setup_test_env_with_freeze}; + +#[tokio::test] +async fn test_thaw() { + let mut env = setup_test_env_with_freeze("light_token_anchor_thaw", ID).await; + + // Mint tokens first + mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, 1_000_000).await; + + // Freeze account first using SDK + let freeze_ix = Freeze { + token_account: env.ata, + mint: env.mint_pda, + freeze_authority: env.freeze_authority, + } + .instruction() + .unwrap(); + + env.rpc + .create_and_send_transaction(&[freeze_ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + // Call the anchor program to thaw account + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(env.ata, false), + AccountMeta::new_readonly(env.mint_pda, false), + AccountMeta::new_readonly(env.freeze_authority, true), + ], + data: Thaw {}.data(), + }; + + let sig = env + .rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/thaw/native-program/full-example.mdx b/snippets/code-snippets/light-token/thaw/native-program/full-example.mdx new file mode 100644 index 00000000..aa8cb23c --- /dev/null +++ b/snippets/code-snippets/light-token/thaw/native-program/full-example.mdx @@ -0,0 +1,111 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::ThawCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn thaw_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + // Thaw frozen token account. freeze_authority must match mint creation + ThawCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), + } + .invoke() +} + +pub fn thaw_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.is_empty() { + return Err(ProgramError::InvalidInstructionData); + } + + let bump = data[0]; + let signer_seeds = authority_seeds!(bump); + + ThawCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use light_client::rpc::Rpc; +use shared::{ + build_freeze_signed_cpi_ix, build_thaw_cpi_ix, build_thaw_signed_cpi_ix, + create_ata, create_test_rpc, get_authority_pda, setup_frozen, + setup_mint_with_tokens, SetupContext, +}; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn thaw_cpi() { + // Setup: create mint, ATA, and freeze account + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup_frozen().await; + + let ix = build_thaw_cpi_ix(ata, mint, payer.pubkey()); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn thaw_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_authority, bump) = get_authority_pda(); + + let (mint, _) = setup_mint_with_tokens( + &mut rpc, + &payer, + payer.pubkey(), + Some(pda_authority), + 9, + vec![], + ) + .await; + + let ata = create_ata(&mut rpc, &payer, payer.pubkey(), mint).await; + + // Freeze first using PDA authority + let freeze_ix = build_freeze_signed_cpi_ix(ata, mint, pda_authority, bump); + rpc.create_and_send_transaction(&[freeze_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // Then thaw using PDA authority + let thaw_ix = build_thaw_signed_cpi_ix(ata, mint, pda_authority, bump); + rpc.create_and_send_transaction(&[thaw_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` + diff --git a/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx new file mode 100644 index 00000000..4f602bb2 --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx @@ -0,0 +1,107 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::TransferCheckedCpi; + +declare_id!("HXmfewpozFdxhM8BayL9v5541gwoGMXTrUoip5KySs2f"); + +#[program] +pub mod light_token_anchor_transfer_checked { + use super::*; + + pub fn transfer_checked( + ctx: Context, + amount: u64, + decimals: u8, + ) -> Result<()> { + TransferCheckedCpi { + source: ctx.accounts.source.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + destination: ctx.accounts.destination.to_account_info(), + amount, + decimals, + authority: ctx.accounts.authority.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + max_top_up: None, + fee_payer: None, + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct TransferCheckedAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub source: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + pub mint: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub destination: AccountInfo<'info>, + pub authority: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::InstructionData; +use light_program_test::Rpc; +use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; +use light_token_anchor_transfer_checked::{instruction::TransferChecked, ID}; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + signature::Keypair, + signer::Signer, + system_program, +}; +use test_utils::{create_ata_for_owner, mint_tokens, setup_test_env}; + +#[tokio::test] +async fn test_transfer_checked() { + let mut env = setup_test_env("light_token_anchor_transfer_checked", ID).await; + mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, 1_000_000).await; + + // Create destination ATA for recipient + let recipient = Keypair::new(); + let dest_ata = + create_ata_for_owner(&mut env.rpc, &env.payer, &recipient.pubkey(), &env.mint_pda).await; + + // TransferChecked validates decimals match the mint's decimals. + // Only use for Light->Light transfers. + // Use TransferInterface for all other transfers (Light, SPL or Token-2022). + let transfer_amount = 100_000u64; + let decimals = 9u8; + + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(env.ata, false), + AccountMeta::new_readonly(env.mint_pda, false), + AccountMeta::new(dest_ata, false), + AccountMeta::new_readonly(env.payer.pubkey(), true), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: TransferChecked { + amount: transfer_amount, + decimals, + } + .data(), + }; + + let sig = env + .rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx new file mode 100644 index 00000000..58e4033d --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx @@ -0,0 +1,177 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::TransferCheckedCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn transfer_checked_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [source, mint, destination, authority, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 9 { + return Err(ProgramError::InvalidInstructionData); + } + + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + + // TransferChecked validates decimals. Only for Light->Light. Use TransferInterface for SPL/T22 + TransferCheckedCpi { + source: source.clone(), + mint: mint.clone(), + destination: destination.clone(), + amount, + decimals, + authority: authority.clone(), + system_program: system_program.clone(), + max_top_up: None, + fee_payer: None, + } + .invoke() +} + +pub fn transfer_checked_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [source, mint, destination, authority, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 10 { + return Err(ProgramError::InvalidInstructionData); + } + + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + let bump = data[9]; + let signer_seeds = authority_seeds!(bump); + + TransferCheckedCpi { + source: source.clone(), + mint: mint.clone(), + destination: destination.clone(), + amount, + decimals, + authority: authority.clone(), + system_program: system_program.clone(), + max_top_up: None, + fee_payer: None, + } + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use light_client::rpc::Rpc; +use shared::{ + build_transfer_checked_cpi_ix, build_transfer_checked_signed_cpi_ix, + create_ata, create_test_rpc, get_authority_pda, setup, setup_pda_owned_ata, + SetupContext, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn transfer_checked_cpi() { + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup().await; + + let transfer_amount = 500_000u64; + + let recipient = Keypair::new(); + let recipient_ata = + create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; + + let ix = build_transfer_checked_cpi_ix( + ata, + mint, + recipient_ata, + payer.pubkey(), + transfer_amount, + 9, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn transfer_checked_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_authority, bump) = get_authority_pda(); + let initial_amount = 1_000_000u64; + + let (mint, pda_ata) = + setup_pda_owned_ata(&mut rpc, &payer, pda_authority, initial_amount) + .await; + + let recipient = Keypair::new(); + let recipient_ata = + create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; + + let transfer_amount = 500_000u64; + let ix = build_transfer_checked_signed_cpi_ix( + pda_ata, + mint, + recipient_ata, + pda_authority, + transfer_amount, + 9, + bump, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} + +/// Tests transferring the exact balance from a token account. +#[tokio::test(flavor = "multi_thread")] +async fn transfer_checked_exact_balance_cpi() { + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup().await; + + let exact_balance = 1_000_000u64; + + let recipient = Keypair::new(); + let recipient_ata = + create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; + + let ix = build_transfer_checked_cpi_ix( + ata, + mint, + recipient_ata, + payer.pubkey(), + exact_balance, + 9, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` + diff --git a/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx b/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx new file mode 100644 index 00000000..49dc7814 --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx @@ -0,0 +1,52 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_client::actions::{CreateAta, TransferChecked}; +use rust_client::{setup, SetupContext}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let SetupContext { + mut rpc, + payer, + mint, + ata, + decimals, + .. + } = setup().await; + + // Create recipient ATA + let recipient = Keypair::new(); + let recipient_ata = CreateAta { + mint, + owner: recipient.pubkey(), + idempotent: true, + } + .execute(&mut rpc, &payer) + .await?; + + // TransferChecked validates decimals match the mint's decimals. + // Only use for Light->Light transfers. + // Use TransferInterface for all other transfers (Light, SPL or Token-2022). + let sig = TransferChecked { + source: ata, + mint, + destination: recipient_ata, + amount: 1000, + decimals, + max_top_up: None, + } + .execute(&mut rpc, &payer, &payer) + .await?; + + let data = rpc + .get_account(recipient_ata) + .await? + .ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx b/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx new file mode 100644 index 00000000..267a9c1c --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx @@ -0,0 +1,62 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token::instruction::{ + get_associated_token_address, CreateAssociatedTokenAccount, TransferChecked, +}; +use rust_client::{setup, SetupContext}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + mint, + ata, + decimals, + .. + } = setup().await; + + let transfer_amount = 400_000u64; + + // Create recipient ATA + let recipient = Keypair::new(); + let recipient_ata = get_associated_token_address(&recipient.pubkey(), &mint); + + let create_ata_ix = CreateAssociatedTokenAccount::new(payer.pubkey(), recipient.pubkey(), mint) + .instruction()?; + + rpc.create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[&payer]) + .await?; + + // TransferChecked validates decimals match the mint's decimals + // Only use for Light->Light transfers. + // Use TransferInterface for all other transfers (Light, SPL or Token-2022). + let transfer_ix = TransferChecked { + source: ata, + mint, + destination: recipient_ata, + amount: transfer_amount, + decimals, + authority: payer.pubkey(), + max_top_up: None, + fee_payer: None, + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[transfer_ix], &payer.pubkey(), &[&payer]) + .await?; + + let data = rpc + .get_account(recipient_ata) + .await? + .ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx new file mode 100644 index 00000000..87120dee --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx @@ -0,0 +1,106 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::TransferInterfaceCpi; + +declare_id!("3rb6sG4jiYNLZC8jo8kLsFHpxr2Ci8e8Hh8UmeCMZmUV"); + +#[program] +pub mod light_token_anchor_transfer_interface { + use super::*; + + pub fn transfer(ctx: Context, amount: u64, decimals: u8) -> Result<()> { + TransferInterfaceCpi::new( + amount, + decimals, + ctx.accounts.source.to_account_info(), + ctx.accounts.destination.to_account_info(), + ctx.accounts.authority.to_account_info(), + ctx.accounts.payer.to_account_info(), + ctx.accounts.cpi_authority.to_account_info(), + ctx.accounts.system_program.to_account_info(), + ) + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct TransferAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub source: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub destination: AccountInfo<'info>, + pub authority: Signer<'info>, + #[account(mut)] + pub payer: Signer<'info>, + /// CHECK: Validated by light-token CPI + pub cpi_authority: AccountInfo<'info>, + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::system_program; +use anchor_lang::InstructionData; +use light_program_test::Rpc; +use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; +use light_token_anchor_transfer_interface::{instruction::Transfer, ID}; +use light_token_types::CPI_AUTHORITY_PDA; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, +}; +use test_utils::{create_ata_for_owner, mint_tokens, setup_test_env}; + +#[tokio::test] +async fn test_transfer() { + let mut env = setup_test_env("light_token_anchor_transfer_interface", ID).await; + mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, 1_000_000).await; + + // Create destination ATA for recipient + let recipient = Keypair::new(); + let dest_ata = + create_ata_for_owner(&mut env.rpc, &env.payer, &recipient.pubkey(), &env.mint_pda).await; + + // Transfers tokens between accounts (SPL, Token-2022, or Light) in a single call. + let transfer_amount = 100_000u64; + let decimals = 9u8; + let cpi_authority_pda = Pubkey::new_from_array(CPI_AUTHORITY_PDA); + + let ix = Instruction { + program_id: ID, + accounts: vec![ + AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), + AccountMeta::new(env.ata, false), + AccountMeta::new(dest_ata, false), + AccountMeta::new_readonly(env.payer.pubkey(), true), + AccountMeta::new(env.payer.pubkey(), true), + AccountMeta::new_readonly(cpi_authority_pda, false), + AccountMeta::new_readonly(system_program::ID, false), + ], + data: Transfer { + amount: transfer_amount, + decimals, + } + .data(), + }; + + let sig = env + .rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + println!("Tx: {}", sig); +} +``` + diff --git a/snippets/code-snippets/light-token/transfer-interface/instruction.mdx b/snippets/code-snippets/light-token/transfer-interface/instruction.mdx index 2ae13150..e8acf2a8 100644 --- a/snippets/code-snippets/light-token/transfer-interface/instruction.mdx +++ b/snippets/code-snippets/light-token/transfer-interface/instruction.mdx @@ -18,10 +18,10 @@ import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; -const rpc = createRpc(RPC_URL); +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const rpc = createRpc(RPC_URL); // localnet: -// const rpc = createRpc(); +const rpc = createRpc(); const payer = Keypair.fromSecretKey( new Uint8Array( diff --git a/snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx new file mode 100644 index 00000000..53d51fa8 --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx @@ -0,0 +1,185 @@ + +```rust instruction.rs +use super::authority_seeds; +use light_token::instruction::TransferInterfaceCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, +}; + +pub fn transfer_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [source, destination, authority, payer, light_token_authority, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 9 { + return Err(ProgramError::InvalidInstructionData); + } + + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + + // Transfer tokens between accounts (SPL, Token-2022, or Light) + TransferInterfaceCpi::new( + amount, + decimals, + source.clone(), + destination.clone(), + authority.clone(), + payer.clone(), + light_token_authority.clone(), + system_program.clone(), + ) + .invoke() +} + +pub fn transfer_invoke_signed( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { + let [source, destination, authority, payer, light_token_authority, system_program, _token_program] = + accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 10 { + return Err(ProgramError::InvalidInstructionData); + } + + let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); + let decimals = data[8]; + let bump = data[9]; + let signer_seeds = authority_seeds!(bump); + + TransferInterfaceCpi::new( + amount, + decimals, + source.clone(), + destination.clone(), + authority.clone(), + payer.clone(), + light_token_authority.clone(), + system_program.clone(), + ) + .invoke_signed(&[signer_seeds]) +} +``` + +```rust test.rs +mod shared; + +use light_client::rpc::Rpc; +use light_token::instruction::cpi_authority; +use shared::{ + build_transfer_interface_cpi_ix, build_transfer_interface_signed_cpi_ix, + create_ata, create_test_rpc, get_authority_pda, setup, setup_pda_owned_ata, + SetupContext, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn transfer_interface_cpi() { + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup().await; + + let transfer_amount = 500_000u64; + + // Setup: create mint and token accounts + let recipient = Keypair::new(); + let recipient_ata = + create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; + + // 1. Transfer from source to destination + let ix = build_transfer_interface_cpi_ix( + ata, + recipient_ata, + payer.pubkey(), + payer.pubkey(), + cpi_authority(), + transfer_amount, + 9, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} + +#[tokio::test(flavor = "multi_thread")] +async fn transfer_interface_signed_cpi() { + let mut rpc = create_test_rpc().await; + let payer = rpc.get_payer().insecure_clone(); + + let (pda_authority, bump) = get_authority_pda(); + let initial_amount = 1_000_000u64; + + // Setup: create mint and token accounts + let (mint, pda_ata) = + setup_pda_owned_ata(&mut rpc, &payer, pda_authority, initial_amount) + .await; + + let recipient = Keypair::new(); + let recipient_ata = + create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; + + let transfer_amount = 500_000u64; + // 1. Transfer from source to destination + let ix = build_transfer_interface_signed_cpi_ix( + pda_ata, + recipient_ata, + pda_authority, + payer.pubkey(), + cpi_authority(), + transfer_amount, + 9, + bump, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} + +/// Transfers exact balance from token account. +#[tokio::test(flavor = "multi_thread")] +async fn transfer_exact_balance_cpi() { + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = setup().await; + + // The setup() function creates an ATA with 1_000_000 tokens + let exact_balance = 1_000_000u64; + + let recipient = Keypair::new(); + let recipient_ata = + create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; + + // Transfer the exact balance, leaving source with 0 + let ix = build_transfer_interface_cpi_ix( + ata, + recipient_ata, + payer.pubkey(), + payer.pubkey(), + cpi_authority(), + exact_balance, + 9, + ); + + rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); +} +``` + diff --git a/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx b/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx new file mode 100644 index 00000000..70da23d0 --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx @@ -0,0 +1,50 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_client::actions::{CreateAta, TransferInterface}; +use rust_client::{setup, SetupContext}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let SetupContext { + mut rpc, + payer, + mint, + ata, + decimals, + .. + } = setup().await; + + // Create recipient ATA + let recipient = Keypair::new(); + let recipient_ata = CreateAta { + mint, + owner: recipient.pubkey(), + idempotent: true, + } + .execute(&mut rpc, &payer) + .await?; + + // Transfers tokens between token accounts (SPL, Token-2022, or Light) in a single call. + let sig = TransferInterface { + source: ata, + mint, + destination: recipient_ata, + amount: 1000, + decimals, + ..Default::default() + } + .execute(&mut rpc, &payer, &payer) + .await?; + + let data = rpc + .get_account(recipient_ata) + .await? + .ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx b/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx new file mode 100644 index 00000000..ad17b90e --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx @@ -0,0 +1,74 @@ +```rust +use anchor_spl::token::spl_token; +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token::{ + instruction::{ + get_associated_token_address, CreateAssociatedTokenAccount, SplInterface, + TransferInterface, LIGHT_TOKEN_PROGRAM_ID, + }, + spl_interface::find_spl_interface_pda_with_index, +}; +use rust_client::{setup_spl_ata, setup_spl_mint}; +use solana_sdk::signer::Signer; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(true, None)).await?; + + let payer = rpc.get_payer().insecure_clone(); + let decimals = 2u8; + let amount = 10_000u64; + + // Setup creates mint, mints tokens and creates SPL ATA + let mint = setup_spl_mint(&mut rpc, &payer, decimals).await; + let spl_ata = setup_spl_ata(&mut rpc, &payer, &mint, &payer.pubkey(), amount).await; + let (interface_pda, interface_bump) = find_spl_interface_pda_with_index(&mint, 0, false); + + // Create Light ATA + let light_ata = get_associated_token_address(&payer.pubkey(), &mint); + + let create_ata_ix = + CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint).instruction()?; + + rpc.create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[&payer]) + .await?; + + // SPL interface PDA for Mint (holds SPL tokens when transferred to Light Token) + let spl_interface = SplInterface { + mint, + spl_token_program: spl_token::ID, + spl_interface_pda: interface_pda, + spl_interface_pda_bump: interface_bump, + }; + + // Transfers tokens between token accounts (SPL, Token-2022, or Light) in a single call. + let transfer_interface_ix = TransferInterface { + source: spl_ata, + destination: light_ata, + amount, + decimals, + authority: payer.pubkey(), + payer: payer.pubkey(), + spl_interface: Some(spl_interface), + max_top_up: None, + source_owner: spl_token::ID, + destination_owner: LIGHT_TOKEN_PROGRAM_ID, + } + .instruction()?; + + let sig = rpc + .create_and_send_transaction(&[transfer_interface_ix], &payer.pubkey(), &[&payer]) + .await?; + + let data = rpc + .get_account(light_ata) + .await? + .ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/unwrap/instruction.mdx b/snippets/code-snippets/light-token/unwrap/instruction.mdx index ec4ee0da..38689df2 100644 --- a/snippets/code-snippets/light-token/unwrap/instruction.mdx +++ b/snippets/code-snippets/light-token/unwrap/instruction.mdx @@ -15,7 +15,7 @@ import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; const rpc = createRpc(RPC_URL); // localnet: // const rpc = createRpc(); diff --git a/snippets/code-snippets/light-token/unwrap/rust-client/action.mdx b/snippets/code-snippets/light-token/unwrap/rust-client/action.mdx new file mode 100644 index 00000000..ed70caaf --- /dev/null +++ b/snippets/code-snippets/light-token/unwrap/rust-client/action.mdx @@ -0,0 +1,40 @@ +```rust +use anchor_spl::token::spl_token::state::Account as SplAccount; +use light_client::rpc::Rpc; +use light_token_client::actions::Unwrap; +use rust_client::{setup_for_unwrap, UnwrapContext}; +use solana_sdk::program_pack::Pack; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates Light ATA with tokens and empty SPL ATA + let UnwrapContext { + mut rpc, + payer, + mint, + destination_ata: destination, + light_ata, + decimals, + } = setup_for_unwrap().await; + + // Unwrap tokens from Light Token ATA to SPL ATA + let sig = Unwrap { + source: light_ata, + destination_spl_ata: destination, + mint, + amount: 500_000, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await?; + + let data = rpc + .get_account(destination) + .await? + .ok_or("Account not found")?; + let token = SplAccount::unpack(&data.data)?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/code-snippets/light-token/wrap/instruction.mdx b/snippets/code-snippets/light-token/wrap/instruction.mdx index 5be18dbd..e360f5d2 100644 --- a/snippets/code-snippets/light-token/wrap/instruction.mdx +++ b/snippets/code-snippets/light-token/wrap/instruction.mdx @@ -16,7 +16,7 @@ import { homedir } from "os"; import { readFileSync } from "fs"; // devnet: -const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; +// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`; const rpc = createRpc(RPC_URL); // localnet: // const rpc = createRpc(); diff --git a/snippets/code-snippets/light-token/wrap/rust-client/action.mdx b/snippets/code-snippets/light-token/wrap/rust-client/action.mdx new file mode 100644 index 00000000..1b8fce16 --- /dev/null +++ b/snippets/code-snippets/light-token/wrap/rust-client/action.mdx @@ -0,0 +1,39 @@ +```rust +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_client::actions::Wrap; +use rust_client::{setup_for_wrap, WrapContext}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Setup creates SPL ATA with tokens and empty Light ATA + let WrapContext { + mut rpc, + payer, + mint, + source_ata, + light_ata, + decimals, + } = setup_for_wrap().await; + + // Wrap tokens from SPL ATA to Light Token ATA + let sig = Wrap { + source_spl_ata: source_ata, + destination: light_ata, + mint, + amount: 500_000, + decimals, + } + .execute(&mut rpc, &payer, &payer) + .await?; + + let data = rpc + .get_account(light_ata) + .await? + .ok_or("Account not found")?; + let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; + println!("Balance: {} Tx: {sig}", token.amount); + + Ok(()) +} +``` diff --git a/snippets/jsx/code-compare.jsx b/snippets/jsx/code-compare.jsx index 86ec2b1f..43afb326 100644 --- a/snippets/jsx/code-compare.jsx +++ b/snippets/jsx/code-compare.jsx @@ -3,18 +3,46 @@ export const CodeCompare = ({ secondCode = "", firstLabel = "Light Token", secondLabel = "SPL", + language = "javascript", }) => { - const [sliderPercent, setSliderPercent] = useState(0); + const [sliderPercent, setSliderPercent] = useState(100); const [isDragging, setIsDragging] = useState(false); const [isAnimating, setIsAnimating] = useState(false); + const [copied, setCopied] = useState(false); const containerRef = useRef(null); const animationRef = useRef(null); - const isLightMode = sliderPercent > 50; + // When slider is on the right (100%), show first code; on left (0%), show second code + const showingFirst = sliderPercent > 50; + + const handleCopy = async () => { + const codeToCopy = showingFirst ? firstCode : secondCode; + await navigator.clipboard.writeText(codeToCopy); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }; const highlightCode = (code) => { let escaped = code.replace(/&/g, "&").replace(//g, ">"); + if (language === "rust") { + // Rust syntax highlighting + const rustPattern = + /(\/\/.*$)|(["'])(?:(?!\2)[^\\]|\\.)*?\2|\b(use|let|mut|pub|fn|struct|impl|enum|mod|const|static|trait|type|where|for|in|if|else|match|loop|while|return|self|Self|true|false|Some|None|Ok|Err|Result|Option|vec!)\b|::([a-zA-Z_][a-zA-Z0-9_]*)|&([a-zA-Z_][a-zA-Z0-9_]*)|\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()|(\?)/gm; + + return escaped.replace(rustPattern, (match, comment, stringQuote, keyword, pathSegment, reference, func, questionMark) => { + if (comment) return `${match}`; + if (stringQuote) return `${match}`; + if (keyword) return `${match}`; + if (pathSegment) return `::${pathSegment}`; + if (reference) return `&${reference}`; + if (func) return `${match}`; + if (questionMark) return `?`; + return match; + }); + } + + // JavaScript/TypeScript syntax highlighting (default) const pattern = /(\/\/.*$)|(["'`])(?:(?!\2)[^\\]|\\.)*?\2|\b(const|let|var|await|async|import|from|export|return|if|else|function|class|new|throw|try|catch)\b|\.([a-zA-Z_][a-zA-Z0-9_]*)\b|\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()/gm; @@ -60,7 +88,7 @@ export const CodeCompare = ({ }; const handleToggle = () => { - animateTo(isLightMode ? 0 : 100); + animateTo(showingFirst ? 0 : 100); }; const handleMouseDown = (e) => { @@ -126,54 +154,64 @@ export const CodeCompare = ({ return ( <>
{/* Header with toggle */}
- {isLightMode ? secondLabel : firstLabel} + {showingFirst ? firstLabel : secondLabel} - {/* Neumorphic Toggle Switch */} -
+
+ {/* Copy button */} + + + {/* Neumorphic Toggle Switch */} +
{/* Toggle button */}
+
{/* Code container */} @@ -209,9 +248,9 @@ export const CodeCompare = ({ aria-valuemax={100} aria-label="Code comparison slider" > -
+
- {/* First code (background) */} + {/* Second code (background) - shown when slider is on left */}
 
-              {/* Second code (foreground) with clip-path */}
+              {/* First code (foreground) with clip-path - revealed when slider moves right */}
               
             
@@ -256,7 +295,7 @@ export const CodeCompare = ({ className="absolute top-0 bottom-0" style={{ right: "50%", - width: "80px", + width: "60px", background: "linear-gradient(to left, rgba(0, 102, 255, 0.15) 0%, transparent 100%)", }} diff --git a/snippets/setup/compressed-pdas-program-setup.mdx b/snippets/setup/compressed-pdas-program-setup.mdx index 74801922..0e3426fb 100644 --- a/snippets/setup/compressed-pdas-program-setup.mdx +++ b/snippets/setup/compressed-pdas-program-setup.mdx @@ -5,13 +5,13 @@ Add dependencies to your program. ```toml Anchor [dependencies] -light-sdk = "0.16.0" +light-sdk = "0.19.0" anchor_lang = "0.31.1" ``` ```toml Native Rust [dependencies] -light-sdk = "0.16.0" +light-sdk = "0.19.0" borsh = "0.10.0" solana-program = "2.2" ``` diff --git a/snippets/setup/rust-install-dependencies.mdx b/snippets/setup/rust-install-dependencies.mdx index cf39be3a..3509c284 100644 --- a/snippets/setup/rust-install-dependencies.mdx +++ b/snippets/setup/rust-install-dependencies.mdx @@ -1,12 +1,8 @@ ```toml Cargo.toml [dependencies] -light-compressed-token-sdk = "0.1" -light-client = "0.1" -light-token-types = "0.1" +light-token = "0.4.0" +light-client = "0.19.0" solana-sdk = "2.2" borsh = "0.10" tokio = { version = "1.36", features = ["full"] } - -[dev-dependencies] -light-program-test = "0.1" # For in-memory tests with LiteSVM ``` diff --git a/snippets/versions/rust-deps-0.16.0.mdx b/snippets/versions/rust-deps-0.16.0.mdx deleted file mode 100644 index c14d002d..00000000 --- a/snippets/versions/rust-deps-0.16.0.mdx +++ /dev/null @@ -1,5 +0,0 @@ -```toml -[dependencies] -light-client = "0.16.0" -light-sdk = "0.16.0" -``` diff --git a/snippets/versions/rust-deps-0.19.0.mdx b/snippets/versions/rust-deps-0.19.0.mdx new file mode 100644 index 00000000..ddb245ee --- /dev/null +++ b/snippets/versions/rust-deps-0.19.0.mdx @@ -0,0 +1,5 @@ +```toml +[dependencies] +light-client = "0.19.0" +light-sdk = "0.19.0" +``` diff --git a/snippets/versions/rust-deps-token-sdk-0.16.0.mdx b/snippets/versions/rust-deps-token-sdk-0.16.0.mdx deleted file mode 100644 index a15a3903..00000000 --- a/snippets/versions/rust-deps-token-sdk-0.16.0.mdx +++ /dev/null @@ -1,8 +0,0 @@ -```toml -[dependencies] -light-sdk = "0.16.0" -light-compressed-token-sdk = "0.1.0" - -[dev-dependencies] -light-program-test = "1.2.1" -``` diff --git a/snippets/versions/rust-deps-token-sdk-0.19.0.mdx b/snippets/versions/rust-deps-token-sdk-0.19.0.mdx new file mode 100644 index 00000000..e51ec9cc --- /dev/null +++ b/snippets/versions/rust-deps-token-sdk-0.19.0.mdx @@ -0,0 +1,5 @@ +```toml +[dependencies] +light-sdk = "0.19.0" +light-token = "0.4.0" +``` From 6b818851d7a57f6f894b3d9064a068cdf70fc27e Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 26 Jan 2026 23:49:02 +0000 Subject: [PATCH 02/21] remove anchor macros, instruction decoder (moved to add-anchor-macros branch) --- light-token/anchor.mdx | 33 ------------- resources/sdks/instruction-decoder.mdx | 68 -------------------------- resources/sdks/program-development.mdx | 1 - 3 files changed, 102 deletions(-) delete mode 100644 light-token/anchor.mdx delete mode 100644 resources/sdks/instruction-decoder.mdx diff --git a/light-token/anchor.mdx b/light-token/anchor.mdx deleted file mode 100644 index fa4772fe..00000000 --- a/light-token/anchor.mdx +++ /dev/null @@ -1,33 +0,0 @@ ---- -title: Anchor -description: Using Anchor with light-token. -keywords: ["anchor, solana anchor, rust, light protocol anchor, light token anchor, token solana anchor, spl token anchor"] ---- - -The Light Token uses standard Anchor and a custom Light Account macro to reduce boilerplate code. Main macros include: - -* declare_id: Specifies the program's on-chain address -* #[program]: Specifies the module containing the program’s instruction logic -* #[derive(Accounts)]: Applied to structs to indicate a list of accounts required by an instruction -* #[account]: Applied to structs to create custom account types for the program - - -The #[derive(Accounts)] macro is applied to a struct -to specify the accounts that must be provided when an instruction is invoked. - -```rust -use light_sdk::compressible::CompressionInfo; -use light_sdk_macros::LightAccount; - -#[derive(Default, Debug, InitSpace, LightAccount)] -#[account] -pub struct PoolState { - /// Add this: - pub compression_info: Option, - - /// Your existing fields - /// ... -} -``` - -The LightAccounts trait complements the standard accounts trait \ No newline at end of file diff --git a/resources/sdks/instruction-decoder.mdx b/resources/sdks/instruction-decoder.mdx deleted file mode 100644 index b387417e..00000000 --- a/resources/sdks/instruction-decoder.mdx +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: Instruction Decoder -description: Decode instruction data for test logging ---- - -## Overview - -The instruction decoder crate decodes raw instruction data into named fields for logging during tests. Off-chain only. - -## Installation - -```toml -[dependencies] -light-instruction-decoder = "0.1.0" -``` - -## Usage - -### Derive macro for instruction enums - -```rust -use light_instruction_decoder_derive::InstructionDecoder; - -#[derive(InstructionDecoder)] -#[instruction_decoder( - program_id = "MyProgram11111111111111111111111111111111111", - program_name = "My Program" -)] -pub enum MyInstruction { - #[instruction_decoder(accounts = CreateRecord)] - CreateRecord, -} -``` - -### Attribute macro for Anchor programs - -```rust -use light_instruction_decoder_derive::instruction_decoder; - -#[instruction_decoder] -#[program] -pub mod my_program { - pub fn create_record(ctx: Context) -> Result<()> { - // ... - } -} -``` - -### Register with test config - -```rust -let config = ProgramTestConfig::new() - .add_decoder(MyProgramInstructionDecoder::default()); -``` - -## Configuration - -| Attribute | Description | -|-----------|-------------| -| `program_id` | Program address | -| `program_name` | Display name (optional) | -| `discriminator_size` | 1, 4, or 8 bytes (default: 8) | -| `accounts` | Struct to extract account names from | -| `params` | Struct for parameter deserialization | - -## API reference - -See [docs.rs/light-instruction-decoder](https://docs.rs/light-instruction-decoder) \ No newline at end of file diff --git a/resources/sdks/program-development.mdx b/resources/sdks/program-development.mdx index fa4d876d..7ae55b87 100644 --- a/resources/sdks/program-development.mdx +++ b/resources/sdks/program-development.mdx @@ -11,7 +11,6 @@ ZK Compression's Rust crates are published to [crates.io](https://docs.rs/releas - [`light-sdk`](https://github.com/Lightprotocol/light-protocol/tree/main/sdk-libs/sdk) — For Anchor and native programs. Includes CPI utilities, compressed account abstractions similar to anchor Account, and metadata structs for CPIs to the Light System program. - [`light-sdk-pinocchio`](https://github.com/Lightprotocol/light-protocol/tree/main/sdk-libs/sdk-pinocchio) — For Pinocchio programs. Pinocchio-optimized SDK with compressed account abstractions and CPI utilities. -- [`light-instruction-decoder`](/resources/sdks/instruction-decoder) — Decodes instruction data into named fields for test logging. Off-chain only. ## Light Programs Overview From bb0256e0a6cfbe22fbd71fc2da2c7db6221583d0 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Tue, 27 Jan 2026 00:55:26 +0000 Subject: [PATCH 03/21] add overview --- docs.json | 9 + light-token/cookbook/approve-revoke.mdx | 6 +- light-token/cookbook/burn.mdx | 4 +- light-token/cookbook/close-token-account.mdx | 4 +- light-token/cookbook/create-ata.mdx | 16 +- light-token/cookbook/create-mint.mdx | 16 +- light-token/cookbook/create-token-account.mdx | 16 +- light-token/cookbook/freeze-thaw.mdx | 6 +- light-token/cookbook/mint-to.mdx | 6 +- light-token/cookbook/transfer-checked.mdx | 4 +- light-token/cookbook/transfer-interface.mdx | 4 +- light-token/examples/client.mdx | 43 ++++ light-token/examples/program.mdx | 23 ++ .../create-ata/anchor-macro/full-example.mdx | 212 ---------------- .../create-mint/anchor-macro/full-example.mdx | 185 -------------- .../anchor-macro/full-example.mdx | 240 ------------------ .../overview-tables/cookbook-guides-table.mdx | 30 +++ 17 files changed, 128 insertions(+), 696 deletions(-) create mode 100644 light-token/examples/client.mdx create mode 100644 light-token/examples/program.mdx delete mode 100644 snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx diff --git a/docs.json b/docs.json index 1086a92d..52bc76a5 100644 --- a/docs.json +++ b/docs.json @@ -62,8 +62,17 @@ "group": "Cookbook", "pages": [ "light-token/cookbook/overview", + { + "group": "Examples", + "expanded": true, + "pages": [ + "light-token/examples/client", + "light-token/examples/program" + ] + }, { "group": "Recipes", + "expanded": true, "pages": [ "light-token/cookbook/create-mint", "light-token/cookbook/create-ata", diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx index b6eedafa..9f3d5836 100644 --- a/light-token/cookbook/approve-revoke.mdx +++ b/light-token/cookbook/approve-revoke.mdx @@ -101,7 +101,7 @@ import RevokeNativeProgramCode from "/snippets/code-snippets/light-token/revoke/ - + Find [a full code example at the end](#full-code-example). @@ -222,7 +222,7 @@ revoke_cpi.invoke_signed(&[signer_seeds])?; - + View the full example with shared test utilities: @@ -250,7 +250,7 @@ revoke_cpi.invoke_signed(&[signer_seeds])?; - + View the full example with shared test utilities: diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx index 8b4f7fe9..ee688fcc 100644 --- a/light-token/cookbook/burn.mdx +++ b/light-token/cookbook/burn.mdx @@ -60,7 +60,7 @@ Compare to SPL: - + Find [a full code example at the end](#full-code-example). @@ -130,7 +130,7 @@ BurnCpi { - + View the full example with shared test utilities: diff --git a/light-token/cookbook/close-token-account.mdx b/light-token/cookbook/close-token-account.mdx index 9eaca143..7ec43fb4 100644 --- a/light-token/cookbook/close-token-account.mdx +++ b/light-token/cookbook/close-token-account.mdx @@ -69,7 +69,7 @@ Compare to SPL: - + Find [a full code example at the end](#full-code-example). @@ -138,7 +138,7 @@ CloseAccountCpi { - + View the full example with shared test utilities: diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index b21a99a0..965013b4 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -24,7 +24,6 @@ import InstructionCode from "/snippets/code-snippets/light-token/create-ata/inst import RustActionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx"; -import AnchorMacroCode from "/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx"; import NativeProgramCode from "/snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx"; 1. Associated light-token accounts can hold token balances of light, SPL, or Token 2022 mints. @@ -120,7 +119,7 @@ Compare to SPL: - + Find [a full code example at the end](#full-code-example). @@ -206,7 +205,7 @@ CreateAssociatedAccountCpi { - + View the full example with shared test utilities: @@ -220,17 +219,6 @@ CreateAssociatedAccountCpi { - - - - View the full example: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/macro-basics/create-ata-macro). - - - - - - # Next Steps diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx index 4ea9b453..dd3845bd 100644 --- a/light-token/cookbook/create-mint.mdx +++ b/light-token/cookbook/create-mint.mdx @@ -25,7 +25,6 @@ import RustActionCode from "/snippets/code-snippets/light-token/create-mint/rust import RustInstructionCode from "/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx"; import NativeProgramCode from "/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx"; -import AnchorMacroCode from "/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx"; import CompressibleRentExplained from "/snippets/compressible-rent-explained.mdx"; 1. Mint accounts uniquely represent a token on Solana and store its global metadata. @@ -131,7 +130,7 @@ Compare to SPL: - + Find [a full code example at the end](#full-code-example). @@ -323,7 +322,7 @@ CreateMintCpi::new( - + View the full example with shared test utilities: @@ -337,17 +336,6 @@ CreateMintCpi::new( - - - - View the full example: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/macro-basics/create-mint-macro). - - - - - - # Next Steps diff --git a/light-token/cookbook/create-token-account.mdx b/light-token/cookbook/create-token-account.mdx index 1d14961c..eea8df59 100644 --- a/light-token/cookbook/create-token-account.mdx +++ b/light-token/cookbook/create-token-account.mdx @@ -17,7 +17,6 @@ import { } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx"; -import AnchorMacroCode from "/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx"; import NativeProgramCode from "/snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx"; 1. Light token accounts are Solana accounts that hold token balances of light, SPL, or Token 2022 mints. @@ -69,7 +68,7 @@ Compare to SPL: - + Find [a full code example at the end](#full-code-example). @@ -150,7 +149,7 @@ CreateTokenAccountCpi { - + View the full example with shared test utilities: @@ -164,17 +163,6 @@ CreateTokenAccountCpi { - - - - View the full example: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/macro-basics/create-token-account-macro). - - - - - - # Next Steps - + Find [a full code example at the end](#full-code-example). @@ -208,7 +208,7 @@ ThawCpi { - + View the full example with shared test utilities: @@ -236,7 +236,7 @@ ThawCpi { - + View the full example with shared test utilities: diff --git a/light-token/cookbook/mint-to.mdx b/light-token/cookbook/mint-to.mdx index ee77a805..dd289e57 100644 --- a/light-token/cookbook/mint-to.mdx +++ b/light-token/cookbook/mint-to.mdx @@ -114,7 +114,7 @@ Compare to SPL: - + Find [a full code example at the end](#full-code-example). @@ -189,7 +189,7 @@ MintToCpi { - + View the full example with shared test utilities: @@ -272,7 +272,7 @@ MintToCheckedCpi { # Full Code Example - + View the full example with shared test utilities: diff --git a/light-token/cookbook/transfer-checked.mdx b/light-token/cookbook/transfer-checked.mdx index 3c73fde3..ff6a2f95 100644 --- a/light-token/cookbook/transfer-checked.mdx +++ b/light-token/cookbook/transfer-checked.mdx @@ -49,7 +49,7 @@ import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-chec - + Find [a full code example at the end](#full-code-example). @@ -121,7 +121,7 @@ TransferCheckedCpi { - + View the full example with shared test utilities: diff --git a/light-token/cookbook/transfer-interface.mdx b/light-token/cookbook/transfer-interface.mdx index 71b9beab..98402787 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -146,7 +146,7 @@ The example transfers - + Find [a full code example at the end](#full-code-example). @@ -218,7 +218,7 @@ TransferInterfaceCpi::new( - + View the full example with shared test utilities: diff --git a/light-token/examples/client.mdx b/light-token/examples/client.mdx new file mode 100644 index 00000000..a52d7046 --- /dev/null +++ b/light-token/examples/client.mdx @@ -0,0 +1,43 @@ +--- +title: "Client examples" +sidebarTitle: "Client" +description: "TypeScript and Rust client examples for light-token SDK." +--- + +Find all examples on Github: [examples-light-token](https://github.com/Lightprotocol/examples-light-token) + +## TypeScript + +| | | | +|---------|--------|-------------| +| **approve** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/delegate-approve.ts) | — | +| **create-ata** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/create-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/create-ata.ts) | +| **create-mint** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/create-mint.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/create-mint.ts) | +| **load-ata** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/load-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/load-ata.ts) | +| **mint-to** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/mint-to.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/mint-to.ts) | +| **revoke** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/delegate-revoke.ts) | — | +| **transfer-interface** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/transfer-interface.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/transfer-interface.ts) | +| **unwrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/unwrap.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/unwrap.ts) | +| **wrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/wrap.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/wrap.ts) | + +## Rust + +| | | | +|---------|--------|-------------| +| **approve** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/approve.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/approve.rs) | +| **burn** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/burn.rs) | +| **burn-checked** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/burn_checked.rs) | +| **close** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/close.rs) | +| **create-ata** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/create_ata.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/create_ata.rs) | +| **create-mint** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/create_mint.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/create_mint.rs) | +| **create-token-account** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/create_token_account.rs) | +| **freeze** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/freeze.rs) | +| **mint-to** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/mint_to.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/mint_to.rs) | +| **mint-to-checked** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/mint_to_checked.rs) | +| **revoke** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/revoke.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/revoke.rs) | +| **spl-to-light** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/spl_to_light_transfer.rs) | +| **thaw** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/thaw.rs) | +| **transfer-checked** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/transfer_checked.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/transfer_checked.rs) | +| **transfer-interface** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/transfer_interface.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/transfer_interface.rs) | +| **unwrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/unwrap.rs) | — | +| **wrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/wrap.rs) | — | \ No newline at end of file diff --git a/light-token/examples/program.mdx b/light-token/examples/program.mdx new file mode 100644 index 00000000..b936715d --- /dev/null +++ b/light-token/examples/program.mdx @@ -0,0 +1,23 @@ +--- +title: "Program examples" +sidebarTitle: "Program" +description: "Anchor and native Rust program examples for light-token CPI." +--- + +Find all examples on Github: [examples-light-token](https://github.com/Lightprotocol/examples-light-token) + +| | | | +|---------|--------|-------------| +| **approve** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/approve) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/approve.rs) | +| **burn** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/burn) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/burn.rs) | +| **close** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/close) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/close.rs) | +| **create-ata** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-ata) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/create_ata.rs) | +| **create-mint** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-mint) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/create_mint.rs) | +| **create-token-account** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-token-account) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/create_token_account.rs) | +| **freeze** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/freeze) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/freeze.rs) | +| **mint-to** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/mint-to) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/mint_to.rs) | +| **mint-to-checked** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/mint-to-checked) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/mint_to_checked.rs) | +| **revoke** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/revoke) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/revoke.rs) | +| **thaw** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/thaw) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/thaw.rs) | +| **transfer-checked** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/transfer-checked) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/transfer_checked.rs) | +| **transfer-interface** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/transfer-interface) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/transfer_interface.rs) | diff --git a/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx deleted file mode 100644 index 84aa2351..00000000 --- a/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx +++ /dev/null @@ -1,212 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; -use light_sdk::derive_light_cpi_signer; -use light_sdk_macros::{light_program, LightAccounts}; -use light_sdk_types::{CpiSigner, LIGHT_TOKEN_PROGRAM_ID}; -use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR}; - -declare_id!("8yizawASBjGGCPnqtMsvBscwPUgDuNjJA93QBdm3PZY8"); - -pub const LIGHT_CPI_SIGNER: CpiSigner = - derive_light_cpi_signer!("8yizawASBjGGCPnqtMsvBscwPUgDuNjJA93QBdm3PZY8"); - -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct CreateAtaParams { - pub create_accounts_proof: CreateAccountsProof, - pub ata_bump: u8, -} - -#[light_program] -#[program] -pub mod light_token_anchor_create_ata_macro { - use super::*; - - #[allow(unused_variables)] - pub fn create_ata<'info>( - ctx: Context<'_, '_, '_, 'info, CreateAta<'info>>, - params: CreateAtaParams, - ) -> Result<()> { - Ok(()) - } -} - -#[derive(Accounts, LightAccounts)] -#[instruction(params: CreateAtaParams)] -pub struct CreateAta<'info> { - #[account(mut)] - pub payer: Signer<'info>, - - /// CHECK: Validated by light-token CPI - pub mint: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - pub owner: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - #[account(mut)] - #[light_account(init, associated_token::authority = owner, associated_token::mint = mint, associated_token::bump = params.ata_bump)] - pub ata: UncheckedAccount<'info>, - - /// CHECK: Validated by light-token CPI - #[account(address = COMPRESSIBLE_CONFIG_V1)] - pub light_token_compressible_config: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] - pub light_token_rent_sponsor: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - #[account(address = LIGHT_TOKEN_PROGRAM_ID.into())] - pub light_token_program: AccountInfo<'info>, - - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::indexer::{AddressWithTree, Indexer}; -use light_client::interface::{get_create_accounts_proof, InitializeRentFreeConfig}; -use light_program_test::program_test::{setup_mock_program_data, LightProgramTest}; -use light_program_test::{ProgramTestConfig, Rpc}; -use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; -use light_token::instruction::{ - CreateMint, CreateMintParams, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR, - derive_mint_compressed_address, derive_token_ata, find_mint_address, -}; -use light_token_anchor_create_ata_macro::{CreateAtaParams, ID}; -use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer, system_program}; - -async fn setup_create_mint( - rpc: &mut (impl Rpc + Indexer), - payer: &Keypair, - mint_authority: solana_sdk::pubkey::Pubkey, - decimals: u8, -) -> (solana_sdk::pubkey::Pubkey, Keypair) { - let mint_seed = Keypair::new(); - let address_tree = rpc.get_address_tree_v2(); - let output_queue = rpc.get_random_state_tree_info().unwrap().queue; - - let compression_address = - derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); - let (mint, bump) = find_mint_address(&mint_seed.pubkey()); - - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - let params = CreateMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority, - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint, - bump, - freeze_authority: None, - extensions: None, - rent_payment: 16, - write_top_up: 766, - }; - - let ix = CreateMint::new( - params, - mint_seed.pubkey(), - payer.pubkey(), - address_tree.tree, - output_queue, - ) - .instruction() - .unwrap(); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer, &mint_seed]) - .await - .unwrap(); - - (mint, mint_seed) -} - -#[tokio::test] -async fn test_create_ata() { - let config = - ProgramTestConfig::new_v2(true, Some(vec![("light_token_anchor_create_ata_macro", ID)])) - .with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &ID); - - let (init_config_ix, _) = InitializeRentFreeConfig::new( - &ID, - &payer.pubkey(), - &program_data_pda, - RENT_SPONSOR, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let (mint_pda, _) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; - let (ata, ata_bump) = derive_token_ata(&payer.pubkey(), &mint_pda); - - let proof_result = get_create_accounts_proof(&rpc, &ID, vec![]).await.unwrap(); - - let accounts = light_token_anchor_create_ata_macro::accounts::CreateAta { - payer: payer.pubkey(), - mint: mint_pda, - owner: payer.pubkey(), - ata, - light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, - light_token_rent_sponsor: RENT_SPONSOR, - light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), - system_program: system_program::ID, - }; - - let instruction_data = light_token_anchor_create_ata_macro::instruction::CreateAta { - params: CreateAtaParams { - create_accounts_proof: proof_result.create_accounts_proof, - ata_bump, - }, - }; - - let ix = Instruction { - program_id: ID, - accounts: [accounts.to_account_metas(None), proof_result.remaining_accounts].concat(), - data: instruction_data.data(), - }; - - let sig = rpc - .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); - - let ata_account = rpc.get_account(ata).await.unwrap().unwrap(); - - use light_token_interface::state::Token; - let token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]).unwrap(); - - assert_eq!(token.owner, payer.pubkey().to_bytes()); - assert_eq!(token.mint, mint_pda.to_bytes()); - assert_eq!(token.amount, 0); -} -``` - diff --git a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx deleted file mode 100644 index c97eec30..00000000 --- a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx +++ /dev/null @@ -1,185 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; -use light_sdk::derive_light_cpi_signer; -use light_sdk_macros::{light_program, LightAccounts}; -use light_sdk_types::CpiSigner; - -declare_id!("JD5N2ZwvCpdcJguF2zRHgpPdrBXf3R9kmhFC7aLALB6j"); - -pub const LIGHT_CPI_SIGNER: CpiSigner = - derive_light_cpi_signer!("JD5N2ZwvCpdcJguF2zRHgpPdrBXf3R9kmhFC7aLALB6j"); - -pub const MINT_SIGNER_SEED: &[u8] = b"mint_signer"; - -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct CreateMintParams { - pub create_accounts_proof: CreateAccountsProof, - pub mint_signer_bump: u8, -} - -#[light_program] -#[program] -pub mod light_token_anchor_create_mint_macro { - use super::*; - - #[allow(unused_variables)] - pub fn create_mint<'info>( - ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>, - params: CreateMintParams, - ) -> Result<()> { - Ok(()) - } -} - -#[derive(Accounts, LightAccounts)] -#[instruction(params: CreateMintParams)] -pub struct CreateMint<'info> { - #[account(mut)] - pub fee_payer: Signer<'info>, - - /// CHECK: Used for mint_signer PDA derivation only - pub authority: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - #[account( - seeds = [MINT_SIGNER_SEED, authority.key().as_ref()], - bump, - )] - pub mint_signer: UncheckedAccount<'info>, - - /// CHECK: Validated by light-token CPI - #[account(mut)] - #[light_account(init, - mint::signer = mint_signer, - mint::authority = fee_payer, - mint::decimals = 9, - mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()], - mint::bump = params.mint_signer_bump - )] - pub mint: UncheckedAccount<'info>, - - /// CHECK: Validated by light-token CPI - pub compression_config: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - pub light_token_compressible_config: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub rent_sponsor: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - pub light_token_program: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - pub light_token_cpi_authority: AccountInfo<'info>, - - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::interface::{ - get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, -}; -use light_program_test::{ - program_test::{setup_mock_program_data, LightProgramTest}, - ProgramTestConfig, Rpc, -}; -use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; -use light_token::instruction::{find_mint_address, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR}; -use light_token_anchor_create_mint_macro::{CreateMintParams, MINT_SIGNER_SEED, ID}; -use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer}; - -#[tokio::test] -async fn test_create_mint() { - let config = ProgramTestConfig::new_v2( - true, - Some(vec![("light_token_anchor_create_mint_macro", ID)]), - ) - .with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &ID); - - let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( - &ID, - &payer.pubkey(), - &program_data_pda, - RENT_SPONSOR, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let authority = Keypair::new(); - - let (mint_signer_pda, mint_signer_bump) = - Pubkey::find_program_address(&[MINT_SIGNER_SEED, authority.pubkey().as_ref()], &ID); - - let (mint_pda, _) = find_mint_address(&mint_signer_pda); - - let proof_result = get_create_accounts_proof( - &rpc, - &ID, - vec![CreateAccountsProofInput::mint(mint_signer_pda)], - ) - .await - .unwrap(); - - let accounts = light_token_anchor_create_mint_macro::accounts::CreateMint { - fee_payer: payer.pubkey(), - authority: authority.pubkey(), - mint_signer: mint_signer_pda, - mint: mint_pda, - compression_config: config_pda, - light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, - rent_sponsor: RENT_SPONSOR, - light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), - light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), - system_program: solana_sdk::system_program::ID, - }; - - let instruction_data = light_token_anchor_create_mint_macro::instruction::CreateMint { - params: CreateMintParams { - create_accounts_proof: proof_result.create_accounts_proof, - mint_signer_bump, - }, - }; - - let ix = Instruction { - program_id: ID, - accounts: [accounts.to_account_metas(None), proof_result.remaining_accounts].concat(), - data: instruction_data.data(), - }; - - let sig = rpc - .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); - - let mint_account = rpc.get_account(mint_pda).await.unwrap().unwrap(); - - use light_token_interface::state::Mint; - let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..]).unwrap(); - - assert_eq!(mint.base.decimals, 9); - assert_eq!( - mint.base.mint_authority, - Some(payer.pubkey().to_bytes().into()) - ); -} -``` - diff --git a/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx deleted file mode 100644 index b895364c..00000000 --- a/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx +++ /dev/null @@ -1,240 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_compressible::CreateAccountsProof; -use light_sdk::derive_light_cpi_signer; -use light_sdk_macros::{light_program, LightAccounts}; -use light_sdk_types::CpiSigner; -use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR}; - -declare_id!("FCczCsMBwaqUSqunWPGQjNP55EeqYrkSwYdLmztgmvKn"); - -pub const LIGHT_CPI_SIGNER: CpiSigner = - derive_light_cpi_signer!("FCczCsMBwaqUSqunWPGQjNP55EeqYrkSwYdLmztgmvKn"); - -pub const TOKEN_AUTH_SEED: &[u8] = b"token_auth"; -pub const TOKEN_ACCOUNT_SEED: &[u8] = b"token_account"; - -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct CreateTokenAccountParams { - pub create_accounts_proof: CreateAccountsProof, - pub account_bump: u8, -} - -#[light_program] -#[program] -pub mod light_token_anchor_create_token_account_macro { - use super::*; - - #[allow(unused_variables)] - pub fn create_token_account<'info>( - ctx: Context<'_, '_, '_, 'info, CreateTokenAccountAccounts<'info>>, - params: CreateTokenAccountParams, - ) -> Result<()> { - Ok(()) - } -} - -#[derive(Accounts, LightAccounts)] -#[instruction(params: CreateTokenAccountParams)] -pub struct CreateTokenAccountAccounts<'info> { - #[account(mut)] - pub payer: Signer<'info>, - - /// CHECK: Validated by light-token CPI - pub mint: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - #[account( - seeds = [TOKEN_AUTH_SEED], - bump, - )] - pub token_authority: UncheckedAccount<'info>, - - /// CHECK: Validated by light-token CPI - #[account( - mut, - seeds = [TOKEN_ACCOUNT_SEED, mint.key().as_ref()], - bump, - )] - #[light_account(init, token, - token::authority = [TOKEN_ACCOUNT_SEED, self.mint.key()], - token::mint = mint, - token::owner = token_authority, - token::bump = params.account_bump - )] - pub account: UncheckedAccount<'info>, - - /// CHECK: Validated by light-token CPI - #[account(address = COMPRESSIBLE_CONFIG_V1)] - pub light_token_compressible_config: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] - pub light_token_rent_sponsor: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - pub light_token_cpi_authority: AccountInfo<'info>, - - /// CHECK: Validated by light-token CPI - pub light_token_program: AccountInfo<'info>, - - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::indexer::AddressWithTree; -use light_client::interface::{get_create_accounts_proof, InitializeRentFreeConfig}; -use light_program_test::program_test::{setup_mock_program_data, LightProgramTest}; -use light_program_test::{Indexer, ProgramTestConfig, Rpc}; -use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; -use light_token::instruction::{ - CreateMint, CreateMintParams, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR, - derive_mint_compressed_address, find_mint_address, -}; -use light_token_anchor_create_token_account_macro::{ - CreateTokenAccountParams, TOKEN_AUTH_SEED, TOKEN_ACCOUNT_SEED, ID, -}; -use solana_sdk::{instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer}; - -async fn setup_create_mint( - rpc: &mut (impl Rpc + Indexer), - payer: &Keypair, - mint_authority: Pubkey, - decimals: u8, -) -> (Pubkey, Keypair) { - let mint_seed = Keypair::new(); - let address_tree = rpc.get_address_tree_v2(); - let output_queue = rpc.get_random_state_tree_info().unwrap().queue; - - let compression_address = - derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); - let (mint, bump) = find_mint_address(&mint_seed.pubkey()); - - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - let params = CreateMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority, - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint, - bump, - freeze_authority: None, - extensions: None, - rent_payment: 16, - write_top_up: 766, - }; - - let ix = CreateMint::new( - params, - mint_seed.pubkey(), - payer.pubkey(), - address_tree.tree, - output_queue, - ) - .instruction() - .unwrap(); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[payer, &mint_seed]) - .await - .unwrap(); - - (mint, mint_seed) -} - -#[tokio::test] -async fn test_create_token_account() { - let config = ProgramTestConfig::new_v2( - true, - Some(vec![("light_token_anchor_create_token_account_macro", ID)]), - ) - .with_light_protocol_events(); - - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &ID); - - let (init_config_ix, _) = InitializeRentFreeConfig::new( - &ID, - &payer.pubkey(), - &program_data_pda, - RENT_SPONSOR, - payer.pubkey(), - ) - .build(); - - rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let (mint_pda, _) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; - - let (token_authority, _) = Pubkey::find_program_address(&[TOKEN_AUTH_SEED], &ID); - let (account_pda, account_bump) = - Pubkey::find_program_address(&[TOKEN_ACCOUNT_SEED, mint_pda.as_ref()], &ID); - - let proof_result = get_create_accounts_proof(&rpc, &ID, vec![]).await.unwrap(); - - let accounts = light_token_anchor_create_token_account_macro::accounts::CreateTokenAccountAccounts { - payer: payer.pubkey(), - mint: mint_pda, - token_authority, - account: account_pda, - light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, - light_token_rent_sponsor: RENT_SPONSOR, - light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), - light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), - system_program: solana_sdk::system_program::ID, - }; - - let instruction_data = - light_token_anchor_create_token_account_macro::instruction::CreateTokenAccount { - params: CreateTokenAccountParams { - create_accounts_proof: proof_result.create_accounts_proof, - account_bump, - }, - }; - - let ix = Instruction { - program_id: ID, - accounts: [accounts.to_account_metas(None), proof_result.remaining_accounts].concat(), - data: instruction_data.data(), - }; - - let sig = rpc - .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); - - let token_account = rpc.get_account(account_pda).await.unwrap().unwrap(); - - use light_token_interface::state::Token; - let token: Token = - borsh::BorshDeserialize::deserialize(&mut &token_account.data[..]).unwrap(); - - assert_eq!(token.owner, token_authority.to_bytes()); - assert_eq!(token.mint, mint_pda.to_bytes()); - assert_eq!(token.amount, 0); -} -``` - diff --git a/snippets/overview-tables/cookbook-guides-table.mdx b/snippets/overview-tables/cookbook-guides-table.mdx index bcafa882..decb9cf0 100644 --- a/snippets/overview-tables/cookbook-guides-table.mdx +++ b/snippets/overview-tables/cookbook-guides-table.mdx @@ -46,11 +46,41 @@ Transfer between light-token and SPL accounts + + + Transfer Checked + + Transfer with decimals verification + + + + Burn + + Burn tokens from light-token accounts + + + + Freeze & Thaw + + Freeze and thaw light-token accounts + + + + Approve & Revoke + + Delegate and revoke token authority + Wrap & Unwrap Convert between SPL/T22 and light-token + + + Load ATA + + Load cold light-token accounts to hot balance for transfers in one instruction + From 263cb99bffc6855051b8368e47155ed2d57a0127 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Tue, 27 Jan 2026 02:00:51 +0000 Subject: [PATCH 04/21] update rust client Co-Authored-By: Claude Opus 4.5 --- .../rust-client/approve-action.mdx | 8 +++---- .../rust-client/approve-instruction.mdx | 12 +++++----- .../rust-client/revoke-action.mdx | 8 +++---- .../rust-client/revoke-instruction.mdx | 12 +++++----- .../burn/rust-client/instruction.mdx | 12 +++++----- .../rust-client/instruction.mdx | 14 +++++------ .../create-ata/rust-client/action.mdx | 6 ++--- .../create-ata/rust-client/instruction.mdx | 12 +++++----- .../create-mint/rust-client/instruction.mdx | 9 +++---- .../rust-client/instruction.mdx | 6 ++--- .../rust-client/freeze-instruction.mdx | 12 +++++----- .../rust-client/thaw-instruction.mdx | 12 +++++----- .../mint-to/rust-client/action.mdx | 8 +++---- .../mint-to/rust-client/instruction.mdx | 16 ++++++------- .../transfer-checked/rust-client/action.mdx | 12 +++++----- .../rust-client/instruction.mdx | 22 ++++++++--------- .../transfer-interface/rust-client/action.mdx | 12 +++++----- .../rust-client/instruction.mdx | 24 +++++++++---------- .../light-token/unwrap/rust-client/action.mdx | 14 +++++------ .../light-token/wrap/rust-client/action.mdx | 14 +++++------ 20 files changed, 123 insertions(+), 122 deletions(-) diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx index 5d56d8df..26156d6c 100644 --- a/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx @@ -7,18 +7,18 @@ use solana_sdk::{signature::Keypair, signer::Signer}; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint and ATA with tokens + // Setup creates mint and associated token account with tokens let SetupContext { mut rpc, payer, - ata, + associated_token_account, .. } = setup().await; let delegate = Keypair::new(); let sig = Approve { - token_account: ata, + token_account: associated_token_account, delegate: delegate.pubkey(), amount: 500_000, owner: Some(payer.pubkey()), @@ -26,7 +26,7 @@ async fn main() -> Result<(), Box> { .execute(&mut rpc, &payer) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("Delegate: {:?} Tx: {sig}", token.delegate); diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx index 07b994ed..3a60f85c 100644 --- a/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx @@ -7,19 +7,19 @@ use solana_sdk::{signature::Keypair, signer::Signer}; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint and ATA with tokens + // Setup creates mint and associated token account with tokens let SetupContext { mut rpc, payer, - ata, + associated_token_account, .. } = setup().await; let delegate = Keypair::new(); let delegate_amount = 500_000u64; - let approve_ix = Approve { - token_account: ata, + let approve_instruction = Approve { + token_account: associated_token_account, delegate: delegate.pubkey(), owner: payer.pubkey(), amount: delegate_amount, @@ -27,10 +27,10 @@ async fn main() -> Result<(), Box> { .instruction()?; let sig = rpc - .create_and_send_transaction(&[approve_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[approve_instruction], &payer.pubkey(), &[&payer]) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("Delegate: {:?} Tx: {sig}", token.delegate); diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx index 5f449840..f1fe2d58 100644 --- a/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx @@ -7,22 +7,22 @@ use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint and ATA with approved delegate + // Setup creates mint and associated token account with approved delegate let SetupContext { mut rpc, payer, - ata, + associated_token_account, .. } = setup().await; let sig = Revoke { - token_account: ata, + token_account: associated_token_account, owner: Some(payer.pubkey()), } .execute(&mut rpc, &payer) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("Delegate: {:?} Tx: {sig}", token.delegate); diff --git a/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx index cc71b60c..d18bc441 100644 --- a/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx +++ b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx @@ -7,25 +7,25 @@ use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint, ATA with tokens, and approves delegate + // Setup creates mint, associated token account with tokens, and approves delegate let SetupContext { mut rpc, payer, - ata, + associated_token_account, .. } = setup().await; - let revoke_ix = Revoke { - token_account: ata, + let revoke_instruction = Revoke { + token_account: associated_token_account, owner: payer.pubkey(), } .instruction()?; let sig = rpc - .create_and_send_transaction(&[revoke_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[revoke_instruction], &payer.pubkey(), &[&payer]) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("Delegate: {:?} Tx: {sig}", token.delegate); diff --git a/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx b/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx index 0f1d0f23..a43ab642 100644 --- a/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx +++ b/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx @@ -7,19 +7,19 @@ use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint and ATA with tokens + // Setup creates mint and associated token account with tokens let SetupContext { mut rpc, payer, mint, - ata, + associated_token_account, .. } = setup().await; let burn_amount = 400_000u64; - let burn_ix = Burn { - source: ata, + let burn_instruction = Burn { + source: associated_token_account, mint, amount: burn_amount, authority: payer.pubkey(), @@ -29,10 +29,10 @@ async fn main() -> Result<(), Box> { .instruction()?; let sig = rpc - .create_and_send_transaction(&[burn_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[burn_instruction], &payer.pubkey(), &[&payer]) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("Balance: {} Tx: {sig}", token.amount); diff --git a/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx b/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx index 21a034e2..8a0f3e36 100644 --- a/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx +++ b/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx @@ -1,26 +1,26 @@ ```rust use light_client::rpc::Rpc; use light_token::instruction::{CloseAccount, LIGHT_TOKEN_PROGRAM_ID}; -use rust_client::{setup_empty_ata, SetupContext}; +use rust_client::{setup_empty_associated_token_account, SetupContext}; use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint and empty ATA (must be empty to close). + // Setup creates mint and empty associated token account (must be empty to close). let SetupContext { mut rpc, payer, - ata, + associated_token_account, .. - } = setup_empty_ata().await; - let close_ix = CloseAccount::new(LIGHT_TOKEN_PROGRAM_ID, ata, payer.pubkey(), payer.pubkey()) + } = setup_empty_associated_token_account().await; + let close_instruction = CloseAccount::new(LIGHT_TOKEN_PROGRAM_ID, associated_token_account, payer.pubkey(), payer.pubkey()) .instruction()?; let sig = rpc - .create_and_send_transaction(&[close_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[close_instruction], &payer.pubkey(), &[&payer]) .await?; - let account = rpc.get_account(ata).await?; + let account = rpc.get_account(associated_token_account).await?; println!("Closed: {} Tx: {sig}", account.is_none()); Ok(()) diff --git a/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx b/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx index e7b61cfb..46e7cb24 100644 --- a/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx @@ -17,8 +17,8 @@ async fn main() -> Result<(), Box> { .execute(&mut rpc, &payer, &mint_seed) .await?; - // Create ATA - let ata = CreateAta { + // Create associated token account + let associated_token_account = CreateAta { mint: mint_result.mint, owner: payer.pubkey(), idempotent: true, @@ -26,7 +26,7 @@ async fn main() -> Result<(), Box> { .execute(&mut rpc, &payer) .await?; - println!("ATA: {ata}"); + println!("ATA: {associated_token_account}"); Ok(()) } diff --git a/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx b/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx index 097b0d33..4d58d882 100644 --- a/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx +++ b/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx @@ -6,7 +6,7 @@ use solana_sdk::{signature::Keypair, signer::Signer}; #[tokio::main] async fn main() -> Result<(), Box> { - // You can use light, spl, t22 mints to create a light token ATA. + // You can use Light, SPL, or Token-2022 mints to create a Light associated token account. let SplMintContext { mut rpc, payer, @@ -15,16 +15,16 @@ async fn main() -> Result<(), Box> { let owner = Keypair::new(); - let create_ata_ix = + let create_ata_instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), owner.pubkey(), mint).instruction()?; let sig = rpc - .create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[create_ata_instruction], &payer.pubkey(), &[&payer]) .await?; - let ata = get_associated_token_address(&owner.pubkey(), &mint); - let data = rpc.get_account(ata).await?; - println!("ATA: {ata} exists: {} Tx: {sig}", data.is_some()); + let associated_token_account = get_associated_token_address(&owner.pubkey(), &mint); + let data = rpc.get_account(associated_token_account).await?; + println!("ATA: {associated_token_account} exists: {} Tx: {sig}", data.is_some()); Ok(()) } diff --git a/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx b/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx index e149c64e..f56e0885 100644 --- a/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx +++ b/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx @@ -6,6 +6,7 @@ use light_client::{ use light_program_test::{LightProgramTest, ProgramTestConfig}; use light_token::instruction::{ derive_mint_compressed_address, find_mint_address, CreateMint, CreateMintParams, + DEFAULT_RENT_PAYMENT, DEFAULT_WRITE_TOP_UP, }; use light_token_interface::{ instructions::extensions::{ @@ -70,12 +71,12 @@ async fn main() -> Result<(), Box> { }]), }, )]), - rent_payment: 16, // ~24 hours rent - write_top_up: 766, // ~3 hours rent per write + rent_payment: DEFAULT_RENT_PAYMENT, // 24h of rent + write_top_up: DEFAULT_WRITE_TOP_UP, // 3h of rent }; // Build and send instruction (mint_seed must sign) - let create_mint_ix = CreateMint::new( + let create_mint_instruction = CreateMint::new( params, mint_seed.pubkey(), payer.pubkey(), @@ -85,7 +86,7 @@ async fn main() -> Result<(), Box> { .instruction()?; let sig = rpc - .create_and_send_transaction(&[create_mint_ix], &payer.pubkey(), &[&payer, &mint_seed]) + .create_and_send_transaction(&[create_mint_instruction], &payer.pubkey(), &[&payer, &mint_seed]) .await?; let data = rpc.get_account(mint).await?; diff --git a/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx b/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx index db612b8c..2795ac32 100644 --- a/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx +++ b/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx @@ -7,7 +7,7 @@ use solana_sdk::{signature::Keypair, signer::Signer}; #[tokio::main] async fn main() -> Result<(), Box> { // Setup creates mint - // You can use light, spl, t22 mints to create a light token account. + // You can use Light, SPL, or Token-2022 mints to create a light token account. let SplMintContext { mut rpc, payer, @@ -16,12 +16,12 @@ async fn main() -> Result<(), Box> { let account = Keypair::new(); - let create_account_ix = + let create_token_account_instruction = CreateTokenAccount::new(payer.pubkey(), account.pubkey(), mint, payer.pubkey()) .instruction()?; let sig = rpc - .create_and_send_transaction(&[create_account_ix], &payer.pubkey(), &[&payer, &account]) + .create_and_send_transaction(&[create_token_account_instruction], &payer.pubkey(), &[&payer, &account]) .await?; let data = rpc.get_account(account.pubkey()).await?; diff --git a/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx b/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx index e7d719fe..4b17db45 100644 --- a/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx +++ b/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx @@ -7,28 +7,28 @@ use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint, ATA with tokens, and approves delegate + // Setup creates mint, associated token account with tokens, and approves delegate let SetupContext { mut rpc, payer, mint, - ata, + associated_token_account, .. } = setup().await; // freeze_authority must match what was set during mint creation. - let freeze_ix = Freeze { - token_account: ata, + let freeze_instruction = Freeze { + token_account: associated_token_account, mint, freeze_authority: payer.pubkey(), } .instruction()?; let sig = rpc - .create_and_send_transaction(&[freeze_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[freeze_instruction], &payer.pubkey(), &[&payer]) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("State: {:?} Tx: {sig}", token.state); diff --git a/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx b/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx index 3e31699a..7d7eb8f9 100644 --- a/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx +++ b/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx @@ -7,27 +7,27 @@ use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint, ATA with tokens, and freezes account + // Setup creates mint, associated token account with tokens, and freezes account let SetupContext { mut rpc, payer, mint, - ata, + associated_token_account, .. } = setup_frozen().await; - let thaw_ix = Thaw { - token_account: ata, + let thaw_instruction = Thaw { + token_account: associated_token_account, mint, freeze_authority: payer.pubkey(), } .instruction()?; let sig = rpc - .create_and_send_transaction(&[thaw_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[thaw_instruction], &payer.pubkey(), &[&payer]) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("State: {:?} Tx: {sig}", token.state); diff --git a/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx b/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx index 86abae0b..58a4a0e1 100644 --- a/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx @@ -19,8 +19,8 @@ async fn main() -> Result<(), Box> { .execute(&mut rpc, &payer, &mint_seed) .await?; - // Create ATA - let ata = CreateAta { + // Create associated token account + let associated_token_account = CreateAta { mint: mint_result.mint, owner: payer.pubkey(), idempotent: true, @@ -31,14 +31,14 @@ async fn main() -> Result<(), Box> { // Mint tokens let sig = MintTo { mint: mint_result.mint, - destination: ata, + destination: associated_token_account, amount: 1_000_000, max_top_up: None, } .execute(&mut rpc, &payer, &mint_seed) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("Balance: {} Tx: {sig}", token.amount); diff --git a/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx b/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx index 4b2ef381..516378bf 100644 --- a/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx +++ b/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx @@ -2,25 +2,25 @@ use borsh::BorshDeserialize; use light_client::rpc::Rpc; use light_token::instruction::MintTo; -use rust_client::{setup_empty_ata, SetupContext}; +use rust_client::{setup_empty_associated_token_account, SetupContext}; use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint and empty ATA + // Setup creates mint and empty associated token account let SetupContext { mut rpc, payer, mint, - ata, + associated_token_account, .. - } = setup_empty_ata().await; + } = setup_empty_associated_token_account().await; let mint_amount = 1_000_000_000u64; - let mint_to_ix = MintTo { + let mint_to_instruction = MintTo { mint, - destination: ata, + destination: associated_token_account, amount: mint_amount, authority: payer.pubkey(), max_top_up: None, @@ -29,10 +29,10 @@ async fn main() -> Result<(), Box> { .instruction()?; let sig = rpc - .create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[mint_to_instruction], &payer.pubkey(), &[&payer]) .await?; - let data = rpc.get_account(ata).await?.ok_or("Account not found")?; + let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; println!("Balance: {} Tx: {sig}", token.amount); diff --git a/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx b/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx index 49dc7814..229ce739 100644 --- a/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx @@ -11,14 +11,14 @@ async fn main() -> Result<(), Box> { mut rpc, payer, mint, - ata, + associated_token_account, decimals, .. } = setup().await; - // Create recipient ATA + // Create recipient associated token account let recipient = Keypair::new(); - let recipient_ata = CreateAta { + let recipient_associated_token_account = CreateAta { mint, owner: recipient.pubkey(), idempotent: true, @@ -30,9 +30,9 @@ async fn main() -> Result<(), Box> { // Only use for Light->Light transfers. // Use TransferInterface for all other transfers (Light, SPL or Token-2022). let sig = TransferChecked { - source: ata, + source: associated_token_account, mint, - destination: recipient_ata, + destination: recipient_associated_token_account, amount: 1000, decimals, max_top_up: None, @@ -41,7 +41,7 @@ async fn main() -> Result<(), Box> { .await?; let data = rpc - .get_account(recipient_ata) + .get_account(recipient_associated_token_account) .await? .ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; diff --git a/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx b/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx index 267a9c1c..0de24f35 100644 --- a/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx +++ b/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx @@ -9,35 +9,35 @@ use solana_sdk::{signature::Keypair, signer::Signer}; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates mint and ATA with tokens + // Setup creates mint and associated token account with tokens let SetupContext { mut rpc, payer, mint, - ata, + associated_token_account, decimals, .. } = setup().await; let transfer_amount = 400_000u64; - // Create recipient ATA + // Create recipient associated token account let recipient = Keypair::new(); - let recipient_ata = get_associated_token_address(&recipient.pubkey(), &mint); + let recipient_associated_token_account = get_associated_token_address(&recipient.pubkey(), &mint); - let create_ata_ix = CreateAssociatedTokenAccount::new(payer.pubkey(), recipient.pubkey(), mint) + let create_ata_instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), recipient.pubkey(), mint) .instruction()?; - rpc.create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[&payer]) + rpc.create_and_send_transaction(&[create_ata_instruction], &payer.pubkey(), &[&payer]) .await?; // TransferChecked validates decimals match the mint's decimals // Only use for Light->Light transfers. // Use TransferInterface for all other transfers (Light, SPL or Token-2022). - let transfer_ix = TransferChecked { - source: ata, + let transfer_instruction = TransferChecked { + source: associated_token_account, mint, - destination: recipient_ata, + destination: recipient_associated_token_account, amount: transfer_amount, decimals, authority: payer.pubkey(), @@ -47,11 +47,11 @@ async fn main() -> Result<(), Box> { .instruction()?; let sig = rpc - .create_and_send_transaction(&[transfer_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[transfer_instruction], &payer.pubkey(), &[&payer]) .await?; let data = rpc - .get_account(recipient_ata) + .get_account(recipient_associated_token_account) .await? .ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; diff --git a/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx b/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx index 70da23d0..a6f21205 100644 --- a/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx @@ -11,14 +11,14 @@ async fn main() -> Result<(), Box> { mut rpc, payer, mint, - ata, + associated_token_account, decimals, .. } = setup().await; - // Create recipient ATA + // Create recipient associated token account let recipient = Keypair::new(); - let recipient_ata = CreateAta { + let recipient_associated_token_account = CreateAta { mint, owner: recipient.pubkey(), idempotent: true, @@ -28,9 +28,9 @@ async fn main() -> Result<(), Box> { // Transfers tokens between token accounts (SPL, Token-2022, or Light) in a single call. let sig = TransferInterface { - source: ata, + source: associated_token_account, mint, - destination: recipient_ata, + destination: recipient_associated_token_account, amount: 1000, decimals, ..Default::default() @@ -39,7 +39,7 @@ async fn main() -> Result<(), Box> { .await?; let data = rpc - .get_account(recipient_ata) + .get_account(recipient_associated_token_account) .await? .ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; diff --git a/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx b/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx index ad17b90e..770cfc43 100644 --- a/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx +++ b/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx @@ -10,7 +10,7 @@ use light_token::{ }, spl_interface::find_spl_interface_pda_with_index, }; -use rust_client::{setup_spl_ata, setup_spl_mint}; +use rust_client::{setup_spl_associated_token_account, setup_spl_mint}; use solana_sdk::signer::Signer; #[tokio::main] @@ -21,18 +21,18 @@ async fn main() -> Result<(), Box> { let decimals = 2u8; let amount = 10_000u64; - // Setup creates mint, mints tokens and creates SPL ATA + // Setup creates mint, mints tokens and creates SPL associated token account let mint = setup_spl_mint(&mut rpc, &payer, decimals).await; - let spl_ata = setup_spl_ata(&mut rpc, &payer, &mint, &payer.pubkey(), amount).await; + let spl_associated_token_account = setup_spl_associated_token_account(&mut rpc, &payer, &mint, &payer.pubkey(), amount).await; let (interface_pda, interface_bump) = find_spl_interface_pda_with_index(&mint, 0, false); - // Create Light ATA - let light_ata = get_associated_token_address(&payer.pubkey(), &mint); + // Create Light associated token account + let light_associated_token_account = get_associated_token_address(&payer.pubkey(), &mint); - let create_ata_ix = + let create_ata_instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint).instruction()?; - rpc.create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[&payer]) + rpc.create_and_send_transaction(&[create_ata_instruction], &payer.pubkey(), &[&payer]) .await?; // SPL interface PDA for Mint (holds SPL tokens when transferred to Light Token) @@ -44,9 +44,9 @@ async fn main() -> Result<(), Box> { }; // Transfers tokens between token accounts (SPL, Token-2022, or Light) in a single call. - let transfer_interface_ix = TransferInterface { - source: spl_ata, - destination: light_ata, + let transfer_instruction = TransferInterface { + source: spl_associated_token_account, + destination: light_associated_token_account, amount, decimals, authority: payer.pubkey(), @@ -59,11 +59,11 @@ async fn main() -> Result<(), Box> { .instruction()?; let sig = rpc - .create_and_send_transaction(&[transfer_interface_ix], &payer.pubkey(), &[&payer]) + .create_and_send_transaction(&[transfer_instruction], &payer.pubkey(), &[&payer]) .await?; let data = rpc - .get_account(light_ata) + .get_account(light_associated_token_account) .await? .ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; diff --git a/snippets/code-snippets/light-token/unwrap/rust-client/action.mdx b/snippets/code-snippets/light-token/unwrap/rust-client/action.mdx index ed70caaf..79f3bf78 100644 --- a/snippets/code-snippets/light-token/unwrap/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/unwrap/rust-client/action.mdx @@ -7,20 +7,20 @@ use solana_sdk::program_pack::Pack; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates Light ATA with tokens and empty SPL ATA + // Setup creates Light associated token account with tokens and empty SPL associated token account let UnwrapContext { mut rpc, payer, mint, - destination_ata: destination, - light_ata, + destination_associated_token_account, + light_associated_token_account, decimals, } = setup_for_unwrap().await; - // Unwrap tokens from Light Token ATA to SPL ATA + // Unwrap tokens from Light Token associated token account to SPL associated token account let sig = Unwrap { - source: light_ata, - destination_spl_ata: destination, + source: light_associated_token_account, + destination_spl_ata: destination_associated_token_account, mint, amount: 500_000, decimals, @@ -29,7 +29,7 @@ async fn main() -> Result<(), Box> { .await?; let data = rpc - .get_account(destination) + .get_account(destination_associated_token_account) .await? .ok_or("Account not found")?; let token = SplAccount::unpack(&data.data)?; diff --git a/snippets/code-snippets/light-token/wrap/rust-client/action.mdx b/snippets/code-snippets/light-token/wrap/rust-client/action.mdx index 1b8fce16..5b235784 100644 --- a/snippets/code-snippets/light-token/wrap/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/wrap/rust-client/action.mdx @@ -6,20 +6,20 @@ use rust_client::{setup_for_wrap, WrapContext}; #[tokio::main] async fn main() -> Result<(), Box> { - // Setup creates SPL ATA with tokens and empty Light ATA + // Setup creates SPL associated token account with tokens and empty Light associated token account let WrapContext { mut rpc, payer, mint, - source_ata, - light_ata, + source_associated_token_account, + light_associated_token_account, decimals, } = setup_for_wrap().await; - // Wrap tokens from SPL ATA to Light Token ATA + // Wrap tokens from SPL associated token account to Light Token associated token account let sig = Wrap { - source_spl_ata: source_ata, - destination: light_ata, + source_spl_ata: source_associated_token_account, + destination: light_associated_token_account, mint, amount: 500_000, decimals, @@ -28,7 +28,7 @@ async fn main() -> Result<(), Box> { .await?; let data = rpc - .get_account(light_ata) + .get_account(light_associated_token_account) .await? .ok_or("Account not found")?; let token = light_token_interface::state::Token::deserialize(&mut &data.data[..])?; From 0672c1ea97bd4ae37637a4e46807f727922be882 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Wed, 28 Jan 2026 22:38:10 +0000 Subject: [PATCH 05/21] add macro1 --- init-light.skill | Bin 0 -> 1701 bytes light-token/cookbook/approve-revoke.mdx | 36 --- light-token/cookbook/burn.mdx | 19 +- light-token/cookbook/close-token-account.mdx | 18 -- light-token/cookbook/create-ata.mdx | 11 +- light-token/cookbook/create-mint.mdx | 170 +++++++++++- light-token/cookbook/create-token-account.mdx | 11 +- light-token/cookbook/freeze-thaw.mdx | 36 --- light-token/cookbook/mint-to.mdx | 30 +-- light-token/cookbook/transfer-checked.mdx | 18 -- light-token/cookbook/transfer-interface.mdx | 18 -- light-token/toolkits/for-streaming-mints.mdx | 95 ++++--- scripts/copy-program-snippets.sh | 11 +- .../code-samples/code-compare-snippets.jsx | 66 +++++ .../approve/anchor-program/full-example.mdx | 26 +- .../approve/native-program/full-example.mdx | 3 +- .../burn/anchor-program/full-example.mdx | 25 +- .../burn/native-program/full-example.mdx | 6 +- .../anchor-program/full-example.mdx | 24 +- .../native-program/full-example.mdx | 2 +- .../create-ata/anchor-macro/full-example.mdx | 252 ++++++++++++++++++ .../anchor-program/full-example.mdx | 27 +- .../create-ata/rust-client/action.mdx | 14 +- .../create-mint/anchor-macro/full-example.mdx | 210 +++++++++++++++ .../anchor-program/full-example.mdx | 41 +-- .../native-program/full-example.mdx | 44 +-- .../create-mint/rust-client/action.mdx | 17 +- .../anchor-macro/full-example.mdx | 251 +++++++++++++++++ .../anchor-program/full-example.mdx | 27 +- .../freeze/anchor-program/full-example.mdx | 22 +- .../freeze/native-program/full-example.mdx | 4 +- .../anchor-program/full-example.mdx | 88 ++++++ .../native-program/full-example.mdx | 19 +- .../mint-to/anchor-program/full-example.mdx | 25 +- .../mint-to/rust-client/action.mdx | 21 +- .../revoke/anchor-program/full-example.mdx | 24 +- .../thaw/anchor-program/full-example.mdx | 22 +- .../anchor-program/full-example.mdx | 28 +- .../native-program/full-example.mdx | 5 +- .../transfer-checked/rust-client/action.mdx | 3 +- .../anchor-program/full-example.mdx | 25 +- .../transfer-interface/rust-client/action.mdx | 2 +- 42 files changed, 1301 insertions(+), 495 deletions(-) create mode 100644 init-light.skill create mode 100644 snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx create mode 100644 snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx create mode 100644 snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx diff --git a/init-light.skill b/init-light.skill new file mode 100644 index 0000000000000000000000000000000000000000..1701001b2660027ddd583b4b734bd817e1eaf87c GIT binary patch literal 1701 zcmV;W23q-0O9KQH000080Edh`Tq>f6799ov04fgv022TJ0BLS%bS-RYXJ~XUQ%gxq zOfGF?l~@06+cpsXUr#|S7?2pUH}tP97z((_3IuCAW63ZK#gNbv9dnULm89Z&@C)oM z_H28SeRrZHT1i_B$PY^-?~eC<_uY9upJzta>Rf1}!@Sb>+mMyY7fv@pTXQbHdPDET z%GqEGTZzW$S_l28{JG%m^I#v8$>YQ7a+!<5){Rr%t5U?p5`X`bf#TQuQLs4x<5a5Wj6@5E?%o z55LgPgSZYdw0O=A2;k@!hZCe|tF5GwLKcN>advB~k^tzI*Aj~b04DRB*V6(izSU-9@5Dx{s$7U0 zCymzw>0*4376TNqY07BziZee2I-y>TEIfUs>5z z9umK`A*mdyPi^KKPHd;6-qwnEiQUSfRHJM{!Q?$|j4YC3BR%J+nb$gxaRgEUNFl@r zIjt4T&<1>%L#(qixLM2M0T$-PmipqOmXKtp;N+Gz-;&O_PDT73H=!j~Ninz5 z+^g7YVw>qcWL+adHu&Tqg5r*0S$3Z9a#F#k`hPBdW|V%o7#FVa{^cDD?6Qyhv&MaI z{p39}<2GLRk2_qI=bSMC2X3rgCTl2WMcTPc563*?uv}Ymb@G0EMaQp_B(eUAg0n-$ zk7+$%#yI9;`Sz`7tgS%1M+s`AoW7XUWR^|ejF(7+ve!3A+0H1}@tFq`LHTtxk5be) zqCjx#A>pKrNqZpPe9|GQ${-C3vFuee4j8B~?ic(}K<3se(U@;al?V=6%1&1mZk}{3 z{KUq(5Z7(fSQoH$y%#HFHn47&;uW~t;5QWysa@%8Gt_mdfsJ0bY;_{!ZG#p7!RiJr zB*>{kH&iOm!@&A-{M==tJnHfi+6_bKfd8-IU$mZXHVna<2LKk9XJ-J?1TIxJ+cqVC z9CR!h5y-^xVahgC70IEy#}=$hJW$+Bk;PCJOeDY64Psnf0n4ycglmGKDUUCcATrP- z>g}akUA@D9l)yt8uW73a&f$p_`mcwh(LX`th6ixUyMfB}x{D7;K6Z)OET!VVfB*BO zy)7^cqA&F=d+$WWwCv7txL%CHuY6s&|o)k(F9nFW6*ywxF5E-qfanUo+ zU*QXbF}K^;bB-hLO+JmVCor?J^VLC$>B{jp63ke-q!tDO*-akF`TTUccml$p8$5ya zVv`J7{ZKZW<&5f|sD|CJ z1`T{@t5gsF0Z>Z;0u%!j000080Edh`Tq>f6799ov04fgv022TJ00000000000JMPs v0001KZfSHaY-wj`bT3m&NlZ*GZDdeO1qJ{B000310RTY&002$~00000-!deP literal 0 HcmV?d00001 diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx index 9f3d5836..2f0514ec 100644 --- a/light-token/cookbook/approve-revoke.mdx +++ b/light-token/cookbook/approve-revoke.mdx @@ -20,9 +20,7 @@ import ApproveInstructionCode from "/snippets/code-snippets/light-token/approve- import RevokeActionCode from "/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx"; import RevokeInstructionCode from "/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-instruction.mdx"; import ApproveAnchorProgramCode from "/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx"; -import ApproveNativeProgramCode from "/snippets/code-snippets/light-token/approve/native-program/full-example.mdx"; import RevokeAnchorProgramCode from "/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx"; -import RevokeNativeProgramCode from "/snippets/code-snippets/light-token/revoke/native-program/full-example.mdx"; 1. Approve grants a delegate permission to transfer up to a specified amount of tokens from your account. * Each token account can have only one delegate at a time. @@ -210,9 +208,6 @@ revoke_cpi.invoke_signed(&[signer_seeds])?; - - - View the full example with shared test utilities: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/approve). @@ -222,25 +217,8 @@ revoke_cpi.invoke_signed(&[signer_seeds])?; - - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - - - - - - - - - - View the full example with shared test utilities: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/revoke). @@ -248,20 +226,6 @@ revoke_cpi.invoke_signed(&[signer_seeds])?; - - - - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - - - - - diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx index ee688fcc..df436b3e 100644 --- a/light-token/cookbook/burn.mdx +++ b/light-token/cookbook/burn.mdx @@ -16,7 +16,7 @@ import { import RustInstructionCode from "/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx"; import BurnCheckedRustInstructionCode from "/snippets/code-snippets/light-token/burn-checked/rust-client/instruction.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx"; -import NativeProgramCode from "/snippets/code-snippets/light-token/burn/native-program/full-example.mdx"; + 1. Burn permanently destroys tokens by reducing the balance in a token account. 2. Burned tokens are removed from circulation and decreases the supply tracked on the mint account. @@ -118,9 +118,6 @@ BurnCpi { # Full Code Example - - - View the full example with shared test utilities: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/burn). @@ -130,20 +127,6 @@ BurnCpi { - - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - - - - - - - # Next Steps diff --git a/light-token/cookbook/close-token-account.mdx b/light-token/cookbook/close-token-account.mdx index 7ec43fb4..4731dbfc 100644 --- a/light-token/cookbook/close-token-account.mdx +++ b/light-token/cookbook/close-token-account.mdx @@ -17,7 +17,6 @@ import { } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustInstructionCode from "/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx"; -import NativeProgramCode from "/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx"; 1. Closing a light-token account transfers remaining lamports to a destination account and the rent sponsor can reclaim sponsored rent. 2. Light token accounts can be closed by the owner. @@ -126,9 +125,6 @@ CloseAccountCpi { # Full Code Example - - - View the full example with shared test utilities: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/close). @@ -138,20 +134,6 @@ CloseAccountCpi { - - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - - - - - - - # Next Steps diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index 965013b4..d70c3144 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -24,7 +24,7 @@ import InstructionCode from "/snippets/code-snippets/light-token/create-ata/inst import RustActionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx"; -import NativeProgramCode from "/snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx"; +import AnchorMacroCode from "/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx"; 1. Associated light-token accounts can hold token balances of light, SPL, or Token 2022 mints. 2. Light-ATAs are on-chain accounts like SPL ATA's, but the light token program sponsors the rent-exemption cost for you. @@ -205,14 +205,9 @@ CreateAssociatedAccountCpi { - + - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - + diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx index dd3845bd..f3ed5982 100644 --- a/light-token/cookbook/create-mint.mdx +++ b/light-token/cookbook/create-mint.mdx @@ -18,13 +18,17 @@ import { lightCreateMintCode, splCreateMintRustCode, lightCreateMintRustCode, + splCreateMintMacroCode, + lightCreateMintMacroCode, + splCreateMintMetadataMacroCode, + lightCreateMintMetadataMacroCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import ActionCode from "/snippets/code-snippets/light-token/create-mint/action.mdx"; import InstructionCode from "/snippets/code-snippets/light-token/create-mint/instruction.mdx"; import RustActionCode from "/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx"; -import NativeProgramCode from "/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx"; +import AnchorMacroCode from "/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx"; import CompressibleRentExplained from "/snippets/compressible-rent-explained.mdx"; 1. Mint accounts uniquely represent a token on Solana and store its global metadata. @@ -132,6 +136,10 @@ Compare to SPL: + + + + Find [a full code example at the end](#full-code-example). @@ -322,18 +330,166 @@ CreateMintCpi::new( - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - + + + - + +Find [a full code example at the end](#full-code-example). + +Compare to SPL: + + + + + + + + +Dependencies + +```toml +[dependencies] + +light-sdk = { version = "0.18.0", features = ["anchor", "v2", "cpi-context"] } +light-sdk-macros = { version = "0.18.0" } +light-token = { version = "0.3.0", features = ["anchor"] } + +light-anchor-spl = { version = "0.31" } # TokenInterface uses light_token::ID +anchor-lang = "0.31" +``` + +Add `#[light_program]` above `#[program]`: + +```rust +use light_sdk_macros::light_program; + +#[light_program] +#[program] +pub mod my_amm { + use super::*; + + pub fn initialize_pool(ctx: Context, params: InitializeParams) -> Result<()> { + process_initialize_pool(ctx, params) + } + + // These don't change + pub fn swap(ctx: Context, amount_in: u64, min_out: u64) -> Result<()> { + process_swap(ctx, amount_in, min_out) + } +} +``` + +Derive `LightAccounts` on your `Accounts` struct and add `#[light_account(...)]` next to `#[account(...)]`. + +```rust +#[account(mut)] +#[light_account( + init, + mint, + mint_signer = lp_mint_signer, + authority = authority, + decimals = 9, + mint_seeds = &[LP_MINT_SIGNER_SEED, pool_state.key().as_ref(), &[params.mint_signer_bump]], + authority_seeds = &[AUTH_SEED.as_bytes(), &[params.authority_bump]] +)] +pub lp_mint: UncheckedAccount<'info>, +``` + +We also need to add `light_token_interface_config`, `rent_sponsor`, and `light_token_cpi_authority`. + +```rust +use light_sdk::interface::CreateAccountsProof; +use light_sdk_macros::LightAccounts; +use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR}; + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct InitializeParams { + pub create_accounts_proof: CreateAccountsProof, + pub lp_mint_signer_bump: u8, + pub creator_lp_token_bump: u8, + pub authority_bump: u8, +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: InitializeParams)] +pub struct InitializePool<'info> { + #[account(mut)] + pub creator: Signer<'info>, + + #[account(mut, seeds = [AUTH_SEED.as_bytes()], bump)] + pub authority: UncheckedAccount<'info>, + + #[account( + init, + seeds = [POOL_SEED.as_bytes(), token_0_mint.key().as_ref(), token_1_mint.key().as_ref()], + bump, + payer = creator, + space = 8 + PoolState::INIT_SPACE + )] + #[light_account(init)] + pub pool_state: Box>, + + pub token_0_mint: Box>, + pub token_1_mint: Box>, + + #[account(seeds = [POOL_LP_MINT_SIGNER_SEED, pool_state.key().as_ref()], bump)] + pub lp_mint_signer: UncheckedAccount<'info>, + + #[account(mut)] + #[light_account(init, mint, + mint_signer = lp_mint_signer, + authority = authority, + decimals = 9, + mint_seeds = &[POOL_LP_MINT_SIGNER_SEED, self.pool_state.to_account_info().key.as_ref(), &[params.lp_mint_signer_bump]], + authority_seeds = &[AUTH_SEED.as_bytes(), &[params.authority_bump]] + )] + pub lp_mint: UncheckedAccount<'info>, + + #[account(mut, seeds = [POOL_VAULT_SEED.as_bytes(), pool_state.key().as_ref(), token_0_mint.key().as_ref()], bump)] + #[light_account(token, authority = [AUTH_SEED.as_bytes()])] + pub token_0_vault: UncheckedAccount<'info>, + + #[account(mut, seeds = [POOL_VAULT_SEED.as_bytes(), pool_state.key().as_ref(), token_1_mint.key().as_ref()], bump)] + #[light_account(token, authority = [AUTH_SEED.as_bytes()])] + pub token_1_vault: UncheckedAccount<'info>, + + #[account(mut)] + pub creator_lp_token: UncheckedAccount<'info>, + + + pub light_interface_config: AccountInfo<'info>, + #[account(address = COMPRESSIBLE_CONFIG_V1)] + pub light_token_interface_config: AccountInfo<'info>, + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub rent_sponsor: AccountInfo<'info>, + pub light_token_program: AccountInfo<'info>, + pub light_token_cpi_authority: AccountInfo<'info>, + pub system_program: Program<'info, System>, +} +``` + + + + + diff --git a/light-token/cookbook/create-token-account.mdx b/light-token/cookbook/create-token-account.mdx index eea8df59..bef33014 100644 --- a/light-token/cookbook/create-token-account.mdx +++ b/light-token/cookbook/create-token-account.mdx @@ -17,7 +17,7 @@ import { } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx"; -import NativeProgramCode from "/snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx"; +import AnchorMacroCode from "/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx"; 1. Light token accounts are Solana accounts that hold token balances of light, SPL, or Token 2022 mints. 2. Light token accounts are on-chain accounts like SPL ATA’s, but the light token program sponsors the rent-exemption cost for you. @@ -149,14 +149,9 @@ CreateTokenAccountCpi { - + - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - + diff --git a/light-token/cookbook/freeze-thaw.mdx b/light-token/cookbook/freeze-thaw.mdx index d620a1a4..00d4aadd 100644 --- a/light-token/cookbook/freeze-thaw.mdx +++ b/light-token/cookbook/freeze-thaw.mdx @@ -18,9 +18,7 @@ import { import FreezeInstructionCode from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx"; import ThawInstructionCode from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx"; import FreezeAnchorProgramCode from "/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx"; -import FreezeNativeProgramCode from "/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx"; import ThawAnchorProgramCode from "/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx"; -import ThawNativeProgramCode from "/snippets/code-snippets/light-token/thaw/native-program/full-example.mdx"; 1. Freeze prevents all transfers or token burns from a specific light-token account. 2. Once frozen, the account cannot send tokens, receive tokens, or be closed until it is thawed. @@ -196,9 +194,6 @@ ThawCpi { - - - View the full example with shared test utilities: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/freeze). @@ -208,25 +203,8 @@ ThawCpi { - - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - - - - - - - - - - View the full example with shared test utilities: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/thaw). @@ -234,20 +212,6 @@ ThawCpi { - - - - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - - - - - diff --git a/light-token/cookbook/mint-to.mdx b/light-token/cookbook/mint-to.mdx index dd289e57..b6797edb 100644 --- a/light-token/cookbook/mint-to.mdx +++ b/light-token/cookbook/mint-to.mdx @@ -23,8 +23,7 @@ import InstructionCode from "/snippets/code-snippets/light-token/mint-to/instruc import RustActionCode from "/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx"; -import NativeProgramCode from "/snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx"; -import NativeMintToCheckedCode from "/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx"; +import AnchorMintToCheckedCode from "/snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx"; 1. Mint To creates new tokens of a mint and deposits them to a specified token account. 2. Before we can mint any tokens, we need an initialized mint account (SPL, Token-2022 or Light) for which we hold the mint authority. @@ -177,9 +176,6 @@ MintToCpi { # Full Code Example - - - View the full example with shared test utilities: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/mint-to). @@ -189,20 +185,6 @@ MintToCpi { - - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - - - - - - - @@ -271,18 +253,12 @@ MintToCheckedCpi { # Full Code Example - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/mint-to-checked). - - - - + diff --git a/light-token/cookbook/transfer-checked.mdx b/light-token/cookbook/transfer-checked.mdx index ff6a2f95..9ea9a0ac 100644 --- a/light-token/cookbook/transfer-checked.mdx +++ b/light-token/cookbook/transfer-checked.mdx @@ -10,7 +10,6 @@ keywords: ["transfer tokens solana", "checked transfer", "decimal validation"] import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; import RustActionCode from "/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx"; -import NativeProgramCode from "/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx"; 1. TransferChecked validates that the decimals parameter matches the mint's decimals. @@ -109,9 +108,6 @@ TransferCheckedCpi { # Full Code Example - - - View the full example: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/transfer-checked). @@ -121,20 +117,6 @@ TransferCheckedCpi { - - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - - - - - - - # Next Steps diff --git a/light-token/cookbook/transfer-interface.mdx b/light-token/cookbook/transfer-interface.mdx index 98402787..61f25434 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -23,7 +23,6 @@ import InstructionCode from "/snippets/code-snippets/light-token/transfer-interf import RustActionCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx"; import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx"; -import NativeProgramCode from "/snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx"; @@ -206,9 +205,6 @@ TransferInterfaceCpi::new( # Full Code Example - - - View the full example with shared test utilities: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/transfer-interface). @@ -218,20 +214,6 @@ TransferInterfaceCpi::new( - - - - View the full example with shared test utilities: - [examples-light-token-native](https://github.com/Lightprotocol/examples-light-token-native/tree/main/program-examples/native). - - - - - - - - - # Next Steps diff --git a/light-token/toolkits/for-streaming-mints.mdx b/light-token/toolkits/for-streaming-mints.mdx index f9a7b1d5..fdb5e905 100644 --- a/light-token/toolkits/for-streaming-mints.mdx +++ b/light-token/toolkits/for-streaming-mints.mdx @@ -19,26 +19,37 @@ Find devnet examples [here](https://github.com/Lightprotocol/examples-light-toke ```toml Cargo.toml [dependencies] -helius-laserstream = "0.1" +helius-laserstream = "0.1.5" tokio = { version = "1", features = ["full"] } futures = "0.3" -light-event = "0.2" -light-compressed-account = "0.7" -light-token-interface = "0.1" -borsh = "0.10" +anyhow = "1" +dotenvy = "0.15" bs58 = "0.5" +borsh = "0.10" + +# Light Protocol dependencies (git until published to crates.io) +light-event = { git = "https://github.com/Lightprotocol/light-protocol" } +light-compressed-account = { git = "https://github.com/Lightprotocol/light-protocol", features = ["std"] } +light-token-interface = { git = "https://github.com/Lightprotocol/light-protocol" } ``` ```rust +use borsh::BorshDeserialize; use futures::StreamExt; +use helius_laserstream::solana::storage::confirmed_block::{CompiledInstruction, Message}; use helius_laserstream::{subscribe, LaserstreamConfig}; +use light_compressed_account::Pubkey; use light_event::parse::event_from_light_transaction; -use light_token_interface::state::Mint; -use light_token_interface::state::extensions::ExtensionStruct; -use borsh::BorshDeserialize; +use light_token_interface::state::{ExtensionStruct, Mint}; const LIGHT_SYSTEM_PROGRAM: &str = "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7"; -const LIGHT_TOKEN_PROGRAM_ID: &str = "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m"; + +// Light Token Program ID bytes (cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m) +const LIGHT_TOKEN_PROGRAM_BYTES: [u8; 32] = [ + 3, 89, 128, 47, 145, 178, 244, 191, 100, 152, 66, 240, 127, 251, 205, 1, + 168, 38, 248, 106, 170, 164, 111, 0, 118, 231, 126, 151, 147, 169, 169, 4, +]; + const COMPRESSED_MINT_DISCRIMINATOR: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 1]; ``` @@ -99,25 +110,33 @@ while let Some(update) = stream.next().await { ### Parse Events ```rust -fn process_transaction(msg: &Message, meta: Option<&TransactionStatusMeta>) { - let account_keys = extract_account_keys(msg, meta); +fn process_transaction(message: &Message, light_token_program_id: &Pubkey) -> anyhow::Result<()> { + let account_keys: Vec = message + .account_keys + .iter() + .filter_map(|k| { + if k.len() == 32 { + let arr: [u8; 32] = k.as_slice().try_into().ok()?; + Some(Pubkey::from(arr)) + } else { + None + } + }) + .collect(); let (program_ids, instruction_data, accounts_per_ix) = - extract_instructions(msg, meta, &account_keys); - - match event_from_light_transaction(&program_ids, &instruction_data, accounts_per_ix) { - Ok(Some(batches)) => { - for batch in batches { - println!( - "Event: {} inputs, {} outputs", - batch.event.input_compressed_account_hashes.len(), - batch.event.output_compressed_accounts.len() - ); - } + extract_light_transaction(&account_keys, &message.instructions); + + if let Some(batches) = + event_from_light_transaction(&program_ids, &instruction_data, accounts_per_ix)? + { + for batch in &batches { + let event = &batch.event; + // Process outputs... } - Ok(None) => {} // No compressed account events - Err(e) => eprintln!("Parse error: {:?}", e), } + + Ok(()) } ``` @@ -127,35 +146,23 @@ fn process_transaction(msg: &Message, meta: Option<&TransactionStatusMeta>) { ### Extract Mints ```rust -for output in event.output_compressed_accounts.iter() { - let owner = output.compressed_account.owner; +let light_token_program_id = Pubkey::from(LIGHT_TOKEN_PROGRAM_BYTES); - // Check if owned by light token program - let light_token_program_id = bs58::decode(LIGHT_TOKEN_PROGRAM_ID).into_vec().unwrap(); - if owner != light_token_program_id.as_slice() { +for output in &event.output_compressed_accounts { + // Filter by owner (Light Token Program) + if output.compressed_account.owner != light_token_program_id { continue; } - // Check discriminator + // Check for mint discriminator let data = match &output.compressed_account.data { Some(d) if d.discriminator == COMPRESSED_MINT_DISCRIMINATOR => &d.data, _ => continue, }; - // Deserialize - let mint = Mint::try_from_slice(data)?; - - // Check if new (address not in inputs) - let is_new = output - .compressed_account - .address - .map(|addr| { - !event.input_compressed_account_hashes.iter().any(|h| *h == addr) - }) - .unwrap_or(true); - - if is_new { - println!("New mint: {}", bs58::encode(mint.metadata.mint).into_string()); + // Deserialize mint + if let Ok(mint) = Mint::deserialize(&mut data.as_slice()) { + println!("Mint: {:?}", mint.metadata.mint); } } ``` diff --git a/scripts/copy-program-snippets.sh b/scripts/copy-program-snippets.sh index a88cf560..27af17e5 100755 --- a/scripts/copy-program-snippets.sh +++ b/scripts/copy-program-snippets.sh @@ -79,7 +79,7 @@ done # ANCHOR PROGRAMS # ============================================================================= -ANCHOR_EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token/program-examples/anchor/programs-sdk" +ANCHOR_EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token-anchor/program-examples/anchor/basic-instructions" # Anchor recipes (output-name:anchor-dir-name) # Some have different directory names (e.g., close-token-account uses 'close' dir) @@ -96,6 +96,7 @@ ANCHOR_RECIPES=( "freeze:freeze" "thaw:thaw" "transfer-checked:transfer-checked" + "mint-to-checked:mint-to-checked" ) echo "" @@ -152,12 +153,12 @@ done # ANCHOR MACROS # ============================================================================= -ANCHOR_MACROS_DIR="/home/tilo/Workspace/examples-light-token/program-examples/anchor/macro-basics" +ANCHOR_MACROS_DIR="/home/tilo/Workspace/examples-light-token-anchor/program-examples/anchor/basic-macros" ANCHOR_MACRO_RECIPES=( - "create-mint:create-mint-macro" - "create-ata:create-ata-macro" - "create-token-account:create-token-account-macro" + "create-mint:create-mint" + "create-ata:create-ata" + "create-token-account:create-token-account" ) echo "" diff --git a/snippets/code-samples/code-compare-snippets.jsx b/snippets/code-samples/code-compare-snippets.jsx index d68db6c0..d075fac0 100644 --- a/snippets/code-samples/code-compare-snippets.jsx +++ b/snippets/code-samples/code-compare-snippets.jsx @@ -436,3 +436,69 @@ export const lightRevokeRustCode = [ "}", ".instruction()?;", ].join("\n"); + +// === CREATE MINT MACRO (ANCHOR) === +export const splCreateMintMacroCode = [ + "// SPL Token (Anchor)", + "#[account(", + " init,", + " payer = fee_payer,", + " mint::decimals = 9,", + " mint::authority = fee_payer,", + ")]", + "pub mint: InterfaceAccount<'info, Mint>,", +].join("\n"); + +export const lightCreateMintMacroCode = [ + "// light-token (Anchor)", + "#[light_account(init,", + " mint::signer = mint_signer,", + " mint::authority = fee_payer,", + " mint::decimals = 9,", + " mint::seeds = &[MINT_SIGNER_SEED, authority.key().as_ref()],", + " mint::bump = params.mint_signer_bump", + ")]", + "pub mint: UncheckedAccount<'info>,", +].join("\n"); + +// === CREATE MINT WITH METADATA MACRO (ANCHOR) === +export const splCreateMintMetadataMacroCode = [ + "// SPL Token-2022 (Anchor)", + "// Macro — only MetadataPointer is declarative", + "#[account(", + " init,", + " payer = fee_payer,", + " mint::decimals = 9,", + " mint::authority = fee_payer,", + " extensions::metadata_pointer::authority = fee_payer,", + " extensions::metadata_pointer::metadata_address = mint_account,", + ")]", + "pub mint_account: InterfaceAccount<'info, Mint>,", + "", + "// Metadata fields require a separate CPI call", + "// in the instruction handler:", + "token_metadata_initialize(", + " cpi_ctx,", + " params.name,", + " params.symbol,", + " params.uri,", + ")?;", +].join("\n"); + +export const lightCreateMintMetadataMacroCode = [ + "// light-token (Anchor)", + "#[light_account(", + " init,", + " mint,", + " mint_signer = mint_signer,", + " authority = fee_payer,", + " decimals = 9,", + " mint_seeds = &[MINT_SIGNER_SEED, authority.key().as_ref(), &[params.mint_signer_bump]],", + " name = params.name.clone(),", + " symbol = params.symbol.clone(),", + " uri = params.uri.clone(),", + " update_authority = authority,", + " additional_metadata = params.additional_metadata.clone()", + ")]", + "pub mint: UncheckedAccount<'info>,", +].join("\n"); diff --git a/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx index 821555fa..cbe58db6 100644 --- a/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx @@ -39,16 +39,11 @@ pub struct ApproveAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::Rpc; use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_approve::{instruction::Approve, ID}; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - signature::Keypair, - signer::Signer, - system_program, -}; +use light_token_anchor_approve::{accounts, instruction::Approve, ID}; +use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer, system_program}; use test_utils::{mint_tokens, setup_test_env}; #[tokio::test] @@ -65,13 +60,14 @@ async fn test_approve() { let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(env.ata, false), - AccountMeta::new_readonly(delegate.pubkey(), false), - AccountMeta::new_readonly(env.payer.pubkey(), true), - AccountMeta::new_readonly(system_program::ID, false), - ], + accounts: accounts::ApproveAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + token_account: env.ata, + delegate: delegate.pubkey(), + owner: env.payer.pubkey(), + system_program: system_program::ID, + } + .to_account_metas(Some(true)), data: Approve { amount: approve_amount }.data(), }; diff --git a/snippets/code-snippets/light-token/approve/native-program/full-example.mdx b/snippets/code-snippets/light-token/approve/native-program/full-example.mdx index 8a3e3f1e..b8da4239 100644 --- a/snippets/code-snippets/light-token/approve/native-program/full-example.mdx +++ b/snippets/code-snippets/light-token/approve/native-program/full-example.mdx @@ -67,8 +67,7 @@ use light_client::rpc::Rpc; use light_token_interface::state::Token; use shared::{ build_approve_cpi_ix, build_approve_signed_cpi_ix, create_test_rpc, - get_authority_pda, setup, setup_empty_ata, setup_pda_owned_ata, - SetupContext, + get_authority_pda, setup, setup_pda_owned_ata, SetupContext, }; use solana_sdk::{signature::Keypair, signer::Signer}; diff --git a/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx index 8efebd61..072098ea 100644 --- a/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx @@ -42,15 +42,11 @@ pub struct BurnAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::Rpc; use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_burn::{instruction::Burn, ID}; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - signer::Signer, - system_program, -}; +use light_token_anchor_burn::{accounts, instruction::Burn, ID}; +use solana_sdk::{instruction::Instruction, signer::Signer, system_program}; use test_utils::{mint_tokens, setup_test_env}; #[tokio::test] @@ -65,13 +61,14 @@ async fn test_burn() { let burn_amount = 250_000u64; let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(env.ata, false), - AccountMeta::new(env.mint_pda, false), - AccountMeta::new_readonly(env.payer.pubkey(), true), - AccountMeta::new_readonly(system_program::ID, false), - ], + accounts: accounts::BurnAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + source: env.ata, + mint: env.mint_pda, + authority: env.payer.pubkey(), + system_program: system_program::ID, + } + .to_account_metas(Some(true)), data: Burn { amount: burn_amount }.data(), }; diff --git a/snippets/code-snippets/light-token/burn/native-program/full-example.mdx b/snippets/code-snippets/light-token/burn/native-program/full-example.mdx index 995c0d77..d1103e81 100644 --- a/snippets/code-snippets/light-token/burn/native-program/full-example.mdx +++ b/snippets/code-snippets/light-token/burn/native-program/full-example.mdx @@ -8,7 +8,8 @@ use solana_program::{ }; pub fn burn_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [source, mint, authority, system_program, _token_program] = accounts else { + let [source, mint, authority, system_program, _token_program] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -34,7 +35,8 @@ pub fn burn_invoke_signed( accounts: &[AccountInfo], data: &[u8], ) -> ProgramResult { - let [source, mint, authority, system_program, _token_program] = accounts else { + let [source, mint, authority, system_program, _token_program] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; diff --git a/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx index 333ece13..f373494c 100644 --- a/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx @@ -42,14 +42,11 @@ pub struct CloseAccountAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::Rpc; use light_token::instruction::{rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID}; -use light_token_anchor_close::{instruction::CloseAccount, ID}; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - signer::Signer, -}; +use light_token_anchor_close::{accounts, instruction::CloseAccount, ID}; +use solana_sdk::{instruction::Instruction, signer::Signer}; use test_utils::setup_test_env; #[tokio::test] @@ -63,13 +60,14 @@ async fn test_close() { let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(env.ata, false), - AccountMeta::new(env.payer.pubkey(), false), - AccountMeta::new_readonly(env.payer.pubkey(), true), - AccountMeta::new(rent_sponsor, false), - ], + accounts: accounts::CloseAccountAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + account: env.ata, + destination: env.payer.pubkey(), + owner: env.payer.pubkey(), + rent_sponsor, + } + .to_account_metas(Some(true)), data: CloseAccount {}.data(), }; diff --git a/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx b/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx index 8dcc78f4..e468009d 100644 --- a/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx +++ b/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx @@ -13,7 +13,7 @@ pub fn close_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { return Err(ProgramError::NotEnoughAccountKeys); }; - // Close token account. Must be empty (balance = 0) + // Close token account. Must be empty (balance == 0) CloseAccountCpi { token_program: token_program.clone(), account: account.clone(), diff --git a/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx new file mode 100644 index 00000000..ac352298 --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx @@ -0,0 +1,252 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_compressible::CreateAccountsProof; +use light_sdk::derive_light_cpi_signer; +use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::{CpiSigner, LIGHT_TOKEN_PROGRAM_ID}; +use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR}; + +declare_id!("CLsn9MTFv97oMTsujRoQAw1u2rSm2HnKtGuWUbbc8Jfn"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("CLsn9MTFv97oMTsujRoQAw1u2rSm2HnKtGuWUbbc8Jfn"); + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateAtaParams { + pub create_accounts_proof: CreateAccountsProof, + pub ata_bump: u8, +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateAtaParams)] +pub struct CreateAta<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// CHECK: Validated by light-token CPI + // You can use Light, SPL, or Token-2022 mints to create a Light associated token account. + pub ata_mint: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub ata_owner: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut)] + #[light_account(init, associated_token::authority = ata_owner, associated_token::mint = ata_mint, associated_token::bump = params.ata_bump)] + pub ata: UncheckedAccount<'info>, + + #[account(address = COMPRESSIBLE_CONFIG_V1)] + pub light_token_compressible_config: AccountInfo<'info>, + + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub light_token_rent_sponsor: AccountInfo<'info>, + + /// CHECK: Light token program for CPI + #[account(address = LIGHT_TOKEN_PROGRAM_ID.into())] + pub light_token_program: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} + +#[light_program] +#[program] +pub mod light_token_macro_create_ata { + use super::*; + + #[allow(unused_variables)] + pub fn create_ata<'info>( + ctx: Context<'_, '_, '_, 'info, CreateAta<'info>>, + params: CreateAtaParams, + ) -> Result<()> { + Ok(()) + } +} +``` + +```rust test.rs +//! Integration test for Light Protocol ATA creation using the macro. + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::interface::{get_create_accounts_proof, InitializeRentFreeConfig}; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest}, + Indexer, ProgramTestConfig, Rpc, +}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Setup helper: Creates a compressed mint directly using the ctoken SDK. +/// Returns (mint_pda, mint_seed_keypair) +async fn setup_create_mint( + rpc: &mut (impl Rpc + Indexer), + payer: &Keypair, + mint_authority: Pubkey, + decimals: u8, +) -> (Pubkey, Keypair) { + use light_token::instruction::{CreateMint, CreateMintParams}; + + let mint_seed = Keypair::new(); + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = light_token::instruction::derive_mint_compressed_address( + &mint_seed.pubkey(), + &address_tree.tree, + ); + + let (mint, bump) = light_token::instruction::find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![light_client::indexer::AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority, + proof: rpc_result.proof.0.unwrap(), + compression_address, + mint, + bump, + freeze_authority: None, + extensions: None, + rent_payment: 16, + write_top_up: 766, + }; + + let create_mint_builder = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ); + let instruction = create_mint_builder.instruction().unwrap(); + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_seed]) + .await + .unwrap(); + + (mint, mint_seed) +} + +/// Test creating a Light Protocol ATA using the macro. +#[tokio::test] +async fn test_create_ata() { + use light_token_macro_create_ata::CreateAtaParams; + + let program_id = light_token_macro_create_ata::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("light_token_macro_create_ata", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + let (init_config_ix, _config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + RENT_SPONSOR, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + // Setup mint first + let (mint, _mint_seed) = setup_create_mint( + &mut rpc, + &payer, + payer.pubkey(), // mint_authority + 9, // decimals + ) + .await; + + // The ATA owner will be the payer + let ata_owner = payer.pubkey(); + + // Derive the ATA address using Light Token SDK's derivation + let (ata, ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &mint); + + // Get proof (no PDA accounts for ATA-only instruction) + let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) + .await + .unwrap(); + + // Build instruction + let accounts = light_token_macro_create_ata::accounts::CreateAta { + fee_payer: payer.pubkey(), + ata_mint: mint, + ata_owner, + ata, + light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, + light_token_rent_sponsor: RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = light_token_macro_create_ata::instruction::CreateAta { + params: CreateAtaParams { + create_accounts_proof: proof_result.create_accounts_proof, + ata_bump, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await + .expect("CreateAta instruction should succeed"); + + // Verify ATA exists on-chain + let ata_account = rpc + .get_account(ata) + .await + .unwrap() + .expect("ATA should exist on-chain"); + + // Parse and verify token data + use light_token_interface::state::Token; + let token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..]) + .expect("Failed to deserialize Token"); + + // Verify owner + assert_eq!(token.owner, ata_owner.to_bytes(), "ATA owner should match"); + + // Verify mint + assert_eq!(token.mint, mint.to_bytes(), "ATA mint should match"); + + // Verify initial amount is 0 + assert_eq!(token.amount, 0, "ATA amount should be 0 initially"); +} +``` + diff --git a/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx index ea1e4613..fbaab1f9 100644 --- a/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx @@ -61,17 +61,17 @@ pub struct CreateAtaAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_client::indexer::AddressWithTree; use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; -use light_token_anchor_create_ata::{instruction::CreateAta, ID}; +use light_token_anchor_create_ata::{accounts, instruction::CreateAta, ID}; use light_token::instruction::{ CreateMint, CreateMintParams, config_pda, derive_mint_compressed_address, derive_token_ata, find_mint_address, rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID, }; use anchor_lang::system_program; use solana_sdk::{ - instruction::{AccountMeta, Instruction}, + instruction::Instruction, signature::Keypair, signer::Signer, }; @@ -145,16 +145,17 @@ async fn test_create_ata() { let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new_readonly(payer.pubkey(), false), - AccountMeta::new_readonly(mint_pda, false), - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(ata, false), - AccountMeta::new_readonly(system_program::ID, false), - AccountMeta::new_readonly(compressible_config, false), - AccountMeta::new(rent_sponsor, false), - ], + accounts: accounts::CreateAtaAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + owner: payer.pubkey(), + mint: mint_pda, + payer: payer.pubkey(), + associated_token_account: ata, + system_program: system_program::ID, + compressible_config, + rent_sponsor, + } + .to_account_metas(Some(true)), data: CreateAta { bump: ata_bump, idempotent: false, diff --git a/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx b/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx index 46e7cb24..f78de568 100644 --- a/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx @@ -1,32 +1,32 @@ ```rust use light_token_client::actions::{CreateAta, CreateMint}; use rust_client::setup_rpc_and_payer; -use solana_sdk::{signature::Keypair, signer::Signer}; +use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { let (mut rpc, payer) = setup_rpc_and_payer().await; - let mint_seed = Keypair::new(); // Create mint - let mint_result = CreateMint { + let (_signature, mint) = CreateMint { decimals: 9, freeze_authority: None, token_metadata: None, + seed: None, } - .execute(&mut rpc, &payer, &mint_seed) + .execute(&mut rpc, &payer, &payer) .await?; // Create associated token account - let associated_token_account = CreateAta { - mint: mint_result.mint, + let (_signature, associated_token_account) = CreateAta { + mint, owner: payer.pubkey(), idempotent: true, } .execute(&mut rpc, &payer) .await?; - println!("ATA: {associated_token_account}"); + println!("Associated token account: {associated_token_account}"); Ok(()) } diff --git a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx new file mode 100644 index 00000000..5da6283f --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx @@ -0,0 +1,210 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_compressible::CreateAccountsProof; +use light_sdk::derive_light_cpi_signer; +use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::CpiSigner; + +declare_id!("HVmVqSJyMejBeUigePMSfX4aENJzCGHNxAJuT2PDMPRx"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("HVmVqSJyMejBeUigePMSfX4aENJzCGHNxAJuT2PDMPRx"); + +pub const MINT_SIGNER_SEED: &[u8] = b"mint_signer"; + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateMintParams { + // We must create a compressed address at creation to ensure the mint does not exist yet. + pub create_accounts_proof: CreateAccountsProof, + pub mint_signer_bump: u8, +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateMintParams)] +pub struct CreateMint<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + pub authority: Signer<'info>, + + /// CHECK: Validated by light-token CPI + #[account( + seeds = [MINT_SIGNER_SEED, authority.key().as_ref()], + bump, + )] + pub mint_signer: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut)] + #[light_account(init, + mint::signer = mint_signer, + mint::authority = fee_payer, + mint::decimals = 9, + mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()], + mint::bump = params.mint_signer_bump + )] + pub mint: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + pub compression_config: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_compressible_config: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub rent_sponsor: AccountInfo<'info>, + + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_cpi_authority: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} + +#[light_program] +#[program] +pub mod light_token_macro_create_mint { + use super::*; + + #[allow(unused_variables)] + pub fn create_mint<'info>( + ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>, + params: CreateMintParams, + ) -> Result<()> { + Ok(()) + } +} +``` + +```rust test.rs +//! Integration test for create-mint macro example. + +use anchor_lang::{prelude::borsh, InstructionData, ToAccountMetas}; +use light_client::interface::{ + get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig, +}; +use light_program_test::{ + program_test::{setup_mock_program_data, LightProgramTest}, + ProgramTestConfig, Rpc, +}; +use light_sdk_types::LIGHT_TOKEN_PROGRAM_ID; +use light_token::instruction::{find_mint_address, COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Test creating a mint using the #[light_account(init, mint, ...)] macro. +#[tokio::test] +async fn test_create_mint() { + use light_token_macro_create_mint::{CreateMintParams, MINT_SIGNER_SEED}; + + let program_id = light_token_macro_create_mint::ID; + let mut config = + ProgramTestConfig::new_v2(true, Some(vec![("light_token_macro_create_mint", program_id)])); + config = config.with_light_protocol_events(); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + // Set up program data PDA for rent-free config + let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id); + + // Initialize rent-free config + let (init_config_ix, config_pda) = InitializeRentFreeConfig::new( + &program_id, + &payer.pubkey(), + &program_data_pda, + RENT_SPONSOR, + payer.pubkey(), + ) + .build(); + + rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer]) + .await + .expect("Initialize config should succeed"); + + let authority = Keypair::new(); + + // Derive PDA for mint signer + let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address( + &[MINT_SIGNER_SEED, authority.pubkey().as_ref()], + &program_id, + ); + + // Derive mint PDA from mint signer + let (mint_pda, _) = find_mint_address(&mint_signer_pda); + + // Get proof for creating the mint account + let proof_result = get_create_accounts_proof( + &rpc, + &program_id, + vec![CreateAccountsProofInput::mint(mint_signer_pda)], + ) + .await + .unwrap(); + + let accounts = light_token_macro_create_mint::accounts::CreateMint { + fee_payer: payer.pubkey(), + authority: authority.pubkey(), + mint_signer: mint_signer_pda, + mint: mint_pda, + compression_config: config_pda, + light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, + rent_sponsor: RENT_SPONSOR, + light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(), + light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(), + system_program: solana_sdk::system_program::ID, + }; + + let instruction_data = light_token_macro_create_mint::instruction::CreateMint { + params: CreateMintParams { + create_accounts_proof: proof_result.create_accounts_proof, + mint_signer_bump, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority]) + .await + .expect("CreateMint should succeed"); + + // Verify mint exists on-chain + let mint_account = rpc + .get_account(mint_pda) + .await + .unwrap() + .expect("Mint should exist on-chain"); + + // Parse and verify mint data + use light_token_interface::state::Mint; + let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..]) + .expect("Failed to deserialize Mint"); + + // Verify decimals match what was specified in #[light_account(init)] + assert_eq!(mint.base.decimals, 9, "Mint should have 9 decimals"); + + // Verify mint authority matches fee_payer + assert_eq!( + mint.base.mint_authority, + Some(payer.pubkey().to_bytes().into()), + "Mint authority should be fee_payer" + ); +} +``` + diff --git a/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx index b4364199..f2bbaf12 100644 --- a/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx @@ -129,16 +129,16 @@ pub struct CreateMintAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; -use light_token_anchor_create_mint::{instruction::CreateMint, ID}; +use light_token_anchor_create_mint::{accounts, instruction::CreateMint, ID}; use light_token::instruction::{ config_pda, derive_mint_compressed_address, find_mint_address, rent_sponsor_pda, SystemAccounts, LIGHT_TOKEN_PROGRAM_ID, }; use anchor_lang::system_program; use solana_sdk::{ - instruction::{AccountMeta, Instruction}, + instruction::Instruction, signature::Keypair, signer::Signer, }; @@ -180,23 +180,24 @@ async fn test_create_mint() { // Call the anchor program to create mint let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(mint_seed.pubkey(), true), - AccountMeta::new_readonly(mint_authority, false), - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(address_tree.tree, false), - AccountMeta::new(output_queue, false), - AccountMeta::new_readonly(system_accounts.light_system_program, false), - AccountMeta::new_readonly(system_accounts.cpi_authority_pda, false), - AccountMeta::new_readonly(system_accounts.registered_program_pda, false), - AccountMeta::new_readonly(system_accounts.account_compression_authority, false), - AccountMeta::new_readonly(system_accounts.account_compression_program, false), - AccountMeta::new_readonly(system_program::ID, false), - AccountMeta::new_readonly(config_pda(), false), - AccountMeta::new(mint_pda, false), - AccountMeta::new(rent_sponsor_pda(), false), - ], + accounts: accounts::CreateMintAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + mint_seed: mint_seed.pubkey(), + authority: mint_authority, + payer: payer.pubkey(), + address_tree: address_tree.tree, + output_queue, + light_system_program: system_accounts.light_system_program, + cpi_authority_pda: system_accounts.cpi_authority_pda, + registered_program_pda: system_accounts.registered_program_pda, + account_compression_authority: system_accounts.account_compression_authority, + account_compression_program: system_accounts.account_compression_program, + system_program: system_program::ID, + compressible_config: config_pda(), + mint: mint_pda, + rent_sponsor: rent_sponsor_pda(), + } + .to_account_metas(Some(true)), data: CreateMint { decimals, address_merkle_tree_root_index: rpc_result.addresses[0].root_index, diff --git a/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx b/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx index 50514950..25a3bfe7 100644 --- a/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx +++ b/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx @@ -6,8 +6,8 @@ use light_compressible::CreateAccountsProof; use light_token::instruction::{ CreateMintCpi, CreateMintParams, SystemAccountInfos, }; -use light_token_interface::instructions::extensions::{ - token_metadata::TokenMetadataInstructionData, ExtensionInstructionData, +use light_token::instruction::{ + ExtensionInstructionData, TokenMetadataInstructionData, }; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, @@ -34,23 +34,8 @@ pub fn create_mint_invoke( accounts: &[AccountInfo], data: &[u8], ) -> ProgramResult { - let [ - mint_seed, - authority, - payer, - address_tree, - output_queue, - compressible_config, - mint, - rent_sponsor, - light_system_program, - cpi_authority_pda, - registered_program_pda, - account_compression_authority, - account_compression_program, - system_program, - _token_program, - ] = accounts + let [mint_seed, authority, payer, address_tree, output_queue, compressible_config, mint, rent_sponsor, light_system_program, cpi_authority_pda, registered_program_pda, account_compression_authority, account_compression_program, system_program, _token_program] = + accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -142,23 +127,8 @@ pub fn create_mint_invoke_signed( accounts: &[AccountInfo], data: &[u8], ) -> ProgramResult { - let [ - mint_seed, - authority, - payer, - address_tree, - output_queue, - compressible_config, - mint, - rent_sponsor, - light_system_program, - cpi_authority_pda, - registered_program_pda, - account_compression_authority, - account_compression_program, - system_program, - _token_program, - ] = accounts + let [mint_seed, authority, payer, address_tree, output_queue, compressible_config, mint, rent_sponsor, light_system_program, cpi_authority_pda, registered_program_pda, account_compression_authority, account_compression_program, system_program, _token_program] = + accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -238,10 +208,10 @@ use light_client::{ indexer::{AddressWithTree, Indexer}, rpc::Rpc, }; -use light_compressible::CreateAccountsProof; use light_compressed_account::instruction_data::{ compressed_proof::ValidityProof, data::PackedAddressTreeInfo, }; +use light_compressible::CreateAccountsProof; use light_token::instruction::{ config_pda, derive_mint_compressed_address, find_mint_address, rent_sponsor_pda, SystemAccounts, LIGHT_TOKEN_PROGRAM_ID, diff --git a/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx b/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx index ae48bd3f..5432b8eb 100644 --- a/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx @@ -2,14 +2,13 @@ use light_client::rpc::Rpc; use light_token_client::actions::{CreateMint, TokenMetadata}; use rust_client::setup_rpc_and_payer; -use solana_sdk::{signature::Keypair, signer::Signer}; +use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { let (mut rpc, payer) = setup_rpc_and_payer().await; - let mint_seed = Keypair::new(); - let result = CreateMint { + let (signature, mint) = CreateMint { decimals: 9, freeze_authority: None, token_metadata: Some(TokenMetadata { @@ -19,17 +18,13 @@ async fn main() -> Result<(), Box> { update_authority: Some(payer.pubkey()), additional_metadata: Some(vec![("type".to_string(), "example".to_string())]), }), + seed: None, } - .execute(&mut rpc, &payer, &mint_seed) + .execute(&mut rpc, &payer, &payer) .await?; - let data = rpc.get_account(result.mint).await?; - println!( - "Mint: {} exists: {} Tx: {}", - result.mint, - data.is_some(), - result.signature - ); + let data = rpc.get_account(mint).await?; + println!("Mint: {mint} exists: {} Tx: {signature}", data.is_some()); Ok(()) } diff --git a/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx new file mode 100644 index 00000000..757c477a --- /dev/null +++ b/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx @@ -0,0 +1,251 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_compressible::CreateAccountsProof; +use light_sdk::derive_light_cpi_signer; +use light_sdk_macros::{light_program, LightAccounts}; +use light_sdk_types::CpiSigner; +use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR}; + +declare_id!("9p5BUDtVmRRJqp2sN73ZUZDbaYtYvEWuxzrHH3A2ni9y"); + +pub const LIGHT_CPI_SIGNER: CpiSigner = + derive_light_cpi_signer!("9p5BUDtVmRRJqp2sN73ZUZDbaYtYvEWuxzrHH3A2ni9y"); + +pub const VAULT_AUTH_SEED: &[u8] = b"vault_auth"; +pub const VAULT_SEED: &[u8] = b"vault"; + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct CreateTokenVaultParams { + pub create_accounts_proof: CreateAccountsProof, + pub vault_bump: u8, +} + +#[derive(Accounts, LightAccounts)] +#[instruction(params: CreateTokenVaultParams)] +pub struct CreateTokenVault<'info> { + #[account(mut)] + pub fee_payer: Signer<'info>, + + /// CHECK: Validated by light-token CPI + // You can use Light, SPL, or Token-2022 mints to create a light token account. + pub mint: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account( + seeds = [VAULT_AUTH_SEED], + bump, + )] + pub vault_authority: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + #[account( + mut, + seeds = [VAULT_SEED, mint.key().as_ref()], + bump, + )] + #[light_account( + init, + token::authority = [VAULT_SEED, self.mint.key()], + token::mint = mint, + token::owner = vault_authority, + token::bump = params.vault_bump + )] + pub vault: UncheckedAccount<'info>, + + /// CHECK: Validated by light-token CPI + #[account(address = COMPRESSIBLE_CONFIG_V1)] + pub light_token_compressible_config: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)] + pub light_token_rent_sponsor: AccountInfo<'info>, + + /// CHECK: Validated by light-token CPI + pub light_token_cpi_authority: AccountInfo<'info>, + + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + + pub system_program: Program<'info, System>, +} + +#[light_program] +#[program] +pub mod light_token_macro_create_token_account { + use super::*; + + #[allow(unused_variables)] + pub fn create_token_vault<'info>( + ctx: Context<'_, '_, '_, 'info, CreateTokenVault<'info>>, + params: CreateTokenVaultParams, + ) -> Result<()> { + Ok(()) + } +} +``` + +```rust test.rs +//! Integration test for token vault creation using the macro. +//! +//! Note: This test requires a properly configured test environment with +//! `light-program-test` that has compatible dependency versions. +//! The test follows the pattern from light-protocol/sdk-tests/single-token-test. + +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_client::{ + indexer::AddressWithTree, + interface::get_create_accounts_proof, +}; +use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; +use light_token::instruction::{ + CreateMint, CreateMintParams, LIGHT_TOKEN_PROGRAM_ID, derive_mint_compressed_address, + find_mint_address, +}; +use solana_instruction::Instruction; +use solana_keypair::Keypair; +use solana_pubkey::Pubkey; +use solana_signer::Signer; + +/// Create a compressed mint for testing. +async fn setup_create_mint( + rpc: &mut LightProgramTest, + payer: &Keypair, + mint_authority: Pubkey, + decimals: u8, +) -> (Pubkey, Keypair) { + let mint_seed = Keypair::new(); + let address_tree = rpc.get_address_tree_v2(); + let output_queue = rpc.get_random_state_tree_info().unwrap().queue; + + let compression_address = + derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); + + let (mint, bump) = find_mint_address(&mint_seed.pubkey()); + + let rpc_result = rpc + .get_validity_proof( + vec![], + vec![AddressWithTree { + address: compression_address, + tree: address_tree.tree, + }], + None, + ) + .await + .unwrap() + .value; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index: rpc_result.addresses[0].root_index, + mint_authority, + proof: rpc_result.proof.0.unwrap(), + compression_address, + mint, + bump, + freeze_authority: None, + extensions: None, + rent_payment: 16, + write_top_up: 766, + }; + + let create_mint_builder = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ); + let instruction = create_mint_builder.instruction().unwrap(); + + rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[payer, &mint_seed]) + .await + .unwrap(); + + (mint, mint_seed) +} + +/// Test creating a token vault using the `#[light_account(init, token, ...)]` macro. +/// +/// This test verifies: +/// 1. The macro-annotated program compiles correctly +/// 2. A token vault can be created via the generated CPI +/// 3. The vault has the correct owner and mint +#[tokio::test] +async fn test_create_token_vault() { + use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR}; + use light_token_macro_create_token_account::{ + CreateTokenVaultParams, VAULT_AUTH_SEED, VAULT_SEED, + }; + use light_token_types::CPI_AUTHORITY_PDA; + + let program_id = light_token_macro_create_token_account::ID; + let config = ProgramTestConfig::new_v2( + true, + Some(vec![("light_token_macro_create_token_account", program_id)]), + ); + + let mut rpc = LightProgramTest::new(config).await.unwrap(); + let payer = rpc.get_payer().insecure_clone(); + + // Create a mint first + let (mint, _mint_seed) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await; + + // Derive PDAs + let (vault_authority, _auth_bump) = + Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id); + let (vault, vault_bump) = + Pubkey::find_program_address(&[VAULT_SEED, mint.as_ref()], &program_id); + + // Get proof for token-only instruction (empty inputs) + let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![]) + .await + .unwrap(); + + // Build instruction accounts + let accounts = light_token_macro_create_token_account::accounts::CreateTokenVault { + fee_payer: payer.pubkey(), + mint, + vault_authority, + vault, + light_token_compressible_config: COMPRESSIBLE_CONFIG_V1, + light_token_rent_sponsor: RENT_SPONSOR, + light_token_cpi_authority: CPI_AUTHORITY_PDA.into(), + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + system_program: solana_sdk::system_program::ID, + }; + + // Build instruction data + let instruction_data = light_token_macro_create_token_account::instruction::CreateTokenVault { + params: CreateTokenVaultParams { + create_accounts_proof: proof_result.create_accounts_proof, + vault_bump, + }, + }; + + let instruction = Instruction { + program_id, + accounts: [ + accounts.to_account_metas(None), + proof_result.remaining_accounts, + ] + .concat(), + data: instruction_data.data(), + }; + + // Execute the instruction + // Note: This may fail without InitializeRentFreeConfig setup. + // The full test requires rent-free config initialization. + let result = rpc + .create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer]) + .await; + + // For now, we verify the instruction builds correctly. + // Full execution requires additional setup (InitializeRentFreeConfig, etc.) + println!("Transaction result: {:?}", result); +} +``` + diff --git a/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx index b1d74ffc..ab0d9280 100644 --- a/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx @@ -35,7 +35,7 @@ pub struct CreateTokenAccountAccounts<'info> { pub payer: Signer<'info>, /// CHECK: Validated by light-token CPI #[account(mut)] - pub account: AccountInfo<'info>, + pub account: Signer<'info>, /// CHECK: Validated by light-token CPI pub mint: AccountInfo<'info>, /// CHECK: Validated by light-token CPI @@ -50,17 +50,17 @@ pub struct CreateTokenAccountAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_client::indexer::AddressWithTree; use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; -use light_token_anchor_create_token_account::{instruction::CreateTokenAccount, ID}; +use light_token_anchor_create_token_account::{accounts, instruction::CreateTokenAccount, ID}; use light_token::instruction::{ CreateMint, CreateMintParams, config_pda, derive_mint_compressed_address, find_mint_address, rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID, }; use anchor_lang::system_program; use solana_sdk::{ - instruction::{AccountMeta, Instruction}, + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, @@ -137,15 +137,16 @@ async fn test_create_token_account() { let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(token_account.pubkey(), true), - AccountMeta::new_readonly(mint_pda, false), - AccountMeta::new_readonly(compressible_config, false), - AccountMeta::new(rent_sponsor, false), - AccountMeta::new_readonly(system_program::ID, false), - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - ], + accounts: accounts::CreateTokenAccountAccounts { + payer: payer.pubkey(), + account: token_account.pubkey(), + mint: mint_pda, + compressible_config, + rent_sponsor, + system_program: system_program::ID, + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + } + .to_account_metas(Some(true)), data: CreateTokenAccount { owner }.data(), }; diff --git a/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx index ab67fd63..2a195a1f 100644 --- a/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx @@ -36,14 +36,11 @@ pub struct FreezeAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::Rpc; use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_freeze::{instruction::Freeze, ID}; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - signer::Signer, -}; +use light_token_anchor_freeze::{accounts, instruction::Freeze, ID}; +use solana_sdk::{instruction::Instruction, signer::Signer}; use test_utils::{mint_tokens, setup_test_env_with_freeze}; #[tokio::test] @@ -56,12 +53,13 @@ async fn test_freeze() { // Call the anchor program to freeze account let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(env.ata, false), - AccountMeta::new_readonly(env.mint_pda, false), - AccountMeta::new_readonly(env.freeze_authority, true), - ], + accounts: accounts::FreezeAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + token_account: env.ata, + mint: env.mint_pda, + freeze_authority: env.freeze_authority, + } + .to_account_metas(Some(true)), data: Freeze {}.data(), }; diff --git a/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx b/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx index 50b5c377..f0156b5b 100644 --- a/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx +++ b/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx @@ -91,7 +91,7 @@ async fn freeze_signed_cpi() { let initial_amount = 1_000_000u64; // Create mint with PDA as freeze authority and mint tokens to payer - let (mint, atas) = setup_mint_with_tokens( + let (mint, associated_token_accounts) = setup_mint_with_tokens( &mut rpc, &payer, payer.pubkey(), @@ -101,7 +101,7 @@ async fn freeze_signed_cpi() { ) .await; - let ata = atas[0]; + let ata = associated_token_accounts[0]; let ix = build_freeze_signed_cpi_ix(ata, mint, pda_authority, bump); diff --git a/snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx new file mode 100644 index 00000000..5f85684c --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx @@ -0,0 +1,88 @@ + +```rust lib.rs +#![allow(unexpected_cfgs, deprecated)] + +use anchor_lang::prelude::*; +use light_token::instruction::MintToCheckedCpi; + +declare_id!("DGu3ofzac2Zndn95z2q9gCp8zHgW22YpMeEWj2up3QDb"); + +#[program] +pub mod light_token_anchor_mint_to_checked { + use super::*; + + pub fn mint_to_checked( + ctx: Context, + amount: u64, + decimals: u8, + ) -> Result<()> { + MintToCheckedCpi { + mint: ctx.accounts.mint.to_account_info(), + destination: ctx.accounts.destination.to_account_info(), + amount, + decimals, + authority: ctx.accounts.authority.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + max_top_up: None, + fee_payer: None, + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct MintToCheckedAccounts<'info> { + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub mint: AccountInfo<'info>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub destination: AccountInfo<'info>, + pub authority: Signer<'info>, + pub system_program: Program<'info, System>, +} +``` + +```rust test.rs +use anchor_lang::{InstructionData, ToAccountMetas}; +use light_program_test::Rpc; +use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; +use light_token_anchor_mint_to_checked::{accounts, instruction::MintToChecked, ID}; +use solana_sdk::{instruction::Instruction, signer::Signer, system_program}; +use test_utils::setup_test_env; + +#[tokio::test(flavor = "multi_thread")] +async fn test_mint_to_checked() { + let mut env = setup_test_env("light_token_anchor_mint_to_checked", ID).await; + + // MintToChecked validates decimals match the mint's decimals. + // No mint_tokens call - the test IS minting tokens via CPI. + let amount = 1_000_000u64; + let decimals = 9u8; + let ix = Instruction { + program_id: ID, + accounts: accounts::MintToCheckedAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + mint: env.mint_pda, + destination: env.ata, + authority: env.payer.pubkey(), + system_program: system_program::ID, + } + .to_account_metas(Some(true)), + data: MintToChecked { amount, decimals }.data(), + }; + + env.rpc + .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) + .await + .unwrap(); + + // Verify the account exists and has data + let ata_data = env.rpc.get_account(env.ata).await.unwrap().unwrap(); + assert!(!ata_data.data.is_empty(), "ATA account should have data"); +} +``` + diff --git a/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx index 8c824529..1803ba14 100644 --- a/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx +++ b/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx @@ -7,7 +7,10 @@ use solana_program::{ program_error::ProgramError, }; -pub fn mint_to_checked_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +pub fn mint_to_checked_invoke( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { let [mint, destination, authority, system_program, _token_program] = accounts else { @@ -74,8 +77,8 @@ mod shared; use light_client::rpc::Rpc; use shared::{ build_mint_to_checked_cpi_ix, build_mint_to_checked_signed_cpi_ix, - create_test_rpc, get_authority_pda, setup_mint_with_pda_authority, - setup_mint_with_tokens, create_ata, + create_ata, create_test_rpc, get_authority_pda, + setup_mint_with_pda_authority, setup_mint_with_tokens, }; use solana_sdk::{signature::Keypair, signer::Signer}; @@ -84,7 +87,7 @@ async fn mint_to_checked_cpi() { let mut rpc = create_test_rpc().await; let payer = rpc.get_payer().insecure_clone(); - let (mint, atas) = setup_mint_with_tokens( + let (mint, associated_token_accounts) = setup_mint_with_tokens( &mut rpc, &payer, payer.pubkey(), @@ -98,7 +101,7 @@ async fn mint_to_checked_cpi() { let ix = build_mint_to_checked_cpi_ix( mint, - atas[0], + associated_token_accounts[0], payer.pubkey(), mint_amount, 9, @@ -116,10 +119,12 @@ async fn mint_to_checked_signed_cpi() { let (pda_authority, bump) = get_authority_pda(); - let mint = setup_mint_with_pda_authority(&mut rpc, &payer, pda_authority, 9).await; + let mint = + setup_mint_with_pda_authority(&mut rpc, &payer, pda_authority, 9).await; let recipient = Keypair::new(); - let recipient_ata = create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; + let recipient_ata = + create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; let mint_amount = 1_000_000u64; diff --git a/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx index e715a575..333e63a6 100644 --- a/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx @@ -42,15 +42,11 @@ pub struct MintToAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::Rpc; use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_mint_to::{instruction::MintTo, ID}; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - signer::Signer, - system_program, -}; +use light_token_anchor_mint_to::{accounts, instruction::MintTo, ID}; +use solana_sdk::{instruction::Instruction, signer::Signer, system_program}; use test_utils::setup_test_env; #[tokio::test(flavor = "multi_thread")] @@ -61,13 +57,14 @@ async fn test_mint_to() { let amount = 1_000_000u64; let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(env.mint_pda, false), - AccountMeta::new(env.ata, false), - AccountMeta::new_readonly(env.payer.pubkey(), true), - AccountMeta::new_readonly(system_program::ID, false), - ], + accounts: accounts::MintToAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + mint: env.mint_pda, + destination: env.ata, + authority: env.payer.pubkey(), + system_program: system_program::ID, + } + .to_account_metas(Some(true)), data: MintTo { amount }.data(), }; diff --git a/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx b/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx index 58a4a0e1..3a741321 100644 --- a/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx @@ -3,39 +3,38 @@ use borsh::BorshDeserialize; use light_client::rpc::Rpc; use light_token_client::actions::{CreateAta, CreateMint, MintTo}; use rust_client::setup_rpc_and_payer; -use solana_sdk::{signature::Keypair, signer::Signer}; +use solana_sdk::signer::Signer; #[tokio::main] async fn main() -> Result<(), Box> { let (mut rpc, payer) = setup_rpc_and_payer().await; - let mint_seed = Keypair::new(); - // Create mint - let mint_result = CreateMint { + // Create mint (payer is also mint authority) + let (_signature, mint) = CreateMint { decimals: 9, freeze_authority: None, token_metadata: None, + seed: None, } - .execute(&mut rpc, &payer, &mint_seed) + .execute(&mut rpc, &payer, &payer) .await?; // Create associated token account - let associated_token_account = CreateAta { - mint: mint_result.mint, + let (_signature, associated_token_account) = CreateAta { + mint, owner: payer.pubkey(), idempotent: true, } .execute(&mut rpc, &payer) .await?; - // Mint tokens + // Mint tokens (payer is mint authority) let sig = MintTo { - mint: mint_result.mint, + mint, destination: associated_token_account, amount: 1_000_000, - max_top_up: None, } - .execute(&mut rpc, &payer, &mint_seed) + .execute(&mut rpc, &payer, &payer) .await?; let data = rpc.get_account(associated_token_account).await?.ok_or("Account not found")?; diff --git a/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx index 84dd14ac..bae31171 100644 --- a/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx @@ -35,16 +35,11 @@ pub struct RevokeAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::Rpc; use light_token::instruction::{Approve, LIGHT_TOKEN_PROGRAM_ID}; -use light_token_anchor_revoke::{instruction::Revoke, ID}; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - signature::Keypair, - signer::Signer, - system_program, -}; +use light_token_anchor_revoke::{accounts, instruction::Revoke, ID}; +use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer, system_program}; use test_utils::{mint_tokens, setup_test_env}; #[tokio::test] @@ -74,12 +69,13 @@ async fn test_revoke() { // Call the anchor program to revoke delegation let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(env.ata, false), - AccountMeta::new_readonly(env.payer.pubkey(), true), - AccountMeta::new_readonly(system_program::ID, false), - ], + accounts: accounts::RevokeAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + token_account: env.ata, + owner: env.payer.pubkey(), + system_program: system_program::ID, + } + .to_account_metas(Some(true)), data: Revoke {}.data(), }; diff --git a/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx index 036d5335..3f51ad98 100644 --- a/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx @@ -36,14 +36,11 @@ pub struct ThawAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::Rpc; use light_token::instruction::{Freeze, LIGHT_TOKEN_PROGRAM_ID}; -use light_token_anchor_thaw::{instruction::Thaw, ID}; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - signer::Signer, -}; +use light_token_anchor_thaw::{accounts, instruction::Thaw, ID}; +use solana_sdk::{instruction::Instruction, signer::Signer}; use test_utils::{mint_tokens, setup_test_env_with_freeze}; #[tokio::test] @@ -70,12 +67,13 @@ async fn test_thaw() { // Call the anchor program to thaw account let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(env.ata, false), - AccountMeta::new_readonly(env.mint_pda, false), - AccountMeta::new_readonly(env.freeze_authority, true), - ], + accounts: accounts::ThawAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + token_account: env.ata, + mint: env.mint_pda, + freeze_authority: env.freeze_authority, + } + .to_account_metas(Some(true)), data: Thaw {}.data(), }; diff --git a/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx index 4f602bb2..63546a9b 100644 --- a/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx @@ -50,16 +50,11 @@ pub struct TransferCheckedAccounts<'info> { ``` ```rust test.rs -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::Rpc; use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_transfer_checked::{instruction::TransferChecked, ID}; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - signature::Keypair, - signer::Signer, - system_program, -}; +use light_token_anchor_transfer_checked::{accounts, instruction::TransferChecked, ID}; +use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer, system_program}; use test_utils::{create_ata_for_owner, mint_tokens, setup_test_env}; #[tokio::test] @@ -80,14 +75,15 @@ async fn test_transfer_checked() { let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(env.ata, false), - AccountMeta::new_readonly(env.mint_pda, false), - AccountMeta::new(dest_ata, false), - AccountMeta::new_readonly(env.payer.pubkey(), true), - AccountMeta::new_readonly(system_program::ID, false), - ], + accounts: accounts::TransferCheckedAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + source: env.ata, + mint: env.mint_pda, + destination: dest_ata, + authority: env.payer.pubkey(), + system_program: system_program::ID, + } + .to_account_metas(Some(true)), data: TransferChecked { amount: transfer_amount, decimals, diff --git a/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx index 58e4033d..646df352 100644 --- a/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx +++ b/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx @@ -7,7 +7,10 @@ use solana_program::{ program_error::ProgramError, }; -pub fn transfer_checked_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +pub fn transfer_checked_invoke( + accounts: &[AccountInfo], + data: &[u8], +) -> ProgramResult { let [source, mint, destination, authority, system_program, _token_program] = accounts else { diff --git a/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx b/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx index 229ce739..165e0989 100644 --- a/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx @@ -18,7 +18,7 @@ async fn main() -> Result<(), Box> { // Create recipient associated token account let recipient = Keypair::new(); - let recipient_associated_token_account = CreateAta { + let (_signature, recipient_associated_token_account) = CreateAta { mint, owner: recipient.pubkey(), idempotent: true, @@ -35,7 +35,6 @@ async fn main() -> Result<(), Box> { destination: recipient_associated_token_account, amount: 1000, decimals, - max_top_up: None, } .execute(&mut rpc, &payer, &payer) .await?; diff --git a/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx index 87120dee..3c6de46b 100644 --- a/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx +++ b/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx @@ -48,13 +48,13 @@ pub struct TransferAccounts<'info> { ```rust test.rs use anchor_lang::system_program; -use anchor_lang::InstructionData; +use anchor_lang::{InstructionData, ToAccountMetas}; use light_program_test::Rpc; use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_transfer_interface::{instruction::Transfer, ID}; +use light_token_anchor_transfer_interface::{accounts, instruction::Transfer, ID}; use light_token_types::CPI_AUTHORITY_PDA; use solana_sdk::{ - instruction::{AccountMeta, Instruction}, + instruction::Instruction, pubkey::Pubkey, signature::Keypair, signer::Signer, @@ -78,15 +78,16 @@ async fn test_transfer() { let ix = Instruction { program_id: ID, - accounts: vec![ - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - AccountMeta::new(env.ata, false), - AccountMeta::new(dest_ata, false), - AccountMeta::new_readonly(env.payer.pubkey(), true), - AccountMeta::new(env.payer.pubkey(), true), - AccountMeta::new_readonly(cpi_authority_pda, false), - AccountMeta::new_readonly(system_program::ID, false), - ], + accounts: accounts::TransferAccounts { + light_token_program: LIGHT_TOKEN_PROGRAM_ID, + source: env.ata, + destination: dest_ata, + authority: env.payer.pubkey(), + payer: env.payer.pubkey(), + cpi_authority: cpi_authority_pda, + system_program: system_program::ID, + } + .to_account_metas(Some(true)), data: Transfer { amount: transfer_amount, decimals, diff --git a/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx b/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx index a6f21205..61f2c9c5 100644 --- a/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx +++ b/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx @@ -18,7 +18,7 @@ async fn main() -> Result<(), Box> { // Create recipient associated token account let recipient = Keypair::new(); - let recipient_associated_token_account = CreateAta { + let (_signature, recipient_associated_token_account) = CreateAta { mint, owner: recipient.pubkey(), idempotent: true, From b081708448d353b9346c01a29af1b9175120a269 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Wed, 28 Jan 2026 22:40:26 +0000 Subject: [PATCH 06/21] fix animation compare --- snippets/jsx/code-compare.jsx | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/snippets/jsx/code-compare.jsx b/snippets/jsx/code-compare.jsx index 43afb326..3d8370ab 100644 --- a/snippets/jsx/code-compare.jsx +++ b/snippets/jsx/code-compare.jsx @@ -11,6 +11,9 @@ export const CodeCompare = ({ const [copied, setCopied] = useState(false); const containerRef = useRef(null); const animationRef = useRef(null); + const firstPreRef = useRef(null); + const secondPreRef = useRef(null); + const [containerHeight, setContainerHeight] = useState(null); // When slider is on the right (100%), show first code; on left (0%), show second code const showingFirst = sliderPercent > 50; @@ -151,6 +154,14 @@ export const CodeCompare = ({ }; }, []); + // Measure and animate height when toggling + useEffect(() => { + const activeRef = showingFirst ? firstPreRef : secondPreRef; + if (activeRef.current) { + setContainerHeight(activeRef.current.scrollHeight); + } + }, [showingFirst]); + return ( <>
-
-
+
+
{/* Second code (background) - shown when slider is on left */}
Date: Wed, 28 Jan 2026 23:18:09 +0000
Subject: [PATCH 07/21] add compares

---
 light-token/cookbook/create-ata.mdx           | 101 +++++++++-
 light-token/cookbook/create-mint.mdx          | 173 ++++++++---------
 light-token/cookbook/create-token-account.mdx | 106 +++++++++-
 .../code-samples/code-compare-snippets.jsx    | 151 +++++++++++----
 .../create-mint/anchor-macro/full-example.mdx | 181 ++++++++++++++++++
 5 files changed, 566 insertions(+), 146 deletions(-)

diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx
index d70c3144..f463a9c9 100644
--- a/light-token/cookbook/create-ata.mdx
+++ b/light-token/cookbook/create-ata.mdx
@@ -18,6 +18,10 @@ import {
   lightCreateAtaCode,
   splCreateAtaRustCode,
   lightCreateAtaRustCode,
+  splCreateAtaMacroCode,
+  lightCreateAtaMacroCode,
+  splCreateAtaCpiCode,
+  lightCreateAtaCpiCode,
 } from "/snippets/code-samples/code-compare-snippets.jsx";
 import ActionCode from "/snippets/code-snippets/light-token/create-ata/action.mdx";
 import InstructionCode from "/snippets/code-snippets/light-token/create-ata/instruction.mdx";
@@ -120,6 +124,18 @@ Compare to SPL:
 
 
 
+
+
+
+Compare to SPL:
+
+
 
 
 Find [a full code example at the end](#full-code-example).
@@ -193,9 +209,6 @@ CreateAssociatedAccountCpi {
 
 # Full Code Example
 
-
-
-
 
   View the full example with shared test utilities:
   [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-ata).
@@ -204,14 +217,92 @@ CreateAssociatedAccountCpi {
 
 
 
+
+
+Compare to SPL:
+
+
+
+
+Find [a full code example at the end](#full-code-example-1).
+
+
+
+
+
+### Dependencies
+
+```toml
+[dependencies]
+light-sdk = { version = "0.18.0", features = ["anchor", "v2", "cpi-context"] }
+light-sdk-macros = "0.18.0"
+light-compressible = "0.1.0"
+anchor-lang = "0.31"
+```
+
+
+
+
+### Program
+
+Add `#[light_program]` above `#[program]`:
+
+```rust
+use light_sdk_macros::light_program;
+
+#[light_program]
+#[program]
+pub mod light_token_macro_create_ata {
+    use super::*;
+
+    pub fn create_ata<'info>(
+        ctx: Context<'_, '_, '_, 'info, CreateAta<'info>>,
+        params: CreateAtaParams,
+    ) -> Result<()> {
+        Ok(())
+    }
+}
+```
+
+
+
+
+### Accounts struct
+
+Derive `LightAccounts` on your `Accounts` struct and add `#[light_account(...)]` next to `#[account(...)]`.
 
-
+```rust
+/// CHECK: Validated by light-token CPI
+#[account(mut)]
+#[light_account(init,
+    associated_token::authority = ata_owner,
+    associated_token::mint = ata_mint,
+    associated_token::bump = params.ata_bump
+)]
+pub ata: UncheckedAccount<'info>,
+```
+
+
+
+
+
+# Full code example
+
+
+  View the full example with test:
+  [examples-light-token-anchor](https://github.com/Lightprotocol/examples-light-token-anchor/tree/main/program-examples/anchor/basic-macros/create-ata).
+
 
 
 
 
 
-
 
 
 
diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx
index f3ed5982..9a6c7ae2 100644
--- a/light-token/cookbook/create-mint.mdx
+++ b/light-token/cookbook/create-mint.mdx
@@ -140,6 +140,16 @@ Compare to SPL:
 
 
 
+Compare to SPL:
+
+
+
 
 Find [a full code example at the end](#full-code-example).
 
@@ -335,10 +345,6 @@ CreateMintCpi::new(
 
 
 
-
-Find [a full code example at the end](#full-code-example).
-
-
 Compare to SPL:
 
 
@@ -362,20 +368,28 @@ Compare to SPL:
 
 
 
+
+Find [a full code example at the end](#full-code-example).
+
 
-Dependencies
+
+
+
+### Dependencies
 
 ```toml
 [dependencies]
-
 light-sdk = { version = "0.18.0", features = ["anchor", "v2", "cpi-context"] }
-light-sdk-macros = { version = "0.18.0" }
-light-token = { version = "0.3.0", features = ["anchor"] }
-
-light-anchor-spl = { version = "0.31" }    # TokenInterface uses light_token::ID
+light-sdk-macros = "0.18.0"
+light-compressible = "0.1.0"
 anchor-lang = "0.31"
 ```
 
+
+
+
+### Program
+
 Add `#[light_program]` above `#[program]`:
 
 ```rust
@@ -383,109 +397,76 @@ use light_sdk_macros::light_program;
 
 #[light_program]
 #[program]
-pub mod my_amm {
+pub mod my_program {
     use super::*;
 
-    pub fn initialize_pool(ctx: Context, params: InitializeParams) -> Result<()> {
-        process_initialize_pool(ctx, params)
-    }
-
-    // These don't change
-    pub fn swap(ctx: Context, amount_in: u64, min_out: u64) -> Result<()> {
-        process_swap(ctx, amount_in, min_out)
+    pub fn create_mint<'info>(
+        ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>,
+        params: CreateMintParams,
+    ) -> Result<()> {
+        Ok(())
     }
 }
 ```
 
+
+
+
+### Accounts struct
+
 Derive `LightAccounts` on your `Accounts` struct and add `#[light_account(...)]` next to `#[account(...)]`.
 
+
+
+
 ```rust
+/// CHECK: Validated by light-token CPI
 #[account(mut)]
-#[light_account(
-    init,
-    mint,
-    mint_signer = lp_mint_signer,
-    authority = authority,
-    decimals = 9,
-    mint_seeds = &[LP_MINT_SIGNER_SEED, pool_state.key().as_ref(), &[params.mint_signer_bump]],
-    authority_seeds = &[AUTH_SEED.as_bytes(), &[params.authority_bump]]
+#[light_account(init,
+    mint::signer = mint_signer,
+    mint::authority = fee_payer,
+    mint::decimals = 9,
+    mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
+    mint::bump = params.mint_signer_bump
 )]
-pub lp_mint: UncheckedAccount<'info>,
+pub mint: UncheckedAccount<'info>,
 ```
 
-We also need to add `light_token_interface_config`, `rent_sponsor`, and `light_token_cpi_authority`.
+
+
 
 ```rust
-use light_sdk::interface::CreateAccountsProof;
-use light_sdk_macros::LightAccounts;
-use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR as LIGHT_TOKEN_RENT_SPONSOR};
-
-#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
-pub struct InitializeParams {
-    pub create_accounts_proof: CreateAccountsProof,
-    pub lp_mint_signer_bump: u8,
-    pub creator_lp_token_bump: u8,
-    pub authority_bump: u8,
-}
-
-#[derive(Accounts, LightAccounts)]
-#[instruction(params: InitializeParams)]
-pub struct InitializePool<'info> {
-    #[account(mut)]
-    pub creator: Signer<'info>,
-
-    #[account(mut, seeds = [AUTH_SEED.as_bytes()], bump)]
-    pub authority: UncheckedAccount<'info>,
-
-    #[account(
-        init,
-        seeds = [POOL_SEED.as_bytes(), token_0_mint.key().as_ref(), token_1_mint.key().as_ref()],
-        bump,
-        payer = creator,
-        space = 8 + PoolState::INIT_SPACE
-    )]
-    #[light_account(init)]
-    pub pool_state: Box>,
-
-    pub token_0_mint: Box>,
-    pub token_1_mint: Box>,
-
-    #[account(seeds = [POOL_LP_MINT_SIGNER_SEED, pool_state.key().as_ref()], bump)]
-    pub lp_mint_signer: UncheckedAccount<'info>,
-
-    #[account(mut)]
-    #[light_account(init, mint,
-        mint_signer = lp_mint_signer,
-        authority = authority,
-        decimals = 9,
-        mint_seeds = &[POOL_LP_MINT_SIGNER_SEED, self.pool_state.to_account_info().key.as_ref(), &[params.lp_mint_signer_bump]],
-        authority_seeds = &[AUTH_SEED.as_bytes(), &[params.authority_bump]]
-    )]
-    pub lp_mint: UncheckedAccount<'info>,
-
-    #[account(mut, seeds = [POOL_VAULT_SEED.as_bytes(), pool_state.key().as_ref(), token_0_mint.key().as_ref()], bump)]
-    #[light_account(token, authority = [AUTH_SEED.as_bytes()])]
-    pub token_0_vault: UncheckedAccount<'info>,
-
-    #[account(mut, seeds = [POOL_VAULT_SEED.as_bytes(), pool_state.key().as_ref(), token_1_mint.key().as_ref()], bump)]
-    #[light_account(token, authority = [AUTH_SEED.as_bytes()])]
-    pub token_1_vault: UncheckedAccount<'info>,
-
-    #[account(mut)]
-    pub creator_lp_token: UncheckedAccount<'info>,
-
-
-    pub light_interface_config: AccountInfo<'info>,
-    #[account(address = COMPRESSIBLE_CONFIG_V1)]
-    pub light_token_interface_config: AccountInfo<'info>,
-    #[account(mut, address = LIGHT_TOKEN_RENT_SPONSOR)]
-    pub rent_sponsor: AccountInfo<'info>,
-    pub light_token_program: AccountInfo<'info>,
-    pub light_token_cpi_authority: AccountInfo<'info>,
-    pub system_program: Program<'info, System>,
-}
+/// CHECK: Validated by light-token CPI
+#[account(mut)]
+#[light_account(init,
+    mint::signer = mint_signer,
+    mint::authority = fee_payer,
+    mint::decimals = 9,
+    mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
+    mint::bump = params.mint_signer_bump,
+    mint::name = params.name.clone(),
+    mint::symbol = params.symbol.clone(),
+    mint::uri = params.uri.clone(),
+    mint::update_authority = authority,
+    mint::additional_metadata = params.additional_metadata.clone()
+)]
+pub mint: UncheckedAccount<'info>,
 ```
 
+
+
+
+
+
+
+
+# Full code example
+
+
+  View the full example with test:
+  [examples-light-token-anchor](https://github.com/Lightprotocol/examples-light-token-anchor/tree/main/program-examples/anchor/basic-macros/create-mint).
+
+
 
 
 
diff --git a/light-token/cookbook/create-token-account.mdx b/light-token/cookbook/create-token-account.mdx
index bef33014..8724831f 100644
--- a/light-token/cookbook/create-token-account.mdx
+++ b/light-token/cookbook/create-token-account.mdx
@@ -14,6 +14,10 @@ import { CodeCompare } from "/snippets/jsx/code-compare.jsx";
 import {
   splCreateTokenAccountRustCode,
   lightCreateTokenAccountRustCode,
+  splCreateTokenAccountMacroCode,
+  lightCreateTokenAccountMacroCode,
+  splCreateTokenAccountCpiCode,
+  lightCreateTokenAccountCpiCode,
 } from "/snippets/code-samples/code-compare-snippets.jsx";
 import RustInstructionCode from "/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx";
 import AnchorProgramCode from "/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx";
@@ -69,6 +73,18 @@ Compare to SPL:
 
 
 
+
+
+
+Compare to SPL:
+
+
 
 
 Find [a full code example at the end](#full-code-example).
@@ -137,9 +153,6 @@ CreateTokenAccountCpi {
 
 # Full Code Example
 
-
-
-
 
   View the full example with shared test utilities:
   [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-token-account).
@@ -148,14 +161,97 @@ CreateTokenAccountCpi {
 
 
 
+
+
+Compare to SPL:
+
+
+
+
+Find [a full code example at the end](#full-code-example-1).
+
+
+
+
+
+### Dependencies
+
+```toml
+[dependencies]
+light-sdk = { version = "0.18.0", features = ["anchor", "v2", "cpi-context"] }
+light-sdk-macros = "0.18.0"
+light-compressible = "0.1.0"
+anchor-lang = "0.31"
+```
+
+
+
+
+### Program
+
+Add `#[light_program]` above `#[program]`:
+
+```rust
+use light_sdk_macros::light_program;
+
+#[light_program]
+#[program]
+pub mod light_token_macro_create_token_account {
+    use super::*;
+
+    pub fn create_token_vault<'info>(
+        ctx: Context<'_, '_, '_, 'info, CreateTokenVault<'info>>,
+        params: CreateTokenVaultParams,
+    ) -> Result<()> {
+        Ok(())
+    }
+}
+```
+
+
+
+
+### Accounts struct
+
+Derive `LightAccounts` on your `Accounts` struct and add `#[light_account(...)]` next to `#[account(...)]`.
 
-
+```rust
+/// CHECK: Validated by light-token CPI
+#[account(
+    mut,
+    seeds = [VAULT_SEED, mint.key().as_ref()],
+    bump,
+)]
+#[light_account(init,
+    token::authority = [VAULT_SEED, self.mint.key()],
+    token::mint = mint,
+    token::owner = vault_authority,
+    token::bump = params.vault_bump
+)]
+pub vault: UncheckedAccount<'info>,
+```
+
+
+
+
+
+# Full code example
+
+
+  View the full example with test:
+  [examples-light-token-anchor](https://github.com/Lightprotocol/examples-light-token-anchor/tree/main/program-examples/anchor/basic-macros/create-token-account).
+
 
 
 
 
 
-
 
 
 
diff --git a/snippets/code-samples/code-compare-snippets.jsx b/snippets/code-samples/code-compare-snippets.jsx
index d075fac0..3c47ffe2 100644
--- a/snippets/code-samples/code-compare-snippets.jsx
+++ b/snippets/code-samples/code-compare-snippets.jsx
@@ -3,7 +3,6 @@
 
 // === CREATE MINT ===
 export const splCreateMintCode = [
-  "// SPL createMint",
   'import { createMint } from "@solana/spl-token";',
   "",
   "const mint = await createMint(",
@@ -16,7 +15,6 @@ export const splCreateMintCode = [
 ].join("\n");
 
 export const lightCreateMintCode = [
-  "// light-token createMint",
   'import { createMintInterface } from "@lightprotocol/compressed-token";',
   "",
   "const { mint } = await createMintInterface(",
@@ -31,7 +29,6 @@ export const lightCreateMintCode = [
 
 // === MINT TO ===
 export const splMintToCode = [
-  "// SPL mintTo",
   'import { mintTo } from "@solana/spl-token";',
   "",
   "const tx = await mintTo(",
@@ -45,7 +42,6 @@ export const splMintToCode = [
 ].join("\n");
 
 export const lightMintToCode = [
-  "// light-token mintTo",
   'import { mintToInterface } from "@lightprotocol/compressed-token";',
   "",
   "const tx = await mintToInterface(",
@@ -60,7 +56,6 @@ export const lightMintToCode = [
 
 // === CREATE ATA ===
 export const splCreateAtaCode = [
-  "// SPL create ATA",
   'import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";',
   "",
   "const ata = await getOrCreateAssociatedTokenAccount(",
@@ -72,7 +67,6 @@ export const splCreateAtaCode = [
 ].join("\n");
 
 export const lightCreateAtaCode = [
-  "// light-token create ATA",
   'import { createAtaInterface } from "@lightprotocol/compressed-token";',
   "",
   "const ata = await createAtaInterface(",
@@ -85,7 +79,6 @@ export const lightCreateAtaCode = [
 
 // === TRANSFER ===
 export const splTransferCode = [
-  "// SPL transfer",
   'import { transfer } from "@solana/spl-token";',
   "",
   "const tx = await transfer(",
@@ -99,7 +92,6 @@ export const splTransferCode = [
 ].join("\n");
 
 export const lightTransferCode = [
-  "// light-token transfer",
   'import { transferInterface } from "@lightprotocol/compressed-token";',
   "",
   "const tx = await transferInterface(",
@@ -115,7 +107,6 @@ export const lightTransferCode = [
 
 // === TRANSFER (RUST) ===
 export const splTransferRustCode = [
-  "// SPL transfer",
   "use spl_token::instruction::transfer;",
   "",
   "let ix = transfer(",
@@ -129,7 +120,6 @@ export const splTransferRustCode = [
 ].join("\n");
 
 export const lightTransferRustCode = [
-  "// light-token transfer",
   "use light_token_sdk::token::TransferInterface;",
   "",
   "let ix = TransferInterface {",
@@ -149,7 +139,6 @@ export const lightTransferRustCode = [
 
 // === CREATE ATA (RUST) ===
 export const splCreateAtaRustCode = [
-  "// SPL create ATA",
   "use spl_associated_token_account::instruction::create_associated_token_account;",
   "",
   "let ix = create_associated_token_account(",
@@ -161,7 +150,6 @@ export const splCreateAtaRustCode = [
 ].join("\n");
 
 export const lightCreateAtaRustCode = [
-  "// light-token create ATA",
   "use light_token_sdk::token::CreateAssociatedTokenAccount;",
   "",
   "let ix = CreateAssociatedTokenAccount::new(",
@@ -174,7 +162,6 @@ export const lightCreateAtaRustCode = [
 
 // === CREATE MINT (RUST) ===
 export const splCreateMintRustCode = [
-  "// SPL create mint",
   "use spl_token::instruction::initialize_mint;",
   "",
   "let ix = initialize_mint(",
@@ -187,11 +174,9 @@ export const splCreateMintRustCode = [
 ].join("\n");
 
 export const lightCreateMintRustCode = [
-  "// light-token create mint",
   "use light_token_sdk::token::CreateMint;",
   "",
   "let ix = CreateMint::new(",
-  "    // includes decimals, mint_authority, freeze_authority, extensions, rent config",
   "    params,",
   "    mint_seed.pubkey(),",
   "    payer.pubkey(),",
@@ -203,7 +188,6 @@ export const lightCreateMintRustCode = [
 
 // === MINT TO (RUST) ===
 export const splMintToRustCode = [
-  "// SPL mint to",
   "use spl_token::instruction::mint_to;",
   "",
   "let ix = mint_to(",
@@ -217,7 +201,6 @@ export const splMintToRustCode = [
 ].join("\n");
 
 export const lightMintToRustCode = [
-  "// light-token mint to",
   "use light_token_sdk::token::MintTo;",
   "",
   "let ix = MintTo {",
@@ -232,7 +215,6 @@ export const lightMintToRustCode = [
 
 // === CREATE TOKEN ACCOUNT (RUST) ===
 export const splCreateTokenAccountRustCode = [
-  "// SPL create token account",
   "use spl_token::instruction::initialize_account;",
   "",
   "let ix = initialize_account(",
@@ -244,7 +226,6 @@ export const splCreateTokenAccountRustCode = [
 ].join("\n");
 
 export const lightCreateTokenAccountRustCode = [
-  "// light-token create token account",
   "use light_token_sdk::token::CreateTokenAccount;",
   "",
   "let ix = CreateTokenAccount::new(",
@@ -258,7 +239,6 @@ export const lightCreateTokenAccountRustCode = [
 
 // === CLOSE TOKEN ACCOUNT (RUST) ===
 export const splCloseAccountRustCode = [
-  "// SPL close account",
   "use spl_token::instruction::close_account;",
   "",
   "let ix = close_account(",
@@ -271,7 +251,6 @@ export const splCloseAccountRustCode = [
 ].join("\n");
 
 export const lightCloseAccountRustCode = [
-  "// light-token close account",
   "use light_token_sdk::token::{CloseAccount, LIGHT_TOKEN_PROGRAM_ID};",
   "",
   "let ix = CloseAccount::new(",
@@ -285,7 +264,6 @@ export const lightCloseAccountRustCode = [
 
 // === BLOG - CREATE ATA (different comments) ===
 export const blogSplCreateAtaCode = [
-  "// Create SPL token account",
   "const ix = createAssociatedTokenAccountInstruction(",
   "  payer,",
   "  ata,",
@@ -295,7 +273,6 @@ export const blogSplCreateAtaCode = [
 ].join("\n");
 
 export const blogLightCreateAtaCode = [
-  "// Create light-token account",
   "const ix = CreateAssociatedTokenAccount.new(",
   "  payer,",
   "  account,",
@@ -306,7 +283,6 @@ export const blogLightCreateAtaCode = [
 
 // === BURN (RUST) ===
 export const splBurnRustCode = [
-  "// SPL burn",
   "use spl_token::instruction::burn;",
   "",
   "let ix = burn(",
@@ -320,7 +296,6 @@ export const splBurnRustCode = [
 ].join("\n");
 
 export const lightBurnRustCode = [
-  "// light-token burn",
   "use light_token_sdk::token::Burn;",
   "",
   "let ix = Burn {",
@@ -335,7 +310,6 @@ export const lightBurnRustCode = [
 
 // === FREEZE (RUST) ===
 export const splFreezeRustCode = [
-  "// SPL freeze",
   "use spl_token::instruction::freeze_account;",
   "",
   "let ix = freeze_account(",
@@ -348,7 +322,6 @@ export const splFreezeRustCode = [
 ].join("\n");
 
 export const lightFreezeRustCode = [
-  "// light-token freeze",
   "use light_token_sdk::token::Freeze;",
   "",
   "let ix = Freeze {",
@@ -361,7 +334,6 @@ export const lightFreezeRustCode = [
 
 // === THAW (RUST) ===
 export const splThawRustCode = [
-  "// SPL thaw",
   "use spl_token::instruction::thaw_account;",
   "",
   "let ix = thaw_account(",
@@ -374,7 +346,6 @@ export const splThawRustCode = [
 ].join("\n");
 
 export const lightThawRustCode = [
-  "// light-token thaw",
   "use light_token_sdk::token::Thaw;",
   "",
   "let ix = Thaw {",
@@ -387,7 +358,6 @@ export const lightThawRustCode = [
 
 // === APPROVE (RUST) ===
 export const splApproveRustCode = [
-  "// SPL approve",
   "use spl_token::instruction::approve;",
   "",
   "let ix = approve(",
@@ -401,7 +371,6 @@ export const splApproveRustCode = [
 ].join("\n");
 
 export const lightApproveRustCode = [
-  "// light-token approve",
   "use light_token_sdk::token::Approve;",
   "",
   "let ix = Approve {",
@@ -415,7 +384,6 @@ export const lightApproveRustCode = [
 
 // === REVOKE (RUST) ===
 export const splRevokeRustCode = [
-  "// SPL revoke",
   "use spl_token::instruction::revoke;",
   "",
   "let ix = revoke(",
@@ -427,7 +395,6 @@ export const splRevokeRustCode = [
 ].join("\n");
 
 export const lightRevokeRustCode = [
-  "// light-token revoke",
   "use light_token_sdk::token::Revoke;",
   "",
   "let ix = Revoke {",
@@ -439,7 +406,6 @@ export const lightRevokeRustCode = [
 
 // === CREATE MINT MACRO (ANCHOR) ===
 export const splCreateMintMacroCode = [
-  "// SPL Token (Anchor)",
   "#[account(",
   "    init,",
   "    payer = fee_payer,",
@@ -450,7 +416,6 @@ export const splCreateMintMacroCode = [
 ].join("\n");
 
 export const lightCreateMintMacroCode = [
-  "// light-token (Anchor)",
   "#[light_account(init,",
   "    mint::signer = mint_signer,",
   "    mint::authority = fee_payer,",
@@ -463,8 +428,6 @@ export const lightCreateMintMacroCode = [
 
 // === CREATE MINT WITH METADATA MACRO (ANCHOR) ===
 export const splCreateMintMetadataMacroCode = [
-  "// SPL Token-2022 (Anchor)",
-  "// Macro — only MetadataPointer is declarative",
   "#[account(",
   "    init,",
   "    payer = fee_payer,",
@@ -475,8 +438,7 @@ export const splCreateMintMetadataMacroCode = [
   ")]",
   "pub mint_account: InterfaceAccount<'info, Mint>,",
   "",
-  "// Metadata fields require a separate CPI call",
-  "// in the instruction handler:",
+  "// Metadata requires a separate CPI:",
   "token_metadata_initialize(",
   "    cpi_ctx,",
   "    params.name,",
@@ -486,7 +448,6 @@ export const splCreateMintMetadataMacroCode = [
 ].join("\n");
 
 export const lightCreateMintMetadataMacroCode = [
-  "// light-token (Anchor)",
   "#[light_account(",
   "    init,",
   "    mint,",
@@ -502,3 +463,113 @@ export const lightCreateMintMetadataMacroCode = [
   ")]",
   "pub mint: UncheckedAccount<'info>,",
 ].join("\n");
+
+// === CREATE ATA MACRO (ANCHOR) ===
+export const splCreateAtaMacroCode = [
+  "#[account(",
+  "    init_if_needed,",
+  "    payer = fee_payer,",
+  "    associated_token::mint = mint,",
+  "    associated_token::authority = owner,",
+  ")]",
+  "pub ata: Account<'info, TokenAccount>,",
+].join("\n");
+
+export const lightCreateAtaMacroCode = [
+  "#[light_account(init,",
+  "    associated_token::authority = ata_owner,",
+  "    associated_token::mint = ata_mint,",
+  "    associated_token::bump = params.ata_bump",
+  ")]",
+  "pub ata: UncheckedAccount<'info>,",
+].join("\n");
+
+// === CREATE TOKEN ACCOUNT MACRO (ANCHOR) ===
+export const splCreateTokenAccountMacroCode = [
+  "#[account(",
+  "    init,",
+  "    payer = fee_payer,",
+  "    token::mint = mint,",
+  "    token::authority = authority,",
+  ")]",
+  "pub vault: Account<'info, TokenAccount>,",
+].join("\n");
+
+export const lightCreateTokenAccountMacroCode = [
+  "#[account(",
+  "    mut,",
+  "    seeds = [VAULT_SEED, mint.key().as_ref()],",
+  "    bump,",
+  ")]",
+  "#[light_account(init,",
+  "    token::authority = [VAULT_SEED, self.mint.key()],",
+  "    token::mint = mint,",
+  "    token::owner = vault_authority,",
+  "    token::bump = params.vault_bump",
+  ")]",
+  "pub vault: UncheckedAccount<'info>,",
+].join("\n");
+
+// === CREATE ATA CPI (RUST) ===
+export const splCreateAtaCpiCode = [
+  "use spl_associated_token_account::instruction::create_associated_token_account;",
+  "",
+  "let ix = create_associated_token_account(",
+  "    &payer.pubkey(),",
+  "    &owner.pubkey(),",
+  "    &mint,",
+  "    &spl_token::id(),",
+  ");",
+  "",
+  "invoke(&ix, &[payer, owner, mint])?;",
+].join("\n");
+
+export const lightCreateAtaCpiCode = [
+  "use light_token::instruction::CreateAssociatedAccountCpi;",
+  "",
+  "CreateAssociatedAccountCpi {",
+  "    payer: payer.clone(),",
+  "    owner: owner.clone(),",
+  "    mint: mint.clone(),",
+  "    ata: associated_token_account.clone(),",
+  "    bump,",
+  "}",
+  ".rent_free(",
+  "    compressible_config.clone(),",
+  "    rent_sponsor.clone(),",
+  "    system_program.clone(),",
+  ")",
+  ".invoke()?",
+].join("\n");
+
+// === CREATE TOKEN ACCOUNT CPI (RUST) ===
+export const splCreateTokenAccountCpiCode = [
+  "use spl_token::instruction::initialize_account;",
+  "",
+  "let ix = initialize_account(",
+  "    &spl_token::id(),",
+  "    &account,",
+  "    &mint,",
+  "    &owner,",
+  ")?;",
+  "",
+  "invoke(&ix, &[account, mint, owner])?;",
+].join("\n");
+
+export const lightCreateTokenAccountCpiCode = [
+  "use light_token::instruction::CreateTokenAccountCpi;",
+  "",
+  "CreateTokenAccountCpi {",
+  "    payer: payer.clone(),",
+  "    account: account.clone(),",
+  "    mint: mint.clone(),",
+  "    owner,",
+  "}",
+  ".rent_free(",
+  "    compressible_config.clone(),",
+  "    rent_sponsor.clone(),",
+  "    system_program.clone(),",
+  "    token_program.key,",
+  ")",
+  ".invoke()?",
+].join("\n");
diff --git a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx
index 5da6283f..c5abf898 100644
--- a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx
+++ b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx
@@ -7,6 +7,7 @@ use light_compressible::CreateAccountsProof;
 use light_sdk::derive_light_cpi_signer;
 use light_sdk_macros::{light_program, LightAccounts};
 use light_sdk_types::CpiSigner;
+use light_token::AdditionalMetadata;
 
 declare_id!("HVmVqSJyMejBeUigePMSfX4aENJzCGHNxAJuT2PDMPRx");
 
@@ -22,6 +23,16 @@ pub struct CreateMintParams {
     pub mint_signer_bump: u8,
 }
 
+#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
+pub struct CreateMintWithMetadataParams {
+    pub create_accounts_proof: CreateAccountsProof,
+    pub mint_signer_bump: u8,
+    pub name: Vec,
+    pub symbol: Vec,
+    pub uri: Vec,
+    pub additional_metadata: Option>,
+}
+
 #[derive(Accounts, LightAccounts)]
 #[instruction(params: CreateMintParams)]
 pub struct CreateMint<'info> {
@@ -67,6 +78,56 @@ pub struct CreateMint<'info> {
     pub system_program: Program<'info, System>,
 }
 
+#[derive(Accounts, LightAccounts)]
+#[instruction(params: CreateMintWithMetadataParams)]
+pub struct CreateMintWithMetadata<'info> {
+    #[account(mut)]
+    pub fee_payer: Signer<'info>,
+
+    pub authority: Signer<'info>,
+
+    /// CHECK: Validated by light-token CPI
+    #[account(
+        seeds = [MINT_SIGNER_SEED, authority.key().as_ref()],
+        bump,
+    )]
+    pub mint_signer: UncheckedAccount<'info>,
+
+    /// CHECK: Validated by light-token CPI
+    #[account(mut)]
+    #[light_account(init,
+        mint::signer = mint_signer,
+        mint::authority = fee_payer,
+        mint::decimals = 9,
+        mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()],
+        mint::bump = params.mint_signer_bump,
+        mint::name = params.name.clone(),
+        mint::symbol = params.symbol.clone(),
+        mint::uri = params.uri.clone(),
+        mint::update_authority = authority,
+        mint::additional_metadata = params.additional_metadata.clone()
+    )]
+    pub mint: UncheckedAccount<'info>,
+
+    /// CHECK: Validated by light-token CPI
+    pub compression_config: AccountInfo<'info>,
+
+    /// CHECK: Validated by light-token CPI
+    pub light_token_compressible_config: AccountInfo<'info>,
+
+    /// CHECK: Validated by light-token CPI
+    #[account(mut)]
+    pub rent_sponsor: AccountInfo<'info>,
+
+    /// CHECK: Light token program for CPI
+    pub light_token_program: AccountInfo<'info>,
+
+    /// CHECK: Validated by light-token CPI
+    pub light_token_cpi_authority: AccountInfo<'info>,
+
+    pub system_program: Program<'info, System>,
+}
+
 #[light_program]
 #[program]
 pub mod light_token_macro_create_mint {
@@ -79,6 +140,14 @@ pub mod light_token_macro_create_mint {
     ) -> Result<()> {
         Ok(())
     }
+
+    #[allow(unused_variables)]
+    pub fn create_mint_with_metadata<'info>(
+        ctx: Context<'_, '_, '_, 'info, CreateMintWithMetadata<'info>>,
+        params: CreateMintWithMetadataParams,
+    ) -> Result<()> {
+        Ok(())
+    }
 }
 ```
 
@@ -206,5 +275,117 @@ async fn test_create_mint() {
         "Mint authority should be fee_payer"
     );
 }
+
+/// Test creating a mint with token metadata using the #[light_account(init, mint, ...)] macro.
+#[tokio::test]
+async fn test_create_mint_with_metadata() {
+    use light_token_macro_create_mint::{CreateMintWithMetadataParams, MINT_SIGNER_SEED};
+
+    let program_id = light_token_macro_create_mint::ID;
+    let mut config =
+        ProgramTestConfig::new_v2(true, Some(vec![("light_token_macro_create_mint", program_id)]));
+    config = config.with_light_protocol_events();
+
+    let mut rpc = LightProgramTest::new(config).await.unwrap();
+    let payer = rpc.get_payer().insecure_clone();
+
+    // Set up program data PDA for rent-free config
+    let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id);
+
+    // Initialize rent-free config
+    let (init_config_ix, config_pda) = InitializeRentFreeConfig::new(
+        &program_id,
+        &payer.pubkey(),
+        &program_data_pda,
+        RENT_SPONSOR,
+        payer.pubkey(),
+    )
+    .build();
+
+    rpc.create_and_send_transaction(&[init_config_ix], &payer.pubkey(), &[&payer])
+        .await
+        .expect("Initialize config should succeed");
+
+    let authority = Keypair::new();
+
+    // Derive PDA for mint signer
+    let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address(
+        &[MINT_SIGNER_SEED, authority.pubkey().as_ref()],
+        &program_id,
+    );
+
+    // Derive mint PDA from mint signer
+    let (mint_pda, _) = find_mint_address(&mint_signer_pda);
+
+    // Get proof for creating the mint account
+    let proof_result = get_create_accounts_proof(
+        &rpc,
+        &program_id,
+        vec![CreateAccountsProofInput::mint(mint_signer_pda)],
+    )
+    .await
+    .unwrap();
+
+    let accounts = light_token_macro_create_mint::accounts::CreateMintWithMetadata {
+        fee_payer: payer.pubkey(),
+        authority: authority.pubkey(),
+        mint_signer: mint_signer_pda,
+        mint: mint_pda,
+        compression_config: config_pda,
+        light_token_compressible_config: COMPRESSIBLE_CONFIG_V1,
+        rent_sponsor: RENT_SPONSOR,
+        light_token_program: LIGHT_TOKEN_PROGRAM_ID.into(),
+        light_token_cpi_authority: light_token_types::CPI_AUTHORITY_PDA.into(),
+        system_program: solana_sdk::system_program::ID,
+    };
+
+    let instruction_data =
+        light_token_macro_create_mint::instruction::CreateMintWithMetadata {
+            params: CreateMintWithMetadataParams {
+                create_accounts_proof: proof_result.create_accounts_proof,
+                mint_signer_bump,
+                name: b"Example Token".to_vec(),
+                symbol: b"EXT".to_vec(),
+                uri: b"https://example.com/token.json".to_vec(),
+                additional_metadata: None,
+            },
+        };
+
+    let instruction = Instruction {
+        program_id,
+        accounts: [
+            accounts.to_account_metas(None),
+            proof_result.remaining_accounts,
+        ]
+        .concat(),
+        data: instruction_data.data(),
+    };
+
+    rpc.create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &authority])
+        .await
+        .expect("CreateMintWithMetadata should succeed");
+
+    // Verify mint exists on-chain
+    let mint_account = rpc
+        .get_account(mint_pda)
+        .await
+        .unwrap()
+        .expect("Mint should exist on-chain");
+
+    // Parse and verify mint data
+    use light_token_interface::state::Mint;
+    let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..])
+        .expect("Failed to deserialize Mint");
+
+    // Verify decimals match what was specified in #[light_account(init)]
+    assert_eq!(mint.base.decimals, 9, "Mint should have 9 decimals");
+
+    // Verify mint authority matches fee_payer
+    assert_eq!(
+        mint.base.mint_authority,
+        Some(payer.pubkey().to_bytes().into()),
+        "Mint authority should be fee_payer"
+    );
+}
 ```
 

From 01972b9a759fee8b185105e983d558a296b7e340 Mon Sep 17 00:00:00 2001
From: tilo-14 
Date: Thu, 29 Jan 2026 02:26:12 +0000
Subject: [PATCH 08/21] a1

---
 light-token/cookbook/create-mint.mdx          | 21 ++++-
 .../code-samples/code-compare-snippets.jsx    | 88 +++++++++++++++++++
 .../create-ata/anchor-macro/full-example.mdx  | 21 +----
 .../create-mint/anchor-macro/full-example.mdx | 24 +----
 .../anchor-macro/full-example.mdx             | 24 +----
 5 files changed, 114 insertions(+), 64 deletions(-)

diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx
index 9a6c7ae2..996c776e 100644
--- a/light-token/cookbook/create-mint.mdx
+++ b/light-token/cookbook/create-mint.mdx
@@ -22,6 +22,10 @@ import {
   lightCreateMintMacroCode,
   splCreateMintMetadataMacroCode,
   lightCreateMintMetadataMacroCode,
+  splCreateMintCpiCode,
+  lightCreateMintCpiCode,
+  splCreateMintMetadataCpiCode,
+  lightCreateMintMetadataCpiCode,
 } from "/snippets/code-samples/code-compare-snippets.jsx";
 import ActionCode from "/snippets/code-snippets/light-token/create-mint/action.mdx";
 import InstructionCode from "/snippets/code-snippets/light-token/create-mint/instruction.mdx";
@@ -142,13 +146,26 @@ Compare to SPL:
 
 Compare to SPL:
 
+
+
 
+
+
+
+
+
 
 
 Find [a full code example at the end](#full-code-example).
diff --git a/snippets/code-samples/code-compare-snippets.jsx b/snippets/code-samples/code-compare-snippets.jsx
index 3c47ffe2..08bd7f4e 100644
--- a/snippets/code-samples/code-compare-snippets.jsx
+++ b/snippets/code-samples/code-compare-snippets.jsx
@@ -573,3 +573,91 @@ export const lightCreateTokenAccountCpiCode = [
   ")",
   ".invoke()?",
 ].join("\n");
+
+// === CREATE MINT CPI (RUST) ===
+export const splCreateMintCpiCode = [
+  "use spl_token::instruction::initialize_mint;",
+  "",
+  "let ix = initialize_mint(",
+  "    &spl_token::id(),",
+  "    &mint.pubkey(),",
+  "    &mint_authority,",
+  "    Some(&freeze_authority),",
+  "    decimals,",
+  ")?;",
+  "",
+  "invoke(&ix, &[mint, rent_sysvar])?;",
+].join("\n");
+
+export const lightCreateMintCpiCode = [
+  "use light_token::instruction::CreateMintCpi;",
+  "",
+  "CreateMintCpi::new(",
+  "    mint_seed.clone(),",
+  "    authority.clone(),",
+  "    payer.clone(),",
+  "    address_tree.clone(),",
+  "    output_queue.clone(),",
+  "    compressible_config.clone(),",
+  "    mint.clone(),",
+  "    rent_sponsor.clone(),",
+  "    system_accounts,",
+  "    params,",
+  ")",
+  ".invoke()?",
+].join("\n");
+
+// === CREATE MINT WITH METADATA CPI (RUST) ===
+export const splCreateMintMetadataCpiCode = [
+  "use spl_token_2022::instruction::initialize_mint;",
+  "use spl_token_metadata_interface::instruction::initialize as init_metadata;",
+  "",
+  "let ix_mint = initialize_mint(",
+  "    &spl_token_2022::id(),",
+  "    &mint.pubkey(),",
+  "    &mint_authority,",
+  "    Some(&freeze_authority),",
+  "    decimals,",
+  ")?;",
+  "invoke(&ix_mint, &[mint.clone(), rent_sysvar.clone()])?;",
+  "",
+  "let ix_meta = init_metadata(",
+  "    &spl_token_2022::id(),",
+  "    &mint.pubkey(),",
+  "    &update_authority,",
+  "    &mint.pubkey(),",
+  "    &mint_authority,",
+  "    name, symbol, uri,",
+  ")?;",
+  "invoke(&ix_meta, &[mint, update_auth])?;",
+].join("\n");
+
+export const lightCreateMintMetadataCpiCode = [
+  "use light_token::instruction::CreateMintCpi;",
+  "",
+  "let extensions = Some(vec![",
+  "    ExtensionInstructionData::TokenMetadata(",
+  "        TokenMetadataInstructionData {",
+  "            update_authority: Some(authority.key.to_bytes().into()),",
+  '            name: b"Example Token".to_vec(),',
+  '            symbol: b"EXT".to_vec(),',
+  '            uri: b"https://example.com/token.json".to_vec(),',
+  "            additional_metadata: None,",
+  "        },",
+  "    ),",
+  "]);",
+  "",
+  "CreateMintCpi::new(",
+  "    mint_seed.clone(),",
+  "    authority.clone(),",
+  "    payer.clone(),",
+  "    address_tree.clone(),",
+  "    output_queue.clone(),",
+  "    compressible_config.clone(),",
+  "    mint.clone(),",
+  "    rent_sponsor.clone(),",
+  "    system_accounts,",
+  "    params, // includes extensions",
+  ")",
+  ".invoke()?",
+].join("\n");
diff --git a/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx
index ac352298..b0770779 100644
--- a/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx
+++ b/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx
@@ -67,8 +67,6 @@ pub mod light_token_macro_create_ata {
 ```
 
 ```rust test.rs
-//! Integration test for Light Protocol ATA creation using the macro.
-
 use anchor_lang::{InstructionData, ToAccountMetas};
 use light_client::interface::{get_create_accounts_proof, InitializeRentFreeConfig};
 use light_program_test::{
@@ -82,8 +80,7 @@ use solana_keypair::Keypair;
 use solana_pubkey::Pubkey;
 use solana_signer::Signer;
 
-/// Setup helper: Creates a compressed mint directly using the ctoken SDK.
-/// Returns (mint_pda, mint_seed_keypair)
+/// Creates a compressed mint. Returns (mint_pda, mint_seed_keypair).
 async fn setup_create_mint(
     rpc: &mut (impl Rpc + Indexer),
     payer: &Keypair,
@@ -146,7 +143,7 @@ async fn setup_create_mint(
     (mint, mint_seed)
 }
 
-/// Test creating a Light Protocol ATA using the macro.
+/// Creates a Light Protocol ATA via the macro.
 #[tokio::test]
 async fn test_create_ata() {
     use light_token_macro_create_ata::CreateAtaParams;
@@ -174,7 +171,6 @@ async fn test_create_ata() {
         .await
         .expect("Initialize config should succeed");
 
-    // Setup mint first
     let (mint, _mint_seed) = setup_create_mint(
         &mut rpc,
         &payer,
@@ -183,18 +179,14 @@ async fn test_create_ata() {
     )
     .await;
 
-    // The ATA owner will be the payer
     let ata_owner = payer.pubkey();
-
-    // Derive the ATA address using Light Token SDK's derivation
     let (ata, ata_bump) = light_token::instruction::derive_token_ata(&ata_owner, &mint);
 
-    // Get proof (no PDA accounts for ATA-only instruction)
+    // No PDA accounts needed for ATA-only instruction
     let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![])
         .await
         .unwrap();
 
-    // Build instruction
     let accounts = light_token_macro_create_ata::accounts::CreateAta {
         fee_payer: payer.pubkey(),
         ata_mint: mint,
@@ -227,25 +219,18 @@ async fn test_create_ata() {
         .await
         .expect("CreateAta instruction should succeed");
 
-    // Verify ATA exists on-chain
     let ata_account = rpc
         .get_account(ata)
         .await
         .unwrap()
         .expect("ATA should exist on-chain");
 
-    // Parse and verify token data
     use light_token_interface::state::Token;
     let token: Token = borsh::BorshDeserialize::deserialize(&mut &ata_account.data[..])
         .expect("Failed to deserialize Token");
 
-    // Verify owner
     assert_eq!(token.owner, ata_owner.to_bytes(), "ATA owner should match");
-
-    // Verify mint
     assert_eq!(token.mint, mint.to_bytes(), "ATA mint should match");
-
-    // Verify initial amount is 0
     assert_eq!(token.amount, 0, "ATA amount should be 0 initially");
 }
 ```
diff --git a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx
index c5abf898..a204a264 100644
--- a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx
+++ b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx
@@ -152,8 +152,6 @@ pub mod light_token_macro_create_mint {
 ```
 
 ```rust test.rs
-//! Integration test for create-mint macro example.
-
 use anchor_lang::{prelude::borsh, InstructionData, ToAccountMetas};
 use light_client::interface::{
     get_create_accounts_proof, CreateAccountsProofInput, InitializeRentFreeConfig,
@@ -169,7 +167,7 @@ use solana_keypair::Keypair;
 use solana_pubkey::Pubkey;
 use solana_signer::Signer;
 
-/// Test creating a mint using the #[light_account(init, mint, ...)] macro.
+/// Creates a mint via #[light_account(init, mint, ...)].
 #[tokio::test]
 async fn test_create_mint() {
     use light_token_macro_create_mint::{CreateMintParams, MINT_SIGNER_SEED};
@@ -182,10 +180,8 @@ async fn test_create_mint() {
     let mut rpc = LightProgramTest::new(config).await.unwrap();
     let payer = rpc.get_payer().insecure_clone();
 
-    // Set up program data PDA for rent-free config
     let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id);
 
-    // Initialize rent-free config
     let (init_config_ix, config_pda) = InitializeRentFreeConfig::new(
         &program_id,
         &payer.pubkey(),
@@ -201,16 +197,13 @@ async fn test_create_mint() {
 
     let authority = Keypair::new();
 
-    // Derive PDA for mint signer
     let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address(
         &[MINT_SIGNER_SEED, authority.pubkey().as_ref()],
         &program_id,
     );
 
-    // Derive mint PDA from mint signer
     let (mint_pda, _) = find_mint_address(&mint_signer_pda);
 
-    // Get proof for creating the mint account
     let proof_result = get_create_accounts_proof(
         &rpc,
         &program_id,
@@ -253,22 +246,18 @@ async fn test_create_mint() {
         .await
         .expect("CreateMint should succeed");
 
-    // Verify mint exists on-chain
     let mint_account = rpc
         .get_account(mint_pda)
         .await
         .unwrap()
         .expect("Mint should exist on-chain");
 
-    // Parse and verify mint data
     use light_token_interface::state::Mint;
     let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..])
         .expect("Failed to deserialize Mint");
 
-    // Verify decimals match what was specified in #[light_account(init)]
     assert_eq!(mint.base.decimals, 9, "Mint should have 9 decimals");
 
-    // Verify mint authority matches fee_payer
     assert_eq!(
         mint.base.mint_authority,
         Some(payer.pubkey().to_bytes().into()),
@@ -276,7 +265,7 @@ async fn test_create_mint() {
     );
 }
 
-/// Test creating a mint with token metadata using the #[light_account(init, mint, ...)] macro.
+/// Creates a mint with metadata via #[light_account(init, mint, ...)].
 #[tokio::test]
 async fn test_create_mint_with_metadata() {
     use light_token_macro_create_mint::{CreateMintWithMetadataParams, MINT_SIGNER_SEED};
@@ -289,10 +278,8 @@ async fn test_create_mint_with_metadata() {
     let mut rpc = LightProgramTest::new(config).await.unwrap();
     let payer = rpc.get_payer().insecure_clone();
 
-    // Set up program data PDA for rent-free config
     let program_data_pda = setup_mock_program_data(&mut rpc, &payer, &program_id);
 
-    // Initialize rent-free config
     let (init_config_ix, config_pda) = InitializeRentFreeConfig::new(
         &program_id,
         &payer.pubkey(),
@@ -308,16 +295,13 @@ async fn test_create_mint_with_metadata() {
 
     let authority = Keypair::new();
 
-    // Derive PDA for mint signer
     let (mint_signer_pda, mint_signer_bump) = Pubkey::find_program_address(
         &[MINT_SIGNER_SEED, authority.pubkey().as_ref()],
         &program_id,
     );
 
-    // Derive mint PDA from mint signer
     let (mint_pda, _) = find_mint_address(&mint_signer_pda);
 
-    // Get proof for creating the mint account
     let proof_result = get_create_accounts_proof(
         &rpc,
         &program_id,
@@ -365,22 +349,18 @@ async fn test_create_mint_with_metadata() {
         .await
         .expect("CreateMintWithMetadata should succeed");
 
-    // Verify mint exists on-chain
     let mint_account = rpc
         .get_account(mint_pda)
         .await
         .unwrap()
         .expect("Mint should exist on-chain");
 
-    // Parse and verify mint data
     use light_token_interface::state::Mint;
     let mint: Mint = borsh::BorshDeserialize::deserialize(&mut &mint_account.data[..])
         .expect("Failed to deserialize Mint");
 
-    // Verify decimals match what was specified in #[light_account(init)]
     assert_eq!(mint.base.decimals, 9, "Mint should have 9 decimals");
 
-    // Verify mint authority matches fee_payer
     assert_eq!(
         mint.base.mint_authority,
         Some(payer.pubkey().to_bytes().into()),
diff --git a/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx
index 757c477a..6a15919f 100644
--- a/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx
+++ b/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx
@@ -88,12 +88,6 @@ pub mod light_token_macro_create_token_account {
 ```
 
 ```rust test.rs
-//! Integration test for token vault creation using the macro.
-//!
-//! Note: This test requires a properly configured test environment with
-//! `light-program-test` that has compatible dependency versions.
-//! The test follows the pattern from light-protocol/sdk-tests/single-token-test.
-
 use anchor_lang::{InstructionData, ToAccountMetas};
 use light_client::{
     indexer::AddressWithTree,
@@ -168,12 +162,7 @@ async fn setup_create_mint(
     (mint, mint_seed)
 }
 
-/// Test creating a token vault using the `#[light_account(init, token, ...)]` macro.
-///
-/// This test verifies:
-/// 1. The macro-annotated program compiles correctly
-/// 2. A token vault can be created via the generated CPI
-/// 3. The vault has the correct owner and mint
+/// Creates a token vault via #[light_account(init, token, ...)].
 #[tokio::test]
 async fn test_create_token_vault() {
     use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR};
@@ -191,21 +180,18 @@ async fn test_create_token_vault() {
     let mut rpc = LightProgramTest::new(config).await.unwrap();
     let payer = rpc.get_payer().insecure_clone();
 
-    // Create a mint first
     let (mint, _mint_seed) = setup_create_mint(&mut rpc, &payer, payer.pubkey(), 9).await;
 
-    // Derive PDAs
     let (vault_authority, _auth_bump) =
         Pubkey::find_program_address(&[VAULT_AUTH_SEED], &program_id);
     let (vault, vault_bump) =
         Pubkey::find_program_address(&[VAULT_SEED, mint.as_ref()], &program_id);
 
-    // Get proof for token-only instruction (empty inputs)
+    // Empty inputs: no PDA accounts for token-only instruction
     let proof_result = get_create_accounts_proof(&rpc, &program_id, vec![])
         .await
         .unwrap();
 
-    // Build instruction accounts
     let accounts = light_token_macro_create_token_account::accounts::CreateTokenVault {
         fee_payer: payer.pubkey(),
         mint,
@@ -218,7 +204,6 @@ async fn test_create_token_vault() {
         system_program: solana_sdk::system_program::ID,
     };
 
-    // Build instruction data
     let instruction_data = light_token_macro_create_token_account::instruction::CreateTokenVault {
         params: CreateTokenVaultParams {
             create_accounts_proof: proof_result.create_accounts_proof,
@@ -236,15 +221,10 @@ async fn test_create_token_vault() {
         data: instruction_data.data(),
     };
 
-    // Execute the instruction
-    // Note: This may fail without InitializeRentFreeConfig setup.
-    // The full test requires rent-free config initialization.
     let result = rpc
         .create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer])
         .await;
 
-    // For now, we verify the instruction builds correctly.
-    // Full execution requires additional setup (InitializeRentFreeConfig, etc.)
     println!("Transaction result: {:?}", result);
 }
 ```

From ccf3ebe09bb12eba18c95b4f28bd2b22fa2ced96 Mon Sep 17 00:00:00 2001
From: tilo-14 
Date: Thu, 29 Jan 2026 22:32:37 +0000
Subject: [PATCH 09/21] ai skill1

---
 agent-skills/research-deepwiki/SKILL.md      | 117 ++++++
 light-token/examples/program.mdx             |  44 +-
 light-token/toolkits/for-streaming-mints.mdx | 121 ++----
 references/airdrop-client.md                 | 407 +++++++++++++++++++
 references/compressed-pda.md                 | 177 ++++++++
 references/deepwiki.md                       | 123 ++++++
 references/error-codes.md                    | 257 ++++++++++++
 references/light-token.md                    | 115 ++++++
 references/sdk-reference.md                  |  54 +++
 references/testing.md                        | 249 ++++++++++++
 references/zk-nullifiers.md                  | 288 +++++++++++++
 skill.md                                     | 175 ++++++++
 12 files changed, 2035 insertions(+), 92 deletions(-)
 create mode 100644 agent-skills/research-deepwiki/SKILL.md
 create mode 100644 references/airdrop-client.md
 create mode 100644 references/compressed-pda.md
 create mode 100644 references/deepwiki.md
 create mode 100644 references/error-codes.md
 create mode 100644 references/light-token.md
 create mode 100644 references/sdk-reference.md
 create mode 100644 references/testing.md
 create mode 100644 references/zk-nullifiers.md
 create mode 100644 skill.md

diff --git a/agent-skills/research-deepwiki/SKILL.md b/agent-skills/research-deepwiki/SKILL.md
new file mode 100644
index 00000000..b04c038e
--- /dev/null
+++ b/agent-skills/research-deepwiki/SKILL.md
@@ -0,0 +1,117 @@
+---
+name: research-deepwiki
+description: Query Light Protocol and related repositories via DeepWiki MCP. Use when answering questions about compressed accounts, Light SDK, Solana development, Claude Code features, or agent skills. Triggers on technical questions requiring repository context.
+---
+
+# DeepWiki Research
+
+Query repositories via DeepWiki MCP to answer technical questions with precise, source-backed answers.
+
+## Execution Steps
+
+### 1. Initialize Context: Understand Current Repository
+
+### 2. Identify Question Scope
+
+Determine the domain:
+- Programs, client SDKs, architecture, implementation details
+- Specific components (LightAccount, ValidityProof, CPI, etc.)
+
+### 3. Fetch Repository Context
+
+Select the appropriate repository based on question scope:
+
+**Light Protocol (compressed accounts, state trees, ZK compression, Light SDK)**
+```
+mcp__deepwiki__read_wiki_structure("Lightprotocol/light-protocol")
+mcp__deepwiki__read_wiki_contents("Lightprotocol/light-protocol")
+mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "your question")
+```
+
+**Solana Development (programs, accounts, general Solana)**
+```
+mcp__deepwiki__read_wiki_structure("blueshift-gg/blueshift-dashboard")
+mcp__deepwiki__read_wiki_contents("blueshift-gg/blueshift-dashboard")
+mcp__deepwiki__ask_question("blueshift-gg/blueshift-dashboard", "your question")
+```
+
+**Claude Code (CLI features, commands, configuration)**
+```
+mcp__deepwiki__read_wiki_structure("anthropics/claude-code")
+mcp__deepwiki__read_wiki_contents("anthropics/claude-code")
+mcp__deepwiki__ask_question("anthropics/claude-code", "your question")
+```
+
+**Agent Skills (skills, subagents, patterns)**
+```
+mcp__deepwiki__read_wiki_structure("anthropics/skills")
+mcp__deepwiki__read_wiki_contents("anthropics/skills")
+mcp__deepwiki__ask_question("anthropics/skills", "your question")
+```
+
+### 4. Apply Precision Rules
+
+**AVOID:**
+- Vague verbs: "handles", "manages", "processes", "enables", "provides"
+- Abstract nouns: "operations", "management", "coordination"
+- -ing endings when a precise verb exists
+- Marketing language: "comprehensive", "rich", "seamless"
+
+**USE:**
+- Exact function/method names
+- Concrete data structures
+- Specific operations: "sets discriminator", "creates hash", "nullifies account"
+- File:line references
+
+### 5. Format Response
+
+Structure answers with:
+- Technical precision
+- Code examples with inline comments
+- Source references (file paths, line numbers)
+
+## Example Output
+
+**Question:** What do you include in instruction data for compressed account creation?
+
+**Answer:**
+
+---
+
+## Compressed Account Creation Instruction Data
+
+Instruction data contains three components for proof verification and tree coordination.
+
+**Components:**
+
+```rust
+pub struct InstructionData {
+    proof: ValidityProof,
+    address_tree_info: PackedAddressTreeInfo,
+    output_state_tree_index: u8,
+    // + custom account data
+}
+```
+
+**1. Non-inclusion proof**
+- `ValidityProof` proves the derived address does not exist in the specified address tree
+- Client generates via `getValidityProof()` RPC call
+
+**2. Tree location parameters**
+- `PackedAddressTreeInfo`: Specifies which address tree registers the address (u8 index, not pubkey)
+- `output_state_tree_index`: Specifies which state tree stores the compressed account hash
+
+**3. Custom account data**
+- Program-specific fields (e.g., `message: String`, user data)
+
+**Execution flow:**
+
+1. Address tree proves uniqueness, stores address
+2. State tree stores account hash
+3. ValidityProof cryptographically guarantees address is unused
+
+**Why separate trees:**
+
+Compressed accounts require client-generated cryptographic proof that address doesn't exist (unlike regular Solana where runtime checks PDA existence). Address trees enforce uniqueness; state trees store account hashes.
+
+**Packed structs** use `u8` indices to reference accounts in `remaining_accounts`, reducing transaction size.
diff --git a/light-token/examples/program.mdx b/light-token/examples/program.mdx
index b936715d..722d3aa9 100644
--- a/light-token/examples/program.mdx
+++ b/light-token/examples/program.mdx
@@ -1,23 +1,35 @@
 ---
 title: "Program examples"
 sidebarTitle: "Program"
-description: "Anchor and native Rust program examples for light-token CPI."
+description: "Anchor program examples for light-token CPI."
 ---
 
 Find all examples on Github: [examples-light-token](https://github.com/Lightprotocol/examples-light-token)
 
-| | | |
-|---------|--------|-------------|
-| **approve** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/approve) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/approve.rs) |
-| **burn** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/burn) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/burn.rs) |
-| **close** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/close) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/close.rs) |
-| **create-ata** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-ata) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/create_ata.rs) |
-| **create-mint** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-mint) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/create_mint.rs) |
-| **create-token-account** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-token-account) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/create_token_account.rs) |
-| **freeze** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/freeze) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/freeze.rs) |
-| **mint-to** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/mint-to) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/mint_to.rs) |
-| **mint-to-checked** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/mint-to-checked) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/mint_to_checked.rs) |
-| **revoke** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/revoke) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/revoke.rs) |
-| **thaw** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/thaw) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/thaw.rs) |
-| **transfer-checked** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/transfer-checked) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/transfer_checked.rs) |
-| **transfer-interface** | [Anchor](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/transfer-interface) | [Native Rust](https://github.com/Lightprotocol/examples-light-token/blob/main/program-examples/native-rust/program/src/instructions/transfer_interface.rs) |
+## CPI instructions
+
+| | |
+|---------|--------|
+| [**approve**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/approve) | Approve delegate |
+| [**burn**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/burn) | Burn tokens |
+| [**close**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/close) | Close token account |
+| [**create-ata**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-ata) | Create associated light-token account |
+| [**create-mint**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-mint) | Create light-token mint |
+| [**create-token-account**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-token-account) | Create light-token account |
+| [**freeze**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/freeze) | Freeze token account |
+| [**mint-to**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to) | Mint tokens |
+| [**mint-to-checked**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to-checked) | Mint tokens with decimal validation |
+| [**revoke**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/revoke) | Revoke delegate |
+| [**thaw**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/thaw) | Thaw token account |
+| [**transfer-checked**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-checked) | Transfer with mint validation |
+| [**transfer-interface**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-interface) | Transfer between light-token, T22, and SPL accounts |
+
+## Anchor macros
+
+| | |
+|---------|--------|
+| [**counter**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/counter) | Create PDA with sponsored rent-exemption |
+| [**create-ata**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/create-ata) | Create associated light-token account |
+| [**create-mint**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/create-mint) | Create light-token mint |
+| [**create-token-account**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/create-token-account) | Create light-token account |
+| [**token-transfer**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/token-transfer) | Create destination ATA and transfer tokens |
diff --git a/light-token/toolkits/for-streaming-mints.mdx b/light-token/toolkits/for-streaming-mints.mdx
index fdb5e905..7972d5b8 100644
--- a/light-token/toolkits/for-streaming-mints.mdx
+++ b/light-token/toolkits/for-streaming-mints.mdx
@@ -17,6 +17,8 @@ Find devnet examples [here](https://github.com/Lightprotocol/examples-light-toke
 
 ## Setup
 
+Light mints are Solana accounts owned by the Light Token Program. Subscribe to account updates to detect new mints and changes.
+
 ```toml Cargo.toml
 [dependencies]
 helius-laserstream = "0.1.5"
@@ -27,30 +29,27 @@ dotenvy = "0.15"
 bs58 = "0.5"
 borsh = "0.10"
 
-# Light Protocol dependencies (git until published to crates.io)
-light-event = { git = "https://github.com/Lightprotocol/light-protocol" }
-light-compressed-account = { git = "https://github.com/Lightprotocol/light-protocol", features = ["std"] }
-light-token-interface = { git = "https://github.com/Lightprotocol/light-protocol" }
+light-token-interface = "0.3.0"
+light-compressed-account = { version = "0.9.0", features = ["std"] }
 ```
 
 ```rust
 use borsh::BorshDeserialize;
 use futures::StreamExt;
-use helius_laserstream::solana::storage::confirmed_block::{CompiledInstruction, Message};
+use helius_laserstream::grpc::subscribe_request_filter_accounts_filter::Filter;
+use helius_laserstream::grpc::subscribe_request_filter_accounts_filter_memcmp::Data;
+use helius_laserstream::grpc::{
+    SubscribeRequestFilterAccounts, SubscribeRequestFilterAccountsFilter,
+    SubscribeRequestFilterAccountsFilterMemcmp,
+};
 use helius_laserstream::{subscribe, LaserstreamConfig};
-use light_compressed_account::Pubkey;
-use light_event::parse::event_from_light_transaction;
 use light_token_interface::state::{ExtensionStruct, Mint};
 
-const LIGHT_SYSTEM_PROGRAM: &str = "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7";
-
-// Light Token Program ID bytes (cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m)
-const LIGHT_TOKEN_PROGRAM_BYTES: [u8; 32] = [
-    3, 89, 128, 47, 145, 178, 244, 191, 100, 152, 66, 240, 127, 251, 205, 1,
-    168, 38, 248, 106, 170, 164, 111, 0, 118, 231, 126, 151, 147, 169, 169, 4,
-];
+const LIGHT_TOKEN_PROGRAM_ID: &str = "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m";
 
-const COMPRESSED_MINT_DISCRIMINATOR: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 1];
+/// Byte offset of account_type in the Mint account data.
+/// BaseMint (82) + MintMetadata (67) + reserved (16) = 165
+const ACCOUNT_TYPE_OFFSET: u64 = 165;
 ```
 
 
@@ -82,13 +81,19 @@ let config = LaserstreamConfig::new(
 
 
 ```rust
+// Subscribe to Solana accounts owned by the Light Token Program
+// with account_type = 1 (Mint) at byte offset 165
 let request = helius_laserstream::grpc::SubscribeRequest {
-    transactions: [(
-        "light".to_string(),
-        helius_laserstream::grpc::SubscribeRequestFilterTransactions {
-            vote: Some(false),
-            failed: Some(false),
-            account_include: vec![LIGHT_SYSTEM_PROGRAM.to_string()],
+    accounts: [(
+        "light_mints".to_string(),
+        SubscribeRequestFilterAccounts {
+            owner: vec![LIGHT_TOKEN_PROGRAM_ID.to_string()],
+            filters: vec![SubscribeRequestFilterAccountsFilter {
+                filter: Some(Filter::Memcmp(SubscribeRequestFilterAccountsFilterMemcmp {
+                    offset: ACCOUNT_TYPE_OFFSET,
+                    data: Some(Data::Bytes(vec![1])),
+                })),
+            }],
             ..Default::default()
         },
     )]
@@ -100,77 +105,41 @@ let (stream, _handle) = subscribe(config, request);
 tokio::pin!(stream);
 
 while let Some(update) = stream.next().await {
-    // Process transactions...
+    // Process account updates...
 }
 ```
 
 
 
 
-### Parse Events
+### Deserialize mint accounts
 
 ```rust
-fn process_transaction(message: &Message, light_token_program_id: &Pubkey) -> anyhow::Result<()> {
-    let account_keys: Vec = message
-        .account_keys
-        .iter()
-        .filter_map(|k| {
-            if k.len() == 32 {
-                let arr: [u8; 32] = k.as_slice().try_into().ok()?;
-                Some(Pubkey::from(arr))
-            } else {
-                None
+if let Some(helius_laserstream::grpc::subscribe_update::UpdateOneof::Account(
+    account_update,
+)) = msg.update_oneof
+{
+    if let Some(account_info) = account_update.account {
+        let pubkey = bs58::encode(&account_info.pubkey).into_string();
+
+        match Mint::deserialize(&mut account_info.data.as_slice()) {
+            Ok(mint) => {
+                println!("Mint: {}", pubkey);
+                println!("  decimals: {}", mint.base.decimals);
+                println!("  supply: {}", mint.base.supply);
+            }
+            Err(e) => {
+                eprintln!("Failed to deserialize mint {}: {}", pubkey, e);
             }
-        })
-        .collect();
-
-    let (program_ids, instruction_data, accounts_per_ix) =
-        extract_light_transaction(&account_keys, &message.instructions);
-
-    if let Some(batches) =
-        event_from_light_transaction(&program_ids, &instruction_data, accounts_per_ix)?
-    {
-        for batch in &batches {
-            let event = &batch.event;
-            // Process outputs...
         }
     }
-
-    Ok(())
-}
-```
-
-
-
-
-### Extract Mints
-
-```rust
-let light_token_program_id = Pubkey::from(LIGHT_TOKEN_PROGRAM_BYTES);
-
-for output in &event.output_compressed_accounts {
-    // Filter by owner (Light Token Program)
-    if output.compressed_account.owner != light_token_program_id {
-        continue;
-    }
-
-    // Check for mint discriminator
-    let data = match &output.compressed_account.data {
-        Some(d) if d.discriminator == COMPRESSED_MINT_DISCRIMINATOR => &d.data,
-        _ => continue,
-    };
-
-    // Deserialize mint
-    if let Ok(mint) = Mint::deserialize(&mut data.as_slice()) {
-        println!("Mint: {:?}", mint.metadata.mint);
-    }
 }
 ```
 
 
 
 
-### Extract Token Metadata from Mint Extensions
+### Extract Token Metadata from mint extensions
 
 ```rust
 fn extract_metadata(mint: &Mint) -> Option<(String, String, String)> {
@@ -225,7 +194,7 @@ pub struct BaseMint {
     pub freeze_authority: Option,
 }
 
-/// Light Protocol metadata for compressed mints (67 bytes)
+/// Light Protocol metadata for mints (67 bytes)
 #[repr(C)]
 pub struct MintMetadata {
     pub version: u8,
diff --git a/references/airdrop-client.md b/references/airdrop-client.md
new file mode 100644
index 00000000..3616ed67
--- /dev/null
+++ b/references/airdrop-client.md
@@ -0,0 +1,407 @@
+# Airdrop
+
+Distribute compressed tokens to multiple recipients using TypeScript client.
+
+## Quick Decision
+
+| Scale | Approach |
+|-------|----------|
+| <10,000 recipients | Single transaction - see [Simple Airdrop](#simple-airdrop-10000-recipients) |
+| 10,000+ recipients | Batched with retry - see [Batched Airdrop](#batched-airdrop-10000-recipients) |
+| No-code | [Airship by Helius](https://airship.helius.dev/) (up to 200k) |
+
+## Core Pattern
+
+```typescript
+import { CompressedTokenProgram, getTokenPoolInfos, selectTokenPoolInfo } from "@lightprotocol/compressed-token";
+import { bn, createRpc, selectStateTreeInfo, buildAndSignTx, sendAndConfirmTx } from "@lightprotocol/stateless.js";
+import { ComputeBudgetProgram } from "@solana/web3.js";
+
+const rpc = createRpc(RPC_ENDPOINT);
+
+// 1. Get infrastructure
+const treeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos());
+const tokenPoolInfo = selectTokenPoolInfo(await getTokenPoolInfos(rpc, mint));
+
+// 2. Build compress instruction (SPL -> compressed to multiple recipients)
+const ix = await CompressedTokenProgram.compress({
+  payer: payer.publicKey,
+  owner: payer.publicKey,
+  source: sourceAta.address,           // SPL ATA holding tokens
+  toAddress: recipients,                // PublicKey[]
+  amount: recipients.map(() => bn(amount)),
+  mint,
+  tokenPoolInfo,
+  outputStateTreeInfo: treeInfo,
+});
+
+// 3. Send with compute budget (120k CU per recipient)
+const instructions = [
+  ComputeBudgetProgram.setComputeUnitLimit({ units: 120_000 * recipients.length }),
+  ix,
+];
+const { blockhash } = await rpc.getLatestBlockhash();
+const tx = buildAndSignTx(instructions, payer, blockhash, []);
+await sendAndConfirmTx(rpc, tx);
+```
+
+## Setup: Create Mint
+
+```typescript
+import { createMint } from "@lightprotocol/compressed-token";
+import { getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token";
+
+const { mint } = await createMint(rpc, payer, payer.publicKey, 9);
+const ata = await getOrCreateAssociatedTokenAccount(rpc, payer, mint, payer.publicKey);
+await mintTo(rpc, payer, mint, ata.address, payer.publicKey, 100_000_000_000);
+```
+
+## Compute Units
+
+| Recipients/instruction | CU |
+|----------------------|-----|
+| 1 | 120,000 |
+| 5 | 170,000 |
+| Batched tx | 500,000 |
+
+## Lookup Tables
+
+Reduce transaction size:
+
+| Network | Address |
+|---------|---------|
+| Mainnet | `9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ` |
+| Devnet | `qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V` |
+
+## Simple Airdrop (<10,000 recipients)
+
+Single transaction approach for small distributions.
+
+```typescript
+import {
+  CompressedTokenProgram,
+  getTokenPoolInfos,
+  selectTokenPoolInfo,
+} from "@lightprotocol/compressed-token";
+import {
+  bn,
+  buildAndSignTx,
+  calculateComputeUnitPrice,
+  createRpc,
+  dedupeSigner,
+  selectStateTreeInfo,
+  sendAndConfirmTx,
+} from "@lightprotocol/stateless.js";
+import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js";
+import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
+
+const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=YOUR_KEY";
+const rpc = createRpc(RPC_ENDPOINT);
+const mint = new PublicKey("YOUR_MINT_ADDRESS");
+
+// Define recipients and amounts
+const recipients = [
+  new PublicKey("..."),
+  new PublicKey("..."),
+  new PublicKey("..."),
+];
+
+const amounts = [
+  bn(20_000_000_000), // 20 tokens (9 decimals)
+  bn(30_000_000_000), // 30 tokens
+  bn(40_000_000_000), // 40 tokens
+];
+
+// Get infrastructure
+const treeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos());
+const tokenPoolInfo = selectTokenPoolInfo(await getTokenPoolInfos(rpc, mint));
+const sourceAta = await getOrCreateAssociatedTokenAccount(rpc, payer, mint, payer.publicKey);
+
+// Build transaction
+const units = 120_000 * recipients.length;
+const instructions = [
+  ComputeBudgetProgram.setComputeUnitLimit({ units }),
+  ComputeBudgetProgram.setComputeUnitPrice({
+    microLamports: calculateComputeUnitPrice(20_000, units),
+  }),
+  await CompressedTokenProgram.compress({
+    payer: payer.publicKey,
+    owner: payer.publicKey,
+    source: sourceAta.address,
+    toAddress: recipients,
+    amount: amounts,
+    mint,
+    tokenPoolInfo,
+    outputStateTreeInfo: treeInfo,
+  }),
+];
+
+// Use lookup table to reduce tx size
+const lut = new PublicKey("9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ"); // mainnet
+// const lut = new PublicKey("qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V"); // devnet
+const lookupTable = (await rpc.getAddressLookupTable(lut)).value!;
+
+const { blockhash } = await rpc.getLatestBlockhash();
+const tx = buildAndSignTx(
+  instructions,
+  payer,
+  blockhash,
+  dedupeSigner(payer, []),
+  [lookupTable]
+);
+const txId = await sendAndConfirmTx(rpc, tx);
+console.log(`Airdrop complete: ${txId}`);
+```
+
+### Verify Distribution
+
+```typescript
+for (const recipient of recipients) {
+  const accounts = await rpc.getCompressedTokenAccountsByOwner(recipient, { mint });
+  const balance = accounts.items.reduce((sum, acc) => sum + Number(acc.parsed.amount), 0);
+  console.log(`${recipient}: ${balance / 1e9} tokens`);
+}
+```
+
+## Batched Airdrop (10,000+ recipients)
+
+For large-scale distributions with retry logic and blockhash management.
+
+### Create Instruction Batches
+
+```typescript
+import {
+  CompressedTokenProgram,
+  TokenPoolInfo,
+  selectTokenPoolInfo,
+} from "@lightprotocol/compressed-token";
+import {
+  bn,
+  selectStateTreeInfo,
+  StateTreeInfo,
+} from "@lightprotocol/stateless.js";
+import {
+  ComputeBudgetProgram,
+  TransactionInstruction,
+  PublicKey,
+} from "@solana/web3.js";
+
+interface CreateAirdropParams {
+  amount: number | bigint;
+  recipients: PublicKey[];
+  payer: PublicKey;
+  sourceTokenAccount: PublicKey;
+  mint: PublicKey;
+  stateTreeInfos: StateTreeInfo[];
+  tokenPoolInfos: TokenPoolInfo[];
+  maxRecipientsPerInstruction?: number;   // default: 5
+  maxInstructionsPerTransaction?: number; // default: 3
+  computeUnitLimit?: number;              // default: 500_000
+  computeUnitPrice?: number;
+}
+
+export async function createAirdropInstructions({
+  amount,
+  recipients,
+  payer,
+  sourceTokenAccount,
+  mint,
+  stateTreeInfos,
+  tokenPoolInfos,
+  maxRecipientsPerInstruction = 5,
+  maxInstructionsPerTransaction = 3,
+  computeUnitLimit = 500_000,
+  computeUnitPrice,
+}: CreateAirdropParams): Promise {
+  const batches: TransactionInstruction[][] = [];
+  const amountBn = bn(amount.toString());
+
+  for (
+    let i = 0;
+    i < recipients.length;
+    i += maxRecipientsPerInstruction * maxInstructionsPerTransaction
+  ) {
+    const instructions: TransactionInstruction[] = [];
+
+    instructions.push(
+      ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit })
+    );
+    if (computeUnitPrice) {
+      instructions.push(
+        ComputeBudgetProgram.setComputeUnitPrice({ microLamports: computeUnitPrice })
+      );
+    }
+
+    const treeInfo = selectStateTreeInfo(stateTreeInfos);
+    const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos);
+
+    for (let j = 0; j < maxInstructionsPerTransaction; j++) {
+      const startIdx = i + j * maxRecipientsPerInstruction;
+      const recipientBatch = recipients.slice(
+        startIdx,
+        startIdx + maxRecipientsPerInstruction
+      );
+
+      if (recipientBatch.length === 0) break;
+
+      instructions.push(
+        await CompressedTokenProgram.compress({
+          payer,
+          owner: payer,
+          source: sourceTokenAccount,
+          toAddress: recipientBatch,
+          amount: recipientBatch.map(() => amountBn),
+          mint,
+          tokenPoolInfo,
+          outputStateTreeInfo: treeInfo,
+        })
+      );
+    }
+
+    if (instructions.length > (computeUnitPrice ? 2 : 1)) {
+      batches.push(instructions);
+    }
+  }
+
+  return batches;
+}
+```
+
+### Background Blockhash Updater
+
+```typescript
+import { Rpc } from "@lightprotocol/stateless.js";
+
+export let currentBlockhash: string;
+
+export async function updateBlockhash(
+  connection: Rpc,
+  signal: AbortSignal
+): Promise {
+  const { blockhash } = await connection.getLatestBlockhash();
+  currentBlockhash = blockhash;
+
+  (function updateInBackground() {
+    if (signal.aborted) return;
+    const timeoutId = setTimeout(async () => {
+      if (signal.aborted) return;
+      try {
+        const { blockhash } = await connection.getLatestBlockhash();
+        currentBlockhash = blockhash;
+      } catch (error) {
+        console.error("Failed to update blockhash:", error);
+      }
+      updateInBackground();
+    }, 30_000);
+
+    signal.addEventListener("abort", () => clearTimeout(timeoutId));
+  })();
+}
+```
+
+### Sign and Send with Retry
+
+```typescript
+import { Rpc, sendAndConfirmTx } from "@lightprotocol/stateless.js";
+import {
+  Keypair,
+  PublicKey,
+  TransactionMessage,
+  VersionedTransaction,
+} from "@solana/web3.js";
+import { currentBlockhash, updateBlockhash } from "./update-blockhash";
+
+export enum BatchResultType {
+  Success = "success",
+  Error = "error",
+}
+
+export type BatchResult =
+  | { type: BatchResultType.Success; index: number; signature: string }
+  | { type: BatchResultType.Error; index: number; error: string };
+
+export async function* signAndSendAirdropBatches(
+  batches: TransactionInstruction[][],
+  payer: Keypair,
+  connection: Rpc,
+  maxRetries = 3
+): AsyncGenerator {
+  const abortController = new AbortController();
+  await updateBlockhash(connection, abortController.signal);
+
+  // Lookup table for your network
+  const lookupTableAddress = new PublicKey(
+    "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ" // mainnet
+    // "qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V" // devnet
+  );
+  const lookupTableAccount = (
+    await connection.getAddressLookupTable(lookupTableAddress)
+  ).value!;
+
+  const statusMap = new Array(batches.length).fill(0); // 0 = pending
+
+  while (statusMap.includes(0)) {
+    const sends = statusMap.map(async (status, index) => {
+      if (status !== 0) return;
+
+      let retries = 0;
+      while (retries < maxRetries && statusMap[index] === 0) {
+        if (!currentBlockhash) {
+          await new Promise((resolve) => setTimeout(resolve, 1000));
+          continue;
+        }
+
+        try {
+          const tx = new VersionedTransaction(
+            new TransactionMessage({
+              payerKey: payer.publicKey,
+              recentBlockhash: currentBlockhash,
+              instructions: batches[index],
+            }).compileToV0Message([lookupTableAccount])
+          );
+          tx.sign([payer]);
+
+          const confirmedSig = await sendAndConfirmTx(connection, tx, {
+            skipPreflight: true,
+            commitment: "confirmed",
+          });
+
+          if (confirmedSig) {
+            statusMap[index] = 1;
+            return { type: BatchResultType.Success, index, signature: confirmedSig };
+          }
+        } catch (e) {
+          retries++;
+          if (retries >= maxRetries) {
+            statusMap[index] = `err: ${(e as Error).message}`;
+            return { type: BatchResultType.Error, index, error: (e as Error).message };
+          }
+        }
+      }
+    });
+
+    const results = await Promise.all(sends);
+    for (const result of results) {
+      if (result) yield result as BatchResult;
+    }
+  }
+
+  abortController.abort();
+}
+```
+
+## Advanced: Claim-Based
+
+For vesting, clawback, or user-initiated claims:
+
+| Implementation | Features |
+|---------------|----------|
+| [Merkle Distributor](https://github.com/Lightprotocol/distributor) | Linear vesting, partial claims, clawback, REST API |
+| [Simple Claim](https://github.com/Lightprotocol/program-examples/tree/main/airdrop-implementations/simple-claim) | Cliff vesting at slot X |
+
+## Resources
+
+- **Docs**: [Airdrop Guide](https://www.zkcompression.com/compressed-tokens/advanced-guides/airdrop)
+- **Docs**: [Claim Implementations](https://www.zkcompression.com/compressed-tokens/advanced-guides/airdrop#claim-reference-implementations)
+- **Code**: [example-token-distribution](https://github.com/Lightprotocol/examples-zk-compression/tree/main/example-token-distribution)
+- **Tool**: [Airship by Helius](https://airship.helius.dev/)
diff --git a/references/compressed-pda.md b/references/compressed-pda.md
new file mode 100644
index 00000000..dd53493e
--- /dev/null
+++ b/references/compressed-pda.md
@@ -0,0 +1,177 @@
+# Compressed PDAs
+
+The base library to use Compressed Accounts in Solana on-chain Rust and Anchor programs.
+Compressed accounts do not require rent-exemption, which makes them suitable for:
+- user owned accounts
+- not config accounts which are often read
+- not pool accounts, since compressed accounts cannot be used concurrently
+
+Compressed Accounts store state as account hashes in State Merkle trees.
+and unique addresses in Address Merkle trees.
+Validity proofs (zero-knowledge proofs) verify that compressed account
+state exists and new addresses do not exist yet.
+
+- No rent exemption payment required.
+- Constant 128-byte validity proof per transaction for one or multiple compressed accounts and addresses.
+- Compressed account data is sent as instruction data when accessed.
+- State and address trees are managed by the protocol.
+
+For full program examples, see the [Program Examples](https://github.com/Lightprotocol/program-examples).
+For detailed documentation, visit [zkcompression.com](https://www.zkcompression.com/).
+For pinocchio solana program development see [`light-sdk-pinocchio`](https://docs.rs/light-sdk-pinocchio).
+For rust client development see [`light-client`](https://docs.rs/light-client).
+For rust program testing see [`light-program-test`](https://docs.rs/light-program-test).
+For local test validator with light system programs see [Light CLI](https://www.npmjs.com/package/@lightprotocol/zk-compression-cli).
+
+### Difference to Light-Accounts (Light-PDA)
+Light-PDA's are Solana accounts with sponsored rent-exemption.
+There is no proof required for interactions with Light-PDA's which makes
+them suitable for Defi Usecases. Compressed PDA's don't require rent-exemption,
+but a proof for interactions.
+
+## Using Compressed Accounts in Solana Programs
+
+1. [`Instruction`](https://docs.rs/light-sdk/latest/light_sdk/instruction/)
+    - `CompressedAccountMeta` - Compressed account metadata structs for instruction data.
+    - `PackedAccounts` - Abstraction to prepare accounts offchain for instructions with compressed accounts.
+    - `ValidityProof` - Proves that new addresses don't exist yet, and compressed account state exists.
+2. Compressed Account in Program
+    - [`LightAccount`](https://docs.rs/light-sdk/latest/light_sdk/account/) - Compressed account abstraction similar to anchor Account.
+    - [`derive_address`](https://docs.rs/light-sdk/latest/light_sdk/address/) - Create a compressed account address.
+    - `LightDiscriminator` - DeriveMacro to derive a compressed account discriminator.
+3. [`Cpi`](https://docs.rs/light-sdk/latest/light_sdk/cpi/)
+    - `CpiAccounts` - Prepare accounts to cpi the light system program.
+    - `LightSystemProgramCpi` - Prepare instruction data to cpi the light system program.
+    - [`InvokeLightSystemProgram::invoke`](https://docs.rs/light-sdk/latest/light_sdk/cpi/) - Invoke the light system program via cpi.
+
+## Client Program Interaction Flow
+
+```text
+ ├─ Client
+ │  ├─ Get ValidityProof from RPC.
+ │  ├─ pack accounts with PackedAccounts into PackedAddressTreeInfo and PackedStateTreeInfo.
+ │  ├─ pack CompressedAccountMeta.
+ │  ├─ Build Instruction from PackedAccounts and CompressedAccountMetas.
+ │  └─ Send transaction.
+ │
+ └─ Custom Program
+    ├─ CpiAccounts parse accounts consistent with PackedAccounts.
+    ├─ LightAccount instantiates from CompressedAccountMeta.
+    │
+    └─ Light System Program CPI
+       ├─ Verify ValidityProof.
+       ├─ Update State Merkle tree.
+       ├─ Update Address Merkle tree.
+       └─ Complete atomic state transition.
+```
+
+## Features
+
+1. `anchor` - Derives AnchorSerialize, AnchorDeserialize instead of BorshSerialize, BorshDeserialize.
+
+2. `v2`
+    - available on devnet, localnet, and light-program-test.
+    - Support for optimized v2 light system program instructions.
+
+3. `cpi-context` - Enables CPI context operations for batched compressed account operations.
+   - available on devnet, localnet, and light-program-test.
+   - Enables the use of one validity proof across multiple cpis from different programs in one instruction.
+   - For example spending compressed tokens (owned by the ctoken program) and updating a compressed pda (owned by a custom program)
+     with one validity proof.
+   - An instruction should not use more than one validity proof.
+   - Requires the v2 feature.
+
+## Example: Create a Compressed Account
+
+```rust
+use anchor_lang::{prelude::*, Discriminator};
+use light_sdk::{
+    account::LightAccount,
+    address::v1::derive_address,
+    cpi::{v1::LightSystemProgramCpi, CpiAccounts, InvokeLightSystemProgram, LightCpiInstruction},
+    derive_light_cpi_signer,
+    instruction::{account_meta::CompressedAccountMeta, PackedAddressTreeInfo},
+    CpiSigner, LightDiscriminator, LightHasher, ValidityProof,
+};
+
+declare_id!("2tzfijPBGbrR5PboyFUFKzfEoLTwdDSHUjANCw929wyt");
+
+pub const LIGHT_CPI_SIGNER: CpiSigner =
+    derive_light_cpi_signer!("2tzfijPBGbrR5PboyFUFKzfEoLTwdDSHUjANCw929wyt");
+
+#[program]
+pub mod counter {
+
+    use super::*;
+
+    pub fn create_compressed_account<'info>(
+        ctx: Context<'_, '_, '_, 'info, CreateCompressedAccount<'info>>,
+        proof: ValidityProof,
+        address_tree_info: PackedAddressTreeInfo,
+        output_tree_index: u8,
+    ) -> Result<()> {
+        let light_cpi_accounts = CpiAccounts::new(
+            ctx.accounts.fee_payer.as_ref(),
+            ctx.remaining_accounts,
+            crate::LIGHT_CPI_SIGNER,
+        )?;
+
+        let (address, address_seed) = derive_address(
+            &[b"counter", ctx.accounts.fee_payer.key().as_ref()],
+            &address_tree_info.get_tree_pubkey(&light_cpi_accounts)?,
+            &crate::ID,
+        );
+
+        let mut new_account = LightAccount::<'_, CounterAccount>::new_init(
+            &crate::ID,
+            Some(address),
+            output_tree_index,
+        );
+
+        new_account.counter = 0;
+
+        let light_cpi = LightSystemProgramCpi::new(light_cpi_accounts, vec![proof])?;
+        let instruction_data = LightCpiInstruction {
+            inputs: Vec::new(),
+            outputs: vec![new_account.to_account_info()?],
+            address_tree_infos: vec![address_tree_info.into()],
+            address_seeds: vec![address_seed],
+        };
+        light_cpi.invoke(instruction_data)?;
+        Ok(())
+    }
+}
+
+#[derive(Accounts)]
+pub struct CreateCompressedAccount<'info> {
+    #[account(mut)]
+    pub fee_payer: Signer<'info>,
+}
+
+#[derive(Debug, LightDiscriminator, LightHasher)]
+pub struct CounterAccount {
+    #[hash]
+    pub counter: u64,
+}
+```
+
+## Guides
+
+| Guide | Docs |
+|-------|------|
+| Create compressed accounts | [create](https://www.zkcompression.com/compressed-pdas/guides/how-to-create-compressed-accounts) |
+| Update compressed accounts | [update](https://www.zkcompression.com/compressed-pdas/guides/how-to-update-compressed-accounts) |
+| Close compressed accounts | [close](https://www.zkcompression.com/compressed-pdas/guides/how-to-close-compressed-accounts) |
+| Reinitialize accounts | [reinit](https://www.zkcompression.com/compressed-pdas/guides/how-to-reinitialize-compressed-accounts) |
+| Burn accounts | [burn](https://www.zkcompression.com/compressed-pdas/guides/how-to-burn-compressed-accounts) |
+| Client guide (TS + Rust) | [client](https://www.zkcompression.com/client-library/client-guide) |
+| Program examples | [examples](https://www.zkcompression.com/compressed-pdas/program-examples) |
+
+## SDKs
+
+- Rust on-chain (Anchor): [`light-sdk`](https://docs.rs/light-sdk) ([crates.io](https://crates.io/crates/light-sdk))
+- Rust on-chain (Pinocchio): [`light-sdk-pinocchio`](https://docs.rs/light-sdk-pinocchio) ([crates.io](https://crates.io/crates/light-sdk-pinocchio))
+- Rust client: [`light-client`](https://docs.rs/light-client) ([crates.io](https://crates.io/crates/light-client))
+- Rust testing: [`light-program-test`](https://docs.rs/light-program-test) ([crates.io](https://crates.io/crates/light-program-test))
+- TypeScript: [`@lightprotocol/stateless.js`](https://www.npmjs.com/package/@lightprotocol/stateless.js)
+- GitHub examples: [program-examples](https://github.com/Lightprotocol/program-examples)
diff --git a/references/deepwiki.md b/references/deepwiki.md
new file mode 100644
index 00000000..3e9ff075
--- /dev/null
+++ b/references/deepwiki.md
@@ -0,0 +1,123 @@
+---
+name: research-deepwiki
+description: Query Light Protocol and related repositories via DeepWiki MCP. Use when answering questions about compressed accounts, Light SDK, Solana development, Claude Code features, or agent skills. Triggers on technical questions requiring repository context.
+---
+
+# DeepWiki Research
+
+Query repositories via DeepWiki MCP to answer technical questions with precise, source-backed answers.
+
+## Execution Steps
+
+### 1. Read Required Context
+
+Before answering any question:
+
+```bash
+cat /home/tilo/.claude/context/terminology-reference.md
+```
+
+### 2. Identify Question Scope
+
+Determine the domain:
+- Programs, client SDKs, architecture, implementation details
+- Specific components (LightAccount, ValidityProof, CPI, etc.)
+
+### 3. Fetch Repository Context
+
+Select the appropriate repository based on question scope:
+
+**Light Protocol (compressed accounts, state trees, ZK compression, Light SDK)**
+```
+mcp__deepwiki__read_wiki_structure("Lightprotocol/light-protocol")
+mcp__deepwiki__read_wiki_contents("Lightprotocol/light-protocol")
+mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "your question")
+```
+
+**Solana Development (programs, accounts, general Solana)**
+```
+mcp__deepwiki__read_wiki_structure("blueshift-gg/blueshift-dashboard")
+mcp__deepwiki__read_wiki_contents("blueshift-gg/blueshift-dashboard")
+mcp__deepwiki__ask_question("blueshift-gg/blueshift-dashboard", "your question")
+```
+
+**Claude Code (CLI features, commands, configuration)**
+```
+mcp__deepwiki__read_wiki_structure("anthropics/claude-code")
+mcp__deepwiki__read_wiki_contents("anthropics/claude-code")
+mcp__deepwiki__ask_question("anthropics/claude-code", "your question")
+```
+
+**Agent Skills (skills, subagents, patterns)**
+```
+mcp__deepwiki__read_wiki_structure("anthropics/skills")
+mcp__deepwiki__read_wiki_contents("anthropics/skills")
+mcp__deepwiki__ask_question("anthropics/skills", "your question")
+```
+
+### 4. Apply Precision Rules
+
+**AVOID:**
+- Vague verbs: "handles", "manages", "processes", "enables", "provides"
+- Abstract nouns: "operations", "management", "coordination"
+- -ing endings when a precise verb exists
+- Marketing language: "comprehensive", "rich", "seamless"
+
+**USE:**
+- Exact function/method names
+- Concrete data structures
+- Specific operations: "sets discriminator", "creates hash", "nullifies account"
+- File:line references
+
+### 5. Format Response
+
+Structure answers with:
+- Technical precision
+- Code examples with inline comments
+- Source references (file paths, line numbers)
+
+## Example Output
+
+**Question:** What do you include in instruction data for compressed account creation?
+
+**Answer:**
+
+---
+
+## Compressed Account Creation Instruction Data
+
+Instruction data contains three components for proof verification and tree coordination.
+
+**Components:**
+
+```rust
+pub struct InstructionData {
+    proof: ValidityProof,
+    address_tree_info: PackedAddressTreeInfo,
+    output_state_tree_index: u8,
+    // + custom account data
+}
+```
+
+**1. Non-inclusion proof**
+- `ValidityProof` proves the derived address does not exist in the specified address tree
+- Client generates via `getValidityProof()` RPC call
+
+**2. Tree location parameters**
+- `PackedAddressTreeInfo`: Specifies which address tree registers the address (u8 index, not pubkey)
+- `output_state_tree_index`: Specifies which state tree stores the compressed account hash
+
+**3. Custom account data**
+- Program-specific fields (e.g., `message: String`, user data)
+
+**Execution flow:**
+
+1. Address tree proves uniqueness, stores address
+2. State tree stores account hash
+3. ValidityProof cryptographically guarantees address is unused
+
+**Why separate trees:**
+
+Compressed accounts require client-generated cryptographic proof that address doesn't exist (unlike regular Solana where runtime checks PDA existence). Address trees enforce uniqueness; state trees store account hashes.
+
+**Packed structs** use `u8` indices to reference accounts in `remaining_accounts`, reducing transaction size.
diff --git a/references/error-codes.md b/references/error-codes.md
new file mode 100644
index 00000000..7698739f
--- /dev/null
+++ b/references/error-codes.md
@@ -0,0 +1,257 @@
+# Error Codes Reference
+
+Complete error code reference for ZK Compression programs. Codes 6000-16034 (hex 0x1770-0x3EB2).
+
+Search for your error code or hex value with `Cmd+F` / `Ctrl+F`.
+
+## 6000 - 6053 / SystemProgramError Variants
+
+> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/programs/system/src/errors.rs#L133)
+
+| Code | Hex    | Error                                           | Message                                                                                                                           |
+| :--- | :----- | :---------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- |
+| 6000 | 0x1770 | `SumCheckFailed`                                | "Sum check failed"                                                                                                                |
+| 6001 | 0x1771 | `SignerCheckFailed`                             | "Signer check failed"                                                                                                             |
+| 6002 | 0x1772 | `CpiSignerCheckFailed`                          | "Cpi signer check failed"                                                                                                         |
+| 6003 | 0x1773 | `ComputeInputSumFailed`                         | "Computing input sum failed."                                                                                                     |
+| 6004 | 0x1774 | `ComputeOutputSumFailed`                        | "Computing output sum failed."                                                                                                    |
+| 6005 | 0x1775 | `ComputeRpcSumFailed`                           | "Computing rpc sum failed."                                                                                                       |
+| 6006 | 0x1776 | `InvalidAddress`                                | "InvalidAddress"                                                                                                                  |
+| 6007 | 0x1777 | `DeriveAddressError`                            | "DeriveAddressError"                                                                                                              |
+| 6008 | 0x1778 | `CompressedSolPdaUndefinedForCompressSol`       | "CompressedSolPdaUndefinedForCompressSol"                                                                                         |
+| 6009 | 0x1779 | `DecompressLamportsUndefinedForCompressSol`     | "DecompressLamportsUndefinedForCompressSol"                                                                                       |
+| 6010 | 0x177A | `CompressedSolPdaUndefinedForDecompressSol`     | "CompressedSolPdaUndefinedForDecompressSol"                                                                                       |
+| 6011 | 0x177B | `DeCompressLamportsUndefinedForDecompressSol`   | "DeCompressLamportsUndefinedForDecompressSol"                                                                                     |
+| 6012 | 0x177C | `DecompressRecipientUndefinedForDecompressSol`  | "DecompressRecipientUndefinedForDecompressSol"                                                                                    |
+| 6013 | 0x177D | `WriteAccessCheckFailed`                        | "WriteAccessCheckFailed"                                                                                                          |
+| 6014 | 0x177E | `InvokingProgramNotProvided`                    | "InvokingProgramNotProvided"                                                                                                      |
+| 6015 | 0x177F | `InvalidCapacity`                               | "InvalidCapacity"                                                                                                                 |
+| 6016 | 0x1780 | `InvalidMerkleTreeOwner`                        | "InvalidMerkleTreeOwner"                                                                                                          |
+| 6017 | 0x1781 | `ProofIsNone`                                   | "ProofIsNone"                                                                                                                     |
+| 6018 | 0x1782 | `ProofIsSome`                                   | "Proof is some but no input compressed accounts or new addresses provided."                                                       |
+| 6019 | 0x1783 | `EmptyInputs`                                   | "EmptyInputs"                                                                                                                     |
+| 6020 | 0x1784 | `CpiContextAccountUndefined`                    | "CpiContextAccountUndefined"                                                                                                      |
+| 6021 | 0x1785 | `CpiContextEmpty`                               | "CpiContextEmpty"                                                                                                                 |
+| 6022 | 0x1786 | `CpiContextMissing`                             | "CpiContextMissing"                                                                                                               |
+| 6023 | 0x1787 | `DecompressionRecipientDefined`                 | "DecompressionRecipientDefined"                                                                                                   |
+| 6024 | 0x1788 | `SolPoolPdaDefined`                             | "SolPoolPdaDefined"                                                                                                               |
+| 6025 | 0x1789 | `AppendStateFailed`                             | "AppendStateFailed"                                                                                                               |
+| 6026 | 0x178A | `InstructionNotCallable`                        | "The instruction is not callable"                                                                                                 |
+| 6027 | 0x178B | `CpiContextFeePayerMismatch`                    | "CpiContextFeePayerMismatch"                                                                                                      |
+| 6028 | 0x178C | `CpiContextAssociatedMerkleTreeMismatch`        | "CpiContextAssociatedMerkleTreeMismatch"                                                                                          |
+| 6029 | 0x178D | `NoInputs`                                      | "NoInputs"                                                                                                                        |
+| 6030 | 0x178E | `InputMerkleTreeIndicesNotInOrder`              | "Input merkle tree indices are not in ascending order."                                                                           |
+| 6031 | 0x178F | `OutputMerkleTreeIndicesNotInOrder`             | "Output merkle tree indices are not in ascending order."                                                                          |
+| 6032 | 0x1790 | `OutputMerkleTreeNotUnique`                     | "OutputMerkleTreeNotUnique"                                                                                                       |
+| 6033 | 0x1791 | `DataFieldUndefined`                            | "DataFieldUndefined"                                                                                                              |
+| 6034 | 0x1792 | `ReadOnlyAddressAlreadyExists`                  | "ReadOnlyAddressAlreadyExists"                                                                                                    |
+| 6035 | 0x1793 | `ReadOnlyAccountDoesNotExist`                   | "ReadOnlyAccountDoesNotExist"                                                                                                     |
+| 6036 | 0x1794 | `HashChainInputsLenghtInconsistent`             | "HashChainInputsLenghtInconsistent"                                                                                               |
+| 6037 | 0x1795 | `InvalidAddressTreeHeight`                      | "InvalidAddressTreeHeight"                                                                                                        |
+| 6038 | 0x1796 | `InvalidStateTreeHeight`                        | "InvalidStateTreeHeight"                                                                                                          |
+| 6039 | 0x1797 | `InvalidArgument`                               | "InvalidArgument"                                                                                                                 |
+| 6040 | 0x1798 | `InvalidAccount`                                | "InvalidAccount"                                                                                                                  |
+| 6041 | 0x1799 | `AddressMerkleTreeAccountDiscriminatorMismatch` | "AddressMerkleTreeAccountDiscriminatorMismatch"                                                                                   |
+| 6042 | 0x179A | `StateMerkleTreeAccountDiscriminatorMismatch`   | "StateMerkleTreeAccountDiscriminatorMismatch"                                                                                     |
+| 6043 | 0x179B | `ProofVerificationFailed`                       | "Proof verification failed." [How to debug](https://zkcompression.com/resources/error-cheatsheet/debug-0x179b-6043-proofverificationfailed) |
+| 6044 | 0x179C | `InvalidAccountMode`                            | "Invalid account mode."                                                                                                           |
+| 6045 | 0x179D | `InvalidInstructionDataDiscriminator`           | "InvalidInstructionDataDiscriminator"                                                                                             |
+| 6046 | 0x179E | `NewAddressAssignedIndexOutOfBounds`            | "NewAddressAssignedIndexOutOfBounds"                                                                                              |
+| 6047 | 0x179F | `AddressIsNone`                                 | "AddressIsNone"                                                                                                                   |
+| 6048 | 0x17A0 | `AddressDoesNotMatch`                           | "AddressDoesNotMatch"                                                                                                             |
+| 6049 | 0x17A1 | `CpiContextAlreadySet`                          | "CpiContextAlreadySet"                                                                                                            |
+| 6050 | 0x17A2 | `InvalidTreeHeight`                             | "InvalidTreeHeight"                                                                                                               |
+| 6051 | 0x17A3 | `TooManyOutputAccounts`                         | "TooManyOutputAccounts"                                                                                                           |
+| 6052 | 0x17A4 | `BorrowingDataFailed`                           | "Borrowing data failed"                                                                                                           |
+| 6053 | 0x17A5 | `DuplicateAccountInInputsAndReadOnly`           | "DuplicateAccountInInputsAndReadOnly"                                                                                             |
+
+## 7001 - 7009 / HasherError Variants
+
+> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/hasher/src/errors.rs)
+
+| Code | Hex    | Error                                   | Message                                                                                  |
+| :--- | :----- | :-------------------------------------- | :--------------------------------------------------------------------------------------- |
+| 7001 | 0x1B59 | `IntegerOverflow`                       | "Integer overflow, value too large"                                                      |
+| 7003 | 0x1B5B | `PoseidonSyscall(PoseidonSyscallError)` | "Poseidon syscall error: {0}"                                                            |
+| 7005 | 0x1B5D | `InvalidInputLength(usize, usize)`      | "Allowed input length {0} provided {1}"                                                  |
+| 7006 | 0x1B5E | `InvalidNumFields`                      | "Invalid number of fields"                                                               |
+| 7007 | 0x1B5F | `EmptyInput`                            | "Empty input"                                                                            |
+| 7008 | 0x1B60 | `BorshError`                            | "Borsh serialization failed."                                                            |
+| 7009 | 0x1B61 | `OptionHashToFieldSizeZero`             | "Option hash to field size returned [0u8;32], a collision with None for an Option type." |
+
+## 10001 - 10014 / ConcurrentMerkleTreeError Variants
+
+> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/concurrent-merkle-tree/src/errors.rs)
+
+| Code  | Hex    | Error                                     | Message                                                                                                           |
+| :---- | :----- | :---------------------------------------- | :---------------------------------------------------------------------------------------------------------------- |
+| 10001 | 0x2711 | `IntegerOverflow`                         | "Integer overflow"                                                                                                |
+| 10002 | 0x2712 | `HeightZero`                              | "Invalid height, it has to be greater than 0"                                                                     |
+| 10003 | 0x2713 | `InvalidHeight(usize)`                    | "Invalid height, expected {0}"                                                                                    |
+| 10004 | 0x2714 | `ChangelogZero`                           | "Invalid changelog size, it has to be greater than 0. Changelog is used for storing Merkle paths during appends." |
+| 10005 | 0x2715 | `RootsZero`                               | "Invalid number of roots, it has to be greater than 0"                                                            |
+| 10006 | 0x2716 | `CanopyGeThanHeight`                      | "Canopy depth has to be lower than height"                                                                        |
+| 10007 | 0x2717 | `TreeIsFull`                              | "Merkle tree is full, cannot append more leaves."                                                                 |
+| 10008 | 0x2718 | `BatchGreaterThanChangelog(usize, usize)` | "Number of leaves ({0}) exceeds the changelog capacity ({1})."                                                    |
+| 10009 | 0x2719 | `InvalidProofLength(usize, usize)`        | "Invalid proof length, expected {0}, got {1}."                                                                    |
+| 10010 | 0x271A | `InvalidProof([u8; 32], [u8; 32])`        | "Invalid Merkle proof, expected root: `{0:?}`, the provided proof produces root: `{1:?}`"                         |
+| 10011 | 0x271B | `CannotUpdateLeaf`                        | "Attempting to update the leaf which was updated by an another newest change."                                    |
+| 10012 | 0x271C | `CannotUpdateEmpty`                       | "Cannot update the empty leaf"                                                                                    |
+| 10013 | 0x271D | `EmptyLeaves`                             | "The batch of leaves is empty"                                                                                    |
+| 10014 | 0x271E | `BufferSize(usize, usize)`                | "Invalid buffer size, expected {0}, got {1}"                                                                      |
+
+## 11001 - 11009 / IndexedMerkleTreeError Variants
+
+> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/indexed-merkle-tree/src/errors.rs)
+
+| Code  | Hex    | Error                                   | Message                                                             |
+| :---- | :----- | :-------------------------------------- | :------------------------------------------------------------------ |
+| 11001 | 0x2AF9 | `IntegerOverflow`                       | "Integer overflow"                                                  |
+| 11002 | 0x2AFA | `IndexHigherThanMax`                    | "Invalid index, it exceeds the number of elements."                 |
+| 11003 | 0x2AFB | `LowElementNotFound`                    | "Could not find the low element."                                   |
+| 11004 | 0x2AFC | `LowElementGreaterOrEqualToNewElement`  | "Low element is greater or equal to the provided new element."      |
+| 11005 | 0x2AFD | `NewElementGreaterOrEqualToNextElement` | "The provided new element is greater or equal to the next element." |
+| 11006 | 0x2AFE | `ElementAlreadyExists`                  | "The element already exists, but was expected to be absent."        |
+| 11007 | 0x2AFF | `ElementDoesNotExist`                   | "The element does not exist, but was expected to be present."       |
+| 11008 | 0x2B00 | `ChangelogBufferSize(usize, usize)`     | "Invalid changelog buffer size, expected {0}, got {1}"              |
+| 11009 | 0x2B01 | `ArrayFull`                             | "Indexed array is full, cannot append more elements"                |
+
+## 12006 - 12019 / AccountError Variants
+
+> **Source:** [error.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/account-checks/src/error.rs)
+
+| Code  | Hex    | Error                        | Message                           |
+| :---- | :----- | :--------------------------- | :-------------------------------- |
+| 12006 | 0x2EE6 | `InvalidDiscriminator`       | "Invalid Discriminator."          |
+| 12007 | 0x2EE7 | `AccountOwnedByWrongProgram` | "Account owned by wrong program." |
+| 12008 | 0x2EE8 | `AccountNotMutable`          | "Account not mutable."            |
+| 12009 | 0x2EE9 | `BorrowAccountDataFailed`    | "Borrow account data failed."     |
+| 12010 | 0x2EEA | `InvalidAccountSize`         | "Invalid Account size."           |
+| 12011 | 0x2EEB | `AccountMutable`             | "Account is mutable."             |
+| 12012 | 0x2EEC | `AlreadyInitialized`         | "Account is already initialized." |
+| 12013 | 0x2EED | `InvalidAccountBalance`      | "Invalid account balance."        |
+| 12014 | 0x2EEE | `FailedBorrowRentSysvar`     | "Failed to borrow rent sysvar."   |
+| 12015 | 0x2EEF | `InvalidSigner`              | "Invalid Signer"                  |
+| 12016 | 0x2EF0 | `InvalidSeeds`               | "Invalid Seeds"                   |
+| 12017 | 0x2EF1 | `InvalidProgramId`           | "Invalid Program Id"              |
+| 12018 | 0x2EF2 | `ProgramNotExecutable`       | "Program not executable."         |
+| 12019 | 0x2EF3 | `AccountNotZeroed`           | "Account not zeroed."             |
+
+## 14001 - 14009 / MerkleTreeMetadataError Variants
+
+> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/merkle-tree-metadata/src/errors.rs)
+
+| Code  | Hex    | Error                             | Message                                     |
+| :---- | :----- | :-------------------------------- | :------------------------------------------ |
+| 14001 | 0x36B1 | `MerkleTreeAndQueueNotAssociated` | "Merkle tree and queue are not associated." |
+| 14002 | 0x36B2 | `RolloverNotConfigured`           | "Rollover not configured."                  |
+| 14003 | 0x36B3 | `MerkleTreeAlreadyRolledOver`     | "Merkle tree already rolled over."          |
+| 14004 | 0x36B4 | `InvalidQueueType`                | "Invalid queue type."                       |
+| 14005 | 0x36B5 | `InsufficientRolloverFee`         | "Insufficient rollover fee."                |
+| 14006 | 0x36B6 | `NotReadyForRollover`             | "Merkle tree not ready for rollover."       |
+| 14007 | 0x36B7 | `InvalidTreeType`                 | "Invalid tree type."                        |
+| 14008 | 0x36B8 | `InvalidRolloverThreshold`        | "Invalid Rollover Threshold."               |
+| 14009 | 0x36B9 | `InvalidHeight`                   | "Invalid Height."                           |
+
+## 14017 - 14034 / LightSdkTypesError Variants
+
+> **Source:** [error.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/sdk-libs/sdk-types/src/error.rs#L4)
+
+| Code  | Hex    | Error                                | Message                                                                                                                                           |
+| :---- | :----- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
+| 14017 | 0x36C1 | `FewerAccountsThanSystemAccounts`    | "Fewer accounts than system accounts"                                                                                                             |
+| 14021 | 0x36C5 | `InitAddressIsNone`                  | "Address is none during initialization"                                                                                                           |
+| 14022 | 0x36C6 | `InitWithAddressIsNone`              | "Address is none during initialization with address"                                                                                              |
+| 14023 | 0x36C7 | `InitWithAddressOutputIsNone`        | "Output is none during initialization with address"                                                                                               |
+| 14024 | 0x36C8 | `MetaMutAddressIsNone`               | "Address is none during meta mutation"                                                                                                            |
+| 14025 | 0x36C9 | `MetaMutInputIsNone`                 | "Input is none during meta mutation"                                                                                                              |
+| 14026 | 0x36CA | `MetaMutOutputLamportsIsNone`        | "Output lamports is none during meta mutation"                                                                                                    |
+| 14027 | 0x36CB | `MetaMutOutputIsNone`                | "Output is none during meta mutation"                                                                                                             |
+| 14028 | 0x36CC | `MetaCloseAddressIsNone`             | "Address is none during meta close"                                                                                                               |
+| 14029 | 0x36CD | `MetaCloseInputIsNone`               | "Input is none during meta close"                                                                                                                 |
+| 14031 | 0x36CF | `CpiAccountsIndexOutOfBounds(usize)` | "CPI accounts index out of bounds: {0}"                                                                                                           |
+| 14032 | 0x36D0 | `InvalidCpiContextAccount`           | "Invalid CPI context account"                                                                                                                     |
+| 14033 | 0x36D1 | `InvalidSolPoolPdaAccount`           | "Invalid sol pool pda account"                                                                                                                    |
+| 14034 | 0x36D2 | `InvalidCpiAccountsOffset`           | "CpiAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7." |
+
+## 14301 - 14312 / BatchedMerkleTreeError Variants
+
+> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/batched-merkle-tree/src/errors.rs)
+
+| Code  | Hex    | Error                                 | Message                                                 |
+| :---- | :----- | :------------------------------------ | :------------------------------------------------------ |
+| 14301 | 0x37DD | `BatchNotReady`                       | "Batch is not ready to be inserted"                     |
+| 14302 | 0x37DE | `BatchAlreadyInserted`                | "Batch is already inserted"                             |
+| 14303 | 0x37DF | `BatchInsertFailed`                   | "Batch insert failed"                                   |
+| 14304 | 0x37E0 | `LeafIndexNotInBatch`                 | "Leaf index not in batch."                              |
+| 14305 | 0x37E1 | `InvalidNetworkFee`                   | "Invalid network fee."                                  |
+| 14306 | 0x37E2 | `BatchSizeNotDivisibleByZkpBatchSize` | "Batch size not divisible by ZKP batch size."           |
+| 14307 | 0x37E3 | `InclusionProofByIndexFailed`         | "Inclusion proof by index failed."                      |
+| 14308 | 0x37E4 | `InvalidBatchIndex`                   | "Invalid batch index"                                   |
+| 14309 | 0x37E5 | `InvalidIndex`                        | "Invalid index"                                         |
+| 14310 | 0x37E6 | `TreeIsFull`                          | "Batched Merkle tree is full."                          |
+| 14311 | 0x37E7 | `NonInclusionCheckFailed`             | "Value already exists in bloom filter."                 |
+| 14312 | 0x37E8 | `BloomFilterNotZeroed`                | "Bloom filter must be zeroed prior to reusing a batch." |
+
+## 15001 - 15017 / ZeroCopyError Variants
+
+> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/zero-copy/src/errors.rs)
+
+| Code  | Hex    | Error                                       | Message                                                      |
+| :---- | :----- | :------------------------------------------ | :----------------------------------------------------------- |
+| 15001 | 0x3A99 | `Full`                                      | "The vector is full, cannot push any new elements"           |
+| 15002 | 0x3A9A | `ArraySize(usize, usize)`                   | "Requested array of size {}, but the vector has {} elements" |
+| 15003 | 0x3A9B | `IterFromOutOfBounds`                       | "The requested start index is out of bounds"                 |
+| 15004 | 0x3A9C | `InsufficientMemoryAllocated(usize, usize)` | "Memory allocated {}, Memory required {}"                    |
+| 15006 | 0x3A9E | `UnalignedPointer`                          | "Unaligned pointer"                                          |
+| 15007 | 0x3A9F | `MemoryNotZeroed`                           | "Memory not zeroed"                                          |
+| 15008 | 0x3AA0 | `InvalidConversion`                         | "Invalid conversion"                                         |
+| 15009 | 0x3AA1 | `InvalidData(Infallible)`                   | "Invalid data"                                               |
+| 15010 | 0x3AA2 | `Size`                                      | "Invalid size"                                               |
+| 15011 | 0x3AA3 | `InvalidOptionByte(u8)`                     | "Invalid option byte {} must be 0 (None) or 1 (Some)"        |
+| 15012 | 0x3AA4 | `InvalidCapacity`                           | "Invalid capacity. Capacity must be greater than 0"          |
+| 15013 | 0x3AA5 | `LengthGreaterThanCapacity`                 | "Length is greater than capacity"                            |
+| 15014 | 0x3AA6 | `CurrentIndexGreaterThanLength`             | "Current index is greater than length"                       |
+| 15015 | 0x3AA7 | `InvalidEnumValue`                          | "Invalid enum value"                                         |
+| 15016 | 0x3AA8 | `InsufficientCapacity`                      | "Insufficient capacity for operation"                        |
+| 15017 | 0x3AA9 | `PlatformSizeOverflow`                      | "Value too large for platform usize"                         |
+
+## 16001 - 16034 / LightSdkError Variants
+
+> **Source:** [error.rs](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/sdk/src/error.rs#L126)
+
+| Code  | Hex    | Error                                | Message                                                                                                                                           |
+| :---- | :----- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
+| 16001 | 0x3E81 | `ConstraintViolation`                | "Constraint violation"                                                                                                                            |
+| 16002 | 0x3E82 | `InvalidLightSystemProgram`          | "Invalid light-system-program ID"                                                                                                                 |
+| 16003 | 0x3E83 | `ExpectedAccounts`                   | "Expected accounts in the instruction"                                                                                                            |
+| 16004 | 0x3E84 | `ExpectedAddressTreeInfo`            | "Expected address Merkle context to be provided"                                                                                                  |
+| 16005 | 0x3E85 | `ExpectedAddressRootIndex`           | "Expected address root index to be provided"                                                                                                      |
+| 16006 | 0x3E86 | `ExpectedData`                       | "Accounts with a specified input are expected to have data"                                                                                       |
+| 16007 | 0x3E87 | `ExpectedDiscriminator`              | "Accounts with specified data are expected to have a discriminator"                                                                               |
+| 16008 | 0x3E88 | `ExpectedHash`                       | "Accounts with specified data are expected to have a hash"                                                                                        |
+| 16009 | 0x3E89 | `ExpectedLightSystemAccount(String)` | "Expected the `{0}` light account to be provided"                                                                                                 |
+| 16010 | 0x3E8A | `ExpectedMerkleContext`              | "`mut` and `close` accounts are expected to have a Merkle context"                                                                                |
+| 16011 | 0x3E8B | `ExpectedRootIndex`                  | "Expected root index to be provided"                                                                                                              |
+| 16012 | 0x3E8C | `TransferFromNoInput`                | "Cannot transfer lamports from an account without input"                                                                                          |
+| 16013 | 0x3E8D | `TransferFromNoLamports`             | "Cannot transfer from an account without lamports"                                                                                                |
+| 16014 | 0x3E8E | `TransferFromInsufficientLamports`   | "Account, from which a transfer was attempted, has insufficient amount of lamports"                                                               |
+| 16015 | 0x3E8F | `TransferIntegerOverflow`            | "Integer overflow resulting from too large resulting amount"                                                                                      |
+| 16016 | 0x3E90 | `Borsh`                              | "Borsh error."                                                                                                                                    |
+| 16017 | 0x3E91 | `FewerAccountsThanSystemAccounts`    | "Fewer accounts than number of system accounts."                                                                                                  |
+| 16018 | 0x3E92 | `InvalidCpiSignerAccount`            | "InvalidCpiSignerAccount"                                                                                                                         |
+| 16019 | 0x3E93 | `MissingField(String)`               | "Missing meta field: {0}"                                                                                                                         |
+| 16020 | 0x3E94 | `OutputStateTreeIndexIsNone`         | "Output state tree index is none. Use an CompressedAccountMeta type with output tree index to initialize or update accounts."                     |
+| 16021 | 0x3E95 | `InitAddressIsNone`                  | "Address is none during initialization"                                                                                                           |
+| 16022 | 0x3E96 | `InitWithAddressIsNone`              | "Address is none during initialization with address"                                                                                              |
+| 16023 | 0x3E97 | `InitWithAddressOutputIsNone`        | "Output is none during initialization with address"                                                                                               |
+| 16024 | 0x3E98 | `MetaMutAddressIsNone`               | "Address is none during meta mutation"                                                                                                            |
+| 16025 | 0x3E99 | `MetaMutInputIsNone`                 | "Input is none during meta mutation"                                                                                                              |
+| 16026 | 0x3E9A | `MetaMutOutputLamportsIsNone`        | "Output lamports is none during meta mutation"                                                                                                    |
+| 16027 | 0x3E9B | `MetaMutOutputIsNone`                | "Output is none during meta mutation"                                                                                                             |
+| 16028 | 0x3E9C | `MetaCloseAddressIsNone`             | "Address is none during meta close"                                                                                                               |
+| 16029 | 0x3E9D | `MetaCloseInputIsNone`               | "Input is none during meta close"                                                                                                                 |
+| 16031 | 0x3E9F | `CpiAccountsIndexOutOfBounds(usize)` | "CPI accounts index out of bounds: {0}"                                                                                                           |
+| 16032 | 0x3EA0 | `InvalidCpiContextAccount`           | "Invalid CPI context account"                                                                                                                     |
+| 16033 | 0x3EA1 | `InvalidSolPoolPdaAccount`           | "Invalid SolPool PDA account"                                                                                                                     |
+| 16034 | 0x3EA2 | `InvalidCpiAccountsOffset`           | "CpiAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7." |
diff --git a/references/light-token.md b/references/light-token.md
new file mode 100644
index 00000000..98062ff3
--- /dev/null
+++ b/references/light-token.md
@@ -0,0 +1,115 @@
+# Light Token
+
+## Light Token SDK
+
+The base library to use Light Token Accounts, Light Mints, and compressed token accounts.
+
+### Light Token Accounts
+- are on Solana devnet.
+- are Solana accounts.
+- can hold tokens of Light, SPL and Token 2022 mints.
+- cost 17,288 lamports to create with 24 hours rent.
+- are rentfree:
+    - rent exemption is sponsored by the token program.
+    - rent is 388 lamports per rent epoch (1.5 hours).
+    - once the account's lamports balance is insufficient, it is auto-compressed to a compressed token account.
+    - the accounts state is cryptographically preserved on the Solana ledger.
+    - compressed tokens can be decompressed to a Light Token account.
+    - configurable lamports per write (eg transfer) keep the Light Token account perpetually funded when used. So you don't have to worry about funding rent.
+    - users load a compressed account into a light account in-flight when using the account again.
+
+### Light Mints
+- are on Solana devnet.
+- are Compressed accounts.
+- cost 15,000 lamports to create.
+- support `TokenMetadata`.
+- have the same rent-config as light token accounts
+
+## CPI Operations
+
+For full program examples, see the [Light Token Examples](https://github.com/Lightprotocol/examples-light-token).
+
+| Operation | Docs guide | GitHub example |
+|-----------|-----------|----------------|
+| `CreateAssociatedAccountCpi` | [create-ata](https://zkcompression.com/light-token/cookbook/create-ata) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-ata) |
+| `CreateTokenAccountCpi` | [create-token-account](https://zkcompression.com/light-token/cookbook/create-token-account) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-token-account) |
+| `CreateMintCpi` | [create-mint](https://zkcompression.com/light-token/cookbook/create-mint) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-mint) |
+| `MintToCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to) |
+| `MintToCheckedCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to-checked) |
+| `BurnCpi` | [burn](https://zkcompression.com/light-token/cookbook/burn) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/burn) |
+| `TransferCheckedCpi` | [transfer-checked](https://zkcompression.com/light-token/cookbook/transfer-checked) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-checked) |
+| `TransferInterfaceCpi` | [transfer-interface](https://zkcompression.com/light-token/cookbook/transfer-interface) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-interface) |
+| `ApproveCpi` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/approve) |
+| `RevokeCpi` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/revoke) |
+| `FreezeCpi` | [freeze-thaw](https://zkcompression.com/light-token/cookbook/freeze-thaw) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/freeze) |
+| `ThawCpi` | [freeze-thaw](https://zkcompression.com/light-token/cookbook/freeze-thaw) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/thaw) |
+| `CloseAccountCpi` | [close-token-account](https://zkcompression.com/light-token/cookbook/close-token-account) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/close-token-account) |
+
+### Common Operations
+
+| Operation | Instruction Builder | CPI Builder |
+|-----------|----------------|-------------|
+| Create Associated Token Account | `CreateAssociatedTokenAccount` | `CreateAssociatedAccountCpi` |
+| Create Token Account | `CreateTokenAccount` | `CreateTokenAccountCpi` |
+| Transfer | `Transfer` | `TransferCpi` |
+| Transfer Interface (auto-detect) | `TransferInterface` | `TransferInterfaceCpi` |
+| Close Token account | `CloseAccount` | `CloseAccountCpi` |
+| Create Mint | `CreateMint` | `CreateMintCpi` |
+| MintTo | `MintTo` | `MintToCpi` |
+
+### Features
+
+1. `anchor` - Derives AnchorSerialize, AnchorDeserialize instead of BorshSerialize, BorshDeserialize.
+2. `compressible` - utility functions for compressible sdk macros.
+
+## TypeScript Client
+
+Rust client for light-token. Each action builds, signs, and sends the transaction.
+
+| Action | Description |
+|--------|-------------|
+| `CreateMint` | Create a light-token mint with metadata |
+| `CreateAta` | Create an associated light-token account |
+| `MintTo` | Mint tokens to a light-token account |
+| `Transfer` | Transfer light-tokens between accounts |
+| `TransferChecked` | Transfer with decimal validation |
+| `TransferInterface` | Transfer between light-token, T22, and SPL accounts |
+| `Approve` | Approve a delegate |
+| `Revoke` | Revoke a delegate |
+| `Wrap` | Wrap SPL/T22 to light-token |
+| `Unwrap` | Unwrap light-token to SPL/T22 |
+
+### TypeScript Examples
+
+- **create-mint** - Create a light-token mint
+  - [Action](typescript-client/actions/create-mint.ts) | [Instruction](typescript-client/instructions/create-mint.ts)
+- **create-ata** - Create an associated light-token account
+  - [Action](typescript-client/actions/create-ata.ts) | [Instruction](typescript-client/instructions/create-ata.ts)
+- **load-ata** - Load token accounts from light-token, compressed tokens, SPL/T22 to one unified balance.
+  - [Action](typescript-client/actions/load-ata.ts) | [Instruction](typescript-client/instructions/load-ata.ts)
+- **mint-to** - Mint tokens to a light-account
+  - [Action](typescript-client/actions/mint-to.ts) | [Instruction](typescript-client/instructions/mint-to.ts)
+- **transfer-interface** - Transfer between light-token, T22, and SPL accounts
+  - [Action](typescript-client/actions/transfer-interface.ts) | [Instruction](typescript-client/instructions/transfer-interface.ts)
+- **wrap** - Wrap SPL/T22 to light-token
+  - [Action](typescript-client/actions/wrap.ts)
+- **unwrap** - Unwrap light-token to SPL/T22
+  - [Action](typescript-client/actions/unwrap.ts)
+
+## Toolkits
+
+| Toolkit | Docs |
+|---------|------|
+| Payments & wallets | [for-payments](https://zkcompression.com/light-token/toolkits/for-payments) |
+| Wallets | [for-wallets](https://zkcompression.com/light-token/toolkits/for-wallets) |
+| Streaming tokens | [for-streaming-tokens](https://zkcompression.com/light-token/toolkits/for-streaming-tokens) |
+| Streaming mints | [for-streaming-mints](https://zkcompression.com/light-token/toolkits/for-streaming-mints) |
+
+## SDKs
+
+- Rust on-chain: [`light-token`](https://docs.rs/light-token) ([crates.io](https://crates.io/crates/light-token))
+- Rust client: [`light-token-client`](https://docs.rs/light-token-client) ([crates.io](https://crates.io/crates/light-token-client))
+- GitHub examples: [examples-light-token](https://github.com/Lightprotocol/examples-light-token)
+
+## Disclaimer
+This library is not audited and in a beta state. Use at your own risk and expect breaking changes.
diff --git a/references/sdk-reference.md b/references/sdk-reference.md
new file mode 100644
index 00000000..22abef52
--- /dev/null
+++ b/references/sdk-reference.md
@@ -0,0 +1,54 @@
+# SDK Reference
+
+## Rust Crates
+
+### Program Development
+
+| Crate | Description | docs.rs | crates.io |
+|-------|-------------|---------|-----------|
+| `light-sdk` | Compressed accounts in Anchor/Rust programs | [docs.rs/light-sdk](https://docs.rs/light-sdk) | [crates.io](https://crates.io/crates/light-sdk) |
+| `light-sdk-pinocchio` | Compressed accounts in native Pinocchio programs | [docs.rs/light-sdk-pinocchio](https://docs.rs/light-sdk-pinocchio) | [crates.io](https://crates.io/crates/light-sdk-pinocchio) |
+| `light-token` | Light Token accounts, mints, compressed tokens (on-chain) | [docs.rs/light-token](https://docs.rs/light-token) | [crates.io](https://crates.io/crates/light-token) |
+| `light-compressed-token-sdk` | Low-level SDK for compressed token operations | [docs.rs/light-compressed-token-sdk](https://docs.rs/light-compressed-token-sdk) | [crates.io](https://crates.io/crates/light-compressed-token-sdk) |
+
+### Client Development
+
+| Crate | Description | docs.rs | crates.io |
+|-------|-------------|---------|-----------|
+| `light-client` | Client library for compressed accounts and RPC | [docs.rs/light-client](https://docs.rs/light-client) | [crates.io](https://crates.io/crates/light-client) |
+| `light-token-client` | Rust client for light-token actions | [docs.rs/light-token-client](https://docs.rs/light-token-client) | [crates.io](https://crates.io/crates/light-token-client) |
+
+### Testing
+
+| Crate | Description | docs.rs | crates.io |
+|-------|-------------|---------|-----------|
+| `light-program-test` | Fast local test environment (LiteSVM) | [docs.rs/light-program-test](https://docs.rs/light-program-test) | [crates.io](https://crates.io/crates/light-program-test) |
+
+## TypeScript Packages
+
+| Package | npm | TypeDocs |
+|---------|-----|---------|
+| `@lightprotocol/stateless.js` | [npm](https://www.npmjs.com/package/@lightprotocol/stateless.js) | [typedocs](https://lightprotocol.github.io/light-protocol/stateless.js/index.html) |
+| `@lightprotocol/compressed-token` | [npm](https://www.npmjs.com/package/@lightprotocol/compressed-token) | [typedocs](https://lightprotocol.github.io/light-protocol/compressed-token/index.html) |
+
+## CLI
+
+| Tool | npm |
+|------|-----|
+| `@lightprotocol/zk-compression-cli` | [npm](https://www.npmjs.com/package/@lightprotocol/zk-compression-cli) |
+
+Install:
+```bash
+npm i -g @lightprotocol/zk-compression-cli
+```
+
+## External Resources
+
+| Resource | Link |
+|----------|------|
+| Documentation | [zkcompression.com](https://www.zkcompression.com/) |
+| Light Token examples | [examples-light-token](https://github.com/Lightprotocol/examples-light-token) |
+| Compressed Token examples | [examples-zk-compression](https://github.com/Lightprotocol/examples-zk-compression) |
+| Program examples | [program-examples](https://github.com/Lightprotocol/program-examples) |
+| MCP server | [zkcompression.com/mcp](https://www.zkcompression.com/mcp) |
+| DeepWiki | [deepwiki.com/Lightprotocol/light-protocol](https://deepwiki.com/Lightprotocol/light-protocol) |
diff --git a/references/testing.md b/references/testing.md
new file mode 100644
index 00000000..bf18a435
--- /dev/null
+++ b/references/testing.md
@@ -0,0 +1,249 @@
+# Light Protocol Testing
+
+## Routing
+
+| Task | Section |
+|------|---------|
+| Start local validator | [Local Testing](#local-testing-with-light-test-validator) |
+| Test on devnet | [Devnet Testing](#devnet-testing) |
+| Rust program tests | [light-program-test](#rust-program-tests-with-light-program-test) |
+
+## Program Addresses
+
+These addresses are identical on devnet and mainnet.
+
+| Program | Address |
+|---------|---------|
+| Light System | `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` |
+| Compressed Token | `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` |
+| Account Compression | `compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq` |
+| Light Registry | `Lighton6oQpVkeewmo2mcPTQQp7kYHr4fWpAgJyEmDX` |
+
+## Local Testing with light-test-validator
+
+Local development environment running Solana test validator with Light Protocol programs, Photon indexer, and ZK prover.
+
+### Quick Start
+
+```bash
+# Start all services
+light test-validator
+
+# Stop
+light test-validator --stop
+```
+
+### Services & Ports
+
+| Service | Port | Endpoint |
+|---------|------|----------|
+| Solana RPC | 8899 | `http://127.0.0.1:8899` |
+| Solana WebSocket | 8900 | `ws://127.0.0.1:8900` |
+| Photon Indexer | 8784 | `http://127.0.0.1:8784` |
+| Light Prover | 3001 | `http://127.0.0.1:3001` |
+
+### Command Flags
+
+| Flag | Default | Description |
+|------|---------|-------------|
+| `--skip-indexer` | false | Run without Photon indexer |
+| `--skip-prover` | false | Run without Light Prover |
+| `--skip-system-accounts` | false | Skip pre-initialized accounts |
+| `--devnet` | false | Clone programs from devnet |
+| `--mainnet` | false | Clone programs from mainnet |
+| `--sbf-program  ` | - | Load additional program |
+| `--skip-reset` | false | Keep existing ledger |
+| `--verbose` | false | Enable verbose logging |
+
+### Deployed Programs
+
+| Program | Address |
+|---------|---------|
+| SPL Noop | `noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV` |
+| Light System | `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` |
+| Compressed Token | `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` |
+| Account Compression | `compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq` |
+| Light Registry | `Lighton6oQpVkeewmo2mcPTQQp7kYHr4fWpAgJyEmDX` |
+
+### TypeScript Test Integration
+
+```typescript
+import { getTestRpc, newAccountWithLamports } from '@lightprotocol/stateless.js/test-helpers';
+import { WasmFactory } from '@lightprotocol/hasher.rs';
+
+const lightWasm = await WasmFactory.getInstance();
+const rpc = await getTestRpc(lightWasm);
+const payer = await newAccountWithLamports(rpc, 1e9, 256);
+```
+
+Run tests:
+
+```bash
+cd js/stateless.js
+pnpm test-validator && pnpm test:e2e:all
+```
+
+### Troubleshooting
+
+Validator fails to start:
+
+```bash
+lsof -i :8899              # Check port
+light test-validator --stop # Stop existing
+rm -rf test-ledger/        # Reset ledger
+```
+
+Photon version mismatch:
+
+```bash
+cargo install --git https://github.com/lightprotocol/photon.git \
+  --rev ac7df6c388db847b7693a7a1cb766a7c9d7809b5 --locked --force
+```
+
+### File Locations
+
+| Component | Location |
+|-----------|----------|
+| Program binaries | `~/.config/light/bin/` |
+| Prover binary | `~/.config/light/bin/prover-{platform}-{arch}` |
+| Proving keys | `~/.config/light/proving-keys/` |
+| Test ledger | `./test-ledger/` |
+
+## Devnet Testing
+
+### Quick Start
+
+```typescript
+import { createRpc } from "@lightprotocol/stateless.js";
+
+const connection = createRpc(
+  "https://devnet.helius-rpc.com?api-key=",
+  "https://devnet.helius-rpc.com?api-key=",
+  "https://devnet.helius-rpc.com?api-key="
+);
+```
+
+### Endpoints
+
+| Service   | URL                                              |
+|-----------|--------------------------------------------------|
+| RPC       | `https://devnet.helius-rpc.com?api-key=` |
+| WebSocket | `wss://devnet.helius-rpc.com?api-key=`   |
+| Indexer   | `https://devnet.helius-rpc.com?api-key=` |
+| Prover    | `https://prover.helius.dev`                       |
+
+### Client Setup
+
+```typescript
+import { Rpc, createRpc } from "@lightprotocol/stateless.js";
+
+const HELIUS_API_KEY = process.env.HELIUS_API_KEY;
+
+const RPC_ENDPOINT = `https://devnet.helius-rpc.com?api-key=${HELIUS_API_KEY}`;
+const COMPRESSION_ENDPOINT = RPC_ENDPOINT;
+const PROVER_ENDPOINT = "https://prover.helius.dev";
+
+const connection: Rpc = createRpc(RPC_ENDPOINT, COMPRESSION_ENDPOINT, PROVER_ENDPOINT);
+
+// Fetch state trees at runtime
+const { stateTrees } = await connection.getCachedActiveStateTreeInfo();
+const outputStateTree = stateTrees[0].tree;
+```
+
+### Key Considerations
+
+- **Helius or Triton required**: The photon indexer implementation is maintained by Helius. You can also use Triton. Currently these RPC's provide compression endpoints
+- **Runtime tree fetch**: Always fetch active state trees at runtime via `getCachedActiveStateTreeInfo()`
+- **Same programs**: Program addresses are identical on devnet and mainnet
+- **Devnet-specific trees**: State tree lookup tables differ from mainnet
+
+### Devnet Addresses
+
+| Lookup Table                    | Address                                        |
+|---------------------------------|------------------------------------------------|
+| State Tree Lookup Table         | `DmRueT3LMJdGj3TEprqKtfwMxyNUHDnKrQua4xrqtbmG` |
+| Address Tree Lookup Table       | `G4HqCAWPJ1E3JmYX1V2RZvNMuzF6gcFdbwT8FccWX6ru` |
+
+Usage notes:
+
+- Always fetch state trees dynamically using `getCachedActiveStateTreeInfo()`
+- Do not hardcode tree addresses; they rotate as trees fill up
+- Lookup table addresses are stable and can be referenced directly
+
+## Rust Program Tests with light-program-test
+
+A fast local test environment for Solana programs using compressed accounts and tokens.
+
+**Use `light-program-test` when:**
+- You need fast test execution
+- You write unit/integration tests for your program or client code
+
+**Use `solana-test-validator` when:**
+- You need RPC methods or external tools that are incompatible with LiteSVM
+- Testing against real validator behavior
+
+### Prerequisites
+
+1. **ZK Compression CLI**: Required to start the prover server and download Light Protocol programs
+
+   ```bash
+   npm i -g @lightprotocol/zk-compression-cli
+   ```
+
+   If programs are missing after CLI installation, run `light test-validator` once to download them
+
+2. **Build programs**: Run `cargo test-sbf` to build program binaries and set the required
+   environment variables for locating program artifacts
+
+### Debugging
+
+Set `RUST_BACKTRACE=1` to show detailed transaction information including accounts and parsed instructions:
+
+```bash
+RUST_BACKTRACE=1 cargo test-sbf -- --nocapture
+```
+
+## Pre-initialized Accounts
+
+The test validator loads 39 pre-initialized accounts from the CLI's `accounts/` directory.
+
+### State Trees (V1)
+
+| Type | Address |
+|------|---------|
+| Merkle tree 1 | `smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT` |
+| Nullifier queue 1 | `nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148` |
+| CPI context 1 | `cpi1uHzrEhBG733DoEJNgHCyRS3XmmyVNZx5fonubE4` |
+| Merkle tree 2 | `smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho` |
+| Nullifier queue 2 | `nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X` |
+| CPI context 2 | `cpi2cdhkH5roePvcudTgUL8ppEBfTay1desGh8G8QxK` |
+
+### Batched State Trees (V2)
+
+Five batched state tree triplets (bmt/oq/cpi):
+
+| Set | BMT | OQ | CPI |
+|-----|-----|-----|-----|
+| 1 | `bmt1LryLZUMmF7ZtqESaw7wifBXLfXHQYoE4GAmrahU` | `oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto` | `cpi15BoVPKgEPw5o8wc2T816GE7b378nMXnhH3Xbq4y` |
+| 2 | `bmt2UxoBxB9xWev4BkLvkGdapsz6sZGkzViPNph7VFi` | `oq2UkeMsJLfXt2QHzim242SUi3nvjJs8Pn7Eac9H9vg` | `cpi2yGapXUR3As5SjnHBAVvmApNiLsbeZpF3euWnW6B` |
+| 3 | `bmt3ccLd4bqSVZVeCJnH1F6C8jNygAhaDfxDwePyyGb` | `oq3AxjekBWgo64gpauB6QtuZNesuv19xrhaC1ZM1THQ` | `cpi3mbwMpSX8FAGMZVP85AwxqCaQMfEk9Em1v8QK9Rf` |
+| 4 | `bmt4d3p1a4YQgk9PeZv5s4DBUmbF5NxqYpk9HGjQsd8` | `oq4ypwvVGzCUMoiKKHWh4S1SgZJ9vCvKpcz6RT6A8dq` | `cpi4yyPDc4bCgHAnsenunGA8Y77j3XEDyjgfyCKgcoc` |
+| 5 | `bmt5yU97jC88YXTuSukYHa8Z5Bi2ZDUtmzfkDTA2mG2` | `oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P` | `cpi5ZTjdgYpZ1Xr7B1cMLLUE81oTtJbNNAyKary2nV6` |
+
+### Address Trees
+
+| Type | Address |
+|------|---------|
+| Address Merkle tree (V1) | `amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2` |
+| Address queue (V1) | `aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F` |
+| Batch address tree (V2) | `amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx` |
+
+### Protocol PDAs
+
+| Type | Address |
+|------|---------|
+| Governance authority | `CuEtcKkkbTn6qy2qxqDswq5U2ADsqoipYDAYfRvxPjcp` |
+| Config counter | `8gH9tmziWsS8Wc4fnoN5ax3jsSumNYoRDuSBvmH2GMH8` |
+| Registered program PDA | `35hkDgaAKwMCaxRz2ocSZ6NaUrtKkyNqU6c4RV3tYJRh` |
+| Registered registry program PDA | `DumMsyvkaGJG4QnQ1BhTgvoRMXsgGxfpKDUCr22Xqu4w` |
+| Group PDA | `24rt4RgeyjUCWGS2eF7L7gyNMuz6JWdqYpAvb1KRoHxs` |
diff --git a/references/zk-nullifiers.md b/references/zk-nullifiers.md
new file mode 100644
index 00000000..0bfaaab8
--- /dev/null
+++ b/references/zk-nullifiers.md
@@ -0,0 +1,288 @@
+# ZK Nullifiers
+
+Uses Compressed PDAs.
+
+## Overview
+
+Building a ZK Solana program requires:
+- Nullifiers to prevent double spending
+- Proof verification
+- A Merkle tree to store state
+- An indexer to serve Merkle proofs
+- Encrypted state
+
+## Nullifiers on Solana
+
+A nullifier is a deterministically derived hash to ensure an action can only be performed once. The nullifier cannot be linked to the action or user. For example Zcash uses nullifiers to prevent double spending.
+
+To implement nullifiers we need a data structure that ensures every nullifier is only created once and never deleted. On Solana a straight forward way to implement nullifiers is to create a PDA account with the nullifier as seed.
+
+PDA accounts cannot be closed and permanently lock 890,880 lamports (per nullifier rent-exemption).
+Compressed PDAs are derived similar to Solana PDAs and cost 15,000 lamports to create (no rent-exemption).
+
+| Storage | Cost per nullifier |
+|---------|-------------------|
+| PDA | 890,880 lamports |
+| Compressed PDA | 15,000 lamports |
+## When to Use Nullifiers
+
+## Pattern Overview
+
+```
+1. Client computes nullifier = hash(secret, context)
+2. Client fetches validity proof for derived address (proves it does not exist)
+3. Client calls create_nullifier with nullifier values and proof
+4. Program derives address from nullifier, creates compressed account via CPI
+5. Light system program rejects CPI if address already exists
+```
+
+## Reference Implementation
+
+Source: [program-examples/zk/nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier)
+
+### Account Structure
+
+```rust
+#[derive(Clone, Debug, Default, BorshSerialize, BorshDeserialize, LightDiscriminator)]
+pub struct NullifierAccount {}
+```
+
+Empty struct since existence alone proves the nullifier was used.
+
+### Address Derivation
+
+```rust
+pub const NULLIFIER_PREFIX: &[u8] = b"nullifier";
+
+let (address, address_seed) = derive_address(
+    &[NULLIFIER_PREFIX, nullifier.as_slice()],  // seeds
+    &address_tree_pubkey,                        // address tree
+    &program_id,                                 // program ID
+);
+```
+
+Address is deterministically derived from:
+- Constant prefix (prevents collisions with other account types)
+- Nullifier value (32 bytes)
+- Address tree pubkey
+- Program ID
+
+### Instruction Data
+
+```rust
+#[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)]
+pub struct NullifierInstructionData {
+    pub proof: ValidityProof,           // ZK proof that addresses don't exist
+    pub address_tree_info: PackedAddressTreeInfo,
+    pub output_state_tree_index: u8,
+    pub system_accounts_offset: u8,
+}
+```
+
+### Create Nullifiers Function
+
+```rust
+pub fn create_nullifiers<'info>(
+    nullifiers: &[[u8; 32]],
+    data: NullifierInstructionData,
+    signer: &AccountInfo<'info>,
+    remaining_accounts: &[AccountInfo<'info>],
+) -> Result<()> {
+    let light_cpi_accounts = CpiAccounts::new(
+        signer,
+        &remaining_accounts[data.system_accounts_offset as usize..],
+        LIGHT_CPI_SIGNER,
+    );
+
+    let address_tree_pubkey = data
+        .address_tree_info
+        .get_tree_pubkey(&light_cpi_accounts)
+        .map_err(|_| ErrorCode::AccountNotEnoughKeys)?;
+
+    let mut cpi_builder = LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, data.proof);
+    let mut new_address_params: Vec =
+        Vec::with_capacity(nullifiers.len());
+
+    for (i, nullifier) in nullifiers.iter().enumerate() {
+        let (address, address_seed) = derive_address(
+            &[NULLIFIER_PREFIX, nullifier.as_slice()],
+            &address_tree_pubkey,
+            &crate::ID,
+        );
+
+        let nullifier_account = LightAccount::::new_init(
+            &crate::ID,
+            Some(address),
+            data.output_state_tree_index,
+        );
+
+        cpi_builder = cpi_builder.with_light_account(nullifier_account)?;
+        new_address_params.push(
+            data.address_tree_info
+                .into_new_address_params_assigned_packed(address_seed, Some(i as u8)),
+        );
+    }
+
+    cpi_builder
+        .with_new_addresses(&new_address_params)
+        .invoke(light_cpi_accounts)?;
+
+    Ok(())
+}
+```
+
+### Program Entry Point
+
+```rust
+#[program]
+pub mod nullifier {
+    pub fn create_nullifier<'info>(
+        ctx: Context<'_, '_, '_, 'info, CreateNullifierAccounts<'info>>,
+        data: NullifierInstructionData,
+        nullifiers: Vec<[u8; 32]>,
+    ) -> Result<()> {
+        // Verify your ZK proof here. Use nullifiers as public inputs.
+        // Example:
+        // let public_inputs = [...nullifiers, ...your_other_inputs];
+        // Groth16Verifier::new(...).verify()?;
+
+        create_nullifiers(
+            &nullifiers,
+            data,
+            ctx.accounts.signer.as_ref(),
+            ctx.remaining_accounts,
+        )
+    }
+}
+
+#[derive(Accounts)]
+pub struct CreateNullifierAccounts<'info> {
+    #[account(mut)]
+    pub signer: Signer<'info>,
+}
+```
+
+## Client Implementation (TypeScript)
+
+```typescript
+const NULLIFIER_PREFIX = Buffer.from("nullifier");
+const addressTree = new web3.PublicKey(batchAddressTree);
+
+// Derive addresses for each nullifier
+const addressesWithTree = nullifiers.map((nullifier) => {
+    const seed = deriveAddressSeedV2([NULLIFIER_PREFIX, nullifier]);
+    const address = deriveAddressV2(seed, addressTree, programId);
+    return { tree: addressTree, queue: addressTree, address: bn(address.toBytes()) };
+});
+
+// Get validity proof (proves addresses don't exist)
+const proofResult = await rpc.getValidityProofV0([], addressesWithTree);
+
+// Build remaining accounts
+const remainingAccounts = new PackedAccounts();
+remainingAccounts.addSystemAccountsV2(SystemAccountMetaConfig.new(programId));
+const addressMerkleTreeIndex = remainingAccounts.insertOrGet(addressTree);
+const outputStateTreeIndex = remainingAccounts.insertOrGet(outputStateTree);
+
+// Build instruction data
+const data = {
+    proof: { 0: proofResult.compressedProof },
+    addressTreeInfo: {
+        addressMerkleTreePubkeyIndex: addressMerkleTreeIndex,
+        addressQueuePubkeyIndex: addressMerkleTreeIndex,
+        rootIndex: proofResult.rootIndices[0],
+    },
+    outputStateTreeIndex,
+    systemAccountsOffset: systemStart,
+};
+
+// Call program
+const ix = await program.methods
+    .createNullifier(data, nullifiers.map((n) => Array.from(n)))
+    .accounts({ signer: signer.publicKey })
+    .remainingAccounts(remainingAccounts)
+    .instruction();
+```
+
+## Client Implementation (Rust)
+
+```rust
+use light_sdk::address::v2::derive_address;
+
+let address_tree_info = rpc.get_address_tree_v2();
+
+// Derive addresses
+let address_with_trees: Vec = nullifiers
+    .iter()
+    .map(|n| {
+        let (address, _) = derive_address(
+            &[NULLIFIER_PREFIX, n.as_slice()],
+            &address_tree_info.tree,
+            &program_id,
+        );
+        AddressWithTree {
+            address,
+            tree: address_tree_info.tree,
+        }
+    })
+    .collect();
+
+// Get validity proof (empty hashes = non-inclusion proof)
+let rpc_result = rpc
+    .get_validity_proof(vec![], address_with_trees, None)
+    .await?
+    .value;
+
+// Build accounts
+let mut remaining_accounts = PackedAccounts::default();
+let config = SystemAccountMetaConfig::new(program_id);
+remaining_accounts.add_system_accounts_v2(config)?;
+
+let packed_address_tree_accounts = rpc_result
+    .pack_tree_infos(&mut remaining_accounts)
+    .address_trees;
+
+let output_state_tree_index = rpc
+    .get_random_state_tree_info()?
+    .pack_output_tree_index(&mut remaining_accounts)?;
+```
+
+## How Nullifier Trees Work
+
+Light Protocol uses indexed Merkle trees for nullifiers (address trees). When creating a nullifier:
+
+1. Program derives a deterministic address from nullifier value
+2. Validity proof proves this address does NOT exist in the tree
+3. Light system program inserts the address into the tree
+4. Future attempts fail because address now exists
+
+The indexed Merkle tree structure allows efficient non-inclusion proofs. Each leaf contains not just its value but also a pointer to the next-highest value, enabling proofs that a value falls between two existing values.
+
+## Dependencies
+
+```toml
+[dependencies]
+anchor-lang = "0.31"
+light-sdk = { version = "0.14", features = ["anchor"] }
+borsh = "0.10"
+
+[dev-dependencies]
+light-program-test = "0.14"
+```
+
+## Testing
+
+```bash
+# Rust tests
+cargo test-sbf -p nullifier
+
+# TypeScript tests (requires light test-validator)
+light test-validator  # separate terminal
+npm run test:ts
+```
+
+## Resources
+
+- Full example: [program-examples/zk/nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier)
+- ZK overview: [zkcompression.com/zk/overview](https://www.zkcompression.com/zk/overview)
+- Additional ZK examples: [program-examples/zk](https://github.com/Lightprotocol/program-examples/tree/main/zk) (nullifier, zk-id, mixer, shielded-pool)
diff --git a/skill.md b/skill.md
new file mode 100644
index 00000000..927ab9c3
--- /dev/null
+++ b/skill.md
@@ -0,0 +1,175 @@
+---
+name: solana-rent-free-dev
+description: Skill for Solana development without rent-exemption. Overview to skills to develop solana client (typescript and rust) and programs (anchor, native rust, pinocchio). Covers: for defi light-token; for airdrops, token distribution, payments compressed token; for user and app state compressed pda's (not config or pool accounts); for privacy and zk programs nullifiers to prevent double spending. add skill via npx skills add zkcompression.com/skill.md
+license: MIT
+compatibility: Requires ZK Compression CLI, Solana CLI, Anchor CLI, Node.js.
+metadata:
+  author: lightprotocol
+  version: "1.0"
+allowed-tools: mcp__zkcompression__SearchLightProtocol WebFetch(https://zkcompression.com/*) WebFetch(https://github.com/Lightprotocol/*)
+---
+
+# Light Protocol Development
+
+Understand the available primitives.
+
+| Primitive | Reference |
+|-----------|-----------|
+| Light Token (DeFi, payments) | [references/light-token.md](references/light-token.md) |
+| Airdrop / Token Distribution | [references/airdrop-client.md](references/airdrop-client.md) |
+| Compressed PDA (user state, app state) | [references/compressed-pda.md](references/compressed-pda.md) |
+| Nullifiers (prevent double spending, ZK/Privacy apps) | [references/zk-nullifiers.md](references/zk-nullifiers.md) |
+| DeepWiki Research | [references/deepwiki.md](references/deepwiki.md) |
+
+add these skills via npx skills add yourdomain.com/docs/skill.md
+
+---
+
+SDK References:
+* Typescript SDK 
+    * for accounts: https://lightprotocol.github.io/light-protocol/stateless.js/index.html
+    * for tokens: https://lightprotocol.github.io/light-protocol/compressed-token/index.html
+* Rust  
+    * Client for accounts and tokens: https://docs.rs/light-client/latest/light_client/
+    * Client SDK for Light Token: https://docs.rs/light-token/latest/light_token/
+    * for building on-chain programs with compressed accounts: https://docs.rs/light-sdk/latest/light_sdk
+* Program Testing: https://docs.rs/light-program-test/latest/light_program_test/
+
+When stuck or debugging load the deepwiki skill.
+
+## Light Token
+
+Light token is a high-performance token standard that reduces the cost of mint and token accounts by 200x.
+
+* All light mint and token accounts are on-chain accounts like SPL, but the light token program sponsors the rent-exemption cost for you.
+* Light-token accounts can hold balances from any light, SPL, or Token-2022 mint.
+* Light-mint accounts represent a unique mint and optionally can store token-metadata. Functionally equivalent to SPL mints.
+
+[Full reference](references/light-token.md)
+
+### Use Case: DeFi
+
+For DeFi program integration (AMMs, vaults, lending), see the `defi-dev` skill which covers macro and CPI program patterns, hot/cold account loading, and Jito bundles.
+
+Load Defi Dev skill -> 
+
+### Toolkits
+
+| Toolkit | Docs |
+|---------|------|
+| Payments & Wallets | [for-payments](https://zkcompression.com/light-token/toolkits/for-payments) |
+| Streaming tokens | [for-streaming-tokens](https://zkcompression.com/light-token/toolkits/for-streaming-tokens) |
+
+Load other Toolkits ->
+
+---
+
+## Compressed Token
+
+[Full reference](references/compressed-token.md)
+
+Compressed Token Accounts
+- are well suited for airdrops and reward distribution.
+- require no rent-exemption
+- are on Solana mainnet.
+- are compressed accounts (similar UX to SPL)
+- can hold Light Mint and SPL Mint tokens.
+- cost 5,000 lamports to create.
+- Wallet support by Phantom and Backpack
+
+### Difference to Light-Token
+light-token: Solana account that holds token balances of light-mints, SPL or Token 22 mints.
+Compressed token: Compressed account storing token data. Rent-free, for storage and distribution.
+
+### Guides & Examples
+
+| Topic | Docs guide | GitHub example |
+|-------|-----------|----------------|
+| Airdrop | [airdrop](https://www.zkcompression.com/compressed-tokens/advanced-guides/airdrop) | [example](https://github.com/Lightprotocol/examples-zk-compression/tree/main/example-token-distribution) |
+| Payments & Sign with Privy integration | [privy](https://www.zkcompression.com/compressed-tokens/for-privy) | [example](https://github.com/Lightprotocol/examples-zk-compression/tree/main/privy) |
+| Overview to Guides | https://www.zkcompression.com/compressed-tokens/overview | 
+add cookbook
+
+---
+
+## Compressed PDAs
+
+Compressed accounts do not require rent-exemption, which makes them suitable for:
+- user owned accounts
+- not config accounts which are often read
+- not pool accounts, since compressed accounts cannot be used concurrently
+
+Load skill: [Full reference](references/compressed-pda.md)
+
+### Difference to Light-Accounts (Light-PDA)
+Light-PDA's are Solana accounts with sponsored rent-exemption.
+There is no proof required for interactions with Light-PDA's which makes
+them suitable for Defi Usecases. Compressed PDA's don't require rent-exemption,
+but a proof for interactions.
+
+### Client-Program Interaction Flow
+```text
+ ├─ Client
+ │  ├─ Get ValidityProof from RPC.
+ │  ├─ pack accounts with PackedAccounts into PackedAddressTreeInfo and PackedStateTreeInfo.
+ │  ├─ pack CompressedAccountMeta.
+ │  ├─ Build Instruction from PackedAccounts and CompressedAccountMetas.
+ │  └─ Send transaction.
+ │
+ └─ Custom Program
+    ├─ CpiAccounts parse accounts consistent with PackedAccounts.
+    ├─ LightAccount instantiates from CompressedAccountMeta.
+    │
+    └─ Light System Program CPI
+       ├─ Verify ValidityProof.
+       ├─ Update State Merkle tree.
+       ├─ Update Address Merkle tree.
+       └─ Complete atomic state transition.
+```
+
+---
+
+## ZK Nullifiers
+
+[Full reference](references/zk-nullifiers.md)
+
+Use for privacy preserving applications on Solana to prevent double spending. Uses compressed PDAs as nullifiers. A nullifier is a deterministically derived hash to ensure an action can only be performed once. The nullifier cannot be linked to the action or user.
+
+| Storage | Cost per nullifier |
+|---------|-------------------|
+| PDA | 890,880 lamports |
+| Compressed PDA | 15,000 lamports |
+
+- Docs: [zk/overview](https://www.zkcompression.com/zk/overview)
+- Example: [program-examples/zk/nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier)
+- load skill: 
+---
+
+## SDK Reference
+
+[Full reference](references/sdk-reference.md)
+
+### TypeScript
+
+| Package | npm |
+|---------|-----|
+| `@lightprotocol/stateless.js` | [npm](https://www.npmjs.com/package/@lightprotocol/stateless.js) |
+| `@lightprotocol/compressed-token` | [npm](https://www.npmjs.com/package/@lightprotocol/compressed-token) |
+
+### Rust
+
+| Crate | docs.rs |
+|-------|---------|
+| `light-sdk` | [docs.rs/light-sdk](https://docs.rs/light-sdk) |
+| `light-sdk-pinocchio` | [docs.rs/light-sdk-pinocchio](https://docs.rs/light-sdk-pinocchio) |
+| `light-token` | [docs.rs/light-token](https://docs.rs/light-token) |
+| `light-token-client` | [docs.rs/light-token-client](https://docs.rs/light-token-client) |
+| `light-compressed-token-sdk` | [docs.rs/light-compressed-token-sdk](https://docs.rs/light-compressed-token-sdk) |
+| `light-client` | [docs.rs/light-client](https://docs.rs/light-client) |
+| `light-program-test` | [docs.rs/light-program-test](https://docs.rs/light-program-test) |
+
+### CLI
+
+```bash
+npm i -g @lightprotocol/zk-compression-cli
+```
\ No newline at end of file

From 28f9e0953152562cd7ffa8ec712318e200345bb6 Mon Sep 17 00:00:00 2001
From: tilo-14 
Date: Fri, 30 Jan 2026 19:56:50 +0000
Subject: [PATCH 10/21] status before anchor

---
 light-token/examples/client.mdx               |  38 +-
 light-token/examples/program.mdx              |  30 +-
 references/airdrop-client.md                  | 407 ------------------
 references/deepwiki.md                        | 123 ------
 references/error-codes.md                     | 257 -----------
 references/light-token.md                     |  82 ++--
 scripts/copy-rust-snippets.sh                 |  34 +-
 skill.md                                      | 175 --------
 .../create-ata/rust-client/instruction.mdx    |   6 +-
 .../mint-to/rust-client/instruction.mdx       |   2 +-
 .../rust-client/instruction.mdx               |   4 +-
 .../rust-client/instruction.mdx               |   4 +-
 .../light-token-client-examples-table.mdx     |  35 ++
 .../light-token-program-examples-table.mdx    |  35 ++
 14 files changed, 158 insertions(+), 1074 deletions(-)
 delete mode 100644 references/airdrop-client.md
 delete mode 100644 references/deepwiki.md
 delete mode 100644 references/error-codes.md
 delete mode 100644 skill.md
 create mode 100644 snippets/overview-tables/light-token-client-examples-table.mdx
 create mode 100644 snippets/overview-tables/light-token-program-examples-table.mdx

diff --git a/light-token/examples/client.mdx b/light-token/examples/client.mdx
index a52d7046..a0c89492 100644
--- a/light-token/examples/client.mdx
+++ b/light-token/examples/client.mdx
@@ -4,40 +4,8 @@ sidebarTitle: "Client"
 description: "TypeScript and Rust client examples for light-token SDK."
 ---
 
-Find all examples on Github: [examples-light-token](https://github.com/Lightprotocol/examples-light-token)
-
-## TypeScript
+import ClientExamplesTable from "/snippets/overview-tables/light-token-client-examples-table.mdx";
 
-| | | |
-|---------|--------|-------------|
-| **approve** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/delegate-approve.ts) | — |
-| **create-ata** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/create-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/create-ata.ts) |
-| **create-mint** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/create-mint.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/create-mint.ts) |
-| **load-ata** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/load-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/load-ata.ts) |
-| **mint-to** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/mint-to.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/mint-to.ts) |
-| **revoke** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/delegate-revoke.ts) | — |
-| **transfer-interface** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/transfer-interface.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/transfer-interface.ts) |
-| **unwrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/unwrap.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/unwrap.ts) |
-| **wrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/actions/wrap.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/src/instructions/wrap.ts) |
-
-## Rust
+Find all examples on Github: [examples-light-token](https://github.com/Lightprotocol/examples-light-token)
 
-| | | |
-|---------|--------|-------------|
-| **approve** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/approve.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/approve.rs) |
-| **burn** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/burn.rs) |
-| **burn-checked** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/burn_checked.rs) |
-| **close** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/close.rs) |
-| **create-ata** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/create_ata.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/create_ata.rs) |
-| **create-mint** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/create_mint.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/create_mint.rs) |
-| **create-token-account** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/create_token_account.rs) |
-| **freeze** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/freeze.rs) |
-| **mint-to** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/mint_to.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/mint_to.rs) |
-| **mint-to-checked** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/mint_to_checked.rs) |
-| **revoke** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/revoke.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/revoke.rs) |
-| **spl-to-light** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/spl_to_light_transfer.rs) |
-| **thaw** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/thaw.rs) |
-| **transfer-checked** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/transfer_checked.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/transfer_checked.rs) |
-| **transfer-interface** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/transfer_interface.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/instructions/transfer_interface.rs) |
-| **unwrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/unwrap.rs) | — |
-| **wrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/src/actions/wrap.rs) | — |
\ No newline at end of file
+
diff --git a/light-token/examples/program.mdx b/light-token/examples/program.mdx
index 722d3aa9..d447d23c 100644
--- a/light-token/examples/program.mdx
+++ b/light-token/examples/program.mdx
@@ -4,32 +4,8 @@ sidebarTitle: "Program"
 description: "Anchor program examples for light-token CPI."
 ---
 
-Find all examples on Github: [examples-light-token](https://github.com/Lightprotocol/examples-light-token)
-
-## CPI instructions
+import ProgramExamplesTable from "/snippets/overview-tables/light-token-program-examples-table.mdx";
 
-| | |
-|---------|--------|
-| [**approve**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/approve) | Approve delegate |
-| [**burn**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/burn) | Burn tokens |
-| [**close**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/close) | Close token account |
-| [**create-ata**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-ata) | Create associated light-token account |
-| [**create-mint**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-mint) | Create light-token mint |
-| [**create-token-account**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-token-account) | Create light-token account |
-| [**freeze**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/freeze) | Freeze token account |
-| [**mint-to**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to) | Mint tokens |
-| [**mint-to-checked**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to-checked) | Mint tokens with decimal validation |
-| [**revoke**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/revoke) | Revoke delegate |
-| [**thaw**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/thaw) | Thaw token account |
-| [**transfer-checked**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-checked) | Transfer with mint validation |
-| [**transfer-interface**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-interface) | Transfer between light-token, T22, and SPL accounts |
-
-## Anchor macros
+Find all examples on Github: [examples-light-token](https://github.com/Lightprotocol/examples-light-token)
 
-| | |
-|---------|--------|
-| [**counter**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/counter) | Create PDA with sponsored rent-exemption |
-| [**create-ata**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/create-ata) | Create associated light-token account |
-| [**create-mint**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/create-mint) | Create light-token mint |
-| [**create-token-account**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/create-token-account) | Create light-token account |
-| [**token-transfer**](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-macros/token-transfer) | Create destination ATA and transfer tokens |
+
diff --git a/references/airdrop-client.md b/references/airdrop-client.md
deleted file mode 100644
index 3616ed67..00000000
--- a/references/airdrop-client.md
+++ /dev/null
@@ -1,407 +0,0 @@
-# Airdrop
-
-Distribute compressed tokens to multiple recipients using TypeScript client.
-
-## Quick Decision
-
-| Scale | Approach |
-|-------|----------|
-| <10,000 recipients | Single transaction - see [Simple Airdrop](#simple-airdrop-10000-recipients) |
-| 10,000+ recipients | Batched with retry - see [Batched Airdrop](#batched-airdrop-10000-recipients) |
-| No-code | [Airship by Helius](https://airship.helius.dev/) (up to 200k) |
-
-## Core Pattern
-
-```typescript
-import { CompressedTokenProgram, getTokenPoolInfos, selectTokenPoolInfo } from "@lightprotocol/compressed-token";
-import { bn, createRpc, selectStateTreeInfo, buildAndSignTx, sendAndConfirmTx } from "@lightprotocol/stateless.js";
-import { ComputeBudgetProgram } from "@solana/web3.js";
-
-const rpc = createRpc(RPC_ENDPOINT);
-
-// 1. Get infrastructure
-const treeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos());
-const tokenPoolInfo = selectTokenPoolInfo(await getTokenPoolInfos(rpc, mint));
-
-// 2. Build compress instruction (SPL -> compressed to multiple recipients)
-const ix = await CompressedTokenProgram.compress({
-  payer: payer.publicKey,
-  owner: payer.publicKey,
-  source: sourceAta.address,           // SPL ATA holding tokens
-  toAddress: recipients,                // PublicKey[]
-  amount: recipients.map(() => bn(amount)),
-  mint,
-  tokenPoolInfo,
-  outputStateTreeInfo: treeInfo,
-});
-
-// 3. Send with compute budget (120k CU per recipient)
-const instructions = [
-  ComputeBudgetProgram.setComputeUnitLimit({ units: 120_000 * recipients.length }),
-  ix,
-];
-const { blockhash } = await rpc.getLatestBlockhash();
-const tx = buildAndSignTx(instructions, payer, blockhash, []);
-await sendAndConfirmTx(rpc, tx);
-```
-
-## Setup: Create Mint
-
-```typescript
-import { createMint } from "@lightprotocol/compressed-token";
-import { getOrCreateAssociatedTokenAccount, mintTo } from "@solana/spl-token";
-
-const { mint } = await createMint(rpc, payer, payer.publicKey, 9);
-const ata = await getOrCreateAssociatedTokenAccount(rpc, payer, mint, payer.publicKey);
-await mintTo(rpc, payer, mint, ata.address, payer.publicKey, 100_000_000_000);
-```
-
-## Compute Units
-
-| Recipients/instruction | CU |
-|----------------------|-----|
-| 1 | 120,000 |
-| 5 | 170,000 |
-| Batched tx | 500,000 |
-
-## Lookup Tables
-
-Reduce transaction size:
-
-| Network | Address |
-|---------|---------|
-| Mainnet | `9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ` |
-| Devnet | `qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V` |
-
-## Simple Airdrop (<10,000 recipients)
-
-Single transaction approach for small distributions.
-
-```typescript
-import {
-  CompressedTokenProgram,
-  getTokenPoolInfos,
-  selectTokenPoolInfo,
-} from "@lightprotocol/compressed-token";
-import {
-  bn,
-  buildAndSignTx,
-  calculateComputeUnitPrice,
-  createRpc,
-  dedupeSigner,
-  selectStateTreeInfo,
-  sendAndConfirmTx,
-} from "@lightprotocol/stateless.js";
-import { ComputeBudgetProgram, Keypair, PublicKey } from "@solana/web3.js";
-import { getOrCreateAssociatedTokenAccount } from "@solana/spl-token";
-
-const RPC_ENDPOINT = "https://devnet.helius-rpc.com?api-key=YOUR_KEY";
-const rpc = createRpc(RPC_ENDPOINT);
-const mint = new PublicKey("YOUR_MINT_ADDRESS");
-
-// Define recipients and amounts
-const recipients = [
-  new PublicKey("..."),
-  new PublicKey("..."),
-  new PublicKey("..."),
-];
-
-const amounts = [
-  bn(20_000_000_000), // 20 tokens (9 decimals)
-  bn(30_000_000_000), // 30 tokens
-  bn(40_000_000_000), // 40 tokens
-];
-
-// Get infrastructure
-const treeInfo = selectStateTreeInfo(await rpc.getStateTreeInfos());
-const tokenPoolInfo = selectTokenPoolInfo(await getTokenPoolInfos(rpc, mint));
-const sourceAta = await getOrCreateAssociatedTokenAccount(rpc, payer, mint, payer.publicKey);
-
-// Build transaction
-const units = 120_000 * recipients.length;
-const instructions = [
-  ComputeBudgetProgram.setComputeUnitLimit({ units }),
-  ComputeBudgetProgram.setComputeUnitPrice({
-    microLamports: calculateComputeUnitPrice(20_000, units),
-  }),
-  await CompressedTokenProgram.compress({
-    payer: payer.publicKey,
-    owner: payer.publicKey,
-    source: sourceAta.address,
-    toAddress: recipients,
-    amount: amounts,
-    mint,
-    tokenPoolInfo,
-    outputStateTreeInfo: treeInfo,
-  }),
-];
-
-// Use lookup table to reduce tx size
-const lut = new PublicKey("9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ"); // mainnet
-// const lut = new PublicKey("qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V"); // devnet
-const lookupTable = (await rpc.getAddressLookupTable(lut)).value!;
-
-const { blockhash } = await rpc.getLatestBlockhash();
-const tx = buildAndSignTx(
-  instructions,
-  payer,
-  blockhash,
-  dedupeSigner(payer, []),
-  [lookupTable]
-);
-const txId = await sendAndConfirmTx(rpc, tx);
-console.log(`Airdrop complete: ${txId}`);
-```
-
-### Verify Distribution
-
-```typescript
-for (const recipient of recipients) {
-  const accounts = await rpc.getCompressedTokenAccountsByOwner(recipient, { mint });
-  const balance = accounts.items.reduce((sum, acc) => sum + Number(acc.parsed.amount), 0);
-  console.log(`${recipient}: ${balance / 1e9} tokens`);
-}
-```
-
-## Batched Airdrop (10,000+ recipients)
-
-For large-scale distributions with retry logic and blockhash management.
-
-### Create Instruction Batches
-
-```typescript
-import {
-  CompressedTokenProgram,
-  TokenPoolInfo,
-  selectTokenPoolInfo,
-} from "@lightprotocol/compressed-token";
-import {
-  bn,
-  selectStateTreeInfo,
-  StateTreeInfo,
-} from "@lightprotocol/stateless.js";
-import {
-  ComputeBudgetProgram,
-  TransactionInstruction,
-  PublicKey,
-} from "@solana/web3.js";
-
-interface CreateAirdropParams {
-  amount: number | bigint;
-  recipients: PublicKey[];
-  payer: PublicKey;
-  sourceTokenAccount: PublicKey;
-  mint: PublicKey;
-  stateTreeInfos: StateTreeInfo[];
-  tokenPoolInfos: TokenPoolInfo[];
-  maxRecipientsPerInstruction?: number;   // default: 5
-  maxInstructionsPerTransaction?: number; // default: 3
-  computeUnitLimit?: number;              // default: 500_000
-  computeUnitPrice?: number;
-}
-
-export async function createAirdropInstructions({
-  amount,
-  recipients,
-  payer,
-  sourceTokenAccount,
-  mint,
-  stateTreeInfos,
-  tokenPoolInfos,
-  maxRecipientsPerInstruction = 5,
-  maxInstructionsPerTransaction = 3,
-  computeUnitLimit = 500_000,
-  computeUnitPrice,
-}: CreateAirdropParams): Promise {
-  const batches: TransactionInstruction[][] = [];
-  const amountBn = bn(amount.toString());
-
-  for (
-    let i = 0;
-    i < recipients.length;
-    i += maxRecipientsPerInstruction * maxInstructionsPerTransaction
-  ) {
-    const instructions: TransactionInstruction[] = [];
-
-    instructions.push(
-      ComputeBudgetProgram.setComputeUnitLimit({ units: computeUnitLimit })
-    );
-    if (computeUnitPrice) {
-      instructions.push(
-        ComputeBudgetProgram.setComputeUnitPrice({ microLamports: computeUnitPrice })
-      );
-    }
-
-    const treeInfo = selectStateTreeInfo(stateTreeInfos);
-    const tokenPoolInfo = selectTokenPoolInfo(tokenPoolInfos);
-
-    for (let j = 0; j < maxInstructionsPerTransaction; j++) {
-      const startIdx = i + j * maxRecipientsPerInstruction;
-      const recipientBatch = recipients.slice(
-        startIdx,
-        startIdx + maxRecipientsPerInstruction
-      );
-
-      if (recipientBatch.length === 0) break;
-
-      instructions.push(
-        await CompressedTokenProgram.compress({
-          payer,
-          owner: payer,
-          source: sourceTokenAccount,
-          toAddress: recipientBatch,
-          amount: recipientBatch.map(() => amountBn),
-          mint,
-          tokenPoolInfo,
-          outputStateTreeInfo: treeInfo,
-        })
-      );
-    }
-
-    if (instructions.length > (computeUnitPrice ? 2 : 1)) {
-      batches.push(instructions);
-    }
-  }
-
-  return batches;
-}
-```
-
-### Background Blockhash Updater
-
-```typescript
-import { Rpc } from "@lightprotocol/stateless.js";
-
-export let currentBlockhash: string;
-
-export async function updateBlockhash(
-  connection: Rpc,
-  signal: AbortSignal
-): Promise {
-  const { blockhash } = await connection.getLatestBlockhash();
-  currentBlockhash = blockhash;
-
-  (function updateInBackground() {
-    if (signal.aborted) return;
-    const timeoutId = setTimeout(async () => {
-      if (signal.aborted) return;
-      try {
-        const { blockhash } = await connection.getLatestBlockhash();
-        currentBlockhash = blockhash;
-      } catch (error) {
-        console.error("Failed to update blockhash:", error);
-      }
-      updateInBackground();
-    }, 30_000);
-
-    signal.addEventListener("abort", () => clearTimeout(timeoutId));
-  })();
-}
-```
-
-### Sign and Send with Retry
-
-```typescript
-import { Rpc, sendAndConfirmTx } from "@lightprotocol/stateless.js";
-import {
-  Keypair,
-  PublicKey,
-  TransactionMessage,
-  VersionedTransaction,
-} from "@solana/web3.js";
-import { currentBlockhash, updateBlockhash } from "./update-blockhash";
-
-export enum BatchResultType {
-  Success = "success",
-  Error = "error",
-}
-
-export type BatchResult =
-  | { type: BatchResultType.Success; index: number; signature: string }
-  | { type: BatchResultType.Error; index: number; error: string };
-
-export async function* signAndSendAirdropBatches(
-  batches: TransactionInstruction[][],
-  payer: Keypair,
-  connection: Rpc,
-  maxRetries = 3
-): AsyncGenerator {
-  const abortController = new AbortController();
-  await updateBlockhash(connection, abortController.signal);
-
-  // Lookup table for your network
-  const lookupTableAddress = new PublicKey(
-    "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ" // mainnet
-    // "qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V" // devnet
-  );
-  const lookupTableAccount = (
-    await connection.getAddressLookupTable(lookupTableAddress)
-  ).value!;
-
-  const statusMap = new Array(batches.length).fill(0); // 0 = pending
-
-  while (statusMap.includes(0)) {
-    const sends = statusMap.map(async (status, index) => {
-      if (status !== 0) return;
-
-      let retries = 0;
-      while (retries < maxRetries && statusMap[index] === 0) {
-        if (!currentBlockhash) {
-          await new Promise((resolve) => setTimeout(resolve, 1000));
-          continue;
-        }
-
-        try {
-          const tx = new VersionedTransaction(
-            new TransactionMessage({
-              payerKey: payer.publicKey,
-              recentBlockhash: currentBlockhash,
-              instructions: batches[index],
-            }).compileToV0Message([lookupTableAccount])
-          );
-          tx.sign([payer]);
-
-          const confirmedSig = await sendAndConfirmTx(connection, tx, {
-            skipPreflight: true,
-            commitment: "confirmed",
-          });
-
-          if (confirmedSig) {
-            statusMap[index] = 1;
-            return { type: BatchResultType.Success, index, signature: confirmedSig };
-          }
-        } catch (e) {
-          retries++;
-          if (retries >= maxRetries) {
-            statusMap[index] = `err: ${(e as Error).message}`;
-            return { type: BatchResultType.Error, index, error: (e as Error).message };
-          }
-        }
-      }
-    });
-
-    const results = await Promise.all(sends);
-    for (const result of results) {
-      if (result) yield result as BatchResult;
-    }
-  }
-
-  abortController.abort();
-}
-```
-
-## Advanced: Claim-Based
-
-For vesting, clawback, or user-initiated claims:
-
-| Implementation | Features |
-|---------------|----------|
-| [Merkle Distributor](https://github.com/Lightprotocol/distributor) | Linear vesting, partial claims, clawback, REST API |
-| [Simple Claim](https://github.com/Lightprotocol/program-examples/tree/main/airdrop-implementations/simple-claim) | Cliff vesting at slot X |
-
-## Resources
-
-- **Docs**: [Airdrop Guide](https://www.zkcompression.com/compressed-tokens/advanced-guides/airdrop)
-- **Docs**: [Claim Implementations](https://www.zkcompression.com/compressed-tokens/advanced-guides/airdrop#claim-reference-implementations)
-- **Code**: [example-token-distribution](https://github.com/Lightprotocol/examples-zk-compression/tree/main/example-token-distribution)
-- **Tool**: [Airship by Helius](https://airship.helius.dev/)
diff --git a/references/deepwiki.md b/references/deepwiki.md
deleted file mode 100644
index 3e9ff075..00000000
--- a/references/deepwiki.md
+++ /dev/null
@@ -1,123 +0,0 @@
----
-name: research-deepwiki
-description: Query Light Protocol and related repositories via DeepWiki MCP. Use when answering questions about compressed accounts, Light SDK, Solana development, Claude Code features, or agent skills. Triggers on technical questions requiring repository context.
----
-
-# DeepWiki Research
-
-Query repositories via DeepWiki MCP to answer technical questions with precise, source-backed answers.
-
-## Execution Steps
-
-### 1. Read Required Context
-
-Before answering any question:
-
-```bash
-cat /home/tilo/.claude/context/terminology-reference.md
-```
-
-### 2. Identify Question Scope
-
-Determine the domain:
-- Programs, client SDKs, architecture, implementation details
-- Specific components (LightAccount, ValidityProof, CPI, etc.)
-
-### 3. Fetch Repository Context
-
-Select the appropriate repository based on question scope:
-
-**Light Protocol (compressed accounts, state trees, ZK compression, Light SDK)**
-```
-mcp__deepwiki__read_wiki_structure("Lightprotocol/light-protocol")
-mcp__deepwiki__read_wiki_contents("Lightprotocol/light-protocol")
-mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "your question")
-```
-
-**Solana Development (programs, accounts, general Solana)**
-```
-mcp__deepwiki__read_wiki_structure("blueshift-gg/blueshift-dashboard")
-mcp__deepwiki__read_wiki_contents("blueshift-gg/blueshift-dashboard")
-mcp__deepwiki__ask_question("blueshift-gg/blueshift-dashboard", "your question")
-```
-
-**Claude Code (CLI features, commands, configuration)**
-```
-mcp__deepwiki__read_wiki_structure("anthropics/claude-code")
-mcp__deepwiki__read_wiki_contents("anthropics/claude-code")
-mcp__deepwiki__ask_question("anthropics/claude-code", "your question")
-```
-
-**Agent Skills (skills, subagents, patterns)**
-```
-mcp__deepwiki__read_wiki_structure("anthropics/skills")
-mcp__deepwiki__read_wiki_contents("anthropics/skills")
-mcp__deepwiki__ask_question("anthropics/skills", "your question")
-```
-
-### 4. Apply Precision Rules
-
-**AVOID:**
-- Vague verbs: "handles", "manages", "processes", "enables", "provides"
-- Abstract nouns: "operations", "management", "coordination"
-- -ing endings when a precise verb exists
-- Marketing language: "comprehensive", "rich", "seamless"
-
-**USE:**
-- Exact function/method names
-- Concrete data structures
-- Specific operations: "sets discriminator", "creates hash", "nullifies account"
-- File:line references
-
-### 5. Format Response
-
-Structure answers with:
-- Technical precision
-- Code examples with inline comments
-- Source references (file paths, line numbers)
-
-## Example Output
-
-**Question:** What do you include in instruction data for compressed account creation?
-
-**Answer:**
-
----
-
-## Compressed Account Creation Instruction Data
-
-Instruction data contains three components for proof verification and tree coordination.
-
-**Components:**
-
-```rust
-pub struct InstructionData {
-    proof: ValidityProof,
-    address_tree_info: PackedAddressTreeInfo,
-    output_state_tree_index: u8,
-    // + custom account data
-}
-```
-
-**1. Non-inclusion proof**
-- `ValidityProof` proves the derived address does not exist in the specified address tree
-- Client generates via `getValidityProof()` RPC call
-
-**2. Tree location parameters**
-- `PackedAddressTreeInfo`: Specifies which address tree registers the address (u8 index, not pubkey)
-- `output_state_tree_index`: Specifies which state tree stores the compressed account hash
-
-**3. Custom account data**
-- Program-specific fields (e.g., `message: String`, user data)
-
-**Execution flow:**
-
-1. Address tree proves uniqueness, stores address
-2. State tree stores account hash
-3. ValidityProof cryptographically guarantees address is unused
-
-**Why separate trees:**
-
-Compressed accounts require client-generated cryptographic proof that address doesn't exist (unlike regular Solana where runtime checks PDA existence). Address trees enforce uniqueness; state trees store account hashes.
-
-**Packed structs** use `u8` indices to reference accounts in `remaining_accounts`, reducing transaction size.
diff --git a/references/error-codes.md b/references/error-codes.md
deleted file mode 100644
index 7698739f..00000000
--- a/references/error-codes.md
+++ /dev/null
@@ -1,257 +0,0 @@
-# Error Codes Reference
-
-Complete error code reference for ZK Compression programs. Codes 6000-16034 (hex 0x1770-0x3EB2).
-
-Search for your error code or hex value with `Cmd+F` / `Ctrl+F`.
-
-## 6000 - 6053 / SystemProgramError Variants
-
-> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/programs/system/src/errors.rs#L133)
-
-| Code | Hex    | Error                                           | Message                                                                                                                           |
-| :--- | :----- | :---------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------- |
-| 6000 | 0x1770 | `SumCheckFailed`                                | "Sum check failed"                                                                                                                |
-| 6001 | 0x1771 | `SignerCheckFailed`                             | "Signer check failed"                                                                                                             |
-| 6002 | 0x1772 | `CpiSignerCheckFailed`                          | "Cpi signer check failed"                                                                                                         |
-| 6003 | 0x1773 | `ComputeInputSumFailed`                         | "Computing input sum failed."                                                                                                     |
-| 6004 | 0x1774 | `ComputeOutputSumFailed`                        | "Computing output sum failed."                                                                                                    |
-| 6005 | 0x1775 | `ComputeRpcSumFailed`                           | "Computing rpc sum failed."                                                                                                       |
-| 6006 | 0x1776 | `InvalidAddress`                                | "InvalidAddress"                                                                                                                  |
-| 6007 | 0x1777 | `DeriveAddressError`                            | "DeriveAddressError"                                                                                                              |
-| 6008 | 0x1778 | `CompressedSolPdaUndefinedForCompressSol`       | "CompressedSolPdaUndefinedForCompressSol"                                                                                         |
-| 6009 | 0x1779 | `DecompressLamportsUndefinedForCompressSol`     | "DecompressLamportsUndefinedForCompressSol"                                                                                       |
-| 6010 | 0x177A | `CompressedSolPdaUndefinedForDecompressSol`     | "CompressedSolPdaUndefinedForDecompressSol"                                                                                       |
-| 6011 | 0x177B | `DeCompressLamportsUndefinedForDecompressSol`   | "DeCompressLamportsUndefinedForDecompressSol"                                                                                     |
-| 6012 | 0x177C | `DecompressRecipientUndefinedForDecompressSol`  | "DecompressRecipientUndefinedForDecompressSol"                                                                                    |
-| 6013 | 0x177D | `WriteAccessCheckFailed`                        | "WriteAccessCheckFailed"                                                                                                          |
-| 6014 | 0x177E | `InvokingProgramNotProvided`                    | "InvokingProgramNotProvided"                                                                                                      |
-| 6015 | 0x177F | `InvalidCapacity`                               | "InvalidCapacity"                                                                                                                 |
-| 6016 | 0x1780 | `InvalidMerkleTreeOwner`                        | "InvalidMerkleTreeOwner"                                                                                                          |
-| 6017 | 0x1781 | `ProofIsNone`                                   | "ProofIsNone"                                                                                                                     |
-| 6018 | 0x1782 | `ProofIsSome`                                   | "Proof is some but no input compressed accounts or new addresses provided."                                                       |
-| 6019 | 0x1783 | `EmptyInputs`                                   | "EmptyInputs"                                                                                                                     |
-| 6020 | 0x1784 | `CpiContextAccountUndefined`                    | "CpiContextAccountUndefined"                                                                                                      |
-| 6021 | 0x1785 | `CpiContextEmpty`                               | "CpiContextEmpty"                                                                                                                 |
-| 6022 | 0x1786 | `CpiContextMissing`                             | "CpiContextMissing"                                                                                                               |
-| 6023 | 0x1787 | `DecompressionRecipientDefined`                 | "DecompressionRecipientDefined"                                                                                                   |
-| 6024 | 0x1788 | `SolPoolPdaDefined`                             | "SolPoolPdaDefined"                                                                                                               |
-| 6025 | 0x1789 | `AppendStateFailed`                             | "AppendStateFailed"                                                                                                               |
-| 6026 | 0x178A | `InstructionNotCallable`                        | "The instruction is not callable"                                                                                                 |
-| 6027 | 0x178B | `CpiContextFeePayerMismatch`                    | "CpiContextFeePayerMismatch"                                                                                                      |
-| 6028 | 0x178C | `CpiContextAssociatedMerkleTreeMismatch`        | "CpiContextAssociatedMerkleTreeMismatch"                                                                                          |
-| 6029 | 0x178D | `NoInputs`                                      | "NoInputs"                                                                                                                        |
-| 6030 | 0x178E | `InputMerkleTreeIndicesNotInOrder`              | "Input merkle tree indices are not in ascending order."                                                                           |
-| 6031 | 0x178F | `OutputMerkleTreeIndicesNotInOrder`             | "Output merkle tree indices are not in ascending order."                                                                          |
-| 6032 | 0x1790 | `OutputMerkleTreeNotUnique`                     | "OutputMerkleTreeNotUnique"                                                                                                       |
-| 6033 | 0x1791 | `DataFieldUndefined`                            | "DataFieldUndefined"                                                                                                              |
-| 6034 | 0x1792 | `ReadOnlyAddressAlreadyExists`                  | "ReadOnlyAddressAlreadyExists"                                                                                                    |
-| 6035 | 0x1793 | `ReadOnlyAccountDoesNotExist`                   | "ReadOnlyAccountDoesNotExist"                                                                                                     |
-| 6036 | 0x1794 | `HashChainInputsLenghtInconsistent`             | "HashChainInputsLenghtInconsistent"                                                                                               |
-| 6037 | 0x1795 | `InvalidAddressTreeHeight`                      | "InvalidAddressTreeHeight"                                                                                                        |
-| 6038 | 0x1796 | `InvalidStateTreeHeight`                        | "InvalidStateTreeHeight"                                                                                                          |
-| 6039 | 0x1797 | `InvalidArgument`                               | "InvalidArgument"                                                                                                                 |
-| 6040 | 0x1798 | `InvalidAccount`                                | "InvalidAccount"                                                                                                                  |
-| 6041 | 0x1799 | `AddressMerkleTreeAccountDiscriminatorMismatch` | "AddressMerkleTreeAccountDiscriminatorMismatch"                                                                                   |
-| 6042 | 0x179A | `StateMerkleTreeAccountDiscriminatorMismatch`   | "StateMerkleTreeAccountDiscriminatorMismatch"                                                                                     |
-| 6043 | 0x179B | `ProofVerificationFailed`                       | "Proof verification failed." [How to debug](https://zkcompression.com/resources/error-cheatsheet/debug-0x179b-6043-proofverificationfailed) |
-| 6044 | 0x179C | `InvalidAccountMode`                            | "Invalid account mode."                                                                                                           |
-| 6045 | 0x179D | `InvalidInstructionDataDiscriminator`           | "InvalidInstructionDataDiscriminator"                                                                                             |
-| 6046 | 0x179E | `NewAddressAssignedIndexOutOfBounds`            | "NewAddressAssignedIndexOutOfBounds"                                                                                              |
-| 6047 | 0x179F | `AddressIsNone`                                 | "AddressIsNone"                                                                                                                   |
-| 6048 | 0x17A0 | `AddressDoesNotMatch`                           | "AddressDoesNotMatch"                                                                                                             |
-| 6049 | 0x17A1 | `CpiContextAlreadySet`                          | "CpiContextAlreadySet"                                                                                                            |
-| 6050 | 0x17A2 | `InvalidTreeHeight`                             | "InvalidTreeHeight"                                                                                                               |
-| 6051 | 0x17A3 | `TooManyOutputAccounts`                         | "TooManyOutputAccounts"                                                                                                           |
-| 6052 | 0x17A4 | `BorrowingDataFailed`                           | "Borrowing data failed"                                                                                                           |
-| 6053 | 0x17A5 | `DuplicateAccountInInputsAndReadOnly`           | "DuplicateAccountInInputsAndReadOnly"                                                                                             |
-
-## 7001 - 7009 / HasherError Variants
-
-> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/hasher/src/errors.rs)
-
-| Code | Hex    | Error                                   | Message                                                                                  |
-| :--- | :----- | :-------------------------------------- | :--------------------------------------------------------------------------------------- |
-| 7001 | 0x1B59 | `IntegerOverflow`                       | "Integer overflow, value too large"                                                      |
-| 7003 | 0x1B5B | `PoseidonSyscall(PoseidonSyscallError)` | "Poseidon syscall error: {0}"                                                            |
-| 7005 | 0x1B5D | `InvalidInputLength(usize, usize)`      | "Allowed input length {0} provided {1}"                                                  |
-| 7006 | 0x1B5E | `InvalidNumFields`                      | "Invalid number of fields"                                                               |
-| 7007 | 0x1B5F | `EmptyInput`                            | "Empty input"                                                                            |
-| 7008 | 0x1B60 | `BorshError`                            | "Borsh serialization failed."                                                            |
-| 7009 | 0x1B61 | `OptionHashToFieldSizeZero`             | "Option hash to field size returned [0u8;32], a collision with None for an Option type." |
-
-## 10001 - 10014 / ConcurrentMerkleTreeError Variants
-
-> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/concurrent-merkle-tree/src/errors.rs)
-
-| Code  | Hex    | Error                                     | Message                                                                                                           |
-| :---- | :----- | :---------------------------------------- | :---------------------------------------------------------------------------------------------------------------- |
-| 10001 | 0x2711 | `IntegerOverflow`                         | "Integer overflow"                                                                                                |
-| 10002 | 0x2712 | `HeightZero`                              | "Invalid height, it has to be greater than 0"                                                                     |
-| 10003 | 0x2713 | `InvalidHeight(usize)`                    | "Invalid height, expected {0}"                                                                                    |
-| 10004 | 0x2714 | `ChangelogZero`                           | "Invalid changelog size, it has to be greater than 0. Changelog is used for storing Merkle paths during appends." |
-| 10005 | 0x2715 | `RootsZero`                               | "Invalid number of roots, it has to be greater than 0"                                                            |
-| 10006 | 0x2716 | `CanopyGeThanHeight`                      | "Canopy depth has to be lower than height"                                                                        |
-| 10007 | 0x2717 | `TreeIsFull`                              | "Merkle tree is full, cannot append more leaves."                                                                 |
-| 10008 | 0x2718 | `BatchGreaterThanChangelog(usize, usize)` | "Number of leaves ({0}) exceeds the changelog capacity ({1})."                                                    |
-| 10009 | 0x2719 | `InvalidProofLength(usize, usize)`        | "Invalid proof length, expected {0}, got {1}."                                                                    |
-| 10010 | 0x271A | `InvalidProof([u8; 32], [u8; 32])`        | "Invalid Merkle proof, expected root: `{0:?}`, the provided proof produces root: `{1:?}`"                         |
-| 10011 | 0x271B | `CannotUpdateLeaf`                        | "Attempting to update the leaf which was updated by an another newest change."                                    |
-| 10012 | 0x271C | `CannotUpdateEmpty`                       | "Cannot update the empty leaf"                                                                                    |
-| 10013 | 0x271D | `EmptyLeaves`                             | "The batch of leaves is empty"                                                                                    |
-| 10014 | 0x271E | `BufferSize(usize, usize)`                | "Invalid buffer size, expected {0}, got {1}"                                                                      |
-
-## 11001 - 11009 / IndexedMerkleTreeError Variants
-
-> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/indexed-merkle-tree/src/errors.rs)
-
-| Code  | Hex    | Error                                   | Message                                                             |
-| :---- | :----- | :-------------------------------------- | :------------------------------------------------------------------ |
-| 11001 | 0x2AF9 | `IntegerOverflow`                       | "Integer overflow"                                                  |
-| 11002 | 0x2AFA | `IndexHigherThanMax`                    | "Invalid index, it exceeds the number of elements."                 |
-| 11003 | 0x2AFB | `LowElementNotFound`                    | "Could not find the low element."                                   |
-| 11004 | 0x2AFC | `LowElementGreaterOrEqualToNewElement`  | "Low element is greater or equal to the provided new element."      |
-| 11005 | 0x2AFD | `NewElementGreaterOrEqualToNextElement` | "The provided new element is greater or equal to the next element." |
-| 11006 | 0x2AFE | `ElementAlreadyExists`                  | "The element already exists, but was expected to be absent."        |
-| 11007 | 0x2AFF | `ElementDoesNotExist`                   | "The element does not exist, but was expected to be present."       |
-| 11008 | 0x2B00 | `ChangelogBufferSize(usize, usize)`     | "Invalid changelog buffer size, expected {0}, got {1}"              |
-| 11009 | 0x2B01 | `ArrayFull`                             | "Indexed array is full, cannot append more elements"                |
-
-## 12006 - 12019 / AccountError Variants
-
-> **Source:** [error.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/account-checks/src/error.rs)
-
-| Code  | Hex    | Error                        | Message                           |
-| :---- | :----- | :--------------------------- | :-------------------------------- |
-| 12006 | 0x2EE6 | `InvalidDiscriminator`       | "Invalid Discriminator."          |
-| 12007 | 0x2EE7 | `AccountOwnedByWrongProgram` | "Account owned by wrong program." |
-| 12008 | 0x2EE8 | `AccountNotMutable`          | "Account not mutable."            |
-| 12009 | 0x2EE9 | `BorrowAccountDataFailed`    | "Borrow account data failed."     |
-| 12010 | 0x2EEA | `InvalidAccountSize`         | "Invalid Account size."           |
-| 12011 | 0x2EEB | `AccountMutable`             | "Account is mutable."             |
-| 12012 | 0x2EEC | `AlreadyInitialized`         | "Account is already initialized." |
-| 12013 | 0x2EED | `InvalidAccountBalance`      | "Invalid account balance."        |
-| 12014 | 0x2EEE | `FailedBorrowRentSysvar`     | "Failed to borrow rent sysvar."   |
-| 12015 | 0x2EEF | `InvalidSigner`              | "Invalid Signer"                  |
-| 12016 | 0x2EF0 | `InvalidSeeds`               | "Invalid Seeds"                   |
-| 12017 | 0x2EF1 | `InvalidProgramId`           | "Invalid Program Id"              |
-| 12018 | 0x2EF2 | `ProgramNotExecutable`       | "Program not executable."         |
-| 12019 | 0x2EF3 | `AccountNotZeroed`           | "Account not zeroed."             |
-
-## 14001 - 14009 / MerkleTreeMetadataError Variants
-
-> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/merkle-tree-metadata/src/errors.rs)
-
-| Code  | Hex    | Error                             | Message                                     |
-| :---- | :----- | :-------------------------------- | :------------------------------------------ |
-| 14001 | 0x36B1 | `MerkleTreeAndQueueNotAssociated` | "Merkle tree and queue are not associated." |
-| 14002 | 0x36B2 | `RolloverNotConfigured`           | "Rollover not configured."                  |
-| 14003 | 0x36B3 | `MerkleTreeAlreadyRolledOver`     | "Merkle tree already rolled over."          |
-| 14004 | 0x36B4 | `InvalidQueueType`                | "Invalid queue type."                       |
-| 14005 | 0x36B5 | `InsufficientRolloverFee`         | "Insufficient rollover fee."                |
-| 14006 | 0x36B6 | `NotReadyForRollover`             | "Merkle tree not ready for rollover."       |
-| 14007 | 0x36B7 | `InvalidTreeType`                 | "Invalid tree type."                        |
-| 14008 | 0x36B8 | `InvalidRolloverThreshold`        | "Invalid Rollover Threshold."               |
-| 14009 | 0x36B9 | `InvalidHeight`                   | "Invalid Height."                           |
-
-## 14017 - 14034 / LightSdkTypesError Variants
-
-> **Source:** [error.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/sdk-libs/sdk-types/src/error.rs#L4)
-
-| Code  | Hex    | Error                                | Message                                                                                                                                           |
-| :---- | :----- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
-| 14017 | 0x36C1 | `FewerAccountsThanSystemAccounts`    | "Fewer accounts than system accounts"                                                                                                             |
-| 14021 | 0x36C5 | `InitAddressIsNone`                  | "Address is none during initialization"                                                                                                           |
-| 14022 | 0x36C6 | `InitWithAddressIsNone`              | "Address is none during initialization with address"                                                                                              |
-| 14023 | 0x36C7 | `InitWithAddressOutputIsNone`        | "Output is none during initialization with address"                                                                                               |
-| 14024 | 0x36C8 | `MetaMutAddressIsNone`               | "Address is none during meta mutation"                                                                                                            |
-| 14025 | 0x36C9 | `MetaMutInputIsNone`                 | "Input is none during meta mutation"                                                                                                              |
-| 14026 | 0x36CA | `MetaMutOutputLamportsIsNone`        | "Output lamports is none during meta mutation"                                                                                                    |
-| 14027 | 0x36CB | `MetaMutOutputIsNone`                | "Output is none during meta mutation"                                                                                                             |
-| 14028 | 0x36CC | `MetaCloseAddressIsNone`             | "Address is none during meta close"                                                                                                               |
-| 14029 | 0x36CD | `MetaCloseInputIsNone`               | "Input is none during meta close"                                                                                                                 |
-| 14031 | 0x36CF | `CpiAccountsIndexOutOfBounds(usize)` | "CPI accounts index out of bounds: {0}"                                                                                                           |
-| 14032 | 0x36D0 | `InvalidCpiContextAccount`           | "Invalid CPI context account"                                                                                                                     |
-| 14033 | 0x36D1 | `InvalidSolPoolPdaAccount`           | "Invalid sol pool pda account"                                                                                                                    |
-| 14034 | 0x36D2 | `InvalidCpiAccountsOffset`           | "CpiAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7." |
-
-## 14301 - 14312 / BatchedMerkleTreeError Variants
-
-> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/batched-merkle-tree/src/errors.rs)
-
-| Code  | Hex    | Error                                 | Message                                                 |
-| :---- | :----- | :------------------------------------ | :------------------------------------------------------ |
-| 14301 | 0x37DD | `BatchNotReady`                       | "Batch is not ready to be inserted"                     |
-| 14302 | 0x37DE | `BatchAlreadyInserted`                | "Batch is already inserted"                             |
-| 14303 | 0x37DF | `BatchInsertFailed`                   | "Batch insert failed"                                   |
-| 14304 | 0x37E0 | `LeafIndexNotInBatch`                 | "Leaf index not in batch."                              |
-| 14305 | 0x37E1 | `InvalidNetworkFee`                   | "Invalid network fee."                                  |
-| 14306 | 0x37E2 | `BatchSizeNotDivisibleByZkpBatchSize` | "Batch size not divisible by ZKP batch size."           |
-| 14307 | 0x37E3 | `InclusionProofByIndexFailed`         | "Inclusion proof by index failed."                      |
-| 14308 | 0x37E4 | `InvalidBatchIndex`                   | "Invalid batch index"                                   |
-| 14309 | 0x37E5 | `InvalidIndex`                        | "Invalid index"                                         |
-| 14310 | 0x37E6 | `TreeIsFull`                          | "Batched Merkle tree is full."                          |
-| 14311 | 0x37E7 | `NonInclusionCheckFailed`             | "Value already exists in bloom filter."                 |
-| 14312 | 0x37E8 | `BloomFilterNotZeroed`                | "Bloom filter must be zeroed prior to reusing a batch." |
-
-## 15001 - 15017 / ZeroCopyError Variants
-
-> **Source:** [errors.rs](https://github.com/Lightprotocol/light-protocol/blob/604892ff3902292dd0b0b047cb0bfacc469ea0d4/program-libs/zero-copy/src/errors.rs)
-
-| Code  | Hex    | Error                                       | Message                                                      |
-| :---- | :----- | :------------------------------------------ | :----------------------------------------------------------- |
-| 15001 | 0x3A99 | `Full`                                      | "The vector is full, cannot push any new elements"           |
-| 15002 | 0x3A9A | `ArraySize(usize, usize)`                   | "Requested array of size {}, but the vector has {} elements" |
-| 15003 | 0x3A9B | `IterFromOutOfBounds`                       | "The requested start index is out of bounds"                 |
-| 15004 | 0x3A9C | `InsufficientMemoryAllocated(usize, usize)` | "Memory allocated {}, Memory required {}"                    |
-| 15006 | 0x3A9E | `UnalignedPointer`                          | "Unaligned pointer"                                          |
-| 15007 | 0x3A9F | `MemoryNotZeroed`                           | "Memory not zeroed"                                          |
-| 15008 | 0x3AA0 | `InvalidConversion`                         | "Invalid conversion"                                         |
-| 15009 | 0x3AA1 | `InvalidData(Infallible)`                   | "Invalid data"                                               |
-| 15010 | 0x3AA2 | `Size`                                      | "Invalid size"                                               |
-| 15011 | 0x3AA3 | `InvalidOptionByte(u8)`                     | "Invalid option byte {} must be 0 (None) or 1 (Some)"        |
-| 15012 | 0x3AA4 | `InvalidCapacity`                           | "Invalid capacity. Capacity must be greater than 0"          |
-| 15013 | 0x3AA5 | `LengthGreaterThanCapacity`                 | "Length is greater than capacity"                            |
-| 15014 | 0x3AA6 | `CurrentIndexGreaterThanLength`             | "Current index is greater than length"                       |
-| 15015 | 0x3AA7 | `InvalidEnumValue`                          | "Invalid enum value"                                         |
-| 15016 | 0x3AA8 | `InsufficientCapacity`                      | "Insufficient capacity for operation"                        |
-| 15017 | 0x3AA9 | `PlatformSizeOverflow`                      | "Value too large for platform usize"                         |
-
-## 16001 - 16034 / LightSdkError Variants
-
-> **Source:** [error.rs](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/sdk/src/error.rs#L126)
-
-| Code  | Hex    | Error                                | Message                                                                                                                                           |
-| :---- | :----- | :----------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
-| 16001 | 0x3E81 | `ConstraintViolation`                | "Constraint violation"                                                                                                                            |
-| 16002 | 0x3E82 | `InvalidLightSystemProgram`          | "Invalid light-system-program ID"                                                                                                                 |
-| 16003 | 0x3E83 | `ExpectedAccounts`                   | "Expected accounts in the instruction"                                                                                                            |
-| 16004 | 0x3E84 | `ExpectedAddressTreeInfo`            | "Expected address Merkle context to be provided"                                                                                                  |
-| 16005 | 0x3E85 | `ExpectedAddressRootIndex`           | "Expected address root index to be provided"                                                                                                      |
-| 16006 | 0x3E86 | `ExpectedData`                       | "Accounts with a specified input are expected to have data"                                                                                       |
-| 16007 | 0x3E87 | `ExpectedDiscriminator`              | "Accounts with specified data are expected to have a discriminator"                                                                               |
-| 16008 | 0x3E88 | `ExpectedHash`                       | "Accounts with specified data are expected to have a hash"                                                                                        |
-| 16009 | 0x3E89 | `ExpectedLightSystemAccount(String)` | "Expected the `{0}` light account to be provided"                                                                                                 |
-| 16010 | 0x3E8A | `ExpectedMerkleContext`              | "`mut` and `close` accounts are expected to have a Merkle context"                                                                                |
-| 16011 | 0x3E8B | `ExpectedRootIndex`                  | "Expected root index to be provided"                                                                                                              |
-| 16012 | 0x3E8C | `TransferFromNoInput`                | "Cannot transfer lamports from an account without input"                                                                                          |
-| 16013 | 0x3E8D | `TransferFromNoLamports`             | "Cannot transfer from an account without lamports"                                                                                                |
-| 16014 | 0x3E8E | `TransferFromInsufficientLamports`   | "Account, from which a transfer was attempted, has insufficient amount of lamports"                                                               |
-| 16015 | 0x3E8F | `TransferIntegerOverflow`            | "Integer overflow resulting from too large resulting amount"                                                                                      |
-| 16016 | 0x3E90 | `Borsh`                              | "Borsh error."                                                                                                                                    |
-| 16017 | 0x3E91 | `FewerAccountsThanSystemAccounts`    | "Fewer accounts than number of system accounts."                                                                                                  |
-| 16018 | 0x3E92 | `InvalidCpiSignerAccount`            | "InvalidCpiSignerAccount"                                                                                                                         |
-| 16019 | 0x3E93 | `MissingField(String)`               | "Missing meta field: {0}"                                                                                                                         |
-| 16020 | 0x3E94 | `OutputStateTreeIndexIsNone`         | "Output state tree index is none. Use an CompressedAccountMeta type with output tree index to initialize or update accounts."                     |
-| 16021 | 0x3E95 | `InitAddressIsNone`                  | "Address is none during initialization"                                                                                                           |
-| 16022 | 0x3E96 | `InitWithAddressIsNone`              | "Address is none during initialization with address"                                                                                              |
-| 16023 | 0x3E97 | `InitWithAddressOutputIsNone`        | "Output is none during initialization with address"                                                                                               |
-| 16024 | 0x3E98 | `MetaMutAddressIsNone`               | "Address is none during meta mutation"                                                                                                            |
-| 16025 | 0x3E99 | `MetaMutInputIsNone`                 | "Input is none during meta mutation"                                                                                                              |
-| 16026 | 0x3E9A | `MetaMutOutputLamportsIsNone`        | "Output lamports is none during meta mutation"                                                                                                    |
-| 16027 | 0x3E9B | `MetaMutOutputIsNone`                | "Output is none during meta mutation"                                                                                                             |
-| 16028 | 0x3E9C | `MetaCloseAddressIsNone`             | "Address is none during meta close"                                                                                                               |
-| 16029 | 0x3E9D | `MetaCloseInputIsNone`               | "Input is none during meta close"                                                                                                                 |
-| 16031 | 0x3E9F | `CpiAccountsIndexOutOfBounds(usize)` | "CPI accounts index out of bounds: {0}"                                                                                                           |
-| 16032 | 0x3EA0 | `InvalidCpiContextAccount`           | "Invalid CPI context account"                                                                                                                     |
-| 16033 | 0x3EA1 | `InvalidSolPoolPdaAccount`           | "Invalid SolPool PDA account"                                                                                                                     |
-| 16034 | 0x3EA2 | `InvalidCpiAccountsOffset`           | "CpiAccounts accounts slice starts with an invalid account. It should start with LightSystemProgram SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7." |
diff --git a/references/light-token.md b/references/light-token.md
index 98062ff3..eaa38c19 100644
--- a/references/light-token.md
+++ b/references/light-token.md
@@ -25,42 +25,44 @@ The base library to use Light Token Accounts, Light Mints, and compressed token
 - support `TokenMetadata`.
 - have the same rent-config as light token accounts
 
-## CPI Operations
+## Program Examples
 
 For full program examples, see the [Light Token Examples](https://github.com/Lightprotocol/examples-light-token).
 
-| Operation | Docs guide | GitHub example |
-|-----------|-----------|----------------|
-| `CreateAssociatedAccountCpi` | [create-ata](https://zkcompression.com/light-token/cookbook/create-ata) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-ata) |
-| `CreateTokenAccountCpi` | [create-token-account](https://zkcompression.com/light-token/cookbook/create-token-account) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-token-account) |
-| `CreateMintCpi` | [create-mint](https://zkcompression.com/light-token/cookbook/create-mint) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/create-mint) |
-| `MintToCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to) |
-| `MintToCheckedCpi` | [mint-to](https://zkcompression.com/light-token/cookbook/mint-to) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/mint-to-checked) |
-| `BurnCpi` | [burn](https://zkcompression.com/light-token/cookbook/burn) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/burn) |
-| `TransferCheckedCpi` | [transfer-checked](https://zkcompression.com/light-token/cookbook/transfer-checked) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-checked) |
-| `TransferInterfaceCpi` | [transfer-interface](https://zkcompression.com/light-token/cookbook/transfer-interface) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/transfer-interface) |
-| `ApproveCpi` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/approve) |
-| `RevokeCpi` | [approve-revoke](https://zkcompression.com/light-token/cookbook/approve-revoke) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/revoke) |
-| `FreezeCpi` | [freeze-thaw](https://zkcompression.com/light-token/cookbook/freeze-thaw) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/freeze) |
-| `ThawCpi` | [freeze-thaw](https://zkcompression.com/light-token/cookbook/freeze-thaw) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/thaw) |
-| `CloseAccountCpi` | [close-token-account](https://zkcompression.com/light-token/cookbook/close-token-account) | [example](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/basic-instructions/close-token-account) |
-
-### Common Operations
-
-| Operation | Instruction Builder | CPI Builder |
-|-----------|----------------|-------------|
-| Create Associated Token Account | `CreateAssociatedTokenAccount` | `CreateAssociatedAccountCpi` |
-| Create Token Account | `CreateTokenAccount` | `CreateTokenAccountCpi` |
-| Transfer | `Transfer` | `TransferCpi` |
-| Transfer Interface (auto-detect) | `TransferInterface` | `TransferInterfaceCpi` |
-| Close Token account | `CloseAccount` | `CloseAccountCpi` |
-| Create Mint | `CreateMint` | `CreateMintCpi` |
-| MintTo | `MintTo` | `MintToCpi` |
-
-### Features
-
-1. `anchor` - Derives AnchorSerialize, AnchorDeserialize instead of BorshSerialize, BorshDeserialize.
-2. `compressible` - utility functions for compressible sdk macros.
+### Instructions
+
+The instructions use pure CPI calls which you can combine with existing and / or light macros.
+For existing programs, you can replace spl_token with light_token instructions as you need. The API is a superset of SPL-token so switching is straightforward.
+
+|  | Description |
+|---------|-------------|
+| [approve](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/approve/src/lib.rs) | Approve delegate via CPI |
+| [burn](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/burn/src/lib.rs) | Burn tokens via CPI |
+| [close](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/close/src/lib.rs) | Close token account via CPI |
+| [create-associated-token-account](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-ata/src/lib.rs) | Create associated light-token account via CPI |
+| [create-mint](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-mint/src/lib.rs) | Create light-token mint via CPI |
+| [create-token-account](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-token-account/src/lib.rs) | Create light-token account via CPI |
+| [freeze](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/freeze/src/lib.rs) | Freeze token account via CPI |
+| [mint-to](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/mint-to/src/lib.rs) | Mint tokens via CPI |
+| [revoke](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/revoke/src/lib.rs) | Revoke delegate via CPI |
+| [thaw](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/thaw/src/lib.rs) | Thaw token account via CPI |
+| [transfer-checked](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-checked/src/lib.rs) | Transfer with mint validation via CPI |
+| [transfer-interface](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-interface/src/lib.rs) | Transfer between light-token, T22, and SPL accounts via CPI |
+
+### Macros
+
+|  | Description |
+|---------|-------------|
+| [counter](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/counter) | Create PDA with sponsored rent-exemption |
+| [create-associated-token-account](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-ata) | Create associated light-token account |
+| [create-mint](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint) | Create light-token mint |
+| [create-token-account](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-token-account) | Create light-token account |
+
+### Examples
+
+|  | Description |
+|---------|-------------|
+| [create-and-transfer](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/create-and-transfer) | Create account via macro and transfer via CPI |
 
 ## TypeScript Client
 
@@ -82,19 +84,19 @@ Rust client for light-token. Each action builds, signs, and sends the transactio
 ### TypeScript Examples
 
 - **create-mint** - Create a light-token mint
-  - [Action](typescript-client/actions/create-mint.ts) | [Instruction](typescript-client/instructions/create-mint.ts)
+  - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-mint.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-mint.ts)
 - **create-ata** - Create an associated light-token account
-  - [Action](typescript-client/actions/create-ata.ts) | [Instruction](typescript-client/instructions/create-ata.ts)
+  - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-ata.ts)
 - **load-ata** - Load token accounts from light-token, compressed tokens, SPL/T22 to one unified balance.
-  - [Action](typescript-client/actions/load-ata.ts) | [Instruction](typescript-client/instructions/load-ata.ts)
+  - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/load-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/load-ata.ts)
 - **mint-to** - Mint tokens to a light-account
-  - [Action](typescript-client/actions/mint-to.ts) | [Instruction](typescript-client/instructions/mint-to.ts)
+  - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/mint-to.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/mint-to.ts)
 - **transfer-interface** - Transfer between light-token, T22, and SPL accounts
-  - [Action](typescript-client/actions/transfer-interface.ts) | [Instruction](typescript-client/instructions/transfer-interface.ts)
+  - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/transfer-interface.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/transfer-interface.ts)
 - **wrap** - Wrap SPL/T22 to light-token
-  - [Action](typescript-client/actions/wrap.ts)
+  - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/wrap.ts)
 - **unwrap** - Unwrap light-token to SPL/T22
-  - [Action](typescript-client/actions/unwrap.ts)
+  - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/unwrap.ts)
 
 ## Toolkits
 
diff --git a/scripts/copy-rust-snippets.sh b/scripts/copy-rust-snippets.sh
index 05e5c8a7..4cad3dc9 100755
--- a/scripts/copy-rust-snippets.sh
+++ b/scripts/copy-rust-snippets.sh
@@ -3,11 +3,16 @@
 # Script to copy Rust client code from examples-light-token-rust-client to docs snippets
 # Creates action.mdx and instruction.mdx files wrapped in rust code blocks
 
-EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token-rust-client/rust-client"
+EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token/rust-client"
 SNIPPETS_DIR="/home/tilo/Workspace/docs/snippets/code-snippets/light-token"
 
 # Full recipes (action + instruction in same directory)
-FULL_RECIPES=("create-mint" "create-ata" "mint-to" "transfer-interface" "transfer-checked")
+FULL_RECIPES=("create-mint" "mint-to" "transfer-interface" "transfer-checked")
+
+# Full recipes with name mapping (target-dir:source-filename)
+FULL_RECIPES_MAPPED=(
+    "create-ata:create_associated_token_account"
+)
 
 # Action-only recipes (action file only)
 ACTION_ONLY=("wrap" "unwrap")
@@ -64,6 +69,31 @@ for recipe in "${FULL_RECIPES[@]}"; do
     fi
 done
 
+echo ""
+echo "--- Full recipes (mapped names) ---"
+for mapping in "${FULL_RECIPES_MAPPED[@]}"; do
+    output_name="${mapping%%:*}"
+    source_name="${mapping##*:}"
+    echo "Processing: $output_name (source: $source_name.rs)"
+
+    output_dir="$SNIPPETS_DIR/$output_name/rust-client"
+    mkdir -p "$output_dir"
+
+    action_file="$EXAMPLES_DIR/actions/$source_name.rs"
+    if [ -f "$action_file" ]; then
+        wrap_rust "$action_file" "$output_dir/action.mdx"
+    else
+        echo "  WARNING: Not found - $action_file"
+    fi
+
+    instruction_file="$EXAMPLES_DIR/instructions/$source_name.rs"
+    if [ -f "$instruction_file" ]; then
+        wrap_rust "$instruction_file" "$output_dir/instruction.mdx"
+    else
+        echo "  WARNING: Not found - $instruction_file"
+    fi
+done
+
 echo ""
 echo "--- Action-only recipes ---"
 for recipe in "${ACTION_ONLY[@]}"; do
diff --git a/skill.md b/skill.md
deleted file mode 100644
index 927ab9c3..00000000
--- a/skill.md
+++ /dev/null
@@ -1,175 +0,0 @@
----
-name: solana-rent-free-dev
-description: Skill for Solana development without rent-exemption. Overview to skills to develop solana client (typescript and rust) and programs (anchor, native rust, pinocchio). Covers: for defi light-token; for airdrops, token distribution, payments compressed token; for user and app state compressed pda's (not config or pool accounts); for privacy and zk programs nullifiers to prevent double spending. add skill via npx skills add zkcompression.com/skill.md
-license: MIT
-compatibility: Requires ZK Compression CLI, Solana CLI, Anchor CLI, Node.js.
-metadata:
-  author: lightprotocol
-  version: "1.0"
-allowed-tools: mcp__zkcompression__SearchLightProtocol WebFetch(https://zkcompression.com/*) WebFetch(https://github.com/Lightprotocol/*)
----
-
-# Light Protocol Development
-
-Understand the available primitives.
-
-| Primitive | Reference |
-|-----------|-----------|
-| Light Token (DeFi, payments) | [references/light-token.md](references/light-token.md) |
-| Airdrop / Token Distribution | [references/airdrop-client.md](references/airdrop-client.md) |
-| Compressed PDA (user state, app state) | [references/compressed-pda.md](references/compressed-pda.md) |
-| Nullifiers (prevent double spending, ZK/Privacy apps) | [references/zk-nullifiers.md](references/zk-nullifiers.md) |
-| DeepWiki Research | [references/deepwiki.md](references/deepwiki.md) |
-
-add these skills via npx skills add yourdomain.com/docs/skill.md
-
----
-
-SDK References:
-* Typescript SDK 
-    * for accounts: https://lightprotocol.github.io/light-protocol/stateless.js/index.html
-    * for tokens: https://lightprotocol.github.io/light-protocol/compressed-token/index.html
-* Rust  
-    * Client for accounts and tokens: https://docs.rs/light-client/latest/light_client/
-    * Client SDK for Light Token: https://docs.rs/light-token/latest/light_token/
-    * for building on-chain programs with compressed accounts: https://docs.rs/light-sdk/latest/light_sdk
-* Program Testing: https://docs.rs/light-program-test/latest/light_program_test/
-
-When stuck or debugging load the deepwiki skill.
-
-## Light Token
-
-Light token is a high-performance token standard that reduces the cost of mint and token accounts by 200x.
-
-* All light mint and token accounts are on-chain accounts like SPL, but the light token program sponsors the rent-exemption cost for you.
-* Light-token accounts can hold balances from any light, SPL, or Token-2022 mint.
-* Light-mint accounts represent a unique mint and optionally can store token-metadata. Functionally equivalent to SPL mints.
-
-[Full reference](references/light-token.md)
-
-### Use Case: DeFi
-
-For DeFi program integration (AMMs, vaults, lending), see the `defi-dev` skill which covers macro and CPI program patterns, hot/cold account loading, and Jito bundles.
-
-Load Defi Dev skill -> 
-
-### Toolkits
-
-| Toolkit | Docs |
-|---------|------|
-| Payments & Wallets | [for-payments](https://zkcompression.com/light-token/toolkits/for-payments) |
-| Streaming tokens | [for-streaming-tokens](https://zkcompression.com/light-token/toolkits/for-streaming-tokens) |
-
-Load other Toolkits ->
-
----
-
-## Compressed Token
-
-[Full reference](references/compressed-token.md)
-
-Compressed Token Accounts
-- are well suited for airdrops and reward distribution.
-- require no rent-exemption
-- are on Solana mainnet.
-- are compressed accounts (similar UX to SPL)
-- can hold Light Mint and SPL Mint tokens.
-- cost 5,000 lamports to create.
-- Wallet support by Phantom and Backpack
-
-### Difference to Light-Token
-light-token: Solana account that holds token balances of light-mints, SPL or Token 22 mints.
-Compressed token: Compressed account storing token data. Rent-free, for storage and distribution.
-
-### Guides & Examples
-
-| Topic | Docs guide | GitHub example |
-|-------|-----------|----------------|
-| Airdrop | [airdrop](https://www.zkcompression.com/compressed-tokens/advanced-guides/airdrop) | [example](https://github.com/Lightprotocol/examples-zk-compression/tree/main/example-token-distribution) |
-| Payments & Sign with Privy integration | [privy](https://www.zkcompression.com/compressed-tokens/for-privy) | [example](https://github.com/Lightprotocol/examples-zk-compression/tree/main/privy) |
-| Overview to Guides | https://www.zkcompression.com/compressed-tokens/overview | 
-add cookbook
-
----
-
-## Compressed PDAs
-
-Compressed accounts do not require rent-exemption, which makes them suitable for:
-- user owned accounts
-- not config accounts which are often read
-- not pool accounts, since compressed accounts cannot be used concurrently
-
-Load skill: [Full reference](references/compressed-pda.md)
-
-### Difference to Light-Accounts (Light-PDA)
-Light-PDA's are Solana accounts with sponsored rent-exemption.
-There is no proof required for interactions with Light-PDA's which makes
-them suitable for Defi Usecases. Compressed PDA's don't require rent-exemption,
-but a proof for interactions.
-
-### Client-Program Interaction Flow
-```text
- ├─ Client
- │  ├─ Get ValidityProof from RPC.
- │  ├─ pack accounts with PackedAccounts into PackedAddressTreeInfo and PackedStateTreeInfo.
- │  ├─ pack CompressedAccountMeta.
- │  ├─ Build Instruction from PackedAccounts and CompressedAccountMetas.
- │  └─ Send transaction.
- │
- └─ Custom Program
-    ├─ CpiAccounts parse accounts consistent with PackedAccounts.
-    ├─ LightAccount instantiates from CompressedAccountMeta.
-    │
-    └─ Light System Program CPI
-       ├─ Verify ValidityProof.
-       ├─ Update State Merkle tree.
-       ├─ Update Address Merkle tree.
-       └─ Complete atomic state transition.
-```
-
----
-
-## ZK Nullifiers
-
-[Full reference](references/zk-nullifiers.md)
-
-Use for privacy preserving applications on Solana to prevent double spending. Uses compressed PDAs as nullifiers. A nullifier is a deterministically derived hash to ensure an action can only be performed once. The nullifier cannot be linked to the action or user.
-
-| Storage | Cost per nullifier |
-|---------|-------------------|
-| PDA | 890,880 lamports |
-| Compressed PDA | 15,000 lamports |
-
-- Docs: [zk/overview](https://www.zkcompression.com/zk/overview)
-- Example: [program-examples/zk/nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier)
-- load skill: 
----
-
-## SDK Reference
-
-[Full reference](references/sdk-reference.md)
-
-### TypeScript
-
-| Package | npm |
-|---------|-----|
-| `@lightprotocol/stateless.js` | [npm](https://www.npmjs.com/package/@lightprotocol/stateless.js) |
-| `@lightprotocol/compressed-token` | [npm](https://www.npmjs.com/package/@lightprotocol/compressed-token) |
-
-### Rust
-
-| Crate | docs.rs |
-|-------|---------|
-| `light-sdk` | [docs.rs/light-sdk](https://docs.rs/light-sdk) |
-| `light-sdk-pinocchio` | [docs.rs/light-sdk-pinocchio](https://docs.rs/light-sdk-pinocchio) |
-| `light-token` | [docs.rs/light-token](https://docs.rs/light-token) |
-| `light-token-client` | [docs.rs/light-token-client](https://docs.rs/light-token-client) |
-| `light-compressed-token-sdk` | [docs.rs/light-compressed-token-sdk](https://docs.rs/light-compressed-token-sdk) |
-| `light-client` | [docs.rs/light-client](https://docs.rs/light-client) |
-| `light-program-test` | [docs.rs/light-program-test](https://docs.rs/light-program-test) |
-
-### CLI
-
-```bash
-npm i -g @lightprotocol/zk-compression-cli
-```
\ No newline at end of file
diff --git a/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx b/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx
index 4d58d882..0b8a1fcd 100644
--- a/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx
+++ b/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx
@@ -15,16 +15,16 @@ async fn main() -> Result<(), Box> {
 
     let owner = Keypair::new();
 
-    let create_ata_instruction =
+    let create_associated_token_account_instruction =
         CreateAssociatedTokenAccount::new(payer.pubkey(), owner.pubkey(), mint).instruction()?;
 
     let sig = rpc
-        .create_and_send_transaction(&[create_ata_instruction], &payer.pubkey(), &[&payer])
+        .create_and_send_transaction(&[create_associated_token_account_instruction], &payer.pubkey(), &[&payer])
         .await?;
 
     let associated_token_account = get_associated_token_address(&owner.pubkey(), &mint);
     let data = rpc.get_account(associated_token_account).await?;
-    println!("ATA: {associated_token_account} exists: {} Tx: {sig}", data.is_some());
+    println!("Associated token account: {associated_token_account} exists: {} Tx: {sig}", data.is_some());
 
     Ok(())
 }
diff --git a/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx b/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx
index 516378bf..cd9f6d90 100644
--- a/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx
+++ b/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx
@@ -23,7 +23,7 @@ async fn main() -> Result<(), Box> {
         destination: associated_token_account,
         amount: mint_amount,
         authority: payer.pubkey(),
-        max_top_up: None,
+        max_top_up: None, 
         fee_payer: None,
     }
     .instruction()?;
diff --git a/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx b/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx
index 0de24f35..621f93de 100644
--- a/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx
+++ b/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx
@@ -25,10 +25,10 @@ async fn main() -> Result<(), Box> {
     let recipient = Keypair::new();
     let recipient_associated_token_account = get_associated_token_address(&recipient.pubkey(), &mint);
 
-    let create_ata_instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), recipient.pubkey(), mint)
+    let create_associated_token_account_instruction = CreateAssociatedTokenAccount::new(payer.pubkey(), recipient.pubkey(), mint)
         .instruction()?;
 
-    rpc.create_and_send_transaction(&[create_ata_instruction], &payer.pubkey(), &[&payer])
+    rpc.create_and_send_transaction(&[create_associated_token_account_instruction], &payer.pubkey(), &[&payer])
         .await?;
 
     // TransferChecked validates decimals match the mint's decimals
diff --git a/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx b/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx
index 770cfc43..bc4ebd49 100644
--- a/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx
+++ b/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx
@@ -29,10 +29,10 @@ async fn main() -> Result<(), Box> {
     // Create Light associated token account
     let light_associated_token_account = get_associated_token_address(&payer.pubkey(), &mint);
 
-    let create_ata_instruction =
+    let create_associated_token_account_instruction =
         CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint).instruction()?;
 
-    rpc.create_and_send_transaction(&[create_ata_instruction], &payer.pubkey(), &[&payer])
+    rpc.create_and_send_transaction(&[create_associated_token_account_instruction], &payer.pubkey(), &[&payer])
         .await?;
 
     // SPL interface PDA for Mint (holds SPL tokens when transferred to Light Token)
diff --git a/snippets/overview-tables/light-token-client-examples-table.mdx b/snippets/overview-tables/light-token-client-examples-table.mdx
new file mode 100644
index 00000000..0dede07a
--- /dev/null
+++ b/snippets/overview-tables/light-token-client-examples-table.mdx
@@ -0,0 +1,35 @@
+### TypeScript
+
+| | | |
+|---------|--------|-------------|
+| **approve** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/delegate-approve.ts) | — |
+| **create-ata** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-ata.ts) |
+| **create-mint** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-mint.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-mint.ts) |
+| **load-ata** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/load-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/load-ata.ts) |
+| **mint-to** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/mint-to.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/mint-to.ts) |
+| **revoke** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/delegate-revoke.ts) | — |
+| **transfer-interface** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/transfer-interface.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/transfer-interface.ts) |
+| **unwrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/unwrap.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/unwrap.ts) |
+| **wrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/wrap.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/wrap.ts) |
+
+### Rust
+
+| | | |
+|---------|--------|-------------|
+| **approve** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/approve.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/approve.rs) |
+| **burn** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/burn.rs) |
+| **burn-checked** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/burn_checked.rs) |
+| **close** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/close.rs) |
+| **create-ata** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/create_associated_token_account.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/create_associated_token_account.rs) |
+| **create-mint** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/create_mint.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/create_mint.rs) |
+| **create-token-account** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/create_token_account.rs) |
+| **freeze** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/freeze.rs) |
+| **mint-to** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/mint_to.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/mint_to.rs) |
+| **mint-to-checked** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/mint_to_checked.rs) |
+| **revoke** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/revoke.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/revoke.rs) |
+| **spl-to-light** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/spl_to_light_transfer.rs) |
+| **thaw** | — | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/thaw.rs) |
+| **transfer-checked** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/transfer_checked.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/transfer_checked.rs) |
+| **transfer-interface** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/transfer_interface.rs) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/instructions/transfer_interface.rs) |
+| **unwrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/unwrap.rs) | — |
+| **wrap** | [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/rust-client/actions/wrap.rs) | — |
diff --git a/snippets/overview-tables/light-token-program-examples-table.mdx b/snippets/overview-tables/light-token-program-examples-table.mdx
new file mode 100644
index 00000000..6801290e
--- /dev/null
+++ b/snippets/overview-tables/light-token-program-examples-table.mdx
@@ -0,0 +1,35 @@
+### Instructions
+
+The instructions use pure CPI calls which you can combine with existing and / or light macros.
+For existing programs, you can replace spl_token with light_token instructions as you need. The API is a superset of SPL-token so switching is straightforward.
+
+|  | Description |
+|---------|-------------|
+| [approve](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/approve/src/lib.rs) | Approve delegate via CPI |
+| [burn](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/burn/src/lib.rs) | Burn tokens via CPI |
+| [close](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/close/src/lib.rs) | Close token account via CPI |
+| [create-associated-token-account](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-ata/src/lib.rs) | Create associated light-token account via CPI |
+| [create-mint](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-mint/src/lib.rs) | Create light-token mint via CPI |
+| [create-token-account](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-token-account/src/lib.rs) | Create light-token account via CPI |
+| [freeze](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/freeze/src/lib.rs) | Freeze token account via CPI |
+| [mint-to](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/mint-to/src/lib.rs) | Mint tokens via CPI |
+| [revoke](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/revoke/src/lib.rs) | Revoke delegate via CPI |
+| [thaw](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/thaw/src/lib.rs) | Thaw token account via CPI |
+| [transfer-checked](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-checked/src/lib.rs) | Transfer with mint validation via CPI |
+| [transfer-interface](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-interface/src/lib.rs) | Transfer between light-token, T22, and SPL accounts via CPI |
+
+### Macros
+
+|  | Description |
+|---------|-------------|
+| [counter](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/counter) | Create PDA with sponsored rent-exemption |
+| [create-associated-token-account](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-ata) | Create associated light-token account |
+| [create-mint](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint) | Create light-token mint |
+| [create-token-account](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-token-account) | Create light-token account |
+
+### Examples
+
+|  | Description |
+|---------|-------------|
+| [cp-swap-reference](https://github.com/Lightprotocol/cp-swap-reference) | Fork of Raydium AMM that creates markets without paying rent-exemption |
+| [create-and-transfer](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/create-and-transfer) | Create account via macro and transfer via CPI |

From 4028ff07f6227fae66d81a4a7c8756d268639f47 Mon Sep 17 00:00:00 2001
From: tilo-14 
Date: Sun, 1 Feb 2026 18:49:55 +0000
Subject: [PATCH 11/21] update streaming

---
 docs.json                                     |  4 +-
 light-token/cookbook/approve-revoke.mdx       | 62 +++++++++++++++++++
 light-token/cookbook/load-ata.mdx             |  4 +-
 light-token/toolkits/overview.mdx             |  4 +-
 scripts/copy-light-token-snippets.sh          | 24 ++++++-
 .../code-samples/code-compare-snippets.jsx    | 50 +++++++++++++++
 .../approve-revoke/approve-action.mdx         | 41 ++++++++++++
 .../approve-revoke/revoke-action.mdx          | 41 ++++++++++++
 .../light-token/create-ata/instruction.mdx    |  8 +--
 .../light-token/create-mint/instruction.mdx   | 14 ++---
 .../light-token/load-ata/instruction.mdx      | 30 +++++----
 .../light-token/mint-to/instruction.mdx       | 20 +++---
 .../transfer-interface/instruction.mdx        | 11 ++--
 13 files changed, 268 insertions(+), 45 deletions(-)
 create mode 100644 snippets/code-snippets/light-token/approve-revoke/approve-action.mdx
 create mode 100644 snippets/code-snippets/light-token/approve-revoke/revoke-action.mdx

diff --git a/docs.json b/docs.json
index 52bc76a5..fdb20d0b 100644
--- a/docs.json
+++ b/docs.json
@@ -55,7 +55,9 @@
             "pages": [
               "light-token/toolkits/overview",
               "light-token/toolkits/for-payments",
-              "light-token/toolkits/for-wallets"
+              "light-token/toolkits/for-wallets",
+              "light-token/toolkits/for-streaming-mints",
+              "light-token/toolkits/for-streaming-tokens"
             ]
           },
           {
diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx
index 2f0514ec..78c71543 100644
--- a/light-token/cookbook/approve-revoke.mdx
+++ b/light-token/cookbook/approve-revoke.mdx
@@ -8,13 +8,20 @@ keywords: ["approve delegate solana", "revoke delegate solana", "token delegatio
 ---
 
 import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx";
+import FullSetup from "/snippets/setup/full-setup.mdx";
 import { CodeCompare } from "/snippets/jsx/code-compare.jsx";
 import {
+  splApproveCode,
+  lightApproveCode,
+  splRevokeCode,
+  lightRevokeCode,
   splApproveRustCode,
   lightApproveRustCode,
   splRevokeRustCode,
   lightRevokeRustCode,
 } from "/snippets/code-samples/code-compare-snippets.jsx";
+import TsApproveActionCode from "/snippets/code-snippets/light-token/approve-revoke/approve-action.mdx";
+import TsRevokeActionCode from "/snippets/code-snippets/light-token/approve-revoke/revoke-action.mdx";
 import ApproveActionCode from "/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-action.mdx";
 import ApproveInstructionCode from "/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-instruction.mdx";
 import RevokeActionCode from "/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-action.mdx";
@@ -29,6 +36,61 @@ import RevokeAnchorProgramCode from "/snippets/code-snippets/light-token/revoke/
 3. Only the token account owner can approve or revoke delegates.
 
 
+
+
+
+
+
+
+`approve` grants a delegate permission to transfer up to a specified amount of tokens.
+
+
+
+
+
+
+`revoke` removes all delegate permissions from a light-token account.
+
+
+
+
+
+
+
+  Find the source code
+  [here](https://github.com/Lightprotocol/examples-light-token/tree/main/typescript-client/actions).
+
+
+
+
+### Approve a delegate
+
+
+
+
+
+
+
+
+### Revoke a delegate
+
+
+
+
+
+
+
+
 
 
 
diff --git a/light-token/cookbook/load-ata.mdx b/light-token/cookbook/load-ata.mdx
index 042e1c20..53db7165 100644
--- a/light-token/cookbook/load-ata.mdx
+++ b/light-token/cookbook/load-ata.mdx
@@ -1,6 +1,6 @@
 ---
-title: Load Token Balances to Light ATA
-sidebarTitle: Load ATA
+title: Load Token Balances to Light Associated Token Account
+sidebarTitle: Load Associated Token Account
 description: Unify token balances from compressed tokens (cold light-tokens), SPL, and Token-2022 to one light ATA.
 keywords: ["load ata on solana", "get token balance for wallets"]
 ---
diff --git a/light-token/toolkits/overview.mdx b/light-token/toolkits/overview.mdx
index 485e7df9..30e36eb4 100644
--- a/light-token/toolkits/overview.mdx
+++ b/light-token/toolkits/overview.mdx
@@ -25,7 +25,7 @@ keywords: ["token sdk for solana developers", "token infrastructure for solana a
   Allow users to display and swap light-tokens.
 
 
-{/* 
   Stream token events from the network in real-time.
- */}
+
diff --git a/scripts/copy-light-token-snippets.sh b/scripts/copy-light-token-snippets.sh
index e607a3fe..c24d40c1 100755
--- a/scripts/copy-light-token-snippets.sh
+++ b/scripts/copy-light-token-snippets.sh
@@ -1,18 +1,19 @@
 #!/bin/bash
 
-# Script to copy TypeScript code from examples-light-token to docs/snippets/code-snippets/light-token
+# Script to copy TypeScript code from streaming-tokens to docs/snippets/code-snippets/light-token
 # Wraps each file in typescript markdown code blocks
 
-EXAMPLES="/home/tilo/Workspace/examples-light-token/typescript-client"
+EXAMPLES="/home/tilo/Workspace/streaming-tokens/typescript-client"
 SNIPPETS_DIR="/home/tilo/Workspace/docs/snippets/code-snippets/light-token"
 
-# Recipes to process (matching directory names)
+# Recipes to process (matching directory and file names)
 RECIPES=("create-mint" "create-ata" "mint-to" "transfer-interface" "load-ata" "wrap" "unwrap")
 
 # Function to wrap TypeScript code in markdown
 wrap_typescript() {
     local input_file="$1"
     local output_file="$2"
+    mkdir -p "$(dirname "$output_file")"
     echo '```typescript' > "$output_file"
     cat "$input_file" >> "$output_file"
     echo '```' >> "$output_file"
@@ -40,6 +41,23 @@ for recipe in "${RECIPES[@]}"; do
     fi
 done
 
+# Approve/revoke: non-standard filenames, action-only
+echo "Processing: approve-revoke"
+
+approve_file="$EXAMPLES/actions/delegate-approve.ts"
+if [ -f "$approve_file" ]; then
+    wrap_typescript "$approve_file" "$SNIPPETS_DIR/approve-revoke/approve-action.mdx"
+else
+    echo "  WARNING: Not found - $approve_file"
+fi
+
+revoke_file="$EXAMPLES/actions/delegate-revoke.ts"
+if [ -f "$revoke_file" ]; then
+    wrap_typescript "$revoke_file" "$SNIPPETS_DIR/approve-revoke/revoke-action.mdx"
+else
+    echo "  WARNING: Not found - $revoke_file"
+fi
+
 echo ""
 echo "Done! Created snippets in: $SNIPPETS_DIR"
 echo ""
diff --git a/snippets/code-samples/code-compare-snippets.jsx b/snippets/code-samples/code-compare-snippets.jsx
index 08bd7f4e..04c7468e 100644
--- a/snippets/code-samples/code-compare-snippets.jsx
+++ b/snippets/code-samples/code-compare-snippets.jsx
@@ -404,6 +404,56 @@ export const lightRevokeRustCode = [
   ".instruction()?;",
 ].join("\n");
 
+// === APPROVE (TYPESCRIPT) ===
+export const splApproveCode = [
+  'import { approve } from "@solana/spl-token";',
+  "",
+  "const tx = await approve(",
+  "  connection,",
+  "  payer,",
+  "  source,",
+  "  delegate,",
+  "  owner,",
+  "  amount",
+  ");",
+].join("\n");
+
+export const lightApproveCode = [
+  'import { approve } from "@lightprotocol/compressed-token";',
+  "",
+  "const tx = await approve(",
+  "  rpc,",
+  "  payer,",
+  "  mint,",
+  "  amount,",
+  "  owner,",
+  "  delegate",
+  ");",
+].join("\n");
+
+// === REVOKE (TYPESCRIPT) ===
+export const splRevokeCode = [
+  'import { revoke } from "@solana/spl-token";',
+  "",
+  "const tx = await revoke(",
+  "  connection,",
+  "  payer,",
+  "  source,",
+  "  owner",
+  ");",
+].join("\n");
+
+export const lightRevokeCode = [
+  'import { revoke } from "@lightprotocol/compressed-token";',
+  "",
+  "const tx = await revoke(",
+  "  rpc,",
+  "  payer,",
+  "  delegatedAccounts,",
+  "  owner",
+  ");",
+].join("\n");
+
 // === CREATE MINT MACRO (ANCHOR) ===
 export const splCreateMintMacroCode = [
   "#[account(",
diff --git a/snippets/code-snippets/light-token/approve-revoke/approve-action.mdx b/snippets/code-snippets/light-token/approve-revoke/approve-action.mdx
new file mode 100644
index 00000000..e358f1f3
--- /dev/null
+++ b/snippets/code-snippets/light-token/approve-revoke/approve-action.mdx
@@ -0,0 +1,41 @@
+```typescript
+import "dotenv/config";
+import { Keypair } from "@solana/web3.js";
+import { createRpc } from "@lightprotocol/stateless.js";
+import {
+    createMintInterface,
+    mintToCompressed,
+    approve,
+} from "@lightprotocol/compressed-token";
+import { homedir } from "os";
+import { readFileSync } from "fs";
+
+// devnet:
+// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
+// const rpc = createRpc(RPC_URL);
+// localnet:
+const rpc = createRpc();
+
+const payer = Keypair.fromSecretKey(
+    new Uint8Array(
+        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")),
+    ),
+);
+
+(async function () {
+    const { mint } = await createMintInterface(rpc, payer, payer, null, 9);
+    await mintToCompressed(rpc, payer, mint, payer, [{ recipient: payer.publicKey, amount: 1000n }]);
+
+    const delegate = Keypair.generate();
+    const tx = await approve(
+        rpc,
+        payer,
+        mint,
+        500,
+        payer,
+        delegate.publicKey,
+    );
+
+    console.log("Tx:", tx);
+})();
+```
diff --git a/snippets/code-snippets/light-token/approve-revoke/revoke-action.mdx b/snippets/code-snippets/light-token/approve-revoke/revoke-action.mdx
new file mode 100644
index 00000000..cd3d7cc2
--- /dev/null
+++ b/snippets/code-snippets/light-token/approve-revoke/revoke-action.mdx
@@ -0,0 +1,41 @@
+```typescript
+import "dotenv/config";
+import { Keypair } from "@solana/web3.js";
+import { createRpc } from "@lightprotocol/stateless.js";
+import {
+    createMintInterface,
+    mintToCompressed,
+    approve,
+    revoke,
+} from "@lightprotocol/compressed-token";
+import { homedir } from "os";
+import { readFileSync } from "fs";
+
+// devnet:
+// const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
+// const rpc = createRpc(RPC_URL);
+// localnet:
+const rpc = createRpc();
+
+const payer = Keypair.fromSecretKey(
+    new Uint8Array(
+        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")),
+    ),
+);
+
+(async function () {
+    const { mint } = await createMintInterface(rpc, payer, payer, null, 9);
+    await mintToCompressed(rpc, payer, mint, payer, [{ recipient: payer.publicKey, amount: 1000n }]);
+
+    const delegate = Keypair.generate();
+    await approve(rpc, payer, mint, 500, payer, delegate.publicKey);
+
+    const delegatedAccounts = await rpc.getCompressedTokenAccountsByDelegate(
+        delegate.publicKey,
+        { mint },
+    );
+    const tx = await revoke(rpc, payer, delegatedAccounts.items, payer);
+
+    console.log("Tx:", tx);
+})();
+```
diff --git a/snippets/code-snippets/light-token/create-ata/instruction.mdx b/snippets/code-snippets/light-token/create-ata/instruction.mdx
index 55dea6d5..ec0a5285 100644
--- a/snippets/code-snippets/light-token/create-ata/instruction.mdx
+++ b/snippets/code-snippets/light-token/create-ata/instruction.mdx
@@ -22,8 +22,8 @@ const rpc = createRpc();
 
 const payer = Keypair.fromSecretKey(
     new Uint8Array(
-        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
-    )
+        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")),
+    ),
 );
 
 (async function () {
@@ -32,7 +32,7 @@ const payer = Keypair.fromSecretKey(
     const owner = Keypair.generate();
     const associatedToken = getAssociatedTokenAddressInterface(
         mint,
-        owner.publicKey
+        owner.publicKey,
     );
 
     const ix = createAssociatedTokenAccountInterfaceInstruction(
@@ -40,7 +40,7 @@ const payer = Keypair.fromSecretKey(
         associatedToken,
         owner.publicKey,
         mint,
-        CTOKEN_PROGRAM_ID
+        CTOKEN_PROGRAM_ID,
     );
 
     const tx = new Transaction().add(ix);
diff --git a/snippets/code-snippets/light-token/create-mint/instruction.mdx b/snippets/code-snippets/light-token/create-mint/instruction.mdx
index 000a6cfe..9789b974 100644
--- a/snippets/code-snippets/light-token/create-mint/instruction.mdx
+++ b/snippets/code-snippets/light-token/create-mint/instruction.mdx
@@ -25,7 +25,7 @@ const COMPRESSED_MINT_SEED = Buffer.from("compressed_mint");
 function findMintAddress(mintSigner: PublicKey): [PublicKey, number] {
     return PublicKey.findProgramAddressSync(
         [COMPRESSED_MINT_SEED, mintSigner.toBuffer()],
-        CTOKEN_PROGRAM_ID
+        CTOKEN_PROGRAM_ID,
     );
 }
 
@@ -37,8 +37,8 @@ const rpc = createRpc();
 
 const payer = Keypair.fromSecretKey(
     new Uint8Array(
-        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
-    )
+        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")),
+    ),
 );
 
 (async function () {
@@ -49,7 +49,7 @@ const payer = Keypair.fromSecretKey(
 
     const validityProof = await rpc.getValidityProofV2(
         [],
-        [{ address: mintPda.toBytes(), treeInfo: addressTreeInfo }]
+        [{ address: mintPda.toBytes(), treeInfo: addressTreeInfo }],
     );
 
     const ix = createMintInstruction(
@@ -64,13 +64,13 @@ const payer = Keypair.fromSecretKey(
         createTokenMetadata(
             "Example Token",
             "EXT",
-            "https://example.com/metadata.json"
-        )
+            "https://example.com/metadata.json",
+        ),
     );
 
     const tx = new Transaction().add(
         ComputeBudgetProgram.setComputeUnitLimit({ units: 1_000_000 }),
-        ix
+        ix,
     );
     const signature = await sendAndConfirmTransaction(rpc, tx, [
         payer,
diff --git a/snippets/code-snippets/light-token/load-ata/instruction.mdx b/snippets/code-snippets/light-token/load-ata/instruction.mdx
index f79158d5..4cf75957 100644
--- a/snippets/code-snippets/light-token/load-ata/instruction.mdx
+++ b/snippets/code-snippets/light-token/load-ata/instruction.mdx
@@ -3,13 +3,12 @@ import "dotenv/config";
 import { Keypair } from "@solana/web3.js";
 import {
     createRpc,
-    bn,
     buildAndSignTx,
     sendAndConfirmTx,
 } from "@lightprotocol/stateless.js";
 import {
-    createMint,
-    mintTo,
+    createMintInterface,
+    mintToCompressed,
     createLoadAtaInstructions,
     getAssociatedTokenAddressInterface,
 } from "@lightprotocol/compressed-token";
@@ -18,30 +17,35 @@ import { readFileSync } from "fs";
 
 // devnet:
 // const RPC_URL = `https://devnet.helius-rpc.com?api-key=${process.env.API_KEY!}`;
-const rpc = createRpc(RPC_URL);
+// const rpc = createRpc(RPC_URL);
 // localnet:
-// const rpc = createRpc();
+const rpc = createRpc();
 
 const payer = Keypair.fromSecretKey(
     new Uint8Array(
-        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
-    )
+        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")),
+    ),
 );
 
 (async function () {
-    // Setup: mint directly to cold state
-    const { mint } = await createMint(rpc, payer, payer.publicKey, 9);
-    await mintTo(rpc, payer, mint, payer.publicKey, payer, bn(1000));
+    // Inactive Light Tokens are cryptographically preserved on the Solana ledger
+    // as compressed tokens (cold storage)
+    // Setup: Get compressed tokens in light-token associated token account
+    const { mint } = await createMintInterface(rpc, payer, payer, null, 9);
+    await mintToCompressed(rpc, payer, mint, payer, [{ recipient: payer.publicKey, amount: 1000n }]);
 
-    const lightTokenAta = getAssociatedTokenAddressInterface(mint, payer.publicKey);
+    const lightTokenAta = getAssociatedTokenAddressInterface(
+        mint,
+        payer.publicKey,
+    );
 
-    // load from cold to hot state
+    // Load compressed tokens to light associated token account (hot balance)
     const ixs = await createLoadAtaInstructions(
         rpc,
         lightTokenAta,
         payer.publicKey,
         mint,
-        payer.publicKey
+        payer.publicKey,
     );
 
     if (ixs.length === 0) return console.log("Nothing to load");
diff --git a/snippets/code-snippets/light-token/mint-to/instruction.mdx b/snippets/code-snippets/light-token/mint-to/instruction.mdx
index 1f904571..fcaf80d0 100644
--- a/snippets/code-snippets/light-token/mint-to/instruction.mdx
+++ b/snippets/code-snippets/light-token/mint-to/instruction.mdx
@@ -1,6 +1,11 @@
 ```typescript
 import "dotenv/config";
-import { Keypair, ComputeBudgetProgram, Transaction, sendAndConfirmTransaction } from "@solana/web3.js";
+import {
+    Keypair,
+    ComputeBudgetProgram,
+    Transaction,
+    sendAndConfirmTransaction,
+} from "@solana/web3.js";
 import { createRpc, bn, DerivationMode } from "@lightprotocol/stateless.js";
 import {
     createMintInterface,
@@ -20,19 +25,18 @@ const rpc = createRpc();
 
 const payer = Keypair.fromSecretKey(
     new Uint8Array(
-        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
-    )
+        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")),
+    ),
 );
 
 (async function () {
-
     const { mint } = await createMintInterface(rpc, payer, payer, null, 9);
 
     const recipient = Keypair.generate();
     await createAtaInterface(rpc, payer, mint, recipient.publicKey);
     const destination = getAssociatedTokenAddressInterface(
         mint,
-        recipient.publicKey
+        recipient.publicKey,
     );
 
     const mintInterface = await getMintInterface(rpc, mint);
@@ -49,7 +53,7 @@ const payer = Keypair.fromSecretKey(
                 },
             ],
             [],
-            DerivationMode.compressible
+            DerivationMode.compressible,
         );
     }
 
@@ -59,12 +63,12 @@ const payer = Keypair.fromSecretKey(
         payer.publicKey,
         payer.publicKey,
         1_000_000_000,
-        validityProof
+        validityProof,
     );
 
     const tx = new Transaction().add(
         ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }),
-        ix
+        ix,
     );
     const signature = await sendAndConfirmTransaction(rpc, tx, [payer]);
 
diff --git a/snippets/code-snippets/light-token/transfer-interface/instruction.mdx b/snippets/code-snippets/light-token/transfer-interface/instruction.mdx
index e8acf2a8..20d076fb 100644
--- a/snippets/code-snippets/light-token/transfer-interface/instruction.mdx
+++ b/snippets/code-snippets/light-token/transfer-interface/instruction.mdx
@@ -25,8 +25,8 @@ const rpc = createRpc();
 
 const payer = Keypair.fromSecretKey(
     new Uint8Array(
-        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8"))
-    )
+        JSON.parse(readFileSync(`${homedir()}/.config/solana/id.json`, "utf8")),
+    ),
 );
 
 (async function () {
@@ -36,7 +36,7 @@ const payer = Keypair.fromSecretKey(
     await createAtaInterface(rpc, payer, mint, sender.publicKey);
     const senderAta = getAssociatedTokenAddressInterface(
         mint,
-        sender.publicKey
+        sender.publicKey,
     );
     await mintToInterface(rpc, payer, mint, senderAta, payer, 1_000_000_000);
 
@@ -44,14 +44,15 @@ const payer = Keypair.fromSecretKey(
     await createAtaInterface(rpc, payer, mint, recipient.publicKey);
     const recipientAta = getAssociatedTokenAddressInterface(
         mint,
-        recipient.publicKey
+        recipient.publicKey,
     );
 
+    // Transfer tokens between light-token associate token accounts
     const ix = createTransferInterfaceInstruction(
         senderAta,
         recipientAta,
         sender.publicKey,
-        500_000_000
+        500_000_000,
     );
 
     const tx = new Transaction().add(ix);

From 7b7dad6cf16baeb68e714f08440a7d96f1efd11d Mon Sep 17 00:00:00 2001
From: tilo-14 
Date: Sun, 1 Feb 2026 18:57:49 +0000
Subject: [PATCH 12/21] update otkens

---
 light-token/toolkits/for-streaming-tokens.mdx | 152 +++---------------
 1 file changed, 20 insertions(+), 132 deletions(-)

diff --git a/light-token/toolkits/for-streaming-tokens.mdx b/light-token/toolkits/for-streaming-tokens.mdx
index 8b181e66..193d5a8e 100644
--- a/light-token/toolkits/for-streaming-tokens.mdx
+++ b/light-token/toolkits/for-streaming-tokens.mdx
@@ -5,153 +5,41 @@ description: "Light token accounts follow the same layout as SPL-token accounts,
 keywords: ["streaming tokens for solana apps", "scalable token distribution on solana", "token streaming for developers"]
 ---
 
-import ToolkitsSetup from "/snippets/setup/toolkits-setup.mdx";
+import ActionCode from "/snippets/code-snippets/light-token/load-ata/action.mdx";
+import InstructionCode from "/snippets/code-snippets/light-token/load-ata/instruction.mdx";
 
-When a market becomes inactive, its token accounts and related PDAs will be compressed - their state is committed and effectively frozen until a client decompresses it.
+When a market becomes inactive, its token accounts and related PDAs will 
+be compressed autoatically (cold storage). 
+The state is cryptographically preserved on the Solana ledger.
 While compressed, pure on-chain lookups will return uninitialized.
 
-Your indexer should keep tracking, quoting, and routing markets even if the on-chain account shows `is_initialized: false`, `is_compressed: true`. To trade a cold market, the first client must prepend an idempotent decompress "warm up" instruction.
+Your indexer should keep tracking, quoting, and routing markets even if the 
+on-chain account shows `is_initialized: false`, `is_compressed: true`. 
+To trade a cold market, the first client must prepend an 
+idempotent decompress "warm up" instruction.
 
 
   Find the source code
   [here](https://github.com/Lightprotocol/light-protocol/blob/main/js/compressed-token/tests/e2e/load-ata-standard.test.ts).
 
 
-## Setup
-
-
-
 
 
-### Load Compressed Tokens to Hot Balance
-
-
-```typescript Action
-import { Keypair } from "@solana/web3.js";
-import { createRpc, bn } from "@lightprotocol/stateless.js";
-import {
-  createMint,
-  mintTo,
-  loadAta,
-  getAssociatedTokenAddressInterface,
-} from "@lightprotocol/compressed-token";
-
-async function main() {
-  const rpc = createRpc();
-  const payer = Keypair.generate();
-  await rpc.requestAirdrop(payer.publicKey, 10e9);
-
-  const owner = Keypair.generate();
-  await rpc.requestAirdrop(owner.publicKey, 1e9);
-
-  const mintAuthority = Keypair.generate();
-  const mintKeypair = Keypair.generate();
-  const { mint } = await createMint(
-    rpc,
-    payer,
-    mintAuthority.publicKey,
-    9,
-    mintKeypair,
-  );
-
-  await mintTo(rpc, payer, mint, owner.publicKey, mintAuthority, bn(1000));
-
-  // Get light-token ATA address
-  const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);
-
-  // Load compressed tokens to hot balance
-  // Creates ATA if needed, returns null if nothing to load
-  const signature = await loadAta(rpc, tokenAta, owner, mint, payer);
-
-  if (signature) {
-    console.log("Loaded tokens to hot balance");
-    console.log("Transaction:", signature);
-  } else {
-    console.log("Nothing to load");
-  }
-}
-
-main().catch(console.error);
-```
-
-```typescript Instruction
-import { Keypair, ComputeBudgetProgram } from "@solana/web3.js";
-import {
-  createRpc,
-  bn,
-  buildAndSignTx,
-  sendAndConfirmTx,
-  dedupeSigner,
-} from "@lightprotocol/stateless.js";
-import {
-  createMint,
-  mintTo,
-  createLoadAtaInstructions,
-  getAssociatedTokenAddressInterface,
-} from "@lightprotocol/compressed-token";
-
-async function main() {
-  const rpc = createRpc();
-  const payer = Keypair.generate();
-  await rpc.requestAirdrop(payer.publicKey, 10e9);
-
-  const owner = Keypair.generate();
-  await rpc.requestAirdrop(owner.publicKey, 1e9);
-
-  const mintAuthority = Keypair.generate();
-  const mintKeypair = Keypair.generate();
-  const { mint } = await createMint(
-    rpc,
-    payer,
-    mintAuthority.publicKey,
-    9,
-    mintKeypair
-  );
-
-  await mintTo(rpc, payer, mint, owner.publicKey, mintAuthority, bn(1000));
-
-  // Get light-token ATA address
-  const tokenAta = getAssociatedTokenAddressInterface(mint, owner.publicKey);
-
-  // Create load instructions
-  const ixs = await createLoadAtaInstructions(
-    rpc,
-    tokenAta,
-    owner.publicKey,
-    mint,
-    payer.publicKey
-  );
-
-  if (ixs.length === 0) {
-    console.log("Nothing to load");
-    return;
-  }
-
-  // Build, sign, and send transaction
-  const { blockhash } = await rpc.getLatestBlockhash();
-  const additionalSigners = dedupeSigner(payer, [owner]);
-
-  const tx = buildAndSignTx(
-    [ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }), ...ixs],
-    payer,
-    blockhash,
-    additionalSigners
-  );
-
-  const signature = await sendAndConfirmTx(rpc, tx);
-  console.log("Loaded tokens to hot balance");
-  console.log("Transaction:", signature);
-}
-
-main().catch(console.error);
-```
+### Load compressed tokens (cold storage) to Associated Token Account (hot balance)
 
-
+
+
+
+
+
+
+
+
 
 
 
 
-# Stream Light-Mint Accounts
+# Stream light-mint accounts
 
 
+/>
\ No newline at end of file

From f01b3d460836a00cf9f32a81877b29bfbb5766c4 Mon Sep 17 00:00:00 2001
From: tilo-14 
Date: Mon, 2 Feb 2026 13:43:37 +0000
Subject: [PATCH 13/21] update cookbook anchor examples

---
 light-token/cookbook/approve-revoke.mdx       | 20 ++--
 light-token/cookbook/burn.mdx                 | 13 ++-
 light-token/cookbook/close-token-account.mdx  | 19 ++--
 light-token/cookbook/create-ata.mdx           | 16 ++--
 light-token/cookbook/create-mint.mdx          | 20 ++--
 light-token/cookbook/create-token-account.mdx | 21 ++---
 light-token/cookbook/freeze-thaw.mdx          | 18 ++--
 light-token/cookbook/load-ata.mdx             |  4 +-
 light-token/cookbook/mint-to.mdx              | 92 ++-----------------
 light-token/cookbook/transfer-checked.mdx     | 10 +-
 light-token/cookbook/transfer-interface.mdx   | 34 +++----
 light-token/cookbook/wrap-unwrap.mdx          | 16 ++--
 .../code-samples/code-compare-snippets.jsx    |  2 +-
 .../create-mint/anchor-macro/full-example.mdx |  2 +-
 .../anchor-macro/full-example.mdx             | 22 ++---
 15 files changed, 113 insertions(+), 196 deletions(-)

diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx
index 78c71543..dcc61985 100644
--- a/light-token/cookbook/approve-revoke.mdx
+++ b/light-token/cookbook/approve-revoke.mdx
@@ -1,7 +1,7 @@
 ---
 title: Approve and Revoke Delegates
 sidebarTitle: Approve / Revoke
-description: Rust client guide to approve and revoke delegates for light-token accounts. Includes step-by-step implementation and full code examples.
+description: Rust client guide to approve and revoke delegates for Light Token accounts. Includes step-by-step implementation and full code examples.
 keywords: ["approve delegate solana", "revoke delegate solana", "token delegation"]
 ---
 
@@ -32,7 +32,7 @@ import RevokeAnchorProgramCode from "/snippets/code-snippets/light-token/revoke/
 1. Approve grants a delegate permission to transfer up to a specified amount of tokens from your account.
     * Each token account can have only one delegate at a time. 
     * Any new approval overwrites the previous one.
-2. Revoke removes all delegate permissions from a light-token account.
+2. Revoke removes all delegate permissions from a Light Token account.
 3. Only the token account owner can approve or revoke delegates.
 
 
@@ -54,7 +54,7 @@ import RevokeAnchorProgramCode from "/snippets/code-snippets/light-token/revoke/
 
 
 
-`revoke` removes all delegate permissions from a light-token account.
+`revoke` removes all delegate permissions from a Light Token account.
 
 
   Find the full example including shared test utilities in the
-  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests).
+  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client).
 
 
 
@@ -174,8 +174,7 @@ Find [a full code example at the end](#full-code-example).
 
 ### Build Account Infos and CPI the Light Token Program
 
-1. Define the light-token account, delegate, and amount to approve
-2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer.
+Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
 
 
 
@@ -224,8 +223,7 @@ approve_cpi.invoke_signed(&[signer_seeds])?;
 
 ### Build Account Infos and CPI the Light Token Program
 
-1. Define the light-token account to revoke delegation from
-2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer.
+Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
 
 
 
@@ -272,7 +270,7 @@ revoke_cpi.invoke_signed(&[signer_seeds])?;
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/approve).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/approve).
 
 
 
@@ -283,7 +281,7 @@ revoke_cpi.invoke_signed(&[signer_seeds])?;
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/revoke).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/revoke).
 
 
 
@@ -298,7 +296,7 @@ revoke_cpi.invoke_signed(&[signer_seeds])?;
 # Next Steps
 
 
 
 
-### Burn Light-Tokens
+### Burn Light Tokens
 
 
   Find the full example including shared test utilities in the
-  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests).
+  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client).
 
 
 
@@ -71,8 +71,7 @@ Find [a full code example at the end](#full-code-example).
 
 ### Build Account Infos and CPI the Light Token Program
 
-1. Use `invoke` when the authority is an external signer
-2. Use `invoke_signed` when the authority is a PDA
+Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
 
 
 
@@ -120,7 +119,7 @@ BurnCpi {
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/burn).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/burn).
 
 
 
@@ -132,7 +131,7 @@ BurnCpi {
 # Next Steps
 
 
 The `compression_authority`
-closes the account and preserves the balance as compressed token account when the account becomes compressible. 
+closes the account and preserves the balance as compressed token account when the account becomes compressible. 
 The account is reinstated in flight with the same state the next time it is accessed.
 
 
 
 
 
-Use `CloseTokenAccount` to close an empty light-token account.
+Use `CloseTokenAccount` to close an empty Light Token account.
 
 Compare to SPL:
 
@@ -51,11 +51,11 @@ Compare to SPL:
 
 
 
-### Close light-token Account
+### Close Light Token Account
 
 
   Find the full example including shared test utilities in the
-  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests).
+  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client).
 
 
 
@@ -78,8 +78,7 @@ Find [a full code example at the end](#full-code-example).
 
 ### Build Account Infos and CPI the Light Token Program
 
-1. Define the account to close and destination for remaining lamports
-2. Use `invoke` or `invoke_signed` when a CPI requires a PDA signer.
+Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
 
 
 
@@ -127,7 +126,7 @@ CloseAccountCpi {
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/close).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/close).
 
 
 
@@ -141,7 +140,7 @@ CloseAccountCpi {
 {" "}
 
 
@@ -41,7 +41,7 @@ import AnchorMacroCode from "/snippets/code-snippets/light-token/create-ata/anch
 
 
 
-The `createAtaInterface` function creates an associated light-token account in a single call.
+The `createAtaInterface` function creates an associated Light Token account in a single call.
 
 Compare to SPL:
 
@@ -107,7 +107,7 @@ Compare to SPL:
 
 
   Find the full example including shared test utilities in the
-  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests).
+  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client).
 
 
 
@@ -153,7 +153,7 @@ Find [a full code example at the end](#full-code-example).
 
 
   The light-ATA address is derived from `[owner, light_token_program_id, mint]`.
-  Unlike light-token accounts, owner and mint are passed as accounts, not in
+  Unlike Light Token accounts, owner and mint are passed as accounts, not in
   instruction data.
 
 
@@ -211,7 +211,7 @@ CreateAssociatedAccountCpi {
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-ata).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-ata).
 
 
 
@@ -296,7 +296,7 @@ pub ata: UncheckedAccount<'info>,
 
 
   View the full example with test:
-  [examples-light-token-anchor](https://github.com/Lightprotocol/examples-light-token-anchor/tree/main/program-examples/anchor/basic-macros/create-ata).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-ata).
 
 
 
@@ -311,7 +311,7 @@ pub ata: UncheckedAccount<'info>,
 {" "}
 
 
 
-`CreateMint` creates an on-chain mint account that can optionally include token metadata. 
-The instruction creates under the hood a compressed mint address for when the mint is inactive.
+`CreateMint` creates an on-chain mint account that can optionally include token metadata.
+The instruction also writes a compressed mint address to the address Merkle tree, which preserves the mint state when the on-chain account is compressed.
 
 Compare to SPL: 
 
@@ -122,7 +122,7 @@ Compare to SPL:
 
 
   Find the full example including shared test utilities in the
-  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests).
+  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client).
 
 
 
@@ -161,7 +161,7 @@ Compare to SPL:
   firstCode={lightCreateMintMetadataCpiCode}
   secondCode={splCreateMintMetadataCpiCode}
   firstLabel="light-token"
-  secondLabel="SPL Token-2022"
+  secondLabel="SPL Token 2022"
   language="rust"
 />
 
@@ -350,7 +350,7 @@ CreateMintCpi::new(
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-mint).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-mint).
 
 
 
@@ -379,7 +379,7 @@ Compare to SPL:
   firstCode={lightCreateMintMetadataMacroCode}
   secondCode={splCreateMintMetadataMacroCode}
   firstLabel="light-token"
-  secondLabel="SPL Token-2022"
+  secondLabel="SPL Token 2022"
   language="rust"
 />
 
@@ -481,7 +481,7 @@ pub mint: UncheckedAccount<'info>,
 
 
   View the full example with test:
-  [examples-light-token-anchor](https://github.com/Lightprotocol/examples-light-token-anchor/tree/main/program-examples/anchor/basic-macros/create-mint).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint).
 
 
 
@@ -495,7 +495,7 @@ pub mint: UncheckedAccount<'info>,
 # Next Steps
 
 
   Find the full example including shared test utilities in the
-  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests).
+  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client).
 
 
 
@@ -94,10 +94,7 @@ Find [a full code example at the end](#full-code-example).
 
 ### Build Account Infos and CPI the Light Token Program
 
-1. Pass token account fields and call `.rent_free()` with rent config accounts.
-2. Use `invoke` or `invoke_signed`:
-   - When `account` is an external keypair, use `invoke`.
-   - When `account` is a PDA, use `invoke_signed` with its seeds.
+Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
 
 
 
@@ -155,7 +152,7 @@ CreateTokenAccountCpi {
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/create-token-account).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-token-account).
 
 
 
@@ -205,9 +202,9 @@ use light_sdk_macros::light_program;
 pub mod light_token_macro_create_token_account {
     use super::*;
 
-    pub fn create_token_vault<'info>(
-        ctx: Context<'_, '_, '_, 'info, CreateTokenVault<'info>>,
-        params: CreateTokenVaultParams,
+    pub fn create_token_account<'info>(
+        ctx: Context<'_, '_, '_, 'info, CreateTokenAccount<'info>>,
+        params: CreateTokenAccountParams,
     ) -> Result<()> {
         Ok(())
     }
@@ -245,7 +242,7 @@ pub vault: UncheckedAccount<'info>,
 
 
   View the full example with test:
-  [examples-light-token-anchor](https://github.com/Lightprotocol/examples-light-token-anchor/tree/main/program-examples/anchor/basic-macros/create-token-account).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-token-account).
 
 
 
@@ -257,7 +254,7 @@ pub vault: UncheckedAccount<'info>,
 
 # Next Steps
   
 
 
-### Freeze or thaw light-token accounts
+### Freeze or thaw Light Token accounts
 
 
   Find the full example including shared test utilities in the
-  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests).
+  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client).
 
 
 
@@ -104,7 +104,7 @@ Find [a full code example at the end](#full-code-example).
 
 ### Build Account Infos and CPI the Light Token Program
 
-Define the light-token account to freeze, the mint it belongs to, and the freeze authority. Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
+Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
 
 
 
@@ -149,7 +149,7 @@ FreezeCpi {
 
 ### Build Account Infos and CPI the Light Token Program
 
-Define the frozen light-token account to thaw, the mint it belongs to, and the freeze authority. Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
+Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
 
 
 
@@ -196,7 +196,7 @@ ThawCpi {
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/freeze).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/freeze).
 
 
 
@@ -207,7 +207,7 @@ ThawCpi {
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/thaw).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/thaw).
 
 
 
diff --git a/light-token/cookbook/load-ata.mdx b/light-token/cookbook/load-ata.mdx
index 53db7165..0f20c3e7 100644
--- a/light-token/cookbook/load-ata.mdx
+++ b/light-token/cookbook/load-ata.mdx
@@ -1,7 +1,7 @@
 ---
 title: Load Token Balances to Light Associated Token Account
 sidebarTitle: Load Associated Token Account
-description: Unify token balances from compressed tokens (cold light-tokens), SPL, and Token-2022 to one light ATA.
+description: Unify token balances from compressed tokens (cold Light Tokens), SPL, and Token 2022 to one light ATA.
 keywords: ["load ata on solana", "get token balance for wallets"]
 ---
 
@@ -12,7 +12,7 @@ import ActionCode from "/snippets/code-snippets/light-token/load-ata/action.mdx"
 import InstructionCode from "/snippets/code-snippets/light-token/load-ata/instruction.mdx";
 
 1. `loadAta` unifies tokens from multiple sources to a single ATA:
-    * Compressed tokens (cold light-tokens) -> Decompresses -> light ATA
+    * Compressed tokens (cold Light Tokens) -> Decompresses -> light ATA
     * SPL balance (if wrap=true) -> Wraps -> light ATA
     * T22 balance (if wrap=true) -> Wraps -> light ATA
 
diff --git a/light-token/cookbook/mint-to.mdx b/light-token/cookbook/mint-to.mdx
index b6797edb..247b42b3 100644
--- a/light-token/cookbook/mint-to.mdx
+++ b/light-token/cookbook/mint-to.mdx
@@ -1,5 +1,6 @@
 ---
 title: Mint to Light Token Accounts
+sidebarTitle: Mint To
 description: Client and program guide to mint tokens to a account. Includes step-by-step implementation and full code examples.
 keywords: ["mint tokens on solana", "spl mint to for developers", "token minting for protocols"]
 ---
@@ -23,10 +24,10 @@ import InstructionCode from "/snippets/code-snippets/light-token/mint-to/instruc
 import RustActionCode from "/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx";
 import RustInstructionCode from "/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx";
 import AnchorProgramCode from "/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx";
-import AnchorMintToCheckedCode from "/snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx";
+
 
 1. Mint To creates new tokens of a mint and deposits them to a specified token account.
-2. Before we can mint any tokens, we need an initialized mint account (SPL, Token-2022 or Light) for which we hold the mint authority.
+2. Before we can mint any tokens, we need an initialized mint account (SPL, Token 2022 or Light) for which we hold the mint authority.
 
 
 
@@ -34,7 +35,7 @@ import AnchorMintToCheckedCode from "/snippets/code-snippets/light-token/mint-to
 
 `mintToInterface` mints tokens to token accounts in a single call.
 
-The function auto-detects the token program (SPL, Token-2022, or Light) from the mint address.
+The function auto-detects the token program (SPL, Token 2022, or Light) from the mint address.
 
 Compare to SPL:
 
@@ -72,7 +73,7 @@ Compare to SPL:
 
 
 
-Use `MintTo` to mint tokens to a light-token account.
+Use `MintTo` to mint tokens to a Light Token account.
 
 Compare to SPL:
 
@@ -97,7 +98,7 @@ Compare to SPL:
 
 
   Find the full example including shared test utilities in the
-  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests).
+  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client).
 
 
 
@@ -178,95 +179,18 @@ MintToCpi {
 
 
   View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/mint-to).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/mint-to).
 
 
 
 
-
-
-
-
-
-MintToChecked validates that the decimals parameter matches the mint's decimals.
-Find [a full code example at the end](#full-code-example-1).
-
-
-
-
-
-
-### Build Account Infos and CPI the Light Token Program
-
-Use `invoke` for external signers or `invoke_signed` when the authority is a PDA.
-
-
-
-
-```rust
-use light_token::instruction::MintToCheckedCpi;
-
-MintToCheckedCpi {
-    mint: mint.clone(),
-    destination: destination.clone(),
-    amount,
-    decimals,
-    authority: authority.clone(),
-    system_program: system_program.clone(),
-    fee_payer: None,
-    max_top_up: None,
-}
-.invoke()
-```
-
-
-
-
-```rust
-use light_token::instruction::MintToCheckedCpi;
-
-let signer_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[bump]];
-
-MintToCheckedCpi {
-    mint: mint.clone(),
-    destination: destination.clone(),
-    amount,
-    decimals,
-    authority: authority.clone(),
-    system_program: system_program.clone(),
-    fee_payer: None,
-    max_top_up: None,
-}
-.invoke_signed(&[signer_seeds])
-```
-
-
-
-
-
-  `fee_payer` and `max_top_up` are optional fields to customize rent top-ups.
-  Set to `None` to use defaults.
-
-
-
-
-
-# Full Code Example
-
-
-  View the full example with shared test utilities:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/mint-to-checked).
-
-
-
-
 
 
 
 # Next Steps
 
 
 
@@ -32,7 +32,7 @@ import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-chec
 
 
   Find the full example including shared test utilities in the
-  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests).
+  [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client).
 
 
 
@@ -110,7 +110,7 @@ TransferCheckedCpi {
 
 
   View the full example:
-  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/sdk-basics/transfer-checked).
+  [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/transfer-checked).
 
 
 
@@ -124,7 +124,7 @@ TransferCheckedCpi {
 {" "}
 
 
   
- + - + - + @@ -61,7 +61,7 @@ import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-inte -The `transferInterface` function transfers tokens between token accounts (SPL, Token-2022, or Light) in a single call. +The `transferInterface` function transfers tokens between token accounts (SPL, Token 2022, or Light) in a single call. Compare to SPL: @@ -100,7 +100,7 @@ Compare to SPL: -Use the unified `TransferInterface` to transfer tokens between token accounts (SPL, Token-2022, or Light) in a single call. +Use the unified `TransferInterface` to transfer tokens between token accounts (SPL, Token 2022, or Light) in a single call. light-token, -2. light-token -> light-token, and -3. light-token -> SPL token. +1. SPL token -> Light Token, +2. Light Token -> Light Token, and +3. Light Token -> SPL token. Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -155,7 +155,7 @@ Find [a full code example at the end](#full-code-example). ### Transfer Interface CPI -The `TransferInterfaceCpi` transfers tokens between token accounts (SPL, Token-2022, or light-token). +The `TransferInterfaceCpi` transfers tokens between token accounts (SPL, Token 2022, or Light Token). @@ -191,7 +191,7 @@ TransferInterfaceCpi::new( destination.clone(), authority.clone(), payer.clone(), - ctoken_authority.clone(), + light_token_authority.clone(), system_program.clone(), ) .invoke_signed(&[signer_seeds])?; @@ -207,7 +207,7 @@ TransferInterfaceCpi::new( View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples/anchor/programs/transfer-interface). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/transfer-interface). diff --git a/light-token/cookbook/wrap-unwrap.mdx b/light-token/cookbook/wrap-unwrap.mdx index 2158ae0d..0d5b3f98 100644 --- a/light-token/cookbook/wrap-unwrap.mdx +++ b/light-token/cookbook/wrap-unwrap.mdx @@ -1,7 +1,7 @@ --- -title: Wrap & Unwrap SPL/Token-22 <> Light Token +title: Wrap & Unwrap SPL/Token 2022 <> Light Token sidebarTitle: Wrap & Unwrap SPL -description: Move tokens between SPL/Token-22 token and light-token accounts. Use to interact with applications that only support SPL/Token-22. +description: Move tokens between SPL/Token 2022 token and Light Token accounts. Use to interact with applications that only support SPL/Token 2022. keywords: ["wrap tokens on solana", "unwrap tokens for developers", "spl to light token conversion"] --- @@ -16,8 +16,8 @@ import WrapRustActionCode from "/snippets/code-snippets/light-token/wrap/rust-cl import UnwrapRustActionCode from "/snippets/code-snippets/light-token/unwrap/rust-client/action.mdx"; import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; -- **Wrap**: Move tokens from SPL/T22 account → light-token ATA (hot balance) -- **Unwrap**: Move tokens from light-token ATA (hot balance) → SPL/T22 account +- **Wrap**: Move tokens from SPL/T22 account → Light Token ATA (hot balance) +- **Unwrap**: Move tokens from Light Token ATA (hot balance) → SPL/T22 account Find the source code: @@ -88,11 +88,11 @@ import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-c -### Wrap SPL tokens to light-token ATA +### Wrap SPL tokens to Light Token ATA Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -115,11 +115,11 @@ import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-c -### Unwrap light-tokens to SPL account +### Unwrap Light Tokens to SPL account Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client/tests). + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/snippets/code-samples/code-compare-snippets.jsx b/snippets/code-samples/code-compare-snippets.jsx index 04c7468e..3e9b4e27 100644 --- a/snippets/code-samples/code-compare-snippets.jsx +++ b/snippets/code-samples/code-compare-snippets.jsx @@ -691,7 +691,7 @@ export const lightCreateMintMetadataCpiCode = [ " update_authority: Some(authority.key.to_bytes().into()),", ' name: b"Example Token".to_vec(),', ' symbol: b"EXT".to_vec(),', - ' uri: b"https://example.com/token.json".to_vec(),', + ' uri: b"https://example.com/metadata.json".to_vec(),', " additional_metadata: None,", " },", " ),", diff --git a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx index a204a264..812571c7 100644 --- a/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx +++ b/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx @@ -330,7 +330,7 @@ async fn test_create_mint_with_metadata() { mint_signer_bump, name: b"Example Token".to_vec(), symbol: b"EXT".to_vec(), - uri: b"https://example.com/token.json".to_vec(), + uri: b"https://example.com/metadata.json".to_vec(), additional_metadata: None, }, }; diff --git a/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx index 6a15919f..85abf4af 100644 --- a/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx +++ b/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx @@ -18,14 +18,14 @@ pub const VAULT_AUTH_SEED: &[u8] = b"vault_auth"; pub const VAULT_SEED: &[u8] = b"vault"; #[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct CreateTokenVaultParams { +pub struct CreateTokenAccountParams { pub create_accounts_proof: CreateAccountsProof, pub vault_bump: u8, } #[derive(Accounts, LightAccounts)] -#[instruction(params: CreateTokenVaultParams)] -pub struct CreateTokenVault<'info> { +#[instruction(params: CreateTokenAccountParams)] +pub struct CreateTokenAccount<'info> { #[account(mut)] pub fee_payer: Signer<'info>, @@ -78,9 +78,9 @@ pub mod light_token_macro_create_token_account { use super::*; #[allow(unused_variables)] - pub fn create_token_vault<'info>( - ctx: Context<'_, '_, '_, 'info, CreateTokenVault<'info>>, - params: CreateTokenVaultParams, + pub fn create_token_account<'info>( + ctx: Context<'_, '_, '_, 'info, CreateTokenAccount<'info>>, + params: CreateTokenAccountParams, ) -> Result<()> { Ok(()) } @@ -164,10 +164,10 @@ async fn setup_create_mint( /// Creates a token vault via #[light_account(init, token, ...)]. #[tokio::test] -async fn test_create_token_vault() { +async fn test_create_token_account() { use light_token::instruction::{COMPRESSIBLE_CONFIG_V1, RENT_SPONSOR}; use light_token_macro_create_token_account::{ - CreateTokenVaultParams, VAULT_AUTH_SEED, VAULT_SEED, + CreateTokenAccountParams, VAULT_AUTH_SEED, VAULT_SEED, }; use light_token_types::CPI_AUTHORITY_PDA; @@ -192,7 +192,7 @@ async fn test_create_token_vault() { .await .unwrap(); - let accounts = light_token_macro_create_token_account::accounts::CreateTokenVault { + let accounts = light_token_macro_create_token_account::accounts::CreateTokenAccount { fee_payer: payer.pubkey(), mint, vault_authority, @@ -204,8 +204,8 @@ async fn test_create_token_vault() { system_program: solana_sdk::system_program::ID, }; - let instruction_data = light_token_macro_create_token_account::instruction::CreateTokenVault { - params: CreateTokenVaultParams { + let instruction_data = light_token_macro_create_token_account::instruction::CreateTokenAccount { + params: CreateTokenAccountParams { create_accounts_proof: proof_result.create_accounts_proof, vault_bump, }, From 5ffcf4662497966ba39c4244b72a8e5b488622d9 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 2 Feb 2026 14:00:38 +0000 Subject: [PATCH 14/21] rm program --- agent-skills/research-deepwiki/SKILL.md | 117 ----- docs.json | 3 +- light-token/cookbook/approve-revoke.mdx | 130 ----- light-token/cookbook/burn.mdx | 68 +-- light-token/cookbook/close-token-account.mdx | 68 --- light-token/cookbook/create-ata.mdx | 184 ------- light-token/cookbook/create-mint.mdx | 354 ------------- light-token/cookbook/create-token-account.mdx | 182 ------- light-token/cookbook/freeze-thaw.mdx | 129 ----- light-token/cookbook/mint-to.mdx | 72 --- light-token/cookbook/transfer-checked.mdx | 71 --- light-token/cookbook/transfer-interface.mdx | 71 --- light-token/examples/program.mdx | 11 - scripts/copy-program-snippets.sh | 214 -------- .../approve/anchor-program/full-example.mdx | 83 --- .../approve/native-program/full-example.mdx | 137 ----- .../burn/anchor-program/full-example.mdx | 84 --- .../burn/native-program/full-example.mdx | 170 ------ .../anchor-program/full-example.mdx | 83 --- .../native-program/full-example.mdx | 116 ----- .../anchor-program/full-example.mdx | 173 ------- .../native-program/full-example.mdx | 172 ------- .../anchor-program/full-example.mdx | 230 --------- .../native-program/full-example.mdx | 483 ------------------ .../anchor-program/full-example.mdx | 161 ------ .../native-program/full-example.mdx | 170 ------ .../freeze/anchor-program/full-example.mdx | 75 --- .../freeze/native-program/full-example.mdx | 118 ----- .../anchor-program/full-example.mdx | 88 ---- .../native-program/full-example.mdx | 145 ------ .../mint-to/anchor-program/full-example.mdx | 81 --- .../mint-to/native-program/full-example.mdx | 187 ------- .../revoke/anchor-program/full-example.mdx | 91 ---- .../revoke/native-program/full-example.mdx | 107 ---- .../thaw/anchor-program/full-example.mdx | 89 ---- .../thaw/native-program/full-example.mdx | 111 ---- .../anchor-program/full-example.mdx | 103 ---- .../native-program/full-example.mdx | 180 ------- .../anchor-program/full-example.mdx | 107 ---- .../native-program/full-example.mdx | 185 ------- 40 files changed, 2 insertions(+), 5401 deletions(-) delete mode 100644 agent-skills/research-deepwiki/SKILL.md delete mode 100644 light-token/examples/program.mdx delete mode 100755 scripts/copy-program-snippets.sh delete mode 100644 snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/approve/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/burn/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/freeze/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/revoke/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/thaw/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx delete mode 100644 snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx diff --git a/agent-skills/research-deepwiki/SKILL.md b/agent-skills/research-deepwiki/SKILL.md deleted file mode 100644 index b04c038e..00000000 --- a/agent-skills/research-deepwiki/SKILL.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -name: research-deepwiki -description: Query Light Protocol and related repositories via DeepWiki MCP. Use when answering questions about compressed accounts, Light SDK, Solana development, Claude Code features, or agent skills. Triggers on technical questions requiring repository context. ---- - -# DeepWiki Research - -Query repositories via DeepWiki MCP to answer technical questions with precise, source-backed answers. - -## Execution Steps - -### 1. Initialize Context: Understand Current Repository - -### 2. Identify Question Scope - -Determine the domain: -- Programs, client SDKs, architecture, implementation details -- Specific components (LightAccount, ValidityProof, CPI, etc.) - -### 3. Fetch Repository Context - -Select the appropriate repository based on question scope: - -**Light Protocol (compressed accounts, state trees, ZK compression, Light SDK)** -``` -mcp__deepwiki__read_wiki_structure("Lightprotocol/light-protocol") -mcp__deepwiki__read_wiki_contents("Lightprotocol/light-protocol") -mcp__deepwiki__ask_question("Lightprotocol/light-protocol", "your question") -``` - -**Solana Development (programs, accounts, general Solana)** -``` -mcp__deepwiki__read_wiki_structure("blueshift-gg/blueshift-dashboard") -mcp__deepwiki__read_wiki_contents("blueshift-gg/blueshift-dashboard") -mcp__deepwiki__ask_question("blueshift-gg/blueshift-dashboard", "your question") -``` - -**Claude Code (CLI features, commands, configuration)** -``` -mcp__deepwiki__read_wiki_structure("anthropics/claude-code") -mcp__deepwiki__read_wiki_contents("anthropics/claude-code") -mcp__deepwiki__ask_question("anthropics/claude-code", "your question") -``` - -**Agent Skills (skills, subagents, patterns)** -``` -mcp__deepwiki__read_wiki_structure("anthropics/skills") -mcp__deepwiki__read_wiki_contents("anthropics/skills") -mcp__deepwiki__ask_question("anthropics/skills", "your question") -``` - -### 4. Apply Precision Rules - -**AVOID:** -- Vague verbs: "handles", "manages", "processes", "enables", "provides" -- Abstract nouns: "operations", "management", "coordination" -- -ing endings when a precise verb exists -- Marketing language: "comprehensive", "rich", "seamless" - -**USE:** -- Exact function/method names -- Concrete data structures -- Specific operations: "sets discriminator", "creates hash", "nullifies account" -- File:line references - -### 5. Format Response - -Structure answers with: -- Technical precision -- Code examples with inline comments -- Source references (file paths, line numbers) - -## Example Output - -**Question:** What do you include in instruction data for compressed account creation? - -**Answer:** - ---- - -## Compressed Account Creation Instruction Data - -Instruction data contains three components for proof verification and tree coordination. - -**Components:** - -```rust -pub struct InstructionData { - proof: ValidityProof, - address_tree_info: PackedAddressTreeInfo, - output_state_tree_index: u8, - // + custom account data -} -``` - -**1. Non-inclusion proof** -- `ValidityProof` proves the derived address does not exist in the specified address tree -- Client generates via `getValidityProof()` RPC call - -**2. Tree location parameters** -- `PackedAddressTreeInfo`: Specifies which address tree registers the address (u8 index, not pubkey) -- `output_state_tree_index`: Specifies which state tree stores the compressed account hash - -**3. Custom account data** -- Program-specific fields (e.g., `message: String`, user data) - -**Execution flow:** - -1. Address tree proves uniqueness, stores address -2. State tree stores account hash -3. ValidityProof cryptographically guarantees address is unused - -**Why separate trees:** - -Compressed accounts require client-generated cryptographic proof that address doesn't exist (unlike regular Solana where runtime checks PDA existence). Address trees enforce uniqueness; state trees store account hashes. - -**Packed structs** use `u8` indices to reference accounts in `remaining_accounts`, reducing transaction size. diff --git a/docs.json b/docs.json index fdb20d0b..68dd3cd2 100644 --- a/docs.json +++ b/docs.json @@ -68,8 +68,7 @@ "group": "Examples", "expanded": true, "pages": [ - "light-token/examples/client", - "light-token/examples/program" + "light-token/examples/client" ] }, { diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx index dcc61985..25c2a973 100644 --- a/light-token/cookbook/approve-revoke.mdx +++ b/light-token/cookbook/approve-revoke.mdx @@ -161,136 +161,6 @@ import RevokeAnchorProgramCode from "/snippets/code-snippets/light-token/revoke/ - - - -Find [a full code example at the end](#full-code-example). - - - - - - - -### Build Account Infos and CPI the Light Token Program - -Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. - - - - -```rust -use light_token::instruction::ApproveCpi; - -ApproveCpi { - token_account: token_account.clone(), - delegate: delegate.clone(), - owner: owner.clone(), - system_program: system_program.clone(), - amount, -} -.invoke()?; -``` - - - - -```rust -use light_token::instruction::ApproveCpi; - -let approve_cpi = ApproveCpi { - token_account: token_account.clone(), - delegate: delegate.clone(), - owner: owner.clone(), - system_program: system_program.clone(), - amount, -}; - -let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]]; -approve_cpi.invoke_signed(&[signer_seeds])?; -``` - - - - - - - - - - - - -### Build Account Infos and CPI the Light Token Program - -Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. - - - - -```rust -use light_token::instruction::RevokeCpi; - -RevokeCpi { - token_account: token_account.clone(), - owner: owner.clone(), - system_program: system_program.clone(), -} -.invoke()?; -``` - - - - -```rust -use light_token::instruction::RevokeCpi; - -let revoke_cpi = RevokeCpi { - token_account: token_account.clone(), - owner: owner.clone(), - system_program: system_program.clone(), -}; - -let signer_seeds: &[&[u8]] = &[TOKEN_ACCOUNT_SEED, &[bump]]; -revoke_cpi.invoke_signed(&[signer_seeds])?; -``` - - - - - - - - - -# Full Code Example - - - - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/approve). - - - - - - - - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/revoke). - - - - - - - - - # Next Steps diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx index ead7fba3..30dd1102 100644 --- a/light-token/cookbook/burn.mdx +++ b/light-token/cookbook/burn.mdx @@ -15,7 +15,7 @@ import { } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustInstructionCode from "/snippets/code-snippets/light-token/burn/rust-client/instruction.mdx"; import BurnCheckedRustInstructionCode from "/snippets/code-snippets/light-token/burn-checked/rust-client/instruction.mdx"; -import AnchorProgramCode from "/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx"; + 1. Burn permanently destroys tokens by reducing the balance in a token account. @@ -60,72 +60,6 @@ Compare to SPL: - - - -Find [a full code example at the end](#full-code-example). - - - - - -### Build Account Infos and CPI the Light Token Program - -Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. - - - - -```rust -use light_token::instruction::BurnCpi; - -BurnCpi { - source: source.clone(), - mint: mint.clone(), - amount, - authority: authority.clone(), - system_program: system_program.clone(), - fee_payer: None, - max_top_up: None, -} -.invoke() -``` - - - - -```rust -use light_token::instruction::BurnCpi; - -BurnCpi { - source: source.clone(), - mint: mint.clone(), - amount, - authority: authority.clone(), - system_program: system_program.clone(), - fee_payer: None, - max_top_up: None, -} -.invoke_signed(&[signer_seeds]) -``` - - - - - - - -# Full Code Example - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/burn). - - - - - - # Next Steps diff --git a/light-token/cookbook/close-token-account.mdx b/light-token/cookbook/close-token-account.mdx index c5840fdf..ea0f9078 100644 --- a/light-token/cookbook/close-token-account.mdx +++ b/light-token/cookbook/close-token-account.mdx @@ -7,7 +7,6 @@ keywords: ["close token account on solana", "reclaim rent on solana"] --- -import CloseAccountInfosAccountsList from "/snippets/accounts-list/close-account-infos-accounts-list.mdx"; import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; import ClientCustomRentConfig from "/snippets/light-token-guides/client-custom-rent-config.mdx"; import { CodeCompare } from "/snippets/jsx/code-compare.jsx"; @@ -16,8 +15,6 @@ import { lightCloseAccountRustCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustInstructionCode from "/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx"; -import AnchorProgramCode from "/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx"; - 1. Closing a Light Token account transfers remaining lamports to a destination account and the rent sponsor can reclaim sponsored rent. 2. Light token accounts can be closed by the owner. @@ -68,71 +65,6 @@ Compare to SPL: - - - -Find [a full code example at the end](#full-code-example). - - - - -### Build Account Infos and CPI the Light Token Program - -Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. - - - - -```rust -use light_token::instruction::CloseAccountCpi; - -CloseAccountCpi { - token_program: token_program.clone(), - account: account.clone(), - destination: destination.clone(), - owner: owner.clone(), - rent_sponsor: rent_sponsor.clone(), -} -.invoke()?; -``` - - - - -```rust -use light_token::instruction::CloseAccountCpi; - -CloseAccountCpi { - token_program: token_program.clone(), - account: account.clone(), - destination: destination.clone(), - owner: owner.clone(), - rent_sponsor: rent_sponsor.clone(), -} -.invoke_signed(&[signer_seeds])?; -``` - - - - - - - - - - - -# Full Code Example - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/close). - - - - - - # Next Steps diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index d7785b51..58e0821c 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -27,9 +27,6 @@ import ActionCode from "/snippets/code-snippets/light-token/create-ata/action.md import InstructionCode from "/snippets/code-snippets/light-token/create-ata/instruction.mdx"; import RustActionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx"; -import AnchorProgramCode from "/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx"; -import AnchorMacroCode from "/snippets/code-snippets/light-token/create-ata/anchor-macro/full-example.mdx"; - 1. Associated Light Token accounts can hold token balances of light, SPL, or Token 2022 mints. 2. Light-ATAs are on-chain accounts like SPL ATA's, but the light token program sponsors the rent-exemption cost for you. @@ -122,187 +119,6 @@ Compare to SPL: - - - - - -Compare to SPL: - - - - -Find [a full code example at the end](#full-code-example). - - - - - -### Build Account Infos and CPI the Light Token Program - -1. Pass ATA accounts and call `.rent_free()` with rent config accounts. -2. Use `invoke` or `invoke_signed`: - - When the `payer` is an external wallet, use `invoke`. - - When the `payer` is a PDA, use `invoke_signed` with its seeds. - - - The light-ATA address is derived from `[owner, light_token_program_id, mint]`. - Unlike Light Token accounts, owner and mint are passed as accounts, not in - instruction data. - - - - - -```rust -use light_token::instruction::CreateAssociatedAccountCpi; - -CreateAssociatedAccountCpi { - payer: payer.clone(), - owner: owner.clone(), - mint: mint.clone(), - ata: associated_token_account.clone(), - bump, -} -.rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), -) -.invoke() -``` - - - - -```rust -use light_token::instruction::CreateAssociatedAccountCpi; - -let signer_seeds: &[&[u8]] = &[ATA_SEED, &[authority_bump]]; - -CreateAssociatedAccountCpi { - payer: payer.clone(), - owner: owner.clone(), - mint: mint.clone(), - ata: associated_token_account.clone(), - bump, -} -.rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), -) -.invoke_signed(&[signer_seeds]) -``` - - - - - - - -# Full Code Example - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-ata). - - - - - - - -Compare to SPL: - - - - -Find [a full code example at the end](#full-code-example-1). - - - - - -### Dependencies - -```toml -[dependencies] -light-sdk = { version = "0.18.0", features = ["anchor", "v2", "cpi-context"] } -light-sdk-macros = "0.18.0" -light-compressible = "0.1.0" -anchor-lang = "0.31" -``` - - - - -### Program - -Add `#[light_program]` above `#[program]`: - -```rust -use light_sdk_macros::light_program; - -#[light_program] -#[program] -pub mod light_token_macro_create_ata { - use super::*; - - pub fn create_ata<'info>( - ctx: Context<'_, '_, '_, 'info, CreateAta<'info>>, - params: CreateAtaParams, - ) -> Result<()> { - Ok(()) - } -} -``` - - - - -### Accounts struct - -Derive `LightAccounts` on your `Accounts` struct and add `#[light_account(...)]` next to `#[account(...)]`. - -```rust -/// CHECK: Validated by light-token CPI -#[account(mut)] -#[light_account(init, - associated_token::authority = ata_owner, - associated_token::mint = ata_mint, - associated_token::bump = params.ata_bump -)] -pub ata: UncheckedAccount<'info>, -``` - - - - - -# Full code example - - - View the full example with test: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-ata). - - - - - - diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx index e2024afd..9aeae213 100644 --- a/light-token/cookbook/create-mint.mdx +++ b/light-token/cookbook/create-mint.mdx @@ -31,8 +31,6 @@ import ActionCode from "/snippets/code-snippets/light-token/create-mint/action.m import InstructionCode from "/snippets/code-snippets/light-token/create-mint/instruction.mdx"; import RustActionCode from "/snippets/code-snippets/light-token/create-mint/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-mint/rust-client/instruction.mdx"; -import AnchorProgramCode from "/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx"; -import AnchorMacroCode from "/snippets/code-snippets/light-token/create-mint/anchor-macro/full-example.mdx"; import CompressibleRentExplained from "/snippets/compressible-rent-explained.mdx"; 1. Mint accounts uniquely represent a token on Solana and store its global metadata. @@ -138,358 +136,6 @@ Compare to SPL: - - - - - - -Compare to SPL: - - - - - - - - - - - -Find [a full code example at the end](#full-code-example). - - - - -### Configure Token Metadata - -```rust -use light_token_interface::instructions::extensions::{ - token_metadata::TokenMetadataInstructionData, ExtensionInstructionData, -}; - -let extensions = Some(vec![ExtensionInstructionData::TokenMetadata( - TokenMetadataInstructionData { - update_authority: Some(authority.key.to_bytes().into()), - name: b"Example Token".to_vec(), - symbol: b"EXT".to_vec(), - uri: b"https://example.com/metadata.json".to_vec(), - additional_metadata: None, - }, -)]); -``` - - - **Fields must be set at light-mint creation.** Standard fields (`name`, - `symbol`, `uri`) can be updated by `update_authority`. For - `additional_metadata`, only existing keys can be modified or removed. New keys - cannot be added after creation. - - - - - -### Configure Mint - -Configure mint parameters including `decimals`, authorities, rent settings, and pass `extensions` from the previous step. - -```rust -use light_token::instruction::CreateMintParams; - -let params = CreateMintParams { - decimals, - address_merkle_tree_root_index, - mint_authority: *ctx.accounts.authority.key, - proof, - compression_address, - mint, - bump, - freeze_authority, - extensions, - rent_payment: rent_payment.unwrap_or(16), // ~24 hours rent - write_top_up: write_top_up.unwrap_or(766), // ~3 hours rent -}; -``` - - - The address of the mint account is stored in an address Merkle tree - , which is maintained by the protocol. - The client passes a validity proof that proves the mint address does not - exist yet. - - - - - -### System Accounts - -Include system accounts such as the Light System Program - to verify the proof and write the mint address to the address tree. - - - - - -```rust -use light_token::instruction::SystemAccountInfos; - -let system_accounts = SystemAccountInfos { - light_system_program: ctx.accounts.light_system_program.to_account_info(), - cpi_authority_pda: ctx.accounts.cpi_authority_pda.to_account_info(), - registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(), - account_compression_authority: ctx.accounts.account_compression_authority.to_account_info(), - account_compression_program: ctx.accounts.account_compression_program.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), -}; -``` - - - - -### Build Account Infos and CPI the Light Token Program - -Use `invoke` or `invoke_signed`: - - When `mint_seed` is an external keypair, use `invoke`. - - When `mint_seed` is a PDA, use `invoke_signed` with its seeds. - - When both `mint_seed` and `authority` are PDAs, use `invoke_signed` with both seeds. - - - - -```rust -use light_token::instruction::CreateMintCpi; - -CreateMintCpi::new( - mint_seed.clone(), - authority.clone(), - payer.clone(), - address_tree.clone(), // stores address - output_queue.clone(), // stores account when inactive - compressible_config.clone(), // rent settings - mint.clone(), - rent_sponsor.clone(), - system_accounts, - params, -) -.invoke() -``` - - - - - -```rust -use light_token::instruction::CreateMintCpi; - -let signer_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[bump]]; - -CreateMintCpi::new( - mint_seed.clone(), - authority.clone(), - payer.clone(), - address_tree.clone(), - output_queue.clone(), - compressible_config.clone(), - mint.clone(), - rent_sponsor.clone(), - system_accounts, - params, -) -.invoke_signed(&[signer_seeds]) -``` - - - - - -```rust -use light_token::instruction::CreateMintCpi; - -let mint_seed_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[mint_seed_bump]]; -let authority_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[authority_bump]]; - -CreateMintCpi::new( - mint_seed.clone(), - authority.clone(), - payer.clone(), - address_tree.clone(), - output_queue.clone(), - compressible_config.clone(), - mint.clone(), - rent_sponsor.clone(), - system_accounts, - params, -) -.invoke_signed(&[mint_seed_seeds, authority_seeds]) -``` - - - - - - - - - -# Full Code Example - - - - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-mint). - - - - - - - - - - - -Compare to SPL: - - - - - - - - - - - -Find [a full code example at the end](#full-code-example). - - - - - -### Dependencies - -```toml -[dependencies] -light-sdk = { version = "0.18.0", features = ["anchor", "v2", "cpi-context"] } -light-sdk-macros = "0.18.0" -light-compressible = "0.1.0" -anchor-lang = "0.31" -``` - - - - -### Program - -Add `#[light_program]` above `#[program]`: - -```rust -use light_sdk_macros::light_program; - -#[light_program] -#[program] -pub mod my_program { - use super::*; - - pub fn create_mint<'info>( - ctx: Context<'_, '_, '_, 'info, CreateMint<'info>>, - params: CreateMintParams, - ) -> Result<()> { - Ok(()) - } -} -``` - - - - -### Accounts struct - -Derive `LightAccounts` on your `Accounts` struct and add `#[light_account(...)]` next to `#[account(...)]`. - - - - -```rust -/// CHECK: Validated by light-token CPI -#[account(mut)] -#[light_account(init, - mint::signer = mint_signer, - mint::authority = fee_payer, - mint::decimals = 9, - mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()], - mint::bump = params.mint_signer_bump -)] -pub mint: UncheckedAccount<'info>, -``` - - - - -```rust -/// CHECK: Validated by light-token CPI -#[account(mut)] -#[light_account(init, - mint::signer = mint_signer, - mint::authority = fee_payer, - mint::decimals = 9, - mint::seeds = &[MINT_SIGNER_SEED, self.authority.to_account_info().key.as_ref()], - mint::bump = params.mint_signer_bump, - mint::name = params.name.clone(), - mint::symbol = params.symbol.clone(), - mint::uri = params.uri.clone(), - mint::update_authority = authority, - mint::additional_metadata = params.additional_metadata.clone() -)] -pub mint: UncheckedAccount<'info>, -``` - - - - - - - - -# Full code example - - - View the full example with test: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint). - - - - - - - - # Next Steps diff --git a/light-token/cookbook/create-token-account.mdx b/light-token/cookbook/create-token-account.mdx index 845a5103..17652f0f 100644 --- a/light-token/cookbook/create-token-account.mdx +++ b/light-token/cookbook/create-token-account.mdx @@ -20,9 +20,6 @@ import { lightCreateTokenAccountCpiCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx"; -import AnchorProgramCode from "/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx"; -import AnchorMacroCode from "/snippets/code-snippets/light-token/create-token-account/anchor-macro/full-example.mdx"; - 1. Light token accounts are Solana accounts that hold token balances of light, SPL, or Token 2022 mints. 2. Light token accounts are on-chain accounts like SPL ATA’s, but the light token program sponsors the rent-exemption cost for you. @@ -72,185 +69,6 @@ Compare to SPL: - - - - -Compare to SPL: - - - - -Find [a full code example at the end](#full-code-example). - - - - -### Build Account Infos and CPI the Light Token Program - -Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. - - - - -```rust -use light_token::instruction::CreateTokenAccountCpi; - -CreateTokenAccountCpi { - payer: payer.clone(), - account: account.clone(), - mint: mint.clone(), - owner, -} -.rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), - token_program.key, // light token program -) -.invoke() -``` - - - - - - -```rust -use light_token::instruction::CreateTokenAccountCpi; - -let signer_seeds = authority_seeds!(authority_bump); - -CreateTokenAccountCpi { - payer: payer.clone(), - account: account.clone(), - mint: mint.clone(), - owner, -} -.rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), - program_id, -) -.invoke_signed(signer_seeds) -``` - - - - - - - -# Full Code Example - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/create-token-account). - - - - - - - -Compare to SPL: - - - - -Find [a full code example at the end](#full-code-example-1). - - - - - -### Dependencies - -```toml -[dependencies] -light-sdk = { version = "0.18.0", features = ["anchor", "v2", "cpi-context"] } -light-sdk-macros = "0.18.0" -light-compressible = "0.1.0" -anchor-lang = "0.31" -``` - - - - -### Program - -Add `#[light_program]` above `#[program]`: - -```rust -use light_sdk_macros::light_program; - -#[light_program] -#[program] -pub mod light_token_macro_create_token_account { - use super::*; - - pub fn create_token_account<'info>( - ctx: Context<'_, '_, '_, 'info, CreateTokenAccount<'info>>, - params: CreateTokenAccountParams, - ) -> Result<()> { - Ok(()) - } -} -``` - - - - -### Accounts struct - -Derive `LightAccounts` on your `Accounts` struct and add `#[light_account(...)]` next to `#[account(...)]`. - -```rust -/// CHECK: Validated by light-token CPI -#[account( - mut, - seeds = [VAULT_SEED, mint.key().as_ref()], - bump, -)] -#[light_account(init, - token::authority = [VAULT_SEED, self.mint.key()], - token::mint = mint, - token::owner = vault_authority, - token::bump = params.vault_bump -)] -pub vault: UncheckedAccount<'info>, -``` - - - - - -# Full code example - - - View the full example with test: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-token-account). - - - - - - - - # Next Steps - - - -Find [a full code example at the end](#full-code-example). - - - - - - - -### Build Account Infos and CPI the Light Token Program - -Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. - - - - -```rust -use light_token::instruction::FreezeCpi; - -FreezeCpi { - token_account: ctx.accounts.token_account.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - freeze_authority: ctx.accounts.freeze_authority.to_account_info(), -} -.invoke()?; -``` - - - - -```rust -use light_token::instruction::FreezeCpi; - -let signer_seeds = authority_seeds!(bump); - -FreezeCpi { - token_account: token_account.clone(), - mint: mint.clone(), - freeze_authority: freeze_authority.clone(), -} -.invoke_signed(&[signer_seeds]) -``` - - - - - - - - - - - - -### Build Account Infos and CPI the Light Token Program - -Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. - - - - -```rust -use light_token::instruction::ThawCpi; - -ThawCpi { - token_account: ctx.accounts.token_account.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - freeze_authority: ctx.accounts.freeze_authority.to_account_info(), -} -.invoke()?; -``` - - - - -```rust -use light_token::instruction::ThawCpi; - -let signer_seeds = authority_seeds!(bump); - -ThawCpi { - token_account: token_account.clone(), - mint: mint.clone(), - freeze_authority: freeze_authority.clone(), -} -.invoke_signed(&[signer_seeds]) -``` - - - - - - - - - -# Full Code Example - - - - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/freeze). - - - - - - - - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/thaw). - - - - - - - - - # Next Steps diff --git a/light-token/cookbook/mint-to.mdx b/light-token/cookbook/mint-to.mdx index 247b42b3..ec8c022f 100644 --- a/light-token/cookbook/mint-to.mdx +++ b/light-token/cookbook/mint-to.mdx @@ -23,7 +23,6 @@ import ActionCode from "/snippets/code-snippets/light-token/mint-to/action.mdx"; import InstructionCode from "/snippets/code-snippets/light-token/mint-to/instruction.mdx"; import RustActionCode from "/snippets/code-snippets/light-token/mint-to/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/mint-to/rust-client/instruction.mdx"; -import AnchorProgramCode from "/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx"; 1. Mint To creates new tokens of a mint and deposits them to a specified token account. @@ -114,77 +113,6 @@ Compare to SPL: - - -Find [a full code example at the end](#full-code-example). - - - - - -### Build Account Infos and CPI the Light Token Program - -Use `invoke` for external signers or `invoke_signed` when the authority is a PDA. - - - - -```rust -use light_token::instruction::MintToCpi; - -MintToCpi { - mint: mint.clone(), - destination: destination.clone(), - amount, - authority: authority.clone(), - system_program: system_program.clone(), - fee_payer: None, - max_top_up: None, -} -.invoke() -``` - - - - -```rust -use light_token::instruction::MintToCpi; - -let signer_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[bump]]; - -MintToCpi { - mint: mint.clone(), - destination: destination.clone(), - amount, - authority: authority.clone(), - system_program: system_program.clone(), - fee_payer: None, - max_top_up: None, -} -.invoke_signed(&[signer_seeds]) -``` - - - - - - `fee_payer` and `max_top_up` are optional fields to customize rent top-ups. - Set to `None` to use defaults. - - - - - -# Full Code Example - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/mint-to). - - - - - # Next Steps diff --git a/light-token/cookbook/transfer-checked.mdx b/light-token/cookbook/transfer-checked.mdx index aa74c65b..d28c932f 100644 --- a/light-token/cookbook/transfer-checked.mdx +++ b/light-token/cookbook/transfer-checked.mdx @@ -10,8 +10,6 @@ keywords: ["transfer tokens solana", "checked transfer", "decimal validation"] import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; import RustActionCode from "/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx"; -import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx"; - 1. TransferChecked validates that the decimals parameter matches the mint's decimals. 2. Use for Light→Light transfers when you need decimal verification. 3. For transfers involving SPL or Token 2022 accounts, use [Transfer Interface](/light-token/cookbook/transfer-interface) instead. @@ -48,75 +46,6 @@ import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-chec - - - -Find [a full code example at the end](#full-code-example). - - - - -### Transfer Checked with CPI - - - - -```rust -use light_token::instruction::TransferCheckedCpi; - -TransferCheckedCpi { - source: source.clone(), - mint: mint.clone(), - destination: destination.clone(), - amount, - decimals, - authority: authority.clone(), - system_program: system_program.clone(), - max_top_up: None, - fee_payer: None, -} -.invoke()?; -``` - - - - -```rust -use light_token::instruction::TransferCheckedCpi; - -let signer_seeds = authority_seeds!(bump); - -TransferCheckedCpi { - source: source.clone(), - mint: mint.clone(), - destination: destination.clone(), - amount, - decimals, - authority: authority.clone(), - system_program: system_program.clone(), - max_top_up: None, - fee_payer: None, -} -.invoke_signed(&[signer_seeds])?; -``` - - - - - - - -# Full Code Example - - - View the full example: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/transfer-checked). - - - - - - # Next Steps diff --git a/light-token/cookbook/transfer-interface.mdx b/light-token/cookbook/transfer-interface.mdx index d22c56ba..98dfe9a0 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -22,8 +22,6 @@ import ActionCode from "/snippets/code-snippets/light-token/transfer-interface/a import InstructionCode from "/snippets/code-snippets/light-token/transfer-interface/instruction.mdx"; import RustActionCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/instruction.mdx"; -import AnchorProgramCode from "/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx"; -
**light-token -> light-token Account****Light Token -> Light Token Account**
    -
  • Transfers tokens between light-token accounts
  • +
  • Transfers tokens between Light Token accounts
**SPL token -> light-token Account****SPL token -> Light Token Account**
    -
  • Transfers SPL tokens to light-token accounts
  • +
  • Transfers SPL tokens to Light Token accounts
  • SPL tokens are locked in interface PDA
  • -
  • Tokens are minted to light-token account
  • +
  • Tokens are minted to Light Token account
**light-token -> SPL Account****Light Token -> SPL Account**
  • Releases SPL tokens from interface PDA to SPL account
  • -
  • Burns tokens in source light-token account
  • +
  • Burns tokens in source Light Token account
@@ -145,75 +143,6 @@ The example transfers - - - -Find [a full code example at the end](#full-code-example). - - - - -### Transfer Interface CPI - -The `TransferInterfaceCpi` transfers tokens between token accounts (SPL, Token 2022, or Light Token). - - - - -```rust -use light_token::instruction::TransferInterfaceCpi; - -TransferInterfaceCpi::new( - amount, - decimals, - source.clone(), - destination.clone(), - authority.clone(), - payer.clone(), - light_token_authority.clone(), - system_program.clone(), -) -.invoke()?; -``` - - - - -```rust -use light_token::instruction::TransferInterfaceCpi; - -let signer_seeds = authority_seeds!(bump); - -TransferInterfaceCpi::new( - amount, - decimals, - source.clone(), - destination.clone(), - authority.clone(), - payer.clone(), - light_token_authority.clone(), - system_program.clone(), -) -.invoke_signed(&[signer_seeds])?; -``` - - - - - - - -# Full Code Example - - - View the full example with shared test utilities: - [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-instructions/transfer-interface). - - - - - - # Next Steps diff --git a/light-token/examples/program.mdx b/light-token/examples/program.mdx deleted file mode 100644 index d447d23c..00000000 --- a/light-token/examples/program.mdx +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: "Program examples" -sidebarTitle: "Program" -description: "Anchor program examples for light-token CPI." ---- - -import ProgramExamplesTable from "/snippets/overview-tables/light-token-program-examples-table.mdx"; - -Find all examples on Github: [examples-light-token](https://github.com/Lightprotocol/examples-light-token) - - diff --git a/scripts/copy-program-snippets.sh b/scripts/copy-program-snippets.sh deleted file mode 100755 index 27af17e5..00000000 --- a/scripts/copy-program-snippets.sh +++ /dev/null @@ -1,214 +0,0 @@ -#!/bin/bash - -# Script to copy program code from example repos to docs snippets -# Creates CodeGroup MDX files with lib.rs/instruction.rs and test.rs combined - -SNIPPETS_DIR="/home/tilo/Workspace/docs/snippets/code-snippets/light-token" - -# ============================================================================= -# NATIVE PROGRAMS -# ============================================================================= - -NATIVE_EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token-native/program-examples/native-rust" - -# Recipes to process (output-name:source_name:test_name) -# test_name is optional, defaults to source_name if not provided -NATIVE_RECIPES=( - "create-mint:create_mint" - "mint-to:mint_to" - "mint-to-checked:mint_to_checked" - "create-ata:create_ata" - "create-token-account:create_token_account" - "close-token-account:close" - "transfer-interface:transfer_interface:transfer" - "transfer-checked:transfer_checked" - "approve:approve" - "revoke:revoke" - "burn:burn" - "freeze:freeze" - "thaw:thaw" -) - -echo "=== Processing Native program files ===" -echo "" - -for mapping in "${NATIVE_RECIPES[@]}"; do - # Parse the mapping (output:source:test) - IFS=':' read -r output_name source_name test_name <<< "$mapping" - # Default test_name to source_name if not provided - test_name="${test_name:-$source_name}" - - echo "Processing: $output_name (source: $source_name, test: $test_name)" - - output_dir="$SNIPPETS_DIR/$output_name/native-program" - mkdir -p "$output_dir" - - instruction_file="$NATIVE_EXAMPLES_DIR/program/src/instructions/$source_name.rs" - test_file="$NATIVE_EXAMPLES_DIR/program/tests/$test_name.rs" - - # Check source files exist - if [ ! -f "$instruction_file" ]; then - echo " WARNING: Not found - $instruction_file" - continue - fi - - if [ ! -f "$test_file" ]; then - echo " WARNING: Not found - $test_file" - continue - fi - - # Create CodeGroup MDX with both files - output_file="$output_dir/full-example.mdx" - - { - echo '' - echo '```rust instruction.rs' - cat "$instruction_file" - echo '```' - echo '' - echo '```rust test.rs' - cat "$test_file" - echo '```' - echo '' - } > "$output_file" - - echo " Created: $output_file" -done - -# ============================================================================= -# ANCHOR PROGRAMS -# ============================================================================= - -ANCHOR_EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token-anchor/program-examples/anchor/basic-instructions" - -# Anchor recipes (output-name:anchor-dir-name) -# Some have different directory names (e.g., close-token-account uses 'close' dir) -ANCHOR_RECIPES=( - "create-mint:create-mint" - "mint-to:mint-to" - "create-ata:create-ata" - "create-token-account:create-token-account" - "close-token-account:close" - "transfer-interface:transfer-interface" - "approve:approve" - "revoke:revoke" - "burn:burn" - "freeze:freeze" - "thaw:thaw" - "transfer-checked:transfer-checked" - "mint-to-checked:mint-to-checked" -) - -echo "" -echo "=== Processing Anchor program files ===" -echo "" - -for mapping in "${ANCHOR_RECIPES[@]}"; do - IFS=':' read -r output_name anchor_dir <<< "$mapping" - - echo "Processing: $output_name (dir: $anchor_dir)" - - output_dir="$SNIPPETS_DIR/$output_name/anchor-program" - mkdir -p "$output_dir" - - lib_file="$ANCHOR_EXAMPLES_DIR/$anchor_dir/src/lib.rs" - test_file="$ANCHOR_EXAMPLES_DIR/$anchor_dir/tests/test.rs" - - # Check lib file exists (required) - if [ ! -f "$lib_file" ]; then - echo " WARNING: Not found - $lib_file" - continue - fi - - # Create CodeGroup MDX - output_file="$output_dir/full-example.mdx" - - if [ -f "$test_file" ]; then - # Both lib.rs and test.rs - { - echo '' - echo '```rust lib.rs' - cat "$lib_file" - echo '```' - echo '' - echo '```rust test.rs' - cat "$test_file" - echo '```' - echo '' - } > "$output_file" - else - # Only lib.rs (no test file) - { - echo '```rust lib.rs' - cat "$lib_file" - echo '```' - } > "$output_file" - echo " Note: No test file found, using lib.rs only" - fi - - echo " Created: $output_file" -done - -# ============================================================================= -# ANCHOR MACROS -# ============================================================================= - -ANCHOR_MACROS_DIR="/home/tilo/Workspace/examples-light-token-anchor/program-examples/anchor/basic-macros" - -ANCHOR_MACRO_RECIPES=( - "create-mint:create-mint" - "create-ata:create-ata" - "create-token-account:create-token-account" -) - -echo "" -echo "=== Processing Anchor Macro program files ===" -echo "" - -for mapping in "${ANCHOR_MACRO_RECIPES[@]}"; do - IFS=':' read -r output_name macro_dir <<< "$mapping" - - echo "Processing: $output_name (dir: $macro_dir)" - - output_dir="$SNIPPETS_DIR/$output_name/anchor-macro" - mkdir -p "$output_dir" - - lib_file="$ANCHOR_MACROS_DIR/$macro_dir/src/lib.rs" - test_file="$ANCHOR_MACROS_DIR/$macro_dir/tests/test.rs" - - if [ ! -f "$lib_file" ]; then - echo " WARNING: Not found - $lib_file" - continue - fi - - output_file="$output_dir/full-example.mdx" - - if [ -f "$test_file" ]; then - { - echo '' - echo '```rust lib.rs' - cat "$lib_file" - echo '```' - echo '' - echo '```rust test.rs' - cat "$test_file" - echo '```' - echo '' - } > "$output_file" - else - { - echo '```rust lib.rs' - cat "$lib_file" - echo '```' - } > "$output_file" - fi - - echo " Created: $output_file" -done - -echo "" -echo "Done! Created program snippets in: $SNIPPETS_DIR" -echo "" -echo "Files created:" -find "$SNIPPETS_DIR" -path "*-program/*.mdx" -type f | sort -find "$SNIPPETS_DIR" -path "*-macro/*.mdx" -type f | sort diff --git a/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx deleted file mode 100644 index cbe58db6..00000000 --- a/snippets/code-snippets/light-token/approve/anchor-program/full-example.mdx +++ /dev/null @@ -1,83 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::ApproveCpi; - -declare_id!("37XmzKqSG2VD1ZBvzyfbt1HN1mT1bqVAmfzX2ziB3KT1"); - -#[program] -pub mod light_token_anchor_approve { - use super::*; - - pub fn approve(ctx: Context, amount: u64) -> Result<()> { - ApproveCpi { - token_account: ctx.accounts.token_account.to_account_info(), - delegate: ctx.accounts.delegate.to_account_info(), - owner: ctx.accounts.owner.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - amount, - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct ApproveAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub token_account: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub delegate: AccountInfo<'info>, - pub owner: Signer<'info>, - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_approve::{accounts, instruction::Approve, ID}; -use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer, system_program}; -use test_utils::{mint_tokens, setup_test_env}; - -#[tokio::test] -async fn test_approve() { - let mut env = setup_test_env("light_token_anchor_approve", ID).await; - - // Mint tokens first - let mint_amount = 1_000_000u64; - mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, mint_amount).await; - - // Call the anchor program to approve delegate - let delegate = Keypair::new(); - let approve_amount = 500_000u64; - - let ix = Instruction { - program_id: ID, - accounts: accounts::ApproveAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - token_account: env.ata, - delegate: delegate.pubkey(), - owner: env.payer.pubkey(), - system_program: system_program::ID, - } - .to_account_metas(Some(true)), - data: Approve { amount: approve_amount }.data(), - }; - - let sig = env - .rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/approve/native-program/full-example.mdx b/snippets/code-snippets/light-token/approve/native-program/full-example.mdx deleted file mode 100644 index b8da4239..00000000 --- a/snippets/code-snippets/light-token/approve/native-program/full-example.mdx +++ /dev/null @@ -1,137 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::ApproveCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn approve_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [token_account, delegate, owner, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - let amount = u64::from_le_bytes( - data.try_into() - .map_err(|_| ProgramError::InvalidInstructionData)?, - ); - - // Approve delegate to transfer tokens on behalf of owner - ApproveCpi { - token_account: token_account.clone(), - delegate: delegate.clone(), - owner: owner.clone(), - system_program: system_program.clone(), - amount, - } - .invoke() -} - -pub fn approve_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [token_account, delegate, owner, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 9 { - return Err(ProgramError::InvalidInstructionData); - } - - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let bump = data[8]; - let signer_seeds = authority_seeds!(bump); - - ApproveCpi { - token_account: token_account.clone(), - delegate: delegate.clone(), - owner: owner.clone(), - system_program: system_program.clone(), - amount, - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use borsh::BorshDeserialize; -use light_client::rpc::Rpc; -use light_token_interface::state::Token; -use shared::{ - build_approve_cpi_ix, build_approve_signed_cpi_ix, create_test_rpc, - get_authority_pda, setup, setup_pda_owned_ata, SetupContext, -}; -use solana_sdk::{signature::Keypair, signer::Signer}; - -#[tokio::test(flavor = "multi_thread")] -async fn approve_cpi() { - // Setup: create mint and ATA with tokens - let SetupContext { - mut rpc, - payer, - ata, - .. - } = setup().await; - - let delegate = Keypair::new(); - let delegate_amount = 500_000u64; - - let ix = build_approve_cpi_ix( - ata, - delegate.pubkey(), - payer.pubkey(), - delegate_amount, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Verify delegate is set - let account_data = rpc.get_account(ata).await.unwrap().unwrap(); - let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); - assert_eq!(token_state.delegate, Some(delegate.pubkey().into())); - assert_eq!(token_state.delegated_amount, delegate_amount); -} - -#[tokio::test(flavor = "multi_thread")] -async fn approve_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_owner, bump) = get_authority_pda(); - - let (_mint, ata) = - setup_pda_owned_ata(&mut rpc, &payer, pda_owner, 1_000_000).await; - - let delegate = Keypair::new(); - let delegate_amount = 500_000u64; - - let ix = build_approve_signed_cpi_ix( - ata, - delegate.pubkey(), - pda_owner, - delegate_amount, - bump, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Verify delegate is set - let account_data = rpc.get_account(ata).await.unwrap().unwrap(); - let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); - assert_eq!(token_state.delegate, Some(delegate.pubkey().into())); - assert_eq!(token_state.delegated_amount, delegate_amount); -} -``` - diff --git a/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx deleted file mode 100644 index 072098ea..00000000 --- a/snippets/code-snippets/light-token/burn/anchor-program/full-example.mdx +++ /dev/null @@ -1,84 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::BurnCpi; - -declare_id!("2TXVn8AqjfyeJvmFBD3kHJmh6fWkC4HNB5T76BmLKV5c"); - -#[program] -pub mod light_token_anchor_burn { - use super::*; - - pub fn burn(ctx: Context, amount: u64) -> Result<()> { - BurnCpi { - source: ctx.accounts.source.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - amount, - authority: ctx.accounts.authority.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - max_top_up: None, - fee_payer: None, - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct BurnAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub source: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub mint: AccountInfo<'info>, - pub authority: Signer<'info>, - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_burn::{accounts, instruction::Burn, ID}; -use solana_sdk::{instruction::Instruction, signer::Signer, system_program}; -use test_utils::{mint_tokens, setup_test_env}; - -#[tokio::test] -async fn test_burn() { - let mut env = setup_test_env("light_token_anchor_burn", ID).await; - - // Mint tokens first - let mint_amount = 1_000_000u64; - mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, mint_amount).await; - - // Call the anchor program to burn tokens - let burn_amount = 250_000u64; - let ix = Instruction { - program_id: ID, - accounts: accounts::BurnAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - source: env.ata, - mint: env.mint_pda, - authority: env.payer.pubkey(), - system_program: system_program::ID, - } - .to_account_metas(Some(true)), - data: Burn { amount: burn_amount }.data(), - }; - - let sig = env - .rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/burn/native-program/full-example.mdx b/snippets/code-snippets/light-token/burn/native-program/full-example.mdx deleted file mode 100644 index d1103e81..00000000 --- a/snippets/code-snippets/light-token/burn/native-program/full-example.mdx +++ /dev/null @@ -1,170 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::BurnCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn burn_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [source, mint, authority, system_program, _token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - let amount = u64::from_le_bytes( - data.try_into() - .map_err(|_| ProgramError::InvalidInstructionData)?, - ); - - // Burn tokens from source account, reducing total supply - BurnCpi { - source: source.clone(), - mint: mint.clone(), - amount, - authority: authority.clone(), - system_program: system_program.clone(), - fee_payer: None, - max_top_up: None, - } - .invoke() -} - -pub fn burn_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [source, mint, authority, system_program, _token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 9 { - return Err(ProgramError::InvalidInstructionData); - } - - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let bump = data[8]; - let signer_seeds = authority_seeds!(bump); - - BurnCpi { - source: source.clone(), - mint: mint.clone(), - amount, - authority: authority.clone(), - system_program: system_program.clone(), - fee_payer: None, - max_top_up: None, - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use borsh::BorshDeserialize; -use light_client::rpc::Rpc; -use light_token_interface::state::Token; -use shared::{ - build_burn_cpi_ix, build_burn_signed_cpi_ix, create_test_rpc, - get_authority_pda, setup, setup_pda_owned_ata, SetupContext, -}; -use solana_sdk::signer::Signer; - -#[tokio::test(flavor = "multi_thread")] -async fn burn_cpi() { - // Setup: create mint and ATA with tokens - let SetupContext { - mut rpc, - payer, - mint, - ata, - .. - } = setup().await; - - let initial_amount = 1_000_000u64; - let burn_amount = 300_000u64; - - // Get balance before burn - let account_data = rpc.get_account(ata).await.unwrap().unwrap(); - let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); - let balance_before = token_state.amount; - assert_eq!( - balance_before, initial_amount, - "Initial balance should match" - ); - - let ix = build_burn_cpi_ix(ata, mint, payer.pubkey(), burn_amount); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Verify balance decreased - let account_data = rpc.get_account(ata).await.unwrap().unwrap(); - let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); - let balance_after = token_state.amount; - assert_eq!( - balance_after, - initial_amount - burn_amount, - "Balance should decrease by burn amount" - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn burn_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_owner, bump) = get_authority_pda(); - let initial_amount = 1_000_000u64; - let burn_amount = 300_000u64; - - let (mint, ata) = - setup_pda_owned_ata(&mut rpc, &payer, pda_owner, initial_amount).await; - - let ix = build_burn_signed_cpi_ix(ata, mint, pda_owner, burn_amount, bump); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Verify balance decreased - let account_data = rpc.get_account(ata).await.unwrap().unwrap(); - let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); - let balance_after = token_state.amount; - assert_eq!( - balance_after, - initial_amount - burn_amount, - "Balance should decrease by burn amount" - ); -} - -#[tokio::test(flavor = "multi_thread")] -async fn burn_fails_with_insufficient_balance() { - // Setup: create mint and ATA with tokens - let SetupContext { - mut rpc, - payer, - mint, - ata, - .. - } = setup().await; - - let initial_amount = 1_000_000u64; - let burn_amount = initial_amount + 1; // More than balance - - let ix = build_burn_cpi_ix(ata, mint, payer.pubkey(), burn_amount); - - let result = rpc - .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await; - assert!( - result.is_err(), - "Burn with insufficient balance should fail" - ); -} -``` - diff --git a/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx deleted file mode 100644 index f373494c..00000000 --- a/snippets/code-snippets/light-token/close-token-account/anchor-program/full-example.mdx +++ /dev/null @@ -1,83 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::CloseAccountCpi; - -declare_id!("GXLCuNhnkRVp596eCdbNsZ9ua1ePbKbb344VKS7V3zQQ"); - -#[program] -pub mod light_token_anchor_close { - use super::*; - - pub fn close_account(ctx: Context) -> Result<()> { - CloseAccountCpi { - token_program: ctx.accounts.light_token_program.to_account_info(), - account: ctx.accounts.account.to_account_info(), - destination: ctx.accounts.destination.to_account_info(), - owner: ctx.accounts.owner.to_account_info(), - rent_sponsor: ctx.accounts.rent_sponsor.to_account_info(), - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct CloseAccountAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub account: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub destination: AccountInfo<'info>, - pub owner: Signer<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub rent_sponsor: AccountInfo<'info>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::{rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID}; -use light_token_anchor_close::{accounts, instruction::CloseAccount, ID}; -use solana_sdk::{instruction::Instruction, signer::Signer}; -use test_utils::setup_test_env; - -#[tokio::test] -async fn test_close() { - let mut env = setup_test_env("light_token_anchor_close", ID).await; - - // ATA must be empty to close (no mint_tokens call). - - // Call the anchor program to close account - let rent_sponsor = rent_sponsor_pda(); - - let ix = Instruction { - program_id: ID, - accounts: accounts::CloseAccountAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - account: env.ata, - destination: env.payer.pubkey(), - owner: env.payer.pubkey(), - rent_sponsor, - } - .to_account_metas(Some(true)), - data: CloseAccount {}.data(), - }; - - let sig = env - .rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx b/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx deleted file mode 100644 index e468009d..00000000 --- a/snippets/code-snippets/light-token/close-token-account/native-program/full-example.mdx +++ /dev/null @@ -1,116 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::CloseAccountCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn close_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { - let [account, destination, owner, rent_sponsor, token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - // Close token account. Must be empty (balance == 0) - CloseAccountCpi { - token_program: token_program.clone(), - account: account.clone(), - destination: destination.clone(), - owner: owner.clone(), - rent_sponsor: rent_sponsor.clone(), - } - .invoke() -} - -pub fn close_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [account, destination, owner, rent_sponsor, token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.is_empty() { - return Err(ProgramError::InvalidInstructionData); - } - - let bump = data[0]; - let signer_seeds = authority_seeds!(bump); - - CloseAccountCpi { - token_program: token_program.clone(), - account: account.clone(), - destination: destination.clone(), - owner: owner.clone(), - rent_sponsor: rent_sponsor.clone(), - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use light_client::rpc::Rpc; -use light_token::instruction::rent_sponsor_pda; -use shared::{ - build_close_cpi_ix, build_close_signed_cpi_ix, create_test_rpc, - get_authority_pda, setup_empty_ata, setup_pda_owned_ata, SetupContext, -}; -use solana_sdk::signer::Signer; - -#[tokio::test(flavor = "multi_thread")] -async fn close_cpi() { - // Setup: create mint and empty ATA (must be empty to close) - let SetupContext { - mut rpc, - payer, - ata, - .. - } = setup_empty_ata().await; - - let ix = build_close_cpi_ix( - ata, - payer.pubkey(), - payer.pubkey(), - rent_sponsor_pda(), - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let account = rpc.get_account(ata).await.unwrap(); - assert!(account.is_none()); -} - -#[tokio::test(flavor = "multi_thread")] -async fn close_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_owner, bump) = get_authority_pda(); - - let (_mint, ata) = - setup_pda_owned_ata(&mut rpc, &payer, pda_owner, 0).await; - - let ix = build_close_signed_cpi_ix( - ata, - payer.pubkey(), - pda_owner, - rent_sponsor_pda(), - bump, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let account = rpc.get_account(ata).await.unwrap(); - assert!(account.is_none()); -} -``` - diff --git a/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx deleted file mode 100644 index fbaab1f9..00000000 --- a/snippets/code-snippets/light-token/create-ata/anchor-program/full-example.mdx +++ /dev/null @@ -1,173 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::CreateAssociatedAccountCpi; - -declare_id!("35MukgdfpNUbPMhTmEk63ECV8vjgpNVFRH9nP8ovMN58"); - -#[program] -pub mod light_token_anchor_create_ata { - use super::*; - - pub fn create_ata(ctx: Context, bump: u8, idempotent: bool) -> Result<()> { - let cpi = CreateAssociatedAccountCpi { - payer: ctx.accounts.payer.to_account_info(), - owner: ctx.accounts.owner.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - ata: ctx.accounts.associated_token_account.to_account_info(), - bump, - }; - - if idempotent { - cpi.idempotent().rent_free( - ctx.accounts.compressible_config.to_account_info(), - ctx.accounts.rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ) - } else { - cpi.rent_free( - ctx.accounts.compressible_config.to_account_info(), - ctx.accounts.rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ) - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct CreateAtaAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub owner: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub mint: AccountInfo<'info>, - #[account(mut)] - pub payer: Signer<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub associated_token_account: AccountInfo<'info>, - pub system_program: Program<'info, System>, - /// CHECK: Validated by light-token CPI - pub compressible_config: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub rent_sponsor: AccountInfo<'info>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::indexer::AddressWithTree; -use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; -use light_token_anchor_create_ata::{accounts, instruction::CreateAta, ID}; -use light_token::instruction::{ - CreateMint, CreateMintParams, config_pda, derive_mint_compressed_address, derive_token_ata, - find_mint_address, rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID, -}; -use anchor_lang::system_program; -use solana_sdk::{ - instruction::Instruction, - signature::Keypair, - signer::Signer, -}; - -#[tokio::test] -async fn test_create_ata() { - let config = - ProgramTestConfig::new_v2(true, Some(vec![("light_token_anchor_create_ata", ID)])); - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let mint_seed = Keypair::new(); - let mint_authority = payer.pubkey(); - let decimals = 9u8; - - let address_tree = rpc.get_address_tree_v2(); - let output_queue = rpc.get_random_state_tree_info().unwrap().queue; - - let compression_address = - derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); - let (mint_pda, bump) = find_mint_address(&mint_seed.pubkey()); - - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - let params = CreateMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority, - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint: mint_pda, - bump, - freeze_authority: None, - extensions: None, - rent_payment: 16, // ~24 hours rent - write_top_up: 766, // ~3 hours rent per write - }; - - let create_mint_ix = CreateMint::new( - params, - mint_seed.pubkey(), - payer.pubkey(), - address_tree.tree, - output_queue, - ) - .instruction() - .unwrap(); - - rpc.create_and_send_transaction(&[create_mint_ix], &payer.pubkey(), &[&payer, &mint_seed]) - .await - .unwrap(); - - // You can use light, spl, t22 mints to create a light token ATA. - // Derive ATA address and bump - let (ata, ata_bump) = derive_token_ata(&payer.pubkey(), &mint_pda); - - // Call the anchor program to create ATA - let compressible_config = config_pda(); - let rent_sponsor = rent_sponsor_pda(); - - let ix = Instruction { - program_id: ID, - accounts: accounts::CreateAtaAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - owner: payer.pubkey(), - mint: mint_pda, - payer: payer.pubkey(), - associated_token_account: ata, - system_program: system_program::ID, - compressible_config, - rent_sponsor, - } - .to_account_metas(Some(true)), - data: CreateAta { - bump: ata_bump, - idempotent: false, - } - .data(), - }; - - let sig = rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx b/snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx deleted file mode 100644 index 668c2657..00000000 --- a/snippets/code-snippets/light-token/create-ata/native-program/full-example.mdx +++ /dev/null @@ -1,172 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::CreateAssociatedAccountCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn create_ata_invoke( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [owner, mint, payer, associated_token_account, system_program, compressible_config, rent_sponsor, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.is_empty() { - return Err(ProgramError::InvalidInstructionData); - } - - let bump = data[0]; - let idempotent = data.get(1).copied().unwrap_or(0) != 0; - - // Create associated token account. Works with light, spl, t22 mints - let cpi = CreateAssociatedAccountCpi { - payer: payer.clone(), - owner: owner.clone(), - mint: mint.clone(), - ata: associated_token_account.clone(), - bump, - }; - - if idempotent { - cpi.idempotent().rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), - ) - } else { - cpi.rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), - ) - } - .invoke() -} - -pub fn create_ata_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [owner, mint, payer, associated_token_account, system_program, compressible_config, rent_sponsor, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 3 { - return Err(ProgramError::InvalidInstructionData); - } - - let bump = data[0]; - let idempotent = data[1] != 0; - let authority_bump = data[2]; - let signer_seeds = authority_seeds!(authority_bump); - - let cpi = CreateAssociatedAccountCpi { - payer: payer.clone(), - owner: owner.clone(), - mint: mint.clone(), - ata: associated_token_account.clone(), - bump, - }; - - if idempotent { - cpi.idempotent().rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), - ) - } else { - cpi.rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), - ) - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use light_client::rpc::Rpc; -use light_token::instruction::derive_token_ata; -use shared::{ - build_create_ata_cpi_ix, build_create_ata_signed_cpi_ix, create_test_rpc, - get_authority_pda, setup_mint_with_tokens, -}; -use solana_sdk::signer::Signer; - -#[tokio::test(flavor = "multi_thread")] -async fn create_ata_cpi() { - // Works with light, spl, or t22 mints - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (mint, _) = setup_mint_with_tokens( - &mut rpc, - &payer, - payer.pubkey(), - None, - 9, - vec![], - ) - .await; - - let owner = payer.pubkey(); - let (ata, bump) = derive_token_ata(&owner, &mint); - - let ix = build_create_ata_cpi_ix(owner, mint, payer.pubkey(), ata, bump); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let account = rpc.get_account(ata).await.unwrap(); - assert!(account.is_some()); -} - -#[tokio::test(flavor = "multi_thread")] -async fn create_ata_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_owner, authority_bump) = get_authority_pda(); - - let (mint, _) = setup_mint_with_tokens( - &mut rpc, - &payer, - payer.pubkey(), - None, - 9, - vec![], - ) - .await; - - let (ata, ata_bump) = derive_token_ata(&pda_owner, &mint); - - let ix = build_create_ata_signed_cpi_ix( - pda_owner, - mint, - payer.pubkey(), - ata, - ata_bump, - authority_bump, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let account = rpc.get_account(ata).await.unwrap(); - assert!(account.is_some()); -} -``` - diff --git a/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx deleted file mode 100644 index f2bbaf12..00000000 --- a/snippets/code-snippets/light-token/create-mint/anchor-program/full-example.mdx +++ /dev/null @@ -1,230 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::{CreateMintCpi, CreateMintParams, SystemAccountInfos}; -use light_token::{CompressedProof, ExtensionInstructionData, TokenMetadataInstructionData}; - -declare_id!("A1rJEoepgKYWZYZ8KVFpxgeeRGwBrU7xk8S39srjVkUX"); - -/// Token metadata parameters for creating a mint with metadata. -#[derive(AnchorSerialize, AnchorDeserialize, Clone)] -pub struct TokenMetadataParams { - pub name: Vec, - pub symbol: Vec, - pub uri: Vec, - pub update_authority: Option, -} - -#[program] -pub mod light_token_anchor_create_mint { - use super::*; - - pub fn create_mint( - ctx: Context, - decimals: u8, - address_merkle_tree_root_index: u16, - compression_address: [u8; 32], - proof: CompressedProof, - freeze_authority: Option, - bump: u8, - rent_payment: Option, - write_top_up: Option, - metadata: Option, - ) -> Result<()> { - let mint = light_token::instruction::find_mint_address(ctx.accounts.mint_seed.key).0; - - let extensions = metadata.map(|m| { - vec![ExtensionInstructionData::TokenMetadata( - TokenMetadataInstructionData { - update_authority: m - .update_authority - .map(|p| p.to_bytes().into()), - name: m.name, - symbol: m.symbol, - uri: m.uri, - additional_metadata: None, - }, - )] - }); - - let params = CreateMintParams { - decimals, - address_merkle_tree_root_index, - mint_authority: *ctx.accounts.authority.key, - proof, - compression_address, - mint, - bump, - freeze_authority, - extensions, - rent_payment: rent_payment.unwrap_or(16), // Default: ~24 hours - write_top_up: write_top_up.unwrap_or(766), // Default: ~3 hours per write - }; - - let system_accounts = SystemAccountInfos { - light_system_program: ctx.accounts.light_system_program.to_account_info(), - cpi_authority_pda: ctx.accounts.cpi_authority_pda.to_account_info(), - registered_program_pda: ctx.accounts.registered_program_pda.to_account_info(), - account_compression_authority: ctx.accounts.account_compression_authority.to_account_info(), - account_compression_program: ctx.accounts.account_compression_program.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - }; - - CreateMintCpi { - mint_seed: ctx.accounts.mint_seed.to_account_info(), - authority: ctx.accounts.authority.to_account_info(), - payer: ctx.accounts.payer.to_account_info(), - address_tree: ctx.accounts.address_tree.to_account_info(), - output_queue: ctx.accounts.output_queue.to_account_info(), - compressible_config: ctx.accounts.compressible_config.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - rent_sponsor: ctx.accounts.rent_sponsor.to_account_info(), - system_accounts, - cpi_context: None, - cpi_context_account: None, - params, - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct CreateMintAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - pub mint_seed: Signer<'info>, - /// CHECK: Validated by light-token CPI - pub authority: AccountInfo<'info>, - #[account(mut)] - pub payer: Signer<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub address_tree: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub output_queue: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub light_system_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub cpi_authority_pda: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub registered_program_pda: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub account_compression_authority: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub account_compression_program: AccountInfo<'info>, - pub system_program: Program<'info, System>, - /// CHECK: Validated by light-token CPI - use light_token::token::config_pda() - pub compressible_config: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - derived from find_mint_address(mint_seed) - #[account(mut)] - pub mint: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - use light_token::token::rent_sponsor_pda() - #[account(mut)] - pub rent_sponsor: AccountInfo<'info>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; -use light_token_anchor_create_mint::{accounts, instruction::CreateMint, ID}; -use light_token::instruction::{ - config_pda, derive_mint_compressed_address, find_mint_address, rent_sponsor_pda, - SystemAccounts, LIGHT_TOKEN_PROGRAM_ID, -}; -use anchor_lang::system_program; -use solana_sdk::{ - instruction::Instruction, - signature::Keypair, - signer::Signer, -}; - -#[tokio::test] -async fn test_create_mint() { - let config = - ProgramTestConfig::new_v2(true, Some(vec![("light_token_anchor_create_mint", ID)])); - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - let mint_seed = Keypair::new(); - let mint_authority = payer.pubkey(); - let decimals = 9u8; - - let address_tree = rpc.get_address_tree_v2(); - let output_queue = rpc.get_random_state_tree_info().unwrap().queue; - - let compression_address = - derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); - - let (mint_pda, bump) = find_mint_address(&mint_seed.pubkey()); - - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![light_client::indexer::AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - let system_accounts = SystemAccounts::default(); - - // Call the anchor program to create mint - let ix = Instruction { - program_id: ID, - accounts: accounts::CreateMintAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - mint_seed: mint_seed.pubkey(), - authority: mint_authority, - payer: payer.pubkey(), - address_tree: address_tree.tree, - output_queue, - light_system_program: system_accounts.light_system_program, - cpi_authority_pda: system_accounts.cpi_authority_pda, - registered_program_pda: system_accounts.registered_program_pda, - account_compression_authority: system_accounts.account_compression_authority, - account_compression_program: system_accounts.account_compression_program, - system_program: system_program::ID, - compressible_config: config_pda(), - mint: mint_pda, - rent_sponsor: rent_sponsor_pda(), - } - .to_account_metas(Some(true)), - data: CreateMint { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - compression_address: compression_address.into(), - proof: rpc_result.proof.0.unwrap(), - freeze_authority: None, - bump, - rent_payment: Some(16), // ~24 hours rent - write_top_up: Some(766), // ~3 hours rent per write - metadata: None, - } - .data(), - }; - - let sig = rpc - .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer, &mint_seed]) - .await - .unwrap(); - - let compressed_account = rpc - .get_compressed_account(compression_address, None) - .await - .unwrap() - .value; - - assert!(compressed_account.is_some(), "Light-mint should exist"); - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx b/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx deleted file mode 100644 index 25a3bfe7..00000000 --- a/snippets/code-snippets/light-token/create-mint/native-program/full-example.mdx +++ /dev/null @@ -1,483 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use borsh::BorshDeserialize; -use light_compressible::CreateAccountsProof; -use light_token::instruction::{ - CreateMintCpi, CreateMintParams, SystemAccountInfos, -}; -use light_token::instruction::{ - ExtensionInstructionData, TokenMetadataInstructionData, -}; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, -}; - -#[derive(BorshDeserialize)] -struct CreateMintData { - decimals: u8, - mint_authority: Pubkey, - create_accounts_proof: CreateAccountsProof, - compression_address: [u8; 32], - mint: Pubkey, - bump: u8, - freeze_authority: Option, - rent_payment: u8, - write_top_up: u32, - metadata_name: Option>, - metadata_symbol: Option>, - metadata_uri: Option>, -} - -pub fn create_mint_invoke( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [mint_seed, authority, payer, address_tree, output_queue, compressible_config, mint, rent_sponsor, light_system_program, cpi_authority_pda, registered_program_pda, account_compression_authority, account_compression_program, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - let ix_data = CreateMintData::deserialize(&mut &data[..]) - .map_err(|_| ProgramError::InvalidInstructionData)?; - - // Build token metadata extension if metadata fields are provided - let extensions = match ( - &ix_data.metadata_name, - &ix_data.metadata_symbol, - &ix_data.metadata_uri, - ) { - (Some(name), Some(symbol), Some(uri)) => { - Some(vec![ExtensionInstructionData::TokenMetadata( - TokenMetadataInstructionData { - update_authority: Some( - ix_data.mint_authority.to_bytes().into(), - ), - name: name.clone(), - symbol: symbol.clone(), - uri: uri.clone(), - additional_metadata: None, - }, - )]) - } - _ => None, - }; - - // Create mint. rent_payment: ~24h rent/unit, write_top_up: ~3h rent/write - let params = CreateMintParams { - decimals: ix_data.decimals, - address_merkle_tree_root_index: ix_data - .create_accounts_proof - .address_tree_info - .root_index, - mint_authority: ix_data.mint_authority, - proof: ix_data.create_accounts_proof.proof.0.unwrap_or_default(), - compression_address: ix_data.compression_address, - mint: ix_data.mint, - bump: ix_data.bump, - freeze_authority: ix_data.freeze_authority, - extensions, - rent_payment: ix_data.rent_payment, - write_top_up: ix_data.write_top_up, - }; - - let system_accounts = SystemAccountInfos { - light_system_program: light_system_program.clone(), - cpi_authority_pda: cpi_authority_pda.clone(), - registered_program_pda: registered_program_pda.clone(), - account_compression_authority: account_compression_authority.clone(), - account_compression_program: account_compression_program.clone(), - system_program: system_program.clone(), - }; - - CreateMintCpi::new( - mint_seed.clone(), - authority.clone(), - payer.clone(), - address_tree.clone(), - output_queue.clone(), - compressible_config.clone(), - mint.clone(), - rent_sponsor.clone(), - system_accounts, - params, - ) - .invoke() -} - -#[derive(BorshDeserialize)] -struct CreateMintSignedData { - decimals: u8, - create_accounts_proof: CreateAccountsProof, - compression_address: [u8; 32], - mint: Pubkey, - bump: u8, - freeze_authority: Option, - rent_payment: u8, - write_top_up: u32, - authority_bump: u8, - metadata_name: Option>, - metadata_symbol: Option>, - metadata_uri: Option>, -} - -pub fn create_mint_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [mint_seed, authority, payer, address_tree, output_queue, compressible_config, mint, rent_sponsor, light_system_program, cpi_authority_pda, registered_program_pda, account_compression_authority, account_compression_program, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - let ix_data = CreateMintSignedData::deserialize(&mut &data[..]) - .map_err(|_| ProgramError::InvalidInstructionData)?; - - let signer_seeds = authority_seeds!(ix_data.authority_bump); - - // Build token metadata extension if metadata fields are provided - let extensions = match ( - &ix_data.metadata_name, - &ix_data.metadata_symbol, - &ix_data.metadata_uri, - ) { - (Some(name), Some(symbol), Some(uri)) => { - Some(vec![ExtensionInstructionData::TokenMetadata( - TokenMetadataInstructionData { - update_authority: Some(authority.key.to_bytes().into()), - name: name.clone(), - symbol: symbol.clone(), - uri: uri.clone(), - additional_metadata: None, - }, - )]) - } - _ => None, - }; - - let params = CreateMintParams { - decimals: ix_data.decimals, - address_merkle_tree_root_index: ix_data - .create_accounts_proof - .address_tree_info - .root_index, - mint_authority: *authority.key, - proof: ix_data.create_accounts_proof.proof.0.unwrap_or_default(), - compression_address: ix_data.compression_address, - mint: ix_data.mint, - bump: ix_data.bump, - freeze_authority: ix_data.freeze_authority, - extensions, - rent_payment: ix_data.rent_payment, - write_top_up: ix_data.write_top_up, - }; - - let system_accounts = SystemAccountInfos { - light_system_program: light_system_program.clone(), - cpi_authority_pda: cpi_authority_pda.clone(), - registered_program_pda: registered_program_pda.clone(), - account_compression_authority: account_compression_authority.clone(), - account_compression_program: account_compression_program.clone(), - system_program: system_program.clone(), - }; - - CreateMintCpi::new( - mint_seed.clone(), - authority.clone(), - payer.clone(), - address_tree.clone(), - output_queue.clone(), - compressible_config.clone(), - mint.clone(), - rent_sponsor.clone(), - system_accounts, - params, - ) - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use borsh::BorshSerialize; -use light_client::{ - indexer::{AddressWithTree, Indexer}, - rpc::Rpc, -}; -use light_compressed_account::instruction_data::{ - compressed_proof::ValidityProof, data::PackedAddressTreeInfo, -}; -use light_compressible::CreateAccountsProof; -use light_token::instruction::{ - config_pda, derive_mint_compressed_address, find_mint_address, - rent_sponsor_pda, SystemAccounts, LIGHT_TOKEN_PROGRAM_ID, -}; -use shared::{create_test_rpc, get_authority_pda, PROGRAM_ID}; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, -}; - -#[tokio::test(flavor = "multi_thread")] -async fn create_mint_cpi() { - let mut rpc = create_test_rpc().await; - - let payer = rpc.get_payer().insecure_clone(); - let mint_seed = Keypair::new(); - let decimals = 9u8; - - // Get address tree to store compressed address for when mint turns inactive - // We must create a compressed address at creation to ensure the mint does not exist yet - let address_tree = rpc.get_address_tree_v2(); - // Get state tree to store mint when inactive - let output_queue = rpc.get_random_state_tree_info().unwrap().queue; - - // Derive mint addresses - let compression_address = - derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); - let (mint, bump) = find_mint_address(&mint_seed.pubkey()); - - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - // Build instruction data - #[derive(BorshSerialize)] - struct CreateMintDataTest { - decimals: u8, - mint_authority: Pubkey, - create_accounts_proof: CreateAccountsProof, - compression_address: [u8; 32], - mint: Pubkey, - bump: u8, - freeze_authority: Option, - rent_payment: u8, - write_top_up: u32, - metadata_name: Option>, - metadata_symbol: Option>, - metadata_uri: Option>, - } - - let create_accounts_proof = CreateAccountsProof { - proof: ValidityProof(rpc_result.proof.0), - address_tree_info: PackedAddressTreeInfo { - address_merkle_tree_pubkey_index: 0, - address_queue_pubkey_index: 0, - root_index: rpc_result.addresses[0].root_index, - }, - output_state_tree_index: 0, - state_tree_index: None, - }; - - let test_data = CreateMintDataTest { - decimals, - mint_authority: payer.pubkey(), - create_accounts_proof, - compression_address, - mint, - bump, - freeze_authority: None, - rent_payment: 16, - write_top_up: 766, - metadata_name: Some(b"Example Token".to_vec()), - metadata_symbol: Some(b"EXT".to_vec()), - metadata_uri: Some(b"https://example.com/metadata.json".to_vec()), - }; - - let mut data = vec![0u8]; - data.extend(test_data.try_to_vec().unwrap()); - - let system_accounts = SystemAccounts::default(); - - // Build and send instruction (mint_seed must sign) - let ix = Instruction { - program_id: PROGRAM_ID, - accounts: vec![ - AccountMeta::new(mint_seed.pubkey(), true), - AccountMeta::new_readonly(payer.pubkey(), true), - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(address_tree.tree, false), - AccountMeta::new(output_queue, false), - AccountMeta::new_readonly(config_pda(), false), - AccountMeta::new(mint, false), - AccountMeta::new(rent_sponsor_pda(), false), - AccountMeta::new_readonly( - system_accounts.light_system_program, - false, - ), - AccountMeta::new_readonly(system_accounts.cpi_authority_pda, false), - AccountMeta::new_readonly( - system_accounts.registered_program_pda, - false, - ), - AccountMeta::new_readonly( - system_accounts.account_compression_authority, - false, - ), - AccountMeta::new_readonly( - system_accounts.account_compression_program, - false, - ), - AccountMeta::new_readonly(system_accounts.system_program, false), - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - ], - data, - }; - - rpc.create_and_send_transaction( - &[ix], - &payer.pubkey(), - &[&payer, &mint_seed], - ) - .await - .unwrap(); - - let mint_account = rpc.get_account(mint).await.unwrap(); - assert!(mint_account.is_some()); -} - -#[tokio::test(flavor = "multi_thread")] -async fn create_mint_signed_cpi() { - let mut rpc = create_test_rpc().await; - - let payer = rpc.get_payer().insecure_clone(); - let mint_seed = Keypair::new(); - let decimals = 9u8; - - let (pda_authority, authority_bump) = get_authority_pda(); - - // Get address tree to store compressed address for when mint turns inactive - // We must create a compressed address at creation to ensure the mint does not exist yet - let address_tree = rpc.get_address_tree_v2(); - // Get state tree to store mint when inactive - let output_queue = rpc.get_random_state_tree_info().unwrap().queue; - - // Derive mint addresses - let compression_address = - derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); - let (mint, bump) = find_mint_address(&mint_seed.pubkey()); - - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - // Build instruction data - #[derive(BorshSerialize)] - struct CreateMintSignedDataTest { - decimals: u8, - create_accounts_proof: CreateAccountsProof, - compression_address: [u8; 32], - mint: Pubkey, - bump: u8, - freeze_authority: Option, - rent_payment: u8, - write_top_up: u32, - authority_bump: u8, - metadata_name: Option>, - metadata_symbol: Option>, - metadata_uri: Option>, - } - - let create_accounts_proof = CreateAccountsProof { - proof: ValidityProof(rpc_result.proof.0), - address_tree_info: PackedAddressTreeInfo { - address_merkle_tree_pubkey_index: 0, - address_queue_pubkey_index: 0, - root_index: rpc_result.addresses[0].root_index, - }, - output_state_tree_index: 0, - state_tree_index: None, - }; - - let test_data = CreateMintSignedDataTest { - decimals, - create_accounts_proof, - compression_address, - mint, - bump, - freeze_authority: None, - rent_payment: 16, - write_top_up: 766, - authority_bump, - metadata_name: Some(b"Example Token".to_vec()), - metadata_symbol: Some(b"EXT".to_vec()), - metadata_uri: Some(b"https://example.com/metadata.json".to_vec()), - }; - - let mut data = vec![19u8]; - data.extend(test_data.try_to_vec().unwrap()); - - let system_accounts = SystemAccounts::default(); - - // Build and send instruction (mint_seed must sign) - let ix = Instruction { - program_id: PROGRAM_ID, - accounts: vec![ - AccountMeta::new(mint_seed.pubkey(), true), - AccountMeta::new(pda_authority, false), - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new(address_tree.tree, false), - AccountMeta::new(output_queue, false), - AccountMeta::new_readonly(config_pda(), false), - AccountMeta::new(mint, false), - AccountMeta::new(rent_sponsor_pda(), false), - AccountMeta::new_readonly( - system_accounts.light_system_program, - false, - ), - AccountMeta::new_readonly(system_accounts.cpi_authority_pda, false), - AccountMeta::new_readonly( - system_accounts.registered_program_pda, - false, - ), - AccountMeta::new_readonly( - system_accounts.account_compression_authority, - false, - ), - AccountMeta::new_readonly( - system_accounts.account_compression_program, - false, - ), - AccountMeta::new_readonly(system_accounts.system_program, false), - AccountMeta::new_readonly(LIGHT_TOKEN_PROGRAM_ID, false), - ], - data, - }; - - rpc.create_and_send_transaction( - &[ix], - &payer.pubkey(), - &[&payer, &mint_seed], - ) - .await - .unwrap(); - - let mint_account = rpc.get_account(mint).await.unwrap(); - assert!(mint_account.is_some()); -} -``` - diff --git a/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx deleted file mode 100644 index ab0d9280..00000000 --- a/snippets/code-snippets/light-token/create-token-account/anchor-program/full-example.mdx +++ /dev/null @@ -1,161 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::CreateTokenAccountCpi; - -declare_id!("zXK1CnWj4WFfFHCArxxr4sh3Qqx2p3oui8ahqpjArgS"); - -#[program] -pub mod light_token_anchor_create_token_account { - use super::*; - - pub fn create_token_account(ctx: Context, owner: Pubkey) -> Result<()> { - CreateTokenAccountCpi { - payer: ctx.accounts.payer.to_account_info(), - account: ctx.accounts.account.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - owner, - } - .rent_free( - ctx.accounts.compressible_config.to_account_info(), - ctx.accounts.rent_sponsor.to_account_info(), - ctx.accounts.system_program.to_account_info(), - &ctx.accounts.light_token_program.key(), - ) - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct CreateTokenAccountAccounts<'info> { - #[account(mut)] - pub payer: Signer<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub account: Signer<'info>, - /// CHECK: Validated by light-token CPI - pub mint: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub compressible_config: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub rent_sponsor: AccountInfo<'info>, - pub system_program: Program<'info, System>, - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_client::indexer::AddressWithTree; -use light_program_test::{Indexer, LightProgramTest, ProgramTestConfig, Rpc}; -use light_token_anchor_create_token_account::{accounts, instruction::CreateTokenAccount, ID}; -use light_token::instruction::{ - CreateMint, CreateMintParams, config_pda, derive_mint_compressed_address, - find_mint_address, rent_sponsor_pda, LIGHT_TOKEN_PROGRAM_ID, -}; -use anchor_lang::system_program; -use solana_sdk::{ - instruction::Instruction, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, -}; - -#[tokio::test] -async fn test_create_token_account() { - let config = ProgramTestConfig::new_v2( - true, - Some(vec![("light_token_anchor_create_token_account", ID)]), - ); - let mut rpc = LightProgramTest::new(config).await.unwrap(); - let payer = rpc.get_payer().insecure_clone(); - - // Create a mint first - let mint_seed = Keypair::new(); - let mint_authority = payer.pubkey(); - let decimals = 9u8; - - let address_tree = rpc.get_address_tree_v2(); - let output_queue = rpc.get_random_state_tree_info().unwrap().queue; - - let compression_address = - derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree); - let (mint_pda, bump) = find_mint_address(&mint_seed.pubkey()); - - let rpc_result = rpc - .get_validity_proof( - vec![], - vec![AddressWithTree { - address: compression_address, - tree: address_tree.tree, - }], - None, - ) - .await - .unwrap() - .value; - - let params = CreateMintParams { - decimals, - address_merkle_tree_root_index: rpc_result.addresses[0].root_index, - mint_authority, - proof: rpc_result.proof.0.unwrap(), - compression_address, - mint: mint_pda, - bump, - freeze_authority: None, - extensions: None, - rent_payment: 16, // ~24 hours rent - write_top_up: 766, // ~3 hours rent per write - }; - - let create_mint_ix = CreateMint::new( - params, - mint_seed.pubkey(), - payer.pubkey(), - address_tree.tree, - output_queue, - ) - .instruction() - .unwrap(); - - rpc.create_and_send_transaction(&[create_mint_ix], &payer.pubkey(), &[&payer, &mint_seed]) - .await - .unwrap(); - - // You can use light, spl, t22 mints to create a light token account. - // Create a token account - let token_account = Keypair::new(); - let owner = payer.pubkey(); - let compressible_config = config_pda(); - let rent_sponsor = rent_sponsor_pda(); - - let ix = Instruction { - program_id: ID, - accounts: accounts::CreateTokenAccountAccounts { - payer: payer.pubkey(), - account: token_account.pubkey(), - mint: mint_pda, - compressible_config, - rent_sponsor, - system_program: system_program::ID, - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - } - .to_account_metas(Some(true)), - data: CreateTokenAccount { owner }.data(), - }; - - let sig = rpc - .create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer, &token_account]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx b/snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx deleted file mode 100644 index fabd355f..00000000 --- a/snippets/code-snippets/light-token/create-token-account/native-program/full-example.mdx +++ /dev/null @@ -1,170 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::CreateTokenAccountCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, pubkey::Pubkey, -}; - -/// Account order: -/// - accounts[0]: payer (signer, mut) -/// - accounts[1]: account (signer for invoke, PDA for invoke_signed, mut) -/// - accounts[2]: mint (readonly) -/// - accounts[3]: compressible_config (readonly) -/// - accounts[4]: system_program (readonly) -/// - accounts[5]: rent_sponsor (mut) -/// - accounts[6]: light_token_program (readonly) -pub fn create_token_account_invoke( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [payer, account, mint, compressible_config, system_program, rent_sponsor, token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 32 { - return Err(ProgramError::InvalidInstructionData); - } - - let owner = Pubkey::try_from(&data[0..32]) - .map_err(|_| ProgramError::InvalidInstructionData)?; - - // Create token account. Works with light, spl, t22 mints - CreateTokenAccountCpi { - payer: payer.clone(), - account: account.clone(), - mint: mint.clone(), - owner, - } - .rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), - token_program.key, - ) - .invoke() -} - -pub fn create_token_account_invoke_signed( - program_id: &Pubkey, - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [payer, account, mint, compressible_config, system_program, rent_sponsor, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 33 { - return Err(ProgramError::InvalidInstructionData); - } - - let owner = Pubkey::try_from(&data[0..32]) - .map_err(|_| ProgramError::InvalidInstructionData)?; - let authority_bump = data[32]; - let signer_seeds = authority_seeds!(authority_bump); - - CreateTokenAccountCpi { - payer: payer.clone(), - account: account.clone(), - mint: mint.clone(), - owner, - } - .rent_free( - compressible_config.clone(), - rent_sponsor.clone(), - system_program.clone(), - program_id, - ) - .invoke_signed(signer_seeds) -} -``` - -```rust test.rs -mod shared; - -use light_client::rpc::Rpc; -use shared::{ - build_create_token_account_cpi_ix, - build_create_token_account_signed_cpi_ix, create_test_rpc, - get_authority_pda, setup_mint_with_tokens, -}; -use solana_sdk::{signature::Keypair, signer::Signer}; - -#[tokio::test(flavor = "multi_thread")] -async fn create_token_account_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (mint, _) = setup_mint_with_tokens( - &mut rpc, - &payer, - payer.pubkey(), - None, - 9, - vec![], - ) - .await; - - let token_account = Keypair::new(); - let owner = payer.pubkey(); - - let ix = build_create_token_account_cpi_ix( - payer.pubkey(), - token_account.pubkey(), - mint, - owner, - ); - - rpc.create_and_send_transaction( - &[ix], - &payer.pubkey(), - &[&payer, &token_account], - ) - .await - .unwrap(); - - let account = rpc.get_account(token_account.pubkey()).await.unwrap(); - assert!(account.is_some()); -} - -#[tokio::test(flavor = "multi_thread")] -async fn create_token_account_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_account, authority_bump) = get_authority_pda(); - - let (mint, _) = setup_mint_with_tokens( - &mut rpc, - &payer, - payer.pubkey(), - None, - 9, - vec![], - ) - .await; - - let owner = payer.pubkey(); - - let ix = build_create_token_account_signed_cpi_ix( - payer.pubkey(), - pda_account, - mint, - owner, - authority_bump, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let account = rpc.get_account(pda_account).await.unwrap(); - assert!(account.is_some()); -} -``` - diff --git a/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx deleted file mode 100644 index 2a195a1f..00000000 --- a/snippets/code-snippets/light-token/freeze/anchor-program/full-example.mdx +++ /dev/null @@ -1,75 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::FreezeCpi; - -declare_id!("JBMzMJX4sqCQfNVbosP2oqP1KZ5ZDWiwYTrupk687qXZ"); - -#[program] -pub mod light_token_anchor_freeze { - use super::*; - - pub fn freeze(ctx: Context) -> Result<()> { - FreezeCpi { - token_account: ctx.accounts.token_account.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - freeze_authority: ctx.accounts.freeze_authority.to_account_info(), - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct FreezeAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub token_account: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub mint: AccountInfo<'info>, - pub freeze_authority: Signer<'info>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_freeze::{accounts, instruction::Freeze, ID}; -use solana_sdk::{instruction::Instruction, signer::Signer}; -use test_utils::{mint_tokens, setup_test_env_with_freeze}; - -#[tokio::test] -async fn test_freeze() { - let mut env = setup_test_env_with_freeze("light_token_anchor_freeze", ID).await; - - // Mint tokens first - mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, 1_000_000).await; - - // Call the anchor program to freeze account - let ix = Instruction { - program_id: ID, - accounts: accounts::FreezeAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - token_account: env.ata, - mint: env.mint_pda, - freeze_authority: env.freeze_authority, - } - .to_account_metas(Some(true)), - data: Freeze {}.data(), - }; - - let sig = env - .rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx b/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx deleted file mode 100644 index f0156b5b..00000000 --- a/snippets/code-snippets/light-token/freeze/native-program/full-example.mdx +++ /dev/null @@ -1,118 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::FreezeCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn freeze_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { - let [token_account, mint, freeze_authority, _token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - // Freeze token account. freeze_authority must match mint creation - FreezeCpi { - token_account: token_account.clone(), - mint: mint.clone(), - freeze_authority: freeze_authority.clone(), - } - .invoke() -} - -pub fn freeze_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [token_account, mint, freeze_authority, _token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.is_empty() { - return Err(ProgramError::InvalidInstructionData); - } - - let bump = data[0]; - let signer_seeds = authority_seeds!(bump); - - FreezeCpi { - token_account: token_account.clone(), - mint: mint.clone(), - freeze_authority: freeze_authority.clone(), - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use borsh::BorshDeserialize; -use light_client::rpc::Rpc; -use light_token_interface::state::Token; -use shared::{ - build_freeze_cpi_ix, build_freeze_signed_cpi_ix, create_test_rpc, - get_authority_pda, setup, setup_mint_with_tokens, SetupContext, -}; -use solana_sdk::signer::Signer; - -#[tokio::test(flavor = "multi_thread")] -async fn freeze_cpi() { - // Setup: create mint and ATA with freeze authority - let SetupContext { - mut rpc, - payer, - mint, - ata, - .. - } = setup().await; - - let ix = build_freeze_cpi_ix(ata, mint, payer.pubkey()); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Verify account is frozen - let account_data = rpc.get_account(ata).await.unwrap().unwrap(); - let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); - assert!(token_state.is_frozen(), "Account should be frozen"); -} - -#[tokio::test(flavor = "multi_thread")] -async fn freeze_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_authority, bump) = get_authority_pda(); - let initial_amount = 1_000_000u64; - - // Create mint with PDA as freeze authority and mint tokens to payer - let (mint, associated_token_accounts) = setup_mint_with_tokens( - &mut rpc, - &payer, - payer.pubkey(), - Some(pda_authority), - 9, - vec![(initial_amount, payer.pubkey())], - ) - .await; - - let ata = associated_token_accounts[0]; - - let ix = build_freeze_signed_cpi_ix(ata, mint, pda_authority, bump); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Verify account is frozen - let account_data = rpc.get_account(ata).await.unwrap().unwrap(); - let token_state = Token::deserialize(&mut &account_data.data[..]).unwrap(); - assert!(token_state.is_frozen(), "Account should be frozen"); -} -``` - diff --git a/snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx deleted file mode 100644 index 5f85684c..00000000 --- a/snippets/code-snippets/light-token/mint-to-checked/anchor-program/full-example.mdx +++ /dev/null @@ -1,88 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::MintToCheckedCpi; - -declare_id!("DGu3ofzac2Zndn95z2q9gCp8zHgW22YpMeEWj2up3QDb"); - -#[program] -pub mod light_token_anchor_mint_to_checked { - use super::*; - - pub fn mint_to_checked( - ctx: Context, - amount: u64, - decimals: u8, - ) -> Result<()> { - MintToCheckedCpi { - mint: ctx.accounts.mint.to_account_info(), - destination: ctx.accounts.destination.to_account_info(), - amount, - decimals, - authority: ctx.accounts.authority.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - max_top_up: None, - fee_payer: None, - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct MintToCheckedAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub mint: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub destination: AccountInfo<'info>, - pub authority: Signer<'info>, - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_mint_to_checked::{accounts, instruction::MintToChecked, ID}; -use solana_sdk::{instruction::Instruction, signer::Signer, system_program}; -use test_utils::setup_test_env; - -#[tokio::test(flavor = "multi_thread")] -async fn test_mint_to_checked() { - let mut env = setup_test_env("light_token_anchor_mint_to_checked", ID).await; - - // MintToChecked validates decimals match the mint's decimals. - // No mint_tokens call - the test IS minting tokens via CPI. - let amount = 1_000_000u64; - let decimals = 9u8; - let ix = Instruction { - program_id: ID, - accounts: accounts::MintToCheckedAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - mint: env.mint_pda, - destination: env.ata, - authority: env.payer.pubkey(), - system_program: system_program::ID, - } - .to_account_metas(Some(true)), - data: MintToChecked { amount, decimals }.data(), - }; - - env.rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - // Verify the account exists and has data - let ata_data = env.rpc.get_account(env.ata).await.unwrap().unwrap(); - assert!(!ata_data.data.is_empty(), "ATA account should have data"); -} -``` - diff --git a/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx deleted file mode 100644 index 1803ba14..00000000 --- a/snippets/code-snippets/light-token/mint-to-checked/native-program/full-example.mdx +++ /dev/null @@ -1,145 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::MintToCheckedCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn mint_to_checked_invoke( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [mint, destination, authority, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 9 { - return Err(ProgramError::InvalidInstructionData); - } - - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let decimals = data[8]; - - // MintToChecked validates decimals match the mint - MintToCheckedCpi { - mint: mint.clone(), - destination: destination.clone(), - amount, - decimals, - authority: authority.clone(), - system_program: system_program.clone(), - max_top_up: None, - fee_payer: None, - } - .invoke() -} - -pub fn mint_to_checked_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [mint, destination, authority, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 10 { - return Err(ProgramError::InvalidInstructionData); - } - - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let decimals = data[8]; - let bump = data[9]; - let signer_seeds = authority_seeds!(bump); - - MintToCheckedCpi { - mint: mint.clone(), - destination: destination.clone(), - amount, - decimals, - authority: authority.clone(), - system_program: system_program.clone(), - max_top_up: None, - fee_payer: None, - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use light_client::rpc::Rpc; -use shared::{ - build_mint_to_checked_cpi_ix, build_mint_to_checked_signed_cpi_ix, - create_ata, create_test_rpc, get_authority_pda, - setup_mint_with_pda_authority, setup_mint_with_tokens, -}; -use solana_sdk::{signature::Keypair, signer::Signer}; - -#[tokio::test(flavor = "multi_thread")] -async fn mint_to_checked_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (mint, associated_token_accounts) = setup_mint_with_tokens( - &mut rpc, - &payer, - payer.pubkey(), - None, - 9, - vec![(0, payer.pubkey())], - ) - .await; - - let mint_amount = 1_000_000u64; - - let ix = build_mint_to_checked_cpi_ix( - mint, - associated_token_accounts[0], - payer.pubkey(), - mint_amount, - 9, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -async fn mint_to_checked_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_authority, bump) = get_authority_pda(); - - let mint = - setup_mint_with_pda_authority(&mut rpc, &payer, pda_authority, 9).await; - - let recipient = Keypair::new(); - let recipient_ata = - create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; - - let mint_amount = 1_000_000u64; - - let ix = build_mint_to_checked_signed_cpi_ix( - mint, - recipient_ata, - pda_authority, - mint_amount, - 9, - bump, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} -``` - diff --git a/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx deleted file mode 100644 index 333e63a6..00000000 --- a/snippets/code-snippets/light-token/mint-to/anchor-program/full-example.mdx +++ /dev/null @@ -1,81 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::MintToCpi; - -declare_id!("8bXEVmHLtAVqDLJp1dYWAZ61WQmqQKoTQ8LpPbRoUDCp"); - -#[program] -pub mod light_token_anchor_mint_to { - use super::*; - - pub fn mint_to(ctx: Context, amount: u64) -> Result<()> { - MintToCpi { - mint: ctx.accounts.mint.to_account_info(), - destination: ctx.accounts.destination.to_account_info(), - amount, - authority: ctx.accounts.authority.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - max_top_up: None, - fee_payer: None, - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct MintToAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub mint: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub destination: AccountInfo<'info>, - pub authority: Signer<'info>, - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_mint_to::{accounts, instruction::MintTo, ID}; -use solana_sdk::{instruction::Instruction, signer::Signer, system_program}; -use test_utils::setup_test_env; - -#[tokio::test(flavor = "multi_thread")] -async fn test_mint_to() { - let mut env = setup_test_env("light_token_anchor_mint_to", ID).await; - - // No mint_tokens call - the test IS minting tokens via CPI. - let amount = 1_000_000u64; - let ix = Instruction { - program_id: ID, - accounts: accounts::MintToAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - mint: env.mint_pda, - destination: env.ata, - authority: env.payer.pubkey(), - system_program: system_program::ID, - } - .to_account_metas(Some(true)), - data: MintTo { amount }.data(), - }; - - env.rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - // Verify the account exists and has data - let ata_data = env.rpc.get_account(env.ata).await.unwrap().unwrap(); - assert!(!ata_data.data.is_empty(), "ATA account should have data"); -} -``` - diff --git a/snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx b/snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx deleted file mode 100644 index 53a52aa5..00000000 --- a/snippets/code-snippets/light-token/mint-to/native-program/full-example.mdx +++ /dev/null @@ -1,187 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::MintToCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn mint_to_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [mint, destination, authority, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - let amount = u64::from_le_bytes( - data.try_into() - .map_err(|_| ProgramError::InvalidInstructionData)?, - ); - - // Mint tokens to destination account - MintToCpi { - mint: mint.clone(), - destination: destination.clone(), - amount, - authority: authority.clone(), - system_program: system_program.clone(), - fee_payer: None, - max_top_up: None, - } - .invoke() -} - -pub fn mint_to_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [mint, destination, authority, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 9 { - return Err(ProgramError::InvalidInstructionData); - } - - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let bump = data[8]; - let signer_seeds = authority_seeds!(bump); - - MintToCpi { - mint: mint.clone(), - destination: destination.clone(), - amount, - authority: authority.clone(), - system_program: system_program.clone(), - fee_payer: None, - max_top_up: None, - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use light_client::rpc::Rpc; -use light_token::instruction::derive_token_ata; -use shared::{ - build_create_ata_signed_cpi_ix, build_mint_to_cpi_ix, - build_mint_to_signed_cpi_ix, create_ata, create_test_rpc, - get_authority_pda, setup_empty_ata, setup_mint_with_pda_authority, - SetupContext, -}; -use solana_sdk::signer::Signer; - -#[tokio::test(flavor = "multi_thread")] -async fn mint_to_cpi() { - // Setup: create mint and empty ATA - let SetupContext { - mut rpc, - payer, - mint, - ata, - .. - } = setup_empty_ata().await; - - let amount = 1_000_000u64; - - let ix = build_mint_to_cpi_ix(mint, ata, payer.pubkey(), amount); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} - -/// Mints tokens via CPI with PDA authority. -#[tokio::test(flavor = "multi_thread")] -async fn mint_to_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_authority, authority_bump) = get_authority_pda(); - let decimals = 9u8; - - // Create mint with PDA authority - let mint = setup_mint_with_pda_authority( - &mut rpc, - &payer, - pda_authority, - decimals, - ) - .await; - - // Create ATA for the payer - let ata = create_ata(&mut rpc, &payer, payer.pubkey(), mint).await; - - // Mint tokens using signed CPI - let amount = 1_000_000u64; - let mint_to_ix = build_mint_to_signed_cpi_ix( - mint, - ata, - pda_authority, - amount, - authority_bump, - ); - - rpc.create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} - -/// Mints tokens using CPI-style ATA creation. -#[tokio::test(flavor = "multi_thread")] -async fn mint_to_signed_cpi_with_ata_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_authority, authority_bump) = get_authority_pda(); - let decimals = 9u8; - - // Create mint with PDA authority - let mint = setup_mint_with_pda_authority( - &mut rpc, - &payer, - pda_authority, - decimals, - ) - .await; - - // Create ATA using the signed CPI instruction - let (ata, ata_bump) = derive_token_ata(&payer.pubkey(), &mint); - let create_ata_ix = build_create_ata_signed_cpi_ix( - payer.pubkey(), - mint, - payer.pubkey(), - ata, - ata_bump, - authority_bump, - ); - - rpc.create_and_send_transaction( - &[create_ata_ix], - &payer.pubkey(), - &[&payer], - ) - .await - .unwrap(); - - // Mint tokens using signed CPI - let amount = 1_000_000u64; - let mint_to_ix = build_mint_to_signed_cpi_ix( - mint, - ata, - pda_authority, - amount, - authority_bump, - ); - - rpc.create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} -``` - diff --git a/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx deleted file mode 100644 index bae31171..00000000 --- a/snippets/code-snippets/light-token/revoke/anchor-program/full-example.mdx +++ /dev/null @@ -1,91 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::RevokeCpi; - -declare_id!("G3ph4MK5qaSdxYnfxToETg31AHEMMqVhPuMRgBhk38tQ"); - -#[program] -pub mod light_token_anchor_revoke { - use super::*; - - pub fn revoke(ctx: Context) -> Result<()> { - RevokeCpi { - token_account: ctx.accounts.token_account.to_account_info(), - owner: ctx.accounts.owner.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct RevokeAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub token_account: AccountInfo<'info>, - pub owner: Signer<'info>, - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::{Approve, LIGHT_TOKEN_PROGRAM_ID}; -use light_token_anchor_revoke::{accounts, instruction::Revoke, ID}; -use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer, system_program}; -use test_utils::{mint_tokens, setup_test_env}; - -#[tokio::test] -async fn test_revoke() { - let mut env = setup_test_env("light_token_anchor_revoke", ID).await; - - // Mint tokens first - let mint_amount = 1_000_000u64; - mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, mint_amount).await; - - // Approve delegate first using SDK - let delegate = Keypair::new(); - let approve_ix = Approve { - token_account: env.ata, - delegate: delegate.pubkey(), - owner: env.payer.pubkey(), - amount: 500_000, - } - .instruction() - .unwrap(); - - env.rpc - .create_and_send_transaction(&[approve_ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - // Call the anchor program to revoke delegation - let ix = Instruction { - program_id: ID, - accounts: accounts::RevokeAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - token_account: env.ata, - owner: env.payer.pubkey(), - system_program: system_program::ID, - } - .to_account_metas(Some(true)), - data: Revoke {}.data(), - }; - - let sig = env - .rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/revoke/native-program/full-example.mdx b/snippets/code-snippets/light-token/revoke/native-program/full-example.mdx deleted file mode 100644 index 574e340d..00000000 --- a/snippets/code-snippets/light-token/revoke/native-program/full-example.mdx +++ /dev/null @@ -1,107 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::RevokeCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn revoke_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { - let [token_account, owner, system_program, _token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - // Revoke delegate authority from token account - RevokeCpi { - token_account: token_account.clone(), - owner: owner.clone(), - system_program: system_program.clone(), - } - .invoke() -} - -pub fn revoke_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [token_account, owner, system_program, _token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.is_empty() { - return Err(ProgramError::InvalidInstructionData); - } - - let bump = data[0]; - let signer_seeds = authority_seeds!(bump); - - RevokeCpi { - token_account: token_account.clone(), - owner: owner.clone(), - system_program: system_program.clone(), - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use light_client::rpc::Rpc; -use shared::{ - build_revoke_cpi_ix, build_revoke_signed_cpi_ix, create_test_rpc, - get_authority_pda, setup, setup_pda_owned_ata, SetupContext, -}; -use solana_sdk::{signature::Keypair, signer::Signer}; - -#[tokio::test(flavor = "multi_thread")] -async fn revoke_cpi() { - // Setup: create mint, ATA with tokens, and approve delegate - let SetupContext { - mut rpc, - payer, - ata, - .. - } = setup().await; - - let ix = build_revoke_cpi_ix(ata, payer.pubkey()); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -async fn revoke_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_owner, bump) = get_authority_pda(); - - let (_mint, ata) = - setup_pda_owned_ata(&mut rpc, &payer, pda_owner, 1_000_000).await; - - let delegate = Keypair::new(); - let approve_ix = shared::build_approve_signed_cpi_ix( - ata, - delegate.pubkey(), - pda_owner, - 500_000, - bump, - ); - - rpc.create_and_send_transaction(&[approve_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - let ix = build_revoke_signed_cpi_ix(ata, pda_owner, bump); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} -``` - diff --git a/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx deleted file mode 100644 index 3f51ad98..00000000 --- a/snippets/code-snippets/light-token/thaw/anchor-program/full-example.mdx +++ /dev/null @@ -1,89 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::ThawCpi; - -declare_id!("7j94EF5hSkDLf7R26bjrd8Qc6s3oLAQpcKiF3re8JYw9"); - -#[program] -pub mod light_token_anchor_thaw { - use super::*; - - pub fn thaw(ctx: Context) -> Result<()> { - ThawCpi { - token_account: ctx.accounts.token_account.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - freeze_authority: ctx.accounts.freeze_authority.to_account_info(), - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct ThawAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub token_account: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub mint: AccountInfo<'info>, - pub freeze_authority: Signer<'info>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::{Freeze, LIGHT_TOKEN_PROGRAM_ID}; -use light_token_anchor_thaw::{accounts, instruction::Thaw, ID}; -use solana_sdk::{instruction::Instruction, signer::Signer}; -use test_utils::{mint_tokens, setup_test_env_with_freeze}; - -#[tokio::test] -async fn test_thaw() { - let mut env = setup_test_env_with_freeze("light_token_anchor_thaw", ID).await; - - // Mint tokens first - mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, 1_000_000).await; - - // Freeze account first using SDK - let freeze_ix = Freeze { - token_account: env.ata, - mint: env.mint_pda, - freeze_authority: env.freeze_authority, - } - .instruction() - .unwrap(); - - env.rpc - .create_and_send_transaction(&[freeze_ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - // Call the anchor program to thaw account - let ix = Instruction { - program_id: ID, - accounts: accounts::ThawAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - token_account: env.ata, - mint: env.mint_pda, - freeze_authority: env.freeze_authority, - } - .to_account_metas(Some(true)), - data: Thaw {}.data(), - }; - - let sig = env - .rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/thaw/native-program/full-example.mdx b/snippets/code-snippets/light-token/thaw/native-program/full-example.mdx deleted file mode 100644 index aa8cb23c..00000000 --- a/snippets/code-snippets/light-token/thaw/native-program/full-example.mdx +++ /dev/null @@ -1,111 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::ThawCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn thaw_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { - let [token_account, mint, freeze_authority, _token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - // Thaw frozen token account. freeze_authority must match mint creation - ThawCpi { - token_account: token_account.clone(), - mint: mint.clone(), - freeze_authority: freeze_authority.clone(), - } - .invoke() -} - -pub fn thaw_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [token_account, mint, freeze_authority, _token_program] = accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.is_empty() { - return Err(ProgramError::InvalidInstructionData); - } - - let bump = data[0]; - let signer_seeds = authority_seeds!(bump); - - ThawCpi { - token_account: token_account.clone(), - mint: mint.clone(), - freeze_authority: freeze_authority.clone(), - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use light_client::rpc::Rpc; -use shared::{ - build_freeze_signed_cpi_ix, build_thaw_cpi_ix, build_thaw_signed_cpi_ix, - create_ata, create_test_rpc, get_authority_pda, setup_frozen, - setup_mint_with_tokens, SetupContext, -}; -use solana_sdk::signer::Signer; - -#[tokio::test(flavor = "multi_thread")] -async fn thaw_cpi() { - // Setup: create mint, ATA, and freeze account - let SetupContext { - mut rpc, - payer, - mint, - ata, - .. - } = setup_frozen().await; - - let ix = build_thaw_cpi_ix(ata, mint, payer.pubkey()); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -async fn thaw_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_authority, bump) = get_authority_pda(); - - let (mint, _) = setup_mint_with_tokens( - &mut rpc, - &payer, - payer.pubkey(), - Some(pda_authority), - 9, - vec![], - ) - .await; - - let ata = create_ata(&mut rpc, &payer, payer.pubkey(), mint).await; - - // Freeze first using PDA authority - let freeze_ix = build_freeze_signed_cpi_ix(ata, mint, pda_authority, bump); - rpc.create_and_send_transaction(&[freeze_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); - - // Then thaw using PDA authority - let thaw_ix = build_thaw_signed_cpi_ix(ata, mint, pda_authority, bump); - rpc.create_and_send_transaction(&[thaw_ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} -``` - diff --git a/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx deleted file mode 100644 index 63546a9b..00000000 --- a/snippets/code-snippets/light-token/transfer-checked/anchor-program/full-example.mdx +++ /dev/null @@ -1,103 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::TransferCheckedCpi; - -declare_id!("HXmfewpozFdxhM8BayL9v5541gwoGMXTrUoip5KySs2f"); - -#[program] -pub mod light_token_anchor_transfer_checked { - use super::*; - - pub fn transfer_checked( - ctx: Context, - amount: u64, - decimals: u8, - ) -> Result<()> { - TransferCheckedCpi { - source: ctx.accounts.source.to_account_info(), - mint: ctx.accounts.mint.to_account_info(), - destination: ctx.accounts.destination.to_account_info(), - amount, - decimals, - authority: ctx.accounts.authority.to_account_info(), - system_program: ctx.accounts.system_program.to_account_info(), - max_top_up: None, - fee_payer: None, - } - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct TransferCheckedAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub source: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - pub mint: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub destination: AccountInfo<'info>, - pub authority: Signer<'info>, - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_transfer_checked::{accounts, instruction::TransferChecked, ID}; -use solana_sdk::{instruction::Instruction, signature::Keypair, signer::Signer, system_program}; -use test_utils::{create_ata_for_owner, mint_tokens, setup_test_env}; - -#[tokio::test] -async fn test_transfer_checked() { - let mut env = setup_test_env("light_token_anchor_transfer_checked", ID).await; - mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, 1_000_000).await; - - // Create destination ATA for recipient - let recipient = Keypair::new(); - let dest_ata = - create_ata_for_owner(&mut env.rpc, &env.payer, &recipient.pubkey(), &env.mint_pda).await; - - // TransferChecked validates decimals match the mint's decimals. - // Only use for Light->Light transfers. - // Use TransferInterface for all other transfers (Light, SPL or Token-2022). - let transfer_amount = 100_000u64; - let decimals = 9u8; - - let ix = Instruction { - program_id: ID, - accounts: accounts::TransferCheckedAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - source: env.ata, - mint: env.mint_pda, - destination: dest_ata, - authority: env.payer.pubkey(), - system_program: system_program::ID, - } - .to_account_metas(Some(true)), - data: TransferChecked { - amount: transfer_amount, - decimals, - } - .data(), - }; - - let sig = env - .rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx deleted file mode 100644 index 646df352..00000000 --- a/snippets/code-snippets/light-token/transfer-checked/native-program/full-example.mdx +++ /dev/null @@ -1,180 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::TransferCheckedCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn transfer_checked_invoke( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [source, mint, destination, authority, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 9 { - return Err(ProgramError::InvalidInstructionData); - } - - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let decimals = data[8]; - - // TransferChecked validates decimals. Only for Light->Light. Use TransferInterface for SPL/T22 - TransferCheckedCpi { - source: source.clone(), - mint: mint.clone(), - destination: destination.clone(), - amount, - decimals, - authority: authority.clone(), - system_program: system_program.clone(), - max_top_up: None, - fee_payer: None, - } - .invoke() -} - -pub fn transfer_checked_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [source, mint, destination, authority, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 10 { - return Err(ProgramError::InvalidInstructionData); - } - - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let decimals = data[8]; - let bump = data[9]; - let signer_seeds = authority_seeds!(bump); - - TransferCheckedCpi { - source: source.clone(), - mint: mint.clone(), - destination: destination.clone(), - amount, - decimals, - authority: authority.clone(), - system_program: system_program.clone(), - max_top_up: None, - fee_payer: None, - } - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use light_client::rpc::Rpc; -use shared::{ - build_transfer_checked_cpi_ix, build_transfer_checked_signed_cpi_ix, - create_ata, create_test_rpc, get_authority_pda, setup, setup_pda_owned_ata, - SetupContext, -}; -use solana_sdk::{signature::Keypair, signer::Signer}; - -#[tokio::test(flavor = "multi_thread")] -async fn transfer_checked_cpi() { - let SetupContext { - mut rpc, - payer, - mint, - ata, - .. - } = setup().await; - - let transfer_amount = 500_000u64; - - let recipient = Keypair::new(); - let recipient_ata = - create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; - - let ix = build_transfer_checked_cpi_ix( - ata, - mint, - recipient_ata, - payer.pubkey(), - transfer_amount, - 9, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -async fn transfer_checked_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_authority, bump) = get_authority_pda(); - let initial_amount = 1_000_000u64; - - let (mint, pda_ata) = - setup_pda_owned_ata(&mut rpc, &payer, pda_authority, initial_amount) - .await; - - let recipient = Keypair::new(); - let recipient_ata = - create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; - - let transfer_amount = 500_000u64; - let ix = build_transfer_checked_signed_cpi_ix( - pda_ata, - mint, - recipient_ata, - pda_authority, - transfer_amount, - 9, - bump, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} - -/// Tests transferring the exact balance from a token account. -#[tokio::test(flavor = "multi_thread")] -async fn transfer_checked_exact_balance_cpi() { - let SetupContext { - mut rpc, - payer, - mint, - ata, - .. - } = setup().await; - - let exact_balance = 1_000_000u64; - - let recipient = Keypair::new(); - let recipient_ata = - create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; - - let ix = build_transfer_checked_cpi_ix( - ata, - mint, - recipient_ata, - payer.pubkey(), - exact_balance, - 9, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} -``` - diff --git a/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx deleted file mode 100644 index 3c6de46b..00000000 --- a/snippets/code-snippets/light-token/transfer-interface/anchor-program/full-example.mdx +++ /dev/null @@ -1,107 +0,0 @@ - -```rust lib.rs -#![allow(unexpected_cfgs, deprecated)] - -use anchor_lang::prelude::*; -use light_token::instruction::TransferInterfaceCpi; - -declare_id!("3rb6sG4jiYNLZC8jo8kLsFHpxr2Ci8e8Hh8UmeCMZmUV"); - -#[program] -pub mod light_token_anchor_transfer_interface { - use super::*; - - pub fn transfer(ctx: Context, amount: u64, decimals: u8) -> Result<()> { - TransferInterfaceCpi::new( - amount, - decimals, - ctx.accounts.source.to_account_info(), - ctx.accounts.destination.to_account_info(), - ctx.accounts.authority.to_account_info(), - ctx.accounts.payer.to_account_info(), - ctx.accounts.cpi_authority.to_account_info(), - ctx.accounts.system_program.to_account_info(), - ) - .invoke()?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct TransferAccounts<'info> { - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub source: AccountInfo<'info>, - /// CHECK: Validated by light-token CPI - #[account(mut)] - pub destination: AccountInfo<'info>, - pub authority: Signer<'info>, - #[account(mut)] - pub payer: Signer<'info>, - /// CHECK: Validated by light-token CPI - pub cpi_authority: AccountInfo<'info>, - pub system_program: Program<'info, System>, -} -``` - -```rust test.rs -use anchor_lang::system_program; -use anchor_lang::{InstructionData, ToAccountMetas}; -use light_program_test::Rpc; -use light_token::instruction::LIGHT_TOKEN_PROGRAM_ID; -use light_token_anchor_transfer_interface::{accounts, instruction::Transfer, ID}; -use light_token_types::CPI_AUTHORITY_PDA; -use solana_sdk::{ - instruction::Instruction, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, -}; -use test_utils::{create_ata_for_owner, mint_tokens, setup_test_env}; - -#[tokio::test] -async fn test_transfer() { - let mut env = setup_test_env("light_token_anchor_transfer_interface", ID).await; - mint_tokens(&mut env.rpc, &env.payer, env.mint_pda, env.ata, 1_000_000).await; - - // Create destination ATA for recipient - let recipient = Keypair::new(); - let dest_ata = - create_ata_for_owner(&mut env.rpc, &env.payer, &recipient.pubkey(), &env.mint_pda).await; - - // Transfers tokens between accounts (SPL, Token-2022, or Light) in a single call. - let transfer_amount = 100_000u64; - let decimals = 9u8; - let cpi_authority_pda = Pubkey::new_from_array(CPI_AUTHORITY_PDA); - - let ix = Instruction { - program_id: ID, - accounts: accounts::TransferAccounts { - light_token_program: LIGHT_TOKEN_PROGRAM_ID, - source: env.ata, - destination: dest_ata, - authority: env.payer.pubkey(), - payer: env.payer.pubkey(), - cpi_authority: cpi_authority_pda, - system_program: system_program::ID, - } - .to_account_metas(Some(true)), - data: Transfer { - amount: transfer_amount, - decimals, - } - .data(), - }; - - let sig = env - .rpc - .create_and_send_transaction(&[ix], &env.payer.pubkey(), &[&env.payer]) - .await - .unwrap(); - - println!("Tx: {}", sig); -} -``` - diff --git a/snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx b/snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx deleted file mode 100644 index 53d51fa8..00000000 --- a/snippets/code-snippets/light-token/transfer-interface/native-program/full-example.mdx +++ /dev/null @@ -1,185 +0,0 @@ - -```rust instruction.rs -use super::authority_seeds; -use light_token::instruction::TransferInterfaceCpi; -use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, - program_error::ProgramError, -}; - -pub fn transfer_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [source, destination, authority, payer, light_token_authority, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 9 { - return Err(ProgramError::InvalidInstructionData); - } - - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let decimals = data[8]; - - // Transfer tokens between accounts (SPL, Token-2022, or Light) - TransferInterfaceCpi::new( - amount, - decimals, - source.clone(), - destination.clone(), - authority.clone(), - payer.clone(), - light_token_authority.clone(), - system_program.clone(), - ) - .invoke() -} - -pub fn transfer_invoke_signed( - accounts: &[AccountInfo], - data: &[u8], -) -> ProgramResult { - let [source, destination, authority, payer, light_token_authority, system_program, _token_program] = - accounts - else { - return Err(ProgramError::NotEnoughAccountKeys); - }; - - if data.len() < 10 { - return Err(ProgramError::InvalidInstructionData); - } - - let amount = u64::from_le_bytes(data[0..8].try_into().unwrap()); - let decimals = data[8]; - let bump = data[9]; - let signer_seeds = authority_seeds!(bump); - - TransferInterfaceCpi::new( - amount, - decimals, - source.clone(), - destination.clone(), - authority.clone(), - payer.clone(), - light_token_authority.clone(), - system_program.clone(), - ) - .invoke_signed(&[signer_seeds]) -} -``` - -```rust test.rs -mod shared; - -use light_client::rpc::Rpc; -use light_token::instruction::cpi_authority; -use shared::{ - build_transfer_interface_cpi_ix, build_transfer_interface_signed_cpi_ix, - create_ata, create_test_rpc, get_authority_pda, setup, setup_pda_owned_ata, - SetupContext, -}; -use solana_sdk::{signature::Keypair, signer::Signer}; - -#[tokio::test(flavor = "multi_thread")] -async fn transfer_interface_cpi() { - let SetupContext { - mut rpc, - payer, - mint, - ata, - .. - } = setup().await; - - let transfer_amount = 500_000u64; - - // Setup: create mint and token accounts - let recipient = Keypair::new(); - let recipient_ata = - create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; - - // 1. Transfer from source to destination - let ix = build_transfer_interface_cpi_ix( - ata, - recipient_ata, - payer.pubkey(), - payer.pubkey(), - cpi_authority(), - transfer_amount, - 9, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -async fn transfer_interface_signed_cpi() { - let mut rpc = create_test_rpc().await; - let payer = rpc.get_payer().insecure_clone(); - - let (pda_authority, bump) = get_authority_pda(); - let initial_amount = 1_000_000u64; - - // Setup: create mint and token accounts - let (mint, pda_ata) = - setup_pda_owned_ata(&mut rpc, &payer, pda_authority, initial_amount) - .await; - - let recipient = Keypair::new(); - let recipient_ata = - create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; - - let transfer_amount = 500_000u64; - // 1. Transfer from source to destination - let ix = build_transfer_interface_signed_cpi_ix( - pda_ata, - recipient_ata, - pda_authority, - payer.pubkey(), - cpi_authority(), - transfer_amount, - 9, - bump, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} - -/// Transfers exact balance from token account. -#[tokio::test(flavor = "multi_thread")] -async fn transfer_exact_balance_cpi() { - let SetupContext { - mut rpc, - payer, - mint, - ata, - .. - } = setup().await; - - // The setup() function creates an ATA with 1_000_000 tokens - let exact_balance = 1_000_000u64; - - let recipient = Keypair::new(); - let recipient_ata = - create_ata(&mut rpc, &payer, recipient.pubkey(), mint).await; - - // Transfer the exact balance, leaving source with 0 - let ix = build_transfer_interface_cpi_ix( - ata, - recipient_ata, - payer.pubkey(), - payer.pubkey(), - cpi_authority(), - exact_balance, - 9, - ); - - rpc.create_and_send_transaction(&[ix], &payer.pubkey(), &[&payer]) - .await - .unwrap(); -} -``` - From 307a015839f7c5322d525c4d67bb2ef15b3a5439 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 2 Feb 2026 14:08:23 +0000 Subject: [PATCH 15/21] fix deps --- light-token/cookbook/approve-revoke.mdx | 2 +- light-token/cookbook/burn.mdx | 2 +- light-token/cookbook/close-token-account.mdx | 3 ++- light-token/cookbook/create-ata.mdx | 3 ++- light-token/cookbook/create-mint.mdx | 2 +- light-token/cookbook/create-token-account.mdx | 3 ++- light-token/cookbook/freeze-thaw.mdx | 3 ++- light-token/cookbook/mint-to.mdx | 2 +- light-token/cookbook/transfer-checked.mdx | 3 ++- light-token/cookbook/transfer-interface.mdx | 2 +- light-token/cookbook/wrap-unwrap.mdx | 4 ++-- snippets/setup/rust-install-dependencies.mdx | 8 ++++---- 12 files changed, 21 insertions(+), 16 deletions(-) diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx index 25c2a973..26fa1b1c 100644 --- a/light-token/cookbook/approve-revoke.mdx +++ b/light-token/cookbook/approve-revoke.mdx @@ -131,7 +131,7 @@ import RevokeAnchorProgramCode from "/snippets/code-snippets/light-token/revoke/ Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx index 30dd1102..ca6b79ca 100644 --- a/light-token/cookbook/burn.mdx +++ b/light-token/cookbook/burn.mdx @@ -48,7 +48,7 @@ Compare to SPL: Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/close-token-account.mdx b/light-token/cookbook/close-token-account.mdx index ea0f9078..7e294438 100644 --- a/light-token/cookbook/close-token-account.mdx +++ b/light-token/cookbook/close-token-account.mdx @@ -15,6 +15,7 @@ import { lightCloseAccountRustCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustInstructionCode from "/snippets/code-snippets/light-token/close-token-account/rust-client/instruction.mdx"; + 1. Closing a Light Token account transfers remaining lamports to a destination account and the rent sponsor can reclaim sponsored rent. 2. Light token accounts can be closed by the owner. @@ -52,7 +53,7 @@ Compare to SPL: Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index 58e0821c..8c856122 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -27,6 +27,7 @@ import ActionCode from "/snippets/code-snippets/light-token/create-ata/action.md import InstructionCode from "/snippets/code-snippets/light-token/create-ata/instruction.mdx"; import RustActionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-ata/rust-client/instruction.mdx"; + 1. Associated Light Token accounts can hold token balances of light, SPL, or Token 2022 mints. 2. Light-ATAs are on-chain accounts like SPL ATA's, but the light token program sponsors the rent-exemption cost for you. @@ -104,7 +105,7 @@ Compare to SPL: Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx index 9aeae213..8ae46ce2 100644 --- a/light-token/cookbook/create-mint.mdx +++ b/light-token/cookbook/create-mint.mdx @@ -120,7 +120,7 @@ Compare to SPL: Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/create-token-account.mdx b/light-token/cookbook/create-token-account.mdx index 17652f0f..f992d275 100644 --- a/light-token/cookbook/create-token-account.mdx +++ b/light-token/cookbook/create-token-account.mdx @@ -20,6 +20,7 @@ import { lightCreateTokenAccountCpiCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustInstructionCode from "/snippets/code-snippets/light-token/create-token-account/rust-client/instruction.mdx"; + 1. Light token accounts are Solana accounts that hold token balances of light, SPL, or Token 2022 mints. 2. Light token accounts are on-chain accounts like SPL ATA’s, but the light token program sponsors the rent-exemption cost for you. @@ -55,7 +56,7 @@ Compare to SPL: Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/freeze-thaw.mdx b/light-token/cookbook/freeze-thaw.mdx index 75270508..2fbcc3c9 100644 --- a/light-token/cookbook/freeze-thaw.mdx +++ b/light-token/cookbook/freeze-thaw.mdx @@ -17,6 +17,7 @@ import { } from "/snippets/code-samples/code-compare-snippets.jsx"; import FreezeInstructionCode from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-instruction.mdx"; import ThawInstructionCode from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-instruction.mdx"; + 1. Freeze prevents all transfers or token burns from a specific Light Token account. 2. Once frozen, the account cannot send tokens, receive tokens, or be closed until it is thawed. 3. Thaw re-enables transfers on a frozen Light Token account. @@ -64,7 +65,7 @@ import ThawInstructionCode from "/snippets/code-snippets/light-token/freeze-thaw Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/mint-to.mdx b/light-token/cookbook/mint-to.mdx index ec8c022f..822d5c8a 100644 --- a/light-token/cookbook/mint-to.mdx +++ b/light-token/cookbook/mint-to.mdx @@ -97,7 +97,7 @@ Compare to SPL: Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/transfer-checked.mdx b/light-token/cookbook/transfer-checked.mdx index d28c932f..06b747f9 100644 --- a/light-token/cookbook/transfer-checked.mdx +++ b/light-token/cookbook/transfer-checked.mdx @@ -10,6 +10,7 @@ keywords: ["transfer tokens solana", "checked transfer", "decimal validation"] import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; import RustActionCode from "/snippets/code-snippets/light-token/transfer-checked/rust-client/action.mdx"; import RustInstructionCode from "/snippets/code-snippets/light-token/transfer-checked/rust-client/instruction.mdx"; + 1. TransferChecked validates that the decimals parameter matches the mint's decimals. 2. Use for Light→Light transfers when you need decimal verification. 3. For transfers involving SPL or Token 2022 accounts, use [Transfer Interface](/light-token/cookbook/transfer-interface) instead. @@ -30,7 +31,7 @@ import RustInstructionCode from "/snippets/code-snippets/light-token/transfer-ch Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/transfer-interface.mdx b/light-token/cookbook/transfer-interface.mdx index 98dfe9a0..d4083af9 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -126,7 +126,7 @@ The example transfers Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/light-token/cookbook/wrap-unwrap.mdx b/light-token/cookbook/wrap-unwrap.mdx index 0d5b3f98..72728b2a 100644 --- a/light-token/cookbook/wrap-unwrap.mdx +++ b/light-token/cookbook/wrap-unwrap.mdx @@ -92,7 +92,7 @@ import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-c Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -119,7 +119,7 @@ import TokenClientPrerequisites from "/snippets/light-token-guides/light-token-c Find the full example including shared test utilities in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). + [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). diff --git a/snippets/setup/rust-install-dependencies.mdx b/snippets/setup/rust-install-dependencies.mdx index 3509c284..c1de57df 100644 --- a/snippets/setup/rust-install-dependencies.mdx +++ b/snippets/setup/rust-install-dependencies.mdx @@ -1,8 +1,8 @@ ```toml Cargo.toml [dependencies] light-token = "0.4.0" -light-client = "0.19.0" -solana-sdk = "2.2" -borsh = "0.10" -tokio = { version = "1.36", features = ["full"] } +light-client = { version = "0.19.0", features = ["v2"] } +solana-sdk = "2" +borsh = "0.10.4" +tokio = { version = "1", features = ["full"] } ``` From d169d4d0564ba68f7a5e2ab7c580e9b09d974454 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 2 Feb 2026 14:30:03 +0000 Subject: [PATCH 16/21] fix dep --- client-library/client-guide.mdx | 16 +- docs.json | 1 - references/compressed-pda.md | 177 --------------- references/light-token-terminology.mdx | 146 ------------- references/light-token.md | 117 ---------- references/sdk-reference.md | 54 ----- references/testing.md | 249 --------------------- references/zk-nullifiers.md | 288 ------------------------- resources/cli-installation.mdx | 2 +- 9 files changed, 9 insertions(+), 1041 deletions(-) delete mode 100644 references/compressed-pda.md delete mode 100644 references/light-token-terminology.mdx delete mode 100644 references/light-token.md delete mode 100644 references/sdk-reference.md delete mode 100644 references/testing.md delete mode 100644 references/zk-nullifiers.md diff --git a/client-library/client-guide.mdx b/client-library/client-guide.mdx index 89bd8cc7..deb82b16 100644 --- a/client-library/client-guide.mdx +++ b/client-library/client-guide.mdx @@ -127,8 +127,8 @@ Use the [API documentation](https://lightprotocol.github.io/light-protocol/) to ```bash npm install --save \ - @lightprotocol/stateless.js@beta \ - @lightprotocol/compressed-token@beta \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ @solana/web3.js ``` @@ -138,8 +138,8 @@ npm install --save \ ```bash yarn add \ - @lightprotocol/stateless.js@beta \ - @lightprotocol/compressed-token@beta \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ @solana/web3.js ``` @@ -149,8 +149,8 @@ yarn add \ ```bash pnpm add \ - @lightprotocol/stateless.js@beta \ - @lightprotocol/compressed-token@beta \ + @lightprotocol/stateless.js \ + @lightprotocol/compressed-token \ @solana/web3.js ``` @@ -182,7 +182,7 @@ const rpc = createRpc('https://devnet.helius-rpc.com/?api-key=YOUR_API_KEY'); 1. Install the CLI ```bash -npm i -g @lightprotocol/zk-compression-cli@beta +npm i -g @lightprotocol/zk-compression-cli ``` 2. Start a local Solana test validator, photon indexer, and prover server on default ports 8899, 8784, and 3001. @@ -253,7 +253,7 @@ client.payer = read_keypair_file("~/.config/solana/id.json")?; 1. Install the CLI ```bash -npm i -g @lightprotocol/zk-compression-cli@beta +npm i -g @lightprotocol/zk-compression-cli ``` 2. Start a single-node Solana cluster, an RPC node, and a prover node at ports 8899, 8784, and 3001. diff --git a/docs.json b/docs.json index 68dd3cd2..66e72957 100644 --- a/docs.json +++ b/docs.json @@ -264,7 +264,6 @@ "references/whitepaper", "references/node-operators", "references/terminology", - "references/light-token-terminology", "references/migration-v1-to-v2", "support", "references/security" diff --git a/references/compressed-pda.md b/references/compressed-pda.md deleted file mode 100644 index dd53493e..00000000 --- a/references/compressed-pda.md +++ /dev/null @@ -1,177 +0,0 @@ -# Compressed PDAs - -The base library to use Compressed Accounts in Solana on-chain Rust and Anchor programs. -Compressed accounts do not require rent-exemption, which makes them suitable for: -- user owned accounts -- not config accounts which are often read -- not pool accounts, since compressed accounts cannot be used concurrently - -Compressed Accounts store state as account hashes in State Merkle trees. -and unique addresses in Address Merkle trees. -Validity proofs (zero-knowledge proofs) verify that compressed account -state exists and new addresses do not exist yet. - -- No rent exemption payment required. -- Constant 128-byte validity proof per transaction for one or multiple compressed accounts and addresses. -- Compressed account data is sent as instruction data when accessed. -- State and address trees are managed by the protocol. - -For full program examples, see the [Program Examples](https://github.com/Lightprotocol/program-examples). -For detailed documentation, visit [zkcompression.com](https://www.zkcompression.com/). -For pinocchio solana program development see [`light-sdk-pinocchio`](https://docs.rs/light-sdk-pinocchio). -For rust client development see [`light-client`](https://docs.rs/light-client). -For rust program testing see [`light-program-test`](https://docs.rs/light-program-test). -For local test validator with light system programs see [Light CLI](https://www.npmjs.com/package/@lightprotocol/zk-compression-cli). - -### Difference to Light-Accounts (Light-PDA) -Light-PDA's are Solana accounts with sponsored rent-exemption. -There is no proof required for interactions with Light-PDA's which makes -them suitable for Defi Usecases. Compressed PDA's don't require rent-exemption, -but a proof for interactions. - -## Using Compressed Accounts in Solana Programs - -1. [`Instruction`](https://docs.rs/light-sdk/latest/light_sdk/instruction/) - - `CompressedAccountMeta` - Compressed account metadata structs for instruction data. - - `PackedAccounts` - Abstraction to prepare accounts offchain for instructions with compressed accounts. - - `ValidityProof` - Proves that new addresses don't exist yet, and compressed account state exists. -2. Compressed Account in Program - - [`LightAccount`](https://docs.rs/light-sdk/latest/light_sdk/account/) - Compressed account abstraction similar to anchor Account. - - [`derive_address`](https://docs.rs/light-sdk/latest/light_sdk/address/) - Create a compressed account address. - - `LightDiscriminator` - DeriveMacro to derive a compressed account discriminator. -3. [`Cpi`](https://docs.rs/light-sdk/latest/light_sdk/cpi/) - - `CpiAccounts` - Prepare accounts to cpi the light system program. - - `LightSystemProgramCpi` - Prepare instruction data to cpi the light system program. - - [`InvokeLightSystemProgram::invoke`](https://docs.rs/light-sdk/latest/light_sdk/cpi/) - Invoke the light system program via cpi. - -## Client Program Interaction Flow - -```text - ├─ Client - │ ├─ Get ValidityProof from RPC. - │ ├─ pack accounts with PackedAccounts into PackedAddressTreeInfo and PackedStateTreeInfo. - │ ├─ pack CompressedAccountMeta. - │ ├─ Build Instruction from PackedAccounts and CompressedAccountMetas. - │ └─ Send transaction. - │ - └─ Custom Program - ├─ CpiAccounts parse accounts consistent with PackedAccounts. - ├─ LightAccount instantiates from CompressedAccountMeta. - │ - └─ Light System Program CPI - ├─ Verify ValidityProof. - ├─ Update State Merkle tree. - ├─ Update Address Merkle tree. - └─ Complete atomic state transition. -``` - -## Features - -1. `anchor` - Derives AnchorSerialize, AnchorDeserialize instead of BorshSerialize, BorshDeserialize. - -2. `v2` - - available on devnet, localnet, and light-program-test. - - Support for optimized v2 light system program instructions. - -3. `cpi-context` - Enables CPI context operations for batched compressed account operations. - - available on devnet, localnet, and light-program-test. - - Enables the use of one validity proof across multiple cpis from different programs in one instruction. - - For example spending compressed tokens (owned by the ctoken program) and updating a compressed pda (owned by a custom program) - with one validity proof. - - An instruction should not use more than one validity proof. - - Requires the v2 feature. - -## Example: Create a Compressed Account - -```rust -use anchor_lang::{prelude::*, Discriminator}; -use light_sdk::{ - account::LightAccount, - address::v1::derive_address, - cpi::{v1::LightSystemProgramCpi, CpiAccounts, InvokeLightSystemProgram, LightCpiInstruction}, - derive_light_cpi_signer, - instruction::{account_meta::CompressedAccountMeta, PackedAddressTreeInfo}, - CpiSigner, LightDiscriminator, LightHasher, ValidityProof, -}; - -declare_id!("2tzfijPBGbrR5PboyFUFKzfEoLTwdDSHUjANCw929wyt"); - -pub const LIGHT_CPI_SIGNER: CpiSigner = - derive_light_cpi_signer!("2tzfijPBGbrR5PboyFUFKzfEoLTwdDSHUjANCw929wyt"); - -#[program] -pub mod counter { - - use super::*; - - pub fn create_compressed_account<'info>( - ctx: Context<'_, '_, '_, 'info, CreateCompressedAccount<'info>>, - proof: ValidityProof, - address_tree_info: PackedAddressTreeInfo, - output_tree_index: u8, - ) -> Result<()> { - let light_cpi_accounts = CpiAccounts::new( - ctx.accounts.fee_payer.as_ref(), - ctx.remaining_accounts, - crate::LIGHT_CPI_SIGNER, - )?; - - let (address, address_seed) = derive_address( - &[b"counter", ctx.accounts.fee_payer.key().as_ref()], - &address_tree_info.get_tree_pubkey(&light_cpi_accounts)?, - &crate::ID, - ); - - let mut new_account = LightAccount::<'_, CounterAccount>::new_init( - &crate::ID, - Some(address), - output_tree_index, - ); - - new_account.counter = 0; - - let light_cpi = LightSystemProgramCpi::new(light_cpi_accounts, vec![proof])?; - let instruction_data = LightCpiInstruction { - inputs: Vec::new(), - outputs: vec![new_account.to_account_info()?], - address_tree_infos: vec![address_tree_info.into()], - address_seeds: vec![address_seed], - }; - light_cpi.invoke(instruction_data)?; - Ok(()) - } -} - -#[derive(Accounts)] -pub struct CreateCompressedAccount<'info> { - #[account(mut)] - pub fee_payer: Signer<'info>, -} - -#[derive(Debug, LightDiscriminator, LightHasher)] -pub struct CounterAccount { - #[hash] - pub counter: u64, -} -``` - -## Guides - -| Guide | Docs | -|-------|------| -| Create compressed accounts | [create](https://www.zkcompression.com/compressed-pdas/guides/how-to-create-compressed-accounts) | -| Update compressed accounts | [update](https://www.zkcompression.com/compressed-pdas/guides/how-to-update-compressed-accounts) | -| Close compressed accounts | [close](https://www.zkcompression.com/compressed-pdas/guides/how-to-close-compressed-accounts) | -| Reinitialize accounts | [reinit](https://www.zkcompression.com/compressed-pdas/guides/how-to-reinitialize-compressed-accounts) | -| Burn accounts | [burn](https://www.zkcompression.com/compressed-pdas/guides/how-to-burn-compressed-accounts) | -| Client guide (TS + Rust) | [client](https://www.zkcompression.com/client-library/client-guide) | -| Program examples | [examples](https://www.zkcompression.com/compressed-pdas/program-examples) | - -## SDKs - -- Rust on-chain (Anchor): [`light-sdk`](https://docs.rs/light-sdk) ([crates.io](https://crates.io/crates/light-sdk)) -- Rust on-chain (Pinocchio): [`light-sdk-pinocchio`](https://docs.rs/light-sdk-pinocchio) ([crates.io](https://crates.io/crates/light-sdk-pinocchio)) -- Rust client: [`light-client`](https://docs.rs/light-client) ([crates.io](https://crates.io/crates/light-client)) -- Rust testing: [`light-program-test`](https://docs.rs/light-program-test) ([crates.io](https://crates.io/crates/light-program-test)) -- TypeScript: [`@lightprotocol/stateless.js`](https://www.npmjs.com/package/@lightprotocol/stateless.js) -- GitHub examples: [program-examples](https://github.com/Lightprotocol/program-examples) diff --git a/references/light-token-terminology.mdx b/references/light-token-terminology.mdx deleted file mode 100644 index 9be0bda4..00000000 --- a/references/light-token-terminology.mdx +++ /dev/null @@ -1,146 +0,0 @@ ---- -title: "Light Token terminology" -description: "Terminology for Light Token concepts and operations" ---- - -For ZK Compression terms, see [Terminology](/references/terminology). - -## Claim - -An instruction to recover rent from expired compressible accounts. When a Light Token account's prepaid rent period expires, any user can claim the rent-exemption lamports stored in the account. - -## Compressibility - -The state when a Light Token account can be compressed permissionlessly. An account becomes compressible when its prepaid rent period expires. The `is_compressible()` function checks the current slot against the account's rent configuration. - -## Compressible extension - -An extension on decompressed Light Token accounts that tracks compression configuration and rent data. Fields include `decimals`, `compression_only`, `is_ata`, and embedded `CompressionInfo`. Accounts with this extension become eligible for permissionless compression when their rent expires. - -Discriminator: 32 - -## CompressibleConfig - -A registry account that stores compression policy for a set of Light Token accounts. Controls rent configuration, compression authority, and address space allocation. - -- **PDA seeds**: `["compressible_config", version_bytes]` -- **States**: Inactive (0), Active (1), Deprecated (2) - -## Compress - -An operation that moves tokens from a decompressed Light Token account into a compressed account stored in a state tree. The token data becomes a leaf in the Merkle tree, and the on-chain Solana account can be closed. - -## CompressAndClose - -An instruction that atomically compresses a Light Token account's balance and closes the Solana account. Rent-exemption lamports return to the rent sponsor. Requires the Compressible extension. - -## Compressed (cold) state - -A Light Token state where token data exists as a leaf in a state Merkle tree. No on-chain Solana account exists. Requires a validity proof to read or modify. Uses ZK Compression state trees. - -Internal identifier: `ctoken-cold` - -## Compressed Mint - -A mint account for Light Tokens stored in a state tree. Contains SPL-compatible fields (`mint_authority`, `supply`, `decimals`, `freeze_authority`) plus Light Protocol metadata. Can be decompressed to a Solana account. - -Discriminator: 1 - -Internal type: `CMint` - - - **Source**: https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token - - -## CompressedOnly extension - -An extension on compressed token accounts indicating they can only be decompressed, not transferred. Created when a Light Token account with this extension is compressed-and-closed. Fields include `delegated_amount`, `withheld_transfer_fee`, and `is_ata`. - -Discriminator: 31 - -## CompressionInfo - -A structure embedded in the Compressible extension that tracks rent and compression configuration. Fields include `config_account_version`, `compress_to_pubkey`, `lamports_per_write`, `compression_authority`, `rent_sponsor`, `last_claimed_slot`, `rent_exemption_paid`, and `rent_config`. - -## Decompress - -An operation that moves tokens from a compressed account in a state tree to a decompressed Light Token account on-chain. Creates or updates a Solana account with the token data. - -## Decompressed (hot) state - -A Light Token state where token data exists in an on-chain Solana account with the Compressible extension. Supports direct read/write access without validity proofs. Standard Solana rent-exemption applies and is reclaimable on compression. - -Internal identifier: `ctoken-hot` - -## Light Token - -A token system built on ZK Compression with sponsored rent-exemption. Token data can exist in Merkle trees (compressed state) or on-chain Solana accounts (decompressed state). Uses ZK Compression state trees and validity proofs. - -**Canonical name**: Light Token - -**Codebase aliases**: `light-token` (Rust crate), `c-token` (JS/TS package), `CToken` (Rust types), `compressed-token` (npm package) - - - **Source**: https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token - - -## Light Token account - -A decompressed token account on-chain with SPL Token layout plus the Compressible extension. Stores mint, owner, amount, delegate, and state fields. - -Discriminator: 2 (matches SPL Token) - -Program owner: `light-compressed-token` - -Internal type: `CToken` - -## MintAction - -A batch instruction for compressed mint operations. Supports `CreateMint`, `MintTo`, `MintToCompressed`, `UpdateMintAuthority`, `UpdateFreezeAuthority`, `UpdateMetadataField`, `UpdateMetadataAuthority`, `RemoveMetadataKey`, `DecompressMint`, and `CompressAndCloseMint`. - -Discriminator: 103 - -## Non-unified path - -An SDK import path (`@lightprotocol/compressed-token`) that decompresses compressed tokens without wrapping SPL or Token-2022 balances. Uses `wrap=false` by default. Output can be SPL, Token-2022, or Light Token. Suited for DeFi integrations where downstream applications may not support Light Token. - -## Sponsored rent-exemption - -The rent model for Light Tokens. Compressed tokens require no lamports because data lives in state Merkle trees. Decompressed tokens pay standard Solana rent-exemption, which the owner reclaims when compressing the account. - -**Canonical term**: Sponsored rent-exemption by the light-token program - -**Avoid**: "rent-free token", "rent-free account" - -## Token pool - -An SPL token account that holds SPL tokens corresponding to compressed tokens in circulation. Tokens deposit during compression and withdraw during decompression. - -- **PDA seeds**: `["pool", mint_pubkey]` or `["pool", mint_pubkey, "restricted"]` for mints with restricted extensions -- **Max pools**: 5 per mint (indices 0-4) - -## TokenData - -The data structure for a compressed Light Token stored in a state tree leaf. Contains `mint`, `owner`, `amount`, `delegate`, and `state` fields. State values: Initialized (0), Frozen (1). Supports optional TLV extensions. - -## TokenMetadata extension - -A metadata extension for compressed mints storing name, symbol, uri, and additional key-value pairs. - -Discriminator: 19 - -Fields: `update_authority`, `mint`, `name`, `symbol`, `uri`, `additional_metadata` array - -## Transfer2 - -A batch instruction for compression operations supporting multiple modes: - -- **Compress** (0): Decompressed account to compressed account in state tree -- **Decompress** (1): State tree to decompressed account -- **CompressAndClose** (2): Compress and close atomically, rent to sponsor - -Discriminator: 101 - -## Unified path - -An SDK import path (`@lightprotocol/compressed-token/unified`) that aggregates balances from hot Light Token, cold Light Token, SPL, and Token-2022 into a single canonical ATA. Enforces `wrap=true`. Output is a single Light Token ATA derived from `CTOKEN_PROGRAM_ID`. Suited for closed-loop payment systems where you control the complete transaction flow. diff --git a/references/light-token.md b/references/light-token.md deleted file mode 100644 index eaa38c19..00000000 --- a/references/light-token.md +++ /dev/null @@ -1,117 +0,0 @@ -# Light Token - -## Light Token SDK - -The base library to use Light Token Accounts, Light Mints, and compressed token accounts. - -### Light Token Accounts -- are on Solana devnet. -- are Solana accounts. -- can hold tokens of Light, SPL and Token 2022 mints. -- cost 17,288 lamports to create with 24 hours rent. -- are rentfree: - - rent exemption is sponsored by the token program. - - rent is 388 lamports per rent epoch (1.5 hours). - - once the account's lamports balance is insufficient, it is auto-compressed to a compressed token account. - - the accounts state is cryptographically preserved on the Solana ledger. - - compressed tokens can be decompressed to a Light Token account. - - configurable lamports per write (eg transfer) keep the Light Token account perpetually funded when used. So you don't have to worry about funding rent. - - users load a compressed account into a light account in-flight when using the account again. - -### Light Mints -- are on Solana devnet. -- are Compressed accounts. -- cost 15,000 lamports to create. -- support `TokenMetadata`. -- have the same rent-config as light token accounts - -## Program Examples - -For full program examples, see the [Light Token Examples](https://github.com/Lightprotocol/examples-light-token). - -### Instructions - -The instructions use pure CPI calls which you can combine with existing and / or light macros. -For existing programs, you can replace spl_token with light_token instructions as you need. The API is a superset of SPL-token so switching is straightforward. - -| | Description | -|---------|-------------| -| [approve](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/approve/src/lib.rs) | Approve delegate via CPI | -| [burn](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/burn/src/lib.rs) | Burn tokens via CPI | -| [close](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/close/src/lib.rs) | Close token account via CPI | -| [create-associated-token-account](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-ata/src/lib.rs) | Create associated light-token account via CPI | -| [create-mint](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-mint/src/lib.rs) | Create light-token mint via CPI | -| [create-token-account](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/create-token-account/src/lib.rs) | Create light-token account via CPI | -| [freeze](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/freeze/src/lib.rs) | Freeze token account via CPI | -| [mint-to](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/mint-to/src/lib.rs) | Mint tokens via CPI | -| [revoke](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/revoke/src/lib.rs) | Revoke delegate via CPI | -| [thaw](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/thaw/src/lib.rs) | Thaw token account via CPI | -| [transfer-checked](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-checked/src/lib.rs) | Transfer with mint validation via CPI | -| [transfer-interface](https://github.com/Lightprotocol/examples-light-token/blob/main/programs/anchor/basic-instructions/transfer-interface/src/lib.rs) | Transfer between light-token, T22, and SPL accounts via CPI | - -### Macros - -| | Description | -|---------|-------------| -| [counter](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/counter) | Create PDA with sponsored rent-exemption | -| [create-associated-token-account](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-ata) | Create associated light-token account | -| [create-mint](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-mint) | Create light-token mint | -| [create-token-account](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/basic-macros/create-token-account) | Create light-token account | - -### Examples - -| | Description | -|---------|-------------| -| [create-and-transfer](https://github.com/Lightprotocol/examples-light-token/tree/main/programs/anchor/create-and-transfer) | Create account via macro and transfer via CPI | - -## TypeScript Client - -Rust client for light-token. Each action builds, signs, and sends the transaction. - -| Action | Description | -|--------|-------------| -| `CreateMint` | Create a light-token mint with metadata | -| `CreateAta` | Create an associated light-token account | -| `MintTo` | Mint tokens to a light-token account | -| `Transfer` | Transfer light-tokens between accounts | -| `TransferChecked` | Transfer with decimal validation | -| `TransferInterface` | Transfer between light-token, T22, and SPL accounts | -| `Approve` | Approve a delegate | -| `Revoke` | Revoke a delegate | -| `Wrap` | Wrap SPL/T22 to light-token | -| `Unwrap` | Unwrap light-token to SPL/T22 | - -### TypeScript Examples - -- **create-mint** - Create a light-token mint - - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-mint.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-mint.ts) -- **create-ata** - Create an associated light-token account - - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/create-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/create-ata.ts) -- **load-ata** - Load token accounts from light-token, compressed tokens, SPL/T22 to one unified balance. - - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/load-ata.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/load-ata.ts) -- **mint-to** - Mint tokens to a light-account - - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/mint-to.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/mint-to.ts) -- **transfer-interface** - Transfer between light-token, T22, and SPL accounts - - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/transfer-interface.ts) | [Instruction](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/instructions/transfer-interface.ts) -- **wrap** - Wrap SPL/T22 to light-token - - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/wrap.ts) -- **unwrap** - Unwrap light-token to SPL/T22 - - [Action](https://github.com/Lightprotocol/examples-light-token/blob/main/typescript-client/actions/unwrap.ts) - -## Toolkits - -| Toolkit | Docs | -|---------|------| -| Payments & wallets | [for-payments](https://zkcompression.com/light-token/toolkits/for-payments) | -| Wallets | [for-wallets](https://zkcompression.com/light-token/toolkits/for-wallets) | -| Streaming tokens | [for-streaming-tokens](https://zkcompression.com/light-token/toolkits/for-streaming-tokens) | -| Streaming mints | [for-streaming-mints](https://zkcompression.com/light-token/toolkits/for-streaming-mints) | - -## SDKs - -- Rust on-chain: [`light-token`](https://docs.rs/light-token) ([crates.io](https://crates.io/crates/light-token)) -- Rust client: [`light-token-client`](https://docs.rs/light-token-client) ([crates.io](https://crates.io/crates/light-token-client)) -- GitHub examples: [examples-light-token](https://github.com/Lightprotocol/examples-light-token) - -## Disclaimer -This library is not audited and in a beta state. Use at your own risk and expect breaking changes. diff --git a/references/sdk-reference.md b/references/sdk-reference.md deleted file mode 100644 index 22abef52..00000000 --- a/references/sdk-reference.md +++ /dev/null @@ -1,54 +0,0 @@ -# SDK Reference - -## Rust Crates - -### Program Development - -| Crate | Description | docs.rs | crates.io | -|-------|-------------|---------|-----------| -| `light-sdk` | Compressed accounts in Anchor/Rust programs | [docs.rs/light-sdk](https://docs.rs/light-sdk) | [crates.io](https://crates.io/crates/light-sdk) | -| `light-sdk-pinocchio` | Compressed accounts in native Pinocchio programs | [docs.rs/light-sdk-pinocchio](https://docs.rs/light-sdk-pinocchio) | [crates.io](https://crates.io/crates/light-sdk-pinocchio) | -| `light-token` | Light Token accounts, mints, compressed tokens (on-chain) | [docs.rs/light-token](https://docs.rs/light-token) | [crates.io](https://crates.io/crates/light-token) | -| `light-compressed-token-sdk` | Low-level SDK for compressed token operations | [docs.rs/light-compressed-token-sdk](https://docs.rs/light-compressed-token-sdk) | [crates.io](https://crates.io/crates/light-compressed-token-sdk) | - -### Client Development - -| Crate | Description | docs.rs | crates.io | -|-------|-------------|---------|-----------| -| `light-client` | Client library for compressed accounts and RPC | [docs.rs/light-client](https://docs.rs/light-client) | [crates.io](https://crates.io/crates/light-client) | -| `light-token-client` | Rust client for light-token actions | [docs.rs/light-token-client](https://docs.rs/light-token-client) | [crates.io](https://crates.io/crates/light-token-client) | - -### Testing - -| Crate | Description | docs.rs | crates.io | -|-------|-------------|---------|-----------| -| `light-program-test` | Fast local test environment (LiteSVM) | [docs.rs/light-program-test](https://docs.rs/light-program-test) | [crates.io](https://crates.io/crates/light-program-test) | - -## TypeScript Packages - -| Package | npm | TypeDocs | -|---------|-----|---------| -| `@lightprotocol/stateless.js` | [npm](https://www.npmjs.com/package/@lightprotocol/stateless.js) | [typedocs](https://lightprotocol.github.io/light-protocol/stateless.js/index.html) | -| `@lightprotocol/compressed-token` | [npm](https://www.npmjs.com/package/@lightprotocol/compressed-token) | [typedocs](https://lightprotocol.github.io/light-protocol/compressed-token/index.html) | - -## CLI - -| Tool | npm | -|------|-----| -| `@lightprotocol/zk-compression-cli` | [npm](https://www.npmjs.com/package/@lightprotocol/zk-compression-cli) | - -Install: -```bash -npm i -g @lightprotocol/zk-compression-cli -``` - -## External Resources - -| Resource | Link | -|----------|------| -| Documentation | [zkcompression.com](https://www.zkcompression.com/) | -| Light Token examples | [examples-light-token](https://github.com/Lightprotocol/examples-light-token) | -| Compressed Token examples | [examples-zk-compression](https://github.com/Lightprotocol/examples-zk-compression) | -| Program examples | [program-examples](https://github.com/Lightprotocol/program-examples) | -| MCP server | [zkcompression.com/mcp](https://www.zkcompression.com/mcp) | -| DeepWiki | [deepwiki.com/Lightprotocol/light-protocol](https://deepwiki.com/Lightprotocol/light-protocol) | diff --git a/references/testing.md b/references/testing.md deleted file mode 100644 index bf18a435..00000000 --- a/references/testing.md +++ /dev/null @@ -1,249 +0,0 @@ -# Light Protocol Testing - -## Routing - -| Task | Section | -|------|---------| -| Start local validator | [Local Testing](#local-testing-with-light-test-validator) | -| Test on devnet | [Devnet Testing](#devnet-testing) | -| Rust program tests | [light-program-test](#rust-program-tests-with-light-program-test) | - -## Program Addresses - -These addresses are identical on devnet and mainnet. - -| Program | Address | -|---------|---------| -| Light System | `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` | -| Compressed Token | `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` | -| Account Compression | `compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq` | -| Light Registry | `Lighton6oQpVkeewmo2mcPTQQp7kYHr4fWpAgJyEmDX` | - -## Local Testing with light-test-validator - -Local development environment running Solana test validator with Light Protocol programs, Photon indexer, and ZK prover. - -### Quick Start - -```bash -# Start all services -light test-validator - -# Stop -light test-validator --stop -``` - -### Services & Ports - -| Service | Port | Endpoint | -|---------|------|----------| -| Solana RPC | 8899 | `http://127.0.0.1:8899` | -| Solana WebSocket | 8900 | `ws://127.0.0.1:8900` | -| Photon Indexer | 8784 | `http://127.0.0.1:8784` | -| Light Prover | 3001 | `http://127.0.0.1:3001` | - -### Command Flags - -| Flag | Default | Description | -|------|---------|-------------| -| `--skip-indexer` | false | Run without Photon indexer | -| `--skip-prover` | false | Run without Light Prover | -| `--skip-system-accounts` | false | Skip pre-initialized accounts | -| `--devnet` | false | Clone programs from devnet | -| `--mainnet` | false | Clone programs from mainnet | -| `--sbf-program ` | - | Load additional program | -| `--skip-reset` | false | Keep existing ledger | -| `--verbose` | false | Enable verbose logging | - -### Deployed Programs - -| Program | Address | -|---------|---------| -| SPL Noop | `noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV` | -| Light System | `SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7` | -| Compressed Token | `cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m` | -| Account Compression | `compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq` | -| Light Registry | `Lighton6oQpVkeewmo2mcPTQQp7kYHr4fWpAgJyEmDX` | - -### TypeScript Test Integration - -```typescript -import { getTestRpc, newAccountWithLamports } from '@lightprotocol/stateless.js/test-helpers'; -import { WasmFactory } from '@lightprotocol/hasher.rs'; - -const lightWasm = await WasmFactory.getInstance(); -const rpc = await getTestRpc(lightWasm); -const payer = await newAccountWithLamports(rpc, 1e9, 256); -``` - -Run tests: - -```bash -cd js/stateless.js -pnpm test-validator && pnpm test:e2e:all -``` - -### Troubleshooting - -Validator fails to start: - -```bash -lsof -i :8899 # Check port -light test-validator --stop # Stop existing -rm -rf test-ledger/ # Reset ledger -``` - -Photon version mismatch: - -```bash -cargo install --git https://github.com/lightprotocol/photon.git \ - --rev ac7df6c388db847b7693a7a1cb766a7c9d7809b5 --locked --force -``` - -### File Locations - -| Component | Location | -|-----------|----------| -| Program binaries | `~/.config/light/bin/` | -| Prover binary | `~/.config/light/bin/prover-{platform}-{arch}` | -| Proving keys | `~/.config/light/proving-keys/` | -| Test ledger | `./test-ledger/` | - -## Devnet Testing - -### Quick Start - -```typescript -import { createRpc } from "@lightprotocol/stateless.js"; - -const connection = createRpc( - "https://devnet.helius-rpc.com?api-key=", - "https://devnet.helius-rpc.com?api-key=", - "https://devnet.helius-rpc.com?api-key=" -); -``` - -### Endpoints - -| Service | URL | -|-----------|--------------------------------------------------| -| RPC | `https://devnet.helius-rpc.com?api-key=` | -| WebSocket | `wss://devnet.helius-rpc.com?api-key=` | -| Indexer | `https://devnet.helius-rpc.com?api-key=` | -| Prover | `https://prover.helius.dev` | - -### Client Setup - -```typescript -import { Rpc, createRpc } from "@lightprotocol/stateless.js"; - -const HELIUS_API_KEY = process.env.HELIUS_API_KEY; - -const RPC_ENDPOINT = `https://devnet.helius-rpc.com?api-key=${HELIUS_API_KEY}`; -const COMPRESSION_ENDPOINT = RPC_ENDPOINT; -const PROVER_ENDPOINT = "https://prover.helius.dev"; - -const connection: Rpc = createRpc(RPC_ENDPOINT, COMPRESSION_ENDPOINT, PROVER_ENDPOINT); - -// Fetch state trees at runtime -const { stateTrees } = await connection.getCachedActiveStateTreeInfo(); -const outputStateTree = stateTrees[0].tree; -``` - -### Key Considerations - -- **Helius or Triton required**: The photon indexer implementation is maintained by Helius. You can also use Triton. Currently these RPC's provide compression endpoints -- **Runtime tree fetch**: Always fetch active state trees at runtime via `getCachedActiveStateTreeInfo()` -- **Same programs**: Program addresses are identical on devnet and mainnet -- **Devnet-specific trees**: State tree lookup tables differ from mainnet - -### Devnet Addresses - -| Lookup Table | Address | -|---------------------------------|------------------------------------------------| -| State Tree Lookup Table | `DmRueT3LMJdGj3TEprqKtfwMxyNUHDnKrQua4xrqtbmG` | -| Address Tree Lookup Table | `G4HqCAWPJ1E3JmYX1V2RZvNMuzF6gcFdbwT8FccWX6ru` | - -Usage notes: - -- Always fetch state trees dynamically using `getCachedActiveStateTreeInfo()` -- Do not hardcode tree addresses; they rotate as trees fill up -- Lookup table addresses are stable and can be referenced directly - -## Rust Program Tests with light-program-test - -A fast local test environment for Solana programs using compressed accounts and tokens. - -**Use `light-program-test` when:** -- You need fast test execution -- You write unit/integration tests for your program or client code - -**Use `solana-test-validator` when:** -- You need RPC methods or external tools that are incompatible with LiteSVM -- Testing against real validator behavior - -### Prerequisites - -1. **ZK Compression CLI**: Required to start the prover server and download Light Protocol programs - - ```bash - npm i -g @lightprotocol/zk-compression-cli - ``` - - If programs are missing after CLI installation, run `light test-validator` once to download them - -2. **Build programs**: Run `cargo test-sbf` to build program binaries and set the required - environment variables for locating program artifacts - -### Debugging - -Set `RUST_BACKTRACE=1` to show detailed transaction information including accounts and parsed instructions: - -```bash -RUST_BACKTRACE=1 cargo test-sbf -- --nocapture -``` - -## Pre-initialized Accounts - -The test validator loads 39 pre-initialized accounts from the CLI's `accounts/` directory. - -### State Trees (V1) - -| Type | Address | -|------|---------| -| Merkle tree 1 | `smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT` | -| Nullifier queue 1 | `nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148` | -| CPI context 1 | `cpi1uHzrEhBG733DoEJNgHCyRS3XmmyVNZx5fonubE4` | -| Merkle tree 2 | `smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho` | -| Nullifier queue 2 | `nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X` | -| CPI context 2 | `cpi2cdhkH5roePvcudTgUL8ppEBfTay1desGh8G8QxK` | - -### Batched State Trees (V2) - -Five batched state tree triplets (bmt/oq/cpi): - -| Set | BMT | OQ | CPI | -|-----|-----|-----|-----| -| 1 | `bmt1LryLZUMmF7ZtqESaw7wifBXLfXHQYoE4GAmrahU` | `oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto` | `cpi15BoVPKgEPw5o8wc2T816GE7b378nMXnhH3Xbq4y` | -| 2 | `bmt2UxoBxB9xWev4BkLvkGdapsz6sZGkzViPNph7VFi` | `oq2UkeMsJLfXt2QHzim242SUi3nvjJs8Pn7Eac9H9vg` | `cpi2yGapXUR3As5SjnHBAVvmApNiLsbeZpF3euWnW6B` | -| 3 | `bmt3ccLd4bqSVZVeCJnH1F6C8jNygAhaDfxDwePyyGb` | `oq3AxjekBWgo64gpauB6QtuZNesuv19xrhaC1ZM1THQ` | `cpi3mbwMpSX8FAGMZVP85AwxqCaQMfEk9Em1v8QK9Rf` | -| 4 | `bmt4d3p1a4YQgk9PeZv5s4DBUmbF5NxqYpk9HGjQsd8` | `oq4ypwvVGzCUMoiKKHWh4S1SgZJ9vCvKpcz6RT6A8dq` | `cpi4yyPDc4bCgHAnsenunGA8Y77j3XEDyjgfyCKgcoc` | -| 5 | `bmt5yU97jC88YXTuSukYHa8Z5Bi2ZDUtmzfkDTA2mG2` | `oq5oh5ZR3yGomuQgFduNDzjtGvVWfDRGLuDVjv9a96P` | `cpi5ZTjdgYpZ1Xr7B1cMLLUE81oTtJbNNAyKary2nV6` | - -### Address Trees - -| Type | Address | -|------|---------| -| Address Merkle tree (V1) | `amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2` | -| Address queue (V1) | `aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F` | -| Batch address tree (V2) | `amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx` | - -### Protocol PDAs - -| Type | Address | -|------|---------| -| Governance authority | `CuEtcKkkbTn6qy2qxqDswq5U2ADsqoipYDAYfRvxPjcp` | -| Config counter | `8gH9tmziWsS8Wc4fnoN5ax3jsSumNYoRDuSBvmH2GMH8` | -| Registered program PDA | `35hkDgaAKwMCaxRz2ocSZ6NaUrtKkyNqU6c4RV3tYJRh` | -| Registered registry program PDA | `DumMsyvkaGJG4QnQ1BhTgvoRMXsgGxfpKDUCr22Xqu4w` | -| Group PDA | `24rt4RgeyjUCWGS2eF7L7gyNMuz6JWdqYpAvb1KRoHxs` | diff --git a/references/zk-nullifiers.md b/references/zk-nullifiers.md deleted file mode 100644 index 0bfaaab8..00000000 --- a/references/zk-nullifiers.md +++ /dev/null @@ -1,288 +0,0 @@ -# ZK Nullifiers - -Uses Compressed PDAs. - -## Overview - -Building a ZK Solana program requires: -- Nullifiers to prevent double spending -- Proof verification -- A Merkle tree to store state -- An indexer to serve Merkle proofs -- Encrypted state - -## Nullifiers on Solana - -A nullifier is a deterministically derived hash to ensure an action can only be performed once. The nullifier cannot be linked to the action or user. For example Zcash uses nullifiers to prevent double spending. - -To implement nullifiers we need a data structure that ensures every nullifier is only created once and never deleted. On Solana a straight forward way to implement nullifiers is to create a PDA account with the nullifier as seed. - -PDA accounts cannot be closed and permanently lock 890,880 lamports (per nullifier rent-exemption). -Compressed PDAs are derived similar to Solana PDAs and cost 15,000 lamports to create (no rent-exemption). - -| Storage | Cost per nullifier | -|---------|-------------------| -| PDA | 890,880 lamports | -| Compressed PDA | 15,000 lamports | -## When to Use Nullifiers - -## Pattern Overview - -``` -1. Client computes nullifier = hash(secret, context) -2. Client fetches validity proof for derived address (proves it does not exist) -3. Client calls create_nullifier with nullifier values and proof -4. Program derives address from nullifier, creates compressed account via CPI -5. Light system program rejects CPI if address already exists -``` - -## Reference Implementation - -Source: [program-examples/zk/nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier) - -### Account Structure - -```rust -#[derive(Clone, Debug, Default, BorshSerialize, BorshDeserialize, LightDiscriminator)] -pub struct NullifierAccount {} -``` - -Empty struct since existence alone proves the nullifier was used. - -### Address Derivation - -```rust -pub const NULLIFIER_PREFIX: &[u8] = b"nullifier"; - -let (address, address_seed) = derive_address( - &[NULLIFIER_PREFIX, nullifier.as_slice()], // seeds - &address_tree_pubkey, // address tree - &program_id, // program ID -); -``` - -Address is deterministically derived from: -- Constant prefix (prevents collisions with other account types) -- Nullifier value (32 bytes) -- Address tree pubkey -- Program ID - -### Instruction Data - -```rust -#[derive(Clone, Debug, AnchorSerialize, AnchorDeserialize)] -pub struct NullifierInstructionData { - pub proof: ValidityProof, // ZK proof that addresses don't exist - pub address_tree_info: PackedAddressTreeInfo, - pub output_state_tree_index: u8, - pub system_accounts_offset: u8, -} -``` - -### Create Nullifiers Function - -```rust -pub fn create_nullifiers<'info>( - nullifiers: &[[u8; 32]], - data: NullifierInstructionData, - signer: &AccountInfo<'info>, - remaining_accounts: &[AccountInfo<'info>], -) -> Result<()> { - let light_cpi_accounts = CpiAccounts::new( - signer, - &remaining_accounts[data.system_accounts_offset as usize..], - LIGHT_CPI_SIGNER, - ); - - let address_tree_pubkey = data - .address_tree_info - .get_tree_pubkey(&light_cpi_accounts) - .map_err(|_| ErrorCode::AccountNotEnoughKeys)?; - - let mut cpi_builder = LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, data.proof); - let mut new_address_params: Vec = - Vec::with_capacity(nullifiers.len()); - - for (i, nullifier) in nullifiers.iter().enumerate() { - let (address, address_seed) = derive_address( - &[NULLIFIER_PREFIX, nullifier.as_slice()], - &address_tree_pubkey, - &crate::ID, - ); - - let nullifier_account = LightAccount::::new_init( - &crate::ID, - Some(address), - data.output_state_tree_index, - ); - - cpi_builder = cpi_builder.with_light_account(nullifier_account)?; - new_address_params.push( - data.address_tree_info - .into_new_address_params_assigned_packed(address_seed, Some(i as u8)), - ); - } - - cpi_builder - .with_new_addresses(&new_address_params) - .invoke(light_cpi_accounts)?; - - Ok(()) -} -``` - -### Program Entry Point - -```rust -#[program] -pub mod nullifier { - pub fn create_nullifier<'info>( - ctx: Context<'_, '_, '_, 'info, CreateNullifierAccounts<'info>>, - data: NullifierInstructionData, - nullifiers: Vec<[u8; 32]>, - ) -> Result<()> { - // Verify your ZK proof here. Use nullifiers as public inputs. - // Example: - // let public_inputs = [...nullifiers, ...your_other_inputs]; - // Groth16Verifier::new(...).verify()?; - - create_nullifiers( - &nullifiers, - data, - ctx.accounts.signer.as_ref(), - ctx.remaining_accounts, - ) - } -} - -#[derive(Accounts)] -pub struct CreateNullifierAccounts<'info> { - #[account(mut)] - pub signer: Signer<'info>, -} -``` - -## Client Implementation (TypeScript) - -```typescript -const NULLIFIER_PREFIX = Buffer.from("nullifier"); -const addressTree = new web3.PublicKey(batchAddressTree); - -// Derive addresses for each nullifier -const addressesWithTree = nullifiers.map((nullifier) => { - const seed = deriveAddressSeedV2([NULLIFIER_PREFIX, nullifier]); - const address = deriveAddressV2(seed, addressTree, programId); - return { tree: addressTree, queue: addressTree, address: bn(address.toBytes()) }; -}); - -// Get validity proof (proves addresses don't exist) -const proofResult = await rpc.getValidityProofV0([], addressesWithTree); - -// Build remaining accounts -const remainingAccounts = new PackedAccounts(); -remainingAccounts.addSystemAccountsV2(SystemAccountMetaConfig.new(programId)); -const addressMerkleTreeIndex = remainingAccounts.insertOrGet(addressTree); -const outputStateTreeIndex = remainingAccounts.insertOrGet(outputStateTree); - -// Build instruction data -const data = { - proof: { 0: proofResult.compressedProof }, - addressTreeInfo: { - addressMerkleTreePubkeyIndex: addressMerkleTreeIndex, - addressQueuePubkeyIndex: addressMerkleTreeIndex, - rootIndex: proofResult.rootIndices[0], - }, - outputStateTreeIndex, - systemAccountsOffset: systemStart, -}; - -// Call program -const ix = await program.methods - .createNullifier(data, nullifiers.map((n) => Array.from(n))) - .accounts({ signer: signer.publicKey }) - .remainingAccounts(remainingAccounts) - .instruction(); -``` - -## Client Implementation (Rust) - -```rust -use light_sdk::address::v2::derive_address; - -let address_tree_info = rpc.get_address_tree_v2(); - -// Derive addresses -let address_with_trees: Vec = nullifiers - .iter() - .map(|n| { - let (address, _) = derive_address( - &[NULLIFIER_PREFIX, n.as_slice()], - &address_tree_info.tree, - &program_id, - ); - AddressWithTree { - address, - tree: address_tree_info.tree, - } - }) - .collect(); - -// Get validity proof (empty hashes = non-inclusion proof) -let rpc_result = rpc - .get_validity_proof(vec![], address_with_trees, None) - .await? - .value; - -// Build accounts -let mut remaining_accounts = PackedAccounts::default(); -let config = SystemAccountMetaConfig::new(program_id); -remaining_accounts.add_system_accounts_v2(config)?; - -let packed_address_tree_accounts = rpc_result - .pack_tree_infos(&mut remaining_accounts) - .address_trees; - -let output_state_tree_index = rpc - .get_random_state_tree_info()? - .pack_output_tree_index(&mut remaining_accounts)?; -``` - -## How Nullifier Trees Work - -Light Protocol uses indexed Merkle trees for nullifiers (address trees). When creating a nullifier: - -1. Program derives a deterministic address from nullifier value -2. Validity proof proves this address does NOT exist in the tree -3. Light system program inserts the address into the tree -4. Future attempts fail because address now exists - -The indexed Merkle tree structure allows efficient non-inclusion proofs. Each leaf contains not just its value but also a pointer to the next-highest value, enabling proofs that a value falls between two existing values. - -## Dependencies - -```toml -[dependencies] -anchor-lang = "0.31" -light-sdk = { version = "0.14", features = ["anchor"] } -borsh = "0.10" - -[dev-dependencies] -light-program-test = "0.14" -``` - -## Testing - -```bash -# Rust tests -cargo test-sbf -p nullifier - -# TypeScript tests (requires light test-validator) -light test-validator # separate terminal -npm run test:ts -``` - -## Resources - -- Full example: [program-examples/zk/nullifier](https://github.com/Lightprotocol/program-examples/tree/main/zk/nullifier) -- ZK overview: [zkcompression.com/zk/overview](https://www.zkcompression.com/zk/overview) -- Additional ZK examples: [program-examples/zk](https://github.com/Lightprotocol/program-examples/tree/main/zk) (nullifier, zk-id, mixer, shielded-pool) diff --git a/resources/cli-installation.mdx b/resources/cli-installation.mdx index 9ce7e4fa..b29b6b37 100644 --- a/resources/cli-installation.mdx +++ b/resources/cli-installation.mdx @@ -33,7 +33,7 @@ Ensure you have Node >= v20.9.0 installed on your machine. Windows users do not Run this single command to install the ZK Compression CLI. ```bash -npm i -g @lightprotocol/zk-compression-cli@beta +npm i -g @lightprotocol/zk-compression-cli ``` From f909958afbdc90593dd216b53a20c3c5abe5f0fb Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 2 Feb 2026 14:42:28 +0000 Subject: [PATCH 17/21] fix conflicts --- light-token/toolkits/for-streaming-mints.mdx | 26 +++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/light-token/toolkits/for-streaming-mints.mdx b/light-token/toolkits/for-streaming-mints.mdx index 7972d5b8..7046300c 100644 --- a/light-token/toolkits/for-streaming-mints.mdx +++ b/light-token/toolkits/for-streaming-mints.mdx @@ -21,7 +21,7 @@ Light mints are Solana accounts owned by the Light Token Program. Subscribe to a ```toml Cargo.toml [dependencies] -helius-laserstream = "0.1.5" +helius-laserstream = "0.1" tokio = { version = "1", features = ["full"] } futures = "0.3" anyhow = "1" @@ -31,6 +31,9 @@ borsh = "0.10" light-token-interface = "0.3.0" light-compressed-account = { version = "0.9.0", features = ["std"] } + +# Pin blake3 to avoid constant_time_eq edition2024 issue +blake3 = "=1.5.5" ``` ```rust @@ -94,6 +97,7 @@ let request = helius_laserstream::grpc::SubscribeRequest { data: Some(Data::Bytes(vec![1])), })), }], + nonempty_txn_signature: Some(true), ..Default::default() }, )] @@ -121,12 +125,32 @@ if let Some(helius_laserstream::grpc::subscribe_update::UpdateOneof::Account( { if let Some(account_info) = account_update.account { let pubkey = bs58::encode(&account_info.pubkey).into_string(); + let tx_sig = account_info + .txn_signature + .as_ref() + .map(|s| bs58::encode(s).into_string()) + .unwrap_or_default(); match Mint::deserialize(&mut account_info.data.as_slice()) { Ok(mint) => { println!("Mint: {}", pubkey); + println!(" tx: {}", tx_sig); println!(" decimals: {}", mint.base.decimals); println!(" supply: {}", mint.base.supply); + + if let Some(authority) = &mint.base.mint_authority { + println!( + " mint_authority: {}", + bs58::encode(authority.to_bytes()).into_string() + ); + } + + if let Some(authority) = &mint.base.freeze_authority { + println!( + " freeze_authority: {}", + bs58::encode(authority.to_bytes()).into_string() + ); + } } Err(e) => { eprintln!("Failed to deserialize mint {}: {}", pubkey, e); From 2ba45738f5410c04fec5b3c045231626f05e676d Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 2 Feb 2026 14:51:00 +0000 Subject: [PATCH 18/21] update docs son --- docs.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs.json b/docs.json index 66e72957..b7949281 100644 --- a/docs.json +++ b/docs.json @@ -79,10 +79,15 @@ "light-token/cookbook/create-ata", "light-token/cookbook/create-token-account", "light-token/cookbook/mint-to", - "light-token/cookbook/close-token-account", "light-token/cookbook/transfer-interface", + "light-token/cookbook/transfer-checked", + "light-token/cookbook/approve-revoke", + "light-token/cookbook/freeze-thaw", "light-token/cookbook/wrap-unwrap", "light-token/cookbook/load-ata" + "light-token/cookbook/close-token-account", + "light-token/cookbook/burn", + ] } ] From 360930d7f5d75e0a6fbee13b64b76eee7da6a0b9 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 2 Feb 2026 14:58:04 +0000 Subject: [PATCH 19/21] update setup --- docs.json | 4 ++-- light-token/cookbook/overview.mdx | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/docs.json b/docs.json index b7949281..15f1b562 100644 --- a/docs.json +++ b/docs.json @@ -84,9 +84,9 @@ "light-token/cookbook/approve-revoke", "light-token/cookbook/freeze-thaw", "light-token/cookbook/wrap-unwrap", - "light-token/cookbook/load-ata" + "light-token/cookbook/load-ata", "light-token/cookbook/close-token-account", - "light-token/cookbook/burn", + "light-token/cookbook/burn" ] } diff --git a/light-token/cookbook/overview.mdx b/light-token/cookbook/overview.mdx index 97ac2af3..63474ff8 100644 --- a/light-token/cookbook/overview.mdx +++ b/light-token/cookbook/overview.mdx @@ -6,7 +6,8 @@ keywords: ["token code examples for solana", "spl token recipes for developers", --- import CookbookGuidesTable from "/snippets/overview-tables/cookbook-guides-table.mdx"; -import Prerequisites from "/snippets/light-token-guides/light-token-ts-client-prerequisites.mdx"; +import TsPrerequisites from "/snippets/light-token-guides/light-token-ts-client-prerequisites.mdx"; +import RustPrerequisites from "/snippets/light-token-guides/light-token-client-prerequisites.mdx"; ## Recipes @@ -14,4 +15,11 @@ import Prerequisites from "/snippets/light-token-guides/light-token-ts-client-pr ## Setup - + + + + + + + + From 327443f33bbf19478b4629539fae5cd9b3062b52 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Mon, 2 Feb 2026 15:01:57 +0000 Subject: [PATCH 20/21] fix linter ci --- .../code-samples/code-compare-snippets.jsx | 4 +- snippets/jsx/code-compare.jsx | 121 +++++++++++------- 2 files changed, 77 insertions(+), 48 deletions(-) diff --git a/snippets/code-samples/code-compare-snippets.jsx b/snippets/code-samples/code-compare-snippets.jsx index 3e9b4e27..07c4e082 100644 --- a/snippets/code-samples/code-compare-snippets.jsx +++ b/snippets/code-samples/code-compare-snippets.jsx @@ -180,8 +180,8 @@ export const lightCreateMintRustCode = [ " params,", " mint_seed.pubkey(),", " payer.pubkey(),", - " address_tree.tree,", - " output_queue,", + " address_tree.tree,", + " output_queue,", ")", ".instruction()?;", ].join("\n"); diff --git a/snippets/jsx/code-compare.jsx b/snippets/jsx/code-compare.jsx index 3d8370ab..7cce628a 100644 --- a/snippets/jsx/code-compare.jsx +++ b/snippets/jsx/code-compare.jsx @@ -33,16 +33,19 @@ export const CodeCompare = ({ const rustPattern = /(\/\/.*$)|(["'])(?:(?!\2)[^\\]|\\.)*?\2|\b(use|let|mut|pub|fn|struct|impl|enum|mod|const|static|trait|type|where|for|in|if|else|match|loop|while|return|self|Self|true|false|Some|None|Ok|Err|Result|Option|vec!)\b|::([a-zA-Z_][a-zA-Z0-9_]*)|&([a-zA-Z_][a-zA-Z0-9_]*)|\b([a-zA-Z_][a-zA-Z0-9_]*)\s*(?=\()|(\?)/gm; - return escaped.replace(rustPattern, (match, comment, stringQuote, keyword, pathSegment, reference, func, questionMark) => { - if (comment) return `${match}`; - if (stringQuote) return `${match}`; - if (keyword) return `${match}`; - if (pathSegment) return `::${pathSegment}`; - if (reference) return `&${reference}`; - if (func) return `${match}`; - if (questionMark) return `?`; - return match; - }); + return escaped.replace( + rustPattern, + (match, comment, stringQuote, keyword, pathSegment, reference, func, questionMark) => { + if (comment) return `${match}`; + if (stringQuote) return `${match}`; + if (keyword) return `${match}`; + if (pathSegment) return `::${pathSegment}`; + if (reference) return `&${reference}`; + if (func) return `${match}`; + if (questionMark) return `?`; + return match; + } + ); } // JavaScript/TypeScript syntax highlighting (default) @@ -169,12 +172,8 @@ export const CodeCompare = ({ style={{ fontFamily: "Inter, sans-serif" }} > {/* Header with toggle */} -
- +
+ {showingFirst ? firstLabel : secondLabel} @@ -187,13 +186,32 @@ export const CodeCompare = ({ style={{ background: "transparent", border: "none", cursor: "pointer" }} > {copied ? ( - + ) : ( - - + + )} @@ -207,42 +225,45 @@ export const CodeCompare = ({ width: "56px", height: "28px", borderRadius: "14px", - boxShadow: "inset -2px -2px 4px rgba(255,255,255,0.3), inset 2px 2px 4px rgba(0,0,0,0.1)", + boxShadow: + "inset -2px -2px 4px rgba(255,255,255,0.3), inset 2px 2px 4px rgba(0,0,0,0.1)", cursor: "pointer", transition: "all 0.3s ease", }} > - {/* Toggle button */} -
- {/* LED */} + {/* Toggle button */}
+ > + {/* LED */} +
+
-
{/* Code container */} @@ -259,7 +280,15 @@ export const CodeCompare = ({ aria-valuemax={100} aria-label="Code comparison slider" > -
+
{/* Second code (background) - shown when slider is on left */}
Date: Mon, 2 Feb 2026 15:05:03 +0000
Subject: [PATCH 21/21] fix linter ci

---
 cspell.json                                   | 4 +++-
 light-token/toolkits/for-streaming-tokens.mdx | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/cspell.json b/cspell.json
index 447b8aac..4b56b634 100644
--- a/cspell.json
+++ b/cspell.json
@@ -192,7 +192,9 @@
     "offramps",
     "zkcompression",
     "Tasktool",
-    "clippy"
+    "clippy",
+    "Raydium",
+    "raydium"
   ],
   "ignorePaths": [
     "node_modules",
diff --git a/light-token/toolkits/for-streaming-tokens.mdx b/light-token/toolkits/for-streaming-tokens.mdx
index 193d5a8e..d1e78f7b 100644
--- a/light-token/toolkits/for-streaming-tokens.mdx
+++ b/light-token/toolkits/for-streaming-tokens.mdx
@@ -9,7 +9,7 @@ import ActionCode from "/snippets/code-snippets/light-token/load-ata/action.mdx"
 import InstructionCode from "/snippets/code-snippets/light-token/load-ata/instruction.mdx";
 
 When a market becomes inactive, its token accounts and related PDAs will 
-be compressed autoatically (cold storage). 
+be compressed automatically (cold storage). 
 The state is cryptographically preserved on the Solana ledger.
 While compressed, pure on-chain lookups will return uninitialized.