light_token/compressed_token/v2/transfer2/
cpi_accounts.rs

1use light_account_checks::{AccountError, AccountInfoTrait, AccountIterator};
2use light_program_profiler::profile;
3use light_sdk_types::{
4    ACCOUNT_COMPRESSION_AUTHORITY_PDA, ACCOUNT_COMPRESSION_PROGRAM_ID, LIGHT_SYSTEM_PROGRAM_ID,
5    REGISTERED_PROGRAM_PDA,
6};
7use light_token_interface::LIGHT_TOKEN_PROGRAM_ID;
8use light_token_types::CPI_AUTHORITY_PDA;
9use solana_instruction::AccountMeta;
10use solana_msg::msg;
11
12use crate::error::TokenSdkError;
13
14/// Parsed Transfer2 CPI accounts for structured access
15#[derive(Debug)]
16pub struct Transfer2CpiAccounts<'a, A: AccountInfoTrait + Clone> {
17    // Programs and authorities (in order)
18    pub compressed_token_program: &'a A,
19    /// Needed with cpi context to do the other cpi to the system program.
20    pub invoking_program_cpi_authority: Option<&'a A>,
21    pub light_system_program: &'a A,
22
23    // Core system accounts
24    pub fee_payer: &'a A,
25    pub compressed_token_cpi_authority: &'a A,
26    pub registered_program_pda: &'a A,
27    pub account_compression_authority: &'a A,
28    pub account_compression_program: &'a A,
29    pub system_program: &'a A,
30
31    // Optional accounts
32    pub sol_pool_pda: Option<&'a A>,
33    pub sol_decompression_recipient: Option<&'a A>,
34    pub cpi_context: Option<&'a A>,
35
36    /// Packed accounts (trees, queues, mints, owners, delegates, etc)
37    /// Trees and queues must be first.
38    pub packed_accounts: &'a [A],
39}
40
41impl<'a, A: AccountInfoTrait + Clone> Transfer2CpiAccounts<'a, A> {
42    /// Following the order: compressed_token_program, invoking_program_cpi_authority, light_system_program, ...
43    /// Checks in this function are for convenience and not security critical.
44    #[profile]
45    #[inline(always)]
46    #[track_caller]
47    pub fn try_from_account_infos_full(
48        fee_payer: &'a A,
49        accounts: &'a [A],
50        with_sol_pool: bool,
51        with_sol_decompression: bool,
52        with_cpi_context: bool,
53        light_system_cpi_authority: bool,
54    ) -> Result<Self, TokenSdkError> {
55        let mut iter = AccountIterator::new(accounts);
56
57        let compressed_token_program =
58            iter.next_checked_pubkey("compressed_token_program", LIGHT_TOKEN_PROGRAM_ID)?;
59
60        let invoking_program_cpi_authority =
61            iter.next_option("CPI_SIGNER.cpi_authority", light_system_cpi_authority)?;
62        let compressed_token_cpi_authority =
63            iter.next_checked_pubkey("compressed_token_cpi_authority", CPI_AUTHORITY_PDA)?;
64
65        let light_system_program =
66            iter.next_checked_pubkey("light_system_program", LIGHT_SYSTEM_PROGRAM_ID)?;
67
68        let registered_program_pda =
69            iter.next_checked_pubkey("registered_program_pda", REGISTERED_PROGRAM_PDA)?;
70
71        let account_compression_authority = iter.next_checked_pubkey(
72            "account_compression_authority",
73            ACCOUNT_COMPRESSION_AUTHORITY_PDA,
74        )?;
75
76        let account_compression_program = iter.next_checked_pubkey(
77            "account_compression_program",
78            ACCOUNT_COMPRESSION_PROGRAM_ID,
79        )?;
80
81        let system_program = iter.next_checked_pubkey("system_program", [0u8; 32])?;
82
83        let sol_pool_pda = iter.next_option_mut("sol_pool_pda", with_sol_pool)?;
84
85        let sol_decompression_recipient =
86            iter.next_option_mut("sol_decompression_recipient", with_sol_decompression)?;
87
88        let cpi_context = iter.next_option_mut("cpi_context", with_cpi_context)?;
89
90        let packed_accounts = iter.remaining()?;
91        if !packed_accounts[0].is_owned_by(&ACCOUNT_COMPRESSION_PROGRAM_ID) {
92            msg!("First packed accounts must be tree or queue accounts.");
93            msg!("Found {:?} instead", packed_accounts[0].pubkey());
94            return Err(AccountError::InvalidAccount.into());
95        }
96
97        Ok(Self {
98            compressed_token_program,
99            invoking_program_cpi_authority,
100            light_system_program,
101            fee_payer,
102            compressed_token_cpi_authority,
103            registered_program_pda,
104            account_compression_authority,
105            account_compression_program,
106            system_program,
107            sol_pool_pda,
108            sol_decompression_recipient,
109            cpi_context,
110            packed_accounts,
111        })
112    }
113
114    #[inline(always)]
115    #[track_caller]
116    pub fn try_from_account_infos(
117        fee_payer: &'a A,
118        accounts: &'a [A],
119    ) -> Result<Self, TokenSdkError> {
120        Self::try_from_account_infos_full(fee_payer, accounts, false, false, false, false)
121    }
122
123    #[inline(always)]
124    #[track_caller]
125    pub fn try_from_account_infos_cpi_context(
126        fee_payer: &'a A,
127        accounts: &'a [A],
128    ) -> Result<Self, TokenSdkError> {
129        Self::try_from_account_infos_full(fee_payer, accounts, false, false, true, false)
130    }
131
132    /// Get tree accounts (accounts owned by account compression program)
133    pub fn packed_accounts(&self) -> &'a [A] {
134        self.packed_accounts
135    }
136
137    /// Get tree accounts (accounts owned by account compression program)
138    #[profile]
139    #[inline(always)]
140    pub fn packed_account_metas(&self) -> Vec<AccountMeta> {
141        let mut vec = Vec::with_capacity(self.packed_accounts.len());
142        for account in self.packed_accounts {
143            vec.push(AccountMeta {
144                pubkey: account.key().into(),
145                is_writable: account.is_writable(),
146                is_signer: account.is_signer(),
147            });
148        }
149        vec
150    }
151
152    /// Get a packed account by index
153    pub fn packed_account_by_index(&self, index: u8) -> Option<&'a A> {
154        self.packed_accounts.get(index as usize)
155    }
156
157    /// Get accounts for CPI to light system program (excludes the programs themselves)
158    #[profile]
159    #[inline(always)]
160    pub fn to_account_infos(&self) -> Vec<A> {
161        let mut accounts = Vec::with_capacity(10 + self.packed_accounts.len());
162
163        accounts.extend_from_slice(
164            &[
165                self.light_system_program.clone(),
166                self.fee_payer.clone(),
167                self.compressed_token_cpi_authority.clone(),
168                self.registered_program_pda.clone(),
169                self.account_compression_authority.clone(),
170                self.account_compression_program.clone(),
171                self.system_program.clone(),
172            ][..],
173        );
174
175        if let Some(sol_pool) = self.sol_pool_pda {
176            accounts.push(sol_pool.clone());
177        }
178        if let Some(recipient) = self.sol_decompression_recipient {
179            accounts.push(recipient.clone());
180        }
181        if let Some(context) = self.cpi_context {
182            accounts.push(context.clone());
183        }
184        self.packed_accounts.iter().for_each(|e| {
185            accounts.push(e.clone());
186        });
187
188        accounts
189    }
190}