fyuz/api/distributor.rs
1//! The `/distributor/*` endpoints: pot, shares and the public payout ledger.
2
3use crate::client::{seg, FyuzClient, QueryPairs};
4use crate::error::Result;
5use crate::models::{
6 AddressPayouts, Claimable, PayoutStats, Pot, RoundDetail, RoundReceipt, Shares,
7};
8use crate::params::{RoundsParams, SharesParams, ToQuery};
9
10/// The Distributor endpoints, reached with
11/// [`FyuzClient::distributor`](crate::FyuzClient::distributor).
12///
13/// The Distributor pays back a share of platform fees every round: most of the
14/// pot pro-rata by points, the remainder to one VRF-picked winner. Everything
15/// here is a public read of that ledger.
16///
17/// **Every `*_wei` field is an exact decimal string.** Lifetime totals run past
18/// `2^53`, so parsing them into `f64` corrupts them silently. See the
19/// [`models`](crate::models) docs.
20///
21/// ```no_run
22/// # async fn demo() -> Result<(), fyuz::Error> {
23/// let client = fyuz::FyuzClient::new()?;
24/// let stats = client.distributor().get_stats().await?;
25/// println!("{} wei paid out over {} rounds", stats.total_paid_wei, stats.rounds_settled);
26/// # Ok(())
27/// # }
28/// ```
29#[derive(Debug, Clone, Copy)]
30pub struct Distributor<'a> {
31 client: &'a FyuzClient,
32}
33
34impl<'a> Distributor<'a> {
35 pub(crate) fn new(client: &'a FyuzClient) -> Self {
36 Self { client }
37 }
38
39 /// `GET /distributor/stats` — lifetime payback totals.
40 ///
41 /// ```no_run
42 /// # async fn demo() -> Result<(), fyuz::Error> {
43 /// let client = fyuz::FyuzClient::new()?;
44 /// let stats = client.distributor().get_stats().await?;
45 /// // Decimal string, not a number: print it, do not parse it.
46 /// println!("largest single payout: {} wei", stats.largest_payout_wei);
47 /// # Ok(())
48 /// # }
49 /// ```
50 pub async fn get_stats(&self) -> Result<PayoutStats> {
51 self.client
52 .get_json("/distributor/stats", QueryPairs::new())
53 .await
54 }
55
56 /// `GET /distributor/pot` — the live undistributed pot and the parameters
57 /// needed to price a point.
58 ///
59 /// [`Pot::pot_bnb`] and [`Pot::total_points`] are `None` when the value could
60 /// not be read. That is **unknown**, not zero — hide the figure rather than
61 /// showing `0`.
62 ///
63 /// ```no_run
64 /// # async fn demo() -> Result<(), fyuz::Error> {
65 /// let client = fyuz::FyuzClient::new()?;
66 /// let pot = client.distributor().get_pot().await?;
67 /// if let (Some(bnb), Some(points)) = (pot.pot_bnb, pot.total_points) {
68 /// println!("{:.6} BNB per point", bnb * pot.distribute_bps / 10_000.0 / points);
69 /// }
70 /// # Ok(())
71 /// # }
72 /// ```
73 pub async fn get_pot(&self) -> Result<Pot> {
74 self.client
75 .get_json("/distributor/pot", QueryPairs::new())
76 .await
77 }
78
79 /// `GET /distributor/shares` — the share allocation for the current window,
80 /// plus the exact calldata the round-runner posts on-chain.
81 ///
82 /// ```no_run
83 /// # async fn demo() -> Result<(), fyuz::Error> {
84 /// use fyuz::SharesParams;
85 ///
86 /// let client = fyuz::FyuzClient::new()?;
87 /// let shares = client.distributor().get_shares(&SharesParams::new().limit(50)).await?;
88 /// println!("{} holders, calldata {} bytes", shares.holders.len(), shares.packed.len() / 2 - 1);
89 /// # Ok(())
90 /// # }
91 /// ```
92 pub async fn get_shares(&self, params: &SharesParams) -> Result<Shares> {
93 self.client
94 .get_json("/distributor/shares", params.to_query())
95 .await
96 }
97
98 /// `GET /distributor/rounds` — the public payout ledger, newest first.
99 ///
100 /// ```no_run
101 /// # async fn demo() -> Result<(), fyuz::Error> {
102 /// use fyuz::RoundsParams;
103 ///
104 /// let client = fyuz::FyuzClient::new()?;
105 /// for round in client.distributor().list_rounds(&RoundsParams::new().limit(5)).await? {
106 /// // `None` means "not indexed yet", never zero.
107 /// match round.distributed_wei.as_deref() {
108 /// Some(wei) => println!("round {}: {wei} wei", round.round_id),
109 /// None => println!("round {}: pending settlement", round.round_id),
110 /// }
111 /// }
112 /// # Ok(())
113 /// # }
114 /// ```
115 pub async fn list_rounds(&self, params: &RoundsParams) -> Result<Vec<RoundReceipt>> {
116 self.client
117 .get_json("/distributor/rounds", params.to_query())
118 .await
119 }
120
121 /// `GET /distributor/rounds/{id}` — one round's receipt and its recipient
122 /// list.
123 ///
124 /// The receipt fields are flattened into the top level of the JSON object;
125 /// in Rust they live under [`RoundDetail::round`].
126 ///
127 /// Answers `404` for an unknown round.
128 ///
129 /// ```no_run
130 /// # async fn demo() -> Result<(), fyuz::Error> {
131 /// let client = fyuz::FyuzClient::new()?;
132 /// let detail = client.distributor().get_round(7).await?;
133 /// println!("round {} paid {} wallets", detail.round.round_id, detail.payouts.len());
134 /// # Ok(())
135 /// # }
136 /// ```
137 pub async fn get_round(&self, id: i64) -> Result<RoundDetail> {
138 self.client
139 .get_json(&format!("/distributor/rounds/{id}"), QueryPairs::new())
140 .await
141 }
142
143 /// `GET /distributor/payouts/{address}` — every payout one wallet has
144 /// received.
145 ///
146 /// ```no_run
147 /// # async fn demo() -> Result<(), fyuz::Error> {
148 /// let client = fyuz::FyuzClient::new()?;
149 /// let paid = client
150 /// .distributor()
151 /// .get_payouts("0x1111111111111111111111111111111111111111")
152 /// .await?;
153 /// println!("{} wei across {} rounds", paid.total_wei, paid.rounds_paid);
154 /// # Ok(())
155 /// # }
156 /// ```
157 pub async fn get_payouts(&self, address: &str) -> Result<AddressPayouts> {
158 self.client
159 .get_json(
160 &format!("/distributor/payouts/{}", seg(address)),
161 QueryPairs::new(),
162 )
163 .await
164 }
165
166 /// `GET /distributor/claimable/{address}` — wei owed from payout pushes the
167 /// contract could not complete.
168 ///
169 /// [`Claimable::claimable_wei`] is `None` when the RPC is unreachable or no
170 /// Distributor is configured. Hide the banner in that case rather than
171 /// telling someone they are owed nothing.
172 ///
173 /// ```no_run
174 /// # async fn demo() -> Result<(), fyuz::Error> {
175 /// let client = fyuz::FyuzClient::new()?;
176 /// let c = client
177 /// .distributor()
178 /// .get_claimable("0x1111111111111111111111111111111111111111")
179 /// .await?;
180 ///
181 /// match c.claimable_wei.as_deref() {
182 /// Some("0") => println!("nothing pending"),
183 /// Some(wei) => println!("{wei} wei claimable from {:?}", c.distributor_address),
184 /// None => println!("unknown — do not render a zero here"),
185 /// }
186 /// # Ok(())
187 /// # }
188 /// ```
189 pub async fn get_claimable(&self, address: &str) -> Result<Claimable> {
190 self.client
191 .get_json(
192 &format!("/distributor/claimable/{}", seg(address)),
193 QueryPairs::new(),
194 )
195 .await
196 }
197}