From 4ed9b040cd79550e75ec47552c993601dab5c9ed Mon Sep 17 00:00:00 2001 From: ananas-block Date: Mon, 8 Dec 2025 00:22:39 +0000 Subject: [PATCH 1/2] feat: add compressible config xtask --- Cargo.lock | 2 + xtask/Cargo.toml | 2 + xtask/src/create_compressible_config.rs | 235 ++++++++++++++++++++++++ xtask/src/main.rs | 8 + 4 files changed, 247 insertions(+) create mode 100644 xtask/src/create_compressible_config.rs diff --git a/Cargo.lock b/Cargo.lock index 25a3fa84c3..a7bec812d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11923,6 +11923,7 @@ name = "xtask" version = "1.1.0" dependencies = [ "account-compression", + "anchor-lang", "anyhow", "ark-bn254 0.5.0", "ark-ff 0.5.0", @@ -11933,6 +11934,7 @@ dependencies = [ "light-batched-merkle-tree", "light-client", "light-compressed-account", + "light-compressible", "light-concurrent-merkle-tree", "light-hash-set", "light-hasher", diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index dedc1b78fe..263c681ace 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -35,3 +35,5 @@ solana-client = { workspace = true } solana-transaction-status = { workspace = true } light-batched-merkle-tree = { workspace = true } light-registry = { workspace = true } +light-compressible = { workspace = true } +anchor-lang = { workspace = true } diff --git a/xtask/src/create_compressible_config.rs b/xtask/src/create_compressible_config.rs new file mode 100644 index 0000000000..63783113a1 --- /dev/null +++ b/xtask/src/create_compressible_config.rs @@ -0,0 +1,235 @@ +use std::path::PathBuf; + +use anchor_lang::{InstructionData, ToAccountMetas}; +use clap::Parser; +use dirs::home_dir; +use light_client::rpc::{LightClient, LightClientConfig, Rpc}; +use light_compressible::{ + config::COMPRESSIBLE_CONFIG_SEED, + registry_instructions::CreateCompressibleConfig as CreateCompressibleConfigParams, + rent::RentConfig, +}; +use light_registry::{ + compressible::create_config_counter::COMPRESSIBLE_CONFIG_COUNTER_SEED, + utils::get_protocol_config_pda_address, +}; +use solana_sdk::{ + instruction::Instruction, + pubkey::Pubkey, + signature::{read_keypair_file, Signer}, +}; + +const REGISTRY_PROGRAM_ID: Pubkey = + solana_sdk::pubkey!("Lighton6oQpVkeewmo2mcPTQQp7kYHr4fWpAgJyEmDX"); + +#[derive(Debug, Parser)] +pub struct Options { + /// Path to payer keypair file (defaults to ~/.config/solana/id.json) + #[clap(long)] + payer: Option, + + /// Network: devnet, mainnet, local, or custom RPC URL (default: devnet) + #[clap(long, default_value = "devnet")] + network: String, + + /// Skip config counter creation (if it already exists) + #[clap(long, default_value = "false")] + skip_counter: bool, +} + +fn get_rpc_url(network: &str) -> String { + match network { + "local" => String::from("http://127.0.0.1:8899"), + "devnet" => String::from("https://api.devnet.solana.com"), + "mainnet" => String::from("https://api.mainnet-beta.solana.com"), + other => other.to_string(), + } +} + +fn get_config_counter_pda() -> (Pubkey, u8) { + Pubkey::find_program_address(&[COMPRESSIBLE_CONFIG_COUNTER_SEED], ®ISTRY_PROGRAM_ID) +} + +fn get_compressible_config_pda(version: u16) -> (Pubkey, u8) { + Pubkey::find_program_address( + &[COMPRESSIBLE_CONFIG_SEED, &version.to_le_bytes()], + ®ISTRY_PROGRAM_ID, + ) +} + +fn create_config_counter_ix(fee_payer: Pubkey, authority: Pubkey) -> Instruction { + let (protocol_config_pda, _) = get_protocol_config_pda_address(); + let (config_counter_pda, _) = get_config_counter_pda(); + + let accounts = light_registry::accounts::CreateConfigCounter { + fee_payer, + authority, + protocol_config_pda, + config_counter: config_counter_pda, + system_program: anchor_lang::system_program::ID, + }; + + let instruction_data = light_registry::instruction::CreateConfigCounter {}; + + Instruction { + program_id: REGISTRY_PROGRAM_ID, + accounts: accounts.to_account_metas(Some(true)), + data: instruction_data.data(), + } +} + +fn create_compressible_config_ix( + fee_payer: Pubkey, + authority: Pubkey, + update_authority: Pubkey, + withdrawal_authority: Pubkey, + version: u16, +) -> Instruction { + let (protocol_config_pda, _) = get_protocol_config_pda_address(); + let (config_counter_pda, _) = get_config_counter_pda(); + let (compressible_config_pda, _) = get_compressible_config_pda(version); + + let accounts = light_registry::accounts::CreateCompressibleConfig { + fee_payer, + authority, + protocol_config_pda, + config_counter: config_counter_pda, + compressible_config: compressible_config_pda, + system_program: anchor_lang::system_program::ID, + }; + + let instruction_data = light_registry::instruction::CreateCompressibleConfig { + params: CreateCompressibleConfigParams { + rent_config: RentConfig::default(), + update_authority, + withdrawal_authority, + active: true, + }, + }; + + Instruction { + program_id: REGISTRY_PROGRAM_ID, + accounts: accounts.to_account_metas(Some(true)), + data: instruction_data.data(), + } +} + +pub async fn create_compressible_config(options: Options) -> anyhow::Result<()> { + let rpc_url = get_rpc_url(&options.network); + println!("Connecting to: {}", rpc_url); + + let mut rpc = LightClient::new(LightClientConfig { + url: rpc_url, + photon_url: None, + commitment_config: None, + fetch_active_tree: false, + api_key: None, + }) + .await?; + + // Load payer keypair + let payer = if let Some(payer_path) = options.payer.as_ref() { + read_keypair_file(payer_path).unwrap_or_else(|_| panic!("Failed to read {:?}", payer_path)) + } else { + let keypair_path: PathBuf = home_dir() + .expect("Could not find home directory") + .join(".config/solana/id.json"); + read_keypair_file(&keypair_path) + .unwrap_or_else(|_| panic!("Keypair not found in default path {:?}", keypair_path)) + }; + println!("Payer: {:?}", payer.pubkey()); + + let balance = rpc.get_balance(&payer.pubkey()).await?; + println!( + "Payer balance: {} lamports ({} SOL)", + balance, + balance as f64 / 1e9 + ); + + // Get protocol config PDA to fetch the authority + let (protocol_config_pda, _) = get_protocol_config_pda_address(); + println!("Protocol config PDA: {:?}", protocol_config_pda); + + let protocol_config_account = rpc + .get_account(protocol_config_pda) + .await? + .ok_or_else(|| anyhow::anyhow!("Protocol config account not found"))?; + + // Extract authority from account data: + // - 8 bytes discriminator + // - 32 bytes authority pubkey + if protocol_config_account.data.len() < 40 { + return Err(anyhow::anyhow!("Protocol config account data too short")); + } + let protocol_authority = Pubkey::try_from(&protocol_config_account.data[8..40]) + .map_err(|_| anyhow::anyhow!("Failed to parse protocol authority pubkey"))?; + println!("Protocol authority: {:?}", protocol_authority); + + // Check that payer is the protocol authority + if payer.pubkey() != protocol_authority { + return Err(anyhow::anyhow!( + "Payer ({}) is not the protocol authority ({}). \ + The protocol authority must sign these transactions.", + payer.pubkey(), + protocol_authority + )); + } + + let (config_counter_pda, _) = get_config_counter_pda(); + println!("Config counter PDA: {:?}", config_counter_pda); + + // Step 1: Create config counter if it doesn't exist + if !options.skip_counter { + let counter_account = rpc.get_account(config_counter_pda).await?; + if counter_account.is_none() { + println!("\n=== Creating Config Counter ==="); + let create_counter_ix = create_config_counter_ix(payer.pubkey(), payer.pubkey()); + + let signature = rpc + .create_and_send_transaction(&[create_counter_ix], &payer.pubkey(), &[&payer]) + .await?; + println!("Config counter created! Signature: {:?}", signature); + } else { + println!("Config counter already exists, skipping creation."); + } + } + + // Step 2: Create compressible config with version 1 + let version: u16 = 1; + let (compressible_config_pda, _) = get_compressible_config_pda(version); + println!("\n=== Creating Compressible Config ==="); + println!("Compressible config PDA: {:?}", compressible_config_pda); + + // Check if config already exists + let config_account = rpc.get_account(compressible_config_pda).await?; + if config_account.is_some() { + println!( + "Compressible config already exists at {:?}", + compressible_config_pda + ); + return Ok(()); + } + + let create_config_ix = create_compressible_config_ix( + payer.pubkey(), + payer.pubkey(), + protocol_authority, + protocol_authority, + version, + ); + + let signature = rpc + .create_and_send_transaction(&[create_config_ix], &payer.pubkey(), &[&payer]) + .await?; + println!("Compressible config created! Signature: {:?}", signature); + + println!("\n=== Summary ==="); + println!("Network: {}", options.network); + println!("Config counter PDA: {:?}", config_counter_pda); + println!("Compressible config PDA: {:?}", compressible_config_pda); + println!("Update authority: {:?}", protocol_authority); + println!("Withdrawal authority: {:?}", protocol_authority); + println!("Rent config: {:?}", RentConfig::default()); + + Ok(()) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 56bab7a25c..ed55f7c6bc 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -3,6 +3,7 @@ use clap::{Parser, ValueEnum}; mod bench; mod create_batch_address_tree; mod create_batch_state_tree; +mod create_compressible_config; mod create_state_tree; mod create_update_protocol_config_ix; mod create_vkeyrs_from_gnark_key; @@ -73,6 +74,10 @@ enum Command { /// cargo xtask fetch-accounts rpc --lut --pubkeys --network mainnet /// cargo xtask fetch-accounts rpc --lut --pubkeys --add-pubkeys , --network mainnet FetchAccounts(fetch_accounts::Options), + /// Create compressible config (config counter + compressible config) + /// Creates the config counter PDA and a compressible config with default RentConfig. + /// Example: cargo xtask create-compressible-config --network devnet + CreateCompressibleConfig(create_compressible_config::Options), } #[tokio::main] @@ -108,5 +113,8 @@ async fn main() -> Result<(), anyhow::Error> { Command::PrintStateTree(opts) => print_state_tree::print_state_tree(opts).await, Command::ReinitCpiAccounts(opts) => reinit_cpi_accounts::reinit_cpi_accounts(opts).await, Command::FetchAccounts(opts) => fetch_accounts::fetch_accounts(opts).await, + Command::CreateCompressibleConfig(opts) => { + create_compressible_config::create_compressible_config(opts).await + } } } From 43d26d42af8e6da0f3cc4e7e6d36fc404f5d18c7 Mon Sep 17 00:00:00 2001 From: ananas-block Date: Mon, 8 Dec 2025 00:28:37 +0000 Subject: [PATCH 2/2] chore(programs): bump light-registry and light-compressed-token to v2.1.0 --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- programs/compressed-token/program/Cargo.toml | 2 +- programs/registry/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a7bec812d2..5ba0428e4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3660,7 +3660,7 @@ dependencies = [ [[package]] name = "light-compressed-token" -version = "2.0.0" +version = "2.1.0" dependencies = [ "account-compression", "anchor-compressed-token", @@ -4085,7 +4085,7 @@ dependencies = [ [[package]] name = "light-registry" -version = "2.0.0" +version = "2.1.0" dependencies = [ "account-compression", "aligned-sized", diff --git a/Cargo.toml b/Cargo.toml index 8df0587a3e..ce6c9d3342 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -198,7 +198,7 @@ forester-utils = { path = "forester-utils", version = "2.0.0" } account-compression = { path = "programs/account-compression", version = "2.0.0", features = [ "cpi", ] } -light-compressed-token = { path = "programs/compressed-token/program", version = "2.0.0", features = [ +light-compressed-token = { path = "programs/compressed-token/program", version = "2.1.0", features = [ "cpi", ] } light-ctoken-types = { path = "sdk-libs/ctoken-types", version = "0.2.1" } @@ -207,7 +207,7 @@ light-token-client = { path = "sdk-libs/token-client", version = "0.1.0" } light-system-program-anchor = { path = "anchor-programs/system", version = "2.0.0", features = [ "cpi", ] } -light-registry = { path = "programs/registry", version = "2.0.0", features = [ +light-registry = { path = "programs/registry", version = "2.1.0", features = [ "cpi", ] } create-address-test-program = { path = "program-tests/create-address-test-program", version = "1.0.0", features = [ diff --git a/programs/compressed-token/program/Cargo.toml b/programs/compressed-token/program/Cargo.toml index d680cfd21e..654be30a12 100644 --- a/programs/compressed-token/program/Cargo.toml +++ b/programs/compressed-token/program/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "light-compressed-token" -version = "2.0.0" +version = "2.1.0" description = "Generalized token compression on Solana" repository = "https://github.com/Lightprotocol/light-protocol" license = "Apache-2.0" diff --git a/programs/registry/Cargo.toml b/programs/registry/Cargo.toml index b050302653..367115c837 100644 --- a/programs/registry/Cargo.toml +++ b/programs/registry/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "light-registry" -version = "2.0.0" +version = "2.1.0" description = "Light core protocol logic" repository = "https://github.com/Lightprotocol/light-protocol" license = "Apache-2.0"