jupiter_amm_interface/
lib.rs

1use anyhow::{anyhow, Context, Error, Result};
2use rust_decimal::Decimal;
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use solana_account_decoder::{encode_ui_account, UiAccount, UiAccountEncoding};
6use solana_sdk::clock::Clock;
7use std::collections::HashSet;
8
9use std::sync::atomic::{AtomicI64, AtomicU64};
10use std::sync::Arc;
11use std::{collections::HashMap, convert::TryFrom, str::FromStr};
12mod custom_serde;
13mod swap;
14use custom_serde::field_as_string;
15pub use swap::{AccountsType, RemainingAccountsInfo, RemainingAccountsSlice, Side, Swap};
16
17/// An abstraction in order to share reserve mints and necessary data
18use solana_sdk::{account::Account, instruction::AccountMeta, pubkey::Pubkey};
19
20#[derive(Serialize, Deserialize, PartialEq, Clone, Copy, Default, Debug)]
21pub enum SwapMode {
22    #[default]
23    ExactIn,
24    ExactOut,
25}
26
27impl FromStr for SwapMode {
28    type Err = Error;
29
30    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
31        match s {
32            "ExactIn" => Ok(SwapMode::ExactIn),
33            "ExactOut" => Ok(SwapMode::ExactOut),
34            _ => Err(anyhow!("{} is not a valid SwapMode", s)),
35        }
36    }
37}
38
39#[derive(Debug)]
40pub struct QuoteParams {
41    pub amount: u64,
42    pub input_mint: Pubkey,
43    pub output_mint: Pubkey,
44    pub swap_mode: SwapMode,
45}
46
47#[derive(Debug, Default, Clone, Copy)]
48pub struct Quote {
49    pub in_amount: u64,
50    pub out_amount: u64,
51    pub fee_amount: u64,
52    pub fee_mint: Pubkey,
53    pub fee_pct: Decimal,
54}
55
56pub type QuoteMintToReferrer = HashMap<Pubkey, Pubkey, ahash::RandomState>;
57
58pub struct SwapParams<'a, 'b> {
59    pub swap_mode: SwapMode,
60    pub in_amount: u64,
61    pub out_amount: u64,
62    pub source_mint: Pubkey,
63    pub destination_mint: Pubkey,
64    pub source_token_account: Pubkey,
65    pub destination_token_account: Pubkey,
66    /// This can be the user or the program authority over the source_token_account.
67    pub token_transfer_authority: Pubkey,
68    pub quote_mint_to_referrer: Option<&'a QuoteMintToReferrer>,
69    pub jupiter_program_id: &'b Pubkey,
70    /// Instead of returning the relevant Err, replace dynamic accounts with the default Pubkey
71    /// This is useful for crawling market with no tick array
72    pub missing_dynamic_accounts_as_default: bool,
73}
74
75impl SwapParams<'_, '_> {
76    /// A placeholder to indicate an optional account or used as a terminator when consuming remaining accounts
77    /// Using the jupiter program id
78    pub fn placeholder_account_meta(&self) -> AccountMeta {
79        AccountMeta::new_readonly(*self.jupiter_program_id, false)
80    }
81}
82
83pub struct SwapAndAccountMetas {
84    pub swap: Swap,
85    pub account_metas: Vec<AccountMeta>,
86}
87
88pub type AccountMap = HashMap<Pubkey, Account, ahash::RandomState>;
89
90pub fn try_get_account_data<'a>(account_map: &'a AccountMap, address: &Pubkey) -> Result<&'a [u8]> {
91    account_map
92        .get(address)
93        .map(|account| account.data.as_slice())
94        .with_context(|| format!("Could not find address: {address}"))
95}
96
97pub fn try_get_account_data_and_owner<'a>(
98    account_map: &'a AccountMap,
99    address: &Pubkey,
100) -> Result<(&'a [u8], &'a Pubkey)> {
101    let account = account_map
102        .get(address)
103        .with_context(|| format!("Could not find address: {address}"))?;
104    Ok((account.data.as_slice(), &account.owner))
105}
106
107pub struct AmmContext {
108    pub clock_ref: ClockRef,
109}
110
111pub trait Amm {
112    fn from_keyed_account(keyed_account: &KeyedAccount, amm_context: &AmmContext) -> Result<Self>
113    where
114        Self: Sized;
115    /// A human readable label of the underlying DEX
116    fn label(&self) -> String;
117    fn program_id(&self) -> Pubkey;
118    /// The pool state or market state address
119    fn key(&self) -> Pubkey;
120    /// The mints that can be traded
121    fn get_reserve_mints(&self) -> Vec<Pubkey>;
122    /// The accounts necessary to produce a quote
123    fn get_accounts_to_update(&self) -> Vec<Pubkey>;
124    /// Picks necessary accounts to update it's internal state
125    /// Heavy deserialization and precomputation caching should be done in this function
126    fn update(&mut self, account_map: &AccountMap) -> Result<()>;
127
128    fn quote(&self, quote_params: &QuoteParams) -> Result<Quote>;
129
130    /// Indicates which Swap has to be performed along with all the necessary account metas
131    fn get_swap_and_account_metas(&self, swap_params: &SwapParams) -> Result<SwapAndAccountMetas>;
132
133    /// Indicates if get_accounts_to_update might return a non constant vec
134    fn has_dynamic_accounts(&self) -> bool {
135        false
136    }
137
138    /// Indicates whether `update` needs to be called before `get_reserve_mints`
139    fn requires_update_for_reserve_mints(&self) -> bool {
140        false
141    }
142
143    // Indicates that whether ExactOut mode is supported
144    fn supports_exact_out(&self) -> bool {
145        false
146    }
147
148    fn clone_amm(&self) -> Box<dyn Amm + Send + Sync>;
149
150    /// It can only trade in one direction from its first mint to second mint, assuming it is a two mint AMM
151    fn unidirectional(&self) -> bool {
152        false
153    }
154
155    /// For testing purposes, provide a mapping of dependency programs to function
156    fn program_dependencies(&self) -> Vec<(Pubkey, String)> {
157        vec![]
158    }
159
160    fn get_accounts_len(&self) -> usize {
161        32 // Default to a near whole legacy transaction to penalize no implementation
162    }
163
164    /// The identifier of the underlying liquidity
165    ///
166    /// Example:
167    /// For RaydiumAmm uses Openbook market A this will return Some(A)
168    /// For Openbook market A, it will also return Some(A)
169    fn underlying_liquidities(&self) -> Option<HashSet<Pubkey>> {
170        None
171    }
172
173    /// Provides a shortcut to establish if the AMM can be used for trading
174    /// If the market is active at all
175    fn is_active(&self) -> bool {
176        true
177    }
178}
179
180impl Clone for Box<dyn Amm + Send + Sync> {
181    fn clone(&self) -> Box<dyn Amm + Send + Sync> {
182        self.clone_amm()
183    }
184}
185
186pub type AmmLabel = &'static str;
187
188pub trait AmmProgramIdToLabel {
189    const PROGRAM_ID_TO_LABELS: &[(Pubkey, AmmLabel)];
190}
191
192pub trait SingleProgramAmm {
193    const PROGRAM_ID: Pubkey;
194    const LABEL: AmmLabel;
195}
196
197impl<T: SingleProgramAmm> AmmProgramIdToLabel for T {
198    const PROGRAM_ID_TO_LABELS: &[(Pubkey, AmmLabel)] = &[(Self::PROGRAM_ID, Self::LABEL)];
199}
200
201#[macro_export]
202macro_rules! single_program_amm {
203    ($amm_struct:ty, $program_id:expr, $label:expr) => {
204        impl SingleProgramAmm for $amm_struct {
205            const PROGRAM_ID: Pubkey = $program_id;
206            const LABEL: &'static str = $label;
207        }
208    };
209}
210
211#[derive(Clone, Deserialize, Serialize)]
212pub struct KeyedAccount {
213    pub key: Pubkey,
214    pub account: Account,
215    pub params: Option<Value>,
216}
217
218#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
219#[serde(rename_all = "camelCase")]
220pub struct Market {
221    #[serde(with = "field_as_string")]
222    pub pubkey: Pubkey,
223    #[serde(with = "field_as_string")]
224    pub owner: Pubkey,
225    /// Additional data an Amm requires, Amm dependent and decoded in the Amm implementation
226    pub params: Option<Value>,
227}
228
229impl From<KeyedAccount> for Market {
230    fn from(
231        KeyedAccount {
232            key,
233            account,
234            params,
235        }: KeyedAccount,
236    ) -> Self {
237        Market {
238            pubkey: key,
239            owner: account.owner,
240            params,
241        }
242    }
243}
244
245#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
246pub struct KeyedUiAccount {
247    pub pubkey: String,
248    #[serde(flatten)]
249    pub ui_account: UiAccount,
250    /// Additional data an Amm requires, Amm dependent and decoded in the Amm implementation
251    pub params: Option<Value>,
252}
253
254impl From<KeyedAccount> for KeyedUiAccount {
255    fn from(keyed_account: KeyedAccount) -> Self {
256        let KeyedAccount {
257            key,
258            account,
259            params,
260        } = keyed_account;
261
262        let ui_account = encode_ui_account(&key, &account, UiAccountEncoding::Base64, None, None);
263
264        KeyedUiAccount {
265            pubkey: key.to_string(),
266            ui_account,
267            params,
268        }
269    }
270}
271
272impl TryFrom<KeyedUiAccount> for KeyedAccount {
273    type Error = Error;
274
275    fn try_from(keyed_ui_account: KeyedUiAccount) -> Result<Self, Self::Error> {
276        let KeyedUiAccount {
277            pubkey,
278            ui_account,
279            params,
280        } = keyed_ui_account;
281        let account = ui_account
282            .decode()
283            .unwrap_or_else(|| panic!("Failed to decode ui_account for {pubkey}"));
284
285        Ok(KeyedAccount {
286            key: Pubkey::from_str(&pubkey)?,
287            account,
288            params,
289        })
290    }
291}
292
293#[derive(Default, Clone)]
294pub struct ClockRef {
295    pub slot: Arc<AtomicU64>,
296    /// The timestamp of the first `Slot` in this `Epoch`.
297    pub epoch_start_timestamp: Arc<AtomicI64>,
298    /// The current `Epoch`.
299    pub epoch: Arc<AtomicU64>,
300    pub leader_schedule_epoch: Arc<AtomicU64>,
301    pub unix_timestamp: Arc<AtomicI64>,
302}
303
304impl ClockRef {
305    pub fn update(&self, clock: Clock) {
306        self.epoch
307            .store(clock.epoch, std::sync::atomic::Ordering::Relaxed);
308        self.slot
309            .store(clock.slot, std::sync::atomic::Ordering::Relaxed);
310        self.unix_timestamp
311            .store(clock.unix_timestamp, std::sync::atomic::Ordering::Relaxed);
312        self.epoch_start_timestamp.store(
313            clock.epoch_start_timestamp,
314            std::sync::atomic::Ordering::Relaxed,
315        );
316        self.leader_schedule_epoch.store(
317            clock.leader_schedule_epoch,
318            std::sync::atomic::Ordering::Relaxed,
319        );
320    }
321}
322
323impl From<Clock> for ClockRef {
324    fn from(clock: Clock) -> Self {
325        ClockRef {
326            epoch: Arc::new(AtomicU64::new(clock.epoch)),
327            epoch_start_timestamp: Arc::new(AtomicI64::new(clock.epoch_start_timestamp)),
328            leader_schedule_epoch: Arc::new(AtomicU64::new(clock.leader_schedule_epoch)),
329            slot: Arc::new(AtomicU64::new(clock.slot)),
330            unix_timestamp: Arc::new(AtomicI64::new(clock.unix_timestamp)),
331        }
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338    use solana_sdk::pubkey;
339
340    #[test]
341    fn test_market_deserialization() {
342        let json = r#"
343        {
344            "lamports": 1000,
345            "owner": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
346            "pubkey": "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
347            "executable": false,
348            "rentEpoch": 0
349        }
350        "#;
351        let market: Market = serde_json::from_str(json).unwrap();
352        assert_eq!(
353            market.owner,
354            pubkey!("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
355        );
356        assert_eq!(
357            market.pubkey,
358            pubkey!("DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263")
359        );
360    }
361}