Skip to main content

df_hyperliquid_core/account/settings/
referral.rs

1//! Referrals: `setReferrer`, the L1 action that gives an account a referrer,
2//! and the `referral` read. The venue keeps the first code an account sets.
3
4use crate::client::{ApiError, Transport, info, user_request};
5use crate::utils::sign::{L1Action, Map};
6use crate::utils::{Address, Dec};
7use serde::Deserialize;
8
9/// The longest code `setReferrer` takes.
10pub const LONGEST_CODE: usize = 20;
11
12#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
13#[error("a referral code is 1 to {LONGEST_CODE} characters, which `{0}` is not")]
14pub struct BadCode(pub String);
15
16/// `setReferrer`: `{type, code}`. An agent may sign it for its master.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct SetReferrer {
19    code: String,
20}
21
22impl SetReferrer {
23    pub fn new(code: &str) -> Result<SetReferrer, BadCode> {
24        match (1..=LONGEST_CODE).contains(&code.chars().count()) {
25            true => Ok(SetReferrer { code: code.to_owned() }),
26            false => Err(BadCode(code.to_owned())),
27        }
28    }
29}
30
31impl L1Action for SetReferrer {
32    fn action(&self) -> Map {
33        Map::new().put("type", "setReferrer").put("code", self.code.clone())
34    }
35}
36
37/// `{"type":"referral","user":..}`.
38#[must_use]
39pub fn referral_request(user: Address) -> String {
40    user_request("referral", user)
41}
42
43pub async fn referral(transport: &impl Transport, user: Address) -> Result<Referral, ApiError> {
44    info(transport, referral_request(user)).await
45}
46
47/// Who referred an account, and its own standing as a referrer. The top
48/// level's figures are USDC's; each quote token's are in `token_to_state`.
49/// Reward history is left unread.
50#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct Referral {
53    #[serde(default)]
54    pub referred_by: Option<ReferredBy>,
55    pub cum_vlm: Dec,
56    pub unclaimed_rewards: Dec,
57    pub claimed_rewards: Dec,
58    #[serde(default)]
59    pub builder_rewards: Dec,
60    pub referrer_state: ReferrerState,
61    /// Each quote token's figures, by the token's index (`0` USDC); not always sent.
62    #[serde(default)]
63    pub token_to_state: Option<Vec<(u32, TokenRewards)>>,
64}
65
66/// What an account traded and was paid in one quote token.
67#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct TokenRewards {
70    pub cum_vlm: Dec,
71    pub unclaimed_rewards: Dec,
72    pub claimed_rewards: Dec,
73    pub builder_rewards: Dec,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
77pub struct ReferredBy {
78    pub referrer: Address,
79    pub code: String,
80}
81
82/// `stage` is `needToTrade` (with `required`, the volume still to trade),
83/// `needToCreateCode`, or `ready` (with the account's own `code`).
84#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
85pub struct ReferrerState {
86    pub stage: String,
87    #[serde(default)]
88    pub data: Option<ReferrerData>,
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct ReferrerData {
94    #[serde(default)]
95    pub code: Option<String>,
96    #[serde(default)]
97    pub required: Option<Dec>,
98    #[serde(default)]
99    pub n_referrals: Option<u64>,
100    /// The accounts referred under the code.
101    #[serde(default)]
102    pub referral_states: Vec<ReferralState>,
103}
104
105/// One account referred under the code. Per-token figures are left unread.
106#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct ReferralState {
109    pub user: Address,
110    #[serde(default)]
111    pub cum_vlm: Dec,
112    #[serde(default)]
113    pub cum_rewarded_fees_since_referred: Dec,
114    #[serde(default)]
115    pub cum_fees_rewarded_to_referrer: Dec,
116    /// Milliseconds.
117    #[serde(default)]
118    pub time_joined: u64,
119}
120
121impl Referral {
122    /// One quote token's figures, by its index.
123    #[must_use]
124    pub fn token(&self, index: u32) -> Option<&TokenRewards> {
125        self.token_to_state.as_deref()?.iter().find(|(token, _)| *token == index).map(|(_, state)| state)
126    }
127
128    /// The account's own code, once it has made one.
129    #[must_use]
130    pub fn own_code(&self) -> Option<&str> {
131        self.referrer_state.data.as_ref()?.code.as_deref()
132    }
133
134    /// The code the account trades under, if any.
135    #[must_use]
136    pub fn referred_code(&self) -> Option<&str> {
137        self.referred_by.as_ref().map(|by| by.code.as_str())
138    }
139
140    /// Who owns the code the account trades under.
141    #[must_use]
142    pub fn referrer(&self) -> Option<Address> {
143        self.referred_by.as_ref().map(|by| by.referrer)
144    }
145
146    /// Whether the account owns `code`, and so cannot be referred under it.
147    /// Codes are compared without case.
148    #[must_use]
149    pub fn owns(&self, code: &str) -> bool {
150        self.own_code().is_some_and(|own| own.eq_ignore_ascii_case(code))
151    }
152}
153
154#[cfg(test)]
155#[path = "tests/referral.rs"]
156mod tests;