light_sdk/cpi/v1/
invoke.rs

1use light_compressed_account::instruction_data::{
2    compressed_proof::ValidityProof, invoke_cpi::InstructionDataInvokeCpi,
3};
4
5use crate::{
6    account::{poseidon::LightAccount as LightAccountPoseidon, LightAccount},
7    cpi::{instruction::LightCpiInstruction, invoke::LightInstructionData, CpiSigner},
8    error::LightSdkError,
9    instruction::account_info::CompressedAccountInfoTrait,
10    AnchorDeserialize, AnchorSerialize, DataHasher, LightDiscriminator, ProgramError,
11};
12
13/// Light system program CPI instruction data builder.
14///
15/// Use this builder to construct instructions for compressed account operations:
16/// creating, updating, closing accounts, and compressing/decompressing SOL.
17///
18/// # Builder Methods
19///
20/// ## Common Methods
21///
22/// - [`with_light_account()`](Self::with_light_account) - Add a compressed account (handles output hashing, and type conversion to instruction data)
23/// - [`with_new_addresses()`](Self::with_new_addresses) - Create new compressed account addresses
24/// - [`compress_lamports()`](Self::compress_lamports) - Compress SOL into compressed accounts
25/// - [`decompress_lamports()`](Self::decompress_lamports) - Decompress SOL from compressed accounts
26///
27/// **Note**: An instruction can either compress **or** decompress lamports, not both.
28///
29/// ## Advanced Methods
30///
31/// For fine-grained control, use these low-level methods instead of [`with_light_account()`](Self::with_light_account):
32///
33/// - [`with_input_compressed_accounts_with_merkle_context()`](Self::with_input_compressed_accounts_with_merkle_context) - Manually specify input accounts
34/// - [`with_output_compressed_accounts()`](Self::with_output_compressed_accounts) - Manually specify output accounts
35///
36/// # Examples
37///
38/// ## Create a compressed account with an address
39/// ```rust,no_run
40/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner};
41/// # use light_sdk::instruction::ValidityProof;
42/// # use light_compressed_account::instruction_data::data::NewAddressParamsPacked;
43/// # use light_sdk::{LightAccount, LightDiscriminator};
44/// # use borsh::{BorshSerialize, BorshDeserialize};
45/// # use solana_pubkey::Pubkey;
46/// # use solana_program_error::ProgramError;
47/// #
48/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner {
49/// #     program_id: [0; 32],
50/// #     cpi_signer: [0; 32],
51/// #     bump: 255,
52/// # };
53/// #
54/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)]
55/// # pub struct MyAccount {
56/// #     pub value: u64,
57/// # }
58/// #
59/// # fn example() -> Result<(), ProgramError> {
60/// # let proof = ValidityProof::default();
61/// # let new_address_params = NewAddressParamsPacked::default();
62/// # let program_id = Pubkey::new_unique();
63/// # let account = LightAccount::<MyAccount>::new_init(&program_id, None, 0);
64/// # let key = Pubkey::new_unique();
65/// # let owner = Pubkey::default();
66/// # let mut lamports = 0u64;
67/// # let mut data = [];
68/// # let fee_payer = &solana_account_info::AccountInfo::new(
69/// #     &key,
70/// #     true,
71/// #     true,
72/// #     &mut lamports,
73/// #     &mut data,
74/// #     &owner,
75/// #     false,
76/// #     0,
77/// # );
78/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER);
79/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
80///     .with_new_addresses(&[new_address_params])
81///     .with_light_account(account)?
82///     .invoke(cpi_accounts)?;
83/// # Ok(())
84/// # }
85/// ```
86/// ## Update a compressed account
87/// ```rust,no_run
88/// # use light_sdk::cpi::{v1::LightSystemProgramCpi, InvokeLightSystemProgram, LightCpiInstruction, CpiSigner};
89/// # use light_sdk::instruction::ValidityProof;
90/// # use light_sdk::{LightAccount, LightDiscriminator};
91/// # use light_sdk::instruction::account_meta::CompressedAccountMeta;
92/// # use borsh::{BorshSerialize, BorshDeserialize};
93/// # use solana_pubkey::Pubkey;
94/// # use solana_program_error::ProgramError;
95/// #
96/// # const LIGHT_CPI_SIGNER: CpiSigner = CpiSigner {
97/// #     program_id: [0; 32],
98/// #     cpi_signer: [0; 32],
99/// #     bump: 255,
100/// # };
101/// #
102/// # #[derive(Clone, Debug, Default, LightDiscriminator, BorshSerialize, BorshDeserialize)]
103/// # pub struct MyAccount {
104/// #     pub value: u64,
105/// # }
106/// #
107/// # fn example() -> Result<(), ProgramError> {
108/// # let proof = ValidityProof::default();
109/// # let program_id = Pubkey::new_unique();
110/// # let account_meta = CompressedAccountMeta::default();
111/// # let account_data = MyAccount::default();
112/// # let account = LightAccount::<MyAccount>::new_mut(&program_id, &account_meta, account_data)?;
113/// # let key = Pubkey::new_unique();
114/// # let owner = Pubkey::default();
115/// # let mut lamports = 0u64;
116/// # let mut data = [];
117/// # let fee_payer = &solana_account_info::AccountInfo::new(
118/// #     &key,
119/// #     true,
120/// #     true,
121/// #     &mut lamports,
122/// #     &mut data,
123/// #     &owner,
124/// #     false,
125/// #     0,
126/// # );
127/// # let cpi_accounts = light_sdk::cpi::v1::CpiAccounts::new(fee_payer, &[], LIGHT_CPI_SIGNER);
128/// LightSystemProgramCpi::new_cpi(LIGHT_CPI_SIGNER, proof)
129///     .with_light_account(account)?
130///     .invoke(cpi_accounts)?;
131/// # Ok(())
132/// # }
133/// ```
134#[derive(Clone)]
135pub struct LightSystemProgramCpi {
136    cpi_signer: CpiSigner,
137    instruction_data: InstructionDataInvokeCpi,
138}
139
140impl LightSystemProgramCpi {
141    #[must_use = "with_new_addresses returns a new value"]
142    pub fn with_new_addresses(
143        mut self,
144        new_address_params: &[light_compressed_account::instruction_data::data::NewAddressParamsPacked],
145    ) -> Self {
146        self.instruction_data = self.instruction_data.with_new_addresses(new_address_params);
147        self
148    }
149
150    #[must_use = "with_input_compressed_accounts_with_merkle_context returns a new value"]
151    pub fn with_input_compressed_accounts_with_merkle_context(
152        mut self,
153        input_compressed_accounts_with_merkle_context: &[light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext],
154    ) -> Self {
155        self.instruction_data = self
156            .instruction_data
157            .with_input_compressed_accounts_with_merkle_context(
158                input_compressed_accounts_with_merkle_context,
159            );
160        self
161    }
162
163    #[must_use = "with_output_compressed_accounts returns a new value"]
164    pub fn with_output_compressed_accounts(
165        mut self,
166        output_compressed_accounts: &[light_compressed_account::instruction_data::data::OutputCompressedAccountWithPackedContext],
167    ) -> Self {
168        self.instruction_data = self
169            .instruction_data
170            .with_output_compressed_accounts(output_compressed_accounts);
171        self
172    }
173
174    #[must_use = "compress_lamports returns a new value"]
175    pub fn compress_lamports(mut self, lamports: u64) -> Self {
176        self.instruction_data = self.instruction_data.compress_lamports(lamports);
177        self
178    }
179
180    #[must_use = "decompress_lamports returns a new value"]
181    pub fn decompress_lamports(mut self, lamports: u64) -> Self {
182        self.instruction_data = self.instruction_data.decompress_lamports(lamports);
183        self
184    }
185
186    #[cfg(feature = "cpi-context")]
187    #[must_use = "write_to_cpi_context_set returns a new value"]
188    pub fn write_to_cpi_context_set(mut self) -> Self {
189        self.instruction_data = self.instruction_data.write_to_cpi_context_set();
190        self
191    }
192
193    #[cfg(feature = "cpi-context")]
194    #[must_use = "write_to_cpi_context_first returns a new value"]
195    pub fn write_to_cpi_context_first(mut self) -> Self {
196        self.instruction_data = self.instruction_data.write_to_cpi_context_first();
197        self
198    }
199
200    #[cfg(feature = "cpi-context")]
201    #[must_use = "with_cpi_context returns a new value"]
202    pub fn with_cpi_context(
203        mut self,
204        cpi_context: light_compressed_account::instruction_data::cpi_context::CompressedCpiContext,
205    ) -> Self {
206        self.instruction_data = self.instruction_data.with_cpi_context(cpi_context);
207        self
208    }
209}
210
211impl LightCpiInstruction for LightSystemProgramCpi {
212    fn new_cpi(cpi_signer: CpiSigner, proof: ValidityProof) -> Self {
213        Self {
214            cpi_signer,
215            instruction_data: InstructionDataInvokeCpi::new(proof.into()),
216        }
217    }
218
219    fn with_light_account<A>(mut self, account: LightAccount<'_, A>) -> Result<Self, ProgramError>
220    where
221        A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + Default,
222    {
223        use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext;
224
225        // Convert LightAccount to account info
226        let account_info = account.to_account_info()?;
227
228        // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext
229        if let Some(input_account) = account_info
230            .input_compressed_account(self.cpi_signer.program_id.into())
231            .map_err(LightSdkError::from)
232            .map_err(ProgramError::from)?
233        {
234            let packed_input = PackedCompressedAccountWithMerkleContext {
235                compressed_account: input_account.compressed_account,
236                merkle_context: input_account.merkle_context,
237                root_index: input_account.root_index,
238                read_only: false, // Default to false for v1
239            };
240            self.instruction_data
241                .input_compressed_accounts_with_merkle_context
242                .push(packed_input);
243        }
244
245        // Handle output accounts
246        if let Some(output_account) = account_info
247            .output_compressed_account(self.cpi_signer.program_id.into())
248            .map_err(LightSdkError::from)
249            .map_err(ProgramError::from)?
250        {
251            self.instruction_data
252                .output_compressed_accounts
253                .push(output_account);
254        }
255
256        Ok(self)
257    }
258
259    fn with_light_account_poseidon<A>(
260        mut self,
261        account: LightAccountPoseidon<'_, A>,
262    ) -> Result<Self, ProgramError>
263    where
264        A: AnchorSerialize + AnchorDeserialize + LightDiscriminator + DataHasher + Default,
265    {
266        use light_compressed_account::compressed_account::PackedCompressedAccountWithMerkleContext;
267
268        // Convert LightAccount to account info
269        let account_info = account.to_account_info()?;
270
271        // Handle input accounts - convert to PackedCompressedAccountWithMerkleContext
272        if let Some(input_account) = account_info
273            .input_compressed_account(self.cpi_signer.program_id.into())
274            .map_err(LightSdkError::from)
275            .map_err(ProgramError::from)?
276        {
277            let packed_input = PackedCompressedAccountWithMerkleContext {
278                compressed_account: input_account.compressed_account,
279                merkle_context: input_account.merkle_context,
280                root_index: input_account.root_index,
281                read_only: false, // Default to false for v1
282            };
283            self.instruction_data
284                .input_compressed_accounts_with_merkle_context
285                .push(packed_input);
286        }
287
288        // Handle output accounts
289        if let Some(output_account) = account_info
290            .output_compressed_account(self.cpi_signer.program_id.into())
291            .map_err(LightSdkError::from)
292            .map_err(ProgramError::from)?
293        {
294            self.instruction_data
295                .output_compressed_accounts
296                .push(output_account);
297        }
298
299        Ok(self)
300    }
301
302    fn get_mode(&self) -> u8 {
303        0 // V1 uses regular mode by default
304    }
305
306    fn get_bump(&self) -> u8 {
307        self.cpi_signer.bump
308    }
309
310    #[cfg(feature = "cpi-context")]
311    fn write_to_cpi_context_first(mut self) -> Self {
312        self.instruction_data = self.instruction_data.write_to_cpi_context_first();
313        self
314    }
315
316    #[cfg(feature = "cpi-context")]
317    fn write_to_cpi_context_set(mut self) -> Self {
318        self.instruction_data = self.instruction_data.write_to_cpi_context_set();
319        self
320    }
321
322    #[cfg(feature = "cpi-context")]
323    fn execute_with_cpi_context(self) -> Self {
324        // V1 doesn't have a direct execute context, just return self
325        // The execute happens through the invoke call
326        self
327    }
328
329    #[cfg(feature = "cpi-context")]
330    fn get_with_cpi_context(&self) -> bool {
331        self.instruction_data.cpi_context.is_some()
332    }
333
334    #[cfg(feature = "cpi-context")]
335    fn get_cpi_context(
336        &self,
337    ) -> &light_compressed_account::instruction_data::cpi_context::CompressedCpiContext {
338        use light_compressed_account::instruction_data::cpi_context::CompressedCpiContext;
339        // Use a static default with all fields set to false/0
340        static DEFAULT: CompressedCpiContext = CompressedCpiContext {
341            set_context: false,
342            first_set_context: false,
343            cpi_context_account_index: 0,
344        };
345        self.instruction_data
346            .cpi_context
347            .as_ref()
348            .unwrap_or(&DEFAULT)
349    }
350}
351
352// Manual BorshSerialize implementation that only serializes instruction_data
353impl AnchorSerialize for LightSystemProgramCpi {
354    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
355        self.instruction_data.serialize(writer)
356    }
357}
358
359impl light_compressed_account::InstructionDiscriminator for LightSystemProgramCpi {
360    fn discriminator(&self) -> &'static [u8] {
361        self.instruction_data.discriminator()
362    }
363}
364
365impl LightInstructionData for LightSystemProgramCpi {
366    fn data(&self) -> Result<Vec<u8>, light_compressed_account::CompressedAccountError> {
367        self.instruction_data.data()
368    }
369}