Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/sdk-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
- program: anchor & pinocchio
sub-tests: '["cargo-test-sbf -p sdk-anchor-test", "cargo-test-sbf -p sdk-compressible-test", "cargo-test-sbf -p csdk-anchor-derived-test", "cargo-test-sbf -p csdk-anchor-full-derived-test", "cargo-test-sbf -p sdk-pinocchio-v1-test", "cargo-test-sbf -p sdk-pinocchio-v2-test", "cargo-test-sbf -p pinocchio-nostd-test", "cargo-test-sbf -p single-mint-test", "cargo-test-sbf -p single-pda-test", "cargo-test-sbf -p single-ata-test", "cargo-test-sbf -p single-token-test"]'
- program: token test
sub-tests: '["cargo-test-sbf -p sdk-token-test"]'
sub-tests: '["cargo-test-sbf -p sdk-token-test", "cargo-test-sbf -p token-client-test"]'
- program: sdk-libs
test_cmd: just sdk-libs test
steps:
Expand Down
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ members = [
"sdk-tests/sdk-v1-native-test",
"sdk-tests/sdk-token-test",
"sdk-tests/sdk-light-token-test",
"sdk-tests/token-client-test",
"sdk-tests/csdk-anchor-full-derived-test",
"sdk-tests/csdk-anchor-full-derived-test-sdk",
"sdk-tests/single-mint-test",
Expand Down
3 changes: 3 additions & 0 deletions forester-utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ light-prover-client = { workspace = true }
light-registry = { workspace = true, features = ["cpi"] }
account-compression = { workspace = true, features = ["cpi"] }
light-token-interface = { workspace = true }
light-compressed-token-sdk = { workspace = true }
light-compressible = { workspace = true }
borsh = { workspace = true }

solana-instruction = { workspace = true }
solana-pubkey = { workspace = true }
Expand Down
118 changes: 118 additions & 0 deletions forester-utils/src/instructions/compress_and_close_mint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use borsh::BorshDeserialize;
use light_client::{
indexer::Indexer,
rpc::{Rpc, RpcError},
};
use light_compressed_account::instruction_data::traits::LightInstructionData;
use light_compressed_token_sdk::compressed_token::{
create_compressed_mint::find_mint_address, mint_action::MintActionMetaConfig,
};
use light_compressible::config::CompressibleConfig;
use light_token_interface::{
instructions::mint_action::{
CompressAndCloseMintAction, MintActionCompressedInstructionData, MintWithContext,
},
state::Mint,
LIGHT_TOKEN_PROGRAM_ID,
};
use solana_instruction::Instruction;
use solana_pubkey::Pubkey;

/// Creates a CompressAndCloseMint instruction by fetching required data from RPC/indexer.
///
/// This is permissionless - anyone can call when the mint is compressible (rent expired).
///
/// # Parameters
/// - `rpc`: RPC client that also implements Indexer
/// - `payer`: Account paying for transaction fees
/// - `compressed_mint_address`: The 32-byte compressed address of the mint
/// - `mint_seed`: The seed pubkey used to derive the mint PDA
/// - `idempotent`: If true, succeed silently when Mint doesn't exist
pub async fn create_compress_and_close_mint_instruction<R: Rpc + Indexer>(
rpc: &mut R,
payer: Pubkey,
compressed_mint_address: [u8; 32],
mint_seed: Pubkey,
idempotent: bool,
) -> Result<Instruction, RpcError> {
// Derive the mint PDA from mint_seed
let (mint_pda, _bump) = find_mint_address(&mint_seed);

// Get the compressed mint account
let compressed_mint_account = rpc
.get_compressed_account(compressed_mint_address, None)
.await?
.value
.ok_or_else(|| RpcError::AccountDoesNotExist(format!("{:?}", compressed_mint_address)))?;

// Try to deserialize the compressed mint - may be None if Mint is already decompressed
let compressed_mint: Option<Mint> = compressed_mint_account
.data
.as_ref()
.and_then(|d| BorshDeserialize::deserialize(&mut d.data.as_slice()).ok());

// Get validity proof for the compressed mint
let rpc_proof_result = rpc
.get_validity_proof(vec![compressed_mint_account.hash], vec![], None)
.await?
.value;

// Build MintWithContext
let compressed_mint_inputs = MintWithContext {
prove_by_index: rpc_proof_result.accounts[0].root_index.proof_by_index(),
leaf_index: compressed_mint_account.leaf_index,
root_index: rpc_proof_result.accounts[0]
.root_index
.root_index()
.unwrap_or_default(),
address: compressed_mint_address,
mint: compressed_mint.map(|m| m.try_into().unwrap()),
};

// Build instruction data with CompressAndCloseMint action
let instruction_data = MintActionCompressedInstructionData::new(
compressed_mint_inputs,
rpc_proof_result.proof.into(),
)
.with_compress_and_close_mint(CompressAndCloseMintAction {
idempotent: if idempotent { 1 } else { 0 },
});

// Get CompressibleConfig for rent_sponsor
let config_address = CompressibleConfig::light_token_v1_config_pda();
let compressible_config: CompressibleConfig = rpc
.get_anchor_account(&config_address)
.await?
.ok_or_else(|| {
RpcError::CustomError(format!(
"CompressibleConfig not found at {}",
config_address
))
})?;

// Build account metas configuration
let state_tree_info = rpc_proof_result.accounts[0].tree_info;
let config = MintActionMetaConfig::new(
payer,
payer, // authority - permissionless, using payer
state_tree_info.tree,
state_tree_info.queue,
state_tree_info.queue,
)
.with_compressible_mint(mint_pda, config_address, compressible_config.rent_sponsor);

// Get account metas
let account_metas = config.to_account_metas();

// Serialize instruction data
let data = instruction_data
.data()
.map_err(|e| RpcError::CustomError(format!("Failed to serialize instruction: {:?}", e)))?;

// Build final instruction
Ok(Instruction {
program_id: LIGHT_TOKEN_PROGRAM_ID.into(),
accounts: account_metas,
data,
})
}
3 changes: 3 additions & 0 deletions forester-utils/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ pub mod create_account;
pub use create_account::create_account_instruction;

pub mod claim;
pub mod compress_and_close_mint;
pub mod withdraw_funding_pool;

pub use compress_and_close_mint::create_compress_and_close_mint_instruction;
65 changes: 29 additions & 36 deletions forester/src/compressible/mint/compressor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ use std::sync::{
Arc,
};

use forester_utils::rpc_pool::SolanaRpcPool;
use forester_utils::{
instructions::create_compress_and_close_mint_instruction, rpc_pool::SolanaRpcPool,
};
use futures::StreamExt;
use light_client::{indexer::Indexer, rpc::Rpc};
use light_token_client::instructions::mint_action::{
create_mint_action_instruction, MintActionParams, MintActionType,
};
use solana_sdk::{
instruction::Instruction,
signature::{Keypair, Signature},
Expand Down Expand Up @@ -71,24 +70,21 @@ impl<R: Rpc + Indexer> MintCompressor<R> {
async move {
let mut rpc = rpc_pool.get_connection().await?;

let params = MintActionParams {
compressed_mint_address: compressed_address,
mint_seed,
authority: payer,
let ix = create_compress_and_close_mint_instruction(
&mut *rpc,
payer,
actions: vec![MintActionType::CompressAndCloseMint { idempotent: true }],
new_mint: None,
};

let ix = create_mint_action_instruction(&mut *rpc, params)
.await
.map_err(|e| {
anyhow::anyhow!(
"Failed to build CompressAndCloseMint instruction for {}: {:?}",
mint_pda,
e
)
})?;
compressed_address,
mint_seed,
true, // idempotent
)
.await
.map_err(|e| {
anyhow::anyhow!(
"Failed to build CompressAndCloseMint instruction for {}: {:?}",
mint_pda,
e
)
})?;
Comment on lines +73 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Add mint context to instruction-build errors.
Including seed/compressed address in both error paths will make triage much faster when a build fails.

♻️ Proposed tweak
-                .map_err(|e| {
-                    anyhow::anyhow!(
-                        "Failed to build CompressAndCloseMint instruction for {}: {:?}",
-                        mint_pda,
-                        e
-                    )
-                })?;
+                .map_err(|e| {
+                    anyhow::anyhow!(
+                        "Failed to build CompressAndCloseMint instruction for {} (seed: {:?}, compressed_address: {:?}): {:?}",
+                        mint_pda,
+                        mint_seed,
+                        compressed_address,
+                        e
+                    )
+                })?;
-        .map_err(|e| {
-            anyhow::anyhow!("Failed to build CompressAndCloseMint instruction: {:?}", e)
-        })?;
+        .map_err(|e| {
+            anyhow::anyhow!(
+                "Failed to build CompressAndCloseMint instruction for {} (seed: {:?}, compressed_address: {:?}): {:?}",
+                mint_pda,
+                mint_seed,
+                compressed_address,
+                e
+            )
+        })?;

Also applies to: 218-230

🤖 Prompt for AI Agents
In `@forester/src/compressible/mint/compressor.rs` around lines 73 - 87, The error
path wrapping the await on create_compress_and_close_mint_instruction should
include mint context (e.g., mint_pda, mint_seed, compressed_address) in the
error message; update the map_err closures for
create_compress_and_close_mint_instruction (and the similar call around lines
218-230) to format and include those variables so the anyhows show seed and
compressed address along with the original error.


Ok::<Instruction, anyhow::Error>(ix)
}
Expand Down Expand Up @@ -219,22 +215,19 @@ impl<R: Rpc + Indexer> MintCompressor<R> {

let mut rpc = self.rpc_pool.get_connection().await?;

// Build the CompressAndCloseMint instruction using the mint action builder
// Build the CompressAndCloseMint instruction
// This is idempotent - succeeds silently if mint doesn't exist or is already compressed
let params = MintActionParams {
compressed_mint_address: compressed_address,
mint_seed: *mint_seed,
authority: self.payer_keypair.pubkey(),
payer: self.payer_keypair.pubkey(),
actions: vec![MintActionType::CompressAndCloseMint { idempotent: true }],
new_mint: None,
};

let ix = create_mint_action_instruction(&mut *rpc, params)
.await
.map_err(|e| {
anyhow::anyhow!("Failed to build CompressAndCloseMint instruction: {:?}", e)
})?;
let ix = create_compress_and_close_mint_instruction(
&mut *rpc,
self.payer_keypair.pubkey(),
compressed_address,
*mint_seed,
true, // idempotent
)
.await
.map_err(|e| {
anyhow::anyhow!("Failed to build CompressAndCloseMint instruction: {:?}", e)
})?;

debug!(
"Built CompressAndCloseMint instruction for Mint {}",
Expand Down
10 changes: 5 additions & 5 deletions forester/tests/e2e_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ use light_hasher::Poseidon;
use light_program_test::accounts::test_accounts::TestAccounts;
use light_prover_client::prover::spawn_prover;
use light_test_utils::{
conversions::sdk_to_program_token_data, get_concurrent_merkle_tree, get_indexed_merkle_tree,
pack::pack_new_address_params_assigned, spl::create_mint_helper_with_keypair,
actions::{create_compressible_token_account, CreateCompressibleTokenAccountInputs},
conversions::sdk_to_program_token_data,
get_concurrent_merkle_tree, get_indexed_merkle_tree,
pack::pack_new_address_params_assigned,
spl::create_mint_helper_with_keypair,
system_program::create_invoke_instruction,
};
use light_token::compat::TokenDataWithMerkleContext;
use light_token_client::actions::{
create_compressible_token_account, CreateCompressibleTokenAccountInputs,
};
use light_token_interface::state::TokenDataVersion;
use rand::{prelude::SliceRandom, rngs::StdRng, Rng, SeedableRng};
use serial_test::serial;
Expand Down
2 changes: 1 addition & 1 deletion forester/tests/test_compressible_ctoken.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use light_registry::{
utils::{get_forester_pda, get_protocol_config_pda_address},
ForesterConfig,
};
use light_token_client::actions::{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming to latest

use light_test_utils::actions::legacy::{
create_compressible_token_account, CreateCompressibleTokenAccountInputs,
};
use light_token_interface::state::TokenDataVersion;
Expand Down
10 changes: 6 additions & 4 deletions program-tests/compressed-token-test/tests/compress_only/all.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ async fn test_compress_and_close_ctoken_with_extensions() {
use light_client::indexer::CompressedTokenAccount;
use light_client::indexer::Indexer;
use light_compressed_token_sdk::spl_interface::find_spl_interface_pda_with_index;
use light_test_utils::mint_2022::{create_token_22_account, mint_spl_tokens_22};
use light_token::instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl};
use light_token_client::instructions::transfer2::{
create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType,
use light_test_utils::{
actions::legacy::instructions::transfer2::{
create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType,
},
mint_2022::{create_token_22_account, mint_spl_tokens_22},
};
use light_token::instruction::{CompressibleParams, CreateTokenAccount, TransferFromSpl};
use light_token_interface::{
instructions::extensions::{
CompressedOnlyExtensionInstructionData, ExtensionInstructionData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use light_program_test::{
program_test::TestRpc, utils::assert::assert_rpc_error, LightProgramTest, ProgramTestConfig,
};
use light_test_utils::{
actions::legacy::instructions::transfer2::{
create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType,
},
mint_2022::{
create_mint_22_with_extension_types, create_token_22_account, mint_spl_tokens_22,
RESTRICTED_EXTENSIONS,
Expand All @@ -19,9 +22,6 @@ use light_token::instruction::{
derive_token_ata, CompressibleParams, CreateAssociatedTokenAccount, CreateTokenAccount,
TransferFromSpl,
};
use light_token_client::instructions::transfer2::{
create_generic_transfer2_instruction, DecompressInput, Transfer2InstructionType,
};
use light_token_interface::{
instructions::extensions::{CompressedOnlyExtensionInstructionData, ExtensionInstructionData},
state::{ExtensionStruct, TokenDataVersion},
Expand Down
Loading
Loading