Skip to main content

fyuz/
params.rs

1//! Optional query parameters, one struct per endpoint that takes them.
2//!
3//! Every struct implements [`Default`] and has chainable setters, so only the
4//! parameters you care about appear at the call site:
5//!
6//! ```
7//! use fyuz::{DiscoverParams, DiscoverTab, TokenStatus};
8//!
9//! let params = DiscoverParams::new()
10//!     .tab(DiscoverTab::Graduating)
11//!     .status(TokenStatus::Bonding)
12//!     .min_holders(25)
13//!     .limit(100);
14//! # let _ = params;
15//! ```
16//!
17//! Parameters left unset are simply not sent, and the server applies its own
18//! documented default.
19
20use crate::client::QueryPairs;
21
22/// Turns a parameter struct into query-string pairs.
23pub(crate) trait ToQuery {
24    fn to_query(&self) -> QueryPairs;
25}
26
27/// Push `name=value` when `value` is set.
28fn push<T: ToString>(pairs: &mut QueryPairs, name: &'static str, value: &Option<T>) {
29    if let Some(value) = value {
30        pairs.push((name, value.to_string()));
31    }
32}
33
34/// Preset ordering for the discovery feed.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub enum DiscoverTab {
37    /// Highest recent activity first.
38    Trending,
39    /// Newest launches first.
40    New,
41    /// Largest 24h price gains first.
42    Gainers,
43    /// Closest to the $30,000 graduation threshold first.
44    Graduating,
45}
46
47impl DiscoverTab {
48    /// The wire value.
49    pub fn as_str(self) -> &'static str {
50        match self {
51            DiscoverTab::Trending => "trending",
52            DiscoverTab::New => "new",
53            DiscoverTab::Gainers => "gainers",
54            DiscoverTab::Graduating => "graduating",
55        }
56    }
57}
58
59impl std::fmt::Display for DiscoverTab {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        f.write_str(self.as_str())
62    }
63}
64
65/// Bonding-curve lifecycle filter.
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
67pub enum TokenStatus {
68    /// Still on the bonding curve — no DEX pool exists yet.
69    Bonding,
70    /// Graduated into a PancakeSwap pair.
71    Graduated,
72}
73
74impl TokenStatus {
75    /// The wire value.
76    pub fn as_str(self) -> &'static str {
77        match self {
78            TokenStatus::Bonding => "bonding",
79            TokenStatus::Graduated => "graduated",
80        }
81    }
82}
83
84impl std::fmt::Display for TokenStatus {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        f.write_str(self.as_str())
87    }
88}
89
90/// Sort direction for [`ListTokensParams::order_flag`].
91#[derive(Debug, Clone, Copy, PartialEq, Eq)]
92pub enum SortDirection {
93    /// Ascending.
94    Asc,
95    /// Descending.
96    Desc,
97}
98
99impl SortDirection {
100    /// The wire value.
101    pub fn as_str(self) -> &'static str {
102        match self {
103            SortDirection::Asc => "asc",
104            SortDirection::Desc => "desc",
105        }
106    }
107}
108
109impl std::fmt::Display for SortDirection {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        f.write_str(self.as_str())
112    }
113}
114
115/// Filters for [`FyuzClient::discover`](crate::FyuzClient::discover).
116#[derive(Debug, Clone, Default)]
117pub struct DiscoverParams {
118    /// Preset ordering.
119    pub tab: Option<DiscoverTab>,
120    /// Chain slug, e.g. `"bsc"`.
121    pub network: Option<String>,
122    /// Minimum USD market cap.
123    pub min_marketcap: Option<f64>,
124    /// Minimum USD 24h volume.
125    pub min_volume: Option<f64>,
126    /// Minimum holder count.
127    pub min_holders: Option<i64>,
128    /// Only tokens created within this many seconds.
129    pub max_age_secs: Option<i64>,
130    /// Bonding-curve lifecycle filter.
131    pub status: Option<TokenStatus>,
132    /// Rows to return. Server default 50.
133    pub limit: Option<i64>,
134}
135
136impl DiscoverParams {
137    /// No filters.
138    pub fn new() -> Self {
139        Self::default()
140    }
141
142    /// Set the preset ordering.
143    pub fn tab(mut self, tab: DiscoverTab) -> Self {
144        self.tab = Some(tab);
145        self
146    }
147
148    /// Restrict to one chain.
149    pub fn network(mut self, network: impl Into<String>) -> Self {
150        self.network = Some(network.into());
151        self
152    }
153
154    /// Set the minimum USD market cap.
155    pub fn min_marketcap(mut self, min_marketcap: f64) -> Self {
156        self.min_marketcap = Some(min_marketcap);
157        self
158    }
159
160    /// Set the minimum USD 24h volume.
161    pub fn min_volume(mut self, min_volume: f64) -> Self {
162        self.min_volume = Some(min_volume);
163        self
164    }
165
166    /// Set the minimum holder count.
167    pub fn min_holders(mut self, min_holders: i64) -> Self {
168        self.min_holders = Some(min_holders);
169        self
170    }
171
172    /// Only return tokens created within this many seconds.
173    pub fn max_age_secs(mut self, max_age_secs: i64) -> Self {
174        self.max_age_secs = Some(max_age_secs);
175        self
176    }
177
178    /// Filter by bonding-curve lifecycle.
179    pub fn status(mut self, status: TokenStatus) -> Self {
180        self.status = Some(status);
181        self
182    }
183
184    /// Set the row limit.
185    pub fn limit(mut self, limit: i64) -> Self {
186        self.limit = Some(limit);
187        self
188    }
189}
190
191impl ToQuery for DiscoverParams {
192    fn to_query(&self) -> QueryPairs {
193        let mut pairs = QueryPairs::new();
194        push(&mut pairs, "tab", &self.tab.map(|t| t.as_str()));
195        push(&mut pairs, "network", &self.network);
196        push(&mut pairs, "minMarketcap", &self.min_marketcap);
197        push(&mut pairs, "minVolume", &self.min_volume);
198        push(&mut pairs, "minHolders", &self.min_holders);
199        push(&mut pairs, "maxAgeSecs", &self.max_age_secs);
200        push(&mut pairs, "status", &self.status.map(|s| s.as_str()));
201        push(&mut pairs, "limit", &self.limit);
202        pairs
203    }
204}
205
206/// Filters, sorting and paging for
207/// [`FyuzClient::list_tokens`](crate::FyuzClient::list_tokens).
208#[derive(Debug, Clone, Default)]
209pub struct ListTokensParams {
210    /// Field to sort by, e.g. `marketcap`, `createdAt`, `score`.
211    pub order_type: Option<String>,
212    /// Sort direction.
213    pub order_flag: Option<SortDirection>,
214    /// Free-text match on name, symbol or address.
215    pub search_word: Option<String>,
216    /// Chain slug.
217    pub network: Option<String>,
218    /// Exact-match filter on generated image style (`all` = no filter).
219    pub style: Option<String>,
220    /// Include tokens flagged NSFW.
221    pub include_nsfw: Option<bool>,
222    /// 1-based page number. Server default 1.
223    pub page_number: Option<i64>,
224    /// Rows per page. Server default 20.
225    pub page_size: Option<i64>,
226}
227
228impl ListTokensParams {
229    /// No filters.
230    pub fn new() -> Self {
231        Self::default()
232    }
233
234    /// Sort by this field.
235    pub fn order_type(mut self, order_type: impl Into<String>) -> Self {
236        self.order_type = Some(order_type.into());
237        self
238    }
239
240    /// Sort in this direction.
241    pub fn order_flag(mut self, order_flag: SortDirection) -> Self {
242        self.order_flag = Some(order_flag);
243        self
244    }
245
246    /// Free-text search on name, symbol or address.
247    pub fn search_word(mut self, search_word: impl Into<String>) -> Self {
248        self.search_word = Some(search_word.into());
249        self
250    }
251
252    /// Restrict to one chain.
253    pub fn network(mut self, network: impl Into<String>) -> Self {
254        self.network = Some(network.into());
255        self
256    }
257
258    /// Filter by generated image style.
259    pub fn style(mut self, style: impl Into<String>) -> Self {
260        self.style = Some(style.into());
261        self
262    }
263
264    /// Include NSFW-flagged tokens.
265    pub fn include_nsfw(mut self, include_nsfw: bool) -> Self {
266        self.include_nsfw = Some(include_nsfw);
267        self
268    }
269
270    /// Request a specific 1-based page.
271    pub fn page_number(mut self, page_number: i64) -> Self {
272        self.page_number = Some(page_number);
273        self
274    }
275
276    /// Set the page size.
277    pub fn page_size(mut self, page_size: i64) -> Self {
278        self.page_size = Some(page_size);
279        self
280    }
281}
282
283impl ToQuery for ListTokensParams {
284    fn to_query(&self) -> QueryPairs {
285        let mut pairs = QueryPairs::new();
286        push(&mut pairs, "orderType", &self.order_type);
287        push(
288            &mut pairs,
289            "orderFlag",
290            &self.order_flag.map(|f| f.as_str()),
291        );
292        push(&mut pairs, "searchWord", &self.search_word);
293        push(&mut pairs, "network", &self.network);
294        push(&mut pairs, "style", &self.style);
295        push(&mut pairs, "includeNsfw", &self.include_nsfw);
296        push(&mut pairs, "pageNumber", &self.page_number);
297        push(&mut pairs, "pageSize", &self.page_size);
298        pairs
299    }
300}
301
302/// Paging of the trade list embedded in
303/// [`FyuzClient::get_token`](crate::FyuzClient::get_token).
304#[derive(Debug, Clone, Default)]
305pub struct TokenDetailParams {
306    /// 1-based page number for the embedded trades. Server default 1.
307    pub page_number: Option<i64>,
308    /// Rows per page. Server default 20.
309    pub page_size: Option<i64>,
310}
311
312impl TokenDetailParams {
313    /// Server defaults.
314    pub fn new() -> Self {
315        Self::default()
316    }
317
318    /// Request a specific 1-based page of trades.
319    pub fn page_number(mut self, page_number: i64) -> Self {
320        self.page_number = Some(page_number);
321        self
322    }
323
324    /// Set the trade page size.
325    pub fn page_size(mut self, page_size: i64) -> Self {
326        self.page_size = Some(page_size);
327        self
328    }
329}
330
331impl ToQuery for TokenDetailParams {
332    fn to_query(&self) -> QueryPairs {
333        let mut pairs = QueryPairs::new();
334        push(&mut pairs, "pageNumber", &self.page_number);
335        push(&mut pairs, "pageSize", &self.page_size);
336        pairs
337    }
338}
339
340/// Incremental polling cursor for
341/// [`FyuzClient::get_recent_trades`](crate::FyuzClient::get_recent_trades).
342#[derive(Debug, Clone, Default)]
343pub struct RecentTradesParams {
344    /// Return only trades with an id greater than this.
345    pub latest_trade_id: Option<i64>,
346    /// Return only tokens with an id greater than this.
347    pub latest_token_id: Option<i64>,
348}
349
350impl RecentTradesParams {
351    /// No cursor — the newest page.
352    pub fn new() -> Self {
353        Self::default()
354    }
355
356    /// Only trades newer than this id.
357    pub fn latest_trade_id(mut self, latest_trade_id: i64) -> Self {
358        self.latest_trade_id = Some(latest_trade_id);
359        self
360    }
361
362    /// Only tokens newer than this id.
363    pub fn latest_token_id(mut self, latest_token_id: i64) -> Self {
364        self.latest_token_id = Some(latest_token_id);
365        self
366    }
367}
368
369impl ToQuery for RecentTradesParams {
370    fn to_query(&self) -> QueryPairs {
371        let mut pairs = QueryPairs::new();
372        push(&mut pairs, "latestTradeId", &self.latest_trade_id);
373        push(&mut pairs, "latestTokenId", &self.latest_token_id);
374        pairs
375    }
376}
377
378/// Paging for
379/// [`FyuzClient::get_token_trades`](crate::FyuzClient::get_token_trades).
380#[derive(Debug, Clone, Default)]
381pub struct TokenTradesParams {
382    /// Rows to return. Server default 50.
383    pub limit: Option<i64>,
384    /// Rows to skip. Server default 0.
385    pub offset: Option<i64>,
386}
387
388impl TokenTradesParams {
389    /// Server defaults.
390    pub fn new() -> Self {
391        Self::default()
392    }
393
394    /// Set the row limit.
395    pub fn limit(mut self, limit: i64) -> Self {
396        self.limit = Some(limit);
397        self
398    }
399
400    /// Set the row offset.
401    pub fn offset(mut self, offset: i64) -> Self {
402        self.offset = Some(offset);
403        self
404    }
405}
406
407impl ToQuery for TokenTradesParams {
408    fn to_query(&self) -> QueryPairs {
409        let mut pairs = QueryPairs::new();
410        push(&mut pairs, "limit", &self.limit);
411        push(&mut pairs, "offset", &self.offset);
412        pairs
413    }
414}
415
416/// Arguments for
417/// [`FyuzClient::get_chart_data`](crate::FyuzClient::get_chart_data).
418///
419/// `token_address`, `interval`, `from` and `to` are required by the API, so they
420/// are constructor arguments rather than setters.
421#[derive(Debug, Clone)]
422pub struct ChartDataParams {
423    /// Token contract address.
424    pub token_address: String,
425    /// Candle width, e.g. `1`, `5`, `15`, `60`, `1D` (minutes unless suffixed).
426    pub interval: String,
427    /// Range start, UNIX seconds.
428    pub from: i64,
429    /// Range end, UNIX seconds.
430    pub to: i64,
431    /// Whether this is the first page of a chart load.
432    pub first: Option<i64>,
433    /// DEX to read candles from, once the token has graduated.
434    pub dex: Option<String>,
435    /// Number of candles to return ending at [`to`](Self::to).
436    pub count_back: Option<i64>,
437}
438
439impl ChartDataParams {
440    /// The four required arguments.
441    pub fn new(
442        token_address: impl Into<String>,
443        interval: impl Into<String>,
444        from: i64,
445        to: i64,
446    ) -> Self {
447        Self {
448            token_address: token_address.into(),
449            interval: interval.into(),
450            from,
451            to,
452            first: None,
453            dex: None,
454            count_back: None,
455        }
456    }
457
458    /// Mark this as the first page of a chart load.
459    pub fn first(mut self, first: i64) -> Self {
460        self.first = Some(first);
461        self
462    }
463
464    /// Read candles from a specific DEX.
465    pub fn dex(mut self, dex: impl Into<String>) -> Self {
466        self.dex = Some(dex.into());
467        self
468    }
469
470    /// Return this many candles ending at [`to`](Self::to).
471    pub fn count_back(mut self, count_back: i64) -> Self {
472        self.count_back = Some(count_back);
473        self
474    }
475}
476
477impl ToQuery for ChartDataParams {
478    fn to_query(&self) -> QueryPairs {
479        let mut pairs: QueryPairs = vec![
480            ("tokenAddress", self.token_address.clone()),
481            ("interval", self.interval.clone()),
482            ("from", self.from.to_string()),
483            ("to", self.to.to_string()),
484        ];
485        push(&mut pairs, "first", &self.first);
486        push(&mut pairs, "dex", &self.dex);
487        push(&mut pairs, "countBack", &self.count_back);
488        pairs
489    }
490}
491
492/// Window and chain filter for
493/// [`FyuzClient::get_top_holders`](crate::FyuzClient::get_top_holders).
494#[derive(Debug, Clone, Default)]
495pub struct TopHoldersParams {
496    /// Window start, UNIX seconds. Server default 0.
497    pub from: Option<i64>,
498    /// Window end, UNIX seconds. Defaults to unbounded.
499    pub to: Option<i64>,
500    /// Only count volume on this chain.
501    pub network: Option<String>,
502}
503
504impl TopHoldersParams {
505    /// All time, all chains.
506    pub fn new() -> Self {
507        Self::default()
508    }
509
510    /// Set the window start, UNIX seconds.
511    pub fn from(mut self, from: i64) -> Self {
512        self.from = Some(from);
513        self
514    }
515
516    /// Set the window end, UNIX seconds.
517    pub fn to(mut self, to: i64) -> Self {
518        self.to = Some(to);
519        self
520    }
521
522    /// Restrict to one chain.
523    pub fn network(mut self, network: impl Into<String>) -> Self {
524        self.network = Some(network.into());
525        self
526    }
527}
528
529impl ToQuery for TopHoldersParams {
530    fn to_query(&self) -> QueryPairs {
531        let mut pairs = QueryPairs::new();
532        push(&mut pairs, "from", &self.from);
533        push(&mut pairs, "to", &self.to);
534        push(&mut pairs, "network", &self.network);
535        pairs
536    }
537}
538
539/// Window and size for
540/// [`Distributor::get_shares`](crate::Distributor::get_shares).
541#[derive(Debug, Clone, Default)]
542pub struct SharesParams {
543    /// Window start, UNIX seconds. Defaults to the end of the last paid round.
544    pub from: Option<i64>,
545    /// Window end, UNIX seconds. Defaults to now. Must be after
546    /// [`from`](Self::from) or the API answers 400.
547    pub to: Option<i64>,
548    /// Max holders, top-N by points. Clamped by the server to 1-100 (the
549    /// contract's `MAX_HOLDERS`). Server default 100.
550    pub limit: Option<i64>,
551}
552
553impl SharesParams {
554    /// Server defaults: the live window, 100 holders.
555    pub fn new() -> Self {
556        Self::default()
557    }
558
559    /// Set the window start, UNIX seconds.
560    pub fn from(mut self, from: i64) -> Self {
561        self.from = Some(from);
562        self
563    }
564
565    /// Set the window end, UNIX seconds.
566    pub fn to(mut self, to: i64) -> Self {
567        self.to = Some(to);
568        self
569    }
570
571    /// Set the holder limit.
572    pub fn limit(mut self, limit: i64) -> Self {
573        self.limit = Some(limit);
574        self
575    }
576}
577
578impl ToQuery for SharesParams {
579    fn to_query(&self) -> QueryPairs {
580        let mut pairs = QueryPairs::new();
581        push(&mut pairs, "from", &self.from);
582        push(&mut pairs, "to", &self.to);
583        push(&mut pairs, "limit", &self.limit);
584        pairs
585    }
586}
587
588/// Paging for [`Distributor::list_rounds`](crate::Distributor::list_rounds).
589#[derive(Debug, Clone, Default)]
590pub struct RoundsParams {
591    /// Rows to return. Clamped by the server to 1-100. Server default 25.
592    pub limit: Option<i64>,
593    /// Rows to skip. Server default 0.
594    pub offset: Option<i64>,
595}
596
597impl RoundsParams {
598    /// Server defaults: the 25 most recent rounds.
599    pub fn new() -> Self {
600        Self::default()
601    }
602
603    /// Set the row limit.
604    pub fn limit(mut self, limit: i64) -> Self {
605        self.limit = Some(limit);
606        self
607    }
608
609    /// Set the row offset.
610    pub fn offset(mut self, offset: i64) -> Self {
611        self.offset = Some(offset);
612        self
613    }
614}
615
616impl ToQuery for RoundsParams {
617    fn to_query(&self) -> QueryPairs {
618        let mut pairs = QueryPairs::new();
619        push(&mut pairs, "limit", &self.limit);
620        push(&mut pairs, "offset", &self.offset);
621        pairs
622    }
623}
624
625#[cfg(test)]
626mod tests {
627    use super::*;
628
629    #[test]
630    fn omits_unset_parameters() {
631        let pairs = DiscoverParams::new().limit(5).to_query();
632        assert_eq!(pairs, vec![("limit", "5".to_string())]);
633    }
634
635    #[test]
636    fn encodes_enums_with_their_wire_values() {
637        let pairs = DiscoverParams::new()
638            .tab(DiscoverTab::Graduating)
639            .status(TokenStatus::Bonding)
640            .to_query();
641        assert_eq!(
642            pairs,
643            vec![
644                ("tab", "graduating".to_string()),
645                ("status", "bonding".to_string())
646            ]
647        );
648    }
649
650    #[test]
651    fn always_sends_the_required_chart_parameters() {
652        let pairs = ChartDataParams::new("0xabc", "5", 1, 2).to_query();
653        assert_eq!(
654            pairs,
655            vec![
656                ("tokenAddress", "0xabc".to_string()),
657                ("interval", "5".to_string()),
658                ("from", "1".to_string()),
659                ("to", "2".to_string()),
660            ]
661        );
662    }
663}