drift_rs/
lib.rs

1//! Drift SDK
2
3use std::{
4    borrow::Cow,
5    collections::BTreeSet,
6    sync::{Arc, RwLock},
7    time::Duration,
8};
9
10use anchor_lang::{AccountDeserialize, Discriminator, InstructionData};
11pub use drift_pubsub_client::PubsubClient;
12use futures_util::TryFutureExt;
13use log::debug;
14pub use solana_rpc_client::nonblocking::rpc_client::RpcClient;
15use solana_rpc_client_api::response::Response;
16use solana_sdk::{
17    account::Account,
18    clock::Slot,
19    commitment_config::CommitmentLevel,
20    compute_budget::ComputeBudgetInstruction,
21    hash::Hash,
22    instruction::{AccountMeta, Instruction},
23    message::{v0, Message, VersionedMessage},
24    signature::Signature,
25};
26pub use solana_sdk::{address_lookup_table::AddressLookupTableAccount, pubkey::Pubkey};
27
28use crate::{
29    account_map::AccountMap,
30    blockhash_subscriber::BlockhashSubscriber,
31    constants::{
32        derive_perp_market_account, derive_spot_market_account, state_account, MarketExt,
33        ProgramData, DEFAULT_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY,
34    },
35    drift_idl::traits::ToAccountMetas,
36    grpc::grpc_subscriber::{AccountFilter, DriftGrpcClient, GeyserSubscribeOpts},
37    marketmap::MarketMap,
38    oraclemap::{Oracle, OracleMap},
39    swift_order_subscriber::{SignedOrderInfo, SwiftOrderStream},
40    types::{
41        accounts::{PerpMarket, SpotMarket, State, User, UserStats},
42        DataAndSlot, MarketType, *,
43    },
44    utils::{get_http_url, get_ws_url},
45};
46pub use crate::{grpc::GrpcSubscribeOpts, types::Context, wallet::Wallet};
47
48// utils
49pub mod async_utils;
50pub mod ffi;
51pub mod math;
52pub mod memcmp;
53pub mod utils;
54pub mod wallet;
55
56// constants & types
57pub mod constants;
58pub mod drift_idl;
59pub mod types;
60
61// internal infra
62pub mod grpc;
63pub mod polled_account_subscriber;
64pub mod websocket_account_subscriber;
65pub mod websocket_program_account_subscriber;
66
67// subscribers
68pub mod auction_subscriber;
69pub mod blockhash_subscriber;
70pub mod event_subscriber;
71pub mod priority_fee_subscriber;
72pub mod swift_order_subscriber;
73
74pub mod jit_client;
75
76pub mod account_map;
77pub mod marketmap;
78pub mod oraclemap;
79pub mod slot_subscriber;
80pub mod usermap;
81
82#[cfg(feature = "dlob")]
83pub mod dlob;
84
85/// DriftClient
86///
87/// It is cheaply clone-able and consumers are encouraged to do so.
88/// It is not recommended to create multiple instances with `::new()` as this will not re-use underlying resources such
89/// as network connections or memory allocations
90///
91/// The client can be used as is to fetch data ad-hoc over RPC or subscribed to receive live updates (transparently)
92/// ```example(no_run)
93/// let client = DriftClient::new(
94///     Context::MainNet,
95///     RpcClient::new("https://rpc.example.com"),
96///     key_pair.into()
97/// ).await.expect("initializes");
98///
99/// // queries over RPC
100/// let sol_perp_price = client.oracle_price(MarketId::perp(0)).await;
101///
102/// // Subscribe to live program changes e.g oracle prices, spot/perp market changes, user accounts
103/// let markets = [MarketId::perp(0), MarketId::spot(2)];
104/// client.subscribe_markets(&markets).await.expect("subscribes");
105/// client.subscribe_oracles(&markets).await.expect("subscribes");
106///
107/// // after subscribing, uses Ws-backed local storage
108/// let sol_perp_price = client.oracle_price(MarketId::perp(0)).await;
109///
110/// client.unsubscribe();
111/// ```
112#[derive(Clone)]
113#[must_use]
114pub struct DriftClient {
115    pub context: Context,
116    backend: &'static DriftClientBackend,
117    pub wallet: Wallet,
118}
119
120impl DriftClient {
121    /// Create a new `DriftClient` instance
122    ///
123    /// * `context` - devnet or mainnet
124    /// * `rpc_client` - an RpcClient instance
125    /// * `wallet` - wallet to use for tx signing convenience
126    pub async fn new(context: Context, rpc_client: RpcClient, wallet: Wallet) -> SdkResult<Self> {
127        // check URL format here to fail early, otherwise happens at request time.
128        let _ = get_http_url(&rpc_client.url())?;
129        Ok(Self {
130            backend: Box::leak(Box::new(
131                DriftClientBackend::new(context, Arc::new(rpc_client)).await?,
132            )),
133            context,
134            wallet,
135        })
136    }
137
138    /// Starts background subscriptions for live blockhashes
139    ///
140    /// This is a no-op if already subscribed
141    pub async fn subscribe_blockhashes(&self) -> SdkResult<()> {
142        self.backend.subscribe_blockhashes().await
143    }
144
145    /// Starts background subscriptions for live market account updates
146    ///
147    /// * `markets` - list of markets to subscribe
148    ///
149    /// This is a no-op if already subscribed
150    pub async fn subscribe_markets(&self, markets: &[MarketId]) -> SdkResult<()> {
151        self.backend.subscribe_markets(markets).await
152    }
153
154    /// Subscribe to all spot and perp markets
155    ///
156    /// This is a no-op if already subscribed
157    pub async fn subscribe_all_markets(&self) -> SdkResult<()> {
158        let markets = self.get_all_market_ids();
159        self.backend.subscribe_markets(&markets).await
160    }
161
162    /// Subscribe to all spot markets
163    ///
164    /// This is a no-op if already subscribed
165    pub async fn subscribe_all_spot_markets(&self) -> SdkResult<()> {
166        let markets = self.get_all_spot_market_ids();
167        self.backend.subscribe_markets(&markets).await
168    }
169
170    /// Subscribe to all perp markets
171    ///
172    /// This is a no-op if already subscribed
173    pub async fn subscribe_all_perp_markets(&self) -> SdkResult<()> {
174        let markets = self.get_all_perp_market_ids();
175        self.backend.subscribe_markets(&markets).await
176    }
177
178    /// Starts background subscriptions for live oracle account updates by market
179    ///
180    /// * `markets` - list of markets to subscribe for oracle updates
181    ///
182    /// This is a no-op if already subscribed
183    pub async fn subscribe_oracles(&self, markets: &[MarketId]) -> SdkResult<()> {
184        self.backend.subscribe_oracles(markets).await
185    }
186
187    /// Subscribe to all oracles
188    ///
189    /// This is a no-op if already subscribed
190    pub async fn subscribe_all_oracles(&self) -> SdkResult<()> {
191        let markets = self.get_all_market_ids();
192        self.backend.subscribe_oracles(&markets).await
193    }
194
195    /// Subscribe to all spot market oracles
196    ///
197    /// This is a no-op if already subscribed
198    pub async fn subscribe_all_spot_oracles(&self) -> SdkResult<()> {
199        let markets = self.get_all_spot_market_ids();
200        self.backend.subscribe_oracles(&markets).await
201    }
202
203    /// Subscribe to all perp market oracles
204    ///
205    /// This is a no-op if already subscribed
206    pub async fn subscribe_all_perp_oracles(&self) -> SdkResult<()> {
207        let markets = self.get_all_perp_market_ids();
208        self.backend.subscribe_oracles(&markets).await
209    }
210
211    /// Subscribe to swift order feed(s) for given `markets`
212    ///
213    /// * `markets` - list of markets to watch for swift orders
214    ///
215    /// Returns a stream of swift orders
216    pub async fn subscribe_swift_orders(
217        &self,
218        markets: &[MarketId],
219    ) -> SdkResult<SwiftOrderStream> {
220        swift_order_subscriber::subscribe_swift_orders(self, markets).await
221    }
222
223    /// Returns the MarketIds for all active spot markets (ignores de-listed and settled markets)
224    ///
225    /// Useful for iterating over all spot markets
226    pub fn get_all_spot_market_ids(&self) -> Vec<MarketId> {
227        self.program_data()
228            .spot_market_configs()
229            .iter()
230            .filter_map(|m| match m.status {
231                MarketStatus::Settlement | MarketStatus::Delisted => {
232                    log::debug!("ignoring settled/delisted spot market: {}", m.market_index);
233                    None
234                }
235                _ => Some(MarketId::spot(m.market_index)),
236            })
237            .collect()
238    }
239
240    /// Returns the MarketIds for all active perp markets (ignores de-listed and settled markets)
241    ///
242    /// Useful for iterating over all perp markets
243    pub fn get_all_perp_market_ids(&self) -> Vec<MarketId> {
244        self.program_data()
245            .perp_market_configs()
246            .iter()
247            .filter_map(|m| match m.status {
248                MarketStatus::Settlement | MarketStatus::Delisted => {
249                    log::debug!("ignoring settled/delisted perp market: {}", m.market_index);
250                    None
251                }
252                _ => Some(MarketId::perp(m.market_index)),
253            })
254            .collect()
255    }
256
257    /// Returns the `MarketId`s for all active markets (ignores de-listed and settled markets)
258    ///
259    /// Useful for iterating over all markets
260    pub fn get_all_market_ids(&self) -> Vec<MarketId> {
261        let spot_markets = self.get_all_spot_market_ids();
262        let perp_markets = self.get_all_perp_market_ids();
263        spot_markets.into_iter().chain(perp_markets).collect()
264    }
265
266    /// Unsubscribe from network resources
267    /// Subsequent queries will pull from the network ad-hoc
268    ///
269    /// This is a no-op if not subscribed
270    pub async fn unsubscribe(&self) -> SdkResult<()> {
271        self.backend.unsubscribe().await
272    }
273
274    /// Return a handle to the inner RPC client
275    #[deprecated]
276    pub fn inner(&self) -> &RpcClient {
277        &self.backend.rpc_client
278    }
279
280    /// Return a handle to the inner RPC client
281    pub fn rpc(&self) -> Arc<RpcClient> {
282        self.backend.client()
283    }
284
285    /// Return a handle to the inner Ws client
286    pub fn ws(&self) -> Arc<PubsubClient> {
287        self.backend.ws()
288    }
289
290    /// Return on-chain program metadata
291    ///
292    /// Useful for inspecting market ids and config
293    pub fn program_data(&self) -> &ProgramData {
294        &self.backend.program_data
295    }
296
297    /// Get an account's open order by id
298    ///
299    /// * `account` - the drift user PDA
300    /// * `order_id` - order id to query
301    ///
302    /// Returns the `Order` if it exists
303    pub async fn get_order_by_id(
304        &self,
305        account: &Pubkey,
306        order_id: u32,
307    ) -> SdkResult<Option<Order>> {
308        let user = self.backend.get_user_account(account).await?;
309
310        Ok(user.orders.iter().find(|o| o.order_id == order_id).copied())
311    }
312
313    /// Get an account's open order by user assigned id
314    ///
315    /// * `account` - the drift user PDA
316    /// * `user_order_id` - user defined order id to query
317    ///
318    /// Returns the `Order` if it exists
319    pub async fn get_order_by_user_id(
320        &self,
321        account: &Pubkey,
322        user_order_id: u8,
323    ) -> SdkResult<Option<Order>> {
324        let user = self.backend.get_user_account(account).await?;
325
326        Ok(user
327            .orders
328            .iter()
329            .find(|o| o.user_order_id == user_order_id)
330            .copied())
331    }
332
333    /// Get the account's open orders
334    ///
335    /// * `account` - the drift user PDA
336    ///
337    /// Returns the list of open orders
338    pub async fn all_orders(&self, account: &Pubkey) -> SdkResult<Vec<Order>> {
339        let user = self.backend.get_user_account(account).await?;
340
341        Ok(user
342            .orders
343            .iter()
344            .filter(|o| o.status == OrderStatus::Open)
345            .copied()
346            .collect())
347    }
348
349    /// Get the account's unsettled positions
350    ///
351    /// * `account` - the drift user PDA
352    ///
353    /// Returns the list of unsettled positions
354    pub async fn unsettled_positions(&self, account: &Pubkey) -> SdkResult<Vec<PerpPosition>> {
355        let user = self.backend.get_user_account(account).await?;
356
357        Ok(user
358            .perp_positions
359            .iter()
360            .filter(|p| p.base_asset_amount == 0 && p.quote_asset_amount != 0)
361            .copied()
362            .collect())
363    }
364
365    /// Get all the account's open positions
366    ///
367    /// * `account` - the drift user PDA
368    pub async fn all_positions(
369        &self,
370        account: &Pubkey,
371    ) -> SdkResult<(Vec<SpotPosition>, Vec<PerpPosition>)> {
372        let user = self.backend.get_user_account(account).await?;
373
374        Ok((
375            user.spot_positions
376                .iter()
377                .filter(|s| !s.is_available())
378                .copied()
379                .collect(),
380            user.perp_positions
381                .iter()
382                .filter(|p| p.is_open_position())
383                .copied()
384                .collect(),
385        ))
386    }
387
388    /// Get a perp position by market
389    ///
390    /// * `account` - the drift user PDA
391    ///
392    /// Returns the position if it exists
393    pub async fn perp_position(
394        &self,
395        account: &Pubkey,
396        market_index: u16,
397    ) -> SdkResult<Option<PerpPosition>> {
398        let user = self.backend.get_user_account(account).await?;
399
400        Ok(user
401            .perp_positions
402            .iter()
403            .find(|p| p.market_index == market_index && !p.is_available())
404            .copied())
405    }
406
407    /// Get a spot position by market
408    ///
409    /// * `account` - the drift user PDA
410    ///
411    /// Returns the position if it exists
412    pub async fn spot_position(
413        &self,
414        account: &Pubkey,
415        market_index: u16,
416    ) -> SdkResult<Option<SpotPosition>> {
417        let user = self.backend.get_user_account(account).await?;
418
419        Ok(user
420            .spot_positions
421            .iter()
422            .find(|p| p.market_index == market_index && !p.is_available())
423            .copied())
424    }
425
426    /// Return the `DriftClient`'s wallet
427    pub fn wallet(&self) -> &Wallet {
428        &self.wallet
429    }
430
431    /// Get the user account data
432    /// Uses cached value if subscribed, falls back to network query
433    ///
434    /// * `account` - the drift user PDA (subaccount)
435    ///
436    /// Returns the deserialized account data (`User`)
437    pub async fn get_user_account(&self, account: &Pubkey) -> SdkResult<User> {
438        self.backend.get_user_account(account).await
439    }
440
441    /// Get a stats account
442    ///
443    /// Returns the deserialized account data (`UserStats`)
444    pub async fn get_user_stats(&self, authority: &Pubkey) -> SdkResult<UserStats> {
445        let user_stats_pubkey = Wallet::derive_stats_account(authority);
446        self.backend.get_account(&user_stats_pubkey).await
447    }
448
449    /// Get the latest recent_block_hash
450    /// uses latest cached if subscribed, otherwise falls back to network query
451    pub async fn get_latest_blockhash(&self) -> SdkResult<Hash> {
452        self.backend.get_latest_blockhash().await
453    }
454
455    /// Get some account value deserialized as T
456    /// Uses cached value if subscribed, falls back to network query
457    ///
458    /// * `account` - any onchain account
459    ///
460    /// Returns the deserialized account data (`User`)
461    pub async fn get_account_value<T: AccountDeserialize>(&self, account: &Pubkey) -> SdkResult<T> {
462        self.backend.get_account(account).await
463    }
464
465    /// Try to get `account` as `T` using latest local value
466    ///
467    /// requires account was previously subscribed too.
468    /// like `get_account_value` without async/network fallback
469    pub fn try_get_account<T: AccountDeserialize>(&self, account: &Pubkey) -> SdkResult<T> {
470        self.backend.try_get_account(account)
471    }
472
473    /// Try get the Drift `State` config account
474    /// It contains various exchange level config parameters
475    pub fn state_account(&self) -> SdkResult<State> {
476        self.backend.try_get_account(state_account())
477    }
478
479    /// Sign and send a tx to the network
480    ///
481    /// Returns the signature on success
482    pub async fn sign_and_send(&self, tx: VersionedMessage) -> SdkResult<Signature> {
483        let recent_block_hash = self.backend.get_latest_blockhash().await?;
484        self.backend
485            .sign_and_send(self.wallet(), tx, recent_block_hash)
486            .await
487            .map_err(|err| err.to_out_of_sol_error().unwrap_or(err))
488    }
489
490    /// Sign and send a tx to the network
491    ///
492    ///  * `recent_block_hash` - some block hash to use for tx signing, if not provided it will be automatically set
493    ///  * `config` - custom RPC config to use when submitting the tx
494    ///
495    /// Returns the signature on success
496    pub async fn sign_and_send_with_config(
497        &self,
498        tx: VersionedMessage,
499        recent_block_hash: Option<Hash>,
500        config: RpcSendTransactionConfig,
501    ) -> SdkResult<Signature> {
502        let recent_block_hash = match recent_block_hash {
503            Some(h) => h,
504            None => self.backend.get_latest_blockhash().await?,
505        };
506        self.backend
507            .sign_and_send_with_config(self.wallet(), tx, recent_block_hash, config)
508            .await
509            .map_err(|err| err.to_out_of_sol_error().unwrap_or(err))
510    }
511
512    /// Get spot market account
513    ///
514    /// * `market_index` - spot market index
515    ///
516    /// uses latest cached value if subscribed, otherwise falls back to network query
517    pub async fn get_spot_market_account(&self, market_index: u16) -> SdkResult<SpotMarket> {
518        match self
519            .backend
520            .try_get_spot_market_account_and_slot(market_index)
521        {
522            Some(market) => Ok(market.data),
523            None => {
524                debug!(target: "rpc", "fetch market: spot/{market_index}");
525                let market = derive_spot_market_account(market_index);
526                self.backend.get_account(&market).await
527            }
528        }
529    }
530
531    /// Get perp market account
532    ///
533    /// * `market_index` - perp market index
534    ///
535    /// uses latest cached value if subscribed, otherwise falls back to network query
536    pub async fn get_perp_market_account(&self, market_index: u16) -> SdkResult<PerpMarket> {
537        match self
538            .backend
539            .try_get_perp_market_account_and_slot(market_index)
540        {
541            Some(market) => Ok(market.data),
542            None => {
543                debug!(target: "rpc", "fetch market: perp/{market_index}");
544                let market = derive_perp_market_account(market_index);
545                self.backend.get_account(&market).await
546            }
547        }
548    }
549
550    /// Try to spot market account from cache
551    ///
552    /// * `market_index` - spot market index
553    ///
554    /// Returns error if not subscribed
555    pub fn try_get_spot_market_account(&self, market_index: u16) -> SdkResult<SpotMarket> {
556        if let Some(market) = self
557            .backend
558            .try_get_spot_market_account_and_slot(market_index)
559        {
560            Ok(market.data)
561        } else {
562            Err(SdkError::NoMarketData(MarketId::spot(market_index)))
563        }
564    }
565
566    /// Try to get perp market account from cache
567    ///
568    /// * `market_index` - spot market index
569    ///
570    /// Returns error if not subscribed
571    pub fn try_get_perp_market_account(&self, market_index: u16) -> SdkResult<PerpMarket> {
572        if let Some(market) = self
573            .backend
574            .try_get_perp_market_account_and_slot(market_index)
575        {
576            Ok(market.data)
577        } else {
578            Err(SdkError::NoMarketData(MarketId::perp(market_index)))
579        }
580    }
581
582    /// Lookup a market by symbol
583    ///
584    /// This operation is not free so lookups should be reused/cached by the caller
585    ///
586    /// Returns None if symbol does not map to any known market
587    pub fn market_lookup(&self, symbol: &str) -> Option<MarketId> {
588        if symbol.to_ascii_lowercase().ends_with("-perp") {
589            let markets = self.program_data().perp_market_configs();
590            markets
591                .iter()
592                .find(|m| m.symbol().eq_ignore_ascii_case(symbol))
593                .map(|m| MarketId::perp(m.market_index))
594        } else {
595            let markets = self.program_data().spot_market_configs();
596            markets
597                .iter()
598                .find(|m| m.symbol().eq_ignore_ascii_case(symbol))
599                .map(|m| MarketId::spot(m.market_index))
600        }
601    }
602
603    /// Get live oracle price for `market`
604    /// uses latest cached if subscribed, otherwise falls back to network query
605    pub async fn oracle_price(&self, market: MarketId) -> SdkResult<i64> {
606        self.backend.oracle_price(market).await
607    }
608
609    /// Initialize a transaction given a (sub)account address
610    ///
611    /// ```ignore
612    /// let tx = client
613    ///     .init_tx(&wallet.sub_account(3), false)
614    ///     .cancel_all_orders()
615    ///     .place_orders(...)
616    ///     .build();
617    /// ```
618    /// Returns a `TransactionBuilder` for composing the tx
619    pub async fn init_tx(
620        &self,
621        account: &Pubkey,
622        delegated: bool,
623    ) -> SdkResult<TransactionBuilder> {
624        let account_data = self.get_user_account(account).await?;
625        Ok(TransactionBuilder::new(
626            self.program_data(),
627            *account,
628            Cow::Owned(account_data),
629            delegated,
630        ))
631    }
632
633    pub async fn get_recent_priority_fees(
634        &self,
635        writable_markets: &[MarketId],
636        window: Option<usize>,
637    ) -> SdkResult<Vec<u64>> {
638        self.backend
639            .get_recent_priority_fees(writable_markets, window)
640            .await
641    }
642
643    /// Try get the latest oracle data for `market`
644    ///
645    /// If only the price is required use `oracle_price` instead
646    pub fn try_get_oracle_price_data_and_slot(&self, market: MarketId) -> Option<Oracle> {
647        self.backend.try_get_oracle_price_data_and_slot(market)
648    }
649
650    /// Get the latest oracle data for `market`
651    ///
652    /// If only the price is required use `oracle_price` instead
653    pub async fn get_oracle_price_data_and_slot(&self, market: MarketId) -> SdkResult<Oracle> {
654        self.backend.get_oracle(market).await
655    }
656
657    /// Subscribe to live WebSocket updates for some `account`
658    ///
659    /// The latest value may be retrieved with `client.get_account(..)`
660    /// ```example(no_run)
661    /// let subaccount = Wallet::derive_user_account(authority, 1);
662    /// client.subscribe_account(&subaccount).await;
663    /// let subaccount_data = client.get_account::<User>(&subaccount);
664    /// ```
665    pub async fn subscribe_account(&self, account: &Pubkey) -> SdkResult<()> {
666        self.backend.account_map.subscribe_account(account).await
667    }
668
669    /// Same as `subscribe_account` but uses RPC polling
670    pub async fn subscribe_account_polled(
671        &self,
672        account: &Pubkey,
673        interval: Duration,
674    ) -> SdkResult<()> {
675        self.backend
676            .account_map
677            .subscribe_account_polled(account, Some(interval))
678            .await
679    }
680
681    /// Unsubscribe from updates for `account`
682    pub fn unsubscribe_account(&self, account: &Pubkey) -> SdkResult<()> {
683        self.backend.account_map.unsubscribe_account(account);
684        Ok(())
685    }
686
687    /// Check IDL and libdrift_ffi_sys version
688    ///
689    /// panics if there's a mismatch
690    pub fn check_libs() -> SdkResult<()> {
691        let libdrift_version = ffi::check_ffi_version();
692        let idl_version = drift_idl::IDL_VERSION;
693
694        if libdrift_version != idl_version {
695            log::warn!(
696                "libdrift_ffi_sys: {} does not match IDL: {}",
697                libdrift_version,
698                drift_idl::IDL_VERSION
699            );
700            return Err(SdkError::LibDriftVersion);
701        }
702
703        Ok(())
704    }
705
706    /// Return a reference to the internal spot market map
707    #[cfg(feature = "unsafe_pub")]
708    pub fn spot_market_map(&self) -> Arc<MapOf<u16, DataAndSlot<SpotMarket>>> {
709        self.backend.spot_market_map.map()
710    }
711
712    /// Return a reference to the internal perp market map
713    #[cfg(feature = "unsafe_pub")]
714    pub fn perp_market_map(&self) -> Arc<MapOf<u16, DataAndSlot<PerpMarket>>> {
715        self.backend.perp_market_map.map()
716    }
717
718    /// Return a reference to the internal oracle map
719    #[cfg(feature = "unsafe_pub")]
720    pub fn oracle_map(&self) -> Arc<MapOf<(Pubkey, u8), Oracle>> {
721        self.backend.oracle_map.map()
722    }
723
724    /// Subscribe to all: markets, oracles, users, and slot updates over gRPC
725    ///
726    /// Updates are transparently handled by the `DriftClient` and calls to get User accounts, markets, oracles, etc.
727    /// will utilize the latest cached updates from the gRPC subscription.
728    ///
729    /// use `opts` to control what is _cached_ by the client. The gRPC connection will always subscribe
730    /// to all drift accounts regardless.
731    ///
732    /// * `endpoint` - the gRPC endpoint
733    /// * `x_token` - gRPC authentication X token
734    /// * `opts` - configure callbacks and caching
735    ///
736    pub async fn grpc_subscribe(
737        &self,
738        endpoint: String,
739        x_token: String,
740        opts: GrpcSubscribeOpts,
741    ) -> SdkResult<()> {
742        self.backend.grpc_subscribe(endpoint, x_token, opts).await
743    }
744
745    /// Unsubscribe the gRPC connection
746    pub fn grpc_unsubscribe(&self) {
747        self.backend.grpc_unsubscribe();
748    }
749
750    /// Return a reference to the internal backend
751    #[cfg(feature = "unsafe_pub")]
752    pub fn backend(&self) -> &'static DriftClientBackend {
753        self.backend
754    }
755}
756
757/// Provides the heavy-lifting and network facing features of the SDK
758/// It is intended to be a singleton
759pub struct DriftClientBackend {
760    rpc_client: Arc<RpcClient>,
761    pubsub_client: Arc<PubsubClient>,
762    program_data: ProgramData,
763    blockhash_subscriber: BlockhashSubscriber,
764    account_map: AccountMap,
765    perp_market_map: MarketMap<PerpMarket>,
766    spot_market_map: MarketMap<SpotMarket>,
767    oracle_map: OracleMap,
768    grpc_unsub: RwLock<Option<UnsubHandle>>,
769}
770impl DriftClientBackend {
771    /// Initialize a new `DriftClientBackend`
772    async fn new(context: Context, rpc_client: Arc<RpcClient>) -> SdkResult<Self> {
773        let pubsub_client =
774            Arc::new(PubsubClient::new(&get_ws_url(rpc_client.url().as_str())?).await?);
775
776        let perp_market_map =
777            MarketMap::<PerpMarket>::new(Arc::clone(&pubsub_client), rpc_client.commitment());
778        let spot_market_map =
779            MarketMap::<SpotMarket>::new(Arc::clone(&pubsub_client), rpc_client.commitment());
780
781        let lut_pubkeys = context.luts();
782
783        let (_, _, lut_accounts) = tokio::try_join!(
784            perp_market_map.sync(&rpc_client),
785            spot_market_map.sync(&rpc_client),
786            rpc_client
787                .get_multiple_accounts(lut_pubkeys)
788                .map_err(Into::into),
789        )?;
790
791        let lookup_tables = lut_pubkeys
792            .iter()
793            .zip(lut_accounts.iter())
794            .map(|(pubkey, account_data)| {
795                utils::deserialize_alt(*pubkey, account_data.as_ref().unwrap())
796                    .expect("LUT decodes")
797            })
798            .collect();
799
800        let mut all_oracles = Vec::<(MarketId, Pubkey, OracleSource)>::with_capacity(
801            perp_market_map.len() + spot_market_map.len(),
802        );
803        for market_oracle_info in perp_market_map
804            .oracles()
805            .iter()
806            .chain(spot_market_map.oracles().iter())
807        {
808            all_oracles.push(*market_oracle_info);
809        }
810
811        let oracle_map = OracleMap::new(
812            Arc::clone(&pubsub_client),
813            all_oracles.as_slice(),
814            rpc_client.commitment(),
815        );
816        let account_map = AccountMap::new(
817            Arc::clone(&pubsub_client),
818            Arc::clone(&rpc_client),
819            rpc_client.commitment(),
820        );
821        account_map
822            .subscribe_account_polled(state_account(), Some(Duration::from_secs(180)))
823            .await?;
824
825        Ok(Self {
826            rpc_client: Arc::clone(&rpc_client),
827            pubsub_client,
828            blockhash_subscriber: BlockhashSubscriber::new(Duration::from_secs(2), rpc_client),
829            program_data: ProgramData::new(
830                spot_market_map.values(),
831                perp_market_map.values(),
832                lookup_tables,
833            ),
834            account_map,
835            perp_market_map,
836            spot_market_map,
837            oracle_map,
838            grpc_unsub: RwLock::default(),
839        })
840    }
841
842    /// Returns true if `DriftClientBackend` is subscribed via gRPC
843    pub fn is_grpc_subscribed(&self) -> bool {
844        let unsub = self.grpc_unsub.read().unwrap();
845        unsub.is_some()
846    }
847
848    /// Start subscription for latest block hashes
849    async fn subscribe_blockhashes(&self) -> SdkResult<()> {
850        self.blockhash_subscriber.subscribe();
851        Ok(())
852    }
853
854    /// Start subscriptions for market accounts
855    async fn subscribe_markets(&self, markets: &[MarketId]) -> SdkResult<()> {
856        if self.is_grpc_subscribed() {
857            log::info!("already subscribed markets via gRPC");
858            return Err(SdkError::AlreadySubscribed);
859        }
860
861        let (perps, spot) = markets
862            .iter()
863            .partition::<Vec<MarketId>, _>(|x| x.is_perp());
864        let _ = tokio::try_join!(
865            self.perp_market_map.subscribe(&perps),
866            self.spot_market_map.subscribe(&spot),
867        )?;
868
869        Ok(())
870    }
871
872    /// Start subscriptions for market oracle accounts
873    async fn subscribe_oracles(&self, markets: &[MarketId]) -> SdkResult<()> {
874        if self.is_grpc_subscribed() {
875            log::info!("already subscribed oracles via gRPC");
876            return Err(SdkError::AlreadySubscribed);
877        }
878
879        self.oracle_map.subscribe(markets).await
880    }
881
882    /// Subscribe to all: markets, oracles, and slot updates over gRPC
883    async fn grpc_subscribe(
884        &self,
885        endpoint: String,
886        x_token: String,
887        opts: GrpcSubscribeOpts,
888    ) -> SdkResult<()> {
889        let mut grpc =
890            DriftGrpcClient::new(endpoint, x_token).grpc_connection_opts(opts.connection_opts);
891
892        grpc.on_account(
893            AccountFilter::partial().with_discriminator(SpotMarket::DISCRIMINATOR),
894            self.spot_market_map.on_account_fn(),
895        );
896        grpc.on_account(
897            AccountFilter::partial().with_discriminator(PerpMarket::DISCRIMINATOR),
898            self.perp_market_map.on_account_fn(),
899        );
900        let oracles: Vec<Pubkey> = self
901            .oracle_map
902            .oracle_by_market
903            .iter()
904            .map(|x| x.1 .0)
905            .collect();
906        grpc.on_account(
907            AccountFilter::partial().with_accounts(oracles.into_iter()),
908            self.oracle_map.on_account_fn(),
909        );
910
911        if opts.usermap {
912            grpc.on_account(
913                AccountFilter::partial().with_discriminator(User::DISCRIMINATOR),
914                self.account_map.on_account_fn(),
915            );
916        } else {
917            // when usermap is on, the custom accounts are already included
918            // usermap off: subscribe to custom `User` accounts
919            grpc.on_account(
920                AccountFilter::full()
921                    .with_discriminator(User::DISCRIMINATOR)
922                    .with_accounts(opts.user_accounts.into_iter()),
923                self.account_map.on_account_fn(),
924            );
925        }
926
927        if opts.user_stats_map {
928            grpc.on_account(
929                AccountFilter::partial().with_discriminator(UserStats::DISCRIMINATOR),
930                self.account_map.on_account_fn(),
931            );
932        }
933
934        // set custom callbacks
935        if let Some((filter, on_account)) = opts.on_account {
936            grpc.on_account(filter, on_account);
937        }
938        if let Some(f) = opts.on_slot {
939            grpc.on_slot(f);
940        }
941
942        // start subscription
943        let grpc_unsub = grpc
944            .subscribe(CommitmentLevel::Confirmed, GeyserSubscribeOpts::default())
945            .await
946            .map_err(SdkError::Grpc)?;
947
948        let mut unsub = self.grpc_unsub.write().unwrap();
949        let _ = unsub.insert(grpc_unsub);
950
951        Ok(())
952    }
953
954    /// Unsubscribe the gRPC connection
955    fn grpc_unsubscribe(&self) {
956        let mut guard = self.grpc_unsub.write().unwrap();
957        let unsub = guard.take();
958        unsub.map(|u| u.send(()));
959    }
960
961    /// End subscriptions to live program data
962    async fn unsubscribe(&self) -> SdkResult<()> {
963        self.blockhash_subscriber.unsubscribe();
964        self.perp_market_map.unsubscribe_all()?;
965        self.spot_market_map.unsubscribe_all()?;
966        self.account_map.unsubscribe_account(state_account());
967        self.oracle_map.unsubscribe_all()
968    }
969
970    pub fn try_get_perp_market_account_and_slot(
971        &self,
972        market_index: u16,
973    ) -> Option<DataAndSlot<PerpMarket>> {
974        if self.is_grpc_subscribed() || self.perp_market_map.is_subscribed(market_index) {
975            self.perp_market_map.get(&market_index)
976        } else {
977            None
978        }
979    }
980
981    pub fn try_get_spot_market_account_and_slot(
982        &self,
983        market_index: u16,
984    ) -> Option<DataAndSlot<SpotMarket>> {
985        if self.is_grpc_subscribed() || self.spot_market_map.is_subscribed(market_index) {
986            self.spot_market_map.get(&market_index)
987        } else {
988            None
989        }
990    }
991
992    pub fn try_get_oracle_price_data_and_slot(&self, market: MarketId) -> Option<Oracle> {
993        self.oracle_map.get_by_market(&market)
994    }
995
996    /// Same as `try_get_oracle_price_data_and_slot` but checks the oracle pubkey has not changed
997    /// this can be useful if the oracle address changes in the program
998    pub fn try_get_oracle_price_data_and_slot_checked(&self, market: MarketId) -> Option<Oracle> {
999        let current_oracle = self
1000            .oracle_map
1001            .get_by_market(&market)
1002            .expect("oracle")
1003            .pubkey;
1004
1005        let program_configured_oracle = if market.is_perp() {
1006            let market = self.try_get_perp_market_account_and_slot(market.index())?;
1007            market.data.amm.oracle
1008        } else {
1009            let market = self.try_get_spot_market_account_and_slot(market.index())?;
1010            market.data.oracle
1011        };
1012
1013        if program_configured_oracle != current_oracle {
1014            panic!("invalid oracle: {}", market.index());
1015        }
1016
1017        self.try_get_oracle_price_data_and_slot(market)
1018    }
1019
1020    /// Return a handle to the inner RPC client
1021    fn client(&self) -> Arc<RpcClient> {
1022        Arc::clone(&self.rpc_client)
1023    }
1024
1025    /// Return a handle to the inner RPC client
1026    fn ws(&self) -> Arc<PubsubClient> {
1027        Arc::clone(&self.pubsub_client)
1028    }
1029
1030    /// Get recent tx priority fees
1031    ///
1032    /// * `writable_markets` - markets to consider for write locks
1033    /// * `window` - # of slots to include in the fee calculation
1034    async fn get_recent_priority_fees(
1035        &self,
1036        writable_markets: &[MarketId],
1037        window: Option<usize>,
1038    ) -> SdkResult<Vec<u64>> {
1039        let addresses: Vec<Pubkey> = writable_markets
1040            .iter()
1041            .filter_map(|x| match x.kind() {
1042                MarketType::Spot => self
1043                    .program_data
1044                    .spot_market_config_by_index(x.index())
1045                    .map(|x| x.pubkey),
1046                MarketType::Perp => self
1047                    .program_data
1048                    .perp_market_config_by_index(x.index())
1049                    .map(|x| x.pubkey),
1050            })
1051            .collect();
1052
1053        let response = self
1054            .rpc_client
1055            .get_recent_prioritization_fees(addresses.as_slice())
1056            .await?;
1057        let window = window.unwrap_or(5).max(1);
1058        let fees = response
1059            .iter()
1060            .take(window)
1061            .map(|x| x.prioritization_fee)
1062            .collect();
1063
1064        Ok(fees)
1065    }
1066
1067    /// Fetch `account` as an Anchor account type `T`
1068    pub async fn get_account<T: AccountDeserialize>(&self, account: &Pubkey) -> SdkResult<T> {
1069        if let Some(value) = self.account_map.account_data(account) {
1070            Ok(value)
1071        } else {
1072            let account_data = self.rpc_client.get_account_data(account).await?;
1073            if account_data.is_empty() {
1074                return Err(SdkError::NoAccountData(*account));
1075            }
1076            T::try_deserialize(&mut account_data.as_slice())
1077                .map_err(|err| SdkError::Anchor(Box::new(err)))
1078        }
1079    }
1080
1081    /// Fetch `account` as an Anchor account type `T` along with the retrieved slot
1082    pub async fn get_account_with_slot<T: AccountDeserialize>(
1083        &self,
1084        account: &Pubkey,
1085    ) -> SdkResult<DataAndSlot<T>> {
1086        if let Some(value) = self.account_map.account_data_and_slot(account) {
1087            Ok(value)
1088        } else {
1089            let (account, slot) = self.get_account_with_slot_raw(account).await?;
1090            Ok(DataAndSlot {
1091                slot,
1092                data: T::try_deserialize(&mut account.data.as_slice())
1093                    .map_err(|err| SdkError::Anchor(Box::new(err)))?,
1094            })
1095        }
1096    }
1097
1098    /// Fetch `account` as a drift User account
1099    ///
1100    /// uses latest cached if subscribed, otherwise falls back to network query
1101    pub async fn get_user_account(&self, account: &Pubkey) -> SdkResult<User> {
1102        self.get_account(account).await
1103    }
1104
1105    /// Try to fetch `account` as `T` using latest local value
1106    /// requires account was previously subscribed too.
1107    pub fn try_get_account<T: AccountDeserialize>(&self, account: &Pubkey) -> SdkResult<T> {
1108        self.account_map
1109            .account_data(account)
1110            .ok_or_else(|| SdkError::NoAccountData(*account))
1111    }
1112
1113    /// Returns latest blockhash
1114    ///
1115    /// uses latest cached if subscribed, otherwise falls back to network query
1116    pub async fn get_latest_blockhash(&self) -> SdkResult<Hash> {
1117        match self.blockhash_subscriber.get_latest_blockhash() {
1118            Some(hash) => Ok(hash),
1119            None => self
1120                .rpc_client
1121                .get_latest_blockhash()
1122                .await
1123                .map_err(SdkError::Rpc),
1124        }
1125    }
1126
1127    /// Sign and send a tx to the network
1128    ///
1129    /// Returns the signature on success
1130    pub async fn sign_and_send(
1131        &self,
1132        wallet: &Wallet,
1133        tx: VersionedMessage,
1134        recent_block_hash: Hash,
1135    ) -> SdkResult<Signature> {
1136        let tx = wallet.sign_tx(tx, recent_block_hash)?;
1137        self.rpc_client
1138            .send_transaction(&tx)
1139            .await
1140            .map_err(Into::into)
1141    }
1142
1143    /// Sign and send a tx to the network with custom send config
1144    /// allows setting commitment level, retries, etc.
1145    ///
1146    /// Returns the signature on success
1147    pub async fn sign_and_send_with_config(
1148        &self,
1149        wallet: &Wallet,
1150        tx: VersionedMessage,
1151        recent_block_hash: Hash,
1152        config: RpcSendTransactionConfig,
1153    ) -> SdkResult<Signature> {
1154        let tx = wallet.sign_tx(tx, recent_block_hash)?;
1155        self.rpc_client
1156            .send_transaction_with_config(&tx, config)
1157            .await
1158            .map_err(Into::into)
1159    }
1160
1161    /// Fetch the live oracle price for `market`
1162    ///
1163    /// Uses latest local value from an `OracleMap` if subscribed, falls back to network query
1164    pub async fn oracle_price(&self, market: MarketId) -> SdkResult<i64> {
1165        self.get_oracle(market).await.map(|o| o.data.price)
1166    }
1167
1168    /// Fetch live oracle data for `market`
1169    ///
1170    /// Uses latest local value from an `OracleMap` if subscribed, falls back to network query
1171    pub async fn get_oracle(&self, market: MarketId) -> SdkResult<Oracle> {
1172        if self.oracle_map.is_subscribed(&market) {
1173            Ok(self
1174                .try_get_oracle_price_data_and_slot(market)
1175                .expect("oracle exists"))
1176        } else {
1177            debug!(target: "rpc", "fetch oracle account: {market:?}");
1178            let (oracle, oracle_source) = match market.kind() {
1179                MarketType::Perp => {
1180                    let market = self
1181                        .program_data
1182                        .perp_market_config_by_index(market.index())
1183                        .ok_or(SdkError::InvalidOracle)?;
1184                    (market.amm.oracle, market.amm.oracle_source)
1185                }
1186                MarketType::Spot => {
1187                    let market = self
1188                        .program_data
1189                        .spot_market_config_by_index(market.index())
1190                        .ok_or(SdkError::InvalidOracle)?;
1191                    (market.oracle, market.oracle_source)
1192                }
1193            };
1194            let (account_data, slot) = self.get_account_with_slot_raw(&oracle).await?;
1195            let oracle_price_data =
1196                ffi::get_oracle_price(oracle_source, &mut (oracle, account_data.clone()), slot)?;
1197
1198            Ok(Oracle {
1199                pubkey: oracle,
1200                source: oracle_source,
1201                slot,
1202                data: oracle_price_data,
1203                raw: account_data.data,
1204            })
1205        }
1206    }
1207
1208    /// Get account via rpc along with retrieved slot number
1209    async fn get_account_with_slot_raw(&self, pubkey: &Pubkey) -> SdkResult<(Account, Slot)> {
1210        match self
1211            .rpc_client
1212            .get_account_with_commitment(pubkey, self.rpc_client.commitment())
1213            .await
1214        {
1215            Ok(Response {
1216                context,
1217                value: Some(account),
1218            }) => Ok((account, context.slot)),
1219            Ok(Response {
1220                context: _,
1221                value: None,
1222            }) => Err(SdkError::InvalidAccount),
1223            Err(err) => Err(err.into()),
1224        }
1225    }
1226
1227    #[cfg(feature = "unsafe_pub")]
1228    pub fn account_map(&self) -> &AccountMap {
1229        &self.account_map
1230    }
1231
1232    #[cfg(feature = "unsafe_pub")]
1233    pub fn perp_market_map(&self) -> &MarketMap<PerpMarket> {
1234        &self.perp_market_map
1235    }
1236
1237    #[cfg(feature = "unsafe_pub")]
1238    pub fn spot_market_map(&self) -> &MarketMap<SpotMarket> {
1239        &self.spot_market_map
1240    }
1241
1242    #[cfg(feature = "unsafe_pub")]
1243    pub fn oracle_map(&self) -> &OracleMap {
1244        &self.oracle_map
1245    }
1246}
1247
1248/// Configure markets as forced for inclusion by `TransactionBuilder`
1249///
1250/// In contrast, without this Transactions are built using the latest known state of
1251/// users's open positions and orders, which can result in race conditions when executed onchain.
1252#[derive(Default)]
1253struct ForceMarkets {
1254    /// markets must include as readable
1255    readable: Vec<MarketId>,
1256    /// markets must include as writeable
1257    writeable: Vec<MarketId>,
1258}
1259
1260impl ForceMarkets {
1261    /// Set given `markets` as readable, enforcing there inclusion in a final Tx
1262    pub fn with_readable(&mut self, markets: &[MarketId]) -> &mut Self {
1263        self.readable = markets.to_vec();
1264        self
1265    }
1266    /// Set given `markets` as writeable, enforcing there inclusion in a final Tx
1267    pub fn with_writeable(&mut self, markets: &[MarketId]) -> &mut Self {
1268        self.writeable = markets.to_vec();
1269        self
1270    }
1271}
1272
1273/// Composable Tx builder for Drift program
1274///
1275/// Alternatively, use `DriftClient::init_tx` for simpler instantiation.
1276///
1277/// ```example(no_run)
1278/// use drift_rs::{types::Context, TransactionBuilder, Wallet};
1279///
1280/// let wallet = Wallet::from_seed_bs58("seed");
1281/// let client = DriftClient::new(Context::DevNet, "api.example.com", wallet).await.unwrap();
1282/// let account_data = client.get_account(wallet.default_sub_account()).await.unwrap();
1283///
1284/// let tx = TransactionBuilder::new(client.program_data, wallet.default_sub_account(), account_data.into())
1285///     .cancel_all_orders()
1286///     .place_orders(&[
1287///         NewOrder::default().build(),
1288///         NewOrder::default().build(),
1289///     ])
1290///     .legacy()
1291///     .build();
1292///
1293/// let signature = client.sign_and_send(tx, &wallet).await?;
1294/// ```
1295///
1296pub struct TransactionBuilder<'a> {
1297    /// contextual on-chain program data
1298    program_data: &'a ProgramData,
1299    /// sub-account data
1300    account_data: Cow<'a, User>,
1301    /// the drift sub-account address
1302    sub_account: Pubkey,
1303    /// either account authority or account delegate
1304    authority: Pubkey,
1305    /// ordered list of instructions
1306    ixs: Vec<Instruction>,
1307    /// use legacy transaction mode
1308    legacy: bool,
1309    /// Tx lookup tables (v0 only)
1310    lookup_tables: Vec<AddressLookupTableAccount>,
1311    /// some markets forced to include in the tx accounts list
1312    force_markets: ForceMarkets,
1313}
1314
1315impl<'a> TransactionBuilder<'a> {
1316    /// Initialize a new `TransactionBuilder` for default signer
1317    ///
1318    /// * `program_data` - program data from chain
1319    /// * `sub_account` - drift sub-account address
1320    /// * `user` - drift sub-account data
1321    /// * `delegated` - set true to build tx for delegated signing
1322    pub fn new<'b>(
1323        program_data: &'b ProgramData,
1324        sub_account: Pubkey,
1325        user: Cow<'b, User>,
1326        delegated: bool,
1327    ) -> Self
1328    where
1329        'b: 'a,
1330    {
1331        Self {
1332            authority: if delegated {
1333                user.delegate
1334            } else {
1335                user.authority
1336            },
1337            program_data,
1338            account_data: user,
1339            sub_account,
1340            ixs: Default::default(),
1341            lookup_tables: program_data.lookup_tables.to_vec(),
1342            legacy: false,
1343            force_markets: Default::default(),
1344        }
1345    }
1346    /// force given `markets` to be included in the final tx accounts list (ensure to call before building ixs)
1347    pub fn force_include_markets(&mut self, readable: &[MarketId], writeable: &[MarketId]) {
1348        self.force_markets.with_readable(readable);
1349        self.force_markets.with_writeable(writeable);
1350    }
1351    /// Use legacy tx mode
1352    pub fn legacy(mut self) -> Self {
1353        self.legacy = true;
1354        self
1355    }
1356    /// Extend the tx lookup tables (always includes the defacto drift LUTs)
1357    pub fn lookup_tables(mut self, lookup_tables: &[AddressLookupTableAccount]) -> Self {
1358        self.lookup_tables = lookup_tables.to_vec();
1359        self.lookup_tables
1360            .extend(self.program_data.lookup_tables.iter().cloned());
1361
1362        self
1363    }
1364    /// Set the priority fee of the tx
1365    ///
1366    /// * `microlamports_per_cu` - the price per unit of compute in µ-lamports
1367    pub fn with_priority_fee(mut self, microlamports_per_cu: u64, cu_limit: Option<u32>) -> Self {
1368        let cu_limit_ix = ComputeBudgetInstruction::set_compute_unit_price(microlamports_per_cu);
1369        self.ixs.insert(0, cu_limit_ix);
1370        if let Some(cu_limit) = cu_limit {
1371            let cu_price_ix = ComputeBudgetInstruction::set_compute_unit_limit(cu_limit);
1372            self.ixs.insert(1, cu_price_ix);
1373        }
1374
1375        self
1376    }
1377    /// Append an ix to the Tx
1378    pub fn add_ix(mut self, ix: Instruction) -> Self {
1379        self.ixs.push(ix);
1380        self
1381    }
1382
1383    /// Deposit collateral into account
1384    pub fn deposit(
1385        mut self,
1386        amount: u64,
1387        spot_market_index: u16,
1388        user_token_account: Pubkey,
1389        reduce_only: Option<bool>,
1390    ) -> Self {
1391        let accounts = build_accounts(
1392            self.program_data,
1393            types::accounts::Deposit {
1394                state: *state_account(),
1395                user: self.sub_account,
1396                user_stats: Wallet::derive_stats_account(&self.authority),
1397                authority: self.authority,
1398                spot_market_vault: constants::derive_spot_market_vault(spot_market_index),
1399                user_token_account,
1400                token_program: constants::TOKEN_PROGRAM_ID,
1401            },
1402            &[self.account_data.as_ref()],
1403            self.force_markets.readable.iter(),
1404            [MarketId::spot(spot_market_index)].iter(),
1405        );
1406
1407        let ix = Instruction {
1408            program_id: constants::PROGRAM_ID,
1409            accounts,
1410            data: InstructionData::data(&drift_idl::instructions::Deposit {
1411                market_index: spot_market_index,
1412                amount,
1413                reduce_only: reduce_only.unwrap_or(false),
1414            }),
1415        };
1416
1417        self.ixs.push(ix);
1418
1419        self
1420    }
1421
1422    /// Withdraw collateral from the account
1423    pub fn withdraw(
1424        mut self,
1425        amount: u64,
1426        spot_market_index: u16,
1427        user_token_account: Pubkey,
1428        reduce_only: Option<bool>,
1429    ) -> Self {
1430        let accounts = build_accounts(
1431            self.program_data,
1432            types::accounts::Withdraw {
1433                state: *state_account(),
1434                user: self.sub_account,
1435                user_stats: Wallet::derive_stats_account(&self.authority),
1436                authority: self.authority,
1437                spot_market_vault: constants::derive_spot_market_vault(spot_market_index),
1438                user_token_account,
1439                drift_signer: constants::derive_drift_signer(),
1440                token_program: constants::TOKEN_PROGRAM_ID,
1441            },
1442            &[self.account_data.as_ref()],
1443            self.force_markets.readable.iter(),
1444            [MarketId::spot(spot_market_index)]
1445                .iter()
1446                .chain(self.force_markets.writeable.iter()),
1447        );
1448
1449        let ix = Instruction {
1450            program_id: constants::PROGRAM_ID,
1451            accounts,
1452            data: InstructionData::data(&drift_idl::instructions::Withdraw {
1453                market_index: spot_market_index,
1454                amount,
1455                reduce_only: reduce_only.unwrap_or(false),
1456            }),
1457        };
1458
1459        self.ixs.push(ix);
1460
1461        self
1462    }
1463
1464    /// Place new orders for account
1465    ///
1466    /// * `orders` list of orders to place
1467    pub fn place_orders(mut self, orders: Vec<OrderParams>) -> Self {
1468        let mut readable_accounts: Vec<MarketId> = orders
1469            .iter()
1470            .map(|o| (o.market_index, o.market_type).into())
1471            .collect();
1472        readable_accounts.extend(&self.force_markets.readable);
1473
1474        let accounts = build_accounts(
1475            self.program_data,
1476            types::accounts::PlaceOrders {
1477                state: *state_account(),
1478                authority: self.authority,
1479                user: self.sub_account,
1480            },
1481            &[self.account_data.as_ref()],
1482            readable_accounts.iter(),
1483            self.force_markets.writeable.iter(),
1484        );
1485
1486        let ix = Instruction {
1487            program_id: constants::PROGRAM_ID,
1488            accounts,
1489            data: InstructionData::data(&drift_idl::instructions::PlaceOrders { params: orders }),
1490        };
1491
1492        self.ixs.push(ix);
1493
1494        self
1495    }
1496
1497    /// Cancel all orders for account
1498    pub fn cancel_all_orders(mut self) -> Self {
1499        let accounts = build_accounts(
1500            self.program_data,
1501            types::accounts::CancelOrder {
1502                state: *state_account(),
1503                authority: self.authority,
1504                user: self.sub_account,
1505            },
1506            &[self.account_data.as_ref()],
1507            self.force_markets.readable.iter(),
1508            self.force_markets.writeable.iter(),
1509        );
1510
1511        let ix = Instruction {
1512            program_id: constants::PROGRAM_ID,
1513            accounts,
1514            data: InstructionData::data(&drift_idl::instructions::CancelOrders {
1515                market_index: None,
1516                market_type: None,
1517                direction: None,
1518            }),
1519        };
1520        self.ixs.push(ix);
1521
1522        self
1523    }
1524
1525    /// Cancel account's orders matching some criteria
1526    ///
1527    /// * `market` - tuple of market index and type (spot or perp)
1528    /// * `direction` - long or short
1529    pub fn cancel_orders(
1530        mut self,
1531        market: (u16, MarketType),
1532        direction: Option<PositionDirection>,
1533    ) -> Self {
1534        let (idx, r#type) = market;
1535        let accounts = build_accounts(
1536            self.program_data,
1537            types::accounts::CancelOrder {
1538                state: *state_account(),
1539                authority: self.authority,
1540                user: self.sub_account,
1541            },
1542            &[self.account_data.as_ref()],
1543            [(idx, r#type).into()]
1544                .iter()
1545                .chain(self.force_markets.readable.iter()),
1546            self.force_markets.writeable.iter(),
1547        );
1548
1549        let ix = Instruction {
1550            program_id: constants::PROGRAM_ID,
1551            accounts,
1552            data: InstructionData::data(&drift_idl::instructions::CancelOrders {
1553                market_index: Some(idx),
1554                market_type: Some(r#type),
1555                direction,
1556            }),
1557        };
1558        self.ixs.push(ix);
1559
1560        self
1561    }
1562
1563    /// Cancel orders given ids
1564    pub fn cancel_orders_by_id(mut self, order_ids: Vec<u32>) -> Self {
1565        let accounts = build_accounts(
1566            self.program_data,
1567            types::accounts::CancelOrder {
1568                state: *state_account(),
1569                authority: self.authority,
1570                user: self.sub_account,
1571            },
1572            &[self.account_data.as_ref()],
1573            self.force_markets.readable.iter(),
1574            self.force_markets.writeable.iter(),
1575        );
1576
1577        let ix = Instruction {
1578            program_id: constants::PROGRAM_ID,
1579            accounts,
1580            data: InstructionData::data(&drift_idl::instructions::CancelOrdersByIds { order_ids }),
1581        };
1582        self.ixs.push(ix);
1583
1584        self
1585    }
1586
1587    /// Cancel orders by given _user_ ids
1588    pub fn cancel_orders_by_user_id(mut self, user_order_ids: Vec<u8>) -> Self {
1589        let accounts = build_accounts(
1590            self.program_data,
1591            types::accounts::CancelOrder {
1592                state: *state_account(),
1593                authority: self.authority,
1594                user: self.sub_account,
1595            },
1596            &[self.account_data.as_ref()],
1597            self.force_markets.readable.iter(),
1598            self.force_markets.writeable.iter(),
1599        );
1600
1601        for user_order_id in user_order_ids {
1602            let ix = Instruction {
1603                program_id: constants::PROGRAM_ID,
1604                accounts: accounts.clone(),
1605                data: InstructionData::data(&drift_idl::instructions::CancelOrderByUserId {
1606                    user_order_id,
1607                }),
1608            };
1609            self.ixs.push(ix);
1610        }
1611
1612        self
1613    }
1614
1615    /// Modify existing order(s) by order id
1616    pub fn modify_orders(mut self, orders: &[(u32, ModifyOrderParams)]) -> Self {
1617        let accounts = build_accounts(
1618            self.program_data,
1619            types::accounts::ModifyOrder {
1620                state: *state_account(),
1621                authority: self.authority,
1622                user: self.sub_account,
1623            },
1624            &[self.account_data.as_ref()],
1625            self.force_markets.readable.iter(),
1626            self.force_markets.writeable.iter(),
1627        );
1628
1629        for (order_id, params) in orders {
1630            let ix = Instruction {
1631                program_id: constants::PROGRAM_ID,
1632                accounts: accounts.clone(),
1633                data: InstructionData::data(&drift_idl::instructions::ModifyOrder {
1634                    order_id: Some(*order_id),
1635                    modify_order_params: *params,
1636                }),
1637            };
1638            self.ixs.push(ix);
1639        }
1640
1641        self
1642    }
1643
1644    /// Modify existing order(s) by user order id
1645    pub fn modify_orders_by_user_id(mut self, orders: &[(u8, ModifyOrderParams)]) -> Self {
1646        let accounts = build_accounts(
1647            self.program_data,
1648            types::accounts::PlaceOrders {
1649                state: *state_account(),
1650                authority: self.authority,
1651                user: self.sub_account,
1652            },
1653            &[self.account_data.as_ref()],
1654            self.force_markets.readable.iter(),
1655            self.force_markets.writeable.iter(),
1656        );
1657
1658        for (user_order_id, params) in orders {
1659            let ix = Instruction {
1660                program_id: constants::PROGRAM_ID,
1661                accounts: accounts.clone(),
1662                data: InstructionData::data(&drift_idl::instructions::ModifyOrderByUserId {
1663                    user_order_id: *user_order_id,
1664                    modify_order_params: *params,
1665                }),
1666            };
1667            self.ixs.push(ix);
1668        }
1669
1670        self
1671    }
1672
1673    /// Add a place and make instruction
1674    ///
1675    /// * `order` - the order to place
1676    /// * `taker_info` - taker account address and data
1677    /// * `taker_order_id` - the id of the taker's order to match with
1678    /// * `referrer` - pubkey of the taker's referrer account, if any
1679    /// * `fulfillment_type` - type of fill for spot orders, ignored for perp orders
1680    pub fn place_and_make(
1681        mut self,
1682        order: OrderParams,
1683        taker_info: &(Pubkey, User),
1684        taker_order_id: u32,
1685        referrer: Option<Pubkey>,
1686        fulfillment_type: Option<SpotFulfillmentType>,
1687    ) -> Self {
1688        let (taker, taker_account) = taker_info;
1689        let is_perp = order.market_type == MarketType::Perp;
1690        let perp_writable = [MarketId::perp(order.market_index)];
1691        let spot_writable = [MarketId::spot(order.market_index), MarketId::QUOTE_SPOT];
1692        let mut accounts = build_accounts(
1693            self.program_data,
1694            types::accounts::PlaceAndMakePerpOrder {
1695                state: *state_account(),
1696                authority: self.authority,
1697                user: self.sub_account,
1698                user_stats: Wallet::derive_stats_account(&self.authority),
1699                taker: *taker,
1700                taker_stats: Wallet::derive_stats_account(taker),
1701            },
1702            &[self.account_data.as_ref(), taker_account],
1703            self.force_markets.readable.iter(),
1704            if is_perp {
1705                perp_writable.iter()
1706            } else {
1707                spot_writable.iter()
1708            }
1709            .chain(self.force_markets.writeable.iter()),
1710        );
1711
1712        if let Some(referrer) = referrer {
1713            accounts.push(AccountMeta::new(
1714                Wallet::derive_stats_account(&referrer),
1715                false,
1716            ));
1717            accounts.push(AccountMeta::new(referrer, false));
1718        }
1719
1720        let ix = if order.market_type == MarketType::Perp {
1721            Instruction {
1722                program_id: constants::PROGRAM_ID,
1723                accounts,
1724                data: InstructionData::data(&drift_idl::instructions::PlaceAndMakePerpOrder {
1725                    params: order,
1726                    taker_order_id,
1727                }),
1728            }
1729        } else {
1730            Instruction {
1731                program_id: constants::PROGRAM_ID,
1732                accounts,
1733                data: InstructionData::data(&drift_idl::instructions::PlaceAndMakeSpotOrder {
1734                    params: order,
1735                    taker_order_id,
1736                    fulfillment_type,
1737                }),
1738            }
1739        };
1740
1741        self.ixs.push(ix);
1742        self
1743    }
1744
1745    /// Add a place and take instruction
1746    ///
1747    /// * `order` - the order to place
1748    /// * `maker_info` - pubkey of the maker/counter-party to take against and account data
1749    /// * `referrer` - pubkey of the maker's referrer account, if any
1750    /// * `fulfillment_type` - type of fill for spot orders, ignored for perp orders
1751    pub fn place_and_take(
1752        mut self,
1753        order: OrderParams,
1754        maker_info: Option<(Pubkey, User)>,
1755        referrer: Option<Pubkey>,
1756        fulfillment_type: Option<SpotFulfillmentType>,
1757        success_condition: Option<u32>,
1758    ) -> Self {
1759        let mut user_accounts = vec![self.account_data.as_ref()];
1760        if let Some((ref _maker_pubkey, ref maker)) = maker_info {
1761            user_accounts.push(maker);
1762        }
1763
1764        let is_perp = order.market_type == MarketType::Perp;
1765        let perp_writable = [MarketId::perp(order.market_index)];
1766        let spot_writable = [MarketId::spot(order.market_index), MarketId::QUOTE_SPOT];
1767
1768        let mut accounts = build_accounts(
1769            self.program_data,
1770            types::accounts::PlaceAndTakePerpOrder {
1771                state: *state_account(),
1772                authority: self.authority,
1773                user: self.sub_account,
1774                user_stats: Wallet::derive_stats_account(&self.authority),
1775            },
1776            user_accounts.as_slice(),
1777            self.force_markets.readable.iter(),
1778            if is_perp {
1779                perp_writable.iter()
1780            } else {
1781                spot_writable.iter()
1782            }
1783            .chain(self.force_markets.writeable.iter()),
1784        );
1785
1786        if referrer.is_some_and(|r| !maker_info.is_some_and(|(m, _)| m == r)) {
1787            let referrer = referrer.unwrap();
1788            accounts.push(AccountMeta::new(
1789                Wallet::derive_stats_account(&referrer),
1790                false,
1791            ));
1792            accounts.push(AccountMeta::new(referrer, false));
1793        }
1794
1795        let ix = if is_perp {
1796            Instruction {
1797                program_id: constants::PROGRAM_ID,
1798                accounts,
1799                data: InstructionData::data(&drift_idl::instructions::PlaceAndTakePerpOrder {
1800                    params: order,
1801                    success_condition,
1802                }),
1803            }
1804        } else {
1805            Instruction {
1806                program_id: constants::PROGRAM_ID,
1807                accounts,
1808                data: InstructionData::data(&drift_idl::instructions::PlaceAndTakeSpotOrder {
1809                    params: order,
1810                    maker_order_id: None,
1811                    fulfillment_type,
1812                }),
1813            }
1814        };
1815
1816        self.ixs.push(ix);
1817        self
1818    }
1819
1820    /// Place and try to fill (make) against the swift order (Perps only)
1821    ///
1822    /// * `maker_order` - order params defined by the maker, e.g. partial or full fill
1823    /// * `signed_order_info` - the signed swift order info (i.e from taker)
1824    /// * `taker_account` - taker account data
1825    /// * `taker_account_referrer` - taker account referrer key
1826    ///
1827    pub fn place_and_make_swift_order(
1828        mut self,
1829        maker_order: OrderParams,
1830        signed_order_info: &SignedOrderInfo,
1831        taker_account: &User,
1832        taker_account_referrer: &Pubkey,
1833    ) -> Self {
1834        let order_params = signed_order_info.order_params();
1835        assert!(
1836            order_params.market_type == MarketType::Perp,
1837            "only swift perps are supported"
1838        );
1839        self = self.place_swift_order(signed_order_info, taker_account);
1840
1841        let perp_writable = [MarketId::perp(order_params.market_index)];
1842        let mut accounts = build_accounts(
1843            self.program_data,
1844            types::accounts::PlaceAndMakeSignedMsgPerpOrder {
1845                state: *state_account(),
1846                authority: self.authority,
1847                user: self.sub_account,
1848                user_stats: Wallet::derive_stats_account(&self.authority),
1849                taker: signed_order_info.taker_subaccount(),
1850                taker_stats: Wallet::derive_stats_account(&taker_account.authority),
1851                taker_signed_msg_user_orders: Wallet::derive_swift_order_account(
1852                    &taker_account.authority,
1853                ),
1854            },
1855            &[self.account_data.as_ref(), taker_account],
1856            self.force_markets.readable.iter(),
1857            perp_writable
1858                .iter()
1859                .chain(self.force_markets.writeable.iter()),
1860        );
1861
1862        if taker_account_referrer != &DEFAULT_PUBKEY {
1863            accounts.push(AccountMeta::new(*taker_account_referrer, false));
1864            accounts.push(AccountMeta::new(
1865                Wallet::derive_stats_account(taker_account_referrer),
1866                false,
1867            ));
1868        }
1869
1870        self.ixs.push(Instruction {
1871            program_id: constants::PROGRAM_ID,
1872            accounts,
1873            data: InstructionData::data(&drift_idl::instructions::PlaceAndMakeSignedMsgPerpOrder {
1874                params: maker_order,
1875                signed_msg_order_uuid: signed_order_info.order_uuid(),
1876            }),
1877        });
1878
1879        self
1880    }
1881
1882    /// Place a swift order (Perps only)
1883    ///
1884    /// ☢️ this Ix will not fill by itself. The caller should add a subsequent Ix
1885    /// e.g. with JIT proxy, to atomically place and fill the order
1886    /// or see `place_and_make_swift_order`
1887    ///
1888    /// * `signed_order_info` - the signed swift order info
1889    /// * `taker_account` - taker subaccount data
1890    ///
1891    pub fn place_swift_order(
1892        mut self,
1893        signed_order_info: &SignedOrderInfo,
1894        taker_account: &User,
1895    ) -> Self {
1896        let order_params = signed_order_info.order_params();
1897        assert!(
1898            order_params.market_type == MarketType::Perp,
1899            "only swift perps are supported"
1900        );
1901
1902        let perp_readable = [MarketId::perp(order_params.market_index)];
1903        let accounts = build_accounts(
1904            self.program_data,
1905            types::accounts::PlaceSignedMsgTakerOrder {
1906                state: *state_account(),
1907                authority: self.authority,
1908                user: signed_order_info.taker_subaccount(),
1909                user_stats: Wallet::derive_stats_account(&taker_account.authority),
1910                signed_msg_user_orders: Wallet::derive_swift_order_account(
1911                    &taker_account.authority,
1912                ),
1913                ix_sysvar: SYSVAR_INSTRUCTIONS_PUBKEY,
1914            },
1915            &[taker_account],
1916            perp_readable
1917                .iter()
1918                .chain(self.force_markets.readable.iter()),
1919            self.force_markets.writeable.iter(),
1920        );
1921
1922        let swift_taker_ix_data = signed_order_info.to_ix_data();
1923        let ed25519_verify_ix = crate::utils::new_ed25519_ix_ptr(
1924            swift_taker_ix_data.as_slice(),
1925            self.ixs.len() as u16 + 1,
1926        );
1927
1928        let place_swift_ix = Instruction {
1929            program_id: constants::PROGRAM_ID,
1930            accounts,
1931            data: InstructionData::data(&drift_idl::instructions::PlaceSignedMsgTakerOrder {
1932                signed_msg_order_params_message_bytes: swift_taker_ix_data,
1933                is_delegate_signer: signed_order_info.using_delegate_signing(),
1934            }),
1935        };
1936
1937        self.ixs
1938            .extend_from_slice(&[ed25519_verify_ix, place_swift_ix]);
1939        self
1940    }
1941
1942    /// Set the subaccount's _max_ initial margin ratio.
1943    ///
1944    /// * `sub_account_id` - index of the subaccount
1945    /// * `margin_ratio` - new margin ratio in MARGIN_PRECISION
1946    ///
1947    /// MARGIN_PRECISION => 1x leverage
1948    /// MARGIN_PRECISION * 10 => .1x leverage
1949    /// MARGIN_PRECISION / 10 =>  10x leverage
1950    ///
1951    pub fn set_max_initial_margin_ratio(mut self, margin_ratio: u32, sub_account_id: u16) -> Self {
1952        let accounts = build_accounts(
1953            self.program_data,
1954            types::accounts::UpdateUserCustomMarginRatio {
1955                authority: self.authority,
1956                user: Wallet::derive_user_account(&self.authority, sub_account_id),
1957            },
1958            &[self.account_data.as_ref()],
1959            std::iter::empty(),
1960            std::iter::empty(),
1961        );
1962        let ix = Instruction {
1963            program_id: constants::PROGRAM_ID,
1964            accounts,
1965            data: InstructionData::data(&drift_idl::instructions::UpdateUserCustomMarginRatio {
1966                sub_account_id,
1967                margin_ratio,
1968            }),
1969        };
1970        self.ixs.push(ix);
1971
1972        self
1973    }
1974
1975    /// Build the transaction message ready for signing and sending
1976    pub fn build(self) -> VersionedMessage {
1977        if self.legacy {
1978            let message = Message::new(self.ixs.as_ref(), Some(&self.authority));
1979            VersionedMessage::Legacy(message)
1980        } else {
1981            let message = v0::Message::try_compile(
1982                &self.authority,
1983                self.ixs.as_slice(),
1984                self.lookup_tables.as_slice(),
1985                Default::default(),
1986            )
1987            .expect("ok");
1988            VersionedMessage::V0(message)
1989        }
1990    }
1991
1992    pub fn program_data(&self) -> &ProgramData {
1993        self.program_data
1994    }
1995
1996    pub fn account_data(&self) -> &Cow<'_, User> {
1997        &self.account_data
1998    }
1999}
2000
2001/// Builds a set of required accounts from a user's open positions and additional given accounts
2002///
2003/// * `base_accounts` - base anchor accounts
2004/// * `users` - Drift user account data
2005/// * `markets_readable` - IDs of markets to include as readable
2006/// * `markets_writable` - IDs of markets to include as writable (takes priority over readable)
2007///
2008/// # Panics
2009///  if the user has positions in an unknown market (i.e unsupported by the SDK)
2010pub fn build_accounts<'a>(
2011    program_data: &ProgramData,
2012    base_accounts: impl ToAccountMetas,
2013    users: &[&User],
2014    markets_readable: impl Iterator<Item = &'a MarketId>,
2015    markets_writable: impl Iterator<Item = &'a MarketId>,
2016) -> Vec<AccountMeta> {
2017    // the order of accounts returned must be instruction, oracles, spot, perps see (https://github.com/drift-labs/protocol-v2/blob/master/programs/drift/src/instructions/optional_accounts.rs#L28)
2018    let mut accounts = BTreeSet::<RemainingAccount>::new();
2019
2020    // add accounts to the ordered list
2021    let mut include_market =
2022        |market_index: u16, market_type: MarketType, writable: bool| match market_type {
2023            MarketType::Spot => {
2024                let SpotMarket { pubkey, oracle, .. } = program_data
2025                    .spot_market_config_by_index(market_index)
2026                    .expect("exists");
2027                accounts.extend(
2028                    [
2029                        RemainingAccount::Spot {
2030                            pubkey: *pubkey,
2031                            writable,
2032                        },
2033                        RemainingAccount::Oracle { pubkey: *oracle },
2034                    ]
2035                    .iter(),
2036                )
2037            }
2038            MarketType::Perp => {
2039                let PerpMarket { pubkey, amm, .. } = program_data
2040                    .perp_market_config_by_index(market_index)
2041                    .expect("exists");
2042                accounts.extend(
2043                    [
2044                        RemainingAccount::Perp {
2045                            pubkey: *pubkey,
2046                            writable,
2047                        },
2048                        RemainingAccount::Oracle { pubkey: amm.oracle },
2049                    ]
2050                    .iter(),
2051                )
2052            }
2053        };
2054
2055    for market in markets_writable {
2056        include_market(market.index(), market.kind(), true);
2057    }
2058
2059    for market in markets_readable {
2060        include_market(market.index(), market.kind(), false);
2061    }
2062
2063    for user in users {
2064        // Drift program performs margin checks which requires reading user positions
2065        for p in user.spot_positions.iter().filter(|p| !p.is_available()) {
2066            include_market(p.market_index, MarketType::Spot, false);
2067        }
2068        for p in user.perp_positions.iter().filter(|p| !p.is_available()) {
2069            include_market(p.market_index, MarketType::Perp, false);
2070        }
2071    }
2072    // always manually try to include the quote (USDC) market
2073    // TODO: this is not exactly the same semantics as the TS sdk
2074    include_market(MarketId::QUOTE_SPOT.index(), MarketType::Spot, false);
2075
2076    let mut account_metas = base_accounts.to_account_metas();
2077    account_metas.extend(accounts.into_iter().map(Into::into));
2078    account_metas
2079}
2080
2081#[cfg(test)]
2082mod tests {
2083    use std::str::FromStr;
2084
2085    use serde_json::json;
2086    use solana_account_decoder_client_types::{UiAccount, UiAccountData, UiAccountEncoding};
2087    use solana_rpc_client::rpc_client::Mocks;
2088    use solana_rpc_client_api::{
2089        request::RpcRequest,
2090        response::{Response, RpcResponseContext},
2091    };
2092    use solana_sdk::signature::Keypair;
2093    use types::accounts::PerpMarket;
2094
2095    use super::*;
2096
2097    // static account data for test/mock
2098    const ACCOUNT_DATA: &str = include_str!("../../res/9Jtc.hex");
2099    const DEVNET_ENDPOINT: &str = "https://api.devnet.solana.com";
2100
2101    /// Init a new `DriftClient` with provided mocked RPC responses
2102    async fn setup(rpc_mocks: Mocks, keypair: Keypair) -> DriftClient {
2103        let rpc_client = Arc::new(RpcClient::new_mock_with_mocks(
2104            DEVNET_ENDPOINT.to_string(),
2105            rpc_mocks,
2106        ));
2107
2108        let pubsub_client = Arc::new(
2109            PubsubClient::new(&get_ws_url(DEVNET_ENDPOINT).unwrap())
2110                .await
2111                .expect("ws connects"),
2112        );
2113
2114        let perp_market_map =
2115            MarketMap::<PerpMarket>::new(Arc::clone(&pubsub_client), rpc_client.commitment());
2116        let spot_market_map =
2117            MarketMap::<SpotMarket>::new(Arc::clone(&pubsub_client), rpc_client.commitment());
2118
2119        let backend = DriftClientBackend {
2120            rpc_client: Arc::clone(&rpc_client),
2121            pubsub_client: Arc::clone(&pubsub_client),
2122            program_data: ProgramData::uninitialized(),
2123            perp_market_map,
2124            spot_market_map,
2125            oracle_map: OracleMap::new(Arc::clone(&pubsub_client), &[], rpc_client.commitment()),
2126            blockhash_subscriber: BlockhashSubscriber::new(
2127                Duration::from_secs(2),
2128                Arc::clone(&rpc_client),
2129            ),
2130            account_map: AccountMap::new(
2131                Arc::clone(&pubsub_client),
2132                Arc::clone(&rpc_client),
2133                CommitmentConfig::processed(),
2134            ),
2135            grpc_unsub: Default::default(),
2136        };
2137
2138        DriftClient {
2139            context: Context::DevNet,
2140            backend: Box::leak(Box::new(backend)),
2141            wallet: Wallet::new(keypair),
2142        }
2143    }
2144
2145    #[tokio::test]
2146    async fn test_backend_send_sync() {
2147        let account_mocks = Mocks::default();
2148        let client = setup(account_mocks, Keypair::new()).await;
2149
2150        tokio::task::spawn(async move {
2151            let _ = client.clone();
2152        });
2153    }
2154
2155    #[tokio::test]
2156    #[cfg(feature = "rpc_tests")]
2157    async fn test_marketmap_subscribe() {
2158        use utils::test_envs::mainnet_endpoint;
2159
2160        let client = DriftClient::new(
2161            Context::MainNet,
2162            RpcAccountProvider::new(&mainnet_endpoint()),
2163            Keypair::new().into(),
2164        )
2165        .await
2166        .unwrap();
2167
2168        let _ = client.subscribe().await;
2169
2170        tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
2171
2172        for _ in 0..20 {
2173            tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
2174            let perp_market = client.get_perp_market_account_and_slot(0);
2175            let slot = perp_market.unwrap().slot;
2176            dbg!(slot);
2177        }
2178
2179        for _ in 0..20 {
2180            tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
2181            let spot_market = client.get_spot_market_account_and_slot(0);
2182            let slot = spot_market.unwrap().slot;
2183            dbg!(slot);
2184        }
2185    }
2186
2187    #[tokio::test]
2188    async fn get_orders() {
2189        let user = Pubkey::from_str("9JtczxrJjPM4J1xooxr2rFXmRivarb4BwjNiBgXDwe2p").unwrap();
2190        let account_data = hex::decode(ACCOUNT_DATA).expect("valid hex");
2191
2192        let mut account_mocks = Mocks::default();
2193        let account_response = json!(Response {
2194            context: RpcResponseContext::new(12_345),
2195            value: Some(UiAccount {
2196                data: UiAccountData::Binary(
2197                    solana_sdk::bs58::encode(account_data).into_string(),
2198                    UiAccountEncoding::Base58
2199                ),
2200                owner: user.to_string(),
2201                executable: false,
2202                lamports: 0,
2203                rent_epoch: 0,
2204                space: None,
2205            })
2206        });
2207        account_mocks.insert(RpcRequest::GetAccountInfo, account_response.clone());
2208
2209        let client = setup(account_mocks, Keypair::new()).await;
2210
2211        let orders = client.all_orders(&user).await.unwrap();
2212        assert_eq!(orders.len(), 3);
2213    }
2214
2215    #[tokio::test]
2216    async fn get_positions() {
2217        let user = Pubkey::from_str("9JtczxrJjPM4J1xooxr2rFXmRivarb4BwjNiBgXDwe2p").unwrap();
2218        let account_data = hex::decode(ACCOUNT_DATA).expect("valid hex");
2219
2220        let mut account_mocks = Mocks::default();
2221        let account_response = json!(Response {
2222            context: RpcResponseContext::new(12_345),
2223            value: Some(UiAccount {
2224                data: UiAccountData::Binary(
2225                    solana_sdk::bs58::encode(account_data).into_string(),
2226                    UiAccountEncoding::Base58
2227                ),
2228                owner: user.to_string(),
2229                executable: false,
2230                lamports: 0,
2231                rent_epoch: 0,
2232                space: None,
2233            })
2234        });
2235        account_mocks.insert(RpcRequest::GetAccountInfo, account_response.clone());
2236        let client = setup(account_mocks, Keypair::new()).await;
2237
2238        let (spot, perp) = client.all_positions(&user).await.unwrap();
2239        assert_eq!(spot.len(), 1);
2240        assert_eq!(perp.len(), 1);
2241    }
2242}