Skip to content
This repository was archived by the owner on Jun 1, 2026. It is now read-only.

Commit cf9872f

Browse files
committed
Address review: simplify HyperCore pending withdrawals
- Source pending from delegatorSummary instead of delegatorHistory, drop countdown - Model staking amounts as String to avoid f64 round-trip - Drop no-op serde rename_all and inline balance parsing
1 parent ec8da70 commit cf9872f

5 files changed

Lines changed: 49 additions & 138 deletions

File tree

‎crates/gem_hypercore/src/models/balance.rs‎

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use gem_evm::ethereum_address_checksum;
22
use serde::{Deserialize, Serialize};
33
use serde_serializers::deserialize_f64_from_str;
4-
use strum::{Display, EnumString};
54

65
#[derive(Debug, Clone, Serialize, Deserialize)]
76
#[serde(rename_all = "camelCase")]
@@ -35,20 +34,16 @@ pub struct Token {
3534
#[derive(Debug, Clone, Serialize, Deserialize)]
3635
#[serde(rename_all = "camelCase")]
3736
pub struct StakeBalance {
38-
#[serde(deserialize_with = "deserialize_f64_from_str")]
39-
pub delegated: f64,
40-
#[serde(deserialize_with = "deserialize_f64_from_str")]
41-
pub undelegated: f64,
42-
#[serde(deserialize_with = "deserialize_f64_from_str")]
43-
pub total_pending_withdrawal: f64,
37+
pub delegated: String,
38+
pub undelegated: String,
39+
pub total_pending_withdrawal: String,
4440
}
4541

4642
#[derive(Debug, Clone, Serialize, Deserialize)]
4743
#[serde(rename_all = "camelCase")]
4844
pub struct DelegationBalance {
4945
pub validator: String,
50-
#[serde(deserialize_with = "deserialize_f64_from_str")]
51-
pub amount: f64,
46+
pub amount: String,
5247
pub locked_until_timestamp: u64,
5348
}
5449

@@ -58,53 +53,6 @@ impl DelegationBalance {
5853
}
5954
}
6055

