Skip to main content

fyuz/models/
distributor.rs

1//! Distributor: pot, share allocation and the public payout ledger.
2//!
3//! # Wei fields are strings, always
4//!
5//! Every `*_wei` field in this module is an exact `uint256` decimal string with
6//! no fractional part. `1_000_000_000_000_000_000` wei is 1 BNB; lifetime totals
7//! run far past `2^53` and would be silently corrupted by `f64`. Keep them as
8//! strings, compare them as strings or big integers, and format them for display
9//! by shifting the decimal point 18 places — never by parsing to a float.
10//!
11//! [`RoundReceipt::vrf_random`] is likewise a decimal string (a `uint256` VRF
12//! word), not hex and not a number.
13
14use serde::Deserialize;
15
16/// Lifetime Distributor payback totals.
17#[derive(Debug, Clone, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub struct PayoutStats {
20    /// Everything ever paid out — pro-rata plus lottery. Exact wei as a decimal
21    /// string; `"0"` when nothing has settled.
22    pub total_paid_wei: String,
23    /// Number of settled rounds.
24    pub rounds_settled: i64,
25    /// Distinct addresses that have received something.
26    pub unique_recipients: i64,
27    /// Largest single payout. Exact wei as a decimal string; `"0"` when nothing
28    /// has settled.
29    pub largest_payout_wei: String,
30    /// Settlement time of the most recent round, UNIX **seconds**. `None` when
31    /// nothing has settled.
32    #[serde(default)]
33    pub last_round_at: Option<i64>,
34}
35
36/// Live undistributed pot and the round parameters needed to price a point.
37#[derive(Debug, Clone, Deserialize)]
38#[serde(rename_all = "camelCase")]
39pub struct Pot {
40    /// Live BNB balance of the Distributor.
41    ///
42    /// `None` when no Distributor is configured or the RPC is unreachable —
43    /// hide the figure, do **not** render `0`.
44    #[serde(default)]
45    pub pot_bnb: Option<f64>,
46    /// Basis points paid pro-rata by points; the remainder goes to the VRF
47    /// winner.
48    pub distribute_bps: f64,
49    /// Next round close, UNIX **seconds**, read from the contract's own
50    /// schedule.
51    pub round_end: i64,
52    /// Points across the whole payout set — the denominator of every share.
53    /// `None` when it could not be computed (not zero).
54    #[serde(default)]
55    pub total_points: Option<f64>,
56    /// Points credited per $1 of volume. Buys and sells score alike.
57    pub points_per_usd: f64,
58}
59
60/// One holder's committed share of the next round.
61#[derive(Debug, Clone, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct ShareEntry {
64    /// Holder wallet address.
65    pub address: String,
66    /// Points earned in the window.
67    pub points: f64,
68    /// The holder's fraction of `2^32` — the on-chain share unit. Fits `u32`.
69    pub share: i64,
70}
71
72/// The share allocation the round-runner posts on-chain, plus the exact calldata
73/// it posts.
74#[derive(Debug, Clone, Deserialize)]
75#[serde(rename_all = "camelCase")]
76pub struct Shares {
77    /// Window start, UNIX **seconds**.
78    pub from: i64,
79    /// Window end, UNIX **seconds**.
80    pub to: i64,
81    /// Total points across the returned set.
82    pub total_points: f64,
83    /// Highest points first, capped at 100 (the contract's `MAX_HOLDERS`).
84    pub holders: Vec<ShareEntry>,
85    /// Calldata for `Distributor.postShares`: 24-byte entries of a 20-byte
86    /// address followed by the big-endian `uint32` share. `0x`-prefixed hex.
87    pub packed: String,
88}
89
90/// A settled distribution round.
91///
92/// Null wei fields mean the on-chain settlement has not been indexed yet — they
93/// do **not** mean zero.
94#[derive(Debug, Clone, Deserialize)]
95#[serde(rename_all = "camelCase")]
96pub struct RoundReceipt {
97    /// Round number.
98    pub round_id: i64,
99    /// Window start, UNIX **seconds**.
100    pub time_start: i64,
101    /// Window end, UNIX **seconds**; becomes the start of the next leaderboard
102    /// window.
103    pub time_end: i64,
104    /// Settlement transaction hash.
105    #[serde(default)]
106    pub tx_hash: Option<String>,
107    /// Whole amount that left the contract — pro-rata plus lottery. Exact wei as
108    /// a decimal string.
109    #[serde(default)]
110    pub pot_wei: Option<String>,
111    /// Pro-rata portion only. Exact wei as a decimal string.
112    #[serde(default)]
113    pub distributed_wei: Option<String>,
114    /// VRF-picked lottery winner.
115    #[serde(default)]
116    pub winner_address: Option<String>,
117    /// Amount sent to the lottery winner. Exact wei as a decimal string.
118    #[serde(default)]
119    pub winner_amount_wei: Option<String>,
120    /// Number of holders paid pro-rata.
121    #[serde(default)]
122    pub holder_count: Option<i32>,
123    /// The VRF word that selected the winner, as a **decimal string** (not hex).
124    /// Published so the draw can be checked against the coordinator's own
125    /// fulfilment.
126    #[serde(default)]
127    pub vrf_random: Option<String>,
128    /// Settlement time, UNIX **seconds**.
129    #[serde(default)]
130    pub distributed_at: Option<i64>,
131}
132
133/// One recipient's line in a round.
134#[derive(Debug, Clone, Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct PayoutLine {
137    /// Round this line belongs to.
138    pub round_id: i64,
139    /// Recipient wallet address.
140    pub address: String,
141    /// The recipient's fraction of `2^32`, exactly as committed on-chain.
142    pub share: i64,
143    /// Exact wei as a decimal string.
144    pub amount_wei: String,
145    /// Recipient's display name, when set.
146    #[serde(default)]
147    pub username: Option<String>,
148    /// Recipient's avatar URL, when set.
149    #[serde(default)]
150    pub avatar: Option<String>,
151}
152
153/// A round receipt with its recipient list.
154///
155/// On the wire the receipt fields sit at the **top level** next to `payouts`;
156/// [`round`](Self::round) is `#[serde(flatten)]`ed, so
157/// `detail.round.round_id` reads the same `roundId` key the API sends.
158#[derive(Debug, Clone, Deserialize)]
159#[serde(rename_all = "camelCase")]
160pub struct RoundDetail {
161    /// The receipt fields, flattened into the top level of the JSON object.
162    #[serde(flatten)]
163    pub round: RoundReceipt,
164    /// Recipients, richest first.
165    pub payouts: Vec<PayoutLine>,
166}
167
168/// Every payout a single wallet has received.
169#[derive(Debug, Clone, Deserialize)]
170#[serde(rename_all = "camelCase")]
171pub struct AddressPayouts {
172    /// Wallet address, lower-cased.
173    pub address: String,
174    /// Lifetime wei received across every settled round. Summed exactly, not in
175    /// floating point.
176    pub total_wei: String,
177    /// Number of rounds this wallet was paid in.
178    pub rounds_paid: i64,
179    /// Payout lines, newest round first.
180    pub payouts: Vec<PayoutLine>,
181}
182
183/// Wei owed to an address from payout pushes the contract could not complete
184/// (contract wallets, reverting receivers).
185///
186/// Withdrawable by calling `claim()` on the Distributor — the wallet signs, this
187/// API only reads.
188#[derive(Debug, Clone, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct Claimable {
191    /// Wallet address, lower-cased.
192    pub address: String,
193    /// Exact wei as a decimal string. `None` when the RPC is unreachable or no
194    /// Distributor is configured — hide the banner rather than telling someone
195    /// they are owed nothing.
196    #[serde(default)]
197    pub claimable_wei: Option<String>,
198    /// Where to send the `claim()`. `None` when unconfigured.
199    #[serde(default)]
200    pub distributor_address: Option<String>,
201}