use polyoxide_core::{HttpClient, QueryBuilder};
use serde::{Deserialize, Serialize};
use crate::{
account::{Credentials, Signer, Wallet},
request::{AuthMode, Request},
types::SignatureType,
};
#[derive(Clone)]
pub struct Rewards {
pub(crate) http_client: HttpClient,
pub(crate) wallet: Wallet,
pub(crate) credentials: Credentials,
pub(crate) signer: Signer,
pub(crate) chain_id: u64,
pub(crate) signature_type: SignatureType,
}
impl Rewards {
fn l2_auth(&self) -> AuthMode {
AuthMode::L2 {
address: self.wallet.address(),
credentials: self.credentials.clone(),
signer: self.signer.clone(),
}
}
pub fn earnings(&self, date: impl Into<String>) -> Request<RewardEarnings> {
Request::get(
self.http_client.clone(),
"/rewards/user",
self.l2_auth(),
self.chain_id,
)
.query("date", date.into())
.query("signature_type", self.signature_type as u8)
}
pub fn total_earnings(&self, date: impl Into<String>) -> Request<Vec<RewardTotalEarnings>> {
Request::get(
self.http_client.clone(),
"/rewards/user/total",
self.l2_auth(),
self.chain_id,
)
.query("date", date.into())
.query("signature_type", self.signature_type as u8)
}
pub fn percentages(&self) -> Request<RewardPercentages> {
Request::get(
self.http_client.clone(),
"/rewards/user/percentages",
self.l2_auth(),
self.chain_id,
)
.query("signature_type", self.signature_type as u8)
}
pub fn market_earnings(&self) -> Request<Paginated<RewardMarketEarning>> {
Request::get(
self.http_client.clone(),
"/rewards/user/markets",
self.l2_auth(),
self.chain_id,
)
.query("signature_type", self.signature_type as u8)
}
pub fn current_markets(&self) -> Request<Paginated<RewardMarket>> {
Request::get(
self.http_client.clone(),
"/rewards/markets/current",
AuthMode::None,
self.chain_id,
)
}
pub fn market(&self, condition_id: impl Into<String>) -> Request<RewardMarket> {
Request::get(
self.http_client.clone(),
format!(
"/rewards/markets/{}",
urlencoding::encode(&condition_id.into())
),
AuthMode::None,
self.chain_id,
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RewardEarnings {
#[serde(flatten)]
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RewardTotalEarnings {
#[serde(flatten)]
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RewardPercentages {
#[serde(flatten)]
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RewardMarketEarning {
#[serde(flatten)]
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RewardMarket {
#[serde(flatten)]
pub data: serde_json::Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Paginated<T> {
#[serde(default = "Vec::new")]
pub data: Vec<T>,
#[serde(default)]
pub next_cursor: Option<String>,
#[serde(default)]
pub limit: Option<u32>,
#[serde(default)]
pub count: Option<u32>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reward_earnings_deserializes() {
let json = r#"{"amount": "1.5", "day": "2024-01-15"}"#;
let resp: RewardEarnings = serde_json::from_str(json).unwrap();
assert_eq!(resp.data["amount"], "1.5");
assert_eq!(resp.data["day"], "2024-01-15");
}
#[test]
fn reward_total_earnings_deserializes() {
let json = r#"{"total": "42.0"}"#;
let resp: RewardTotalEarnings = serde_json::from_str(json).unwrap();
assert_eq!(resp.data["total"], "42.0");
}
#[test]
fn reward_total_earnings_list_deserializes() {
let json = r#"[{"asset_address": "0xabc", "total": "42.0"}]"#;
let resp: Vec<RewardTotalEarnings> = serde_json::from_str(json).unwrap();
assert_eq!(resp.len(), 1);
assert_eq!(resp[0].data["total"], "42.0");
}
#[test]
fn reward_percentages_deserializes() {
let json = r#"{"maker": "0.5", "taker": "0.3"}"#;
let resp: RewardPercentages = serde_json::from_str(json).unwrap();
assert_eq!(resp.data["maker"], "0.5");
}
#[test]
fn reward_market_earning_list_deserializes() {
let json = r#"[
{"condition_id": "0xabc", "amount": "10.0"},
{"condition_id": "0xdef", "amount": "5.0"}
]"#;
let resp: Vec<RewardMarketEarning> = serde_json::from_str(json).unwrap();
assert_eq!(resp.len(), 2);
assert_eq!(resp[0].data["condition_id"], "0xabc");
}
#[test]
fn reward_market_deserializes() {
let json = r#"{"condition_id": "0xabc", "reward_rate": "0.01"}"#;
let resp: RewardMarket = serde_json::from_str(json).unwrap();
assert_eq!(resp.data["condition_id"], "0xabc");
}
#[test]
fn reward_market_list_deserializes() {
let json = r#"[{"condition_id": "0xabc"}, {"condition_id": "0xdef"}]"#;
let resp: Vec<RewardMarket> = serde_json::from_str(json).unwrap();
assert_eq!(resp.len(), 2);
}
#[test]
fn current_markets_paginated_response_deserializes() {
let json = r#"{
"limit": 500,
"count": 1,
"next_cursor": "LTE=",
"data": [
{"condition_id": "0xabc", "rewards_max_spread": 99}
]
}"#;
let page: Paginated<RewardMarket> =
serde_json::from_str(json).expect("paginated reward markets should deserialize");
assert_eq!(page.data.len(), 1);
assert_eq!(page.count, Some(1));
assert_eq!(page.next_cursor.as_deref(), Some("LTE="));
assert_eq!(page.data[0].data["condition_id"], "0xabc");
}
#[test]
fn market_earnings_paginated_response_deserializes() {
let json = r#"{
"limit": 100,
"count": 1,
"next_cursor": "LTE=",
"data": [
{"condition_id": "0xabc", "earnings": 0.237519}
]
}"#;
let page: Paginated<RewardMarketEarning> =
serde_json::from_str(json).expect("paginated market earnings should deserialize");
assert_eq!(page.data.len(), 1);
assert_eq!(page.count, Some(1));
assert_eq!(page.data[0].data["condition_id"], "0xabc");
}
#[test]
fn reward_earnings_empty_object_deserializes() {
let json = r#"{}"#;
let resp: RewardEarnings = serde_json::from_str(json).unwrap();
assert!(resp.data.is_object());
}
}