61-
#[derive(Debug, Clone, Serialize, Deserialize)]
62-
#[serde(rename_all = "camelCase")]
63-
pub struct DelegatorHistoryEntry {
64-
pub time: u64,
65-
pub delta: DelegatorDelta,
66-
}
67-
68-
#[derive(Debug, Clone, Serialize, Deserialize)]
69-
#[serde(rename_all = "camelCase")]
70-
pub struct DelegatorDelta {
71-
pub withdrawal: Option<DelegatorWithdrawal>,
72-
}
73-
74-
#[derive(Debug, Clone, Serialize, Deserialize)]
75-
#[serde(rename_all = "camelCase")]
76-
pub struct DelegatorWithdrawal {
77-
#[serde(deserialize_with = "deserialize_f64_from_str")]
78-
pub amount: f64,
79-
pub phase: WithdrawalPhase,
80-
}
81-
82-
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Display, EnumString)]
83-
#[serde(from = "String", into = "String")]
84-
pub enum WithdrawalPhase {
85-
#[strum(serialize = "initiated")]
86-
Initiated,
87-
#[strum(serialize = "finalized")]
88-
Finalized,
89-
#[strum(default)]
90-
Other(String),
91-
}
92-
93-
impl From<String> for WithdrawalPhase {
94-
fn from(value: String) -> Self {
95-
match value.parse() {
96-
Ok(phase) => phase,
97-
Err(_) => WithdrawalPhase::Other(value),
98-
}
99-
}
100-
}
101-
102-
impl From<WithdrawalPhase> for String {
103-
fn from(value: WithdrawalPhase) -> Self {
104-
value.to_string()
105-
}
106-
}
107-
10856
#[derive(Debug, Clone, Serialize, Deserialize)]
10957
#[serde(rename_all = "camelCase")]
11058
pub struct Validator {

‎crates/gem_hypercore/src/provider/balances_mapper.rs‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ pub fn map_balance_tokens(spot_balances: &Balances, spot_tokens: &[SpotToken], t
4747

4848
pub fn map_balance_staking(balance: &StakeBalance, chain: Chain) -> Result<AssetBalance, Box<dyn Error + Sync + Send>> {
4949
let native_decimals = Asset::from_chain(chain).decimals as u32;
50-
let available_biguint = BigNumberFormatter::value_from_amount_biguint(&balance.delegated.to_string(), native_decimals).unwrap_or_default();
51-
let pending_biguint = BigNumberFormatter::value_from_amount_biguint(&balance.total_pending_withdrawal.to_string(), native_decimals).unwrap_or_default();
50+
let available_biguint = BigNumberFormatter::value_from_amount_biguint(&balance.delegated, native_decimals).unwrap_or_default();
51+
let pending_biguint = BigNumberFormatter::value_from_amount_biguint(&balance.total_pending_withdrawal, native_decimals).unwrap_or_default();
5252

5353
Ok(AssetBalance::new_balance(
5454
chain.as_asset_id(),
@@ -138,9 +138,9 @@ mod tests {
138138
#[test]
139139
fn test_map_balance_staking() {
140140
let stake_balance = StakeBalance {
141-
delegated: 100.0,
142-
undelegated: 0.0,
143-
total_pending_withdrawal: 10.0,
141+
delegated: "100.0".to_string(),
142+
undelegated: "0.0".to_string(),
143+
total_pending_withdrawal: "10.0".to_string(),
144144
};
145145
let result = map_balance_staking(&stake_balance, Chain::HyperCore).unwrap();
146146

‎crates/gem_hypercore/src/provider/staking.rs‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use async_trait::async_trait;
22
use chain_traits::ChainStaking;
3-
use chrono::Utc;
43
use futures::try_join;
54
use std::error::Error;
65

@@ -23,7 +22,7 @@ impl<C: Client> ChainStaking for HyperCoreClient<C> {
2322
}
2423

2524
async fn get_staking_delegations(&self, address: String) -> Result<Vec<DelegationBase>, Box<dyn Error + Sync + Send>> {
26-
let (delegations, history) = try_join!(self.get_staking_delegations(&address), self.get_staking_history(&address))?;
27-
Ok(staking_mapper::map_staking_delegations(delegations, history, Utc::now(), self.chain))
25+
let (delegations, stake_balance) = try_join!(self.get_staking_delegations(&address), self.get_stake_balance(&address))?;
26+
Ok(staking_mapper::map_staking_delegations(delegations, stake_balance, self.chain))
2827
}
2928
}
Lines changed: 37 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
use crate::models::balance::{DelegationBalance, DelegatorHistoryEntry, Validator, WithdrawalPhase};
2-
use chrono::{DateTime, Duration, Utc};
1+
use crate::models::balance::{DelegationBalance, StakeBalance, Validator};
32
use num_bigint::BigUint;
43
use number_formatter::BigNumberFormatter;
54
use primitives::{Asset, Chain, DelegationBase, DelegationState, DelegationValidator};
6-
use std::str::FromStr;
75

86
pub fn map_staking_validators(validators: Vec<Validator>, chain: Chain, apy: Option<f64>) -> Vec<DelegationValidator> {
97
let calculated_apy = apy.unwrap_or_else(|| Validator::max_apr(validators.clone()));
@@ -17,14 +15,14 @@ pub fn map_staking_validators(validators: Vec<Validator>, chain: Chain, apy: Opt
1715
result
1816
}
1917

20-
pub fn map_staking_delegations(delegations: Vec<DelegationBalance>, history: Vec<DelegatorHistoryEntry>, now: DateTime<Utc>, chain: Chain) -> Vec<DelegationBase> {
18+
pub fn map_staking_delegations(delegations: Vec<DelegationBalance>, stake_balance: StakeBalance, chain: Chain) -> Vec<DelegationBase> {
2119
let native_decimals = Asset::from_chain(chain).decimals as u32;
2220
let mut result: Vec<DelegationBase> = delegations
2321
.into_iter()
2422
.map(|x| DelegationBase {
2523
asset_id: chain.as_asset_id(),
2624
state: DelegationState::Active,
27-
balance: parse_balance(&x.amount.to_string(), native_decimals),
25+
balance: BigNumberFormatter::value_from_amount_biguint(&x.amount, native_decimals).unwrap_or_default(),
2826
shares: BigUint::from(0u32),
2927
rewards: BigUint::from(0u32),
3028
completion_date: None,
@@ -33,58 +31,34 @@ pub fn map_staking_delegations(delegations: Vec<DelegationBalance>, history: Vec
3331
})
3432
.collect();
3533

36-
result.extend(map_pending_withdrawals(history, now, chain, native_decimals));
37-
result
38-
}
39-
40-
fn map_pending_withdrawals(history: Vec<DelegatorHistoryEntry>, now: DateTime<Utc>, chain: Chain, native_decimals: u32) -> Vec<DelegationBase> {
41-
let lock_time = chain.config().stake.as_ref().map(|stake| stake.lock_time).unwrap_or_default();
42-
let lock = Duration::seconds(lock_time as i64);
43-
44-
history
45-
.into_iter()
46-
.filter_map(|entry| {
47-
let withdrawal = entry.delta.withdrawal?;
48-
if withdrawal.phase != WithdrawalPhase::Initiated {
49-
return None;
50-
}
51-
let completion_date = DateTime::from_timestamp_millis(entry.time as i64)? + lock;
52-
if completion_date <= now {
53-
return None;
54-
}
55-
Some(DelegationBase {
56-
asset_id: chain.as_asset_id(),
57-
state: DelegationState::Pending,
58-
balance: parse_balance(&withdrawal.amount.to_string(), native_decimals),
59-
shares: BigUint::from(0u32),
60-
rewards: BigUint::from(0u32),
61-
completion_date: Some(completion_date),
62-
delegation_id: format!("unstaking_{}", entry.time),
63-
validator_id: DelegationValidator::SYSTEM_ID.to_string(),
64-
})
65-
})
66-
.collect()
67-
}
34+
let pending = BigNumberFormatter::value_from_amount_biguint(&stake_balance.total_pending_withdrawal, native_decimals).unwrap_or_default();
35+
if pending > BigUint::from(0u32) {
36+
result.push(DelegationBase {
37+
asset_id: chain.as_asset_id(),
38+
state: DelegationState::Pending,
39+
balance: pending,
40+
shares: BigUint::from(0u32),
41+
rewards: BigUint::from(0u32),
42+
completion_date: None,
43+
delegation_id: DelegationValidator::SYSTEM_ID.to_string(),
44+
validator_id: DelegationValidator::SYSTEM_ID.to_string(),
45+
});
46+
}
6847

69-
fn parse_balance(amount: &str, native_decimals: u32) -> BigUint {
70-
BigNumberFormatter::value_from_amount(amount, native_decimals)
71-
.ok()
72-
.and_then(|s| BigUint::from_str(&s).ok())
73-
.unwrap_or_default()
48+
result
7449
}
7550

7651
#[cfg(test)]
7752
mod tests {
7853
use super::*;
79-
use crate::models::balance::{DelegatorDelta, DelegatorWithdrawal, ValidatorStats};
54+
use crate::models::balance::ValidatorStats;
8055
use primitives::{Chain, DelegationState};
8156

82-
fn withdrawal_entry(time: u64, amount: f64, phase: WithdrawalPhase) -> DelegatorHistoryEntry {
83-
DelegatorHistoryEntry {
84-
time,
85-
delta: DelegatorDelta {
86-
withdrawal: Some(DelegatorWithdrawal { amount, phase }),
87-
},
57+
fn stake_balance(total_pending_withdrawal: &str) -> StakeBalance {
58+
StakeBalance {
59+
delegated: "0".to_string(),
60+
undelegated: "0".to_string(),
61+
total_pending_withdrawal: total_pending_withdrawal.to_string(),
8862
}
8963
}
9064

@@ -105,7 +79,7 @@ mod tests {
10579
assert_eq!(result[0].chain, Chain::HyperCore);
10680
assert!(result[0].is_active);
10781
assert_eq!(result[0].commission, 5.0);
108-
assert_eq!(result[0].apr, 15.0); // max_apr * 100
82+
assert_eq!(result[0].apr, 15.0);
10983

11084
let system = &result[1];
11185
assert_eq!(system.id, DelegationValidator::SYSTEM_ID);
@@ -125,15 +99,15 @@ mod tests {
12599

126100
let result = map_staking_validators(validators, Chain::HyperCore, Some(10.0));
127101
assert_eq!(result.len(), 2);
128-
assert_eq!(result[0].apr, 10.0); // Uses provided APY
102+
assert_eq!(result[0].apr, 10.0);
129103
assert_eq!(result[1].id, DelegationValidator::SYSTEM_ID);
130104
}
131105

132106
#[test]
133107
fn test_map_staking_delegations() {
134108
let delegations: Vec<DelegationBalance> = serde_json::from_str(include_str!("../../testdata/staking_delegations.json")).unwrap();
135109

136-
let result = map_staking_delegations(delegations, vec![], Utc::now(), Chain::HyperCore);
110+
let result = map_staking_delegations(delegations, stake_balance("0"), Chain::HyperCore);
137111

138112
assert_eq!(result.len(), 2);
139113

@@ -153,27 +127,21 @@ mod tests {
153127
}
154128

155129
#[test]
156-
fn test_map_staking_delegations_pending_withdrawals() {
157-
let now = DateTime::from_timestamp(1_780_000_000, 0).unwrap();
158-
let history = vec![
159-
withdrawal_entry(1_779_913_600_000, 1.5, WithdrawalPhase::Initiated),
160-
withdrawal_entry(1_779_308_800_000, 2.0, WithdrawalPhase::Initiated),
161-
withdrawal_entry(1_779_950_000_000, 3.0, WithdrawalPhase::Finalized),
162-
DelegatorHistoryEntry {
163-
time: 1_779_950_000_001,
164-
delta: DelegatorDelta { withdrawal: None },
165-
},
166-
];
167-
168-
let result = map_staking_delegations(vec![], history, now, Chain::HyperCore);
130+
fn test_map_staking_delegations_pending_withdrawal() {
131+
let result = map_staking_delegations(vec![], stake_balance("0.015"), Chain::HyperCore);
169132

170133
assert_eq!(result.len(), 1);
171134
let pending = &result[0];
172135
assert!(matches!(pending.state, DelegationState::Pending));
173136
assert_eq!(pending.validator_id, DelegationValidator::SYSTEM_ID);
174-
assert_eq!(pending.balance.to_string(), "150000000");
175-
assert_eq!(pending.delegation_id, "unstaking_1779913600000");
176-
assert_eq!(pending.completion_date, Some(now + Duration::seconds(518_400)));
177-
assert!(pending.completion_date.unwrap() > now);
137+
assert_eq!(pending.balance.to_string(), "1500000");
138+
assert!(pending.completion_date.is_none());
139+
}
140+
141+
#[test]
142+
fn test_map_staking_delegations_no_pending_withdrawal() {
143+
let result = map_staking_delegations(vec![], stake_balance("0"), Chain::HyperCore);
144+
145+
assert!(result.is_empty());
178146
}
179147
}

‎crates/gem_hypercore/src/rpc/client.rs‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::models::{
2-
balance::{Balances, DelegationBalance, DelegatorHistoryEntry, StakeBalance, Validator},
2+
balance::{Balances, DelegationBalance, StakeBalance, Validator},
33
candlestick::Candlestick,
44
metadata::HypercoreMetadataResponse,
55
order::{OpenOrder, UserFill},
@@ -115,10 +115,6 @@ impl<C: Client> HyperCoreClient<C> {
115115
self.info(json!({"type": "delegations", "user": user})).await
116116
}
117117

118-
pub async fn get_staking_history(&self, user: &str) -> Result<Vec<DelegatorHistoryEntry>, Box<dyn Error + Send + Sync>> {
119-
self.info(json!({"type": "delegatorHistory", "user": user})).await
120-
}
121-
122118
pub async fn get_staking_apy(&self) -> Result<f64, Box<dyn Error + Send + Sync>> {
123119
let validators = self.get_validators().await?;
124120
Ok(Validator::max_apr(validators))

0 commit comments

Comments
 (0)