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
17use 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 pub token_transfer_authority: Pubkey,
68 pub quote_mint_to_referrer: Option<&'a QuoteMintToReferrer>,
69 pub jupiter_program_id: &'b Pubkey,
70 pub missing_dynamic_accounts_as_default: bool,
73}
74
75impl SwapParams<'_, '_> {
76 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 fn label(&self) -> String;
117 fn program_id(&self) -> Pubkey;
118 fn key(&self) -> Pubkey;
120 fn get_reserve_mints(&self) -> Vec<Pubkey>;
122 fn get_accounts_to_update(&self) -> Vec<Pubkey>;
124 fn update(&mut self, account_map: &AccountMap) -> Result<()>;
127
128 fn quote(&self, quote_params: &QuoteParams) -> Result<Quote>;
129
130 fn get_swap_and_account_metas(&self, swap_params: &SwapParams) -> Result<SwapAndAccountMetas>;
132
133 fn has_dynamic_accounts(&self) -> bool {
135 false
136 }
137
138 fn requires_update_for_reserve_mints(&self) -> bool {
140 false
141 }
142
143 fn supports_exact_out(&self) -> bool {
145 false
146 }
147
148 fn clone_amm(&self) -> Box<dyn Amm + Send + Sync>;
149
150 fn unidirectional(&self) -> bool {
152 false
153 }
154
155 fn program_dependencies(&self) -> Vec<(Pubkey, String)> {
157 vec![]
158 }
159
160 fn get_accounts_len(&self) -> usize {
161 32 }
163
164 fn underlying_liquidities(&self) -> Option<HashSet<Pubkey>> {
170 None
171 }
172
173 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 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 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 pub epoch_start_timestamp: Arc<AtomicI64>,
298 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}