1use std::{
2 cell::{BorrowError, BorrowMutError},
3 cmp::Ordering,
4 str::FromStr,
5};
6
7use dashmap::DashMap;
8pub use solana_rpc_client_api::config::RpcSendTransactionConfig;
9pub use solana_sdk::{
10 commitment_config::CommitmentConfig, message::VersionedMessage,
11 transaction::VersionedTransaction,
12};
13use solana_sdk::{
14 instruction::{AccountMeta, InstructionError},
15 pubkey::Pubkey,
16 transaction::TransactionError,
17};
18use thiserror::Error;
19use tokio::sync::oneshot;
20use tokio_tungstenite::tungstenite;
21
22pub use crate::drift_idl::{
24 accounts::{self},
25 errors::{self},
26 events::{self},
27 instructions::{self},
28 types::*,
29};
30use crate::{
31 constants::{ids, LUTS_DEVNET, LUTS_MAINNET},
32 drift_idl::errors::ErrorCode,
33 grpc::grpc_subscriber::GrpcError,
34 Wallet,
35};
36
37pub type MapOf<K, V> = DashMap<K, V, ahash::RandomState>;
39
40pub type UnsubHandle = oneshot::Sender<()>;
42
43pub type SdkResult<T> = Result<T, SdkError>;
44
45pub fn is_one_of_variant<T: PartialEq>(value: &T, variants: &[T]) -> bool {
46 variants.iter().any(|variant| value == variant)
47}
48
49#[derive(Debug, Copy, Clone, PartialEq)]
54pub struct Context {
55 name: &'static str,
56 luts: &'static [Pubkey],
58 pyth: Pubkey,
60}
61
62impl Context {
63 #[allow(non_upper_case_globals)]
65 pub const MainNet: Context = Self {
66 name: "mainnet",
67 luts: LUTS_MAINNET,
68 pyth: ids::pyth_program::ID,
69 };
70 #[allow(non_upper_case_globals)]
72 pub const DevNet: Context = Self {
73 name: "devnet",
74 luts: LUTS_DEVNET,
75 pyth: ids::pyth_program::ID_DEVNET,
76 };
77
78 pub fn luts(&self) -> &[Pubkey] {
80 self.luts
81 }
82
83 pub fn pyth(&self) -> Pubkey {
85 self.pyth
86 }
87
88 pub fn name(&self) -> &'static str {
90 self.name
91 }
92}
93
94#[derive(Debug, Clone)]
96pub struct DataAndSlot<T> {
97 pub slot: u64,
98 pub data: T,
99}
100
101#[derive(Copy, Clone, Default, PartialEq, Eq, Hash)]
103pub struct MarketId {
104 index: u16,
105 kind: MarketType,
106}
107
108impl core::cmp::Eq for MarketType {}
112impl core::hash::Hash for MarketType {
113 fn hash<H: core::hash::Hasher>(&self, ra_expand_state: &mut H) {
114 core::mem::discriminant(self).hash(ra_expand_state);
115 match self {
116 MarketType::Spot => {}
117 MarketType::Perp => {}
118 }
119 }
120}
121
122impl MarketId {
123 pub fn new(index: u16, kind: MarketType) -> Self {
125 Self { index, kind }
126 }
127 pub const QUOTE_SPOT: Self = Self {
129 index: 0,
130 kind: MarketType::Spot,
131 };
132 pub const fn perp(index: u16) -> Self {
134 Self {
135 index,
136 kind: MarketType::Perp,
137 }
138 }
139 pub const fn spot(index: u16) -> Self {
141 Self {
142 index,
143 kind: MarketType::Spot,
144 }
145 }
146 pub fn index(&self) -> u16 {
148 self.index
149 }
150 pub fn kind(&self) -> MarketType {
152 self.kind
153 }
154 pub fn to_parts(self) -> (u16, MarketType) {
156 (self.index, self.kind)
157 }
158 pub fn is_perp(self) -> bool {
159 self.kind == MarketType::Perp
160 }
161 pub fn is_spot(self) -> bool {
162 self.kind == MarketType::Spot
163 }
164}
165
166impl std::fmt::Debug for MarketId {
167 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
168 match self.kind {
169 MarketType::Perp => {
170 write!(f, "perp/{}", self.index)
171 }
172 MarketType::Spot => {
173 write!(f, "spot/{}", self.index)
174 }
175 }
176 }
177}
178
179impl From<(u16, MarketType)> for MarketId {
180 fn from(value: (u16, MarketType)) -> Self {
181 Self {
182 index: value.0,
183 kind: value.1,
184 }
185 }
186}
187
188#[derive(Default)]
190pub struct NewOrder {
191 order_type: OrderType,
192 direction: PositionDirection,
193 reduce_only: bool,
194 market_id: MarketId,
195 post_only: PostOnlyParam,
196 ioc: bool,
197 amount: u64,
198 price: u64,
199 user_order_id: u8,
200}
201
202impl NewOrder {
203 pub fn market(market_id: MarketId) -> Self {
205 Self {
206 order_type: OrderType::Market,
207 market_id,
208 ..Default::default()
209 }
210 }
211 pub fn limit(market_id: MarketId) -> Self {
213 Self {
214 order_type: OrderType::Limit,
215 market_id,
216 ..Default::default()
217 }
218 }
219 pub fn amount(mut self, amount: i64) -> Self {
223 self.direction = if amount >= 0 {
224 PositionDirection::Long
225 } else {
226 PositionDirection::Short
227 };
228 self.amount = amount.unsigned_abs();
229
230 self
231 }
232 pub fn price(mut self, price: u64) -> Self {
234 self.price = price;
235 self
236 }
237 pub fn reduce_only(mut self, flag: bool) -> Self {
239 self.reduce_only = flag;
240 self
241 }
242 pub fn ioc(mut self, flag: bool) -> Self {
244 self.ioc = flag;
245 self
246 }
247 pub fn post_only(mut self, value: PostOnlyParam) -> Self {
249 self.post_only = value;
250 self
251 }
252 pub fn user_order_id(mut self, user_order_id: u8) -> Self {
254 self.user_order_id = user_order_id;
255 self
256 }
257 pub fn build(self) -> OrderParams {
259 OrderParams {
260 order_type: self.order_type,
261 market_index: self.market_id.index,
262 market_type: self.market_id.kind,
263 price: self.price,
264 base_asset_amount: self.amount,
265 reduce_only: self.reduce_only,
266 direction: self.direction,
267 immediate_or_cancel: self.ioc,
268 post_only: self.post_only,
269 user_order_id: self.user_order_id,
270 ..Default::default()
271 }
272 }
273}
274
275#[derive(Debug, Error)]
276pub enum SdkError {
277 #[error("{0}")]
278 Rpc(#[from] solana_rpc_client_api::client_error::Error),
279 #[error("{0}")]
280 Ws(#[from] drift_pubsub_client::PubsubClientError),
281 #[error("{0}")]
282 Anchor(#[from] Box<anchor_lang::error::Error>),
283 #[error("error while deserializing")]
284 Deserializing,
285 #[error("invalid drift account")]
286 InvalidAccount,
287 #[error("invalid oracle account")]
288 InvalidOracle,
289 #[error("invalid keypair seed")]
290 InvalidSeed,
291 #[error("invalid base58 value")]
292 InvalidBase58,
293 #[error("user does not have position: {0}")]
294 NoPosition(u16),
295 #[error("insufficient SOL balance for fees")]
296 OutOfSOL,
297 #[error("{0}")]
298 Signing(#[from] solana_sdk::signer::SignerError),
299 #[error("Received Error from websocket")]
300 WebsocketError,
301 #[error("Missed DLOB heartbeat")]
302 MissedHeartbeat,
303 #[error("Unsupported account data format")]
304 UnsupportedAccountData,
305 #[error("Could not decode data: {0}")]
306 CouldntDecode(#[from] base64::DecodeError),
307 #[error("Couldn't join task: {0}")]
308 CouldntJoin(#[from] tokio::task::JoinError),
309 #[error("Couldn't send unsubscribe message")]
310 CouldntUnsubscribe,
311 #[error("MathError")]
312 MathError(String),
313 #[error("{0}")]
314 BorrowMutError(#[from] BorrowMutError),
315 #[error("{0}")]
316 BorrowError(#[from] BorrowError),
317 #[error("{0}")]
318 Generic(String),
319 #[error("max connection attempts reached")]
320 MaxReconnectionAttemptsReached,
321 #[error("jit taker order not found")]
322 JitOrderNotFound,
323 #[error("market data unavailable. subscribe market: {0:?}")]
324 NoMarketData(MarketId),
325 #[error("account data unavailable. subscribe account: {0:?}")]
326 NoAccountData(Pubkey),
327 #[error("component is already subscribed")]
328 AlreadySubscribed,
329 #[error("invalid URL")]
330 InvalidUrl,
331 #[error("{0}")]
332 WsClient(#[from] tungstenite::Error),
333 #[error("libdrift_ffi_sys out-of-date")]
334 LibDriftVersion,
335 #[error("wallet signing disabled")]
336 WalletSigningDisabled,
337 #[error("{0}")]
338 Grpc(#[from] GrpcError),
339}
340
341impl SdkError {
342 pub fn to_anchor_error_code(&self) -> Option<ErrorCode> {
344 if let SdkError::Rpc(inner) = self {
345 if let Some(TransactionError::InstructionError(_, InstructionError::Custom(code))) =
346 inner.get_transaction_error()
347 {
348 return Some(unsafe {
350 std::mem::transmute::<u32, ErrorCode>(
351 code - anchor_lang::error::ERROR_CODE_OFFSET,
352 )
353 });
354 }
355 }
356 None
357 }
358 pub fn to_out_of_sol_error(&self) -> Option<SdkError> {
360 if let SdkError::Rpc(inner) = self {
361 if let Some(
362 TransactionError::InsufficientFundsForFee
363 | TransactionError::InsufficientFundsForRent { account_index: _ },
364 ) = inner.get_transaction_error()
365 {
366 return Some(Self::OutOfSOL);
367 }
368 }
369 None
370 }
371}
372
373#[derive(Copy, Clone, Debug, PartialEq, Eq)]
377#[repr(u8)]
378pub(crate) enum RemainingAccount {
379 Oracle { pubkey: Pubkey },
380 Spot { pubkey: Pubkey, writable: bool },
381 Perp { pubkey: Pubkey, writable: bool },
382}
383
384impl RemainingAccount {
385 fn pubkey(&self) -> &Pubkey {
386 match self {
387 Self::Oracle { pubkey } => pubkey,
388 Self::Spot { pubkey, .. } => pubkey,
389 Self::Perp { pubkey, .. } => pubkey,
390 }
391 }
392 fn parts(self) -> (Pubkey, bool) {
393 match self {
394 Self::Oracle { pubkey } => (pubkey, false),
395 Self::Spot {
396 pubkey, writable, ..
397 } => (pubkey, writable),
398 Self::Perp {
399 pubkey, writable, ..
400 } => (pubkey, writable),
401 }
402 }
403 fn discriminant(&self) -> u8 {
404 let ptr = <*const RemainingAccount>::from(self);
408 unsafe { *ptr.cast::<u8>() }
409 }
410}
411
412impl Ord for RemainingAccount {
413 fn cmp(&self, other: &Self) -> Ordering {
414 let type_order = self.discriminant().cmp(&other.discriminant());
415 if let Ordering::Equal = type_order {
416 self.pubkey().cmp(other.pubkey())
417 } else {
418 type_order
419 }
420 }
421}
422
423impl PartialOrd for RemainingAccount {
424 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
425 Some(self.cmp(other))
426 }
427}
428
429impl From<RemainingAccount> for AccountMeta {
430 fn from(value: RemainingAccount) -> Self {
431 let (pubkey, is_writable) = value.parts();
432 AccountMeta {
433 pubkey,
434 is_writable,
435 is_signer: false,
436 }
437 }
438}
439
440pub trait MarketPrecision {
442 fn price_tick(&self) -> u64;
444 fn quantity_tick(&self) -> u64;
446 fn min_order_size(&self) -> u64;
448}
449
450impl MarketPrecision for accounts::SpotMarket {
451 fn min_order_size(&self) -> u64 {
452 self.min_order_size
453 }
454 fn price_tick(&self) -> u64 {
455 self.order_tick_size
456 }
457 fn quantity_tick(&self) -> u64 {
458 self.order_step_size
459 }
460}
461
462impl MarketPrecision for accounts::PerpMarket {
463 fn min_order_size(&self) -> u64 {
464 self.amm.min_order_size
465 }
466 fn price_tick(&self) -> u64 {
467 self.amm.order_tick_size
468 }
469 fn quantity_tick(&self) -> u64 {
470 self.amm.order_step_size
471 }
472}
473
474#[derive(Copy, Clone)]
475pub struct ReferrerInfo {
476 referrer: Pubkey,
477 referrer_stats: Pubkey,
478}
479
480impl ReferrerInfo {
481 pub fn new(referrer: Pubkey, referrer_stats: Pubkey) -> Self {
482 Self {
483 referrer,
484 referrer_stats,
485 }
486 }
487
488 pub fn referrer(&self) -> Pubkey {
489 self.referrer
490 }
491
492 pub fn referrer_stats(&self) -> Pubkey {
493 self.referrer_stats
494 }
495
496 pub fn get_referrer_info(taker_stats: accounts::UserStats) -> Option<Self> {
497 if taker_stats.referrer == Pubkey::default() {
498 return None;
499 }
500
501 let user_account_pubkey = Wallet::derive_user_account(&taker_stats.referrer, 0);
502 let user_stats_pubkey = Wallet::derive_stats_account(&taker_stats.referrer);
503
504 Some(Self {
505 referrer: user_account_pubkey,
506 referrer_stats: user_stats_pubkey,
507 })
508 }
509}
510
511impl OrderType {
512 pub fn as_str(&self) -> &str {
513 match self {
514 OrderType::Limit => "limit",
515 OrderType::Market => "market",
516 OrderType::Oracle => "oracle",
517 OrderType::TriggerLimit => "trigger_limit",
518 OrderType::TriggerMarket => "trigger_market",
519 }
520 }
521}
522
523impl MarketType {
524 pub fn as_str(&self) -> &str {
525 match self {
526 MarketType::Perp => "perp",
527 MarketType::Spot => "spot",
528 }
529 }
530}
531
532impl FromStr for MarketType {
533 type Err = ();
534 fn from_str(s: &str) -> Result<Self, Self::Err> {
535 if s.eq_ignore_ascii_case("perp") {
536 Ok(Self::Perp)
537 } else if s.eq_ignore_ascii_case("spot") {
538 Ok(Self::Spot)
539 } else {
540 Err(())
541 }
542 }
543}
544
545#[cfg(test)]
546mod tests {
547 use std::str::FromStr;
548
549 use solana_rpc_client_api::{
550 client_error::{Error as ClientError, ErrorKind as ClientErrorKind},
551 request::{RpcError, RpcRequest, RpcResponseErrorData},
552 response::RpcSimulateTransactionResult,
553 };
554 use solana_sdk::{
555 instruction::InstructionError, pubkey::Pubkey, transaction::TransactionError,
556 };
557
558 use super::{RemainingAccount, SdkError};
559 use crate::{drift_idl::errors::ErrorCode, MarketType};
560
561 #[test]
562 fn market_type_str() {
563 assert_eq!(MarketType::from_str("PERP").unwrap(), MarketType::Perp,);
564 assert_eq!(MarketType::from_str("spot").unwrap(), MarketType::Spot,);
565 assert_eq!("perp", MarketType::Perp.as_str());
566 assert_eq!("spot", MarketType::Spot.as_str());
567 }
568
569 #[test]
570 fn extract_anchor_error() {
571 let err = SdkError::Rpc(
572 ClientError {
573 request: Some(RpcRequest::SendTransaction),
574 kind: ClientErrorKind::RpcError(
575 RpcError::RpcResponseError {
576 code: -32002,
577 message: "Transaction simulation failed: Error processing Instruction 0: custom program error: 0x17b7".to_string(),
578 data: RpcResponseErrorData::SendTransactionPreflightFailure(
579 RpcSimulateTransactionResult {
580 err: Some(TransactionError::InstructionError(0, InstructionError::Custom(6071))),
581 logs: None,
582 accounts: None,
583 units_consumed: None,
584 return_data: None,
585 inner_instructions: None,
586 replacement_blockhash: None,
587 }
588 )
589 }
590 )
591 }
592 );
593
594 assert_eq!(
595 err.to_anchor_error_code().unwrap(),
596 ErrorCode::UserOrderIdAlreadyInUse,
597 );
598 }
599
600 #[test]
601 fn account_type_sorting() {
602 let mut accounts = vec![
603 RemainingAccount::Perp {
604 pubkey: Pubkey::new_from_array([4_u8; 32]),
605 writable: false,
606 },
607 RemainingAccount::Oracle {
608 pubkey: Pubkey::new_from_array([2_u8; 32]),
609 },
610 RemainingAccount::Oracle {
611 pubkey: Pubkey::new_from_array([1_u8; 32]),
612 },
613 RemainingAccount::Spot {
614 pubkey: Pubkey::new_from_array([3_u8; 32]),
615 writable: true,
616 },
617 ];
618 accounts.sort();
619
620 assert_eq!(
621 accounts,
622 vec![
623 RemainingAccount::Oracle {
624 pubkey: Pubkey::new_from_array([1_u8; 32])
625 },
626 RemainingAccount::Oracle {
627 pubkey: Pubkey::new_from_array([2_u8; 32])
628 },
629 RemainingAccount::Spot {
630 pubkey: Pubkey::new_from_array([3_u8; 32]),
631 writable: true
632 },
633 RemainingAccount::Perp {
634 pubkey: Pubkey::new_from_array([4_u8; 32]),
635 writable: false
636 },
637 ]
638 )
639 }
640}