1use polyoxide_core::{HttpClient, QueryBuilder};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5 account::{Credentials, Signer, Wallet},
6 request::{AuthMode, Request},
7 types::SignatureType,
8};
9
10#[derive(Clone)]
12pub struct Rewards {
13 pub(crate) http_client: HttpClient,
14 pub(crate) wallet: Wallet,
15 pub(crate) credentials: Credentials,
16 pub(crate) signer: Signer,
17 pub(crate) chain_id: u64,
18 pub(crate) signature_type: SignatureType,
19}
20
21impl Rewards {
22 fn l2_auth(&self) -> AuthMode {
23 AuthMode::L2 {
24 address: self.wallet.address(),
25 credentials: self.credentials.clone(),
26 signer: self.signer.clone(),
27 }
28 }
29
30 pub fn earnings(&self, date: impl Into<String>) -> Request<RewardEarnings> {
35 Request::get(
36 self.http_client.clone(),
37 "/rewards/user",
38 self.l2_auth(),
39 self.chain_id,
40 )
41 .query("date", date.into())
42 .query("signature_type", self.signature_type as u8)
43 }
44
45 pub fn total_earnings(&self, date: impl Into<String>) -> Request<Vec<RewardTotalEarnings>> {
50 Request::get(
51 self.http_client.clone(),
52 "/rewards/user/total",
53 self.l2_auth(),
54 self.chain_id,
55 )
56 .query("date", date.into())
57 .query("signature_type", self.signature_type as u8)
58 }
59
60 pub fn percentages(&self) -> Request<RewardPercentages> {
62 Request::get(
63 self.http_client.clone(),
64 "/rewards/user/percentages",
65 self.l2_auth(),
66 self.chain_id,
67 )
68 .query("signature_type", self.signature_type as u8)
69 }
70
71 pub fn market_earnings(&self) -> Request<Paginated<RewardMarketEarning>> {
75 Request::get(
76 self.http_client.clone(),
77 "/rewards/user/markets",
78 self.l2_auth(),
79 self.chain_id,
80 )
81 .query("signature_type", self.signature_type as u8)
82 }
83
84 pub fn current_markets(&self) -> Request<Paginated<RewardMarket>> {
89 Request::get(
90 self.http_client.clone(),
91 "/rewards/markets/current",
92 AuthMode::None,
93 self.chain_id,
94 )
95 }
96
97 pub fn market(&self, condition_id: impl Into<String>) -> Request<RewardMarket> {
99 Request::get(
100 self.http_client.clone(),
101 format!(
102 "/rewards/markets/{}",
103 urlencoding::encode(&condition_id.into())
104 ),
105 AuthMode::None,
106 self.chain_id,
107 )
108 }
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct RewardEarnings {
114 #[serde(flatten)]
115 pub data: serde_json::Value,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct RewardTotalEarnings {
121 #[serde(flatten)]
122 pub data: serde_json::Value,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct RewardPercentages {
128 #[serde(flatten)]
129 pub data: serde_json::Value,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct RewardMarketEarning {
135 #[serde(flatten)]
136 pub data: serde_json::Value,
137}
138
139#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct RewardMarket {
142 #[serde(flatten)]
143 pub data: serde_json::Value,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct Paginated<T> {
153 #[serde(default = "Vec::new")]
155 pub data: Vec<T>,
156 #[serde(default)]
158 pub next_cursor: Option<String>,
159 #[serde(default)]
161 pub limit: Option<u32>,
162 #[serde(default)]
164 pub count: Option<u32>,
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn reward_earnings_deserializes() {
173 let json = r#"{"amount": "1.5", "day": "2024-01-15"}"#;
174 let resp: RewardEarnings = serde_json::from_str(json).unwrap();
175 assert_eq!(resp.data["amount"], "1.5");
176 assert_eq!(resp.data["day"], "2024-01-15");
177 }
178
179 #[test]
180 fn reward_total_earnings_deserializes() {
181 let json = r#"{"total": "42.0"}"#;
182 let resp: RewardTotalEarnings = serde_json::from_str(json).unwrap();
183 assert_eq!(resp.data["total"], "42.0");
184 }
185
186 #[test]
187 fn reward_total_earnings_list_deserializes() {
188 let json = r#"[{"asset_address": "0xabc", "total": "42.0"}]"#;
191 let resp: Vec<RewardTotalEarnings> = serde_json::from_str(json).unwrap();
192 assert_eq!(resp.len(), 1);
193 assert_eq!(resp[0].data["total"], "42.0");
194 }
195
196 #[test]
197 fn reward_percentages_deserializes() {
198 let json = r#"{"maker": "0.5", "taker": "0.3"}"#;
199 let resp: RewardPercentages = serde_json::from_str(json).unwrap();
200 assert_eq!(resp.data["maker"], "0.5");
201 }
202
203 #[test]
204 fn reward_market_earning_list_deserializes() {
205 let json = r#"[
206 {"condition_id": "0xabc", "amount": "10.0"},
207 {"condition_id": "0xdef", "amount": "5.0"}
208 ]"#;
209 let resp: Vec<RewardMarketEarning> = serde_json::from_str(json).unwrap();
210 assert_eq!(resp.len(), 2);
211 assert_eq!(resp[0].data["condition_id"], "0xabc");
212 }
213
214 #[test]
215 fn reward_market_deserializes() {
216 let json = r#"{"condition_id": "0xabc", "reward_rate": "0.01"}"#;
217 let resp: RewardMarket = serde_json::from_str(json).unwrap();
218 assert_eq!(resp.data["condition_id"], "0xabc");
219 }
220
221 #[test]
222 fn reward_market_list_deserializes() {
223 let json = r#"[{"condition_id": "0xabc"}, {"condition_id": "0xdef"}]"#;
224 let resp: Vec<RewardMarket> = serde_json::from_str(json).unwrap();
225 assert_eq!(resp.len(), 2);
226 }
227
228 #[test]
229 fn current_markets_paginated_response_deserializes() {
230 let json = r#"{
233 "limit": 500,
234 "count": 1,
235 "next_cursor": "LTE=",
236 "data": [
237 {"condition_id": "0xabc", "rewards_max_spread": 99}
238 ]
239 }"#;
240 let page: Paginated<RewardMarket> =
241 serde_json::from_str(json).expect("paginated reward markets should deserialize");
242 assert_eq!(page.data.len(), 1);
243 assert_eq!(page.count, Some(1));
244 assert_eq!(page.next_cursor.as_deref(), Some("LTE="));
245 assert_eq!(page.data[0].data["condition_id"], "0xabc");
246 }
247
248 #[test]
249 fn market_earnings_paginated_response_deserializes() {
250 let json = r#"{
252 "limit": 100,
253 "count": 1,
254 "next_cursor": "LTE=",
255 "data": [
256 {"condition_id": "0xabc", "earnings": 0.237519}
257 ]
258 }"#;
259 let page: Paginated<RewardMarketEarning> =
260 serde_json::from_str(json).expect("paginated market earnings should deserialize");
261 assert_eq!(page.data.len(), 1);
262 assert_eq!(page.count, Some(1));
263 assert_eq!(page.data[0].data["condition_id"], "0xabc");
264 }
265
266 #[test]
267 fn reward_earnings_empty_object_deserializes() {
268 let json = r#"{}"#;
269 let resp: RewardEarnings = serde_json::from_str(json).unwrap();
270 assert!(resp.data.is_object());
271 }
272}