use solana_program::pubkey::Pubkey;
use spl_associated_token_account::get_associated_token_address;
use steel::*;
use crate::{consts::*, instruction::*, state::*};
pub fn kamino_accounts(vault_address: Pubkey) -> Vec<AccountMeta> {
let vault_ctoken_address = get_associated_token_address(&vault_address, &CTOKEN_ADDRESS);
vec![
AccountMeta::new(vault_address, false),
AccountMeta::new(OBLIGATION_ADDRESS, false),
AccountMeta::new(LENDING_MARKET_ADDRESS, false),
AccountMeta::new(LENDING_MARKET_AUTHORITY_ADDRESS, false),
AccountMeta::new(RESERVE_ADDRESS, false),
AccountMeta::new(USDC_ADDRESS, false),
AccountMeta::new(RESERVE_LIQUIDITY_SUPPLY_ADDRESS, false),
AccountMeta::new(CTOKEN_ADDRESS, false),
AccountMeta::new(RESERVE_CTOKEN_ADDRESS, false),
AccountMeta::new(get_associated_token_address(&vault_address, &USDC_ADDRESS), false),
AccountMeta::new(vault_ctoken_address, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(sysvar::instructions::ID, false),
AccountMeta::new(RESERVE_FARM_USER_STATE_ADDRESS, false),
AccountMeta::new(RESERVE_FARM_STATE_ADDRESS, false),
AccountMeta::new_readonly(KFARMS_PROGRAM_ID, false),
AccountMeta::new_readonly(SCOPE_PRICES_ADDRESS, false),
AccountMeta::new_readonly(KLEND_PROGRAM_ID, false),
]
}
pub fn perena_accounts(vault_address: Pubkey) -> Vec<AccountMeta> {
let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
let vault_usd_star_address = get_associated_token_address(&vault_address, &USD_STAR_MINT);
vec![
AccountMeta::new(vault_address, false),
AccountMeta::new(PERENA_BANK_STATE, false),
AccountMeta::new(PERENA_VAULT_STATE, false),
AccountMeta::new_readonly(PERENA_ORACLE_STATE, false),
AccountMeta::new_readonly(USDC_ADDRESS, false),
AccountMeta::new(USD_STAR_MINT, false),
AccountMeta::new(vault_usdc_address, false),
AccountMeta::new(vault_usd_star_address, false),
AccountMeta::new(PERENA_YIELDING_VAULT, false),
AccountMeta::new(PERENA_TEAM_STATE, false),
AccountMeta::new(PERENA_FEE_TEAM_ATA, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(spl_associated_token_account::ID, false),
AccountMeta::new_readonly(PERENA_PROGRAM_ID, false),
]
}
pub fn protocol_accounts(protocol_id: u8, vault_address: Pubkey) -> Vec<AccountMeta> {
match protocol_id {
PROTOCOL_KAMINO => kamino_accounts(vault_address),
PROTOCOL_PERENA => perena_accounts(vault_address),
_ => vec![],
}
}
pub fn init(signer: Pubkey, platform_fee_bps: u16, treasury: Pubkey) -> Instruction {
let vault_address = vault_pda().0;
let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
Instruction {
program_id: crate::ID,
accounts: vec![
AccountMeta::new(signer, true),
AccountMeta::new_readonly(USDC_ADDRESS, false),
AccountMeta::new(vault_address, false),
AccountMeta::new(vault_usdc_address, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(spl_associated_token_account::ID, false),
],
data: Init {
platform_fee_bps: platform_fee_bps.to_le_bytes(),
_padding: [0u8; 6],
treasury: treasury.to_bytes(),
}
.to_bytes(),
}
}
pub fn init_protocol(signer: Pubkey, protocol_id: u8, name: &str, user_lookup_table: Option<Pubkey>) -> Instruction {
let vault_address = vault_pda().0;
let protocol_address = protocol_pda(protocol_id).0;
let receipt_token_mint = match protocol_id {
PROTOCOL_KAMINO => CTOKEN_ADDRESS,
PROTOCOL_PERENA => USD_STAR_MINT,
_ => panic!("Unknown protocol"),
};
let vault_receipt_token_address = get_associated_token_address(&vault_address, &receipt_token_mint);
let name_bytes = name.as_bytes();
let mut name_array = [0u8; 32];
let len = name_bytes.len().min(32);
name_array[..len].copy_from_slice(&name_bytes[..len]);
let mut accounts = vec![
AccountMeta::new(signer, true),
AccountMeta::new(vault_address, false),
AccountMeta::new(protocol_address, false),
AccountMeta::new_readonly(receipt_token_mint, false),
AccountMeta::new(vault_receipt_token_address, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(spl_associated_token_account::ID, false),
];
if protocol_id == PROTOCOL_KAMINO {
let user_metadata_address = Pubkey::find_program_address(
&[b"user_meta", vault_address.as_ref()],
&klend_sdk::programs::KAMINO_LENDING_ID.to_bytes().into(),
)
.0;
accounts.extend(vec![
AccountMeta::new(OBLIGATION_ADDRESS, false),
AccountMeta::new(LENDING_MARKET_ADDRESS, false),
AccountMeta::new_readonly(USDC_ADDRESS, false),
AccountMeta::new_readonly(USDC_ADDRESS, false),
AccountMeta::new(user_metadata_address, false),
AccountMeta::new(user_lookup_table.unwrap_or(signer), false),
AccountMeta::new(LENDING_MARKET_AUTHORITY_ADDRESS, false),
AccountMeta::new(RESERVE_ADDRESS, false),
AccountMeta::new(RESERVE_FARM_USER_STATE_ADDRESS, false),
AccountMeta::new(RESERVE_FARM_STATE_ADDRESS, false),
AccountMeta::new_readonly(KFARMS_PROGRAM_ID, false),
AccountMeta::new_readonly(KLEND_PROGRAM_ID, false),
AccountMeta::new_readonly(sysvar::rent::ID, false),
AccountMeta::new_readonly(system_program::ID, false),
]);
}
Instruction {
program_id: crate::ID,
accounts,
data: InitProtocol {
protocol_id,
name: name_array,
}
.to_bytes(),
}
}
pub fn checkpoint(signer: Pubkey, protocol_id: u8) -> Instruction {
let vault_address = vault_pda().0;
let protocol_address = protocol_pda(protocol_id).0;
let mut accounts = vec![
AccountMeta::new(signer, true),
AccountMeta::new(vault_address, false),
AccountMeta::new(protocol_address, false),
];
match protocol_id {
PROTOCOL_KAMINO => {
accounts.push(AccountMeta::new_readonly(RESERVE_ADDRESS, false));
}
PROTOCOL_PERENA => {
accounts.push(AccountMeta::new_readonly(PERENA_ORACLE_STATE, false));
}
_ => {}
}
Instruction {
program_id: crate::ID,
accounts,
data: Checkpoint { protocol_id }.to_bytes(),
}
}
pub fn rebalance(
signer: Pubkey,
stable_address: Pubkey,
from_protocol: u8,
to_protocol: u8,
amount: u64,
) -> Instruction {
let vault_address = vault_pda().0;
let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
let from_protocol_address = protocol_pda(from_protocol).0;
let to_protocol_address = protocol_pda(to_protocol).0;
let from_position_address = position_pda(stable_address, from_protocol).0;
let to_position_address = position_pda(stable_address, to_protocol).0;
let mut accounts = vec![
AccountMeta::new(signer, true),
AccountMeta::new(vault_address, false),
AccountMeta::new(stable_address, false),
AccountMeta::new(from_protocol_address, false),
AccountMeta::new(to_protocol_address, false),
AccountMeta::new(from_position_address, false),
AccountMeta::new(to_position_address, false),
AccountMeta::new(vault_usdc_address, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
];
accounts.extend(protocol_accounts(from_protocol, vault_address));
accounts.extend(protocol_accounts(to_protocol, vault_address));
Instruction {
program_id: crate::ID,
accounts,
data: Rebalance {
from_protocol,
to_protocol,
_padding: [0u8; 6],
amount: amount.to_le_bytes(),
}
.to_bytes(),
}
}
pub fn set_primary_protocol(signer: Pubkey, protocol_id: u8) -> Instruction {
let vault_address = vault_pda().0;
let protocol_address = protocol_pda(protocol_id).0;
Instruction {
program_id: crate::ID,
accounts: vec![
AccountMeta::new(signer, true),
AccountMeta::new(vault_address, false),
AccountMeta::new_readonly(protocol_address, false),
],
data: SetPrimaryProtocol { protocol_id }.to_bytes(),
}
}
pub fn set_paused(signer: Pubkey, paused: bool) -> Instruction {
let vault_address = vault_pda().0;
Instruction {
program_id: crate::ID,
accounts: vec![
AccountMeta::new(signer, true),
AccountMeta::new(vault_address, false),
],
data: SetPaused {
paused: if paused { 1 } else { 0 },
}
.to_bytes(),
}
}
pub fn collect_platform_fee(
signer: Pubkey,
treasury: Pubkey,
stable_address: Pubkey,
protocol_id: u8,
) -> Instruction {
let vault_address = vault_pda().0;
let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
let protocol_address = protocol_pda(protocol_id).0;
let position_address = position_pda(stable_address, protocol_id).0;
let mut accounts = vec![
AccountMeta::new(signer, true),
AccountMeta::new(vault_address, false),
AccountMeta::new(treasury, false),
AccountMeta::new(vault_usdc_address, false),
AccountMeta::new_readonly(stable_address, false),
AccountMeta::new(protocol_address, false),
AccountMeta::new(position_address, false),
AccountMeta::new_readonly(spl_token::ID, false),
];
accounts.extend(protocol_accounts(protocol_id, vault_address));
Instruction {
program_id: crate::ID,
accounts,
data: CollectPlatformFee { protocol_id }.to_bytes(),
}
}
pub fn set_treasury(signer: Pubkey, treasury: Pubkey) -> Instruction {
let vault_address = vault_pda().0;
Instruction {
program_id: crate::ID,
accounts: vec![
AccountMeta::new(signer, true),
AccountMeta::new(vault_address, false),
AccountMeta::new_readonly(treasury, false),
],
data: SetTreasury {
treasury: treasury.to_bytes(),
}
.to_bytes(),
}
}
pub fn delete_vault(signer: Pubkey) -> Instruction {
let vault_address = vault_pda().0;
Instruction {
program_id: crate::ID,
accounts: vec![
AccountMeta::new(signer, true),
AccountMeta::new(vault_address, false),
],
data: DeleteVault {}.to_bytes(),
}
}
pub fn create(
signer: Pubkey,
payer: Pubkey,
id: String,
name: String,
symbol: String,
noise: [u8; 32],
) -> Instruction {
let id_bytes = id.as_bytes();
if id_bytes.len() > 32 {
panic!("Id must be at most 32 bytes");
}
let mut id_array = [0u8; 32];
id_array[..id_bytes.len()].copy_from_slice(id_bytes);
let symbol_bytes = symbol.as_bytes();
if symbol_bytes.len() > 32 {
panic!("Symbol must be at most 32 bytes");
}
let mut symbol_array = [0u8; 32];
symbol_array[..symbol_bytes.len()].copy_from_slice(symbol_bytes);
let name_bytes = name.as_bytes();
if name_bytes.len() > 32 {
panic!("Name must be at most 32 bytes");
}
let mut name_array = [0u8; 32];
name_array[..name_bytes.len()].copy_from_slice(name_bytes);
let mint_pda =
Pubkey::find_program_address(&[MINT, noise.as_ref()], &crate::ID.to_bytes().into());
let metadata_address = mpl_token_metadata::accounts::Metadata::find_pda(&mint_pda.0).0;
let stable_address = stable_pda(mint_pda.0).0;
Instruction {
program_id: crate::ID,
accounts: vec![
AccountMeta::new(signer, true),
AccountMeta::new(payer, true),
AccountMeta::new(metadata_address, false),
AccountMeta::new(stable_address, false),
AccountMeta::new(mint_pda.0, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(spl_associated_token_account::ID, false),
AccountMeta::new_readonly(mpl_token_metadata::ID, false),
AccountMeta::new_readonly(sysvar::rent::ID, false),
],
data: Create {
id: id_array,
name: name_array,
symbol: symbol_array,
noise,
bump: mint_pda.1,
}
.to_bytes(),
}
}
pub fn update_authority(signer: Pubkey, mint_stable: Pubkey, new_authority: Pubkey) -> Instruction {
let stable_address = stable_pda(mint_stable).0;
Instruction {
program_id: crate::ID,
accounts: vec![
AccountMeta::new(signer, true),
AccountMeta::new(stable_address, false),
],
data: UpdateAuthority {
new_authority: new_authority.to_bytes(),
}
.to_bytes(),
}
}
pub fn claim(signer: Pubkey, mint_stable: Pubkey, protocol_id: u8, amount: u64, min_usdc_out: u64) -> Instruction {
let stable_address = stable_pda(mint_stable).0;
let signer_stable_address = get_associated_token_address(&signer, &mint_stable);
let signer_usdc_address = get_associated_token_address(&signer, &USDC_ADDRESS);
let vault_address = vault_pda().0;
let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
let protocol_address = protocol_pda(protocol_id).0;
let position_address = position_pda(stable_address, protocol_id).0;
let mut accounts = vec![
AccountMeta::new(signer, true),
AccountMeta::new(signer, true),
AccountMeta::new(signer_stable_address, false),
AccountMeta::new(signer_usdc_address, false),
AccountMeta::new_readonly(USDC_ADDRESS, false),
AccountMeta::new(mint_stable, false),
AccountMeta::new(stable_address, false),
AccountMeta::new(vault_address, false),
AccountMeta::new(vault_usdc_address, false),
AccountMeta::new(protocol_address, false),
AccountMeta::new(position_address, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(spl_associated_token_account::ID, false),
AccountMeta::new_readonly(sysvar::instructions::ID, false),
];
accounts.extend(protocol_accounts(protocol_id, vault_address));
Instruction {
program_id: crate::ID,
accounts,
data: Claim {
amount: amount.to_le_bytes(),
min_usdc_out: min_usdc_out.to_le_bytes(),
}
.to_bytes(),
}
}
pub fn wrap(signer: Pubkey, mint_stable: Pubkey, protocol_id: u8, amount: u64) -> Instruction {
let stable_address = stable_pda(mint_stable).0;
let signer_stable_address = get_associated_token_address(&signer, &mint_stable);
let signer_usdc_address = get_associated_token_address(&signer, &USDC_ADDRESS);
let vault_address = vault_pda().0;
let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
let protocol_address = protocol_pda(protocol_id).0;
let position_address = position_pda(stable_address, protocol_id).0;
let mut accounts = vec![
AccountMeta::new(signer, true),
AccountMeta::new(signer, true),
AccountMeta::new(signer_stable_address, false),
AccountMeta::new(signer_usdc_address, false),
AccountMeta::new_readonly(USDC_ADDRESS, false),
AccountMeta::new(mint_stable, false),
AccountMeta::new(stable_address, false),
AccountMeta::new(vault_address, false),
AccountMeta::new(vault_usdc_address, false),
AccountMeta::new(protocol_address, false),
AccountMeta::new(position_address, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(spl_associated_token_account::ID, false),
AccountMeta::new_readonly(sysvar::instructions::ID, false),
];
accounts.extend(protocol_accounts(protocol_id, vault_address));
Instruction {
program_id: crate::ID,
accounts,
data: Wrap {
amount: amount.to_le_bytes(),
}
.to_bytes(),
}
}
pub fn unwrap(signer: Pubkey, mint_stable: Pubkey, protocol_id: u8, amount: u64, min_usdc_out: u64) -> Instruction {
let stable_address = stable_pda(mint_stable).0;
let signer_stable_address = get_associated_token_address(&signer, &mint_stable);
let signer_usdc_address = get_associated_token_address(&signer, &USDC_ADDRESS);
let vault_address = vault_pda().0;
let vault_usdc_address = get_associated_token_address(&vault_address, &USDC_ADDRESS);
let protocol_address = protocol_pda(protocol_id).0;
let position_address = position_pda(stable_address, protocol_id).0;
let mut accounts = vec![
AccountMeta::new(signer, true),
AccountMeta::new(signer, true),
AccountMeta::new(signer_stable_address, false),
AccountMeta::new(signer_usdc_address, false),
AccountMeta::new_readonly(USDC_ADDRESS, false),
AccountMeta::new(mint_stable, false),
AccountMeta::new(stable_address, false),
AccountMeta::new(vault_address, false),
AccountMeta::new(vault_usdc_address, false),
AccountMeta::new(protocol_address, false),
AccountMeta::new(position_address, false),
AccountMeta::new_readonly(system_program::ID, false),
AccountMeta::new_readonly(spl_token::ID, false),
AccountMeta::new_readonly(spl_associated_token_account::ID, false),
AccountMeta::new_readonly(sysvar::instructions::ID, false),
];
accounts.extend(protocol_accounts(protocol_id, vault_address));
Instruction {
program_id: crate::ID,
accounts,
data: Unwrap {
amount: amount.to_le_bytes(),
min_usdc_out: min_usdc_out.to_le_bytes(),
}
.to_bytes(),
}
}
pub struct ProtocolAllocation {
pub protocol_id: u8,
pub usdc_available: u64,
}
pub fn build_unwrap_bundle(
_signer: Pubkey,
_mint_stable: Pubkey,
_amount: u64,
_protocols: &[ProtocolAllocation],
) -> Vec<Instruction> {
todo!("Implement multi-protocol unwrap bundle builder")
}