use crate::constants::SYSTEM_PROGRAM_ID;
use crate::error::SolanaKiteError;
use crate::token_extensions::TOKEN_EXTENSIONS_PROGRAM_ID;
use crate::transaction::send_transaction_from_instructions;
use litesvm::LiteSVM;
use solana_instruction::account_meta::AccountMeta;
use solana_instruction::Instruction;
use solana_keypair::Keypair;
use solana_pubkey::Pubkey;
use solana_signer::Signer;
const INITIALIZE_EXTRA_ACCOUNT_META_LIST_DISCRIMINATOR: [u8; 8] =
[43, 34, 13, 49, 167, 88, 235, 235];
const EXTRA_ACCOUNT_META_SERIALIZED_SIZE: usize = 35;
#[derive(Debug, Clone)]
pub struct HookAccount {
pub pubkey: Pubkey,
pub is_signer: bool,
pub is_writable: bool,
}
impl HookAccount {
fn to_bytes(&self) -> [u8; EXTRA_ACCOUNT_META_SERIALIZED_SIZE] {
let mut buf = [0u8; EXTRA_ACCOUNT_META_SERIALIZED_SIZE];
buf[0] = 0; buf[1..33].copy_from_slice(&self.pubkey.to_bytes());
buf[33] = self.is_signer as u8;
buf[34] = self.is_writable as u8;
buf
}
}
#[must_use]
pub fn get_hook_accounts_address(mint: &Pubkey, hook_program_id: &Pubkey) -> Pubkey {
let (address, _bump) =
Pubkey::find_program_address(&[b"extra-account-metas", mint.as_ref()], hook_program_id);
address
}
pub fn initialize_hook_accounts(
litesvm: &mut LiteSVM,
hook_program_id: &Pubkey,
mint: &Pubkey,
authority: &Keypair,
hook_accounts: &[HookAccount],
) -> Result<(), SolanaKiteError> {
let hook_accounts_address = get_hook_accounts_address(mint, hook_program_id);
let mut data =
Vec::with_capacity(8 + 4 + hook_accounts.len() * EXTRA_ACCOUNT_META_SERIALIZED_SIZE);
data.extend_from_slice(&INITIALIZE_EXTRA_ACCOUNT_META_LIST_DISCRIMINATOR);
data.extend_from_slice(&(hook_accounts.len() as u32).to_le_bytes());
for account in hook_accounts {
data.extend_from_slice(&account.to_bytes());
}
let instruction = Instruction {
program_id: *hook_program_id,
accounts: vec![
AccountMeta::new(hook_accounts_address, false), AccountMeta::new_readonly(*mint, false), AccountMeta::new(authority.pubkey(), true), AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false), ],
data,
};
send_transaction_from_instructions(
litesvm,
vec![instruction],
&[authority],
&authority.pubkey(),
)
.map_err(|e| match e {
SolanaKiteError::TransactionFailed(msg) => SolanaKiteError::HookOperationFailed(msg),
other => other,
})?;
Ok(())
}
#[must_use]
pub fn build_hook_accounts(
mint: &Pubkey,
hook_program_id: &Pubkey,
hook_accounts: &[HookAccount],
) -> Vec<AccountMeta> {
let hook_accounts_address = get_hook_accounts_address(mint, hook_program_id);
let mut accounts: Vec<AccountMeta> = hook_accounts
.iter()
.map(|account| {
if account.is_writable {
AccountMeta::new(account.pubkey, account.is_signer)
} else {
AccountMeta::new_readonly(account.pubkey, account.is_signer)
}
})
.collect();
accounts.push(AccountMeta::new_readonly(*hook_program_id, false));
accounts.push(AccountMeta::new_readonly(hook_accounts_address, false));
accounts.push(AccountMeta::new_readonly(
TOKEN_EXTENSIONS_PROGRAM_ID,
false,
));
accounts
}