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}