fyuz/models/trade.rs
1//! Trades and OHLCV candles.
2
3use serde::{Deserialize, Deserializer};
4
5use super::token::Token;
6
7/// Side of a trade.
8///
9/// Deserialisation is tolerant in two ways. A value the API adds later lands in
10/// [`TradeSide::Other`] instead of failing the whole response, and the casing is
11/// folded: `/trades/recent` sends `"buy"`/`"sell"` while the trades embedded in
12/// `/tokens/{network}/{token_address}` send `"BUY"`/`"SELL"`, and both map to
13/// [`TradeSide::Buy`] and [`TradeSide::Sell`].
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub enum TradeSide {
16 /// Bought the token (BNB in, tokens out).
17 Buy,
18 /// Sold the token (tokens in, BNB out).
19 Sell,
20 /// A side this SDK version does not know about, kept verbatim.
21 Other(String),
22}
23
24impl TradeSide {
25 /// The wire representation of this side.
26 pub fn as_str(&self) -> &str {
27 match self {
28 TradeSide::Buy => "buy",
29 TradeSide::Sell => "sell",
30 TradeSide::Other(s) => s,
31 }
32 }
33}
34
35impl std::fmt::Display for TradeSide {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 f.write_str(self.as_str())
38 }
39}
40
41impl<'de> Deserialize<'de> for TradeSide {
42 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
43 // Matched case-insensitively: `/trades/recent` sends "buy"/"sell" while
44 // the trades embedded in `/tokens/{network}/{token_address}` send
45 // "BUY"/"SELL". Folding the case here is what lets callers match on
46 // `TradeSide::Buy` at all; anything genuinely unrecognised still keeps
47 // its exact wire text in `Other`.
48 let raw = String::deserialize(deserializer)?;
49 Ok(match raw.to_ascii_lowercase().as_str() {
50 "buy" => TradeSide::Buy,
51 "sell" => TradeSide::Sell,
52 _ => TradeSide::Other(raw),
53 })
54 }
55}
56
57/// A single swap against the bonding curve or the graduated pair.
58#[derive(Debug, Clone, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct Trade {
61 /// Internal row id. Pass the highest one you have seen as `latest_trade_id`
62 /// to [`RecentTradesParams`](crate::RecentTradesParams) to poll incrementally.
63 pub id: i64,
64 /// Token name.
65 pub token_name: String,
66 /// Token ticker.
67 pub token_symbol: String,
68 /// Token contract address.
69 pub token_address: String,
70 /// Token image URL.
71 #[serde(default)]
72 pub token_image: Option<String>,
73 /// Wallet that made the trade.
74 pub swapper_address: String,
75 /// Trader's display name, when set.
76 #[serde(default)]
77 pub swapper_username: Option<String>,
78 /// Trader's avatar URL, when set.
79 #[serde(default)]
80 pub swapper_avatar: Option<String>,
81 /// Buy or sell.
82 #[serde(rename = "type")]
83 pub trade_side: TradeSide,
84 /// BNB amount. Decimal string — do not parse as `f64`.
85 pub eth_amount: String,
86 /// Token amount. Decimal string — do not parse as `f64`.
87 pub token_amount: String,
88 /// Chain slug.
89 pub network: String,
90 /// Trade time, UNIX **seconds**.
91 pub date: i64,
92 /// Transaction hash.
93 pub tx_hash: String,
94 /// Token price in USD at trade time. Decimal string.
95 pub token_price: String,
96 /// BNB price in USD at trade time. Decimal string.
97 pub eth_price: String,
98}
99
100/// Response of `GET /trades/recent`.
101#[derive(Debug, Clone, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct RecentTrades {
104 /// Platform-wide trades, newest first.
105 pub trades: Vec<Trade>,
106 /// The tokens referenced by [`trades`](Self::trades), so a poller does not
107 /// have to fetch them one at a time.
108 pub tokens: Vec<Token>,
109}
110
111/// One OHLCV candle.
112///
113/// These are already-aggregated chart numbers and are floats on the wire.
114#[derive(Debug, Clone, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct Candle {
117 /// Candle open time, UNIX **seconds**.
118 pub time: i64,
119 /// Open price.
120 pub open: f64,
121 /// High price.
122 pub high: f64,
123 /// Low price.
124 pub low: f64,
125 /// Close price.
126 pub close: f64,
127 /// Volume over the candle.
128 pub volume: f64,
129}