use std::fmt;
use solana_address::Address;
use super::pda::find_associated_token_pda;
use crate::constants::TOKEN_2022_PROGRAM_ID;
use crate::math::amm::{BuyQuote, MintFee, QuoteError, SellQuote};
pub use crate::launchpad::types::TradeDirection;
impl From<TradeDirection> for crate::dex::types::TradeDirection {
fn from(direction: TradeDirection) -> Self {
match direction {
TradeDirection::Buy => Self::Buy,
TradeDirection::Sell => Self::Sell,
}
}
}
impl From<crate::dex::types::TradeDirection> for TradeDirection {
fn from(direction: crate::dex::types::TradeDirection) -> Self {
match direction {
crate::dex::types::TradeDirection::Buy => Self::Buy,
crate::dex::types::TradeDirection::Sell => Self::Sell,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum TradeMode {
ExactIn,
ExactOut,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TradeAccounts {
pub user: Address,
pub payer: Address,
pub market: Address,
pub base_mint: Address,
pub quote_mint: Address,
pub base_vault: Address,
pub quote_vault: Address,
pub user_base_account: Address,
pub user_quote_account: Address,
pub partner: Address,
pub partner_config: Address,
pub quote_token_program: Address,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct QuoteRequest {
pub direction: TradeDirection,
pub mode: TradeMode,
pub amount: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Landing {
pub base_fee: Option<MintFee>,
pub quote_fee: Option<MintFee>,
pub unix_timestamp: i64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct MarketQuote {
pub in_amount: u64,
pub out_amount: u64,
pub fee: u64,
pub fee_bps: u16,
pub supply_capped: bool,
}
impl MarketQuote {
pub(crate) const fn bought(
quote: &BuyQuote,
request: QuoteRequest,
fee_bps: u16,
) -> Self {
let requested = match request.mode {
TradeMode::ExactIn => quote.quote_from_user,
TradeMode::ExactOut => quote.base_to_user,
};
Self {
in_amount: quote.quote_from_user,
out_amount: quote.base_to_user,
fee: quote.fee,
fee_bps,
supply_capped: requested < request.amount,
}
}
pub(crate) const fn sold(quote: &SellQuote, fee_bps: u16) -> Self {
Self {
in_amount: quote.base_from_user,
out_amount: quote.quote_to_user,
fee: quote.fee,
fee_bps,
supply_capped: false,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MarketQuoteError {
Quote(QuoteError),
FeeOutOfRange,
SupplyExhausted,
ExceedsQuoteHeld,
}
impl From<QuoteError> for MarketQuoteError {
fn from(error: QuoteError) -> Self {
Self::Quote(error)
}
}
impl fmt::Display for MarketQuoteError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Quote(error) => error.fmt(f),
Self::FeeOutOfRange => write!(f, "fee out of range"),
Self::SupplyExhausted => write!(f, "the curve has no supply left"),
Self::ExceedsQuoteHeld => {
write!(f, "sell exceeds the quote the curve holds")
}
}
}
}
impl std::error::Error for MarketQuoteError {}
pub(crate) struct TradeStateFields<'a> {
pub base_mint: &'a Address,
pub quote_mint: &'a Address,
pub platform_config: &'a Address,
}
pub(crate) struct DerivedTradeAccounts {
pub user_base_account: Address,
pub user_quote_account: Address,
pub partner_config: Address,
}
pub(crate) struct TradeUserContext<'a> {
pub user: &'a Address,
pub partner: &'a Address,
pub quote_token_program: &'a Address,
}
pub(crate) struct DeriveTradeArgs<'a> {
pub state: &'a TradeStateFields<'a>,
pub ctx: TradeUserContext<'a>,
}
pub(crate) fn derive_trade_accounts(
args: DeriveTradeArgs<'_>,
) -> DerivedTradeAccounts {
let state = args.state;
let user = args.ctx.user;
let partner = args.ctx.partner;
let quote_token_program = args.ctx.quote_token_program;
let (user_base_account, _) = find_associated_token_pda(
user,
state.base_mint,
&TOKEN_2022_PROGRAM_ID,
);
let (user_quote_account, _) =
find_associated_token_pda(user, state.quote_mint, quote_token_program);
let (partner_config, _) = crate::nexus::pda::find_partner_config_pda(
state.platform_config,
partner,
);
DerivedTradeAccounts {
user_base_account,
user_quote_account,
partner_config,
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct TradeLegInput {
pub accounts: TradeAccounts,
pub direction: TradeDirection,
}
pub(crate) fn legs_of_trade(
input: &TradeLegInput,
writes_market: bool,
) -> crate::transfer_hook::HookLegs {
use crate::transfer_hook::{HookLeg, HookLegs};
let TradeLegInput {
accounts: a,
direction,
} = *input;
let into_market = |source, mint, vault| HookLeg {
source,
mint,
destination: vault,
authority: a.user,
destination_owner: a.market,
};
let out_of_market = |vault, mint, destination| HookLeg {
source: vault,
mint,
destination,
authority: a.market,
destination_owner: a.user,
};
let legs = match direction {
TradeDirection::Buy => vec![
into_market(a.user_quote_account, a.quote_mint, a.quote_vault),
out_of_market(a.base_vault, a.base_mint, a.user_base_account),
],
TradeDirection::Sell => vec![
into_market(a.user_base_account, a.base_mint, a.base_vault),
out_of_market(a.quote_vault, a.quote_mint, a.user_quote_account),
],
};
HookLegs {
legs,
written: if writes_market {
vec![a.market]
} else {
Vec::new()
},
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum BuildError {
Quote(QuoteError),
Hook(crate::transfer_hook::HookError),
BaseNotToken2022(Address),
HookNotRead(Address),
}
impl From<QuoteError> for BuildError {
fn from(error: QuoteError) -> Self {
Self::Quote(error)
}
}
impl From<crate::math::amm::AmmError> for BuildError {
fn from(error: crate::math::amm::AmmError) -> Self {
Self::Quote(error.into())
}
}
impl From<crate::transfer_hook::HookError> for BuildError {
fn from(error: crate::transfer_hook::HookError) -> Self {
Self::Hook(error)
}
}
impl fmt::Display for BuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Quote(error) => error.fmt(f),
Self::Hook(error) => error.fmt(f),
Self::BaseNotToken2022(mint) => {
write!(f, "the base mint {mint} is not a Token-2022 mint")
}
Self::HookNotRead(mint) => {
write!(f, "the mint {mint} has a hook program and no hook")
}
}
}
}
impl std::error::Error for BuildError {}
pub(crate) struct TradeHookStep<'a> {
pub input: TradeLegInput,
pub writes_market: bool,
pub mints: super::TradeMints<'a>,
}
pub(crate) fn append_trade_hooks(
instruction: solana_instruction::Instruction,
step: &TradeHookStep<'_>,
) -> Result<solana_instruction::Instruction, BuildError> {
super::append_hooks(
instruction,
step.mints,
&legs_of_trade(&step.input, step.writes_market),
)
}