light_program_test/forester/
compress_and_close_forester.rs

1use light_client::{
2    indexer::Indexer,
3    rpc::{Rpc, RpcError},
4};
5use light_compressible::config::CompressibleConfig;
6use light_sdk::instruction::PackedAccounts;
7use light_token::compressed_token::CompressAndCloseAccounts as CTokenCompressAndCloseAccounts;
8use solana_sdk::{
9    pubkey::Pubkey,
10    signature::{Keypair, Signature},
11    signer::Signer,
12};
13
14use crate::registry_sdk::{
15    build_compress_and_close_instruction, get_forester_epoch_pda_from_authority,
16    CompressAndCloseIndices, REGISTRY_PROGRAM_ID,
17};
18
19/// Compress and close token accounts via the registry program
20///
21/// This function invokes the registry program's compress_and_close instruction,
22/// which then CPIs to the compressed token program with the correct compression_authority PDA signer.
23///
24/// # Arguments
25/// * `rpc` - RPC client with indexer capabilities
26/// * `solana_ctoken_accounts` - List of compressible token accounts to compress and close
27/// * `authority` - Authority that can execute the compress and close
28/// * `payer` - Transaction fee payer
29/// * `destination` - Optional destination for compression incentive (defaults to payer)
30///
31/// # Returns
32/// `Result<Signature, RpcError>` - Transaction signature
33pub async fn compress_and_close_forester<R: Rpc + Indexer>(
34    rpc: &mut R,
35    solana_ctoken_accounts: &[Pubkey],
36    authority: &Keypair,
37    payer: &Keypair,
38    destination: Option<Pubkey>,
39) -> Result<Signature, RpcError> {
40    // Compressed token program ID
41    let compressed_token_program_id =
42        Pubkey::from_str_const("cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m");
43
44    let current_epoch = 0;
45
46    // Derive registered forester PDA for the current epoch
47    let (registered_forester_pda, _) =
48        get_forester_epoch_pda_from_authority(&authority.pubkey(), current_epoch);
49
50    let config = CompressibleConfig::light_token_v1(Pubkey::default(), Pubkey::default());
51
52    let compressible_config = CompressibleConfig::derive_v1_config_pda(&REGISTRY_PROGRAM_ID).0;
53
54    // Derive compression_authority PDA (uses u16 version)
55    let compression_authority = config.compression_authority;
56    println!("config compression_authority {:?}", compression_authority);
57
58    // Validate input
59    if solana_ctoken_accounts.is_empty() {
60        return Err(RpcError::CustomError(
61            "No token accounts provided".to_string(),
62        ));
63    }
64
65    // Get output tree for compression
66    let output_tree_info = rpc
67        .get_random_state_tree_info()
68        .map_err(|e| RpcError::CustomError(format!("Failed to get state tree info: {}", e)))?;
69    let output_queue = output_tree_info
70        .get_output_pubkey()
71        .map_err(|e| RpcError::CustomError(format!("Failed to get output queue: {}", e)))?;
72
73    // Prepare accounts using PackedAccounts
74    let mut packed_accounts = PackedAccounts::default();
75
76    // Add output queue first
77    packed_accounts.insert_or_get(output_queue);
78
79    // Parse the ctoken account to get required pubkeys
80    use light_token_interface::state::Token;
81    use light_zero_copy::traits::ZeroCopyAt;
82
83    let mut indices_vec = Vec::with_capacity(solana_ctoken_accounts.len());
84
85    let mut compression_authority_pubkey: Option<Pubkey> = None;
86
87    for solana_ctoken_account_pubkey in solana_ctoken_accounts {
88        // Get the ctoken account data
89        let ctoken_solana_account = rpc
90            .get_account(*solana_ctoken_account_pubkey)
91            .await
92            .map_err(|e| {
93                RpcError::CustomError(format!(
94                    "Failed to get ctoken account {}: {}",
95                    solana_ctoken_account_pubkey, e
96                ))
97            })?
98            .ok_or_else(|| {
99                RpcError::CustomError(format!(
100                    "Light Token account {} not found",
101                    solana_ctoken_account_pubkey
102                ))
103            })?;
104
105        let (ctoken_account, _) = Token::zero_copy_at(ctoken_solana_account.data.as_slice())
106            .map_err(|e| {
107                RpcError::CustomError(format!(
108                    "Failed to parse ctoken account {}: {:?}",
109                    solana_ctoken_account_pubkey, e
110                ))
111            })?;
112
113        // Pack the basic accounts
114        let source_index = packed_accounts.insert_or_get(*solana_ctoken_account_pubkey);
115        let mint_index =
116            packed_accounts.insert_or_get(Pubkey::from(ctoken_account.mint.to_bytes()));
117
118        // Get compression info from Compressible extension
119        let compressible_ext = ctoken_account.get_compressible_extension().ok_or_else(|| {
120            RpcError::CustomError(
121                "Missing Compressible extension on Light Token account".to_string(),
122            )
123        })?;
124        let current_authority = Pubkey::from(compressible_ext.info.compression_authority);
125        let rent_sponsor_pubkey = Pubkey::from(compressible_ext.info.rent_sponsor);
126
127        if compression_authority_pubkey.is_none() {
128            compression_authority_pubkey = Some(current_authority);
129        }
130
131        let compressed_token_owner = if compressible_ext.info.compress_to_pubkey == 1 {
132            *solana_ctoken_account_pubkey
133        } else {
134            Pubkey::from(ctoken_account.owner.to_bytes())
135        };
136
137        let owner_index = packed_accounts.insert_or_get(compressed_token_owner);
138        let rent_sponsor_index = packed_accounts.insert_or_get(rent_sponsor_pubkey);
139
140        // Get delegate if present
141        let delegate_index = if ctoken_account.delegate != [0u8; 32] {
142            let delegate_pubkey = Pubkey::from(ctoken_account.delegate);
143            packed_accounts.insert_or_get(delegate_pubkey)
144        } else {
145            0 // 0 means no delegate
146        };
147
148        let indices = CompressAndCloseIndices {
149            source_index,
150            mint_index,
151            owner_index,
152            rent_sponsor_index,
153            delegate_index,
154        };
155
156        indices_vec.push(indices);
157    }
158
159    let destination_pubkey = destination.unwrap_or_else(|| payer.pubkey());
160    let destination_index = packed_accounts.insert_or_get_config(destination_pubkey, false, true);
161
162    let compression_authority_pubkey = compression_authority_pubkey.ok_or_else(|| {
163        RpcError::CustomError("No compression authority found in accounts".to_string())
164    })?;
165
166    let authority_index =
167        packed_accounts.insert_or_get_config(compression_authority_pubkey, false, true);
168
169    let ctoken_config = CTokenCompressAndCloseAccounts {
170        compressed_token_program: compressed_token_program_id,
171        cpi_authority_pda: Pubkey::find_program_address(
172            &[b"cpi_authority"],
173            &compressed_token_program_id,
174        )
175        .0,
176        cpi_context: None,
177        self_program: None, // Critical: None means no light_system_cpi_authority is added
178    };
179    packed_accounts
180        .add_custom_system_accounts(ctoken_config)
181        .map_err(|e| RpcError::CustomError(format!("Failed to add system accounts: {:?}", e)))?;
182
183    // Get account metas for remaining accounts
184    let (remaining_account_metas, _, _) = packed_accounts.to_account_metas();
185
186    // Build the compress_and_close instruction using local SDK
187    let compress_and_close_ix = build_compress_and_close_instruction(
188        authority.pubkey(),
189        registered_forester_pda,
190        compression_authority,
191        compressible_config,
192        authority_index,
193        destination_index,
194        indices_vec,
195        remaining_account_metas,
196    );
197
198    // Prepare signers
199    let mut signers = vec![payer];
200    if authority.pubkey() != payer.pubkey() {
201        signers.push(authority);
202    }
203
204    // Send transaction
205    rpc.create_and_send_transaction(&[compress_and_close_ix], &payer.pubkey(), &signers)
206        .await
207}