acta-maker-sdk 0.8.2

Rust SDK for the Acta options RFQ venue on Solana: maker and vault-operator WebSocket clients, order signing, chain builders
Documentation
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)
}

/// Rotate the maker's quote-signing key; `maker_owner` signs.
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");