light_token/compressed_token/v2/transfer2/
account_metas.rs

1use light_sdk::constants::LIGHT_SYSTEM_PROGRAM_ID;
2use light_token_types::CPI_AUTHORITY_PDA;
3use solana_instruction::AccountMeta;
4use solana_pubkey::Pubkey;
5
6use crate::utils::TokenDefaultAccounts;
7
8/// Account metadata configuration for compressed token multi-transfer instructions
9#[derive(Debug, Default, Clone, PartialEq)]
10pub struct Transfer2AccountsMetaConfig {
11    pub fee_payer: Option<Pubkey>,
12    pub sol_pool_pda: Option<Pubkey>,
13    pub sol_decompression_recipient: Option<Pubkey>,
14    pub cpi_context: Option<Pubkey>,
15    pub with_sol_pool: bool,
16    pub decompressed_accounts_only: bool,
17    pub packed_accounts: Option<Vec<AccountMeta>>, // TODO: check whether this can ever be None
18}
19
20impl Transfer2AccountsMetaConfig {
21    pub fn new(fee_payer: Pubkey, packed_accounts: Vec<AccountMeta>) -> Self {
22        Self {
23            fee_payer: Some(fee_payer),
24            decompressed_accounts_only: false,
25            sol_pool_pda: None,
26            sol_decompression_recipient: None,
27            cpi_context: None,
28            with_sol_pool: false,
29            packed_accounts: Some(packed_accounts),
30        }
31    }
32
33    pub fn new_decompressed_accounts_only(
34        fee_payer: Pubkey,
35        packed_accounts: Vec<AccountMeta>,
36    ) -> Self {
37        Self {
38            fee_payer: Some(fee_payer),
39            sol_pool_pda: None,
40            sol_decompression_recipient: None,
41            cpi_context: None,
42            with_sol_pool: false,
43            decompressed_accounts_only: true,
44            packed_accounts: Some(packed_accounts),
45        }
46    }
47}
48
49/// Get the standard account metas for a compressed token multi-transfer instruction
50pub fn get_transfer2_instruction_account_metas(
51    config: Transfer2AccountsMetaConfig,
52) -> Vec<AccountMeta> {
53    let default_pubkeys = TokenDefaultAccounts::default();
54    let packed_accounts_len = if let Some(packed_accounts) = config.packed_accounts.as_ref() {
55        packed_accounts.len()
56    } else {
57        0
58    };
59
60    // Build the account metas following the order expected by Transfer2ValidatedAccounts
61    let mut metas = Vec::with_capacity(10 + packed_accounts_len);
62    if !config.decompressed_accounts_only {
63        metas.push(AccountMeta::new_readonly(
64            Pubkey::new_from_array(LIGHT_SYSTEM_PROGRAM_ID),
65            false,
66        ));
67        // Add fee payer and authority if provided (for direct invoke)
68        if let Some(fee_payer) = config.fee_payer {
69            metas.push(AccountMeta::new(fee_payer, true));
70        }
71
72        // Core system accounts (always present)
73        metas.extend([
74            AccountMeta::new_readonly(Pubkey::new_from_array(CPI_AUTHORITY_PDA), false),
75            // registered_program_pda
76            AccountMeta::new_readonly(default_pubkeys.registered_program_pda, false),
77            // account_compression_authority
78            AccountMeta::new_readonly(default_pubkeys.account_compression_authority, false),
79            // account_compression_program
80            AccountMeta::new_readonly(default_pubkeys.account_compression_program, false),
81        ]);
82
83        // system_program (always present)
84        metas.push(AccountMeta::new_readonly(
85            default_pubkeys.system_program,
86            false,
87        ));
88
89        // Optional sol pool accounts
90        if config.with_sol_pool {
91            if let Some(sol_pool_pda) = config.sol_pool_pda {
92                metas.push(AccountMeta::new(sol_pool_pda, false));
93            }
94            if let Some(sol_decompression_recipient) = config.sol_decompression_recipient {
95                metas.push(AccountMeta::new(sol_decompression_recipient, false));
96            }
97        }
98        if let Some(cpi_context) = config.cpi_context {
99            metas.push(AccountMeta::new(cpi_context, false));
100        }
101    } else if config.cpi_context.is_some() || config.with_sol_pool {
102        // TODO: replace with error
103        unimplemented!(
104            "config.cpi_context.is_some() {}, config.with_sol_pool {} must both be false",
105            config.cpi_context.is_some(),
106            config.with_sol_pool
107        );
108    } else {
109        // For decompressed accounts only, add compressions_only_cpi_authority_pda first
110        metas.push(AccountMeta::new_readonly(
111            Pubkey::new_from_array(CPI_AUTHORITY_PDA),
112            false,
113        ));
114        // Then add compressions_only_fee_payer if provided
115        if let Some(fee_payer) = config.fee_payer {
116            metas.push(AccountMeta::new(fee_payer, true));
117        }
118    }
119    if let Some(packed_accounts) = config.packed_accounts.as_ref() {
120        for account in packed_accounts {
121            metas.push(account.clone());
122        }
123    }
124
125    metas
126}