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
133pub 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");