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 pub token_transfer_authority: Pubkey,
78 pub user: Pubkey,
80 pub payer: Pubkey,
82 pub quote_mint_to_referrer: Option<&'a QuoteMintToReferrer>,
83 pub jupiter_program_id: &'b Pubkey,
84 pub missing_dynamic_accounts_as_default: bool,
87}
88
89impl SwapParams<'_, '_> {
90 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 fn label(&self) -> String;
132 fn program_id(&self) -> Pubkey;
133 fn key(&self) -> Pubkey;
135 fn get_reserve_mints(&self) -> Vec<Pubkey>;
137 fn get_accounts_to_update(&self) -> Vec<Pubkey>;
139 fn update(&mut self, account_map: &AccountMap) -> Result<()>;
142
143 fn quote(&self, quote_params: &QuoteParams) -> Result<Quote>;
144
145 fn get_swap_and_account_metas(&self, swap_params: &SwapParams) -> Result<SwapAndAccountMetas>;
147
148 fn has_dynamic_accounts(&self) -> bool {
150 false
151 }
152
153 fn requires_update_for_reserve_mints(&self) -> bool {
155 false
156 }
157
158 fn supports_exact_out(&self) -> bool {
160 false
161 }
162
163 fn clone_amm(&self) -> Box<dyn Amm + Send + Sync>;
164
165 fn unidirectional(&self) -> bool {
167 false
168 }
169
170 fn program_dependencies(&self) -> Vec<(Pubkey, String)> {
172 vec![]
173 }
174
175 fn get_accounts_len(&self) -> usize {
176 32 }
178
179 fn underlying_liquidities(&self) -> Option<HashSet<Pubkey>> {
185 None
186 }
187
188 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 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 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 pub epoch_start_timestamp: Arc<AtomicI64>,
313 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}