use anyhow::{anyhow, Context, Error, Result};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use solana_account_decoder::{UiAccount, UiAccountEncoding};
use solana_sdk::clock::Clock;
use std::collections::HashSet;
use std::sync::atomic::{AtomicI64, AtomicU64};
use std::sync::Arc;
use std::{collections::HashMap, convert::TryFrom, str::FromStr};
mod swap;
pub use swap::{Side, Swap};
use solana_sdk::{account::Account, instruction::AccountMeta, pubkey::Pubkey};
#[derive(Serialize, Deserialize, PartialEq, Clone, Copy, Default, Debug)]
pub enum SwapMode {
#[default]
ExactIn,
ExactOut,
}
impl FromStr for SwapMode {
type Err = Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
match s {
"ExactIn" => Ok(SwapMode::ExactIn),
"ExactOut" => Ok(SwapMode::ExactOut),
_ => Err(anyhow!("{} is not a valid SwapMode", s)),
}
}
}
#[derive(Debug)]
pub struct QuoteParams {
pub amount: u64,
pub input_mint: Pubkey,
pub output_mint: Pubkey,
pub swap_mode: SwapMode,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct Quote {
pub min_in_amount: Option<u64>,
pub min_out_amount: Option<u64>,
pub in_amount: u64,
pub out_amount: u64,
pub fee_amount: u64,
pub fee_mint: Pubkey,
pub fee_pct: Decimal,
}
pub type QuoteMintToReferrer = HashMap<Pubkey, Pubkey>;
pub struct SwapParams<'a, 'b> {
pub in_amount: u64,
pub out_amount: u64,
pub source_mint: Pubkey,
pub destination_mint: Pubkey,
pub source_token_account: Pubkey,
pub destination_token_account: Pubkey,
pub token_transfer_authority: Pubkey,
pub open_order_address: Option<Pubkey>,
pub quote_mint_to_referrer: Option<&'a QuoteMintToReferrer>,
pub jupiter_program_id: &'b Pubkey,
pub missing_dynamic_accounts_as_default: bool,
}
impl<'a, 'b> SwapParams<'a, 'b> {
pub fn placeholder_account_meta(&self) -> AccountMeta {
AccountMeta::new_readonly(*self.jupiter_program_id, false)
}
}
pub struct SwapAndAccountMetas {
pub swap: Swap,
pub account_metas: Vec<AccountMeta>,
}
#[derive(Clone)]
pub enum AmmUserSetup {
SerumDexOpenOrdersSetup { market: Pubkey, program_id: Pubkey },
}
pub type AccountMap = HashMap<Pubkey, Account>;
pub fn try_get_account_data<'a>(account_map: &'a AccountMap, address: &Pubkey) -> Result<&'a [u8]> {
account_map
.get(address)
.map(|account| account.data.as_slice())
.with_context(|| format!("Could not find address: {address}"))
}
pub struct AmmContext {
pub clock_ref: ClockRef,
}
pub trait Amm {
fn from_keyed_account(keyed_account: &KeyedAccount, amm_context: &AmmContext) -> Result<Self>
where
Self: Sized;
fn label(&self) -> String;
fn program_id(&self) -> Pubkey;
fn key(&self) -> Pubkey;
fn get_reserve_mints(&self) -> Vec<Pubkey>;
fn get_accounts_to_update(&self) -> Vec<Pubkey>;
fn update(&mut self, account_map: &AccountMap) -> Result<()>;
fn quote(&self, quote_params: &QuoteParams) -> Result<Quote>;
fn get_swap_and_account_metas(&self, swap_params: &SwapParams) -> Result<SwapAndAccountMetas>;
fn has_dynamic_accounts(&self) -> bool {
false
}
fn requires_update_for_reserve_mints(&self) -> bool {
false
}
fn supports_exact_out(&self) -> bool {
false
}
fn get_user_setup(&self) -> Option<AmmUserSetup> {
None
}
fn clone_amm(&self) -> Box<dyn Amm + Send + Sync>;
fn unidirectional(&self) -> bool {
false
}
fn program_dependencies(&self) -> Vec<(Pubkey, String)> {
vec![]
}
fn get_accounts_len(&self) -> usize {
32 }
fn underlying_liquidities(&self) -> Option<HashSet<Pubkey>> {
None
}
fn is_active(&self) -> bool {
true
}
}
impl Clone for Box<dyn Amm + Send + Sync> {
fn clone(&self) -> Box<dyn Amm + Send + Sync> {
self.clone_amm()
}
}
#[derive(Clone, Deserialize, Serialize)]
pub struct KeyedAccount {
pub key: Pubkey,
pub account: Account,
pub params: Option<Value>,
}
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
pub struct KeyedUiAccount {
pub pubkey: String,
#[serde(flatten)]
pub ui_account: UiAccount,
pub params: Option<Value>,
}
impl From<KeyedAccount> for KeyedUiAccount {
fn from(keyed_account: KeyedAccount) -> Self {
let KeyedAccount {
key,
account,
params,
} = keyed_account;
let ui_account = UiAccount::encode(&key, &account, UiAccountEncoding::Base64, None, None);
KeyedUiAccount {
pubkey: key.to_string(),
ui_account,
params,
}
}
}
impl TryFrom<KeyedUiAccount> for KeyedAccount {
type Error = Error;
fn try_from(keyed_ui_account: KeyedUiAccount) -> Result<Self, Self::Error> {
let KeyedUiAccount {
pubkey,
ui_account,
params,
} = keyed_ui_account;
let account = ui_account
.decode()
.unwrap_or_else(|| panic!("Failed to decode ui_account for {}", pubkey));
Ok(KeyedAccount {
key: Pubkey::from_str(&pubkey)?,
account,
params,
})
}
}
#[derive(Default, Clone)]
pub struct ClockRef {
pub slot: Arc<AtomicU64>,
pub epoch_start_timestamp: Arc<AtomicI64>,
pub epoch: Arc<AtomicU64>,
pub leader_schedule_epoch: Arc<AtomicU64>,
pub unix_timestamp: Arc<AtomicI64>,
}
impl ClockRef {
pub fn update(&self, clock: Clock) {
self.epoch
.store(clock.epoch, std::sync::atomic::Ordering::Relaxed);
self.slot
.store(clock.slot, std::sync::atomic::Ordering::Relaxed);
self.unix_timestamp
.store(clock.unix_timestamp, std::sync::atomic::Ordering::Relaxed);
self.epoch_start_timestamp.store(
clock.epoch_start_timestamp,
std::sync::atomic::Ordering::Relaxed,
);
self.leader_schedule_epoch.store(
clock.leader_schedule_epoch,
std::sync::atomic::Ordering::Relaxed,
);
}
}
impl From<Clock> for ClockRef {
fn from(clock: Clock) -> Self {
ClockRef {
epoch: Arc::new(AtomicU64::new(clock.epoch)),
epoch_start_timestamp: Arc::new(AtomicI64::new(clock.epoch_start_timestamp)),
leader_schedule_epoch: Arc::new(AtomicU64::new(clock.leader_schedule_epoch)),
slot: Arc::new(AtomicU64::new(clock.slot)),
unix_timestamp: Arc::new(AtomicI64::new(clock.unix_timestamp)),
}
}
}