From 319d2034997a8e0de160c7b1167758710c30576b Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Fri, 16 Jan 2026 20:20:13 +0000 Subject: [PATCH 01/10] add cookbook rust client --- docs.json | 5 +- light-token/cookbook/approve-revoke.mdx | 98 ++++++ light-token/cookbook/burn.mdx | 66 ++++ light-token/cookbook/close-token-account.mdx | 222 ++----------- light-token/cookbook/create-ata.mdx | 202 ++---------- light-token/cookbook/create-mint.mdx | 193 ++--------- light-token/cookbook/create-token-account.mdx | 194 ++--------- light-token/cookbook/freeze-thaw.mdx | 101 ++++++ light-token/cookbook/load-ata.mdx | 8 +- light-token/cookbook/mint-to.mdx | 301 +---------------- light-token/cookbook/transfer-interface.mdx | 250 ++------------- light-token/cookbook/update-metadata.mdx | 0 light-token/cookbook/wrap-unwrap.mdx | 2 - light-token/{cookbook => }/extensions.mdx | 0 scripts/copy-rust-snippets.sh | 57 ++++ .../code-samples/code-compare-snippets.jsx | 303 ++++++++++++++++++ .../rust-client/approve-full.mdx | 35 ++ .../rust-client/revoke-full.mdx | 30 ++ .../light-token/burn/rust-client/basic.mdx | 12 + .../light-token/burn/rust-client/full.mdx | 42 +++ .../close-token-account/rust-client/basic.mdx | 11 + .../close-token-account/rust-client/full.mdx | 34 ++ .../create-ata/rust-client/basic.mdx | 10 + .../create-ata/rust-client/full.mdx | 64 ++++ .../create-ata/rust-client/idempotent.mdx | 11 + .../create-mint/rust-client/basic.mdx | 15 + .../create-mint/rust-client/full.mdx | 101 ++++++ .../rust-client/basic.mdx | 11 + .../create-token-account/rust-client/full.mdx | 37 +++ .../freeze-thaw/rust-client/freeze-full.mdx | 32 ++ .../freeze-thaw/rust-client/thaw-full.mdx | 32 ++ .../light-token/mint-to/rust-client/basic.mdx | 13 + .../light-token/mint-to/rust-client/full.mdx | 41 +++ .../transfer-interface/rust-client/full.mdx | 123 +++++++ snippets/compressible-rent-explained.mdx | 8 +- snippets/jsx/code-compare.jsx | 98 ++++-- 36 files changed, 1492 insertions(+), 1270 deletions(-) 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 delete mode 100644 light-token/cookbook/update-metadata.mdx rename light-token/{cookbook => }/extensions.mdx (100%) create mode 100755 scripts/copy-rust-snippets.sh 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/revoke-full.mdx create mode 100644 snippets/code-snippets/light-token/burn/rust-client/basic.mdx create mode 100644 snippets/code-snippets/light-token/burn/rust-client/full.mdx create mode 100644 snippets/code-snippets/light-token/close-token-account/rust-client/basic.mdx create mode 100644 snippets/code-snippets/light-token/close-token-account/rust-client/full.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/rust-client/basic.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/rust-client/full.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/rust-client/idempotent.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/rust-client/basic.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/rust-client/full.mdx create mode 100644 snippets/code-snippets/light-token/create-token-account/rust-client/basic.mdx create mode 100644 snippets/code-snippets/light-token/create-token-account/rust-client/full.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/thaw-full.mdx create mode 100644 snippets/code-snippets/light-token/mint-to/rust-client/basic.mdx create mode 100644 snippets/code-snippets/light-token/mint-to/rust-client/full.mdx create mode 100644 snippets/code-snippets/light-token/transfer-interface/rust-client/full.mdx diff --git a/docs.json b/docs.json index 3e3db01..44710d9 100644 --- a/docs.json +++ b/docs.json @@ -65,7 +65,10 @@ "light-token/cookbook/close-token-account", "light-token/cookbook/transfer-interface", "light-token/cookbook/wrap-unwrap", - "light-token/cookbook/load-ata" + "light-token/cookbook/load-ata", + "light-token/cookbook/burn", + "light-token/cookbook/freeze-thaw", + "light-token/cookbook/approve-revoke" ] } ] diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx new file mode 100644 index 0000000..69dc631 --- /dev/null +++ b/light-token/cookbook/approve-revoke.mdx @@ -0,0 +1,98 @@ +--- +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 ApproveFull from "/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-full.mdx"; +import RevokeFull from "/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-full.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). + + + + + + + + + + + + + + + + + + + + + +# Next Steps + + diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx new file mode 100644 index 0000000..21398cc --- /dev/null +++ b/light-token/cookbook/burn.mdx @@ -0,0 +1,66 @@ +--- +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 RustFullCode from "/snippets/code-snippets/light-token/burn/rust-client/full.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). + + + + + + + + + +# Next Steps + + diff --git a/light-token/cookbook/close-token-account.mdx b/light-token/cookbook/close-token-account.mdx index b5970ed..e4d7b46 100644 --- a/light-token/cookbook/close-token-account.mdx +++ b/light-token/cookbook/close-token-account.mdx @@ -10,30 +10,36 @@ 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 RustFullCode from "/snippets/code-snippets/light-token/close-token-account/rust-client/full.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,186 +52,12 @@ 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[..])?) -} -``` + diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index 26f46dc..dbf3a62 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -20,22 +20,22 @@ import FullSetup from "/snippets/setup/full-setup.mdx"; import { 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 RustFullCode from "/snippets/code-snippets/light-token/create-ata/rust-client/full.mdx"; + +1. Associated light-token accounts can hold token balances of light, SPL, or Token 2022 mints. +2. The address is derived with the owner's address, compressed token program ID, and mint address. +3. Light-ATAs are on-chain accounts like SPL ATA's, but the light token program sponsors the rent-exemption cost for you. -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, compressed 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. -## Get Started - @@ -79,22 +79,17 @@ Compare to SPL: -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: +`CreateAssociatedTokenAccount` creates an on-chain ATA to store token balances of light, SPL, or Token 2022 mints. -```rust -use light_token_sdk::token::CreateAssociatedTokenAccount; - -let instruction = CreateAssociatedTokenAccount::new( - payer.pubkey(), - owner, - mint, -) -.instruction()?; -``` +Compare to SPL: -4. Send transaction & verify light-ATA creation with `get_account`. + @@ -109,167 +104,12 @@ 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[..])?) -} -``` + diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx index 3812a9b..cba077c 100644 --- a/light-token/cookbook/create-mint.mdx +++ b/light-token/cookbook/create-mint.mdx @@ -16,14 +16,21 @@ 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 RustFullCode from "/snippets/code-snippets/light-token/create-mint/rust-client/full.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 + + + + @@ -78,26 +85,18 @@ Compare to SPL: -The example creates a light-mint with token metadata. +`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. -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. +Compare to SPL: -3. Configure mint and your token metadata (name, symbol, URI, additional metadata) -4. Build the instruction with `CreateCMint::new()` and send the transaction. - -```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()?; -``` + @@ -110,158 +109,12 @@ 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) -} + + 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[..])?) -} -``` + diff --git a/light-token/cookbook/create-token-account.mdx b/light-token/cookbook/create-token-account.mdx index 0d16594..a57ac63 100644 --- a/light-token/cookbook/create-token-account.mdx +++ b/light-token/cookbook/create-token-account.mdx @@ -11,38 +11,34 @@ import TokenCreateAccountsList from "/snippets/accounts-list/light-token-create- 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 RustFullCode from "/snippets/code-snippets/light-token/create-token-account/rust-client/full.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,160 +51,12 @@ 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[..])?) -} -``` + diff --git a/light-token/cookbook/freeze-thaw.mdx b/light-token/cookbook/freeze-thaw.mdx new file mode 100644 index 0000000..a12da73 --- /dev/null +++ b/light-token/cookbook/freeze-thaw.mdx @@ -0,0 +1,101 @@ +--- +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 FreezeFull from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-full.mdx"; +import ThawFull from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-full.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. + +The ThawAccount instruction reverses a freeze, restoring full functionality to a previously frozen token account. + After thawing, the account can once again send and receive tokens normally. + + + + + + + + + + + + + + + + + + + +### 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). + + + + + + + + + + + + + + + + + + + + + +# Next Steps + + diff --git a/light-token/cookbook/load-ata.mdx b/light-token/cookbook/load-ata.mdx index 17eced7..042e1c2 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 19ac18f..aa05bc8 100644 --- a/light-token/cookbook/mint-to.mdx +++ b/light-token/cookbook/mint-to.mdx @@ -15,15 +15,16 @@ 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 RustFullCode from "/snippets/code-snippets/light-token/mint-to/rust-client/full.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 - @@ -68,26 +69,17 @@ Compare to SPL: -The example mints light-tokens to existing light-token accounts. - -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. +Use `MintTo` to mint tokens to a light-token account. -```rust -use light_token_sdk::token::MintTo; +Compare to SPL: -let instruction = MintTo::new( - params, - payer.pubkey(), - state_tree, - output_queue, - input_queue, - vec![recipient_account.pubkey()], -) -.instruction()?; -``` + @@ -100,271 +92,12 @@ 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[..])?) -} -``` + diff --git a/light-token/cookbook/transfer-interface.mdx b/light-token/cookbook/transfer-interface.mdx index 8ddce1f..c9b6fbb 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -17,9 +17,12 @@ 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 RustFullCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/full.mdx"; @@ -54,8 +57,6 @@ import InstructionCode from "/snippets/code-snippets/light-token/transfer-interf
-## Get Started - @@ -99,11 +100,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,231 +119,22 @@ The example transfers SPL token -> light-token and light-token -> light-token: -### Transfer Interface - - +### Transfer Interface -```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, - ); +The example transfers +1. SPL token -> light-token, +2. light-token -> light-token, and +3. light-token -> SPL token. - 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" - ); + + 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). + - // 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[..])?) -} -``` +
+
diff --git a/light-token/cookbook/update-metadata.mdx b/light-token/cookbook/update-metadata.mdx deleted file mode 100644 index e69de29..0000000 diff --git a/light-token/cookbook/wrap-unwrap.mdx b/light-token/cookbook/wrap-unwrap.mdx index 59a50ee..ae3d3da 100644 --- a/light-token/cookbook/wrap-unwrap.mdx +++ b/light-token/cookbook/wrap-unwrap.mdx @@ -23,8 +23,6 @@ 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 - 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/scripts/copy-rust-snippets.sh b/scripts/copy-rust-snippets.sh new file mode 100755 index 0000000..1e66f17 --- /dev/null +++ b/scripts/copy-rust-snippets.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Script to copy Rust client code from examples-light-token to docs/snippets/code-snippets/light-token +# Wraps each file in rust markdown code blocks + +EXAMPLES_DIR="/home/tilo/Workspace/examples-light-token/rust-client/tests" +SNIPPETS_DIR="/home/tilo/Workspace/docs/snippets/code-snippets/light-token" + +# Mapping: source file -> target directory/output-name +# Format: "target-dir:output-name" (output-name without .mdx extension) +declare -A FILE_MAP=( + ["create_ata.rs"]="create-ata:full" + ["create_token_account.rs"]="create-token-account:full" + ["create_mint.rs"]="create-mint:full" + ["mint_to.rs"]="mint-to:full" + ["close.rs"]="close-token-account:full" + ["transfer.rs"]="transfer-interface:full" + ["burn.rs"]="burn:full" + ["approve.rs"]="approve-revoke:approve-full" + ["revoke.rs"]="approve-revoke:revoke-full" + ["freeze.rs"]="freeze-thaw:freeze-full" + ["thaw.rs"]="freeze-thaw:thaw-full" +) + +# 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" +} + +# Process each mapped file +for source_file in "${!FILE_MAP[@]}"; do + mapping="${FILE_MAP[$source_file]}" + target_dir="${mapping%%:*}" + output_name="${mapping##*:}" + echo "Processing: $source_file -> $target_dir/$output_name.mdx" + + input_file="$EXAMPLES_DIR/$source_file" + output_dir="$SNIPPETS_DIR/$target_dir/rust-client" + + if [ -f "$input_file" ]; then + mkdir -p "$output_dir" + wrap_rust "$input_file" "$output_dir/$output_name.mdx" + else + echo " WARNING: Not found - $input_file" + fi +done + +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/code-samples/code-compare-snippets.jsx b/snippets/code-samples/code-compare-snippets.jsx index 9fc0e28..e000ecb 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-full.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-full.mdx new file mode 100644 index 0000000..e157cb8 --- /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/revoke-full.mdx b/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-full.mdx new file mode 100644 index 0000000..5a15cc0 --- /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/burn/rust-client/basic.mdx b/snippets/code-snippets/light-token/burn/rust-client/basic.mdx new file mode 100644 index 0000000..eb2a449 --- /dev/null +++ b/snippets/code-snippets/light-token/burn/rust-client/basic.mdx @@ -0,0 +1,12 @@ +```rust +use light_token_sdk::token::Burn; + +let burn_ix = Burn { + source: ata, + cmint: mint, + amount: burn_amount, + authority: payer.pubkey(), + max_top_up: None, +} +.instruction()?; +``` diff --git a/snippets/code-snippets/light-token/burn/rust-client/full.mdx b/snippets/code-snippets/light-token/burn/rust-client/full.mdx new file mode 100644 index 0000000..21ef3b4 --- /dev/null +++ b/snippets/code-snippets/light-token/burn/rust-client/full.mdx @@ -0,0 +1,42 @@ +```rust +mod shared; + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_sdk::token::Burn; +use shared::SetupContext; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn burn() { + // Setup creates mint and ATA with tokens + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = shared::setup().await; + + let initial_amount = 1_000_000u64; + let burn_amount = 400_000u64; + + let burn_ix = Burn { + source: ata, + mint, + amount: burn_amount, + authority: payer.pubkey(), + max_top_up: None, + } + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[burn_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let ata_data = rpc.get_account(ata).await.unwrap().unwrap(); + let token = light_token_interface::state::Token::deserialize(&mut &ata_data.data[..]).unwrap(); + assert_eq!(token.amount, initial_amount - burn_amount); +} +``` diff --git a/snippets/code-snippets/light-token/close-token-account/rust-client/basic.mdx b/snippets/code-snippets/light-token/close-token-account/rust-client/basic.mdx new file mode 100644 index 0000000..054d06b --- /dev/null +++ b/snippets/code-snippets/light-token/close-token-account/rust-client/basic.mdx @@ -0,0 +1,11 @@ +```rust +use light_token_sdk::token::CloseTokenAccount; + +let close_instruction = CloseTokenAccount::new( + LIGHT_TOKEN_PROGRAM_ID, + account.pubkey(), + payer.pubkey(), // Destination for remaining lamports + owner, +) +.instruction()?; +``` diff --git a/snippets/code-snippets/light-token/close-token-account/rust-client/full.mdx b/snippets/code-snippets/light-token/close-token-account/rust-client/full.mdx new file mode 100644 index 0000000..c9871d5 --- /dev/null +++ b/snippets/code-snippets/light-token/close-token-account/rust-client/full.mdx @@ -0,0 +1,34 @@ +```rust +mod shared; + +use light_client::rpc::Rpc; +use light_token_sdk::token::{CloseAccount, LIGHT_TOKEN_PROGRAM_ID}; +use shared::SetupContext; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn close_account() { + // Setup creates mint and empty ATA (must be empty to close). + let SetupContext { + mut rpc, + payer, + ata, + .. + } = shared::setup_empty_ata().await; + let close_ix = CloseAccount::new( + LIGHT_TOKEN_PROGRAM_ID, + ata, + payer.pubkey(), + payer.pubkey(), + ) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[close_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let account_after = rpc.get_account(ata).await.unwrap(); + assert!(account_after.is_none()); +} +``` diff --git a/snippets/code-snippets/light-token/create-ata/rust-client/basic.mdx b/snippets/code-snippets/light-token/create-ata/rust-client/basic.mdx new file mode 100644 index 0000000..6e2a6af --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/rust-client/basic.mdx @@ -0,0 +1,10 @@ +```rust +use light_token_sdk::token::CreateAssociatedTokenAccount; + +let create_ata_ix = CreateAssociatedTokenAccount::new( + payer.pubkey(), + owner.pubkey(), + mint, +) +.instruction()?; +``` diff --git a/snippets/code-snippets/light-token/create-ata/rust-client/full.mdx b/snippets/code-snippets/light-token/create-ata/rust-client/full.mdx new file mode 100644 index 0000000..4576792 --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/rust-client/full.mdx @@ -0,0 +1,64 @@ +```rust +mod shared; + +use light_client::rpc::Rpc; +use light_token_sdk::token::{get_associated_token_address, CreateAssociatedTokenAccount}; +use shared::SplMintContext; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn create_ata() { + // You can use light, spl, t22 mints to create a light token ATA. + let SplMintContext { + mut rpc, + payer, + mint, + } = shared::setup_spl_mint_context().await; + + let owner = Keypair::new(); + + let create_ata_ix = CreateAssociatedTokenAccount::new( + payer.pubkey(), + owner.pubkey(), + mint, + ) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let expected_ata = get_associated_token_address(&owner.pubkey(), &mint); + let ata_account = rpc.get_account(expected_ata).await.unwrap(); + assert!(ata_account.is_some()); +} + +#[tokio::test(flavor = "multi_thread")] +async fn create_ata_idempotent() { + let SplMintContext { + mut rpc, + payer, + mint, + } = shared::setup_spl_mint_context().await; + + let create_ata_ix = CreateAssociatedTokenAccount::new( + payer.pubkey(), + payer.pubkey(), + mint, + ) + .idempotent() + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[create_ata_ix.clone()], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let result = rpc + .create_and_send_transaction(&[create_ata_ix], &payer.pubkey(), &[&payer]) + .await; + + assert!(result.is_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 0000000..5f4fa5f --- /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-mint/rust-client/basic.mdx b/snippets/code-snippets/light-token/create-mint/rust-client/basic.mdx new file mode 100644 index 0000000..e8c1445 --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/rust-client/basic.mdx @@ -0,0 +1,15 @@ +```rust +let params = CreateMintParams { + 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, + bump: find_mint_address(&mint_seed.pubkey()).1, + freeze_authority: None, + extensions: Some(vec![ExtensionInstructionData::TokenMetadata(...)]), + rent_payment: 16, // ~24 hours rent + write_top_up: 766, // ~3 hours per write +}; +``` diff --git a/snippets/code-snippets/light-token/create-mint/rust-client/full.mdx b/snippets/code-snippets/light-token/create-mint/rust-client/full.mdx new file mode 100644 index 0000000..4d12247 --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/rust-client/full.mdx @@ -0,0 +1,101 @@ +```rust +use light_client::{ + indexer::{AddressWithTree, Indexer}, + rpc::Rpc, +}; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token_sdk::token::{ + 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::test(flavor = "multi_thread")] +async fn create_mint() { + let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(false, None)) + .await + .unwrap(); + + 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 instruction = CreateMint::new( + params, + mint_seed.pubkey(), + payer.pubkey(), + address_tree.tree, + output_queue, + ) + .instruction() + .unwrap(); + + let sig = rpc + .create_and_send_transaction(&[instruction], &payer.pubkey(), &[&payer, &mint_seed]) + .await + .unwrap(); + + println!("Mint: {}", mint); + println!("Tx: {}", sig); + + let mint_account = rpc.get_account(mint).await.unwrap(); + assert!(mint_account.is_some(), "Solana mint account should exist"); +} +``` diff --git a/snippets/code-snippets/light-token/create-token-account/rust-client/basic.mdx b/snippets/code-snippets/light-token/create-token-account/rust-client/basic.mdx new file mode 100644 index 0000000..cf6f8f8 --- /dev/null +++ b/snippets/code-snippets/light-token/create-token-account/rust-client/basic.mdx @@ -0,0 +1,11 @@ +```rust +use light_token_sdk::token::CreateTokenAccount; + +let instruction = CreateTokenAccount::new( + payer.pubkey(), + account.pubkey(), + mint, + owner, +) +.instruction()?; +``` diff --git a/snippets/code-snippets/light-token/create-token-account/rust-client/full.mdx b/snippets/code-snippets/light-token/create-token-account/rust-client/full.mdx new file mode 100644 index 0000000..ddcf657 --- /dev/null +++ b/snippets/code-snippets/light-token/create-token-account/rust-client/full.mdx @@ -0,0 +1,37 @@ +```rust +mod shared; + +use light_client::rpc::Rpc; +use light_token_sdk::token::CreateTokenAccount; +use shared::SplMintContext; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn create_token_account() { + // Setup creates mint + // You can use light, spl, t22 mints to create a light token account. + let SplMintContext { + mut rpc, + payer, + mint, + } = shared::setup_spl_mint_context().await; + + let account = Keypair::new(); + + let create_account_ix = CreateTokenAccount::new( + payer.pubkey(), + account.pubkey(), + mint, + payer.pubkey(), + ) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[create_account_ix], &payer.pubkey(), &[&payer, &account]) + .await + .unwrap(); + + let account_data = rpc.get_account(account.pubkey()).await.unwrap(); + assert!(account_data.is_some()); +} +``` 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 0000000..40ede2f --- /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/thaw-full.mdx b/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-full.mdx new file mode 100644 index 0000000..6086434 --- /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/mint-to/rust-client/basic.mdx b/snippets/code-snippets/light-token/mint-to/rust-client/basic.mdx new file mode 100644 index 0000000..374fda5 --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to/rust-client/basic.mdx @@ -0,0 +1,13 @@ +```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()?; +``` diff --git a/snippets/code-snippets/light-token/mint-to/rust-client/full.mdx b/snippets/code-snippets/light-token/mint-to/rust-client/full.mdx new file mode 100644 index 0000000..3441109 --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to/rust-client/full.mdx @@ -0,0 +1,41 @@ +```rust +mod shared; + +use borsh::BorshDeserialize; +use light_client::rpc::Rpc; +use light_token_sdk::token::MintTo; +use shared::SetupContext; +use solana_sdk::signer::Signer; + +#[tokio::test(flavor = "multi_thread")] +async fn mint_to() { + // Setup creates mint and empty ATA + let SetupContext { + mut rpc, + payer, + mint, + ata, + .. + } = shared::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, + } + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[mint_to_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + let ata_data = rpc.get_account(ata).await.unwrap().unwrap(); + let token = light_token_interface::state::Token::deserialize(&mut &ata_data.data[..]).unwrap(); + assert_eq!(token.amount, mint_amount); +} +``` diff --git a/snippets/code-snippets/light-token/transfer-interface/rust-client/full.mdx b/snippets/code-snippets/light-token/transfer-interface/rust-client/full.mdx new file mode 100644 index 0000000..9ab1fce --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-interface/rust-client/full.mdx @@ -0,0 +1,123 @@ +```rust +mod shared; + +use anchor_spl::token::spl_token; +use light_client::rpc::Rpc; +use light_program_test::{LightProgramTest, ProgramTestConfig}; +use light_token_sdk::{ + spl_interface::find_spl_interface_pda_with_index, + token::{ + get_associated_token_address, CreateAssociatedTokenAccount, SplInterface, + TransferInterface, LIGHT_TOKEN_PROGRAM_ID, + }, +}; +use solana_sdk::{signature::Keypair, signer::Signer}; + +#[tokio::test(flavor = "multi_thread")] +async fn transfer_interface() { + let mut rpc = LightProgramTest::new(ProgramTestConfig::new_v2(true, None)) + .await + .unwrap(); + + let payer = rpc.get_payer().insecure_clone(); + let recipient = Keypair::new(); + let decimals = 2u8; + let amount = 10_000u64; + + // Setup creates mint, mints tokens and creates SPL ATA + let mint = shared::setup_spl_mint(&mut rpc, &payer, decimals).await; + let spl_ata = shared::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_a = get_associated_token_address(&payer.pubkey(), &mint); + + let create_ata_a_ix = CreateAssociatedTokenAccount::new(payer.pubkey(), payer.pubkey(), mint) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[create_ata_a_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // Create SPL interface PDA (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, + }; + + // 1. Transfer SPL tokens to Light ATA + let spl_to_light_ix = TransferInterface { + source: spl_ata, + destination: light_ata_a, + amount, + decimals, + authority: payer.pubkey(), + payer: payer.pubkey(), + spl_interface: Some(spl_interface.clone()), + max_top_up: None, + source_owner: spl_token::ID, + destination_owner: LIGHT_TOKEN_PROGRAM_ID, + } + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[spl_to_light_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // Create Second Light ATA + let light_ata_b = get_associated_token_address(&recipient.pubkey(), &mint); + + let create_ata_b_ix = + CreateAssociatedTokenAccount::new(payer.pubkey(), recipient.pubkey(), mint) + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[create_ata_b_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // 2. Transfer Light-Tokens to Light ATA + let light_to_light_ix = TransferInterface { + source: light_ata_a, + destination: light_ata_b, + amount: amount / 2, + 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() + .unwrap(); + + rpc.create_and_send_transaction(&[light_to_light_ix], &payer.pubkey(), &[&payer]) + .await + .unwrap(); + + // 3. Transfer Light-Tokens from Light ATA to SPL + let light_to_spl_ix = TransferInterface { + source: light_ata_b, + destination: spl_ata, + amount: amount / 4, + decimals, + authority: recipient.pubkey(), + payer: payer.pubkey(), + spl_interface: Some(spl_interface), + max_top_up: None, + source_owner: LIGHT_TOKEN_PROGRAM_ID, + destination_owner: spl_token::ID, + } + .instruction() + .unwrap(); + + rpc.create_and_send_transaction(&[light_to_spl_ix], &payer.pubkey(), &[&payer, &recipient]) + .await + .unwrap(); +} +``` diff --git a/snippets/compressible-rent-explained.mdx b/snippets/compressible-rent-explained.mdx index 86d0dde..62b46d1 100644 --- a/snippets/compressible-rent-explained.mdx +++ b/snippets/compressible-rent-explained.mdx @@ -1,7 +1,11 @@ 1. The rent-exemption for light account creation is sponsored by the Light Token Program. 2. Transaction payer's pay rent per rent-epoch (388 lamports for 1.5h)
- to keep accounts "active". + to keep accounts "active" 3. "Inactive" accounts (rent below one epoch) get automatically compressed. 4. The account's state is cryptographically preserved and will be loaded into hot account state in-flight, when the account is used again.
-The hot state fee is paid for by the transaction payer when writing to the respective account. +The hot state fee is paid for by the transaction payer when writing to the respective account: + +* At account creation ~17,208 lamports for 24h of rent
+and compression incentive. +* When the account's rent is below 3h, the transaction payer tops up 776 lamports. diff --git a/snippets/jsx/code-compare.jsx b/snippets/jsx/code-compare.jsx index 86ec2b1..2c7f5a8 100644 --- a/snippets/jsx/code-compare.jsx +++ b/snippets/jsx/code-compare.jsx @@ -3,18 +3,45 @@ export const CodeCompare = ({ secondCode = "", firstLabel = "Light Token", secondLabel = "SPL", + language = "javascript", }) => { const [sliderPercent, setSliderPercent] = useState(0); 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; + const handleCopy = async () => { + const codeToCopy = isLightMode ? secondCode : firstCode; + 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; @@ -126,54 +153,64 @@ export const CodeCompare = ({ return ( <>
{/* Header with toggle */}
{isLightMode ? secondLabel : firstLabel} - {/* Neumorphic Toggle Switch */} -
+
+ {/* Copy button */} + + + {/* Neumorphic Toggle Switch */} +
{/* Toggle button */}
+
{/* Code container */} From 8924f1236eb2a59c11f83fd729554b97c93e8c9e Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Fri, 16 Jan 2026 20:20:39 +0000 Subject: [PATCH 02/10] add cookbook rust client --- light-token/cookbook/freeze-thaw.mdx | 3 --- 1 file changed, 3 deletions(-) diff --git a/light-token/cookbook/freeze-thaw.mdx b/light-token/cookbook/freeze-thaw.mdx index a12da73..c4e10e3 100644 --- a/light-token/cookbook/freeze-thaw.mdx +++ b/light-token/cookbook/freeze-thaw.mdx @@ -24,9 +24,6 @@ import ThawFull from "/snippets/code-snippets/light-token/freeze-thaw/rust-clien 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. -The ThawAccount instruction reverses a freeze, restoring full functionality to a previously frozen token account. - After thawing, the account can once again send and receive tokens normally. - From af32503ebe68f6527fdf51fa17da78a2d77eda58 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Fri, 16 Jan 2026 20:59:19 +0000 Subject: [PATCH 03/10] add cookbook rust client --- light-token/cookbook/approve-revoke.mdx | 16 +++++++-------- light-token/cookbook/burn.mdx | 8 ++++---- light-token/cookbook/close-token-account.mdx | 8 ++++---- light-token/cookbook/create-ata.mdx | 20 +++++++++---------- light-token/cookbook/create-mint.mdx | 16 +++++++-------- light-token/cookbook/create-token-account.mdx | 8 ++++---- light-token/cookbook/freeze-thaw.mdx | 16 +++++++-------- light-token/cookbook/mint-to.mdx | 16 +++++++-------- light-token/cookbook/transfer-interface.mdx | 16 +++++++-------- 9 files changed, 61 insertions(+), 63 deletions(-) diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx index 69dc631..d282fae 100644 --- a/light-token/cookbook/approve-revoke.mdx +++ b/light-token/cookbook/approve-revoke.mdx @@ -31,10 +31,10 @@ import RevokeFull from "/snippets/code-snippets/light-token/approve-revoke/rust- @@ -42,10 +42,10 @@ import RevokeFull from "/snippets/code-snippets/light-token/approve-revoke/rust- diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx index 21398cc..1fbfb0c 100644 --- a/light-token/cookbook/burn.mdx +++ b/light-token/cookbook/burn.mdx @@ -25,10 +25,10 @@ import RustFullCode from "/snippets/code-snippets/light-token/burn/rust-client/f Compare to SPL: diff --git a/light-token/cookbook/close-token-account.mdx b/light-token/cookbook/close-token-account.mdx index e4d7b46..d0d4cb9 100644 --- a/light-token/cookbook/close-token-account.mdx +++ b/light-token/cookbook/close-token-account.mdx @@ -34,10 +34,10 @@ Use `CloseTokenAccount` to close an empty light-token account. Compare to SPL: diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index dbf3a62..cab340a 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -28,9 +28,7 @@ import InstructionCode from "/snippets/code-snippets/light-token/create-ata/inst import RustFullCode from "/snippets/code-snippets/light-token/create-ata/rust-client/full.mdx"; 1. Associated light-token accounts can hold token balances of light, SPL, or Token 2022 mints. -2. The address is derived with the owner's address, compressed token program ID, and mint address. -3. Light-ATAs are on-chain accounts like SPL ATA's, but the light token program sponsors the rent-exemption cost for you. - +2. Light-ATAs are on-chain accounts like SPL ATA's, but the light token program sponsors the rent-exemption cost for you. @@ -45,10 +43,10 @@ The `createAtaInterface` function creates an associated light-token account in a Compare to SPL: @@ -84,10 +82,10 @@ Compare to SPL: Compare to SPL: diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx index cba077c..d74f6df 100644 --- a/light-token/cookbook/create-mint.mdx +++ b/light-token/cookbook/create-mint.mdx @@ -46,10 +46,10 @@ You can use the same interface regardless of mint type. Compare to SPL: @@ -91,10 +91,10 @@ The instruction creates under the hood a compressed mint address for when the mi Compare to SPL: diff --git a/light-token/cookbook/create-token-account.mdx b/light-token/cookbook/create-token-account.mdx index a57ac63..2cc2330 100644 --- a/light-token/cookbook/create-token-account.mdx +++ b/light-token/cookbook/create-token-account.mdx @@ -33,10 +33,10 @@ import RustFullCode from "/snippets/code-snippets/light-token/create-token-accou Compare to SPL: diff --git a/light-token/cookbook/freeze-thaw.mdx b/light-token/cookbook/freeze-thaw.mdx index c4e10e3..16ef49a 100644 --- a/light-token/cookbook/freeze-thaw.mdx +++ b/light-token/cookbook/freeze-thaw.mdx @@ -31,10 +31,10 @@ import ThawFull from "/snippets/code-snippets/light-token/freeze-thaw/rust-clien @@ -42,10 +42,10 @@ import ThawFull from "/snippets/code-snippets/light-token/freeze-thaw/rust-clien diff --git a/light-token/cookbook/mint-to.mdx b/light-token/cookbook/mint-to.mdx index aa05bc8..4599c14 100644 --- a/light-token/cookbook/mint-to.mdx +++ b/light-token/cookbook/mint-to.mdx @@ -36,10 +36,10 @@ The function auto-detects the token program (SPL, Token-2022, or Light) from the Compare to SPL: @@ -74,10 +74,10 @@ Use `MintTo` to mint tokens to a light-token account. Compare to SPL: diff --git a/light-token/cookbook/transfer-interface.mdx b/light-token/cookbook/transfer-interface.mdx index c9b6fbb..9173ac6 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -66,10 +66,10 @@ The `transferInterface` function transfers tokens between token accounts (SPL, T Compare to SPL: @@ -103,10 +103,10 @@ Compare to SPL: Use the unified `TransferInterface` to transfer tokens between token accounts (SPL, Token-2022, or Light) in a single call. From 59e8429a3b74fbe8e93af2bb6d223e662821b30a Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Fri, 16 Jan 2026 21:17:41 +0000 Subject: [PATCH 04/10] component fix --- light-token/cookbook/create-ata.mdx | 4 ---- snippets/jsx/code-compare.jsx | 29 +++++++++++++++-------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index cab340a..90f08b2 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -8,13 +8,9 @@ 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 { diff --git a/snippets/jsx/code-compare.jsx b/snippets/jsx/code-compare.jsx index 2c7f5a8..43afb32 100644 --- a/snippets/jsx/code-compare.jsx +++ b/snippets/jsx/code-compare.jsx @@ -5,17 +5,18 @@ export const CodeCompare = ({ 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 = isLightMode ? secondCode : firstCode; + const codeToCopy = showingFirst ? firstCode : secondCode; await navigator.clipboard.writeText(codeToCopy); setCopied(true); setTimeout(() => setCopied(false), 2000); @@ -87,7 +88,7 @@ export const CodeCompare = ({ }; const handleToggle = () => { - animateTo(isLightMode ? 0 : 100); + animateTo(showingFirst ? 0 : 100); }; const handleMouseDown = (e) => { @@ -163,7 +164,7 @@ export const CodeCompare = ({ - {isLightMode ? secondLabel : firstLabel} + {showingFirst ? firstLabel : secondLabel}
@@ -209,7 +210,7 @@ export const CodeCompare = ({ height: "24px", borderRadius: "12px", top: "2px", - left: isLightMode ? "30px" : "2px", + left: showingFirst ? "30px" : "2px", boxShadow: "0 2px 4px rgba(0,0,0,0.2)", transition: "all 0.3s ease-in-out", display: "flex", @@ -222,9 +223,9 @@ export const CodeCompare = ({ style={{ width: "6px", height: "6px", - background: isLightMode ? "#0066ff" : "#999", + background: showingFirst ? "#0066ff" : "#999", borderRadius: "50%", - boxShadow: isLightMode ? "0 0 8px 2px #0066ff" : "0 0 4px 1px rgba(0, 0, 0, 0.1)", + boxShadow: showingFirst ? "0 0 5px 1px rgba(0, 102, 255, 0.6)" : "0 0 4px 1px rgba(0, 0, 0, 0.1)", transition: "all 0.3s ease-in-out", }} /> @@ -247,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 */}
               
             
@@ -294,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%)", }} From ff3a6e54a90314b0d758568fcf83ce2dbea42547 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Fri, 16 Jan 2026 21:48:40 +0000 Subject: [PATCH 05/10] update stream toolkit --- light-token/toolkits/for-streaming-mints.mdx | 25 ++++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/light-token/toolkits/for-streaming-mints.mdx b/light-token/toolkits/for-streaming-mints.mdx index 7d87c79..f9a7b1d 100644 --- a/light-token/toolkits/for-streaming-mints.mdx +++ b/light-token/toolkits/for-streaming-mints.mdx @@ -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::CompressedMint; +use light_token_interface::state::Mint; use light_token_interface::state::extensions::ExtensionStruct; use borsh::BorshDeserialize; @@ -143,7 +143,7 @@ for output in event.output_compressed_accounts.iter() { }; // Deserialize - let mint = CompressedMint::try_from_slice(data)?; + let mint = Mint::try_from_slice(data)?; // Check if new (address not in inputs) let is_new = output @@ -166,7 +166,7 @@ for output in event.output_compressed_accounts.iter() { ### Extract Token Metadata from Mint Extensions ```rust -fn extract_metadata(mint: &CompressedMint) -> Option<(String, String, String)> { +fn extract_metadata(mint: &Mint) -> Option<(String, String, String)> { let extensions = mint.extensions.as_ref()?; for ext in extensions { @@ -199,13 +199,16 @@ if let Some((name, symbol, uri)) = extract_metadata(&mint) { ```rust #[repr(C)] -pub struct CompressedMint { +pub struct Mint { pub base: BaseMint, - pub metadata: CompressedMintMetadata, + pub metadata: MintMetadata, + pub reserved: [u8; 16], + pub account_type: u8, + 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, @@ -215,12 +218,14 @@ pub struct BaseMint { pub freeze_authority: Option, } -/// metadata used by the light token program (34 bytes) +/// Light Protocol metadata for compressed mints (67 bytes) #[repr(C)] -pub struct CompressedMintMetadata { +pub struct MintMetadata { pub version: u8, - pub spl_mint_initialized: bool, - pub mint: Pubkey, // PDA with compressed mint seed + pub mint_decompressed: bool, + pub mint: Pubkey, + pub mint_signer: [u8; 32], + pub bump: u8, } ``` From 02a52879cef85dc61394b6b6390987f9ab79ba0a Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Sat, 17 Jan 2026 02:20:49 +0000 Subject: [PATCH 06/10] ata program --- light-token/cookbook/create-ata.mdx | 154 +++++------------- scripts/copy-program-snippets.sh | 111 +++++++++++++ .../approve-revoke/program/anchor.mdx | 76 +++++++++ .../approve-revoke/program/native.mdx | 91 +++++++++++ .../light-token/burn/program/anchor.mdx | 41 +++++ .../light-token/burn/program/native.mdx | 50 ++++++ .../close-token-account/program/anchor.mdx | 43 +++++ .../close-token-account/program/native.mdx | 44 +++++ .../light-token/create-ata/program/anchor.mdx | 57 +++++++ .../light-token/create-ata/program/native.mdx | 75 +++++++++ .../create-mint/program/anchor.mdx | 102 ++++++++++++ .../create-mint/program/native.mdx | 149 +++++++++++++++++ .../freeze-thaw/program/anchor.mdx | 71 ++++++++ .../freeze-thaw/program/native.mdx | 81 +++++++++ .../light-token/mint-to/program/anchor.mdx | 43 +++++ .../light-token/mint-to/program/native.mdx | 52 ++++++ .../transfer-interface/program/anchor.mdx | 50 ++++++ .../transfer-interface/program/native.mdx | 63 +++++++ snippets/light-token-configure-rent.mdx | 41 ++--- 19 files changed, 1252 insertions(+), 142 deletions(-) create mode 100755 scripts/copy-program-snippets.sh create mode 100644 snippets/code-snippets/light-token/approve-revoke/program/anchor.mdx create mode 100644 snippets/code-snippets/light-token/approve-revoke/program/native.mdx create mode 100644 snippets/code-snippets/light-token/burn/program/anchor.mdx create mode 100644 snippets/code-snippets/light-token/burn/program/native.mdx create mode 100644 snippets/code-snippets/light-token/close-token-account/program/anchor.mdx create mode 100644 snippets/code-snippets/light-token/close-token-account/program/native.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/program/anchor.mdx create mode 100644 snippets/code-snippets/light-token/create-ata/program/native.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/program/anchor.mdx create mode 100644 snippets/code-snippets/light-token/create-mint/program/native.mdx create mode 100644 snippets/code-snippets/light-token/freeze-thaw/program/anchor.mdx create mode 100644 snippets/code-snippets/light-token/freeze-thaw/program/native.mdx create mode 100644 snippets/code-snippets/light-token/mint-to/program/anchor.mdx create mode 100644 snippets/code-snippets/light-token/mint-to/program/native.mdx create mode 100644 snippets/code-snippets/light-token/transfer-interface/program/anchor.mdx create mode 100644 snippets/code-snippets/light-token/transfer-interface/program/native.mdx diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index 90f08b2..d5187f7 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -22,6 +22,8 @@ import { 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 RustFullCode from "/snippets/code-snippets/light-token/create-ata/rust-client/full.mdx"; +import NativeProgram from "/snippets/code-snippets/light-token/create-ata/program/native.mdx"; +import AnchorProgram from "/snippets/code-snippets/light-token/create-ata/program/anchor.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. @@ -139,39 +141,51 @@ Find [a full code example at the end](#full-code-example). ```rust -use light_token_sdk::token::CreateAssociatedTokenAccountCpi; +use light_token_sdk::token::{CompressibleParamsCpi, CreateAssociatedAccountCpi}; -CreateAssociatedTokenAccountCpi { +let compressible = CompressibleParamsCpi::new_ata( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), +); + +CreateAssociatedAccountCpi { 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, + bump, + compressible, + idempotent, } -.invoke()?; +.invoke() ``` ```rust -use light_token_sdk::token::CreateAssociatedTokenAccountCpi; +use light_token_sdk::token::{CompressibleParamsCpi, CreateAssociatedAccountCpi}; + +let compressible = CompressibleParamsCpi::new_ata( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), +); -let signer_seeds: &[&[u8]] = &[ATA_SEED, &[bump]]; -CreateAssociatedTokenAccountCpi { +let signer_seeds = authority_seeds!(authority_bump); +CreateAssociatedAccountCpi { 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, + bump, + compressible, + idempotent, } -.invoke_signed(&[signer_seeds])?; +.invoke_signed(&[signer_seeds]) ``` @@ -183,112 +197,18 @@ 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). + Find the source code in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). -```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(()) -} - -/// 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(()) -} -``` + + + + + + + + diff --git a/scripts/copy-program-snippets.sh b/scripts/copy-program-snippets.sh new file mode 100755 index 0000000..5af10e0 --- /dev/null +++ b/scripts/copy-program-snippets.sh @@ -0,0 +1,111 @@ +#!/bin/bash + +# Script to copy program example code from examples-light-token to docs/snippets/code-snippets/light-token +# Wraps each file in rust markdown code blocks + +NATIVE_DIR="/home/tilo/Workspace/examples-light-token/program-examples/native/program/src/instructions" +ANCHOR_DIR="/home/tilo/Workspace/examples-light-token/program-examples/anchor/programs" +SNIPPETS_DIR="/home/tilo/Workspace/docs/snippets/code-snippets/light-token" + +# Mapping: target-dir -> "native-file:anchor-program-dir" +# For combined operations (approve-revoke, freeze-thaw), use comma-separated native files and anchor dirs +declare -A OPERATION_MAP=( + ["create-ata"]="create_ata.rs:create-ata" + ["create-mint"]="create_mint.rs:create-mint" + ["mint-to"]="mint_to.rs:mint-to" + ["transfer-interface"]="transfer_interface.rs:transfer-interface" + ["close-token-account"]="close.rs:close" + ["burn"]="burn.rs:burn" + ["approve-revoke"]="approve.rs,revoke.rs:approve,revoke" + ["freeze-thaw"]="freeze.rs,thaw.rs:freeze,thaw" +) + +# 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" +} + +# Function to combine multiple Rust files into one markdown file +wrap_rust_multi() { + local output_file="$1" + shift + local input_files=("$@") + + echo '```rust' > "$output_file" + local first=true + for input_file in "${input_files[@]}"; do + if [ -f "$input_file" ]; then + if [ "$first" = true ]; then + first=false + else + echo "" >> "$output_file" + echo "// ---" >> "$output_file" + echo "" >> "$output_file" + fi + cat "$input_file" >> "$output_file" + fi + done + echo '```' >> "$output_file" + echo "Created: $output_file" +} + +# Process each operation +for target_dir in "${!OPERATION_MAP[@]}"; do + mapping="${OPERATION_MAP[$target_dir]}" + native_part="${mapping%%:*}" + anchor_part="${mapping##*:}" + + echo "Processing: $target_dir" + + output_dir="$SNIPPETS_DIR/$target_dir/program" + mkdir -p "$output_dir" + + # Handle native files (may be comma-separated for combined operations) + IFS=',' read -ra native_files <<< "$native_part" + if [ ${#native_files[@]} -eq 1 ]; then + # Single file + native_file="$NATIVE_DIR/${native_files[0]}" + if [ -f "$native_file" ]; then + wrap_rust "$native_file" "$output_dir/native.mdx" + else + echo " WARNING: Not found - $native_file" + fi + else + # Multiple files - combine them + native_paths=() + for nf in "${native_files[@]}"; do + native_paths+=("$NATIVE_DIR/$nf") + done + wrap_rust_multi "$output_dir/native.mdx" "${native_paths[@]}" + fi + + # Handle anchor files (may be comma-separated for combined operations) + IFS=',' read -ra anchor_dirs <<< "$anchor_part" + if [ ${#anchor_dirs[@]} -eq 1 ]; then + # Single anchor program + anchor_file="$ANCHOR_DIR/${anchor_dirs[0]}/src/lib.rs" + if [ -f "$anchor_file" ]; then + wrap_rust "$anchor_file" "$output_dir/anchor.mdx" + else + echo " WARNING: Not found - $anchor_file" + fi + else + # Multiple anchor programs - combine them + anchor_paths=() + for ad in "${anchor_dirs[@]}"; do + anchor_paths+=("$ANCHOR_DIR/$ad/src/lib.rs") + done + wrap_rust_multi "$output_dir/anchor.mdx" "${anchor_paths[@]}" + fi +done + +echo "" +echo "Done! Created program snippets in: $SNIPPETS_DIR" +echo "" +echo "Files created:" +find "$SNIPPETS_DIR" -path "*/program/*.mdx" -type f | sort diff --git a/snippets/code-snippets/light-token/approve-revoke/program/anchor.mdx b/snippets/code-snippets/light-token/approve-revoke/program/anchor.mdx new file mode 100644 index 0000000..29e43d4 --- /dev/null +++ b/snippets/code-snippets/light-token/approve-revoke/program/anchor.mdx @@ -0,0 +1,76 @@ +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::ApproveCpi; + +declare_id!("A9b9t3qNQmYTpbui7TXLQbTJPXbNaV5dW11vxn2M7eC5"); + +#[program] +pub mod light_token_anchor_approve { + use super::*; + + pub fn approve<'info>( + ctx: Context<'_, '_, '_, 'info, ApproveAccounts<'info>>, + 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: 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>, + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, +} + +// --- + +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::RevokeCpi; + +declare_id!("Dvq3UxQUDEF3B6khoJTdcbm3UutDsvxkdkTvxoJvegfg"); + +#[program] +pub mod light_token_anchor_revoke { + use super::*; + + pub fn revoke<'info>(ctx: Context<'_, '_, '_, 'info, RevokeAccounts<'info>>) -> 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: Validated by light-token CPI + #[account(mut)] + pub token_account: AccountInfo<'info>, + pub owner: Signer<'info>, + pub system_program: Program<'info, System>, + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, +} +``` diff --git a/snippets/code-snippets/light-token/approve-revoke/program/native.mdx b/snippets/code-snippets/light-token/approve-revoke/program/native.mdx new file mode 100644 index 0000000..9d89651 --- /dev/null +++ b/snippets/code-snippets/light-token/approve-revoke/program/native.mdx @@ -0,0 +1,91 @@ +```rust +use super::authority_seeds; +use light_token_sdk::token::ApproveCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +pub fn process(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)?, + ); + + ApproveCpi { + token_account: token_account.clone(), + delegate: delegate.clone(), + owner: owner.clone(), + system_program: system_program.clone(), + amount, + } + .invoke() +} + +pub fn process_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]) +} + +// --- + +use super::authority_seeds; +use light_token_sdk::token::RevokeCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_account, owner, system_program, _token_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + RevokeCpi { + token_account: token_account.clone(), + owner: owner.clone(), + system_program: system_program.clone(), + } + .invoke() +} + +pub fn process_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]) +} +``` diff --git a/snippets/code-snippets/light-token/burn/program/anchor.mdx b/snippets/code-snippets/light-token/burn/program/anchor.mdx new file mode 100644 index 0000000..7c3ca78 --- /dev/null +++ b/snippets/code-snippets/light-token/burn/program/anchor.mdx @@ -0,0 +1,41 @@ +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::BurnCpi; + +declare_id!("BHTGZDjDw9Gpz8oYm7CRMg2WtKwW65YAYHXXMKv4dpr6"); + +#[program] +pub mod light_token_anchor_burn { + use super::*; + + pub fn burn<'info>( + ctx: Context<'_, '_, '_, 'info, BurnAccounts<'info>>, + 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(), + max_top_up: None, + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct BurnAccounts<'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>, + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, +} +``` diff --git a/snippets/code-snippets/light-token/burn/program/native.mdx b/snippets/code-snippets/light-token/burn/program/native.mdx new file mode 100644 index 0000000..99685a5 --- /dev/null +++ b/snippets/code-snippets/light-token/burn/program/native.mdx @@ -0,0 +1,50 @@ +```rust +use super::authority_seeds; +use light_token_sdk::token::BurnCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [source, mint, authority, _token_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + let amount = u64::from_le_bytes( + data.try_into() + .map_err(|_| ProgramError::InvalidInstructionData)?, + ); + + BurnCpi { + source: source.clone(), + mint: mint.clone(), + amount, + authority: authority.clone(), + max_top_up: None, + } + .invoke() +} + +pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [source, mint, authority, _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(), + max_top_up: None, + } + .invoke_signed(&[signer_seeds]) +} +``` diff --git a/snippets/code-snippets/light-token/close-token-account/program/anchor.mdx b/snippets/code-snippets/light-token/close-token-account/program/anchor.mdx new file mode 100644 index 0000000..2a487e9 --- /dev/null +++ b/snippets/code-snippets/light-token/close-token-account/program/anchor.mdx @@ -0,0 +1,43 @@ +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::CloseAccountCpi; + +declare_id!("4fi27siKEvKXJYN5WCzWuHdAw1rLed6Tprv9ZARv3Gxu"); + +#[program] +pub mod light_token_anchor_close { + use super::*; + + pub fn close_account<'info>( + ctx: Context<'_, '_, '_, 'info, CloseAccountAccounts<'info>>, + ) -> Result<()> { + CloseAccountCpi { + token_program: ctx.accounts.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: Validated by light-token CPI + pub 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>, +} +``` diff --git a/snippets/code-snippets/light-token/close-token-account/program/native.mdx b/snippets/code-snippets/light-token/close-token-account/program/native.mdx new file mode 100644 index 0000000..e4217f0 --- /dev/null +++ b/snippets/code-snippets/light-token/close-token-account/program/native.mdx @@ -0,0 +1,44 @@ +```rust +use super::authority_seeds; +use light_token_sdk::token::CloseAccountCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_program, account, destination, owner, rent_sponsor] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + CloseAccountCpi { + token_program: token_program.clone(), + account: account.clone(), + destination: destination.clone(), + owner: owner.clone(), + rent_sponsor: rent_sponsor.clone(), + } + .invoke() +} + +pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [token_program, account, destination, owner, rent_sponsor] = 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]) +} +``` diff --git a/snippets/code-snippets/light-token/create-ata/program/anchor.mdx b/snippets/code-snippets/light-token/create-ata/program/anchor.mdx new file mode 100644 index 0000000..f46c8a5 --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/program/anchor.mdx @@ -0,0 +1,57 @@ +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::{CreateAssociatedAccountCpi, CompressibleParamsCpi}; + +declare_id!("77bt3j6A3g9s1WtwYnRFTGP9y8H1nigW7mLtywGKPmMi"); + +#[program] +pub mod light_token_anchor_create_ata { + use super::*; + + pub fn create_ata<'info>( + ctx: Context<'_, '_, '_, 'info, CreateAtaAccounts<'info>>, + bump: u8, + idempotent: bool, + ) -> Result<()> { + let compressible = CompressibleParamsCpi::new_ata( + ctx.accounts.compressible_config.to_account_info(), + ctx.accounts.rent_sponsor.to_account_info(), + ctx.accounts.system_program.to_account_info(), + ); + + CreateAssociatedAccountCpi { + owner: ctx.accounts.owner.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + payer: ctx.accounts.payer.to_account_info(), + associated_token_account: ctx.accounts.associated_token_account.to_account_info(), + system_program: ctx.accounts.system_program.to_account_info(), + bump, + compressible, + idempotent, + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct CreateAtaAccounts<'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>, +} +``` diff --git a/snippets/code-snippets/light-token/create-ata/program/native.mdx b/snippets/code-snippets/light-token/create-ata/program/native.mdx new file mode 100644 index 0000000..5675130 --- /dev/null +++ b/snippets/code-snippets/light-token/create-ata/program/native.mdx @@ -0,0 +1,75 @@ +```rust +use super::authority_seeds; +use light_token_sdk::token::{CompressibleParamsCpi, CreateAssociatedAccountCpi}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +pub fn process(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; + + let compressible = CompressibleParamsCpi::new_ata( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + ); + + CreateAssociatedAccountCpi { + owner: owner.clone(), + mint: mint.clone(), + payer: payer.clone(), + associated_token_account: associated_token_account.clone(), + system_program: system_program.clone(), + bump, + compressible, + idempotent, + } + .invoke() +} + +pub fn process_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 compressible = CompressibleParamsCpi::new_ata( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + ); + + CreateAssociatedAccountCpi { + owner: owner.clone(), + mint: mint.clone(), + payer: payer.clone(), + associated_token_account: associated_token_account.clone(), + system_program: system_program.clone(), + bump, + compressible, + idempotent, + } + .invoke_signed(&[signer_seeds]) +} +``` diff --git a/snippets/code-snippets/light-token/create-mint/program/anchor.mdx b/snippets/code-snippets/light-token/create-mint/program/anchor.mdx new file mode 100644 index 0000000..fcce23f --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/program/anchor.mdx @@ -0,0 +1,102 @@ +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::{CreateMintCpi, CreateMintParams, SystemAccountInfos}; +use light_token_sdk::CompressedProof; + +declare_id!("Ev7tKaozVxbZLVGcKcVcz8A9yKZjUf5ATqoNSe5BDkjj"); + +#[program] +pub mod light_token_anchor_create_mint { + use super::*; + + pub fn create_mint<'info>( + ctx: Context<'_, '_, '_, 'info, CreateMintAccounts<'info>>, + 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, + ) -> Result<()> { + let mint = light_token_sdk::token::find_mint_address(ctx.accounts.mint_seed.key).0; + + let params = CreateMintParams { + decimals, + address_merkle_tree_root_index, + mint_authority: *ctx.accounts.authority.key, + proof, + compression_address, + mint, + bump, + freeze_authority, + extensions: None, + 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> { + 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_sdk::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_sdk::token::rent_sponsor_pda() + #[account(mut)] + pub rent_sponsor: AccountInfo<'info>, +} +``` diff --git a/snippets/code-snippets/light-token/create-mint/program/native.mdx b/snippets/code-snippets/light-token/create-mint/program/native.mdx new file mode 100644 index 0000000..3f097cc --- /dev/null +++ b/snippets/code-snippets/light-token/create-mint/program/native.mdx @@ -0,0 +1,149 @@ +```rust +use borsh::BorshDeserialize; +use light_token_sdk::{ + token::{CreateMintCpi, CreateMintParams, SystemAccountInfos}, + CompressedProof, +}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + pubkey::Pubkey, +}; + +#[derive(BorshDeserialize)] +struct CreateMintData { + decimals: u8, + address_merkle_tree_root_index: u16, + mint_authority: Pubkey, + proof_a: [u8; 32], + proof_b: [u8; 64], + proof_c: [u8; 32], + compression_address: [u8; 32], + mint: Pubkey, + bump: u8, + freeze_authority: Option, + rent_payment: u8, + write_top_up: u32, +} + +pub fn process(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)?; + + let params = CreateMintParams { + decimals: ix_data.decimals, + address_merkle_tree_root_index: ix_data.address_merkle_tree_root_index, + mint_authority: ix_data.mint_authority, + proof: CompressedProof { + a: ix_data.proof_a, + b: ix_data.proof_b, + c: ix_data.proof_c, + }, + compression_address: ix_data.compression_address, + mint: ix_data.mint, + bump: ix_data.bump, + freeze_authority: ix_data.freeze_authority, + extensions: None, + 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, + address_merkle_tree_root_index: u16, + proof_a: [u8; 32], + proof_b: [u8; 64], + proof_c: [u8; 32], + compression_address: [u8; 32], + mint: Pubkey, + bump: u8, + freeze_authority: Option, + rent_payment: u8, + write_top_up: u32, + authority_bump: u8, +} + +pub fn process_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: &[&[u8]] = &[b"authority", &[ix_data.authority_bump]]; + + let params = CreateMintParams { + decimals: ix_data.decimals, + address_merkle_tree_root_index: ix_data.address_merkle_tree_root_index, + mint_authority: *authority.key, + proof: CompressedProof { + a: ix_data.proof_a, + b: ix_data.proof_b, + c: ix_data.proof_c, + }, + compression_address: ix_data.compression_address, + mint: ix_data.mint, + bump: ix_data.bump, + freeze_authority: ix_data.freeze_authority, + extensions: None, + 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]) +} +``` diff --git a/snippets/code-snippets/light-token/freeze-thaw/program/anchor.mdx b/snippets/code-snippets/light-token/freeze-thaw/program/anchor.mdx new file mode 100644 index 0000000..8968c38 --- /dev/null +++ b/snippets/code-snippets/light-token/freeze-thaw/program/anchor.mdx @@ -0,0 +1,71 @@ +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::FreezeCpi; + +declare_id!("7ovuM3dD2MZtcWQesVMiSYJef3oh1XH3e8nUk1ArpWX6"); + +#[program] +pub mod light_token_anchor_freeze { + use super::*; + + pub fn freeze<'info>(ctx: Context<'_, '_, '_, 'info, FreezeAccounts<'info>>) -> 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: 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>, + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, +} + +// --- + +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::ThawCpi; + +declare_id!("FL4MY6v5mTqncytUeuPoGroo6DHSsmBCPirckygGbcip"); + +#[program] +pub mod light_token_anchor_thaw { + use super::*; + + pub fn thaw<'info>(ctx: Context<'_, '_, '_, 'info, ThawAccounts<'info>>) -> 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: 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>, + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, +} +``` diff --git a/snippets/code-snippets/light-token/freeze-thaw/program/native.mdx b/snippets/code-snippets/light-token/freeze-thaw/program/native.mdx new file mode 100644 index 0000000..3c73d94 --- /dev/null +++ b/snippets/code-snippets/light-token/freeze-thaw/program/native.mdx @@ -0,0 +1,81 @@ +```rust +use super::authority_seeds; +use light_token_sdk::token::FreezeCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + FreezeCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), + } + .invoke() +} + +pub fn process_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]) +} + +// --- + +use super::authority_seeds; +use light_token_sdk::token::ThawCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + ThawCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), + } + .invoke() +} + +pub fn process_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]) +} +``` diff --git a/snippets/code-snippets/light-token/mint-to/program/anchor.mdx b/snippets/code-snippets/light-token/mint-to/program/anchor.mdx new file mode 100644 index 0000000..c661399 --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to/program/anchor.mdx @@ -0,0 +1,43 @@ +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::MintToCpi; + +declare_id!("7SUgjNZYC1h89MuPVYkgEP5A4uYx5GFSjC7mzqMbN8U2"); + +#[program] +pub mod light_token_anchor_mint_to { + use super::*; + + pub fn mint_to<'info>( + ctx: Context<'_, '_, '_, 'info, MintToAccounts<'info>>, + 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, + } + .invoke()?; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct MintToAccounts<'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>, + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, +} +``` diff --git a/snippets/code-snippets/light-token/mint-to/program/native.mdx b/snippets/code-snippets/light-token/mint-to/program/native.mdx new file mode 100644 index 0000000..3e3bc28 --- /dev/null +++ b/snippets/code-snippets/light-token/mint-to/program/native.mdx @@ -0,0 +1,52 @@ +```rust +use super::authority_seeds; +use light_token_sdk::token::MintToCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +pub fn process(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)?, + ); + + MintToCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, + authority: authority.clone(), + system_program: system_program.clone(), + max_top_up: None, + } + .invoke() +} + +pub fn process_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(), + max_top_up: None, + } + .invoke_signed(&[signer_seeds]) +} +``` diff --git a/snippets/code-snippets/light-token/transfer-interface/program/anchor.mdx b/snippets/code-snippets/light-token/transfer-interface/program/anchor.mdx new file mode 100644 index 0000000..da473a0 --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-interface/program/anchor.mdx @@ -0,0 +1,50 @@ +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::TransferInterfaceCpi; + +declare_id!("ChkDqFsvNNT5CGrV2YCkmK4DiVSATnXc98mNozPbhC6u"); + +#[program] +pub mod light_token_anchor_transfer_interface { + use super::*; + + pub fn transfer<'info>( + ctx: Context<'_, '_, '_, 'info, TransferAccounts<'info>>, + 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: 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>, + /// CHECK: Light token program for CPI + pub light_token_program: AccountInfo<'info>, +} +``` diff --git a/snippets/code-snippets/light-token/transfer-interface/program/native.mdx b/snippets/code-snippets/light-token/transfer-interface/program/native.mdx new file mode 100644 index 0000000..ca0a49e --- /dev/null +++ b/snippets/code-snippets/light-token/transfer-interface/program/native.mdx @@ -0,0 +1,63 @@ +```rust +use super::authority_seeds; +use light_token_sdk::token::TransferInterfaceCpi; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, +}; + +pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [source, destination, authority, payer, ctoken_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]; + + TransferInterfaceCpi::new( + amount, + decimals, + source.clone(), + destination.clone(), + authority.clone(), + payer.clone(), + ctoken_authority.clone(), + system_program.clone(), + ) + .invoke() +} + +pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [source, destination, authority, payer, ctoken_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(), + ctoken_authority.clone(), + system_program.clone(), + ) + .invoke_signed(&[signer_seeds]) +} +``` diff --git a/snippets/light-token-configure-rent.mdx b/snippets/light-token-configure-rent.mdx index 5b4f701..cedea8b 100644 --- a/snippets/light-token-configure-rent.mdx +++ b/snippets/light-token-configure-rent.mdx @@ -1,7 +1,7 @@ ```rust -use light_token_sdk::token::CompressibleParamsInfos; +use light_token_sdk::token::CompressibleParamsCpi; -let compressible_params = CompressibleParamsInfos::new( +let compressible = CompressibleParamsCpi::new_ata( compressible_config.clone(), rent_sponsor.clone(), system_program.clone(), @@ -22,38 +22,29 @@ let compressible_params = CompressibleParamsInfos::new( - - - Compressible Config - - + + Compressible Config + Protocol PDA that stores account rent config. - - - Rent Sponsor - - - - - - light token program PDA that fronts rent exemption at creation. -
- Claims rent when account compresses. + + Rent Sponsor + + Light token program PDA that pays rent exemption at creation and claims rent when account compresses. - - - System Program - - + + System Program + Solana System Program to create the on-chain account. From ffe985c8794c2b415da2c6a9c9605a2c153d76e0 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Sat, 17 Jan 2026 02:49:53 +0000 Subject: [PATCH 07/10] program tabs --- docs.json | 1 + light-token/cookbook/approve-revoke.mdx | 112 ++++++ light-token/cookbook/burn.mdx | 72 ++++ light-token/cookbook/close-token-account.mdx | 113 ++---- light-token/cookbook/create-mint.mdx | 359 +++---------------- light-token/cookbook/freeze-thaw.mdx | 108 ++++++ light-token/cookbook/mint-to.mdx | 274 ++------------ light-token/cookbook/transfer-interface.mdx | 234 +++--------- 8 files changed, 433 insertions(+), 840 deletions(-) diff --git a/docs.json b/docs.json index 44710d9..be1e05e 100644 --- a/docs.json +++ b/docs.json @@ -57,6 +57,7 @@ "light-token/cookbook/overview", { "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 d282fae..ec9b6ae 100644 --- a/light-token/cookbook/approve-revoke.mdx +++ b/light-token/cookbook/approve-revoke.mdx @@ -17,6 +17,8 @@ import { } from "/snippets/code-samples/code-compare-snippets.jsx"; import ApproveFull from "/snippets/code-snippets/light-token/approve-revoke/rust-client/approve-full.mdx"; import RevokeFull from "/snippets/code-snippets/light-token/approve-revoke/rust-client/revoke-full.mdx"; +import NativeProgram from "/snippets/code-snippets/light-token/approve-revoke/program/native.mdx"; +import AnchorProgram from "/snippets/code-snippets/light-token/approve-revoke/program/anchor.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. @@ -85,6 +87,116 @@ import RevokeFull from "/snippets/code-snippets/light-token/approve-revoke/rust- + + +Find [a full code example at the end](#full-code-example). + + + + +### Approve + +Grant a delegate permission to transfer tokens. + + + + +```rust +use light_token_sdk::token::ApproveCpi; + +ApproveCpi { + token_account: token_account.clone(), + delegate: delegate.clone(), + owner: owner.clone(), + system_program: system_program.clone(), + amount, +} +.invoke() +``` + + + + +```rust +use light_token_sdk::token::ApproveCpi; + +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]) +``` + + + + + + + + +### Revoke + +Remove delegate permissions from a token account. + + + + +```rust +use light_token_sdk::token::RevokeCpi; + +RevokeCpi { + token_account: token_account.clone(), + owner: owner.clone(), + system_program: system_program.clone(), +} +.invoke() +``` + + + + +```rust +use light_token_sdk::token::RevokeCpi; + +let signer_seeds = authority_seeds!(bump); + +RevokeCpi { + token_account: token_account.clone(), + owner: owner.clone(), + system_program: system_program.clone(), +} +.invoke_signed(&[signer_seeds]) +``` + + + + + + + +# Full Code Example + + + Find the source code in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + + + + + + + + + + + + + # Next Steps diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx index 1fbfb0c..f3828f9 100644 --- a/light-token/cookbook/burn.mdx +++ b/light-token/cookbook/burn.mdx @@ -14,6 +14,8 @@ import { lightBurnRustCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustFullCode from "/snippets/code-snippets/light-token/burn/rust-client/full.mdx"; +import NativeProgram from "/snippets/code-snippets/light-token/burn/program/native.mdx"; +import AnchorProgram from "/snippets/code-snippets/light-token/burn/program/anchor.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. @@ -53,6 +55,76 @@ Compare to SPL: + + +Find [a full code example at the end](#full-code-example). + + + + +### Build Account Infos and CPI + +1. Pass the source token account, mint, amount, and authority. +2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. + + + + +```rust +use light_token_sdk::token::BurnCpi; + +BurnCpi { + source: source.clone(), + mint: mint.clone(), + amount, + authority: authority.clone(), + max_top_up: None, +} +.invoke() +``` + + + + +```rust +use light_token_sdk::token::BurnCpi; + +let signer_seeds = authority_seeds!(bump); + +BurnCpi { + source: source.clone(), + mint: mint.clone(), + amount, + authority: authority.clone(), + max_top_up: None, +} +.invoke_signed(&[signer_seeds]) +``` + + + + + + + +# Full Code Example + + + Find the source code in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + + + + + + + + + + + + + # Next Steps diff --git a/light-token/cookbook/close-token-account.mdx b/light-token/cookbook/close-token-account.mdx index d0d4cb9..1705c15 100644 --- a/light-token/cookbook/close-token-account.mdx +++ b/light-token/cookbook/close-token-account.mdx @@ -16,6 +16,8 @@ import { lightCloseAccountRustCode, } from "/snippets/code-samples/code-compare-snippets.jsx"; import RustFullCode from "/snippets/code-snippets/light-token/close-token-account/rust-client/full.mdx"; +import NativeProgram from "/snippets/code-snippets/light-token/close-token-account/program/native.mdx"; +import AnchorProgram from "/snippets/code-snippets/light-token/close-token-account/program/anchor.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. @@ -80,34 +82,34 @@ Find [a full code example at the end](#full-code-example). ```rust -use light_token_sdk::token::CloseTokenAccountCpi; +use light_token_sdk::token::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()?; +.invoke() ``` ```rust -use light_token_sdk::token::CloseTokenAccountCpi; +use light_token_sdk::token::CloseAccountCpi; -let close_account_cpi = CloseTokenAccountCpi { +let signer_seeds = authority_seeds!(bump); + +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]) ``` @@ -123,87 +125,18 @@ 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). + Find the source code in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). -```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(()) -} -``` + + + + + + + + diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx index d74f6df..2870c04 100644 --- a/light-token/cookbook/create-mint.mdx +++ b/light-token/cookbook/create-mint.mdx @@ -23,6 +23,8 @@ 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 RustFullCode from "/snippets/code-snippets/light-token/create-mint/rust-client/full.mdx"; import CompressibleRentExplained from "/snippets/compressible-rent-explained.mdx"; +import NativeProgram from "/snippets/code-snippets/light-token/create-mint/program/native.mdx"; +import AnchorProgram from "/snippets/code-snippets/light-token/create-mint/program/anchor.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. @@ -226,73 +228,50 @@ let system_accounts = SystemAccountInfos { 3. 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_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_sdk::token::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, - 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(), +use light_token_sdk::token::CreateMintCpi; + +let signer_seeds: &[&[u8]] = &[b"authority", &[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, - cpi_context: None, - cpi_context_account: None, params, -}; - -let signer_seeds: &[&[u8]] = &[MINT_SIGNER_SEED, &[bump]]; -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, -}; - -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])?; +) +.invoke_signed(&[signer_seeds]) ``` @@ -306,274 +285,18 @@ 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). + Find the source code in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). -```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(()) -} -``` + + + + + + + + diff --git a/light-token/cookbook/freeze-thaw.mdx b/light-token/cookbook/freeze-thaw.mdx index 16ef49a..6c4d5bd 100644 --- a/light-token/cookbook/freeze-thaw.mdx +++ b/light-token/cookbook/freeze-thaw.mdx @@ -17,6 +17,8 @@ import { } from "/snippets/code-samples/code-compare-snippets.jsx"; import FreezeFull from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/freeze-full.mdx"; import ThawFull from "/snippets/code-snippets/light-token/freeze-thaw/rust-client/thaw-full.mdx"; +import NativeProgram from "/snippets/code-snippets/light-token/freeze-thaw/program/native.mdx"; +import AnchorProgram from "/snippets/code-snippets/light-token/freeze-thaw/program/anchor.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. @@ -85,6 +87,112 @@ import ThawFull from "/snippets/code-snippets/light-token/freeze-thaw/rust-clien + + +Find [a full code example at the end](#full-code-example). + + + + +### Freeze + +Freeze a token account to prevent transfers. + + + + +```rust +use light_token_sdk::token::FreezeCpi; + +FreezeCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), +} +.invoke() +``` + + + + +```rust +use light_token_sdk::token::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]) +``` + + + + + + + + +### Thaw + +Thaw a frozen token account to re-enable transfers. + + + + +```rust +use light_token_sdk::token::ThawCpi; + +ThawCpi { + token_account: token_account.clone(), + mint: mint.clone(), + freeze_authority: freeze_authority.clone(), +} +.invoke() +``` + + + + +```rust +use light_token_sdk::token::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 + + + Find the source code in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + + + + + + + + + + + + + # Next Steps diff --git a/light-token/cookbook/mint-to.mdx b/light-token/cookbook/mint-to.mdx index 4599c14..7ae34f2 100644 --- a/light-token/cookbook/mint-to.mdx +++ b/light-token/cookbook/mint-to.mdx @@ -21,6 +21,8 @@ import { 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 RustFullCode from "/snippets/code-snippets/light-token/mint-to/rust-client/full.mdx"; +import NativeProgram from "/snippets/code-snippets/light-token/mint-to/program/native.mdx"; +import AnchorProgram from "/snippets/code-snippets/light-token/mint-to/program/anchor.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. @@ -110,57 +112,10 @@ Compare to SPL: -### Configure Mint Parameters - -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. - -```rust -use light_token_sdk::token::MintToParams; - -let params = MintToParams::new( - data.compressed_mint_inputs, - data.amount, - data.mint_authority, - data.proof, -); -``` - - - - - -### 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_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(), - system_program: system_program.clone(), -}; -``` - - - - - ### Build Account Infos and CPI -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. +1. Pass the mint, destination token account, amount, and authority. +2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. @@ -169,18 +124,14 @@ let system_accounts = SystemAccountInfos { use light_token_sdk::token::MintToCpi; MintToCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, 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(), + max_top_up: None, } -.invoke()?; +.invoke() ``` @@ -189,21 +140,17 @@ MintToCpi { ```rust use light_token_sdk::token::MintToCpi; -let account_infos = MintToCpi { +let signer_seeds = authority_seeds!(bump); + +MintToCpi { + mint: mint.clone(), + destination: destination.clone(), + amount, 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, -}; - -let signer_seeds: &[&[u8]] = &[MINT_AUTHORITY_SEED, &[bump]]; -account_infos.invoke_signed(&[signer_seeds])?; + system_program: system_program.clone(), + max_top_up: None, +} +.invoke_signed(&[signer_seeds]) ``` @@ -219,179 +166,18 @@ account_infos.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/ctoken_mint_to.rs). + Find the source code in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). -```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-interface.mdx b/light-token/cookbook/transfer-interface.mdx index 9173ac6..3141c1e 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -23,6 +23,8 @@ import { 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 RustFullCode from "/snippets/code-snippets/light-token/transfer-interface/rust-client/full.mdx"; +import NativeProgram from "/snippets/code-snippets/light-token/transfer-interface/program/native.mdx"; +import AnchorProgram from "/snippets/code-snippets/light-token/transfer-interface/program/anchor.mdx"; @@ -146,220 +148,76 @@ Find [a full code example at the end](#full-code-example). -### Light Token Transfer Interface +### Build Account Infos and CPI -Define the number of light-tokens / SPL tokens to transfer +1. Pass the amount, decimals, source and destination accounts, authority, payer, ctoken authority, and system program. +2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. -- from which SPL or light-token account, and -- to which SPL or light-token account. + + ```rust use light_token_sdk::token::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(), -); + ctoken_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, -)?; -``` - -SPL ↔ light-token transfers require a `spl_interface_pda`: - -- **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 - -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. - - - + + ```rust -transfer.invoke()?; -``` +use light_token_sdk::token::TransferInterfaceCpi; - - +let signer_seeds = authority_seeds!(bump); -```rust -let authority_seeds: &[&[u8]] = &[TRANSFER_INTERFACE_AUTHORITY_SEED, &[authority_bump]]; -transfer.invoke_signed(&[authority_seeds])?; +TransferInterfaceCpi::new( + amount, + decimals, + source.clone(), + destination.clone(), + authority.clone(), + payer.clone(), + ctoken_authority.clone(), + system_program.clone(), +) +.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/transfer_interface.rs). + Find the source code in the + [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). -```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(()) -} -``` + + + + + + + + From 979d714a952b541d3fd6f9954aa7bd7d120d97fc Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Sat, 17 Jan 2026 02:53:27 +0000 Subject: [PATCH 08/10] program tabs reorder --- light-token/cookbook/approve-revoke.mdx | 6 +++--- light-token/cookbook/burn.mdx | 6 +++--- light-token/cookbook/close-token-account.mdx | 6 +++--- light-token/cookbook/create-ata.mdx | 6 +++--- light-token/cookbook/create-mint.mdx | 6 +++--- light-token/cookbook/freeze-thaw.mdx | 6 +++--- light-token/cookbook/mint-to.mdx | 6 +++--- light-token/cookbook/transfer-interface.mdx | 6 +++--- 8 files changed, 24 insertions(+), 24 deletions(-) diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx index ec9b6ae..9baa069 100644 --- a/light-token/cookbook/approve-revoke.mdx +++ b/light-token/cookbook/approve-revoke.mdx @@ -187,12 +187,12 @@ RevokeCpi { - - - + + + diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx index f3828f9..860f1d3 100644 --- a/light-token/cookbook/burn.mdx +++ b/light-token/cookbook/burn.mdx @@ -115,12 +115,12 @@ BurnCpi { - - - + + + diff --git a/light-token/cookbook/close-token-account.mdx b/light-token/cookbook/close-token-account.mdx index 1705c15..bd6d066 100644 --- a/light-token/cookbook/close-token-account.mdx +++ b/light-token/cookbook/close-token-account.mdx @@ -130,12 +130,12 @@ CloseAccountCpi { - - - + + + diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index d5187f7..7baf4c5 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -202,12 +202,12 @@ CreateAssociatedAccountCpi { - - - + + + diff --git a/light-token/cookbook/create-mint.mdx b/light-token/cookbook/create-mint.mdx index 2870c04..4e97116 100644 --- a/light-token/cookbook/create-mint.mdx +++ b/light-token/cookbook/create-mint.mdx @@ -290,12 +290,12 @@ CreateMintCpi::new( - - - + + + diff --git a/light-token/cookbook/freeze-thaw.mdx b/light-token/cookbook/freeze-thaw.mdx index 6c4d5bd..c858e37 100644 --- a/light-token/cookbook/freeze-thaw.mdx +++ b/light-token/cookbook/freeze-thaw.mdx @@ -183,12 +183,12 @@ ThawCpi { - - - + + + diff --git a/light-token/cookbook/mint-to.mdx b/light-token/cookbook/mint-to.mdx index 7ae34f2..6181336 100644 --- a/light-token/cookbook/mint-to.mdx +++ b/light-token/cookbook/mint-to.mdx @@ -171,12 +171,12 @@ MintToCpi { - - - + + + diff --git a/light-token/cookbook/transfer-interface.mdx b/light-token/cookbook/transfer-interface.mdx index 3141c1e..f293fae 100644 --- a/light-token/cookbook/transfer-interface.mdx +++ b/light-token/cookbook/transfer-interface.mdx @@ -211,12 +211,12 @@ TransferInterfaceCpi::new( - - - + + + From 1208e7498198293d2c4b8ff21250e76e23020577 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Sat, 17 Jan 2026 02:55:31 +0000 Subject: [PATCH 09/10] reduce shimmmer --- snippets/jsx/code-compare.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snippets/jsx/code-compare.jsx b/snippets/jsx/code-compare.jsx index 43afb32..8d251cd 100644 --- a/snippets/jsx/code-compare.jsx +++ b/snippets/jsx/code-compare.jsx @@ -297,7 +297,7 @@ export const CodeCompare = ({ right: "50%", width: "60px", background: - "linear-gradient(to left, rgba(0, 102, 255, 0.15) 0%, transparent 100%)", + "linear-gradient(to left, rgba(0, 102, 255, 0.08) 0%, transparent 100%)", }} /> From 59bfb77cc3e724654334e26a61db977a9aa63393 Mon Sep 17 00:00:00 2001 From: tilo-14 Date: Sat, 17 Jan 2026 05:02:40 +0000 Subject: [PATCH 10/10] links and program examples --- docs.json | 2 +- light-token/cookbook/approve-revoke.mdx | 16 +-- light-token/cookbook/burn.mdx | 15 +- light-token/cookbook/close-token-account.mdx | 16 +-- light-token/cookbook/create-ata.mdx | 23 ++-- light-token/cookbook/create-mint.mdx | 17 +-- light-token/cookbook/create-token-account.mdx | 129 ++---------------- light-token/cookbook/freeze-thaw.mdx | 14 +- light-token/cookbook/mint-to.mdx | 13 +- light-token/cookbook/transfer-interface.mdx | 14 +- .../approve-revoke/program/anchor.mdx | 74 ---------- .../approve-revoke/program/native.mdx | 28 ++-- .../light-token/burn/program/native.mdx | 7 +- .../close-token-account/program/native.mdx | 13 +- .../light-token/create-ata/program/native.mdx | 11 +- .../create-mint/program/native.mdx | 57 +++++++- .../create-token-account/program/anchor.mdx | 51 +++++++ .../create-token-account/program/native.mdx | 66 +++++++++ .../freeze-thaw/program/anchor.mdx | 69 ---------- .../freeze-thaw/program/native.mdx | 26 ++-- .../light-token/mint-to/program/native.mdx | 15 +- .../transfer-interface/program/native.mdx | 7 +- 22 files changed, 281 insertions(+), 402 deletions(-) create mode 100644 snippets/code-snippets/light-token/create-token-account/program/anchor.mdx create mode 100644 snippets/code-snippets/light-token/create-token-account/program/native.mdx diff --git a/docs.json b/docs.json index be1e05e..bf01fa9 100644 --- a/docs.json +++ b/docs.json @@ -63,10 +63,10 @@ "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/wrap-unwrap", "light-token/cookbook/load-ata", + "light-token/cookbook/close-token-account", "light-token/cookbook/burn", "light-token/cookbook/freeze-thaw", "light-token/cookbook/approve-revoke" diff --git a/light-token/cookbook/approve-revoke.mdx b/light-token/cookbook/approve-revoke.mdx index 9baa069..2a7d47c 100644 --- a/light-token/cookbook/approve-revoke.mdx +++ b/light-token/cookbook/approve-revoke.mdx @@ -66,8 +66,7 @@ import AnchorProgram from "/snippets/code-snippets/light-token/approve-revoke/pr ### 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). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/sdk-libs/token-sdk/src/token) or find full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -89,15 +88,11 @@ import AnchorProgram from "/snippets/code-snippets/light-token/approve-revoke/pr -Find [a full code example at the end](#full-code-example). - ### Approve -Grant a delegate permission to transfer tokens. - @@ -141,8 +136,6 @@ ApproveCpi { ### Revoke -Remove delegate permissions from a token account. - @@ -182,8 +175,7 @@ RevokeCpi { # Full Code Example - Find the source code in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token/program/src/ctoken/approve_revoke.rs) or full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). @@ -202,9 +194,9 @@ RevokeCpi { # Next Steps diff --git a/light-token/cookbook/burn.mdx b/light-token/cookbook/burn.mdx index 860f1d3..d1ae308 100644 --- a/light-token/cookbook/burn.mdx +++ b/light-token/cookbook/burn.mdx @@ -1,6 +1,6 @@ --- title: Burn Light Tokens -sidebarTitle: Burn +sidebarTitle: Burn Light Tokens 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"] --- @@ -46,8 +46,7 @@ Compare to SPL: ### 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). + View [Source Code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/token/burn.rs) or find full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -57,16 +56,11 @@ Compare to SPL: -Find [a full code example at the end](#full-code-example). - ### Build Account Infos and CPI -1. Pass the source token account, mint, amount, and authority. -2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. - @@ -110,8 +104,7 @@ BurnCpi { # Full Code Example - Find the source code in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token/program/src/ctoken/burn.rs) or full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). @@ -130,7 +123,7 @@ BurnCpi { # 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). + View [Source Code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/token/close.rs) or find full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -67,17 +66,11 @@ Compare to SPL: - -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 to close and where remaining lamports should go -2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. - @@ -125,8 +118,7 @@ CloseAccountCpi { # Full Code Example - Find the source code in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token/program/src/ctoken/close/) or full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). @@ -147,9 +139,9 @@ CloseAccountCpi { {" "} diff --git a/light-token/cookbook/create-ata.mdx b/light-token/cookbook/create-ata.mdx index 7baf4c5..dc58507 100644 --- a/light-token/cookbook/create-ata.mdx +++ b/light-token/cookbook/create-ata.mdx @@ -101,8 +101,7 @@ Compare to SPL: ### Create 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). + View [Source Code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/token/create_ata.rs) or find full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -113,9 +112,6 @@ Compare to SPL: - -Find [a full code example at the end](#full-code-example). - @@ -129,13 +125,11 @@ Find [a full code example at the end](#full-code-example). ### Build Account Infos and CPI the Compressed 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. - + + 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. + @@ -197,8 +191,7 @@ CreateAssociatedAccountCpi { # Full Code Example - Find the source code in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token/program/src/ctoken/create_ata.rs) or full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). @@ -218,7 +211,7 @@ CreateAssociatedAccountCpi { {" "} - 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). + View [Source Code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/token/create_mint.rs) or find full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -124,9 +123,6 @@ Compare to SPL: - -Find [a full code example at the end](#full-code-example). - @@ -223,12 +219,6 @@ let system_accounts = SystemAccountInfos { ### 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`: - - When `mint_seed` is an external keypair, use `invoke`. - - When `mint_seed` is a PDA, use `invoke_signed` with its seeds. - @@ -285,8 +275,7 @@ CreateMintCpi::new( # Full Code Example - Find the source code in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token/program/src/compressed_token/mint_action/) or full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). @@ -305,7 +294,7 @@ CreateMintCpi::new( # 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). + View [Source Code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/token/create.rs) or find full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -65,9 +66,6 @@ Compare to SPL: - -Find [a full code example at the end](#full-code-example). - @@ -80,10 +78,6 @@ Find [a full code example at the end](#full-code-example). ### Build Account Infos and CPI -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. - @@ -129,121 +123,24 @@ 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 [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token/program/src/ctoken/create.rs) or full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). -```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(()) -} - -/// 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(()) -} -``` + + + + + + + + # 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). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/sdk-libs/token-sdk/src/token) or find full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -89,15 +88,11 @@ import AnchorProgram from "/snippets/code-snippets/light-token/freeze-thaw/progr -Find [a full code example at the end](#full-code-example). - ### Freeze -Freeze a token account to prevent transfers. - @@ -137,8 +132,6 @@ FreezeCpi { ### Thaw -Thaw a frozen token account to re-enable transfers. - @@ -178,8 +171,7 @@ ThawCpi { # Full Code Example - Find the source code in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token/program/src/ctoken/freeze_thaw.rs) or full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). @@ -198,7 +190,7 @@ ThawCpi { # 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). + View [Source Code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/token/mint_to.rs) or find full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -107,16 +106,11 @@ Compare to SPL: -Find [a full code example at the end](#full-code-example). - ### Build Account Infos and CPI -1. Pass the mint, destination token account, amount, and authority. -2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. - @@ -166,8 +160,7 @@ MintToCpi { # Full Code Example - Find the source code in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token/program/src/ctoken/mint_to.rs) or full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). @@ -185,7 +178,7 @@ MintToCpi { # Next Steps 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). + View [Source Code](https://github.com/Lightprotocol/light-protocol/blob/main/sdk-libs/token-sdk/src/token/transfer_interface.rs) or find full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/rust-client). @@ -142,17 +141,11 @@ The example transfers - -Find [a full code example at the end](#full-code-example). - ### Build Account Infos and CPI -1. Pass the amount, decimals, source and destination accounts, authority, payer, ctoken authority, and system program. -2. Use `invoke` or `invoke_signed`, when a CPI requires a PDA signer. - @@ -206,8 +199,7 @@ TransferInterfaceCpi::new( # Full Code Example - Find the source code in the - [examples-light-token repo](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). + View [Source Code](https://github.com/Lightprotocol/light-protocol/tree/main/programs/compressed-token/program/src/ctoken/transfer/) or full examples with tests: [examples-light-token](https://github.com/Lightprotocol/examples-light-token/tree/main/program-examples). @@ -228,7 +220,7 @@ TransferInterfaceCpi::new( {" "} ( - ctx: Context<'_, '_, '_, 'info, ApproveAccounts<'info>>, - 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: 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>, - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, -} - -// --- - -#![allow(unexpected_cfgs)] - -use anchor_lang::prelude::*; -use light_token_sdk::token::RevokeCpi; - -declare_id!("Dvq3UxQUDEF3B6khoJTdcbm3UutDsvxkdkTvxoJvegfg"); - -#[program] -pub mod light_token_anchor_revoke { - use super::*; - - pub fn revoke<'info>(ctx: Context<'_, '_, '_, 'info, RevokeAccounts<'info>>) -> 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: Validated by light-token CPI - #[account(mut)] - pub token_account: AccountInfo<'info>, - pub owner: Signer<'info>, - pub system_program: Program<'info, System>, - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, -} ``` diff --git a/snippets/code-snippets/light-token/approve-revoke/program/native.mdx b/snippets/code-snippets/light-token/approve-revoke/program/native.mdx index 9d89651..851284f 100644 --- a/snippets/code-snippets/light-token/approve-revoke/program/native.mdx +++ b/snippets/code-snippets/light-token/approve-revoke/program/native.mdx @@ -2,11 +2,14 @@ use super::authority_seeds; use light_token_sdk::token::ApproveCpi; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, }; -pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [token_account, delegate, owner, system_program, _token_program] = accounts else { +pub fn approve_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [token_account, delegate, owner, system_program, _token_program] = + accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -25,8 +28,10 @@ pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { .invoke() } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [token_account, delegate, owner, system_program, _token_program] = accounts else { +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); }; @@ -53,11 +58,13 @@ pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { use super::authority_seeds; use light_token_sdk::token::RevokeCpi; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, }; -pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { - let [token_account, owner, system_program, _token_program] = accounts else { +pub fn revoke_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_account, owner, system_program, _token_program] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -69,8 +76,9 @@ pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { .invoke() } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [token_account, owner, system_program, _token_program] = accounts else { +pub fn revoke_invoke_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [token_account, owner, system_program, _token_program] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; diff --git a/snippets/code-snippets/light-token/burn/program/native.mdx b/snippets/code-snippets/light-token/burn/program/native.mdx index 99685a5..cfce0d1 100644 --- a/snippets/code-snippets/light-token/burn/program/native.mdx +++ b/snippets/code-snippets/light-token/burn/program/native.mdx @@ -2,10 +2,11 @@ use super::authority_seeds; use light_token_sdk::token::BurnCpi; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, }; -pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +pub fn burn_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { let [source, mint, authority, _token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -25,7 +26,7 @@ pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { .invoke() } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +pub fn burn_invoke_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { let [source, mint, authority, _token_program] = accounts else { return Err(ProgramError::NotEnoughAccountKeys); }; diff --git a/snippets/code-snippets/light-token/close-token-account/program/native.mdx b/snippets/code-snippets/light-token/close-token-account/program/native.mdx index e4217f0..15652f6 100644 --- a/snippets/code-snippets/light-token/close-token-account/program/native.mdx +++ b/snippets/code-snippets/light-token/close-token-account/program/native.mdx @@ -2,11 +2,13 @@ use super::authority_seeds; use light_token_sdk::token::CloseAccountCpi; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, }; -pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { - let [token_program, account, destination, owner, rent_sponsor] = accounts else { +pub fn close_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_program, account, destination, owner, rent_sponsor] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -20,8 +22,9 @@ pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { .invoke() } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [token_program, account, destination, owner, rent_sponsor] = accounts else { +pub fn close_invoke_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [token_program, account, destination, owner, rent_sponsor] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; diff --git a/snippets/code-snippets/light-token/create-ata/program/native.mdx b/snippets/code-snippets/light-token/create-ata/program/native.mdx index 5675130..32dfd1e 100644 --- a/snippets/code-snippets/light-token/create-ata/program/native.mdx +++ b/snippets/code-snippets/light-token/create-ata/program/native.mdx @@ -1,11 +1,14 @@ ```rust use super::authority_seeds; -use light_token_sdk::token::{CompressibleParamsCpi, CreateAssociatedAccountCpi}; +use light_token_sdk::token::{ + CompressibleParamsCpi, CreateAssociatedAccountCpi, +}; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, }; -pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +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 { @@ -38,7 +41,7 @@ pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { .invoke() } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +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 { diff --git a/snippets/code-snippets/light-token/create-mint/program/native.mdx b/snippets/code-snippets/light-token/create-mint/program/native.mdx index 3f097cc..081edac 100644 --- a/snippets/code-snippets/light-token/create-mint/program/native.mdx +++ b/snippets/code-snippets/light-token/create-mint/program/native.mdx @@ -1,12 +1,15 @@ ```rust use borsh::BorshDeserialize; +use light_token_interface::instructions::extensions::{ + token_metadata::TokenMetadataInstructionData, ExtensionInstructionData, +}; use light_token_sdk::{ token::{CreateMintCpi, CreateMintParams, SystemAccountInfos}, CompressedProof, }; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, - pubkey::Pubkey, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, }; #[derive(BorshDeserialize)] @@ -23,9 +26,12 @@ struct CreateMintData { freeze_authority: Option, rent_payment: u8, write_top_up: u32, + metadata_name: Option>, + metadata_symbol: Option>, + metadata_uri: Option>, } -pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +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 { @@ -35,6 +41,24 @@ pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { 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, + }; + let params = CreateMintParams { decimals: ix_data.decimals, address_merkle_tree_root_index: ix_data.address_merkle_tree_root_index, @@ -48,7 +72,7 @@ pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { mint: ix_data.mint, bump: ix_data.bump, freeze_authority: ix_data.freeze_authority, - extensions: None, + extensions, rent_payment: ix_data.rent_payment, write_top_up: ix_data.write_top_up, }; @@ -91,9 +115,12 @@ struct CreateMintSignedData { rent_payment: u8, write_top_up: u32, authority_bump: u8, + metadata_name: Option>, + metadata_symbol: Option>, + metadata_uri: Option>, } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +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 { @@ -105,6 +132,24 @@ pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { let signer_seeds: &[&[u8]] = &[b"authority", &[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.address_merkle_tree_root_index, @@ -118,7 +163,7 @@ pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { mint: ix_data.mint, bump: ix_data.bump, freeze_authority: ix_data.freeze_authority, - extensions: None, + extensions, rent_payment: ix_data.rent_payment, write_top_up: ix_data.write_top_up, }; diff --git a/snippets/code-snippets/light-token/create-token-account/program/anchor.mdx b/snippets/code-snippets/light-token/create-token-account/program/anchor.mdx new file mode 100644 index 0000000..4c7aae0 --- /dev/null +++ b/snippets/code-snippets/light-token/create-token-account/program/anchor.mdx @@ -0,0 +1,51 @@ +```rust +#![allow(unexpected_cfgs)] + +use anchor_lang::prelude::*; +use light_token_sdk::token::{CompressibleParamsCpi, CreateTokenAccountCpi}; + +declare_id!("4fi27siKEvKXJYN5WCzWuHdAw1rLed6Tprv9ZARv3Gxu"); + +#[program] +pub mod light_token_anchor_create_token_account { + use super::*; + + pub fn create_token_account<'info>( + ctx: Context<'_, '_, '_, 'info, CreateTokenAccountAccounts<'info>>, + owner: Pubkey, + ) -> Result<()> { + let compressible_params = CompressibleParamsCpi::new( + ctx.accounts.compressible_config.to_account_info(), + ctx.accounts.rent_sponsor.to_account_info(), + ctx.accounts.system_program.to_account_info(), + ); + + CreateTokenAccountCpi { + payer: ctx.accounts.payer.to_account_info(), + account: ctx.accounts.account.to_account_info(), + mint: ctx.accounts.mint.to_account_info(), + owner, + compressible: Some(compressible_params), + } + .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>, + pub system_program: Program<'info, System>, + /// CHECK: Validated by light-token CPI + #[account(mut)] + pub rent_sponsor: AccountInfo<'info>, +} +``` diff --git a/snippets/code-snippets/light-token/create-token-account/program/native.mdx b/snippets/code-snippets/light-token/create-token-account/program/native.mdx new file mode 100644 index 0000000..7c08412 --- /dev/null +++ b/snippets/code-snippets/light-token/create-token-account/program/native.mdx @@ -0,0 +1,66 @@ +```rust +use super::authority_seeds; +use light_token_sdk::token::{CompressibleParamsCpi, CreateTokenAccountCpi}; +use solana_program::{ + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, pubkey::Pubkey, +}; + +pub fn create_token_account_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [payer, account, mint, compressible_config, system_program, rent_sponsor] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 32 { + return Err(ProgramError::InvalidInstructionData); + } + + let owner = Pubkey::try_from(&data[..32]).map_err(|_| ProgramError::InvalidInstructionData)?; + + let compressible_params = CompressibleParamsCpi::new( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + ); + + CreateTokenAccountCpi { + payer: payer.clone(), + account: account.clone(), + mint: mint.clone(), + owner, + compressible: Some(compressible_params), + } + .invoke() +} + +pub fn create_token_account_invoke_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [payer, account, mint, compressible_config, system_program, rent_sponsor] = accounts + else { + return Err(ProgramError::NotEnoughAccountKeys); + }; + + if data.len() < 33 { + return Err(ProgramError::InvalidInstructionData); + } + + let owner = Pubkey::try_from(&data[..32]).map_err(|_| ProgramError::InvalidInstructionData)?; + let bump = data[32]; + let signer_seeds = authority_seeds!(bump); + + let compressible_params = CompressibleParamsCpi::new( + compressible_config.clone(), + rent_sponsor.clone(), + system_program.clone(), + ); + + CreateTokenAccountCpi { + payer: payer.clone(), + account: account.clone(), + mint: mint.clone(), + owner, + compressible: Some(compressible_params), + } + .invoke_signed(&[signer_seeds]) +} +``` diff --git a/snippets/code-snippets/light-token/freeze-thaw/program/anchor.mdx b/snippets/code-snippets/light-token/freeze-thaw/program/anchor.mdx index 8968c38..25d322f 100644 --- a/snippets/code-snippets/light-token/freeze-thaw/program/anchor.mdx +++ b/snippets/code-snippets/light-token/freeze-thaw/program/anchor.mdx @@ -1,71 +1,2 @@ ```rust -#![allow(unexpected_cfgs)] - -use anchor_lang::prelude::*; -use light_token_sdk::token::FreezeCpi; - -declare_id!("7ovuM3dD2MZtcWQesVMiSYJef3oh1XH3e8nUk1ArpWX6"); - -#[program] -pub mod light_token_anchor_freeze { - use super::*; - - pub fn freeze<'info>(ctx: Context<'_, '_, '_, 'info, FreezeAccounts<'info>>) -> 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: 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>, - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, -} - -// --- - -#![allow(unexpected_cfgs)] - -use anchor_lang::prelude::*; -use light_token_sdk::token::ThawCpi; - -declare_id!("FL4MY6v5mTqncytUeuPoGroo6DHSsmBCPirckygGbcip"); - -#[program] -pub mod light_token_anchor_thaw { - use super::*; - - pub fn thaw<'info>(ctx: Context<'_, '_, '_, 'info, ThawAccounts<'info>>) -> 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: 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>, - /// CHECK: Light token program for CPI - pub light_token_program: AccountInfo<'info>, -} ``` diff --git a/snippets/code-snippets/light-token/freeze-thaw/program/native.mdx b/snippets/code-snippets/light-token/freeze-thaw/program/native.mdx index 3c73d94..6ae4c75 100644 --- a/snippets/code-snippets/light-token/freeze-thaw/program/native.mdx +++ b/snippets/code-snippets/light-token/freeze-thaw/program/native.mdx @@ -2,11 +2,13 @@ use super::authority_seeds; use light_token_sdk::token::FreezeCpi; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, }; -pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { - let [token_account, mint, freeze_authority, _token_program] = accounts else { +pub fn freeze_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -18,8 +20,9 @@ pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { .invoke() } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [token_account, mint, freeze_authority, _token_program] = accounts else { +pub fn freeze_invoke_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -43,11 +46,13 @@ pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { use super::authority_seeds; use light_token_sdk::token::ThawCpi; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, }; -pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { - let [token_account, mint, freeze_authority, _token_program] = accounts else { +pub fn thaw_invoke(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -59,8 +64,9 @@ pub fn process(accounts: &[AccountInfo], _data: &[u8]) -> ProgramResult { .invoke() } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [token_account, mint, freeze_authority, _token_program] = accounts else { +pub fn thaw_invoke_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [token_account, mint, freeze_authority, _token_program] = accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; diff --git a/snippets/code-snippets/light-token/mint-to/program/native.mdx b/snippets/code-snippets/light-token/mint-to/program/native.mdx index 3e3bc28..79f50dc 100644 --- a/snippets/code-snippets/light-token/mint-to/program/native.mdx +++ b/snippets/code-snippets/light-token/mint-to/program/native.mdx @@ -2,11 +2,14 @@ use super::authority_seeds; use light_token_sdk::token::MintToCpi; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, }; -pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [mint, destination, authority, system_program, _token_program] = accounts else { +pub fn mint_to_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + let [mint, destination, authority, system_program, _token_program] = + accounts + else { return Err(ProgramError::NotEnoughAccountKeys); }; @@ -26,8 +29,10 @@ pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { .invoke() } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { - let [mint, destination, authority, system_program, _token_program] = accounts else { +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); }; diff --git a/snippets/code-snippets/light-token/transfer-interface/program/native.mdx b/snippets/code-snippets/light-token/transfer-interface/program/native.mdx index ca0a49e..05c8293 100644 --- a/snippets/code-snippets/light-token/transfer-interface/program/native.mdx +++ b/snippets/code-snippets/light-token/transfer-interface/program/native.mdx @@ -2,10 +2,11 @@ use super::authority_seeds; use light_token_sdk::token::TransferInterfaceCpi; use solana_program::{ - account_info::AccountInfo, entrypoint::ProgramResult, program_error::ProgramError, + account_info::AccountInfo, entrypoint::ProgramResult, + program_error::ProgramError, }; -pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +pub fn transfer_invoke(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { let [source, destination, authority, payer, ctoken_authority, system_program, _token_program] = accounts else { @@ -32,7 +33,7 @@ pub fn process(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { .invoke() } -pub fn process_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { +pub fn transfer_invoke_signed(accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { let [source, destination, authority, payer, ctoken_authority, system_program, _token_program] = accounts else {