Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class DelegationViewModel @Inject constructor(
.getValidatorUrl(getCurrentBlockExplorer.getCurrentBlockExplorer(chain), delegation.validator.id)
listOfNotNull(
DelegationProperty.Name(delegation.validator.name, validatorUrl),
DelegationProperty.Apr(delegation.validator),
delegation.validator.takeIf { it.apr != 0.0 }?.let { DelegationProperty.Apr(it) },
DelegationProperty.TransactionStatus(delegation.base.state, delegation.validator.isActive),
delegation.base.state
.takeIf {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ import com.gemwallet.android.ui.components.list_head.CenteredListHead
import com.gemwallet.android.ui.components.list_head.HeaderIcon
import com.gemwallet.android.ui.components.list_item.DelegationItem
import com.gemwallet.android.ui.components.list_item.SubheaderItem
import com.gemwallet.android.ui.components.list_item.availableIn
import com.gemwallet.android.ui.components.list_item.energyItem
import com.gemwallet.android.ui.components.list_item.property.PropertyItem
import com.gemwallet.android.ui.components.list_item.property.itemsPositioned
Expand Down Expand Up @@ -129,7 +128,6 @@ fun StakeScene(
DelegationItem(
assetInfo = assetInfo,
delegation = item,
completedAt = availableIn(item),
listPosition = ListPosition.getPosition(index, delegations.size),
onClick = { onDelegation(item) }
)
Expand All @@ -150,9 +148,9 @@ private fun LazyListScope.stakeInfoSection(assetInfo: AssetInfo) {
val minAmountValue = Config().getStakeConfig(assetInfo.asset.chain.string).minAmount.toLong()
val iconUrl = assetInfo.id().getIconUrl()
val rows = listOfNotNull(
minAmountValue.takeIf { it > 0 }?.let { StakeInfoRow.MinAmount(it, assetInfo.asset.chain) },
StakeInfoRow.Apr(assetInfo.stakeApr ?: 0.0, iconUrl),
assetInfo.lockTime?.let { StakeInfoRow.LockTime(it, iconUrl) },
minAmountValue.takeIf { it > 0 }?.let { StakeInfoRow.MinAmount(it, assetInfo.asset.chain) },
)
itemsPositioned(rows) { position, row ->
when (row) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.gemwallet.android.ui.components.list_item

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -29,7 +28,6 @@ import com.wallet.core.primitives.DelegationState.Pending
fun DelegationItem(
assetInfo: AssetInfo,
delegation: Delegation,
completedAt: String,
listPosition: ListPosition,
onClick: () -> Unit
) {
Expand All @@ -46,21 +44,7 @@ fun DelegationItem(
ListItemTitleText(text = delegation.validator.name)
},
subtitle = {
val stateColor = delegation.base.state.color()
val stateText = delegation.stateText()
Column {
ListItemSupportText(stateText, color = stateColor)
when (delegation.base.state) {
Pending,
Activating,
Deactivating -> completedAt.takeIf { it.isNotEmpty() && it != "0" }?.let {
ListItemSupportText(it)
}
Active,
Inactive,
AwaitingWithdrawal -> Unit
}
}
ListItemSupportText(delegation.stateText(), color = delegation.base.state.color())
},
trailing = {
val balance = DelegationBalanceInfoUIModel(
Expand Down
5 changes: 3 additions & 2 deletions core/crates/gem_hypercore/src/provider/staking.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use async_trait::async_trait;
use chain_traits::ChainStaking;
use chrono::Utc;
use futures::try_join;
use std::error::Error;

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

async fn get_staking_delegations(&self, address: String) -> Result<Vec<DelegationBase>, Box<dyn Error + Sync + Send>> {
let (delegations, stake_balance) = try_join!(self.get_staking_delegations(&address), self.get_stake_balance(&address))?;
Ok(staking_mapper::map_staking_delegations(delegations, stake_balance, self.chain))
let (delegations, history) = try_join!(self.get_staking_delegations(&address), self.get_delegator_history(&address))?;
Ok(staking_mapper::map_staking_delegations(delegations, history, Utc::now(), self.chain))
}
}
104 changes: 71 additions & 33 deletions core/crates/gem_hypercore/src/provider/staking_mapper.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::models::balance::{DelegationBalance, StakeBalance, Validator};
use crate::models::balance::{DelegationBalance, Validator};
use crate::models::user::DelegatorHistoryUpdate;
use crate::provider::transaction_state_mapper::DELEGATOR_WITHDRAWAL_INITIATED;
use chrono::{DateTime, Duration, Utc};
use num_bigint::BigUint;
use number_formatter::BigNumberFormatter;
use primitives::{Asset, Chain, DelegationBase, DelegationState, DelegationValidator};
Expand All @@ -15,7 +18,7 @@ pub fn map_staking_validators(validators: Vec<Validator>, chain: Chain, apy: Opt
result
}

pub fn map_staking_delegations(delegations: Vec<DelegationBalance>, stake_balance: StakeBalance, chain: Chain) -> Vec<DelegationBase> {
pub fn map_staking_delegations(delegations: Vec<DelegationBalance>, history: Vec<DelegatorHistoryUpdate>, now: DateTime<Utc>, chain: Chain) -> Vec<DelegationBase> {
let native_decimals = Asset::from_chain(chain).decimals as u32;
let mut result: Vec<DelegationBase> = delegations
.into_iter()
Expand All @@ -31,34 +34,57 @@ pub fn map_staking_delegations(delegations: Vec<DelegationBalance>, stake_balanc
})
.collect();

let pending = BigNumberFormatter::value_from_amount_biguint(&stake_balance.total_pending_withdrawal, native_decimals).unwrap_or_default();
if pending > BigUint::from(0u32) {
result.push(DelegationBase {
asset_id: chain.as_asset_id(),
state: DelegationState::Pending,
balance: pending,
shares: BigUint::from(0u32),
rewards: BigUint::from(0u32),
completion_date: None,
delegation_id: DelegationValidator::SYSTEM_ID.to_string(),
validator_id: DelegationValidator::SYSTEM_ID.to_string(),
});
}

result.extend(map_pending_withdrawals(history, now, chain, native_decimals));
result
}

fn map_pending_withdrawals(history: Vec<DelegatorHistoryUpdate>, now: DateTime<Utc>, chain: Chain, native_decimals: u32) -> Vec<DelegationBase> {
let lock = Duration::seconds(chain.config().stake.as_ref().map(|stake| stake.lock_time).unwrap_or_default() as i64);

history
.into_iter()
.filter_map(|entry| {
let withdrawal = entry.delta.withdrawal?;
if withdrawal.phase != DELEGATOR_WITHDRAWAL_INITIATED {
return None;
}
let completion_date = DateTime::from_timestamp_millis(entry.time as i64)? + lock;
if completion_date <= now {
return None;
}
Some(DelegationBase {
asset_id: chain.as_asset_id(),
state: DelegationState::Pending,
balance: BigNumberFormatter::value_from_amount_biguint(&withdrawal.amount, native_decimals).unwrap_or_default(),
shares: BigUint::from(0u32),
rewards: BigUint::from(0u32),
completion_date: Some(completion_date),
delegation_id: format!("unstaking_{}", entry.time),
validator_id: DelegationValidator::SYSTEM_ID.to_string(),
})
})
.collect()
}

#[cfg(test)]
mod tests {
use super::*;
use crate::models::balance::ValidatorStats;
use crate::models::user::{DelegatorHistoryDelta, DelegatorWithdrawalDelta};
use primitives::{Chain, DelegationState};

fn stake_balance(total_pending_withdrawal: &str) -> StakeBalance {
StakeBalance {
delegated: "0".to_string(),
undelegated: "0".to_string(),
total_pending_withdrawal: total_pending_withdrawal.to_string(),
fn withdrawal_entry(time: u64, amount: &str, phase: &str) -> DelegatorHistoryUpdate {
DelegatorHistoryUpdate {
time,
hash: "0x0".to_string(),
delta: DelegatorHistoryDelta {
c_deposit: None,
delegate: None,
withdrawal: Some(DelegatorWithdrawalDelta {
amount: amount.to_string(),
phase: phase.to_string(),
}),
},
}
}

Expand Down Expand Up @@ -107,7 +133,7 @@ mod tests {
fn test_map_staking_delegations() {
let delegations: Vec<DelegationBalance> = serde_json::from_str(include_str!("../../testdata/staking_delegations.json")).unwrap();

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

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

Expand All @@ -127,21 +153,33 @@ mod tests {
}

#[test]
fn test_map_staking_delegations_pending_withdrawal() {
let result = map_staking_delegations(vec![], stake_balance("0.015"), Chain::HyperCore);
fn test_map_staking_delegations_pending_withdrawals() {
let now = DateTime::from_timestamp(1_780_000_000, 0).unwrap();
let at = |days: i64| (now - Duration::days(days)).timestamp_millis() as u64;
let history = vec![
withdrawal_entry(at(1), "1.5", "initiated"),
withdrawal_entry(at(8), "2.0", "initiated"),
withdrawal_entry(at(1), "3.0", "finalized"),
DelegatorHistoryUpdate {
time: at(0),
hash: "0x0".to_string(),
delta: DelegatorHistoryDelta {
c_deposit: None,
delegate: None,
withdrawal: None,
},
},
];

let result = map_staking_delegations(vec![], history, now, Chain::HyperCore);

assert_eq!(result.len(), 1);
let pending = &result[0];
assert_eq!(pending.state, DelegationState::Pending);
assert_eq!(pending.validator_id, DelegationValidator::SYSTEM_ID);
assert_eq!(pending.balance.to_string(), "1500000");
assert!(pending.completion_date.is_none());
}

#[test]
fn test_map_staking_delegations_no_pending_withdrawal() {
let result = map_staking_delegations(vec![], stake_balance("0"), Chain::HyperCore);

assert!(result.is_empty());
assert_eq!(pending.balance.to_string(), "150000000");
assert_eq!(pending.delegation_id, format!("unstaking_{}", at(1)));
assert_eq!(pending.completion_date, Some(now + Duration::days(6)));
assert!(pending.completion_date.unwrap() > now);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::perpetual_formatter::usdc_value;

pub const ACTION_HISTORY_QUERY_LOOKBACK_MS: u64 = 5_000;
const ACTION_HISTORY_MATCH_WINDOW_MS: u64 = 5 * 60 * 1_000;
const DELEGATOR_WITHDRAWAL_INITIATED: &str = "initiated";
pub(crate) const DELEGATOR_WITHDRAWAL_INITIATED: &str = "initiated";

fn perpetual_fill_type_and_direction(dir: &FillDirection) -> Option<(TransactionType, PerpetualDirection)> {
match dir {
Expand Down
3 changes: 2 additions & 1 deletion ios/Packages/Store/Sources/Requests/DelegationsRequest.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c). Gem Wallet. All rights reserved.

internal import BigInt
import Foundation
import GRDB
import Primitives
Expand All @@ -23,9 +24,9 @@ public struct DelegationsRequest: DatabaseQueryable {
.filter(StakeDelegationRecord.Columns.assetId == assetId.identifier)
.joining(required: StakeDelegationRecord.validator
.filter(StakeValidatorRecord.Columns.providerType == providerType.rawValue))
.order(StakeDelegationRecord.Columns.balance.desc)
.asRequest(of: StakeDelegationInfo.self)
.fetchAll(db)
.compactMap { $0.mapToDelegation() }
.sorted { $0.base.balanceValue > $1.base.balanceValue }
}
}
Loading