1use std::fmt;
2
3use solana_address::Address;
4
5use super::pda::find_associated_token_pda;
6use crate::constants::TOKEN_2022_PROGRAM_ID;
7use crate::math::amm::{BuyQuote, MintFee, QuoteError, SellQuote};
8
9pub use crate::launchpad::types::TradeDirection;
12
13impl From<TradeDirection> for crate::dex::types::TradeDirection {
14 fn from(direction: TradeDirection) -> Self {
15 match direction {
16 TradeDirection::Buy => Self::Buy,
17 TradeDirection::Sell => Self::Sell,
18 }
19 }
20}
21
22impl From<crate::dex::types::TradeDirection> for TradeDirection {
23 fn from(direction: crate::dex::types::TradeDirection) -> Self {
24 match direction {
25 crate::dex::types::TradeDirection::Buy => Self::Buy,
26 crate::dex::types::TradeDirection::Sell => Self::Sell,
27 }
28 }
29}
30
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32pub enum TradeMode {
33 ExactIn,
34 ExactOut,
35}
36
37#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
40pub struct TradeAccounts {
41 pub user: Address,
42 pub payer: Address,
43 pub market: Address,
45 pub base_mint: Address,
46 pub quote_mint: Address,
47 pub base_vault: Address,
48 pub quote_vault: Address,
49 pub user_base_account: Address,
50 pub user_quote_account: Address,
51 pub partner: Address,
53 pub partner_config: Address,
55 pub quote_token_program: Address,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
59pub struct QuoteRequest {
60 pub direction: TradeDirection,
61 pub mode: TradeMode,
62 pub amount: u64,
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub struct Landing {
73 pub base_fee: Option<MintFee>,
74 pub quote_fee: Option<MintFee>,
75 pub unix_timestamp: i64,
76}
77
78#[derive(Clone, Copy, Debug, PartialEq, Eq)]
81#[non_exhaustive]
82pub struct MarketQuote {
83 pub in_amount: u64,
85 pub out_amount: u64,
87 pub fee: u64,
89 pub fee_bps: u16,
91 pub supply_capped: bool,
95}
96
97impl MarketQuote {
98 pub(crate) const fn bought(
100 quote: &BuyQuote,
101 request: QuoteRequest,
102 fee_bps: u16,
103 ) -> Self {
104 let requested = match request.mode {
105 TradeMode::ExactIn => quote.quote_from_user,
106 TradeMode::ExactOut => quote.base_to_user,
107 };
108 Self {
109 in_amount: quote.quote_from_user,
110 out_amount: quote.base_to_user,
111 fee: quote.fee,
112 fee_bps,
113 supply_capped: requested < request.amount,
114 }
115 }
116
117 pub(crate) const fn sold(quote: &SellQuote, fee_bps: u16) -> Self {
118 Self {
119 in_amount: quote.base_from_user,
120 out_amount: quote.quote_to_user,
121 fee: quote.fee,
122 fee_bps,
123 supply_capped: false,
124 }
125 }
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
130#[non_exhaustive]
131pub enum MarketQuoteError {
132 Quote(QuoteError),
133 FeeOutOfRange,
134 SupplyExhausted,
136 ExceedsQuoteHeld,
138}
139
140impl From<QuoteError> for MarketQuoteError {
141 fn from(error: QuoteError) -> Self {
142 Self::Quote(error)
143 }
144}
145
146impl fmt::Display for MarketQuoteError {
147 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
148 match self {
149 Self::Quote(error) => error.fmt(f),
150 Self::FeeOutOfRange => write!(f, "fee out of range"),
151 Self::SupplyExhausted => write!(f, "the curve has no supply left"),
152 Self::ExceedsQuoteHeld => {
153 write!(f, "sell exceeds the quote the curve holds")
154 }
155 }
156 }
157}
158
159impl std::error::Error for MarketQuoteError {}
160
161pub(crate) struct TradeStateFields<'a> {
162 pub base_mint: &'a Address,
163 pub quote_mint: &'a Address,
164 pub platform_config: &'a Address,
166}
167
168pub(crate) struct DerivedTradeAccounts {
169 pub user_base_account: Address,
170 pub user_quote_account: Address,
171 pub partner_config: Address,
172}
173
174pub(crate) struct TradeUserContext<'a> {
175 pub user: &'a Address,
176 pub partner: &'a Address,
177 pub quote_token_program: &'a Address,
178}
179
180pub(crate) struct DeriveTradeArgs<'a> {
181 pub state: &'a TradeStateFields<'a>,
182 pub ctx: TradeUserContext<'a>,
183}
184
185pub(crate) fn derive_trade_accounts(
186 args: DeriveTradeArgs<'_>,
187) -> DerivedTradeAccounts {
188 let state = args.state;
189 let user = args.ctx.user;
190 let partner = args.ctx.partner;
191 let quote_token_program = args.ctx.quote_token_program;
192
193 let (user_base_account, _) = find_associated_token_pda(
194 user,
195 state.base_mint,
196 &TOKEN_2022_PROGRAM_ID,
197 );
198 let (user_quote_account, _) =
199 find_associated_token_pda(user, state.quote_mint, quote_token_program);
200 let (partner_config, _) = crate::nexus::pda::find_partner_config_pda(
201 state.platform_config,
202 partner,
203 );
204
205 DerivedTradeAccounts {
206 user_base_account,
207 user_quote_account,
208 partner_config,
209 }
210}
211
212#[derive(Clone, Copy, Debug)]
214pub(crate) struct TradeLegInput {
215 pub accounts: TradeAccounts,
216 pub direction: TradeDirection,
217}
218
219pub(crate) fn legs_of_trade(
225 input: &TradeLegInput,
226 writes_market: bool,
227) -> crate::transfer_hook::HookLegs {
228 use crate::transfer_hook::{HookLeg, HookLegs};
229
230 let TradeLegInput {
231 accounts: a,
232 direction,
233 } = *input;
234 let into_market = |source, mint, vault| HookLeg {
235 source,
236 mint,
237 destination: vault,
238 authority: a.user,
239 destination_owner: a.market,
240 };
241 let out_of_market = |vault, mint, destination| HookLeg {
242 source: vault,
243 mint,
244 destination,
245 authority: a.market,
246 destination_owner: a.user,
247 };
248 let legs = match direction {
249 TradeDirection::Buy => vec![
250 into_market(a.user_quote_account, a.quote_mint, a.quote_vault),
251 out_of_market(a.base_vault, a.base_mint, a.user_base_account),
252 ],
253 TradeDirection::Sell => vec![
254 into_market(a.user_base_account, a.base_mint, a.base_vault),
255 out_of_market(a.quote_vault, a.quote_mint, a.user_quote_account),
256 ],
257 };
258 HookLegs {
259 legs,
260 written: if writes_market {
261 vec![a.market]
262 } else {
263 Vec::new()
264 },
265 }
266}
267
268#[derive(Clone, Debug, PartialEq, Eq)]
270#[non_exhaustive]
271pub enum BuildError {
272 Quote(QuoteError),
273 Hook(crate::transfer_hook::HookError),
274 BaseNotToken2022(Address),
276 HookNotRead(Address),
279}
280
281impl From<QuoteError> for BuildError {
282 fn from(error: QuoteError) -> Self {
283 Self::Quote(error)
284 }
285}
286
287impl From<crate::math::amm::AmmError> for BuildError {
288 fn from(error: crate::math::amm::AmmError) -> Self {
289 Self::Quote(error.into())
290 }
291}
292
293impl From<crate::transfer_hook::HookError> for BuildError {
294 fn from(error: crate::transfer_hook::HookError) -> Self {
295 Self::Hook(error)
296 }
297}
298
299impl fmt::Display for BuildError {
300 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301 match self {
302 Self::Quote(error) => error.fmt(f),
303 Self::Hook(error) => error.fmt(f),
304 Self::BaseNotToken2022(mint) => {
305 write!(f, "the base mint {mint} is not a Token-2022 mint")
306 }
307 Self::HookNotRead(mint) => {
308 write!(f, "the mint {mint} has a hook program and no hook")
309 }
310 }
311 }
312}
313
314impl std::error::Error for BuildError {}
315
316pub(crate) struct TradeHookStep<'a> {
318 pub input: TradeLegInput,
319 pub writes_market: bool,
320 pub mints: super::TradeMints<'a>,
321}
322
323pub(crate) fn append_trade_hooks(
324 instruction: solana_instruction::Instruction,
325 step: &TradeHookStep<'_>,
326) -> Result<solana_instruction::Instruction, BuildError> {
327 super::append_hooks(
328 instruction,
329 step.mints,
330 &legs_of_trade(&step.input, step.writes_market),
331 )
332}