1use std::borrow::Cow;
5
6use anchor_lang::{
7 prelude::borsh::{self, BorshDeserialize, BorshSerialize},
8 AnchorDeserialize, AnchorSerialize, InstructionData,
9};
10use solana_rpc_client_api::config::RpcSendTransactionConfig;
11use solana_sdk::{
12 compute_budget::ComputeBudgetInstruction,
13 instruction::{AccountMeta, Instruction},
14 message::{v0, VersionedMessage},
15 pubkey::Pubkey,
16 signature::Signature,
17};
18
19use crate::{
20 accounts::User,
21 build_accounts,
22 constants::{self, state_account, JIT_PROXY_ID},
23 drift_idl,
24 swift_order_subscriber::SignedOrderInfo,
25 types::PositionDirection,
26 DriftClient, MarketId, MarketType, PostOnlyParam, ReferrerInfo, SdkError, SdkResult,
27 TransactionBuilder, Wallet,
28};
29
30#[derive(Clone, Copy, BorshSerialize, BorshDeserialize, PartialEq, Debug, Eq)]
31pub enum PriceType {
32 Limit,
33 Oracle,
34}
35
36pub struct JitTakerParams {
38 taker: User,
39 taker_key: Pubkey,
40 taker_stats_key: Pubkey,
41 taker_referrer_info: Option<ReferrerInfo>,
42}
43
44impl JitTakerParams {
45 pub fn new(
46 taker_key: Pubkey,
47 taker_stats_key: Pubkey,
48 taker: User,
49 taker_referrer_info: Option<ReferrerInfo>,
50 ) -> Self {
51 Self {
52 taker_key,
53 taker_stats_key,
54 taker,
55 taker_referrer_info,
56 }
57 }
58}
59
60#[derive(Copy, Clone, Debug)]
61pub struct JitIxParams {
63 pub max_position: i64,
64 pub min_position: i64,
65 pub bid: i64,
66 pub ask: i64,
67 pub price_type: PriceType,
68 pub post_only: Option<PostOnlyParam>,
69}
70
71impl JitIxParams {
72 pub fn new(
73 max_position: i64,
74 min_position: i64,
75 bid: i64,
76 ask: i64,
77 price_type: PriceType,
78 post_only: Option<PostOnlyParam>,
79 ) -> Self {
80 Self {
81 max_position,
82 min_position,
83 bid,
84 ask,
85 price_type,
86 post_only,
87 }
88 }
89}
90
91#[derive(Clone)]
92pub struct JitProxyClient {
93 drift_client: DriftClient,
94 config: RpcSendTransactionConfig,
95 cu_params: Option<ComputeBudgetParams>,
96}
97
98impl JitProxyClient {
99 pub fn new(
100 drift_client: DriftClient,
101 config: Option<RpcSendTransactionConfig>,
102 cu_params: Option<ComputeBudgetParams>,
103 ) -> Self {
104 Self {
105 drift_client,
106 config: config.unwrap_or_default(),
107 cu_params,
108 }
109 }
110
111 pub fn update_config(&mut self, config: RpcSendTransactionConfig) {
112 self.config = config;
113 }
114
115 pub fn update_cu_params(&mut self, cu_params: ComputeBudgetParams) {
116 self.cu_params = Some(cu_params);
117 }
118
119 pub async fn build_jit_tx(
126 &self,
127 taker_order_id: u32,
128 taker_params: &JitTakerParams,
129 jit_ix_params: JitIxParams,
130 maker_params: (&Pubkey, &User),
131 ) -> SdkResult<VersionedMessage> {
132 let order = taker_params
133 .taker
134 .orders
135 .iter()
136 .find(|order| order.order_id == taker_order_id)
137 .ok_or(SdkError::JitOrderNotFound)?;
138
139 let tx_builder = TransactionBuilder::new(
140 self.drift_client.program_data(),
141 *maker_params.0,
142 Cow::Borrowed(maker_params.1),
143 false,
144 );
145
146 let program_data = tx_builder.program_data();
147 let account_data = tx_builder.account_data();
148
149 let writable_markets = match order.market_type {
150 MarketType::Perp => {
151 vec![MarketId::perp(order.market_index)]
152 }
153 MarketType::Spot => {
154 vec![MarketId::spot(order.market_index), MarketId::QUOTE_SPOT]
155 }
156 };
157
158 let maker_authority = maker_params.1.authority;
159 let mut accounts = build_accounts(
160 program_data,
161 self::accounts::Jit {
162 state: *state_account(),
163 user: *maker_params.0,
164 user_stats: Wallet::derive_stats_account(&maker_authority),
165 taker: taker_params.taker_key,
166 taker_stats: taker_params.taker_stats_key,
167 authority: maker_authority,
168 drift_program: constants::PROGRAM_ID,
169 },
170 &[&taker_params.taker, account_data],
171 std::iter::empty(),
172 writable_markets.iter(),
173 );
174
175 if let Some(referrer_info) = taker_params.taker_referrer_info {
176 accounts.push(AccountMeta::new(referrer_info.referrer(), false));
177 accounts.push(AccountMeta::new(referrer_info.referrer_stats(), false));
178 }
179
180 if order.market_type == drift_idl::types::MarketType::Spot {
181 let spot_market_vault = self
182 .drift_client
183 .try_get_spot_market_account(order.market_index)?
184 .vault;
185 let quote_spot_market_vault = self
186 .drift_client
187 .try_get_spot_market_account(MarketId::QUOTE_SPOT.index())?
188 .vault;
189 accounts.push(AccountMeta::new_readonly(spot_market_vault, false));
190 accounts.push(AccountMeta::new_readonly(quote_spot_market_vault, false));
191 }
192
193 let jit_params = self::instruction::JitParams {
194 taker_order_id,
195 max_position: jit_ix_params.max_position,
196 min_position: jit_ix_params.min_position,
197 bid: jit_ix_params.bid,
198 ask: jit_ix_params.ask,
199 price_type: jit_ix_params.price_type,
200 post_only: jit_ix_params.post_only,
201 };
202
203 let ix = Instruction {
204 program_id: JIT_PROXY_ID,
205 accounts,
206 data: instruction::Jit { params: jit_params }.data(),
207 };
208
209 let mut ixs = Vec::with_capacity(3);
210 if let Some(cu_params) = self.cu_params {
211 let cu_limit_ix =
212 ComputeBudgetInstruction::set_compute_unit_price(cu_params.microlamports_per_cu());
213 let cu_price_ix =
214 ComputeBudgetInstruction::set_compute_unit_limit(cu_params.cu_limit());
215
216 ixs.push(cu_limit_ix);
217 ixs.push(cu_price_ix);
218 }
219 ixs.push(ix);
220
221 let luts = program_data.lookup_tables;
222
223 let message =
224 v0::Message::try_compile(&maker_authority, ixs.as_slice(), luts, Default::default())
225 .expect("failed to compile message");
226
227 Ok(VersionedMessage::V0(message))
228 }
229
230 pub async fn build_swift_ix(
240 &self,
241 signed_order_info: &SignedOrderInfo,
242 taker_params: &JitTakerParams,
243 jit_ix_params: &JitIxParams,
244 maker_pubkey: &Pubkey,
245 maker_account_data: &User,
246 ) -> SdkResult<VersionedMessage> {
247 let maker_authority = maker_account_data.authority;
248 let program_data = self.drift_client.program_data();
249 let signed_order_info_params = signed_order_info.order_params();
250 let account_data = maker_account_data;
251 let market_index = signed_order_info_params.market_index;
252 let market_type = signed_order_info_params.market_type;
253
254 let writable_markets = match market_type {
255 MarketType::Perp => {
256 vec![MarketId::perp(market_index)]
257 }
258 MarketType::Spot => {
259 vec![MarketId::spot(market_index), MarketId::QUOTE_SPOT]
260 }
261 };
262
263 let mut accounts = build_accounts(
264 program_data,
265 self::accounts::JitSignedMsg {
266 state: *state_account(),
267 authority: maker_authority,
268 user: *maker_pubkey,
269 user_stats: Wallet::derive_stats_account(&maker_authority),
270 taker: taker_params.taker_key,
271 taker_stats: taker_params.taker_stats_key,
272 taker_signed_msg_user_orders: Wallet::derive_swift_order_account(
273 &taker_params.taker.authority,
274 ),
275 drift_program: constants::PROGRAM_ID,
276 },
277 &[&taker_params.taker, account_data],
278 std::iter::empty(),
279 writable_markets.iter(),
280 );
281
282 if let Some(referrer_info) = taker_params.taker_referrer_info {
283 accounts.push(AccountMeta::new(referrer_info.referrer(), false));
284 accounts.push(AccountMeta::new(referrer_info.referrer_stats(), false));
285 }
286
287 if market_type == drift_idl::types::MarketType::Spot {
288 let spot_market_vault = self
289 .drift_client
290 .try_get_spot_market_account(market_index)?
291 .vault;
292 let quote_spot_market_vault = self
293 .drift_client
294 .try_get_spot_market_account(MarketId::QUOTE_SPOT.index())?
295 .vault;
296 accounts.push(AccountMeta::new_readonly(spot_market_vault, false));
297 accounts.push(AccountMeta::new_readonly(quote_spot_market_vault, false));
298 }
299
300 let jit_params = self::instruction::JitSignedMsgParams {
301 signed_order_info_uuid: signed_order_info.order_uuid(),
302 max_position: jit_ix_params.max_position,
303 min_position: jit_ix_params.min_position,
304 bid: jit_ix_params.bid,
305 ask: jit_ix_params.ask,
306 price_type: jit_ix_params.price_type,
307 post_only: jit_ix_params.post_only,
308 };
309
310 let fill_ix = Instruction {
311 program_id: JIT_PROXY_ID,
312 accounts,
313 data: instruction::JitSignedMsg { params: jit_params }.data(),
314 };
315
316 let message = TransactionBuilder::new(
317 self.drift_client.program_data(),
318 *maker_pubkey,
319 Cow::Borrowed(maker_account_data),
320 false,
321 )
322 .place_swift_order(signed_order_info, &taker_params.taker)
323 .add_ix(fill_ix)
324 .build();
325
326 Ok(message)
327 }
328
329 pub async fn jit(
337 &self,
338 taker_order_id: u32,
339 taker_params: &JitTakerParams,
340 jit_params: JitIxParams,
341 maker_authority: &Pubkey,
342 sub_account_id: Option<u16>,
343 ) -> SdkResult<Signature> {
344 let sub_account =
345 Wallet::derive_user_account(maker_authority, sub_account_id.unwrap_or_default());
346 let sub_account_data = self.drift_client.get_user_account(&sub_account).await?;
347 let tx = self
348 .build_jit_tx(
349 taker_order_id,
350 taker_params,
351 jit_params,
352 (&sub_account, &sub_account_data),
353 )
354 .await?;
355 self.drift_client
356 .sign_and_send_with_config(tx, None, self.config)
357 .await
358 }
359
360 pub async fn try_swift_fill(
368 &self,
369 signed_order_info: &SignedOrderInfo,
370 taker_params: &JitTakerParams,
371 jit_params: &JitIxParams,
372 maker_authority: &Pubkey,
373 sub_account_id: Option<u16>,
374 ) -> SdkResult<Signature> {
375 let sub_account =
376 Wallet::derive_user_account(maker_authority, sub_account_id.unwrap_or_default());
377 let sub_account_data = self.drift_client.get_user_account(&sub_account).await?;
378 let tx = self
379 .build_swift_ix(
380 signed_order_info,
381 taker_params,
382 jit_params,
383 &sub_account,
384 &sub_account_data,
385 )
386 .await?;
387 self.drift_client
388 .sign_and_send_with_config(tx, None, self.config)
389 .await
390 }
391}
392
393#[derive(Clone, Copy)]
394pub struct ComputeBudgetParams {
395 microlamports_per_cu: u64,
396 cu_limit: u32,
397}
398
399impl ComputeBudgetParams {
400 pub fn new(microlamports_per_cu: u64, cu_limit: u32) -> Self {
401 Self {
402 microlamports_per_cu,
403 cu_limit,
404 }
405 }
406
407 pub fn microlamports_per_cu(&self) -> u64 {
408 self.microlamports_per_cu
409 }
410
411 pub fn cu_limit(&self) -> u32 {
412 self.cu_limit
413 }
414}
415
416#[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize, PartialEq)]
417pub struct JitSwiftParams {
418 pub signed_order_info_uuid: [u8; 8],
419 pub max_position: i64,
420 pub min_position: i64,
421 pub bid: i64,
422 pub ask: i64,
423 pub price_type: PriceType,
424 pub post_only: Option<PostOnlyParam>,
425}
426
427impl Default for JitSwiftParams {
428 fn default() -> Self {
429 Self {
430 signed_order_info_uuid: [0; 8],
431 max_position: 0,
432 min_position: 0,
433 bid: 0,
434 ask: 0,
435 price_type: PriceType::Limit,
436 post_only: None,
437 }
438 }
439}
440
441impl JitSwiftParams {
442 pub fn get_worst_price(
443 self,
444 oracle_price: i64,
445 taker_direction: PositionDirection,
446 ) -> SdkResult<u64> {
447 match (taker_direction, self.price_type) {
448 (PositionDirection::Long, PriceType::Limit) => Ok(self.ask.unsigned_abs()),
449 (PositionDirection::Short, PriceType::Limit) => Ok(self.bid.unsigned_abs()),
450 (PositionDirection::Long, PriceType::Oracle) => {
451 Ok(oracle_price.saturating_add(self.ask).unsigned_abs())
452 }
453 (PositionDirection::Short, PriceType::Oracle) => {
454 Ok(oracle_price.saturating_add(self.bid).unsigned_abs())
455 }
456 }
457 }
458}
459
460pub mod instruction {
461 use super::*;
464 use crate::PostOnlyParam;
465 #[derive(BorshDeserialize, BorshSerialize)]
466 pub struct Jit {
467 pub params: JitParams,
468 }
469 impl anchor_lang::Discriminator for Jit {
470 const DISCRIMINATOR: &[u8] = &[99, 42, 97, 140, 152, 62, 167, 234];
471 }
472 impl anchor_lang::InstructionData for Jit {}
473
474 #[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize)]
475 pub struct JitParams {
476 pub taker_order_id: u32,
477 pub max_position: i64,
478 pub min_position: i64,
479 pub bid: i64,
480 pub ask: i64,
481 pub price_type: PriceType,
482 pub post_only: Option<PostOnlyParam>,
483 }
484
485 #[derive(BorshDeserialize, BorshSerialize)]
486 pub struct JitSignedMsg {
487 pub params: JitSignedMsgParams,
488 }
489 impl anchor_lang::Discriminator for JitSignedMsg {
490 const DISCRIMINATOR: &[u8] = &[134, 130, 156, 72, 37, 120, 153, 21];
491 }
492 impl anchor_lang::InstructionData for JitSignedMsg {}
493
494 #[derive(Debug, Clone, Copy, AnchorSerialize, AnchorDeserialize)]
495 pub struct JitSignedMsgParams {
496 pub signed_order_info_uuid: [u8; 8],
497 pub max_position: i64,
498 pub min_position: i64,
499 pub bid: i64,
500 pub ask: i64,
501 pub price_type: PriceType,
502 pub post_only: Option<PostOnlyParam>,
503 }
504}
505
506pub mod accounts {
507 use solana_sdk::instruction::AccountMeta;
510
511 use super::*;
512 use crate::drift_idl::traits::ToAccountMetas;
513
514 #[derive(anchor_lang::AnchorSerialize)]
516 pub struct Jit {
517 pub state: Pubkey,
518 pub user: Pubkey,
519 pub user_stats: Pubkey,
520 pub taker: Pubkey,
521 pub taker_stats: Pubkey,
522 pub authority: Pubkey,
523 pub drift_program: Pubkey,
524 }
525 #[automatically_derived]
526 impl ToAccountMetas for Jit {
527 fn to_account_metas(&self) -> Vec<AccountMeta> {
528 vec![
529 AccountMeta::new_readonly(self.state, false),
530 AccountMeta::new(self.user, false),
531 AccountMeta::new(self.user_stats, false),
532 AccountMeta::new(self.taker, false),
533 AccountMeta::new(self.taker_stats, false),
534 AccountMeta::new_readonly(self.authority, true),
535 AccountMeta::new_readonly(self.drift_program, false),
536 ]
537 }
538 }
539
540 pub struct JitSignedMsg {
541 pub state: Pubkey,
542 pub user: Pubkey,
543 pub user_stats: Pubkey,
544 pub taker: Pubkey,
545 pub taker_stats: Pubkey,
546 pub taker_signed_msg_user_orders: Pubkey,
547 pub authority: Pubkey,
548 pub drift_program: Pubkey,
549 }
550 #[automatically_derived]
551 impl ToAccountMetas for JitSignedMsg {
552 fn to_account_metas(&self) -> Vec<AccountMeta> {
553 vec![
554 AccountMeta::new_readonly(self.state, false),
555 AccountMeta::new(self.user, false),
556 AccountMeta::new(self.user_stats, false),
557 AccountMeta::new(self.taker, false),
558 AccountMeta::new(self.taker_stats, false),
559 AccountMeta::new(self.taker_signed_msg_user_orders, false),
560 AccountMeta::new_readonly(self.authority, true),
561 AccountMeta::new_readonly(self.drift_program, false),
562 ]
563 }
564 }
565}