Skip to main content

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