use serde::Deserialize;
use crate::client::{ApiError, Call, Transport, read};
use crate::utils::ids::whole;
use crate::utils::lenient::{Done, blank_dec, list, text, zero_dec};
use crate::utils::{AccountIndex, Address, Dec, Figure};
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
pub struct TradeStats {
#[serde(default, deserialize_with = "whole")]
pub count: u64,
#[serde(default, deserialize_with = "zero_dec")]
pub volume: Dec,
#[serde(default, deserialize_with = "whole")]
pub web_count: u64,
#[serde(default, deserialize_with = "zero_dec")]
pub web_volume: Dec,
#[serde(default, deserialize_with = "whole")]
pub mobile_app_count: u64,
#[serde(default, deserialize_with = "zero_dec")]
pub mobile_app_volume: Dec,
#[serde(default, deserialize_with = "whole")]
pub mobile_browser_count: u64,
#[serde(default, deserialize_with = "zero_dec")]
pub mobile_browser_volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub free_maker_volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub free_taker_volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub non_free_maker_volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub non_free_taker_volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub maker_fees_paid: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub taker_fees_paid: Dec,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Referral {
pub l1_address: Address,
#[serde(default, deserialize_with = "text")]
pub referral_code: String,
#[serde(default, deserialize_with = "whole")]
pub used_at: u64,
#[serde(default)]
pub trade_stats: TradeStats,
#[serde(default, deserialize_with = "text")]
pub tier: String,
#[serde(default, deserialize_with = "text")]
pub source: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
pub struct ReferralTotals {
#[serde(default, deserialize_with = "whole")]
pub count: u64,
#[serde(default, deserialize_with = "zero_dec")]
pub volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub free_maker_volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub free_taker_volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub non_free_maker_volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub non_free_taker_volume: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub maker_fees_paid: Dec,
#[serde(default, deserialize_with = "zero_dec")]
pub taker_fees_paid: Dec,
#[serde(default, deserialize_with = "whole")]
pub total_referrals: u64,
#[serde(default, deserialize_with = "whole")]
pub total_premium_referrals: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct UserReferrals {
#[serde(default, deserialize_with = "list")]
pub referrals: Vec<Referral>,
#[serde(default, deserialize_with = "text")]
pub cursor: String,
#[serde(default, deserialize_with = "text")]
pub used_code: String,
#[serde(default)]
pub totals: ReferralTotals,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct ReferralStat {
#[serde(default, deserialize_with = "whole")]
pub start_timestamp: u64,
#[serde(default, deserialize_with = "whole")]
pub end_timestamp: u64,
#[serde(default)]
pub trade_stats: TradeStats,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct ReferralPointEntry {
pub l1_address: Address,
pub total_points: Figure,
pub week_points: Figure,
pub total_reward_points: Figure,
pub week_reward_points: Figure,
#[serde(default, deserialize_with = "text")]
pub reward_point_multiplier: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct ReferralPoints {
#[serde(default, deserialize_with = "list")]
pub referrals: Vec<ReferralPointEntry>,
pub user_total_points: Figure,
pub user_last_week_points: Figure,
pub user_total_referral_reward_points: Figure,
pub user_last_week_referral_reward_points: Figure,
#[serde(default, deserialize_with = "text")]
pub reward_point_multiplier: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct Kickback {
#[serde(default, deserialize_with = "whole")]
pub date: u64,
#[serde(default, deserialize_with = "blank_dec")]
pub percentage: Option<Dec>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct L1Metadata {
pub l1_address: Address,
#[serde(default)]
pub can_invite: bool,
#[serde(default, deserialize_with = "blank_dec")]
pub referral_points_percentage: Option<Dec>,
#[serde(default, deserialize_with = "blank_dec")]
pub referral_program_kickback: Option<Dec>,
#[serde(default, deserialize_with = "list")]
pub referral_program_kickback_history: Vec<Kickback>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct UseReferral {
pub l1_address: Address,
pub referral_code: String,
pub discord: Option<String>,
pub telegram: Option<String>,
pub x: Option<String>,
pub signature: Option<String>,
pub source: Option<String>,
}
#[derive(Deserialize)]
struct Code {
#[serde(default, deserialize_with = "text")]
referral_code: String,
}
#[derive(Deserialize)]
struct Success {
#[serde(default)]
success: bool,
}
#[derive(Deserialize)]
struct Stats {
#[serde(default, deserialize_with = "list")]
stats: Vec<ReferralStat>,
}
#[must_use]
pub fn referral_code_call(account: AccountIndex) -> Call {
Call::get("referral/get").field("account_index", account).authed()
}
pub async fn referral_code(transport: &impl Transport, account: AccountIndex) -> Result<String, ApiError> {
Ok(read::<Code>(transport, referral_code_call(account)).await?.referral_code)
}
#[must_use]
pub fn create_referral_code_call(account: AccountIndex) -> Call {
Call::post("referral/create").field("account_index", account).authed()
}
pub async fn create_referral_code(transport: &impl Transport, account: AccountIndex) -> Result<String, ApiError> {
Ok(read::<Code>(transport, create_referral_code_call(account)).await?.referral_code)
}
#[must_use]
pub fn update_referral_code_call(account: AccountIndex, code: &str) -> Call {
Call::post("referral/update").field("account_index", account).field("new_referral_code", code).authed()
}
pub async fn update_referral_code(
transport: &impl Transport,
account: AccountIndex,
code: &str,
) -> Result<bool, ApiError> {
Ok(read::<Success>(transport, update_referral_code_call(account, code)).await?.success)
}
#[must_use]
pub fn update_kickback_call(account: AccountIndex, percentage: Dec) -> Call {
Call::post("referral/kickback/update")
.field("account_index", account)
.field("kickback_percentage", percentage.to_wire())
.authed()
}
pub async fn update_kickback(
transport: &impl Transport,
account: AccountIndex,
percentage: Dec,
) -> Result<bool, ApiError> {
Ok(read::<Success>(transport, update_kickback_call(account, percentage)).await?.success)
}
#[must_use]
pub fn use_referral_code_call(form: &UseReferral) -> Call {
Call::post("referral/use")
.field("l1_address", form.l1_address.to_hex())
.field("referral_code", &form.referral_code)
.maybe("discord", form.discord.as_deref())
.maybe("telegram", form.telegram.as_deref())
.maybe("x", form.x.as_deref())
.maybe("signature", form.signature.as_deref())
.maybe("source", form.source.as_deref())
.authed()
}
pub async fn use_referral_code(transport: &impl Transport, form: &UseReferral) -> Result<(), ApiError> {
read::<Done>(transport, use_referral_code_call(form)).await.map(drop)
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ReferralQuery {
pub cursor: Option<String>,
pub stats_from: Option<u64>,
pub stats_to: Option<u64>,
pub limit: Option<u16>,
pub eligible_only: bool,
}
#[must_use]
pub fn user_referrals_call(owner: Address, query: &ReferralQuery) -> Call {
Call::get("referral/userReferrals")
.field("l1_address", owner.to_hex())
.maybe("cursor", query.cursor.as_deref())
.maybe("stats_start_timestamp", query.stats_from)
.maybe("stats_end_timestamp", query.stats_to)
.maybe("limit", query.limit.map(|limit| limit.clamp(1, 300)))
.maybe("is_eligible", query.eligible_only.then_some(true))
.authed()
}
pub async fn user_referrals(
transport: &impl Transport,
owner: Address,
query: &ReferralQuery,
) -> Result<UserReferrals, ApiError> {
read(transport, user_referrals_call(owner, query)).await
}
#[must_use]
pub fn referral_stats_call(owner: Address, eligible_only: bool) -> Call {
Call::get("referral/stats")
.field("l1_address", owner.to_hex())
.maybe("is_eligible", eligible_only.then_some(true))
.authed()
}
pub async fn referral_stats(
transport: &impl Transport,
owner: Address,
eligible_only: bool,
) -> Result<Vec<ReferralStat>, ApiError> {
Ok(read::<Stats>(transport, referral_stats_call(owner, eligible_only)).await?.stats)
}
#[must_use]
pub fn referral_points_call(account: AccountIndex) -> Call {
Call::get("referral/points").field("account_index", account).authed()
}
pub async fn referral_points(transport: &impl Transport, account: AccountIndex) -> Result<ReferralPoints, ApiError> {
read(transport, referral_points_call(account)).await
}
#[must_use]
pub fn l1_metadata_call(owner: Address) -> Call {
Call::get("l1Metadata").field("l1_address", owner.to_hex()).authed()
}
pub async fn l1_metadata(transport: &impl Transport, owner: Address) -> Result<L1Metadata, ApiError> {
read(transport, l1_metadata_call(owner)).await
}
#[cfg(test)]
#[path = "tests/referral.rs"]
mod tests;