use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ChainIxError {
#[error("invalid position type: {0}")]
InvalidPositionType(u8),
}
#[derive(Debug, Clone)]
pub struct MakerDepositIxArgs {
pub maker_owner: Pubkey,
pub amount: u64,
pub premium_mint: Pubkey,
pub token_program: Pubkey,
pub create_atas: bool,
}
#[derive(Debug, Clone)]
pub struct MakerWithdrawIxArgs {
pub maker_owner: Pubkey,
pub amount: u64,
pub premium_mint: Pubkey,
pub token_program: Pubkey,
pub create_atas: bool,
}
#[derive(Debug, Clone)]
pub struct FundPositionIxArgs {
pub holder: Pubkey,
pub position_pda: Pubkey,
pub market_pda: Pubkey,
pub position_type: u8,
pub underlying_mint: Pubkey,
pub quote_mint: Pubkey,
pub underlying_token_program_id: Pubkey,
pub quote_token_program_id: Pubkey,
pub create_atas: bool,
}
#[derive(Debug, Clone)]
pub struct UpdateQuoteSigningIxArgs {
pub maker_owner: Pubkey,
pub new_quote_signing: Pubkey,
}
pub fn build_maker_deposit_ixs(
program_id: &Pubkey,
args: &MakerDepositIxArgs,
fee_payer: Pubkey,
) -> Result<Vec<Instruction>, ChainIxError> {
let (maker_pda, _) = maker_pda_with_program_id(program_id, &args.maker_owner);
let maker_owner_premium_ata =
derive_associated_token_address(&args.maker_owner, &args.premium_mint, &args.token_program);
let maker_custody_ata =
derive_associated_token_address(&maker_pda, &args.premium_mint, &args.token_program);
let mut ixs = Vec::new();
if args.create_atas {
ixs.push(create_associated_token_account_idempotent_ix(
&fee_payer,
&maker_owner_premium_ata,
&args.maker_owner,
&args.premium_mint,
&args.token_program,
));
ixs.push(create_associated_token_account_idempotent_ix(
&fee_payer,
&maker_custody_ata,
&maker_pda,
&args.premium_mint,
&args.token_program,
));
}
let ix = Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(args.maker_owner, true),
AccountMeta::new_readonly(maker_pda, false),
AccountMeta::new(maker_owner_premium_ata, false),
AccountMeta::new(maker_custody_ata, false),
AccountMeta::new_readonly(args.token_program, false),
],
data: build_amount_instruction(InstructionKind::MakerDeposit, args.amount),
};
ixs.push(ix);
Ok(ixs)
}
pub fn build_maker_withdraw_ixs(
program_id: &Pubkey,
args: &MakerWithdrawIxArgs,
fee_payer: Pubkey,
) -> Result<Vec<Instruction>, ChainIxError> {
let (maker_pda, _) = maker_pda_with_program_id(program_id, &args.maker_owner);
let maker_owner_premium_ata =
derive_associated_token_address(&args.maker_owner, &args.premium_mint, &args.token_program);
let maker_custody_ata =
derive_associated_token_address(&maker_pda, &args.premium_mint, &args.token_program);
let mut ixs = Vec::new();
if args.create_atas {
ixs.push(create_associated_token_account_idempotent_ix(
&fee_payer,
&maker_owner_premium_ata,
&args.maker_owner,
&args.premium_mint,
&args.token_program,
));
}
let ix = Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(args.maker_owner, true),
AccountMeta::new(maker_pda, false),
AccountMeta::new(maker_custody_ata, false),
AccountMeta::new(maker_owner_premium_ata, false),
AccountMeta::new_readonly(args.token_program, false),
],
data: build_amount_instruction(InstructionKind::MakerWithdraw, args.amount),
};
ixs.push(ix);
Ok(ixs)
}
pub fn build_update_quote_signing_ixs(
program_id: &Pubkey,
args: &UpdateQuoteSigningIxArgs,
) -> Vec<Instruction> {
let (maker_pda, _) = maker_pda_with_program_id(program_id, &args.maker_owner);
let mut data = Vec::with_capacity(1 + 32);
data.push(InstructionKind::UpdateMakerQuoteSigning as u8);
data.extend_from_slice(args.new_quote_signing.as_ref());
vec![Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new_readonly(args.maker_owner, true),
AccountMeta::new(maker_pda, false),
],
data,
}]
}
pub fn build_fund_position_ixs(
program_id: &Pubkey,
args: &FundPositionIxArgs,
fee_payer: Pubkey,
) -> Result<Vec<Instruction>, ChainIxError> {
let (maker_pda, _) = maker_pda_with_program_id(program_id, &args.holder);
let (settlement_mint, settlement_program) = match args.position_type {
0 => (args.quote_mint, args.quote_token_program_id),
1 => (args.underlying_mint, args.underlying_token_program_id),
other => return Err(ChainIxError::InvalidPositionType(other)),
};
let holder_funding_ata =
derive_associated_token_address(&args.holder, &settlement_mint, &settlement_program);
let pos_settlement_ata =
derive_associated_token_address(&args.position_pda, &settlement_mint, &settlement_program);
let mut ixs = Vec::new();
if args.create_atas {
ixs.push(create_associated_token_account_idempotent_ix(
&fee_payer,
&holder_funding_ata,
&args.holder,
&settlement_mint,
&settlement_program,
));
ixs.push(create_associated_token_account_idempotent_ix(
&fee_payer,
&pos_settlement_ata,
&args.position_pda,
&settlement_mint,
&settlement_program,
));
}
let ix = Instruction {
program_id: *program_id,
accounts: vec![
AccountMeta::new(args.holder, true),
AccountMeta::new_readonly(maker_pda, false),
AccountMeta::new(args.position_pda, false),
AccountMeta::new_readonly(args.market_pda, false),
AccountMeta::new(holder_funding_ata, false),
AccountMeta::new(pos_settlement_ata, false),
AccountMeta::new_readonly(settlement_program, false),
],
data: vec![InstructionKind::DepositFundsToPosition as u8],
};
ixs.push(ix);
Ok(ixs)
}
#[repr(u8)]
enum InstructionKind {
UpdateMakerQuoteSigning = 3,
MakerDeposit = 4,
MakerWithdraw = 5,
DepositFundsToPosition = 9,
}
fn build_amount_instruction(kind: InstructionKind, amount: u64) -> Vec<u8> {
let mut data = Vec::with_capacity(1 + 8);
data.push(kind as u8);
data.extend_from_slice(&amount.to_le_bytes());
data
}
pub fn maker_pda_with_program_id(program_id: &Pubkey, maker_owner: &Pubkey) -> (Pubkey, u8) {
Pubkey::find_program_address(&[b"maker", maker_owner.as_ref()], program_id)
}
pub fn derive_associated_token_address(
owner: &Pubkey,
mint: &Pubkey,
token_program: &Pubkey,
) -> Pubkey {
derive_associated_token_address_with_bump(owner, mint, token_program).0
}
pub(crate) fn derive_associated_token_address_with_bump(
owner: &Pubkey,
mint: &Pubkey,
token_program: &Pubkey,
) -> (Pubkey, u8) {
Pubkey::find_program_address(
&[owner.as_ref(), token_program.as_ref(), mint.as_ref()],
&ASSOCIATED_TOKEN_PROGRAM_ID,
)
}
pub(crate) fn create_associated_token_account_idempotent_ix(
payer: &Pubkey,
ata: &Pubkey,
owner: &Pubkey,
mint: &Pubkey,
token_program: &Pubkey,
) -> Instruction {
Instruction {
program_id: ASSOCIATED_TOKEN_PROGRAM_ID,
accounts: vec![
AccountMeta::new(*payer, true),
AccountMeta::new(*ata, false),
AccountMeta::new_readonly(*owner, false),
AccountMeta::new_readonly(*mint, false),
AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
AccountMeta::new_readonly(*token_program, false),
AccountMeta::new_readonly(RENT_SYSVAR_ID, false),
],
data: vec![1u8],
}
}
pub(crate) const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
pub(crate) const SYSTEM_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("11111111111111111111111111111111");
const RENT_SYSVAR_ID: Pubkey = solana_sdk::pubkey!("SysvarRent111111111111111111111111111111111");
pub(crate) const TOKEN_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
pub(crate) const TOKEN_2022_PROGRAM_ID: Pubkey =
solana_sdk::pubkey!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");