Skip to main content

acta_maker_sdk/chain/
ix.rs

1use solana_sdk::{
2    instruction::{AccountMeta, Instruction},
3    pubkey::Pubkey,
4};
5use thiserror::Error;
6
7#[derive(Debug, Error)]
8pub enum ChainIxError {
9    #[error("invalid position type: {0}")]
10    InvalidPositionType(u8),
11}
12
13#[derive(Debug, Clone)]
14pub struct MakerDepositIxArgs {
15    pub maker_owner: Pubkey,
16    pub amount: u64,
17    pub premium_mint: Pubkey,
18    pub token_program: Pubkey,
19    pub create_atas: bool,
20}
21
22#[derive(Debug, Clone)]
23pub struct MakerWithdrawIxArgs {
24    pub maker_owner: Pubkey,
25    pub amount: u64,
26    pub premium_mint: Pubkey,
27    pub token_program: Pubkey,
28    pub create_atas: bool,
29}
30
31#[derive(Debug, Clone)]
32pub struct FundPositionIxArgs {
33    pub holder: Pubkey,
34    pub position_pda: Pubkey,
35    pub market_pda: Pubkey,
36    pub position_type: u8,
37    pub underlying_mint: Pubkey,
38    pub quote_mint: Pubkey,
39    pub underlying_token_program_id: Pubkey,
40    pub quote_token_program_id: Pubkey,
41    pub create_atas: bool,
42}
43
44#[derive(Debug, Clone)]
45pub struct UpdateQuoteSigningIxArgs {
46    pub maker_owner: Pubkey,
47    pub new_quote_signing: Pubkey,
48}
49
50pub fn build_maker_deposit_ixs(
51    program_id: &Pubkey,
52    args: &MakerDepositIxArgs,
53    fee_payer: Pubkey,
54) -> Result<Vec<Instruction>, ChainIxError> {
55    let (maker_pda, _) = maker_pda_with_program_id(program_id, &args.maker_owner);
56    let maker_owner_premium_ata =
57        derive_associated_token_address(&args.maker_owner, &args.premium_mint, &args.token_program);
58    let maker_custody_ata =
59        derive_associated_token_address(&maker_pda, &args.premium_mint, &args.token_program);
60
61    let mut ixs = Vec::new();
62    if args.create_atas {
63        ixs.push(create_associated_token_account_idempotent_ix(
64            &fee_payer,
65            &maker_owner_premium_ata,
66            &args.maker_owner,
67            &args.premium_mint,
68            &args.token_program,
69        ));
70        ixs.push(create_associated_token_account_idempotent_ix(
71            &fee_payer,
72            &maker_custody_ata,
73            &maker_pda,
74            &args.premium_mint,
75            &args.token_program,
76        ));
77    }
78
79    let ix = Instruction {
80        program_id: *program_id,
81        accounts: vec![
82            AccountMeta::new(args.maker_owner, true),
83            AccountMeta::new_readonly(maker_pda, false),
84            AccountMeta::new(maker_owner_premium_ata, false),
85            AccountMeta::new(maker_custody_ata, false),
86            AccountMeta::new_readonly(args.token_program, false),
87        ],
88        data: build_amount_instruction(InstructionKind::MakerDeposit, args.amount),
89    };
90
91    ixs.push(ix);
92    Ok(ixs)
93}
94
95pub fn build_maker_withdraw_ixs(
96    program_id: &Pubkey,
97    args: &MakerWithdrawIxArgs,
98    fee_payer: Pubkey,
99) -> Result<Vec<Instruction>, ChainIxError> {
100    let (maker_pda, _) = maker_pda_with_program_id(program_id, &args.maker_owner);
101    let maker_owner_premium_ata =
102        derive_associated_token_address(&args.maker_owner, &args.premium_mint, &args.token_program);
103    let maker_custody_ata =
104        derive_associated_token_address(&maker_pda, &args.premium_mint, &args.token_program);
105
106    let mut ixs = Vec::new();
107    if args.create_atas {
108        ixs.push(create_associated_token_account_idempotent_ix(
109            &fee_payer,
110            &maker_owner_premium_ata,
111            &args.maker_owner,
112            &args.premium_mint,
113            &args.token_program,
114        ));
115    }
116
117    let ix = Instruction {
118        program_id: *program_id,
119        accounts: vec![
120            AccountMeta::new(args.maker_owner, true),
121            AccountMeta::new(maker_pda, false),
122            AccountMeta::new(maker_custody_ata, false),
123            AccountMeta::new(maker_owner_premium_ata, false),
124            AccountMeta::new_readonly(args.token_program, false),
125        ],
126        data: build_amount_instruction(InstructionKind::MakerWithdraw, args.amount),
127    };
128
129    ixs.push(ix);
130    Ok(ixs)
131}
132
133/// Rotate the maker's quote-signing key; `maker_owner` signs.
134pub fn build_update_quote_signing_ixs(
135    program_id: &Pubkey,
136    args: &UpdateQuoteSigningIxArgs,
137) -> Vec<Instruction> {
138    let (maker_pda, _) = maker_pda_with_program_id(program_id, &args.maker_owner);
139    let mut data = Vec::with_capacity(1 + 32);
140    data.push(InstructionKind::UpdateMakerQuoteSigning as u8);
141    data.extend_from_slice(args.new_quote_signing.as_ref());
142    vec![Instruction {
143        program_id: *program_id,
144        accounts: vec![
145            AccountMeta::new_readonly(args.maker_owner, true),
146            AccountMeta::new(maker_pda, false),
147        ],
148        data,
149    }]
150}
151
152pub fn build_fund_position_ixs(
153    program_id: &Pubkey,
154    args: &FundPositionIxArgs,
155    fee_payer: Pubkey,
156) -> Result<Vec<Instruction>, ChainIxError> {
157    let (maker_pda, _) = maker_pda_with_program_id(program_id, &args.holder);
158    let (settlement_mint, settlement_program) = match args.position_type {
159        0 => (args.quote_mint, args.quote_token_program_id),
160        1 => (args.underlying_mint, args.underlying_token_program_id),
161        other => return Err(ChainIxError::InvalidPositionType(other)),
162    };
163
164    let holder_funding_ata =
165        derive_associated_token_address(&args.holder, &settlement_mint, &settlement_program);
166    let pos_settlement_ata =
167        derive_associated_token_address(&args.position_pda, &settlement_mint, &settlement_program);
168
169    let mut ixs = Vec::new();
170    if args.create_atas {
171        ixs.push(create_associated_token_account_idempotent_ix(
172            &fee_payer,
173            &holder_funding_ata,
174            &args.holder,
175            &settlement_mint,
176            &settlement_program,
177        ));
178        ixs.push(create_associated_token_account_idempotent_ix(
179            &fee_payer,
180            &pos_settlement_ata,
181            &args.position_pda,
182            &settlement_mint,
183            &settlement_program,
184        ));
185    }
186
187    let ix = Instruction {
188        program_id: *program_id,
189        accounts: vec![
190            AccountMeta::new(args.holder, true),
191            AccountMeta::new_readonly(maker_pda, false),
192            AccountMeta::new(args.position_pda, false),
193            AccountMeta::new_readonly(args.market_pda, false),
194            AccountMeta::new(holder_funding_ata, false),
195            AccountMeta::new(pos_settlement_ata, false),
196            AccountMeta::new_readonly(settlement_program, false),
197        ],
198        data: vec![InstructionKind::DepositFundsToPosition as u8],
199    };
200
201    ixs.push(ix);
202    Ok(ixs)
203}
204
205#[repr(u8)]
206enum InstructionKind {
207    UpdateMakerQuoteSigning = 3,
208    MakerDeposit = 4,
209    MakerWithdraw = 5,
210    DepositFundsToPosition = 9,
211}
212
213fn build_amount_instruction(kind: InstructionKind, amount: u64) -> Vec<u8> {
214    let mut data = Vec::with_capacity(1 + 8);
215    data.push(kind as u8);
216    data.extend_from_slice(&amount.to_le_bytes());
217    data
218}
219
220pub fn maker_pda_with_program_id(program_id: &Pubkey, maker_owner: &Pubkey) -> (Pubkey, u8) {
221    Pubkey::find_program_address(&[b"maker", maker_owner.as_ref()], program_id)
222}
223
224pub fn derive_associated_token_address(
225    owner: &Pubkey,
226    mint: &Pubkey,
227    token_program: &Pubkey,
228) -> Pubkey {
229    derive_associated_token_address_with_bump(owner, mint, token_program).0
230}
231
232pub(crate) fn derive_associated_token_address_with_bump(
233    owner: &Pubkey,
234    mint: &Pubkey,
235    token_program: &Pubkey,
236) -> (Pubkey, u8) {
237    Pubkey::find_program_address(
238        &[owner.as_ref(), token_program.as_ref(), mint.as_ref()],
239        &ASSOCIATED_TOKEN_PROGRAM_ID,
240    )
241}
242
243pub(crate) fn create_associated_token_account_idempotent_ix(
244    payer: &Pubkey,
245    ata: &Pubkey,
246    owner: &Pubkey,
247    mint: &Pubkey,
248    token_program: &Pubkey,
249) -> Instruction {
250    Instruction {
251        program_id: ASSOCIATED_TOKEN_PROGRAM_ID,
252        accounts: vec![
253            AccountMeta::new(*payer, true),
254            AccountMeta::new(*ata, false),
255            AccountMeta::new_readonly(*owner, false),
256            AccountMeta::new_readonly(*mint, false),
257            AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false),
258            AccountMeta::new_readonly(*token_program, false),
259            AccountMeta::new_readonly(RENT_SYSVAR_ID, false),
260        ],
261        data: vec![1u8],
262    }
263}
264
265pub(crate) const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey =
266    solana_sdk::pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL");
267pub(crate) const SYSTEM_PROGRAM_ID: Pubkey =
268    solana_sdk::pubkey!("11111111111111111111111111111111");
269const RENT_SYSVAR_ID: Pubkey = solana_sdk::pubkey!("SysvarRent111111111111111111111111111111111");
270pub(crate) const TOKEN_PROGRAM_ID: Pubkey =
271    solana_sdk::pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
272pub(crate) const TOKEN_2022_PROGRAM_ID: Pubkey =
273    solana_sdk::pubkey!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb");