From 116462af2d5540cb1311523d72ccea578251588e Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Sat, 5 Apr 2025 08:19:05 +0900 Subject: [PATCH 01/12] oraclemap: small tidyups --- crates/src/oraclemap.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/src/oraclemap.rs b/crates/src/oraclemap.rs index 4368f66..9c80d9b 100644 --- a/crates/src/oraclemap.rs +++ b/crates/src/oraclemap.rs @@ -41,7 +41,7 @@ pub struct OracleMap { /// Oracle data keyed by pubkey oraclemap: Arc>, /// Oracle subscription handles by pubkey - subcriptions: DashMap<(Pubkey, u8), UnsubHandle, ahash::RandomState>, + subscriptions: DashMap<(Pubkey, u8), UnsubHandle, ahash::RandomState>, /// Oracle (pubkey, source) by MarketId (immutable) oracle_by_market: ReadOnlyView, latest_slot: Arc, @@ -88,7 +88,7 @@ impl OracleMap { Self { oraclemap: Arc::new(oraclemap), oracle_by_market: oracle_by_market.into_read_only(), - subcriptions: Default::default(), + subscriptions: Default::default(), latest_slot: Arc::new(AtomicU64::new(0)), commitment, pubsub: pubsub_client, @@ -119,11 +119,11 @@ impl OracleMap { // markets can share oracle pubkeys, only want one sub per oracle pubkey if self - .subcriptions + .subscriptions .contains_key(&(*oracle_pubkey, *oracle_source as u8)) || pending_subscriptions .iter() - .any(|(_, o)| &o.pubkey == oracle_pubkey) + .any(|(_, o)| &o.pubkey == oracle_pubkey && o.source == oracle_source) { log::debug!(target: LOG_TARGET, "subscription exists: {market:?}/{oracle_pubkey:?}"); continue; @@ -156,7 +156,7 @@ impl OracleMap { while let Some((info, unsub)) = subscription_futs.next().await { log::debug!(target: LOG_TARGET, "subscribed market oracle: {:?}", info.market); - self.subcriptions + self.subscriptions .insert((info.pubkey, info.source as u8), unsub?); } @@ -169,7 +169,7 @@ impl OracleMap { for market in markets { if let Some((oracle_pubkey, oracle_source)) = self.oracle_by_market.get(market) { if let Some((market, unsub)) = self - .subcriptions + .subscriptions .remove(&(*oracle_pubkey, *oracle_source as u8)) { let _ = unsub.send(()); @@ -185,7 +185,7 @@ impl OracleMap { /// Unsubscribe from all oracle updates pub fn unsubscribe_all(&self) -> SdkResult<()> { let all_markets: Vec = self - .subcriptions + .subscriptions .iter() .filter_map(|s| self.oraclemap.get(s.key()).map(|o| o.market)) .collect(); @@ -259,7 +259,7 @@ impl OracleMap { /// Returns true if the oraclemap has a subscription for `market` pub fn is_subscribed(&self, market: &MarketId) -> bool { if let Some((oracle_pubkey, oracle_source)) = self.oracle_by_market.get(market) { - self.subcriptions + self.subscriptions .contains_key(&(*oracle_pubkey, *oracle_source as u8)) } else { false From ca84f47e58a799ec1365b445c396f283544b0ec8 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Sat, 5 Apr 2025 08:19:26 +0900 Subject: [PATCH 02/12] wip --- Cargo.toml | 1 + crates/src/constants.rs | 19 ++++++++- crates/src/lib.rs | 89 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 88d3aa1..3ef7de3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ dashmap = "6" env_logger = "0.11" futures-util = "0.3" hex = "0.4" +jupiter-swap-api-client = { git = "https://github.com/jup-ag/jupiter-swap-api-client.git", package = "jupiter-swap-api-client"} log = "0.4" rayon = { version = "1.9.0", optional = true } regex = "1.10" diff --git a/crates/src/constants.rs b/crates/src/constants.rs index a1f737f..f745e25 100644 --- a/crates/src/constants.rs +++ b/crates/src/constants.rs @@ -4,7 +4,7 @@ use solana_sdk::{address_lookup_table::AddressLookupTableAccount, pubkey::Pubkey use crate::{ drift_idl::accounts::{PerpMarket, SpotMarket}, - types::Context, + types::{accounts::State, Context}, MarketId, MarketType, OracleSource, }; @@ -27,9 +27,14 @@ pub const DEFAULT_PUBKEY: Pubkey = solana_sdk::pubkey!("111111111111111111111111 static STATE_ACCOUNT: OnceLock = OnceLock::new(); +/// Address of the SPL Token program pub const TOKEN_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"); +/// Address of the SPL Token 2022 program +pub const TOKEN_2022_PROGRAM_ID: Pubkey = + solana_sdk::pubkey!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); + /// Drift market lookup table (DevNet) pub const LUTS_DEVNET: &[Pubkey] = &[solana_sdk::pubkey!( "FaMS3U4uBojvGn5FSDEPimddcXsCfwkKsFgMVVnDdxGb" @@ -117,6 +122,8 @@ pub struct ProgramData { spot_markets: &'static [SpotMarket], perp_markets: &'static [PerpMarket], pub lookup_tables: &'static [AddressLookupTableAccount], + // drift state account + state: State, } impl ProgramData { @@ -126,6 +133,7 @@ impl ProgramData { spot_markets: &[], perp_markets: &[], lookup_tables: &[], + state: State::default(), } } /// Initialize `ProgramData` @@ -133,6 +141,7 @@ impl ProgramData { mut spot: Vec, mut perp: Vec, lookup_tables: Vec, + state: State, ) -> Self { spot.sort_by(|a, b| a.market_index.cmp(&b.market_index)); perp.sort_by(|a, b| a.market_index.cmp(&b.market_index)); @@ -154,9 +163,17 @@ impl ProgramData { spot_markets: Box::leak(spot.into_boxed_slice()), perp_markets: Box::leak(perp.into_boxed_slice()), lookup_tables: Box::leak(lookup_tables.into_boxed_slice()), + state, } } + /// Return drift `State` account (cached) + /// + /// prefer live + pub fn state(&self) -> &State { + &self.state + } + /// Return known spot markets pub fn spot_market_configs(&self) -> &'static [SpotMarket] { self.spot_markets diff --git a/crates/src/lib.rs b/crates/src/lib.rs index 629b3ce..001969e 100644 --- a/crates/src/lib.rs +++ b/crates/src/lib.rs @@ -3,6 +3,7 @@ use std::{borrow::Cow, collections::BTreeSet, sync::Arc, time::Duration}; use anchor_lang::{AccountDeserialize, InstructionData}; +use constants::{TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID}; pub use drift_pubsub_client::PubsubClient; use futures_util::TryFutureExt; use log::debug; @@ -739,12 +740,13 @@ impl DriftClientBackend { let lut_pubkeys = context.luts(); - let (_, _, lut_accounts) = tokio::try_join!( + let (_, _, lut_accounts, state) = tokio::try_join!( perp_market_map.sync(&rpc_client), spot_market_map.sync(&rpc_client), rpc_client .get_multiple_accounts(lut_pubkeys) .map_err(Into::into), + rpc_client.get_account_with_config(state_account()), )?; let lookup_tables = lut_pubkeys @@ -789,6 +791,7 @@ impl DriftClientBackend { spot_market_map.values(), perp_market_map.values(), lookup_tables, + state, ), account_map, perp_market_map, @@ -1800,7 +1803,7 @@ impl<'a> TransactionBuilder<'a> { self.program_data, types::accounts::UpdateUserCustomMarginRatio { authority: self.authority, - user: Wallet::derive_user_account(&self.authority, sub_account_id), + user: self.sub_account, }, &[self.account_data.as_ref()], std::iter::empty(), @@ -1819,6 +1822,88 @@ impl<'a> TransactionBuilder<'a> { self } + /// Add a spot `begin_swap` ix + /// + /// This should be followed by a subsequent `end_swap` ix + pub fn begin_swap_ix( + mut self, + out_market_index: u16, + in_market_index: u16, + amount_in: u64, + payer_token_account: &Pubkey, + payee_token_account: &Pubkey, + limit_price: Option, + reduce_only: bool, + ) -> Self { + let out_market = self + .program_data + .spot_market_config_by_index(out_market_index) + .unwrap(); + let in_market = self + .program_data + .spot_market_config_by_index(in_market_index) + .unwrap(); + + let in_token_program = if in_market.token_program == 1 { + TOKEN_2022_PROGRAM_ID + } else { + TOKEN_PROGRAM_ID + }; + + let out_token_program = if out_market.token_program == 1 { + TOKEN_2022_PROGRAM_ID + } else { + TOKEN_PROGRAM_ID + }; + + let accounts = build_accounts( + self.program_data, + types::accounts::BeginSwap { + state: *state_account(), + user: self.sub_account, + user_stats: Wallet::derive_stats_account(&self.authority), + authority: self.authority, + out_spot_market_vault: out_market.vault, + in_spot_market_vault: in_market.vault, + in_token_account: payer_token_account, + out_token_account: payee_token_account, + token_program: in_token_program, + drift_signer: self.program_data.state().signer, + instructions: SYSVAR_INSTRUCTIONS_PUBKEY, + }, + &[self.account_data.as_ref()], + self.force_markets.readable.iter(), + self.force_markets.writeable.iter(), + ); + + if out_token_program != in_token_program { + accounts.push(AccountMeta::new(out_token_program, false)); + } + + if out_market.token_program == 1 || in_market.token_program == 1 { + accounts.push(AccountMeta::new(in_market.mint, false)); + accounts.push(AccountMeta::new(out_market.mint, false)); + } + + let ix = Instruction { + program_id: constants::PROGRAM_ID, + accounts, + data: InstructionData::data(&drift_idl::instructions::BeginSwap { + in_market_index, + out_market_index, + amount_in, + }), + }; + self.ixs.push(ix); + + self + } + + /// Add a spot `end_swap` ix + /// + /// This should follow a preceding `begin_swap` ix + pub fn end_swap_ix(mut self) -> Self {} + /// Build the transaction message ready for signing and sending pub fn build(self) -> VersionedMessage { if self.legacy { From c4039cc8e66b996d6d3267777ce150378beda4a5 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Wed, 9 Apr 2025 09:27:07 +0900 Subject: [PATCH 03/12] wip --- Cargo.lock | 606 ++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 2 +- crates/src/constants.rs | 2 +- crates/src/lib.rs | 130 +++++++-- crates/src/oraclemap.rs | 2 +- 5 files changed, 697 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 18c18bf..5bfdc99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,6 +109,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.15", + "once_cell", + "version_check", +] + [[package]] name = "ahash" version = "0.8.11" @@ -565,6 +576,12 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "atty" version = "0.2.14" @@ -639,6 +656,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake3" version = "1.6.1" @@ -785,6 +814,28 @@ dependencies = [ "serde", ] +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "bytemuck" version = "1.22.0" @@ -1231,7 +1282,7 @@ name = "drift-rs" version = "1.0.0-alpha.14" dependencies = [ "abi_stable", - "ahash", + "ahash 0.8.11", "anchor-lang", "base64 0.22.1", "bytemuck", @@ -1243,6 +1294,7 @@ dependencies = [ "futures-util", "hex", "hex-literal", + "jupiter-swap-api-client", "log", "rayon", "regex", @@ -1468,6 +1520,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.31" @@ -1645,6 +1703,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.2.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hash32" version = "0.2.1" @@ -1654,13 +1731,22 @@ dependencies = [ "byteorder", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.8", +] + [[package]] name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" dependencies = [ - "ahash", + "ahash 0.8.11", ] [[package]] @@ -1768,6 +1854,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.2.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.2.0", + "http-body 1.0.1", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.10.1" @@ -1796,9 +1905,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1810,6 +1919,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.8", + "http 1.2.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -1818,10 +1947,62 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper", - "rustls", + "hyper 0.14.32", + "rustls 0.21.12", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.2.0", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.25", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.2.0", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] @@ -2118,6 +2299,22 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jupiter-swap-api-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "reqwest 0.12.15", + "rust_decimal", + "serde", + "serde_json", + "serde_qs", + "solana-account-decoder", + "solana-sdk", + "thiserror 2.0.12", +] + [[package]] name = "keccak" version = "0.1.5" @@ -2637,6 +2834,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "qstring" version = "0.7.2" @@ -2655,6 +2872,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -2814,6 +3037,15 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + [[package]] name = "repr_offset" version = "0.2.2" @@ -2835,11 +3067,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", - "hyper", - "hyper-rustls", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -2848,15 +3080,15 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tokio-util", "tower-service", "url", @@ -2867,6 +3099,50 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.8", + "http 1.2.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls 0.27.5", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "system-configuration 0.6.1", + "tokio", + "tokio-native-tls", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + [[package]] name = "reqwest-middleware" version = "0.2.5" @@ -2876,7 +3152,7 @@ dependencies = [ "anyhow", "async-trait", "http 0.2.12", - "reqwest", + "reqwest 0.11.27", "serde", "task-local-extensions", "thiserror 1.0.69", @@ -2896,6 +3172,51 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9008cd6385b9e161d8229e1f6549dd23c3d022f132a2ea37ac3a10ac4935779b" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503d1d27590a2b0a3a4ca4c94755aa2875657196ecbf401a42eff41d7de532c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faa7de2ba56ac291bd90c6b9bece784a52ae1411f9506544b3eae36dd2356d50" +dependencies = [ + "arrayvec", + "borsh 1.5.5", + "bytes", + "num-traits", + "rand 0.8.5", + "rkyv", + "serde", + "serde_json", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2932,10 +3253,23 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.1", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2945,6 +3279,21 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2955,6 +3304,17 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -2992,6 +3352,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "security-framework" version = "2.11.1" @@ -3071,6 +3437,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_qs" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" +dependencies = [ + "percent-encoding", + "serde", + "thiserror 1.0.69", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -3191,6 +3568,12 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "siphasher" version = "0.3.11" @@ -3658,7 +4041,7 @@ version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e1d3b52b4a014efeaaab67f14e40af3972a4be61c523d612860db8e3145529" dependencies = [ - "ahash", + "ahash 0.8.11", "lazy_static", "solana-epoch-schedule", "solana-hash", @@ -4344,7 +4727,7 @@ dependencies = [ "bs58", "indicatif", "log", - "reqwest", + "reqwest 0.11.27", "reqwest-middleware", "semver", "serde", @@ -4380,7 +4763,7 @@ dependencies = [ "base64 0.22.1", "bs58", "jsonrpc-core", - "reqwest", + "reqwest 0.11.27", "reqwest-middleware", "semver", "serde", @@ -5439,6 +5822,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -5458,7 +5850,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "system-configuration-sys 0.6.0", ] [[package]] @@ -5471,6 +5874,22 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "task-local-extensions" version = "0.1.4" @@ -5613,7 +6032,17 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.25", "tokio", ] @@ -5698,6 +6127,27 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper 1.0.2", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -5868,6 +6318,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" + [[package]] name = "vcpkg" version = "0.2.15" @@ -6059,6 +6515,35 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -6110,13 +6595,29 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -6129,6 +6630,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -6141,6 +6648,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -6153,12 +6666,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -6171,6 +6696,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -6183,6 +6714,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -6195,6 +6732,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -6207,6 +6750,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + [[package]] name = "winnow" version = "0.7.3" @@ -6247,6 +6796,15 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index 3ef7de3..afa99fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ dashmap = "6" env_logger = "0.11" futures-util = "0.3" hex = "0.4" -jupiter-swap-api-client = { git = "https://github.com/jup-ag/jupiter-swap-api-client.git", package = "jupiter-swap-api-client"} +jupiter-swap-api-client = { path = "../jupiter-swap-api-client//jupiter-swap-api-client" } log = "0.4" rayon = { version = "1.9.0", optional = true } regex = "1.10" diff --git a/crates/src/constants.rs b/crates/src/constants.rs index f745e25..1760d57 100644 --- a/crates/src/constants.rs +++ b/crates/src/constants.rs @@ -133,7 +133,7 @@ impl ProgramData { spot_markets: &[], perp_markets: &[], lookup_tables: &[], - state: State::default(), + state: unsafe { std::mem::zeroed() }, } } /// Initialize `ProgramData` diff --git a/crates/src/lib.rs b/crates/src/lib.rs index 001969e..39a1f89 100644 --- a/crates/src/lib.rs +++ b/crates/src/lib.rs @@ -6,6 +6,7 @@ use anchor_lang::{AccountDeserialize, InstructionData}; use constants::{TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID}; pub use drift_pubsub_client::PubsubClient; use futures_util::TryFutureExt; +use jupiter_swap_api_client::JupiterSwapApiClient; use log::debug; pub use solana_rpc_client::nonblocking::rpc_client::RpcClient; use solana_rpc_client_api::response::Response; @@ -740,13 +741,21 @@ impl DriftClientBackend { let lut_pubkeys = context.luts(); - let (_, _, lut_accounts, state) = tokio::try_join!( + let account_map = AccountMap::new( + Arc::clone(&pubsub_client), + Arc::clone(&rpc_client), + rpc_client.commitment(), + ); + account_map + .subscribe_account_polled(state_account(), Some(Duration::from_secs(180))) + .await?; + + let (_, _, lut_accounts) = tokio::try_join!( perp_market_map.sync(&rpc_client), spot_market_map.sync(&rpc_client), rpc_client .get_multiple_accounts(lut_pubkeys) .map_err(Into::into), - rpc_client.get_account_with_config(state_account()), )?; let lookup_tables = lut_pubkeys @@ -774,14 +783,6 @@ impl DriftClientBackend { all_oracles.as_slice(), rpc_client.commitment(), ); - let account_map = AccountMap::new( - Arc::clone(&pubsub_client), - Arc::clone(&rpc_client), - rpc_client.commitment(), - ); - account_map - .subscribe_account_polled(state_account(), Some(Duration::from_secs(180))) - .await?; Ok(Self { rpc_client: Arc::clone(&rpc_client), @@ -791,7 +792,7 @@ impl DriftClientBackend { spot_market_map.values(), perp_market_map.values(), lookup_tables, - state, + account_map.account_data(state_account()).unwrap(), ), account_map, perp_market_map, @@ -1825,15 +1826,13 @@ impl<'a> TransactionBuilder<'a> { /// Add a spot `begin_swap` ix /// /// This should be followed by a subsequent `end_swap` ix - pub fn begin_swap_ix( + pub fn begin_swap( mut self, out_market_index: u16, in_market_index: u16, amount_in: u64, payer_token_account: &Pubkey, payee_token_account: &Pubkey, - limit_price: Option, - reduce_only: bool, ) -> Self { let out_market = self .program_data @@ -1856,7 +1855,7 @@ impl<'a> TransactionBuilder<'a> { TOKEN_PROGRAM_ID }; - let accounts = build_accounts( + let mut accounts = build_accounts( self.program_data, types::accounts::BeginSwap { state: *state_account(), @@ -1865,8 +1864,8 @@ impl<'a> TransactionBuilder<'a> { authority: self.authority, out_spot_market_vault: out_market.vault, in_spot_market_vault: in_market.vault, - in_token_account: payer_token_account, - out_token_account: payee_token_account, + in_token_account: *payer_token_account, + out_token_account: *payee_token_account, token_program: in_token_program, drift_signer: self.program_data.state().signer, instructions: SYSVAR_INSTRUCTIONS_PUBKEY, @@ -1902,7 +1901,102 @@ impl<'a> TransactionBuilder<'a> { /// Add a spot `end_swap` ix /// /// This should follow a preceding `begin_swap` ix - pub fn end_swap_ix(mut self) -> Self {} + pub fn end_swap( + mut self, + out_market_index: u16, + in_market_index: u16, + payer_token_account: &Pubkey, + payee_token_account: &Pubkey, + limit_price: Option, + reduce_only: Option, + ) -> Self { + let out_market = self + .program_data + .spot_market_config_by_index(out_market_index) + .unwrap(); + let in_market = self + .program_data + .spot_market_config_by_index(in_market_index) + .unwrap(); + + let in_token_program = if in_market.token_program == 1 { + TOKEN_2022_PROGRAM_ID + } else { + TOKEN_PROGRAM_ID + }; + + let accounts = build_accounts( + self.program_data, + types::accounts::EndSwap { + state: *state_account(), + user: self.sub_account, + user_stats: Wallet::derive_stats_account(&self.authority), + authority: self.authority, + out_spot_market_vault: out_market.vault, + in_spot_market_vault: in_market.vault, + in_token_account: *payer_token_account, + out_token_account: *payee_token_account, + token_program: in_token_program, + drift_signer: self.program_data.state().signer, + instructions: SYSVAR_INSTRUCTIONS_PUBKEY, + }, + &[self.account_data.as_ref()], + self.force_markets.readable.iter(), + self.force_markets.writeable.iter(), + ); + + let ix = Instruction { + program_id: constants::PROGRAM_ID, + accounts, + data: InstructionData::data(&drift_idl::instructions::EndSwap { + in_market_index, + out_market_index, + limit_price, + reduce_only, + }), + }; + self.ixs.push(ix); + + self + } + + pub async fn jupiter_swap_v6(mut self, jup: JupiterSwapApiClient) -> Self { + use jupiter_swap_api_client::{ + quote::QuoteRequest, swap::SwapRequest, transaction_config::TransactionConfig, + JupiterSwapApiClient, + }; + // TODO: set the params + let quote = jup.quote(&QuoteRequest { + input_mint: (), + output_mint: (), + amount: (), + swap_mode: (), + slippage_bps: (), + auto_slippage: (), + max_auto_slippage_bps: (), + compute_auto_slippage: (), + auto_slippage_collision_usd_value: (), + minimize_slippage: (), + platform_fee_bps: (), + dexes: (), + excluded_dexes: (), + only_direct_routes: (), + as_legacy_transaction: (), + restrict_intermediate_tokens: (), + max_accounts: (), + quote_type: (), + quote_args: (), + prefer_liquid_dexes: (), + compute_unit_score: (), + routing_constraints: (), + token_category_based_intermediate_tokens: () + }) + .await + .unwrap(); + + let swap_ix = jup.swap_instructions(&SwapRequest { user_public_key: (), quote_response: quote, config: () }).await.unwrap(); + // add `ixs` to self.ixs`` + } /// Build the transaction message ready for signing and sending pub fn build(self) -> VersionedMessage { diff --git a/crates/src/oraclemap.rs b/crates/src/oraclemap.rs index 9c80d9b..c2f6939 100644 --- a/crates/src/oraclemap.rs +++ b/crates/src/oraclemap.rs @@ -123,7 +123,7 @@ impl OracleMap { .contains_key(&(*oracle_pubkey, *oracle_source as u8)) || pending_subscriptions .iter() - .any(|(_, o)| &o.pubkey == oracle_pubkey && o.source == oracle_source) + .any(|(_, o)| &o.pubkey == oracle_pubkey && o.source == *oracle_source) { log::debug!(target: LOG_TARGET, "subscription exists: {market:?}/{oracle_pubkey:?}"); continue; From 79c76090c9a3259dc9726d433d59bc2edc71dea0 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 18 Apr 2025 09:04:47 +0800 Subject: [PATCH 04/12] Add jupiter api query --- crates/src/jupiter.rs | 58 +++++++++++++++++++++++++++++++++ crates/src/lib.rs | 76 +++++++++++++++---------------------------- 2 files changed, 85 insertions(+), 49 deletions(-) create mode 100644 crates/src/jupiter.rs diff --git a/crates/src/jupiter.rs b/crates/src/jupiter.rs new file mode 100644 index 0000000..3e66d19 --- /dev/null +++ b/crates/src/jupiter.rs @@ -0,0 +1,58 @@ +use jupiter_swap_api_client::{ + quote::{QuoteResponse, SwapMode}, + swap::SwapInstructionsResponse, + transaction_config::TransactionConfig, + JupiterSwapApiClient, +}; +use solana_sdk::pubkey::Pubkey; + +use crate::types::{SdkError, SdkResult}; + +/// Default Jupiter API url +const DEFAULT_JUPITER_API_URL: &str = "https://lite-api.jup.ag/v1"; + +/// Queries jupiter API for a quote and subsequent swap +/// +/// Returns (quote, swap ixs) on success +pub async fn get_jupiter_swap_ixs( + user_authority: Pubkey, + amount: u64, + swap_mode: SwapMode, + slippage_bps: u16, + input_mint: Pubkey, + output_mint: Pubkey, + transaction_config: Option, +) -> SdkResult<(QuoteResponse, SwapInstructionsResponse)> { + let jupiter_url = std::env::var("JUPITER_API_URL").unwrap_or(DEFAULT_JUPITER_API_URL.into()); + let jup_client = JupiterSwapApiClient::new(jupiter_url); + + // GET /quote + let quote_request = jupiter_swap_api_client::quote::QuoteRequest { + amount, + swap_mode: Some(swap_mode), + input_mint, + output_mint, + dexes: Some("Whirlpool,Meteora DLMM,Raydium CLMM".into()), + slippage_bps, + ..Default::default() + }; + + let quote_response = jup_client.quote("e_request).await.map_err(|err| { + log::error!("jupiter api request: {err:?}"); + SdkError::Generic(err.to_string()) + })?; + // POST /swap-instructions + let swap_instructions = jup_client + .swap_instructions(&jupiter_swap_api_client::swap::SwapRequest { + user_public_key: user_authority, + quote_response: quote_response.clone(), + config: transaction_config.unwrap_or_default(), + }) + .await + .map_err(|err| { + log::error!("jupiter api request: {err:?}"); + SdkError::Generic(err.to_string()) + })?; + + Ok((quote_response, swap_instructions)) +} diff --git a/crates/src/lib.rs b/crates/src/lib.rs index ac32f7b..d9aa11e 100644 --- a/crates/src/lib.rs +++ b/crates/src/lib.rs @@ -10,7 +10,11 @@ use std::{ use anchor_lang::{AccountDeserialize, Discriminator, InstructionData}; pub use drift_pubsub_client::PubsubClient; use futures_util::TryFutureExt; -use jupiter_swap_api_client::{quote::QuoteResponse, swap::SwapInstructionsResponse, JupiterSwapApiClient}; +use jupiter_swap_api_client::{ + quote::{self, QuoteResponse, SwapMode}, + swap::SwapInstructionsResponse, + JupiterSwapApiClient, +}; use log::debug; pub use solana_rpc_client::nonblocking::rpc_client::RpcClient; use solana_rpc_client_api::response::Response; @@ -30,9 +34,9 @@ use crate::{ account_map::AccountMap, blockhash_subscriber::BlockhashSubscriber, constants::{ - TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, derive_perp_market_account, derive_spot_market_account, state_account, MarketExt, - ProgramData, DEFAULT_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, + ProgramData, DEFAULT_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, TOKEN_2022_PROGRAM_ID, + TOKEN_PROGRAM_ID, }, drift_idl::traits::ToAccountMetas, grpc::grpc_subscriber::{AccountFilter, DriftGrpcClient, GeyserSubscribeOpts}, @@ -50,6 +54,7 @@ pub use crate::{grpc::GrpcSubscribeOpts, types::Context, wallet::Wallet}; // utils pub mod async_utils; pub mod ffi; +pub mod jupiter; pub mod math; pub mod memcmp; pub mod utils; @@ -1173,7 +1178,7 @@ impl DriftClientBackend { /// /// Uses latest local value from an `OracleMap` if subscribed, falls back to network query pub async fn get_oracle(&self, market: MarketId) -> SdkResult { - if self.oracle_map.is_subscribed(&market) { + if self.is_grpc_subscribed() || self.oracle_map.is_subscribed(&market) { Ok(self .try_get_oracle_price_data_and_slot(market) .expect("oracle exists")) @@ -2113,14 +2118,25 @@ impl<'a> TransactionBuilder<'a> { self } - pub fn jupiter_swap_v6( - mut self, - jupiter_swap_ixs: SwapInstructionsResponse, - ) -> Self { + pub fn jupiter_swap_v6(mut self, jupiter_swap_ixs: SwapInstructionsResponse) -> Self { // add `ixs` to self.ixs`` - self.begin_swap(out_market_index, in_market_index, amount_in, payer_token_account, payee_token_account); - self.ixs.push(jupiter_swap_ixs); - self.end_swap(out_market_index, in_market_index, payer_token_account, payee_token_account, limit_price, reduce_only); + // self.begin_swap( + // out_market_index, + // in_market_index, + // amount_in, + // payer_token_account, + // payee_token_account, + // ); + // // TODO: what order do I push the ixs here? + // self.ixs.push(jupiter_swap_ixs); + // self.end_swap( + // out_market_index, + // in_market_index, + // payer_token_account, + // payee_token_account, + // limit_price, + // reduce_only, + // ); self } @@ -2151,44 +2167,6 @@ impl<'a> TransactionBuilder<'a> { } } -/* -trait for building Jupiter Ixs -TRansactionBuilder add Jupiter client -add method to do -- Get quote -- Get Ixs -- Build Tx - */ -pub async fn jupiter_quote() -> QuoteResponse { - let jup = JupiterSwapApiClient::new("example.come"); - // TODO: set the params - jup.quote(&QuoteRequest { - input_mint: (), - output_mint: (), - amount: (), - swap_mode: (), - slippage_bps: (), - auto_slippage: (), - max_auto_slippage_bps: (), - compute_auto_slippage: (), - auto_slippage_collision_usd_value: (), - minimize_slippage: (), - platform_fee_bps: (), - dexes: (), - excluded_dexes: (), - only_direct_routes: (), - as_legacy_transaction: (), - restrict_intermediate_tokens: (), - max_accounts: (), - quote_type: (), - quote_args: (), - prefer_liquid_dexes: (), - compute_unit_score: (), - routing_constraints: (), - token_category_based_intermediate_tokens: () - }).await -} - /// Builds a set of required accounts from a user's open positions and additional given accounts /// /// * `base_accounts` - base anchor accounts From 209a1e9d767bbd1749b9cb5409d7e0f403111381 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 18 Apr 2025 09:13:15 +0800 Subject: [PATCH 05/12] fix oracle price query --- crates/src/lib.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/src/lib.rs b/crates/src/lib.rs index d9aa11e..6b44632 100644 --- a/crates/src/lib.rs +++ b/crates/src/lib.rs @@ -1178,10 +1178,8 @@ impl DriftClientBackend { /// /// Uses latest local value from an `OracleMap` if subscribed, falls back to network query pub async fn get_oracle(&self, market: MarketId) -> SdkResult { - if self.is_grpc_subscribed() || self.oracle_map.is_subscribed(&market) { - Ok(self - .try_get_oracle_price_data_and_slot(market) - .expect("oracle exists")) + if let Some(oracle) = self.try_get_oracle_price_data_and_slot(market) { + Ok(oracle) } else { debug!(target: "rpc", "fetch oracle account: {market:?}"); let (oracle, oracle_source) = match market.kind() { From 49db6c1e2de261f4a975d1658e00015978a283c5 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 18 Apr 2025 10:54:54 +0800 Subject: [PATCH 06/12] doc comments --- crates/src/jupiter.rs | 83 +++++++++++++++++++++++++++++++++++++++---- crates/src/lib.rs | 79 ++++++++++++++++++++++++++++------------ 2 files changed, 132 insertions(+), 30 deletions(-) diff --git a/crates/src/jupiter.rs b/crates/src/jupiter.rs index 3e66d19..1de8a04 100644 --- a/crates/src/jupiter.rs +++ b/crates/src/jupiter.rs @@ -4,25 +4,77 @@ use jupiter_swap_api_client::{ transaction_config::TransactionConfig, JupiterSwapApiClient, }; -use solana_sdk::pubkey::Pubkey; +use solana_rpc_client::nonblocking::rpc_client::RpcClient; +use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey}; -use crate::types::{SdkError, SdkResult}; +use crate::{ + types::{SdkError, SdkResult}, + utils, +}; /// Default Jupiter API url const DEFAULT_JUPITER_API_URL: &str = "https://lite-api.jup.ag/v1"; -/// Queries jupiter API for a quote and subsequent swap +/// jupiter swap IXs and metadata for building a swap Tx +pub struct JupiterSwapInfo { + pub quote: QuoteResponse, + pub ixs: SwapInstructionsResponse, + pub luts: Vec, +} + +/// Fetches Jupiter swap instructions for token swaps on Solana +/// +/// This function queries Jupiter API to get the optimal swap route and corresponding instructions +/// for swapping between two tokens. +/// +/// # Arguments +/// +/// * `rpc` - A Solana RPC client +/// * `user_authority` - The public key of the user's wallet that will execute the swap +/// * `amount` - The amount of input tokens to swap, in native units (smallest denomination) +/// * `swap_mode` - The type of swap to perform (e.g. ExactIn, ExactOut) +/// * `slippage_bps` - Maximum allowed slippage in basis points (1 bp = 0.01%) +/// * `input_mint` - The mint address of the token to swap from +/// * `output_mint` - The mint address of the token to swap to +/// * `only_direct_routes` - If Some(true), only consider direct swap routes between the tokens +/// * `excluded_dexes` - Optional comma-separated string of DEX names to exclude from routing +/// * `transaction_config` - Optional configuration for the swap transaction +/// +/// # Returns /// -/// Returns (quote, swap ixs) on success +/// Returns a `Result` containing `JupiterSwapInfo` with the swap instructions and route details +/// if successful, or a `SdkError` if the operation fails. +/// +/// # Example +/// +/// ```no_run +/// use solana_sdk::pubkey::Pubkey; +/// +/// let swap_info = get_jupiter_swap_ixs( +/// rpc_client, +/// user_wallet.pubkey(), +/// 1_000_000, // 1 USDC +/// SwapMode::ExactIn, +/// 50, // 0.5% slippage +/// usdc_mint, +/// sol_mint, +/// Some(true), +/// None, +/// None +/// ).await?; +/// ``` pub async fn get_jupiter_swap_ixs( + rpc: RpcClient, user_authority: Pubkey, amount: u64, swap_mode: SwapMode, slippage_bps: u16, input_mint: Pubkey, output_mint: Pubkey, + only_direct_routes: Option, + excluded_dexes: Option, transaction_config: Option, -) -> SdkResult<(QuoteResponse, SwapInstructionsResponse)> { +) -> SdkResult { let jupiter_url = std::env::var("JUPITER_API_URL").unwrap_or(DEFAULT_JUPITER_API_URL.into()); let jup_client = JupiterSwapApiClient::new(jupiter_url); @@ -32,8 +84,9 @@ pub async fn get_jupiter_swap_ixs( swap_mode: Some(swap_mode), input_mint, output_mint, - dexes: Some("Whirlpool,Meteora DLMM,Raydium CLMM".into()), slippage_bps, + only_direct_routes, + excluded_dexes, ..Default::default() }; @@ -54,5 +107,21 @@ pub async fn get_jupiter_swap_ixs( SdkError::Generic(err.to_string()) })?; - Ok((quote_response, swap_instructions)) + let res = rpc + .get_multiple_accounts(swap_instructions.address_lookup_table_addresses.as_slice()) + .await?; + + let luts = res + .iter() + .zip(swap_instructions.address_lookup_table_addresses.iter()) + .map(|(acc, key)| { + utils::deserialize_alt(*key, acc.as_ref().expect("deser LUT")).expect("deser LUT") + }) + .collect(); + + Ok(JupiterSwapInfo { + luts, + quote: quote_response, + ixs: swap_instructions, + }) } diff --git a/crates/src/lib.rs b/crates/src/lib.rs index 9a137a5..35e2b9a 100644 --- a/crates/src/lib.rs +++ b/crates/src/lib.rs @@ -10,6 +10,7 @@ use std::{ use anchor_lang::{AccountDeserialize, Discriminator, InstructionData}; pub use drift_pubsub_client::PubsubClient; use futures_util::TryFutureExt; +use jupiter::JupiterSwapInfo; use jupiter_swap_api_client::{ quote::{self, QuoteResponse, SwapMode}, swap::SwapInstructionsResponse, @@ -1354,9 +1355,7 @@ impl<'a> TransactionBuilder<'a> { } /// Extend the tx lookup tables (always includes the defacto drift LUTs) pub fn lookup_tables(mut self, lookup_tables: &[AddressLookupTableAccount]) -> Self { - self.lookup_tables = lookup_tables.to_vec(); - self.lookup_tables - .extend(self.program_data.lookup_tables.iter().cloned()); + self.lookup_tables.extend_from_slice(lookup_tables); self } @@ -2108,27 +2107,61 @@ impl<'a> TransactionBuilder<'a> { self } - pub fn jupiter_swap_v6(mut self, jupiter_swap_ixs: SwapInstructionsResponse) -> Self { - // add `ixs` to self.ixs`` - // self.begin_swap( - // out_market_index, - // in_market_index, - // amount_in, - // payer_token_account, - // payee_token_account, - // ); - // // TODO: what order do I push the ixs here? - // self.ixs.push(jupiter_swap_ixs); - // self.end_swap( - // out_market_index, - // in_market_index, - // payer_token_account, - // payee_token_account, - // limit_price, - // reduce_only, - // ); + /// Add a Jupiter v6 swap to the tx + /// + /// # Arguments + /// * `jupiter_swap_info` - Jupiter swap route and instructions + /// * `in_market_index` - Market index for input token + /// * `out_market_index` - Market index for output token + /// * `in_token_account` - Input token account pubkey + /// * `out_token_account` - Output token account pubkey + /// * `limit_price` - Optional minimum output amount + /// * `reduce_only` - Optional flag to only reduce positions + pub fn jupiter_swap_v6( + self, + jupiter_swap_info: JupiterSwapInfo, + in_market_index: u16, + out_market_index: u16, + in_token_account: &Pubkey, + out_token_account: &Pubkey, + limit_price: Option, + reduce_only: Option, + ) -> Self { + let mut new_self = self.begin_swap( + out_market_index, + in_market_index, + jupiter_swap_info.quote.in_amount, + in_token_account, + out_token_account, + ); - self + let jupiter_swap_ixs = jupiter_swap_info.ixs; + + // user account should initialize token accounts + new_self.ixs.extend(jupiter_swap_ixs.setup_instructions); + + // TODO: suppot jito bundle + if !jupiter_swap_ixs.other_instructions.is_empty() { + panic!("jupiter swap unsupported ix: Jito tip"); + } + + new_self.ixs.push(jupiter_swap_ixs.swap_instruction); + + // TODO: support unwrap SOL + if jupiter_swap_ixs.cleanup_instruction.is_some() { + panic!("jupiter swap unsupported ix: unwrap SOL"); + } + + new_self = new_self.lookup_tables(&jupiter_swap_info.luts); + + new_self.end_swap( + out_market_index, + in_market_index, + in_token_account, + out_token_account, + limit_price, + reduce_only, + ) } /// Build the transaction message ready for signing and sending From 7c46068edb9d5b0b37449705010bccd5312821bd Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 18 Apr 2025 20:59:21 +0800 Subject: [PATCH 07/12] add integration tests --- Cargo.lock | 1 + Cargo.toml | 1 + crates/src/constants.rs | 10 ++ crates/src/jupiter.rs | 218 ++++++++++++++++------------- crates/src/lib.rs | 233 ++++++++++++++++++------------- crates/src/types.rs | 15 +- crates/src/wallet.rs | 11 +- tests/jupiter.rs | 295 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 590 insertions(+), 194 deletions(-) create mode 100644 tests/jupiter.rs diff --git a/Cargo.lock b/Cargo.lock index 8fb20f2..070c367 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1395,6 +1395,7 @@ dependencies = [ "solana-rpc-client-api", "solana-sdk", "solana-transaction-status", + "spl-associated-token-account", "thiserror 1.0.69", "tokio", "tokio-stream", diff --git a/Cargo.toml b/Cargo.toml index 4879762..f579314 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ solana-rpc-client = "2.1" solana-rpc-client-api = "2.1" solana-sdk = "2.1" solana-transaction-status = "2.1" +spl-associated-token-account = "6.0" thiserror = "1" tokio = { version = "1.42", features = ["full"] } tokio-stream = "0.1.17" diff --git a/crates/src/constants.rs b/crates/src/constants.rs index 1760d57..60a133f 100644 --- a/crates/src/constants.rs +++ b/crates/src/constants.rs @@ -25,6 +25,8 @@ pub const JIT_PROXY_ID: Pubkey = /// Empty pubkey pub const DEFAULT_PUBKEY: Pubkey = solana_sdk::pubkey!("11111111111111111111111111111111"); +pub const SYSTEM_PROGRAM_ID: Pubkey = DEFAULT_PUBKEY; + static STATE_ACCOUNT: OnceLock = OnceLock::new(); /// Address of the SPL Token program @@ -35,6 +37,10 @@ pub const TOKEN_PROGRAM_ID: Pubkey = pub const TOKEN_2022_PROGRAM_ID: Pubkey = solana_sdk::pubkey!("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"); +/// Address of Associated Token Program +pub const ASSOCIATED_TOKEN_PROGRAM_ID: Pubkey = + solana_sdk::pubkey!("ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"); + /// Drift market lookup table (DevNet) pub const LUTS_DEVNET: &[Pubkey] = &[solana_sdk::pubkey!( "FaMS3U4uBojvGn5FSDEPimddcXsCfwkKsFgMVVnDdxGb" @@ -185,11 +191,15 @@ impl ProgramData { } /// Return the spot market config given a market index + /// + /// Useful for static metadata e.g. token program address pub fn spot_market_config_by_index(&self, market_index: u16) -> Option<&'static SpotMarket> { self.spot_markets.get(market_index as usize) } /// Return the perp market config given a market index + /// + /// Useful for static metadata e.g. token program address pub fn perp_market_config_by_index(&self, market_index: u16) -> Option<&'static PerpMarket> { self.perp_markets.get(market_index as usize) } diff --git a/crates/src/jupiter.rs b/crates/src/jupiter.rs index 1de8a04..61ba6d4 100644 --- a/crates/src/jupiter.rs +++ b/crates/src/jupiter.rs @@ -1,19 +1,19 @@ -use jupiter_swap_api_client::{ +//! Jupiter SDK helpers +pub use jupiter_swap_api_client::{ quote::{QuoteResponse, SwapMode}, swap::SwapInstructionsResponse, transaction_config::TransactionConfig, JupiterSwapApiClient, }; -use solana_rpc_client::nonblocking::rpc_client::RpcClient; use solana_sdk::{message::AddressLookupTableAccount, pubkey::Pubkey}; use crate::{ types::{SdkError, SdkResult}, - utils, + utils, DriftClient, }; /// Default Jupiter API url -const DEFAULT_JUPITER_API_URL: &str = "https://lite-api.jup.ag/v1"; +const DEFAULT_JUPITER_API_URL: &str = "https://lite-api.jup.ag/swap/v1"; /// jupiter swap IXs and metadata for building a swap Tx pub struct JupiterSwapInfo { @@ -22,106 +22,128 @@ pub struct JupiterSwapInfo { pub luts: Vec, } -/// Fetches Jupiter swap instructions for token swaps on Solana -/// -/// This function queries Jupiter API to get the optimal swap route and corresponding instructions -/// for swapping between two tokens. -/// -/// # Arguments -/// -/// * `rpc` - A Solana RPC client -/// * `user_authority` - The public key of the user's wallet that will execute the swap -/// * `amount` - The amount of input tokens to swap, in native units (smallest denomination) -/// * `swap_mode` - The type of swap to perform (e.g. ExactIn, ExactOut) -/// * `slippage_bps` - Maximum allowed slippage in basis points (1 bp = 0.01%) -/// * `input_mint` - The mint address of the token to swap from -/// * `output_mint` - The mint address of the token to swap to -/// * `only_direct_routes` - If Some(true), only consider direct swap routes between the tokens -/// * `excluded_dexes` - Optional comma-separated string of DEX names to exclude from routing -/// * `transaction_config` - Optional configuration for the swap transaction -/// -/// # Returns -/// -/// Returns a `Result` containing `JupiterSwapInfo` with the swap instructions and route details -/// if successful, or a `SdkError` if the operation fails. -/// -/// # Example -/// -/// ```no_run -/// use solana_sdk::pubkey::Pubkey; -/// -/// let swap_info = get_jupiter_swap_ixs( -/// rpc_client, -/// user_wallet.pubkey(), -/// 1_000_000, // 1 USDC -/// SwapMode::ExactIn, -/// 50, // 0.5% slippage -/// usdc_mint, -/// sol_mint, -/// Some(true), -/// None, -/// None -/// ).await?; -/// ``` -pub async fn get_jupiter_swap_ixs( - rpc: RpcClient, - user_authority: Pubkey, - amount: u64, - swap_mode: SwapMode, - slippage_bps: u16, - input_mint: Pubkey, - output_mint: Pubkey, - only_direct_routes: Option, - excluded_dexes: Option, - transaction_config: Option, -) -> SdkResult { - let jupiter_url = std::env::var("JUPITER_API_URL").unwrap_or(DEFAULT_JUPITER_API_URL.into()); - let jup_client = JupiterSwapApiClient::new(jupiter_url); +pub trait JupiterSwapApi { + fn jupiter_swap_query( + &self, + user_authority: &Pubkey, + amount: u64, + swap_mode: SwapMode, + in_market: u16, + out_market: u16, + slippage_bps: u16, + only_direct_routes: Option, + excluded_dexes: Option, + transaction_config: Option, + ) -> impl std::future::Future> + Send; +} - // GET /quote - let quote_request = jupiter_swap_api_client::quote::QuoteRequest { - amount, - swap_mode: Some(swap_mode), - input_mint, - output_mint, - slippage_bps, - only_direct_routes, - excluded_dexes, - ..Default::default() - }; +impl JupiterSwapApi for DriftClient { + /// Fetch Jupiter swap ixs and metadata for a token swap + /// + /// This function queries Jupiter API to get the optimal swap route and corresponding instructions + /// for swapping between two tokens. + /// + /// # Arguments + /// + /// * `rpc` - A Solana RPC client + /// * `user_authority` - The public key of the user's wallet that will execute the swap + /// * `amount` - The amount of input tokens to swap, in native units (smallest denomination) + /// * `swap_mode` - The type of swap to perform (e.g. ExactIn, ExactOut) + /// * `in_market` - The market index of the token to swap from + /// * `out_market` - The market index of the token to swap to + /// * `slippage_bps` - Maximum allowed slippage in basis points (1 bp = 0.01%) + /// * `only_direct_routes` - If Some(true), only consider direct swap routes between the tokens + /// * `excluded_dexes` - Optional comma-separated string of DEX names to exclude from routing + /// * `transaction_config` - Optional configuration for the swap transaction + /// + /// # Returns + /// + /// Returns a `Result` containing `JupiterSwapInfo` with the swap instructions and route details + /// if successful, or a `SdkError` if the operation fails. + /// + /// # Example + /// + /// ```no_run + /// use solana_sdk::pubkey::Pubkey; + /// + /// let swap_info = jupiter_swap_query( + /// rpc_client, + /// user_wallet.pubkey(), + /// 1_000_000, // 1 USDC + /// SwapMode::ExactIn, + /// 50, // 0.5% slippage + /// usdc_mint, + /// sol_mint, + /// Some(true), + /// None, + /// None + /// ).await?; + /// ``` + async fn jupiter_swap_query( + &self, + user_authority: &Pubkey, + amount: u64, + swap_mode: SwapMode, + slippage_bps: u16, + in_market: u16, + out_market: u16, + only_direct_routes: Option, + excluded_dexes: Option, + transaction_config: Option, + ) -> SdkResult { + let jupiter_url = + std::env::var("JUPITER_API_URL").unwrap_or(DEFAULT_JUPITER_API_URL.into()); + let jup_client = JupiterSwapApiClient::new(jupiter_url); - let quote_response = jup_client.quote("e_request).await.map_err(|err| { - log::error!("jupiter api request: {err:?}"); - SdkError::Generic(err.to_string()) - })?; - // POST /swap-instructions - let swap_instructions = jup_client - .swap_instructions(&jupiter_swap_api_client::swap::SwapRequest { - user_public_key: user_authority, - quote_response: quote_response.clone(), - config: transaction_config.unwrap_or_default(), - }) - .await - .map_err(|err| { + let in_market = self.try_get_spot_market_account(in_market)?; + let out_market = self.try_get_spot_market_account(out_market)?; + + // GET /quote + let quote_request = jupiter_swap_api_client::quote::QuoteRequest { + amount, + swap_mode: Some(swap_mode), + input_mint: in_market.mint, + output_mint: out_market.mint, + slippage_bps, + only_direct_routes, + excluded_dexes, + ..Default::default() + }; + + let quote_response = jup_client.quote("e_request).await.map_err(|err| { log::error!("jupiter api request: {err:?}"); SdkError::Generic(err.to_string()) })?; + // POST /swap-instructions + let swap_instructions = jup_client + .swap_instructions(&jupiter_swap_api_client::swap::SwapRequest { + user_public_key: *user_authority, + quote_response: quote_response.clone(), + config: transaction_config.unwrap_or_default(), + }) + .await + .map_err(|err| { + log::error!("jupiter api request: {err:?}"); + SdkError::Generic(err.to_string()) + })?; - let res = rpc - .get_multiple_accounts(swap_instructions.address_lookup_table_addresses.as_slice()) - .await?; + let res = self + .rpc() + .get_multiple_accounts(swap_instructions.address_lookup_table_addresses.as_slice()) + .await?; - let luts = res - .iter() - .zip(swap_instructions.address_lookup_table_addresses.iter()) - .map(|(acc, key)| { - utils::deserialize_alt(*key, acc.as_ref().expect("deser LUT")).expect("deser LUT") - }) - .collect(); + let luts = res + .iter() + .zip(swap_instructions.address_lookup_table_addresses.iter()) + .map(|(acc, key)| { + utils::deserialize_alt(*key, acc.as_ref().expect("deser LUT")).expect("deser LUT") + }) + .collect(); - Ok(JupiterSwapInfo { - luts, - quote: quote_response, - ixs: swap_instructions, - }) + Ok(JupiterSwapInfo { + luts, + quote: quote_response, + ixs: swap_instructions, + }) + } } diff --git a/crates/src/lib.rs b/crates/src/lib.rs index 35e2b9a..3c259b2 100644 --- a/crates/src/lib.rs +++ b/crates/src/lib.rs @@ -8,17 +8,17 @@ use std::{ }; use anchor_lang::{AccountDeserialize, Discriminator, InstructionData}; +use constants::{ + ASSOCIATED_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID, +}; pub use drift_pubsub_client::PubsubClient; use futures_util::TryFutureExt; -use jupiter::JupiterSwapInfo; -use jupiter_swap_api_client::{ - quote::{self, QuoteResponse, SwapMode}, - swap::SwapInstructionsResponse, - JupiterSwapApiClient, -}; use log::debug; pub use solana_rpc_client::nonblocking::rpc_client::RpcClient; -use solana_rpc_client_api::response::Response; +use solana_rpc_client_api::{ + config::RpcSimulateTransactionConfig, + response::{Response, RpcSimulateTransactionResult}, +}; use solana_sdk::{ account::Account, clock::Slot, @@ -36,11 +36,11 @@ use crate::{ blockhash_subscriber::BlockhashSubscriber, constants::{ derive_perp_market_account, derive_spot_market_account, state_account, MarketExt, - ProgramData, DEFAULT_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, TOKEN_2022_PROGRAM_ID, - TOKEN_PROGRAM_ID, + ProgramData, DEFAULT_PUBKEY, SYSVAR_INSTRUCTIONS_PUBKEY, }, drift_idl::traits::ToAccountMetas, grpc::grpc_subscriber::{AccountFilter, DriftGrpcClient, GeyserSubscribeOpts}, + jupiter::JupiterSwapInfo, marketmap::MarketMap, oraclemap::{Oracle, OracleMap}, swift_order_subscriber::{SignedOrderInfo, SwiftOrderStream}, @@ -484,6 +484,29 @@ impl DriftClient { self.backend.try_get_account(state_account()) } + /// Simulate the tx on remote RPC node + pub async fn simulate_tx( + &self, + tx: VersionedMessage, + ) -> SdkResult { + let response = self + .rpc() + .simulate_transaction_with_config( + &VersionedTransaction { + message: tx, + // must provide a signature for the RPC call to work + signatures: vec![Signature::new_unique()], + }, + RpcSimulateTransactionConfig { + sig_verify: false, + replace_recent_blockhash: true, + ..Default::default() + }, + ) + .await; + response.map(|r| r.value).map_err(Into::into) + } + /// Sign and send a tx to the network /// /// Returns the signature on success @@ -1382,7 +1405,7 @@ impl<'a> TransactionBuilder<'a> { pub fn deposit( mut self, amount: u64, - spot_market_index: u16, + market_index: u16, user_token_account: Pubkey, reduce_only: Option, ) -> Self { @@ -1393,20 +1416,24 @@ impl<'a> TransactionBuilder<'a> { user: self.sub_account, user_stats: Wallet::derive_stats_account(&self.authority), authority: self.authority, - spot_market_vault: constants::derive_spot_market_vault(spot_market_index), + spot_market_vault: constants::derive_spot_market_vault(market_index), user_token_account, - token_program: constants::TOKEN_PROGRAM_ID, + token_program: self + .program_data + .spot_market_config_by_index(market_index) + .unwrap() + .token_program(), }, &[self.account_data.as_ref()], self.force_markets.readable.iter(), - [MarketId::spot(spot_market_index)].iter(), + [MarketId::spot(market_index)].iter(), ); let ix = Instruction { program_id: constants::PROGRAM_ID, accounts, data: InstructionData::data(&drift_idl::instructions::Deposit { - market_index: spot_market_index, + market_index, amount, reduce_only: reduce_only.unwrap_or(false), }), @@ -1421,7 +1448,7 @@ impl<'a> TransactionBuilder<'a> { pub fn withdraw( mut self, amount: u64, - spot_market_index: u16, + market_index: u16, user_token_account: Pubkey, reduce_only: Option, ) -> Self { @@ -1432,14 +1459,18 @@ impl<'a> TransactionBuilder<'a> { user: self.sub_account, user_stats: Wallet::derive_stats_account(&self.authority), authority: self.authority, - spot_market_vault: constants::derive_spot_market_vault(spot_market_index), + spot_market_vault: constants::derive_spot_market_vault(market_index), user_token_account, drift_signer: constants::derive_drift_signer(), - token_program: constants::TOKEN_PROGRAM_ID, + token_program: self + .program_data + .spot_market_config_by_index(market_index) + .unwrap() + .token_program(), }, &[self.account_data.as_ref()], self.force_markets.readable.iter(), - [MarketId::spot(spot_market_index)] + [MarketId::spot(market_index)] .iter() .chain(self.force_markets.writeable.iter()), ); @@ -1448,7 +1479,7 @@ impl<'a> TransactionBuilder<'a> { program_id: constants::PROGRAM_ID, accounts, data: InstructionData::data(&drift_idl::instructions::Withdraw { - market_index: spot_market_index, + market_index, amount, reduce_only: reduce_only.unwrap_or(false), }), @@ -1975,32 +2006,14 @@ impl<'a> TransactionBuilder<'a> { /// This should be followed by a subsequent `end_swap` ix pub fn begin_swap( mut self, - out_market_index: u16, - in_market_index: u16, amount_in: u64, + in_market: &SpotMarket, + out_market: &SpotMarket, payer_token_account: &Pubkey, payee_token_account: &Pubkey, ) -> Self { - let out_market = self - .program_data - .spot_market_config_by_index(out_market_index) - .unwrap(); - let in_market = self - .program_data - .spot_market_config_by_index(in_market_index) - .unwrap(); - - let in_token_program = if in_market.token_program == 1 { - TOKEN_2022_PROGRAM_ID - } else { - TOKEN_PROGRAM_ID - }; - - let out_token_program = if out_market.token_program == 1 { - TOKEN_2022_PROGRAM_ID - } else { - TOKEN_PROGRAM_ID - }; + let in_token_program = in_market.token_program(); + let out_token_program = out_market.token_program(); let mut accounts = build_accounts( self.program_data, @@ -2018,25 +2031,29 @@ impl<'a> TransactionBuilder<'a> { instructions: SYSVAR_INSTRUCTIONS_PUBKEY, }, &[self.account_data.as_ref()], - self.force_markets.readable.iter(), - self.force_markets.writeable.iter(), + [MarketId::QUOTE_SPOT].iter(), + [ + MarketId::spot(in_market.market_index), + MarketId::spot(out_market.market_index), + ] + .iter(), ); if out_token_program != in_token_program { - accounts.push(AccountMeta::new(out_token_program, false)); + accounts.push(AccountMeta::new_readonly(out_token_program, false)); } if out_market.token_program == 1 || in_market.token_program == 1 { - accounts.push(AccountMeta::new(in_market.mint, false)); - accounts.push(AccountMeta::new(out_market.mint, false)); + accounts.push(AccountMeta::new_readonly(in_market.mint, false)); + accounts.push(AccountMeta::new_readonly(out_market.mint, false)); } let ix = Instruction { program_id: constants::PROGRAM_ID, accounts, data: InstructionData::data(&drift_idl::instructions::BeginSwap { - in_market_index, - out_market_index, + in_market_index: in_market.market_index, + out_market_index: out_market.market_index, amount_in, }), }; @@ -2050,27 +2067,14 @@ impl<'a> TransactionBuilder<'a> { /// This should follow a preceding `begin_swap` ix pub fn end_swap( mut self, - out_market_index: u16, - in_market_index: u16, + in_market: &SpotMarket, + out_market: &SpotMarket, payer_token_account: &Pubkey, payee_token_account: &Pubkey, limit_price: Option, reduce_only: Option, ) -> Self { - let out_market = self - .program_data - .spot_market_config_by_index(out_market_index) - .unwrap(); - let in_market = self - .program_data - .spot_market_config_by_index(in_market_index) - .unwrap(); - - let in_token_program = if in_market.token_program == 1 { - TOKEN_2022_PROGRAM_ID - } else { - TOKEN_PROGRAM_ID - }; + let in_token_program = in_market.token_program(); let accounts = build_accounts( self.program_data, @@ -2088,16 +2092,20 @@ impl<'a> TransactionBuilder<'a> { instructions: SYSVAR_INSTRUCTIONS_PUBKEY, }, &[self.account_data.as_ref()], - self.force_markets.readable.iter(), - self.force_markets.writeable.iter(), + [MarketId::QUOTE_SPOT].iter(), + [ + MarketId::spot(in_market.market_index), + MarketId::spot(out_market.market_index), + ] + .iter(), ); let ix = Instruction { program_id: constants::PROGRAM_ID, accounts, data: InstructionData::data(&drift_idl::instructions::EndSwap { - in_market_index, - out_market_index, + in_market_index: in_market.market_index, + out_market_index: out_market.market_index, limit_price, reduce_only, }), @@ -2107,61 +2115,98 @@ impl<'a> TransactionBuilder<'a> { self } - /// Add a Jupiter v6 swap to the tx + /// Add a Jupiter token swap to the tx /// /// # Arguments /// * `jupiter_swap_info` - Jupiter swap route and instructions - /// * `in_market_index` - Market index for input token - /// * `out_market_index` - Market index for output token + /// * `in_market` - Spot market of the input token + /// * `out_market` - Spot market of the output token /// * `in_token_account` - Input token account pubkey /// * `out_token_account` - Output token account pubkey - /// * `limit_price` - Optional minimum output amount - /// * `reduce_only` - Optional flag to only reduce positions - pub fn jupiter_swap_v6( - self, + /// * `limit_price` - Set a limit price + /// * `reduce_only` - Set a reduce only order + pub fn jupiter_swap( + mut self, jupiter_swap_info: JupiterSwapInfo, - in_market_index: u16, - out_market_index: u16, + in_market: &SpotMarket, + out_market: &SpotMarket, in_token_account: &Pubkey, out_token_account: &Pubkey, limit_price: Option, reduce_only: Option, ) -> Self { + let jupiter_swap_ixs = jupiter_swap_info.ixs; + + // initialize token accounts + if !jupiter_swap_ixs.setup_instructions.is_empty() { + // jupiter swap ixs imply account creation is required + // provide our own creation ixs + // new_self.ixs.extend(jupiter_swap_ixs.setup_instructions); + + // TODO: support alternative payer address e.g. delegate + let create_in_account_ix = Instruction { + program_id: ASSOCIATED_TOKEN_PROGRAM_ID, + accounts: vec![ + AccountMeta::new(self.authority, true), // payer + AccountMeta::new(*in_token_account, false), + AccountMeta::new_readonly(self.authority, false), // wallet + AccountMeta::new_readonly(in_market.mint, false), + AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false), + AccountMeta::new_readonly(in_market.token_program(), false), + ], + data: vec![1], // idempotent mode + }; + let create_out_account_ix = Instruction { + program_id: ASSOCIATED_TOKEN_PROGRAM_ID, + accounts: vec![ + AccountMeta::new(self.authority, true), // payer + AccountMeta::new(*out_token_account, false), + AccountMeta::new_readonly(self.authority, false), // wallet + AccountMeta::new_readonly(out_market.mint, false), + AccountMeta::new_readonly(SYSTEM_PROGRAM_ID, false), + AccountMeta::new_readonly(out_market.token_program(), false), + ], + data: vec![1], // idempotent mode + }; + self.ixs + .extend_from_slice(&[create_in_account_ix, create_out_account_ix]); + } + let mut new_self = self.begin_swap( - out_market_index, - in_market_index, jupiter_swap_info.quote.in_amount, + in_market, + out_market, in_token_account, out_token_account, ); - let jupiter_swap_ixs = jupiter_swap_info.ixs; - - // user account should initialize token accounts - new_self.ixs.extend(jupiter_swap_ixs.setup_instructions); - - // TODO: suppot jito bundle + // TODO: support jito bundle if !jupiter_swap_ixs.other_instructions.is_empty() { panic!("jupiter swap unsupported ix: Jito tip"); } new_self.ixs.push(jupiter_swap_ixs.swap_instruction); - // TODO: support unwrap SOL - if jupiter_swap_ixs.cleanup_instruction.is_some() { - panic!("jupiter swap unsupported ix: unwrap SOL"); + // support SOL unwrap ixs, ignore account delete/reclaim ixs + if let Some(unwrap_ix) = jupiter_swap_ixs.cleanup_instruction { + if unwrap_ix.program_id != TOKEN_PROGRAM_ID + && unwrap_ix.program_id != TOKEN_2022_PROGRAM_ID + { + new_self.ixs.push(unwrap_ix); + } } - new_self = new_self.lookup_tables(&jupiter_swap_info.luts); - - new_self.end_swap( - out_market_index, - in_market_index, + new_self = new_self.end_swap( + in_market, + out_market, in_token_account, out_token_account, limit_price, reduce_only, - ) + ); + + // Add the jup tx LUTs + new_self.lookup_tables(&jupiter_swap_info.luts) } /// Build the transaction message ready for signing and sending diff --git a/crates/src/types.rs b/crates/src/types.rs index 46c0e3e..e08ae4e 100644 --- a/crates/src/types.rs +++ b/crates/src/types.rs @@ -28,12 +28,14 @@ pub use crate::drift_idl::{ types::*, }; use crate::{ - constants::{ids, LUTS_DEVNET, LUTS_MAINNET}, + constants::{ids, LUTS_DEVNET, LUTS_MAINNET, TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID}, drift_idl::errors::ErrorCode, grpc::grpc_subscriber::GrpcError, Wallet, }; +use self::accounts::SpotMarket; + /// Map from K => V pub type MapOf = DashMap; @@ -46,6 +48,17 @@ pub fn is_one_of_variant(value: &T, variants: &[T]) -> bool { variants.iter().any(|variant| value == variant) } +impl SpotMarket { + /// Return the spot market's token program address + pub fn token_program(&self) -> Pubkey { + if self.token_program == 1 { + TOKEN_2022_PROGRAM_ID + } else { + TOKEN_PROGRAM_ID + } + } +} + /// Drift program context /// /// Contains network specific variables necessary for interacting with drift program diff --git a/crates/src/wallet.rs b/crates/src/wallet.rs index dca7d62..863371d 100644 --- a/crates/src/wallet.rs +++ b/crates/src/wallet.rs @@ -11,7 +11,7 @@ use solana_sdk::{ use crate::{ constants::{self}, - types::{SdkError, SdkResult}, + types::{accounts::SpotMarket, SdkError, SdkResult}, utils, }; @@ -159,6 +159,15 @@ impl Wallet { account_drift_pda } + /// Calculate the wallet's ATA for drift spot market + pub fn derive_associated_token_address(authority: &Pubkey, market: &SpotMarket) -> Pubkey { + spl_associated_token_account::get_associated_token_address_with_program_id( + authority, + &market.mint, + &market.token_program(), + ) + } + /// Signs a solana message (ixs, accounts) and builds a signed tx /// ready for sending over RPC /// diff --git a/tests/jupiter.rs b/tests/jupiter.rs new file mode 100644 index 0000000..1783377 --- /dev/null +++ b/tests/jupiter.rs @@ -0,0 +1,295 @@ +use drift_rs::{ + event_subscriber::RpcClient, + jupiter::{JupiterSwapApi, SwapMode}, + types::{accounts::User, Context, MarketId}, + utils::test_envs::{mainnet_endpoint, test_keypair}, + DriftClient, Pubkey, TransactionBuilder, Wallet, +}; +use solana_sdk::{ + instruction::InstructionError, native_token::LAMPORTS_PER_SOL, transaction::TransactionError, +}; +use tokio::sync::OnceCell; + +const DRIFT_CLIENT: OnceCell = OnceCell::const_new(); + +async fn drift_client() -> DriftClient { + DRIFT_CLIENT + .get_or_init(|| async move { + let wallet: Wallet = test_keypair().into(); + DriftClient::new( + Context::MainNet, + RpcClient::new(mainnet_endpoint()), + wallet.clone(), + ) + .await + .unwrap() + }) + .await + .clone() +} + +#[tokio::test] +async fn jupiter_swap_exact_in_udsc_to_sol() { + let _ = env_logger::try_init(); + let client = drift_client().await; + let wallet = client.wallet(); + + let token_in = MarketId::QUOTE_SPOT; + let token_out = MarketId::spot(1); + + let user: User = client + .get_user_account(&wallet.default_sub_account()) + .await + .expect("exists"); + + let jupiter_swap_info = client + .jupiter_swap_query( + wallet.authority(), + 10_000_000, + SwapMode::ExactIn, + 10, + token_in.index(), + token_out.index(), + Some(true), + None, + None, + ) + .await + .expect("got jup swap ixs"); + + let in_market = client + .program_data() + .spot_market_config_by_index(token_in.index()) + .unwrap(); + let out_market = client + .program_data() + .spot_market_config_by_index(token_out.index()) + .unwrap(); + + let in_token_account = Wallet::derive_associated_token_address(&wallet.authority(), &in_market); + let out_token_account = + Wallet::derive_associated_token_address(&wallet.authority(), &out_market); + + let tx = TransactionBuilder::new( + client.program_data(), + wallet.default_sub_account(), + std::borrow::Cow::Borrowed(&user), + false, + ) + .jupiter_swap( + jupiter_swap_info, + &in_market, + &out_market, + &in_token_account, + &out_token_account, + None, + None, + ) + .build(); + + let result = client.simulate_tx(tx).await; + dbg!(&result); + assert!(result.expect("sim ok").err.is_none()); +} + +#[tokio::test] +async fn jupiter_swap_exact_out_udsc_to_sol() { + let _ = env_logger::try_init(); + let client = drift_client().await; + let wallet = client.wallet(); + + let token_in = MarketId::QUOTE_SPOT; + let token_out = MarketId::spot(1); + + let user: User = client + .get_user_account(&wallet.default_sub_account()) + .await + .expect("exists"); + + let jupiter_swap_info = client + .jupiter_swap_query( + wallet.authority(), + 1 * LAMPORTS_PER_SOL, + SwapMode::ExactOut, + 10, + token_in.index(), + token_out.index(), + Some(true), + None, + None, + ) + .await + .expect("got jup swap ixs"); + + let in_market = client + .program_data() + .spot_market_config_by_index(token_in.index()) + .unwrap(); + let out_market = client + .program_data() + .spot_market_config_by_index(token_out.index()) + .unwrap(); + + let in_token_account = Wallet::derive_associated_token_address(&wallet.authority(), &in_market); + let out_token_account = + Wallet::derive_associated_token_address(&wallet.authority(), &out_market); + + let tx = TransactionBuilder::new( + client.program_data(), + wallet.default_sub_account(), + std::borrow::Cow::Borrowed(&user), + false, + ) + .jupiter_swap( + jupiter_swap_info, + &in_market, + &out_market, + &in_token_account, + &out_token_account, + None, + None, + ) + .build(); + + let result = client.simulate_tx(tx).await; + dbg!(&result); + assert!(result.expect("sim ok").err.is_none()); +} + +#[tokio::test] +async fn jupiter_swap_exact_out_udsc_jto() { + let _ = env_logger::try_init(); + let client = drift_client().await; + let wallet = client.wallet(); + + let token_in = MarketId::QUOTE_SPOT; + let token_out = client.market_lookup("JTO").unwrap(); + + let in_market = client + .program_data() + .spot_market_config_by_index(token_in.index()) + .unwrap(); + let out_market = client + .program_data() + .spot_market_config_by_index(token_out.index()) + .unwrap(); + + let user: User = client + .get_user_account(&wallet.default_sub_account()) + .await + .expect("exists"); + + let jupiter_swap_info = client + .jupiter_swap_query( + wallet.authority(), + 5 * 10_u64.pow(out_market.decimals), + SwapMode::ExactOut, + 10, + token_in.index(), + token_out.index(), + Some(true), + None, + None, + ) + .await + .expect("got jup swap ixs"); + + let in_token_account = Wallet::derive_associated_token_address(&wallet.authority(), &in_market); + let out_token_account = + Wallet::derive_associated_token_address(&wallet.authority(), &out_market); + + let tx = TransactionBuilder::new( + client.program_data(), + wallet.default_sub_account(), + std::borrow::Cow::Borrowed(&user), + false, + ) + .jupiter_swap( + jupiter_swap_info, + &in_market, + &out_market, + &in_token_account, + &out_token_account, + None, + None, + ) + .build(); + + let result = client.simulate_tx(tx).await; + dbg!(&result); + assert!(result.expect("sim ok").err.is_none()); +} + +#[tokio::test] +async fn jupiter_swap_sol_unwrap() { + let _ = env_logger::try_init(); + let client = drift_client().await; + let wallet = client.wallet(); + + let token_in = client.market_lookup("SOL").unwrap(); + let token_out = client.market_lookup("mSOL").unwrap(); + + let in_market = client + .program_data() + .spot_market_config_by_index(token_in.index()) + .unwrap(); + let out_market = client + .program_data() + .spot_market_config_by_index(token_out.index()) + .unwrap(); + + let user: User = client + .get_user_account(&wallet.default_sub_account()) + .await + .expect("exists"); + + let jupiter_swap_info = client + .jupiter_swap_query( + wallet.authority(), + 1 * LAMPORTS_PER_SOL, + SwapMode::ExactIn, + 10, + token_in.index(), + token_out.index(), + Some(true), + None, + None, + ) + .await + .expect("got jup swap ixs"); + + let in_token_account = Wallet::derive_associated_token_address(&wallet.authority(), &in_market); + let out_token_account = + Wallet::derive_associated_token_address(&wallet.authority(), &out_market); + + let tx = TransactionBuilder::new( + client.program_data(), + wallet.default_sub_account(), + std::borrow::Cow::Borrowed(&user), + false, + ) + .jupiter_swap( + jupiter_swap_info, + &in_market, + &out_market, + &in_token_account, + &out_token_account, + None, + None, + ) + .build(); + + let result = client.simulate_tx(tx).await; + dbg!(&result); + let err = result.expect("sim ok").err; + // either swap OK or it would incur borrow which is fine (test account missing 'token in' amount) + match err { + Some(err) => { + assert_eq!( + err, + TransactionError::InstructionError(4, InstructionError::Custom(6157)) + ) + } + None => assert!(true), + } +} From 9a12001f734c32dd8dfe2b4e0eebaa0b63554ad3 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 18 Apr 2025 21:05:10 +0800 Subject: [PATCH 08/12] point to jup sdk fork --- Cargo.lock | 1 + Cargo.toml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 070c367..3409e45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2430,6 +2430,7 @@ dependencies = [ [[package]] name = "jupiter-swap-api-client" version = "0.1.0" +source = "git+https://github.com/drift-labs/jupiter-swap-api-client#b699edf30ff1f561460579ee2504622e28f9a797" dependencies = [ "anyhow", "base64 0.22.1", diff --git a/Cargo.toml b/Cargo.toml index f579314..4cc8b50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,8 @@ dashmap = "6" env_logger = "0.11" futures-util = "0.3" hex = "0.4" -jupiter-swap-api-client = { path = "../jupiter-swap-api-client//jupiter-swap-api-client" } +# TODO: use jup-ag version once solana crate versions relaxed +jupiter-swap-api-client = { git = "https://github.com/drift-labs/jupiter-swap-api-client", package = "jupiter-swap-api-client" } log = "0.4" rayon = { version = "1.9.0", optional = true } regex = "1.10" From 3899bc1f6cdcc430952a4b90d193e4002a9482db Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 18 Apr 2025 21:17:42 +0800 Subject: [PATCH 09/12] enable jupiter --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3cb0f0b..279f63c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,6 +62,7 @@ jobs: run: | cargo test --no-fail-fast --lib -- --nocapture cargo test --no-fail-fast --test integration -- --nocapture --test-threads 1 + cargo test --no-fail-fast --test jupiter -- --nocapture --test-threads 2 env: TEST_DEVNET_RPC_ENDPOINT: ${{ secrets.DEVNET_RPC_ENDPOINT }} TEST_MAINNET_RPC_ENDPOINT: ${{ secrets.MAINNET_RPC_ENDPOINT }} From 7ce3368146bdc485f35a80482ee87c42e61037e4 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 18 Apr 2025 21:29:04 +0800 Subject: [PATCH 10/12] retry --- .github/workflows/build.yml | 1 + crates/src/utils.rs | 6 ++++++ tests/integration.rs | 1 - tests/jupiter.rs | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 279f63c..4340997 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,4 +67,5 @@ jobs: TEST_DEVNET_RPC_ENDPOINT: ${{ secrets.DEVNET_RPC_ENDPOINT }} TEST_MAINNET_RPC_ENDPOINT: ${{ secrets.MAINNET_RPC_ENDPOINT }} TEST_PRIVATE_KEY: ${{ secrets.TEST_PRIVATE_KEY }} + TEST_MAINNET_PRIVATE_KEY: ${{ secrets.TEST_MAINNET_PRIVATE_KEY }} TEST_GRPC_X_TOKEN: ${{ secrets.TEST_GRPC_X_TOKEN }} diff --git a/crates/src/utils.rs b/crates/src/utils.rs index 757c1fc..f2923be 100644 --- a/crates/src/utils.rs +++ b/crates/src/utils.rs @@ -152,6 +152,12 @@ pub mod test_envs { let private_key = std::env::var("TEST_PRIVATE_KEY").expect("TEST_PRIVATE_KEY set"); Keypair::from_base58_string(private_key.as_str()) } + /// keypair for mainnet integration tests + pub fn mainnet_test_keypair() -> Keypair { + let private_key = + std::env::var("TEST_MAINNET_PRIVATE_KEY").expect("TEST_MAINNET_PRIVATE_KEY set"); + Keypair::from_base58_string(private_key.as_str()) + } } /// copy of `solana_sdk::ed25519_instruction::Ed25519SignatureOffsets` diff --git a/tests/integration.rs b/tests/integration.rs index ef4588f..4b78c6b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -208,7 +208,6 @@ async fn place_and_cancel_orders() { let result = client.sign_and_send(tx).await; dbg!(&result); assert!(result.is_ok()); - client.unsubscribe().await.unwrap(); } #[ignore] diff --git a/tests/jupiter.rs b/tests/jupiter.rs index 1783377..eeda36f 100644 --- a/tests/jupiter.rs +++ b/tests/jupiter.rs @@ -3,7 +3,7 @@ use drift_rs::{ jupiter::{JupiterSwapApi, SwapMode}, types::{accounts::User, Context, MarketId}, utils::test_envs::{mainnet_endpoint, test_keypair}, - DriftClient, Pubkey, TransactionBuilder, Wallet, + DriftClient, TransactionBuilder, Wallet, }; use solana_sdk::{ instruction::InstructionError, native_token::LAMPORTS_PER_SOL, transaction::TransactionError, From be65afa53d520c7653b18efa7c65d351aa19ad2f Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 18 Apr 2025 21:33:31 +0800 Subject: [PATCH 11/12] retry --- tests/jupiter.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/jupiter.rs b/tests/jupiter.rs index eeda36f..20763f0 100644 --- a/tests/jupiter.rs +++ b/tests/jupiter.rs @@ -2,7 +2,7 @@ use drift_rs::{ event_subscriber::RpcClient, jupiter::{JupiterSwapApi, SwapMode}, types::{accounts::User, Context, MarketId}, - utils::test_envs::{mainnet_endpoint, test_keypair}, + utils::test_envs::{mainnet_endpoint, mainnet_test_keypair}, DriftClient, TransactionBuilder, Wallet, }; use solana_sdk::{ @@ -15,7 +15,7 @@ const DRIFT_CLIENT: OnceCell = OnceCell::const_new(); async fn drift_client() -> DriftClient { DRIFT_CLIENT .get_or_init(|| async move { - let wallet: Wallet = test_keypair().into(); + let wallet: Wallet = mainnet_test_keypair().into(); DriftClient::new( Context::MainNet, RpcClient::new(mainnet_endpoint()), From 4af3bd4bb0ec3c5ef2c8589cdbe57db8b3149492 Mon Sep 17 00:00:00 2001 From: jordy25519 Date: Fri, 18 Apr 2025 21:42:57 +0800 Subject: [PATCH 12/12] rerun --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4340997..ff60d2b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: run: | cargo test --no-fail-fast --lib -- --nocapture cargo test --no-fail-fast --test integration -- --nocapture --test-threads 1 - cargo test --no-fail-fast --test jupiter -- --nocapture --test-threads 2 + cargo test --no-fail-fast --test jupiter -- --nocapture --test-threads 1 env: TEST_DEVNET_RPC_ENDPOINT: ${{ secrets.DEVNET_RPC_ENDPOINT }} TEST_MAINNET_RPC_ENDPOINT: ${{ secrets.MAINNET_RPC_ENDPOINT }}