Updated on 2026-08-14
This commit is contained in:
parent
79eab436df
commit
78720c5cab
20 changed files with 267 additions and 215 deletions
|
|
@ -52,6 +52,7 @@ dependencies {
|
|||
/** Coroutines */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.kotlin.coroutines.rx2)
|
||||
implementation(deps.kotlin.datetime)
|
||||
|
||||
/** Logging */
|
||||
implementation(deps.timber)
|
||||
|
|
|
|||
|
|
@ -1,53 +1,61 @@
|
|||
package com.tangem.datasource.local.token.converter
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.staking.model.StakingID
|
||||
import com.tangem.domain.staking.model.stakekit.BalanceItem
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalanceItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
class YieldBalanceConverter(
|
||||
private val source: StatusSource,
|
||||
) : Converter<YieldBalanceWrapperDTO, YieldBalance> {
|
||||
) : Converter<YieldBalanceWrapperDTO, YieldBalance?> {
|
||||
|
||||
constructor(isCached: Boolean) : this(source = if (isCached) StatusSource.CACHE else StatusSource.ACTUAL)
|
||||
|
||||
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance {
|
||||
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance? {
|
||||
val stakingId = StakingID(
|
||||
integrationId = value.integrationId ?: return null,
|
||||
address = value.addresses.address,
|
||||
)
|
||||
|
||||
return if (value.balances.isEmpty()) {
|
||||
YieldBalance.Empty(
|
||||
integrationId = value.integrationId,
|
||||
address = value.addresses.address,
|
||||
source = source,
|
||||
)
|
||||
YieldBalance.Empty(stakingId = stakingId, source = source)
|
||||
} else {
|
||||
YieldBalance.Data(
|
||||
integrationId = value.integrationId,
|
||||
address = value.addresses.address,
|
||||
stakingId = stakingId,
|
||||
balance = YieldBalanceItem(
|
||||
items = value.balances.map { item ->
|
||||
BalanceItem(
|
||||
groupId = item.groupId,
|
||||
token = TokenConverter.convert(item.tokenDTO),
|
||||
type = BalanceTypeConverter.convert(item.type),
|
||||
amount = item.amount,
|
||||
rawCurrencyId = item.tokenDTO.coinGeckoId,
|
||||
// tron-specific. operates validatorAddresses instead of validatorAddress
|
||||
validatorAddress = item.validatorAddress ?: item.validatorAddresses?.get(0),
|
||||
date = item.date?.toDateTime(),
|
||||
pendingActions = PendingActionConverter
|
||||
.convertList(item.pendingActions)
|
||||
.sortedBy { it.passthrough },
|
||||
pendingActionsConstraints = PendingActionConstraintsConverter
|
||||
.convertList(item.pendingActionConstraints.orEmpty()),
|
||||
isPending = false,
|
||||
)
|
||||
}
|
||||
.sortedWith(compareBy({ it.type }, { it.amount })),
|
||||
items = value.balances
|
||||
.map { item -> item.toBalanceItem() }
|
||||
.sortedWith(comparator = compareBy({ it.type }, { it.amount })),
|
||||
integrationId = value.integrationId,
|
||||
),
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun BalanceDTO.toBalanceItem(): BalanceItem {
|
||||
val item = this
|
||||
|
||||
return BalanceItem(
|
||||
groupId = item.groupId,
|
||||
token = TokenConverter.convert(item.tokenDTO),
|
||||
type = BalanceTypeConverter.convert(item.type),
|
||||
amount = item.amount,
|
||||
rawCurrencyId = item.tokenDTO.coinGeckoId,
|
||||
// tron-specific. operates validatorAddresses instead of validatorAddress
|
||||
validatorAddress = item.validatorAddress ?: item.validatorAddresses?.get(0),
|
||||
date = item.date?.toString()?.let { Instant.parse(it) },
|
||||
pendingActions = PendingActionConverter
|
||||
.convertList(item.pendingActions)
|
||||
.sortedBy { it.passthrough },
|
||||
pendingActionsConstraints = PendingActionConstraintsConverter
|
||||
.convertList(item.pendingActionConstraints.orEmpty()),
|
||||
isPending = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -35,10 +35,7 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor(
|
|||
) : SingleYieldBalanceProducer {
|
||||
|
||||
override val fallback: YieldBalance by lazy {
|
||||
YieldBalance.Error(
|
||||
integrationId = params.stakingId.integrationId,
|
||||
address = params.stakingId.address,
|
||||
)
|
||||
YieldBalance.Error(stakingId = params.stakingId)
|
||||
}
|
||||
|
||||
override fun produce(): Flow<YieldBalance> {
|
||||
|
|
@ -50,7 +47,7 @@ internal class DefaultSingleYieldBalanceProducer @AssistedInject constructor(
|
|||
.mapNotNull { balances ->
|
||||
val currentStakingId = params.stakingId
|
||||
|
||||
val currentBalances = balances.filter { it.getStakingId() == currentStakingId }
|
||||
val currentBalances = balances.filter { it.stakingId == currentStakingId }
|
||||
|
||||
if (currentBalances.size > 1) {
|
||||
analyticsExceptionHandler.sendException(
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@ internal class DefaultYieldsBalancesStore(
|
|||
value = cachedStatuses.map { (stringWalletId, wrappers) ->
|
||||
val key = UserWalletId(stringWalletId)
|
||||
val value = YieldBalanceConverter(isCached = true).convertSet(input = wrappers)
|
||||
.filterNotNull()
|
||||
.toSet()
|
||||
|
||||
key to value
|
||||
}
|
||||
|
|
@ -61,7 +63,7 @@ internal class DefaultYieldsBalancesStore(
|
|||
override suspend fun getSyncOrNull(userWalletId: UserWalletId, stakingId: StakingID): YieldBalance? {
|
||||
return runtimeStore.getSyncOrNull()
|
||||
?.get(userWalletId)
|
||||
?.firstOrNull { stakingId == it.getStakingId() }
|
||||
?.firstOrNull { it.stakingId == stakingId }
|
||||
}
|
||||
|
||||
override suspend fun getAllSyncOrNull(userWalletId: UserWalletId): Set<YieldBalance>? {
|
||||
|
|
@ -96,11 +98,13 @@ internal class DefaultYieldsBalancesStore(
|
|||
|
||||
private suspend fun storeInRuntime(userWalletId: UserWalletId, values: Set<YieldBalanceWrapperDTO>) {
|
||||
val newBalances = YieldBalanceConverter(isCached = false).convertSet(input = values)
|
||||
.filterNotNull()
|
||||
.toSet()
|
||||
|
||||
runtimeStore.update(default = emptyMap()) { saved ->
|
||||
saved.toMutableMap().apply {
|
||||
this[userWalletId] = saved[userWalletId]
|
||||
?.addOrReplace(newBalances) { old, new -> old.getStakingId() == new.getStakingId() }
|
||||
?.addOrReplace(newBalances) { old, new -> old.stakingId == new.stakingId }
|
||||
?: newBalances
|
||||
}
|
||||
}
|
||||
|
|
@ -135,7 +139,7 @@ internal class DefaultYieldsBalancesStore(
|
|||
|
||||
val balances = stakingIds.mapNotNullTo(hashSetOf()) { stakingId ->
|
||||
val balance = portfolioBalances
|
||||
.firstOrNull { stakingId == it.getStakingId() }
|
||||
.firstOrNull { it.stakingId == stakingId }
|
||||
?: ifNotFound(stakingId)
|
||||
?: return@mapNotNullTo null
|
||||
|
||||
|
|
@ -143,7 +147,7 @@ internal class DefaultYieldsBalancesStore(
|
|||
}
|
||||
|
||||
val updatedBalances = portfolioBalances.addOrReplace(items = balances) { old, new ->
|
||||
old.getStakingId() == new.getStakingId()
|
||||
old.stakingId == new.stakingId
|
||||
}
|
||||
|
||||
put(key = userWalletId, value = updatedBalances)
|
||||
|
|
@ -151,9 +155,7 @@ internal class DefaultYieldsBalancesStore(
|
|||
}
|
||||
}
|
||||
|
||||
private fun createErrorYieldBalance(id: StakingID): YieldBalance {
|
||||
return YieldBalance.Error(integrationId = id.integrationId, address = id.address)
|
||||
}
|
||||
private fun createErrorYieldBalance(id: StakingID): YieldBalance = YieldBalance.Error(stakingId = id)
|
||||
|
||||
private fun YieldBalanceWrapperDTO.getStakingId(): StakingID? {
|
||||
val integrationId = integrationId
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ import com.tangem.domain.models.StatusSource
|
|||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
|
||||
internal fun YieldBalanceWrapperDTO.toDomain(source: StatusSource = StatusSource.CACHE): YieldBalance {
|
||||
return YieldBalanceConverter(source = source).convert(this)
|
||||
return YieldBalanceConverter(source = source).convert(this)!!
|
||||
}
|
||||
|
|
@ -84,7 +84,7 @@ internal class DefaultSingleYieldBalanceProducerTest {
|
|||
val producerFlow = producer.produceWithFallback()
|
||||
|
||||
val balance = MockYieldBalanceWrapperDTOFactory.createWithBalance(tonId).toDomain()
|
||||
val updatedBalance = YieldBalance.Error(integrationId = tonId.integrationId, address = tonId.address)
|
||||
val updatedBalance = YieldBalance.Error(stakingId = tonId)
|
||||
|
||||
// Act (first emit)
|
||||
multiFlow.emit(value = setOf(balance))
|
||||
|
|
@ -162,7 +162,7 @@ internal class DefaultSingleYieldBalanceProducerTest {
|
|||
val actual1 = getEmittedValues(flow = producerFlow)
|
||||
|
||||
// Assert (first emit)
|
||||
val fallbackStatus = YieldBalance.Error(integrationId = tonId.integrationId, address = "0x1")
|
||||
val fallbackStatus = YieldBalance.Error(stakingId = tonId.copy(address = "0x1"))
|
||||
|
||||
Truth.assertThat(actual1).hasSize(1)
|
||||
Truth.assertThat(actual1).containsExactly(fallbackStatus)
|
||||
|
|
|
|||
|
|
@ -129,12 +129,7 @@ internal class YieldsBalancesStoreUpdateMethodsTest {
|
|||
store.storeError(userWalletId = userWalletId, stakingIds = setOf(stakingId))
|
||||
|
||||
val runtimeExpected = mapOf(
|
||||
userWalletId to setOf(
|
||||
YieldBalance.Error(
|
||||
integrationId = stakingId.integrationId,
|
||||
address = stakingId.address,
|
||||
),
|
||||
),
|
||||
userWalletId to setOf(YieldBalance.Error(stakingId)),
|
||||
)
|
||||
|
||||
Truth.assertThat(runtimeStore.getSyncOrNull()).isEqualTo(runtimeExpected)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ dependencies {
|
|||
api(projects.core.analytics)
|
||||
api(projects.core.utils)
|
||||
|
||||
implementation(deps.kotlin.datetime)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.jodatime)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ dependencies {
|
|||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.models)
|
||||
|
||||
implementation(deps.kotlin.datetime)
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(deps.jodatime)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
package com.tangem.domain.staking.model
|
||||
|
||||
// TODO: make part of YieldBalance in the future
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class StakingID(val integrationId: String, val address: String)
|
||||
|
|
@ -2,166 +2,67 @@ package com.tangem.domain.staking.model.stakekit
|
|||
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.staking.model.StakingID
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
sealed class YieldBalance {
|
||||
/**
|
||||
* Represents a yield balance in the staking system
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface YieldBalance {
|
||||
|
||||
abstract val integrationId: String?
|
||||
abstract val address: String?
|
||||
abstract val source: StatusSource
|
||||
/** The unique identifier of the staking operation */
|
||||
val stakingId: StakingID
|
||||
|
||||
/** The source of the status information */
|
||||
val source: StatusSource
|
||||
|
||||
/**
|
||||
* Represents a yield balance with actual data
|
||||
*
|
||||
* @property stakingId the unique identifier of the staking operation
|
||||
* @property source the source of the status information
|
||||
* @property balance the balance details of the yield
|
||||
*/
|
||||
@Serializable
|
||||
data class Data(
|
||||
override val stakingId: StakingID,
|
||||
override val source: StatusSource,
|
||||
val balance: YieldBalanceItem,
|
||||
) : YieldBalance
|
||||
|
||||
/**
|
||||
* Represents an empty yield balance
|
||||
*
|
||||
* @property stakingId the unique identifier of the staking operation
|
||||
* @property source the source of the status information
|
||||
*/
|
||||
@Serializable
|
||||
data class Empty(
|
||||
override val stakingId: StakingID,
|
||||
override val source: StatusSource,
|
||||
) : YieldBalance
|
||||
|
||||
/**
|
||||
* Represents an error state for the yield balance
|
||||
*
|
||||
* @property stakingId the unique identifier of the staking operation
|
||||
*/
|
||||
@Serializable
|
||||
data class Error(override val stakingId: StakingID) : YieldBalance {
|
||||
override val source: StatusSource = StatusSource.ACTUAL
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of the current yield balance with a new status source
|
||||
*
|
||||
* @param source the new source of the status information
|
||||
*/
|
||||
fun copySealed(source: StatusSource): YieldBalance {
|
||||
return when (this) {
|
||||
is Data -> copy(source = source)
|
||||
is Empty -> copy(source = source)
|
||||
is Error,
|
||||
is Unsupported,
|
||||
-> this
|
||||
}
|
||||
}
|
||||
|
||||
fun getStakingId(): StakingID? {
|
||||
val integrationId = integrationId
|
||||
val address = address
|
||||
|
||||
if (integrationId == null || address == null) return null
|
||||
|
||||
return StakingID(integrationId = integrationId, address = address)
|
||||
}
|
||||
|
||||
data class Data(
|
||||
override val integrationId: String?,
|
||||
override val address: String,
|
||||
override val source: StatusSource,
|
||||
val balance: YieldBalanceItem,
|
||||
) : YieldBalance()
|
||||
|
||||
data class Empty(
|
||||
override val integrationId: String?,
|
||||
override val address: String,
|
||||
override val source: StatusSource,
|
||||
) : YieldBalance()
|
||||
|
||||
data object Unsupported : YieldBalance() {
|
||||
override val integrationId: String? = null
|
||||
override val address: String? = null
|
||||
override val source: StatusSource = StatusSource.ACTUAL
|
||||
}
|
||||
|
||||
data class Error(override val integrationId: String?, override val address: String?) : YieldBalance() {
|
||||
override val source: StatusSource = StatusSource.ACTUAL
|
||||
}
|
||||
}
|
||||
|
||||
data class YieldBalanceItem(
|
||||
val items: List<BalanceItem>,
|
||||
val integrationId: String?,
|
||||
)
|
||||
|
||||
data class BalanceItem(
|
||||
val groupId: String,
|
||||
val token: Token,
|
||||
val type: BalanceType,
|
||||
val amount: BigDecimal,
|
||||
val rawCurrencyId: String?,
|
||||
val validatorAddress: String?,
|
||||
val date: DateTime?,
|
||||
val pendingActions: List<PendingAction>,
|
||||
val pendingActionsConstraints: List<PendingActionConstraints>,
|
||||
val isPending: Boolean,
|
||||
)
|
||||
|
||||
data class PendingActionConstraints(
|
||||
val type: StakingActionType,
|
||||
val amountArg: PendingAction.PendingActionArgs.Amount?,
|
||||
)
|
||||
|
||||
data class PendingAction(
|
||||
val type: StakingActionType,
|
||||
val passthrough: String,
|
||||
val args: PendingActionArgs?,
|
||||
) {
|
||||
data class PendingActionArgs(
|
||||
val amount: Amount?,
|
||||
val duration: Duration?,
|
||||
val validatorAddress: Boolean?,
|
||||
val validatorAddresses: Boolean?,
|
||||
val tronResource: TronResource?,
|
||||
val signatureVerification: Boolean?,
|
||||
) {
|
||||
data class Amount(
|
||||
val required: Boolean,
|
||||
val minimum: BigDecimal?,
|
||||
val maximum: BigDecimal?,
|
||||
)
|
||||
|
||||
data class Duration(
|
||||
val required: Boolean,
|
||||
val minimum: Int?,
|
||||
val maximum: Int?,
|
||||
)
|
||||
|
||||
data class TronResource(
|
||||
val required: Boolean,
|
||||
val options: List<String>,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT!!!
|
||||
* Order is used to sort balances.
|
||||
*/
|
||||
@Suppress("MagicNumber")
|
||||
enum class BalanceType(val order: Int) {
|
||||
AVAILABLE(1),
|
||||
STAKED(2),
|
||||
PREPARING(3),
|
||||
LOCKED(4),
|
||||
UNSTAKING(5),
|
||||
UNLOCKING(6),
|
||||
UNSTAKED(7),
|
||||
REWARDS(8),
|
||||
UNKNOWN(9),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun BalanceType.isClickable() = when (this) {
|
||||
STAKED,
|
||||
UNSTAKED,
|
||||
LOCKED,
|
||||
-> true
|
||||
AVAILABLE,
|
||||
UNSTAKING,
|
||||
PREPARING,
|
||||
REWARDS,
|
||||
UNLOCKING,
|
||||
UNKNOWN,
|
||||
-> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class RewardBlockType {
|
||||
NoRewards,
|
||||
Rewards,
|
||||
RewardsRequirementsError,
|
||||
RewardUnavailable,
|
||||
;
|
||||
|
||||
/**
|
||||
* Indicated whether action can be performed on available reward
|
||||
* For example, pending action CLAIM_REWARDS could be called
|
||||
*/
|
||||
val isActionable: Boolean
|
||||
get() = when (this) {
|
||||
NoRewards,
|
||||
RewardUnavailable,
|
||||
-> false
|
||||
RewardsRequirementsError,
|
||||
Rewards,
|
||||
-> true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package com.tangem.domain.staking.model.stakekit
|
||||
|
||||
import com.tangem.domain.core.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class YieldBalanceItem(
|
||||
val items: List<BalanceItem>,
|
||||
val integrationId: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BalanceItem(
|
||||
val groupId: String,
|
||||
val token: Token,
|
||||
val type: BalanceType,
|
||||
val amount: SerializedBigDecimal,
|
||||
val rawCurrencyId: String?,
|
||||
val validatorAddress: String?,
|
||||
val date: Instant?,
|
||||
val pendingActions: List<PendingAction>,
|
||||
val pendingActionsConstraints: List<PendingActionConstraints>,
|
||||
val isPending: Boolean,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PendingActionConstraints(
|
||||
val type: StakingActionType,
|
||||
val amountArg: PendingAction.PendingActionArgs.Amount?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class PendingAction(
|
||||
val type: StakingActionType,
|
||||
val passthrough: String,
|
||||
val args: PendingActionArgs?,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
data class PendingActionArgs(
|
||||
val amount: Amount?,
|
||||
val duration: Duration?,
|
||||
val validatorAddress: Boolean?,
|
||||
val validatorAddresses: Boolean?,
|
||||
val tronResource: TronResource?,
|
||||
val signatureVerification: Boolean?,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
data class Amount(
|
||||
val required: Boolean,
|
||||
val minimum: SerializedBigDecimal?,
|
||||
val maximum: SerializedBigDecimal?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Duration(
|
||||
val required: Boolean,
|
||||
val minimum: Int?,
|
||||
val maximum: Int?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class TronResource(
|
||||
val required: Boolean,
|
||||
val options: List<String>,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IMPORTANT!!!
|
||||
* Order is used to sort balances.
|
||||
*/
|
||||
@Serializable
|
||||
@Suppress("MagicNumber")
|
||||
enum class BalanceType(val order: Int) {
|
||||
AVAILABLE(1),
|
||||
STAKED(2),
|
||||
PREPARING(3),
|
||||
LOCKED(4),
|
||||
UNSTAKING(5),
|
||||
UNLOCKING(6),
|
||||
UNSTAKED(7),
|
||||
REWARDS(8),
|
||||
UNKNOWN(9),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
||||
fun BalanceType.isClickable() = when (this) {
|
||||
STAKED,
|
||||
UNSTAKED,
|
||||
LOCKED,
|
||||
-> true
|
||||
AVAILABLE,
|
||||
UNSTAKING,
|
||||
PREPARING,
|
||||
REWARDS,
|
||||
UNLOCKING,
|
||||
UNKNOWN,
|
||||
-> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class RewardBlockType {
|
||||
NoRewards,
|
||||
Rewards,
|
||||
RewardsRequirementsError,
|
||||
RewardUnavailable,
|
||||
;
|
||||
|
||||
/**
|
||||
* Indicated whether action can be performed on available reward
|
||||
* For example, pending action CLAIM_REWARDS could be called
|
||||
*/
|
||||
val isActionable: Boolean
|
||||
get() = when (this) {
|
||||
NoRewards,
|
||||
RewardUnavailable,
|
||||
-> false
|
||||
RewardsRequirementsError,
|
||||
Rewards,
|
||||
-> true
|
||||
}
|
||||
}
|
||||
|
|
@ -420,7 +420,7 @@ abstract class BaseCurrencyStatusOperations(
|
|||
params = MultiYieldBalanceProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
.orEmpty()
|
||||
.filter { it.getStakingId() in stakingIds }
|
||||
.filter { it.stakingId in stakingIds }
|
||||
|
||||
ensure(balances.isNotEmpty()) { Error.EmptyYieldBalances }
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher
|
|||
import com.tangem.domain.quotes.single.SingleQuoteStatusProducer
|
||||
import com.tangem.domain.quotes.single.SingleQuoteStatusSupplier
|
||||
import com.tangem.domain.staking.StakingIdFactory
|
||||
import com.tangem.domain.staking.model.StakingID
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.staking.multi.MultiYieldBalanceFetcher
|
||||
|
|
@ -277,12 +278,17 @@ class CachedCurrenciesStatusesOperations(
|
|||
): YieldBalance? {
|
||||
if (yieldBalances.isNullOrEmpty()) return null
|
||||
|
||||
val supportedIntegration = StakingIntegrationID.create(currencyId = currency.id)?.value ?: return null
|
||||
|
||||
val supportedIntegration = StakingIntegrationID.create(currencyId = currency.id)?.value
|
||||
val address = extractAddress(networkStatus)
|
||||
|
||||
return yieldBalances.firstOrNull { it.integrationId == supportedIntegration && it.address == address }
|
||||
?: YieldBalance.Error(integrationId = supportedIntegration, address = address)
|
||||
return if (supportedIntegration != null && address != null) {
|
||||
val stakingId = StakingID(integrationId = supportedIntegration, address = address)
|
||||
|
||||
yieldBalances.firstOrNull { it.stakingId == stakingId }
|
||||
?: YieldBalance.Error(stakingId = stakingId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getCurrencies(userWalletId: UserWalletId): EitherFlow<TokenListError, List<CryptoCurrency>> {
|
||||
|
|
@ -390,7 +396,7 @@ class CachedCurrenciesStatusesOperations(
|
|||
)
|
||||
.onEach { balance ->
|
||||
state.update { loadedBalances ->
|
||||
loadedBalances.addOrReplace(balance) { balance.getStakingId() == it }
|
||||
loadedBalances.addOrReplace(balance) { balance.stakingId == it }
|
||||
}
|
||||
}
|
||||
.launchIn(scope = this)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ internal class CurrencyStatusOperations(
|
|||
val hasCurrentNetworkTransactions = networkStatusValue.pendingTransactions.isNotEmpty()
|
||||
val currentTransactions = networkStatusValue.pendingTransactions.getOrElse(currency.id, ::emptySet)
|
||||
val yieldBalanceData = yieldBalance as? YieldBalance.Data
|
||||
val isCurrentAddressStaking = yieldBalanceData?.address == networkStatusValue.address.defaultAddress.value
|
||||
val isCurrentAddressStaking =
|
||||
yieldBalanceData?.stakingId?.address == networkStatusValue.address.defaultAddress.value
|
||||
val filteredTokenBalances = yieldBalanceData?.balance?.items?.filter {
|
||||
it.token.coinGeckoId == currency.id.rawCurrencyId?.value
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import arrow.core.toNonEmptySetOrNull
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.staking.model.StakingID
|
||||
import com.tangem.domain.staking.model.StakingIntegrationID
|
||||
import com.tangem.domain.staking.model.stakekit.YieldBalance
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
|
|
@ -74,9 +75,12 @@ class CurrencyStatusProxyCreator {
|
|||
val address = extractAddress(networkStatus)
|
||||
|
||||
val supportedIntegration = StakingIntegrationID.create(currencyId = currency.id)?.value
|
||||
val yieldBalance = if (supportedIntegration != null) {
|
||||
yieldBalances?.firstOrNull { it.integrationId == supportedIntegration && it.address == address }
|
||||
?: YieldBalance.Error(integrationId = supportedIntegration, address = address)
|
||||
|
||||
val yieldBalance = if (supportedIntegration != null && address != null) {
|
||||
val stakingId = StakingID(integrationId = supportedIntegration, address = address)
|
||||
|
||||
yieldBalances?.firstOrNull { it.stakingId == stakingId }
|
||||
?: YieldBalance.Error(stakingId = stakingId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ dependencies {
|
|||
implementation(deps.androidx.paging.runtime)
|
||||
|
||||
/** Other dependencies */
|
||||
implementation(deps.kotlin.datetime)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.material)
|
||||
implementation(deps.arrow.core)
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import com.tangem.utils.Provider
|
|||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import org.joda.time.DateTime
|
||||
import kotlinx.datetime.Instant
|
||||
import java.math.BigDecimal
|
||||
import java.util.Calendar
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ internal class BalanceItemConverter(
|
|||
-> null
|
||||
}
|
||||
|
||||
private fun getUnbondingDate(date: DateTime?): TextReference? {
|
||||
private fun getUnbondingDate(date: Instant?): TextReference? {
|
||||
val unbondingPeriod = yield.metadata.cooldownPeriod?.days ?: return null
|
||||
if (date == null) {
|
||||
return combinedReference(
|
||||
|
|
@ -136,7 +136,7 @@ internal class BalanceItemConverter(
|
|||
nowCalendar.resetHours()
|
||||
|
||||
val endDate = Calendar.getInstance()
|
||||
endDate.timeInMillis = date.millis
|
||||
endDate.timeInMillis = date.toEpochMilliseconds()
|
||||
endDate.resetHours()
|
||||
|
||||
val days = ((endDate.timeInMillis - nowCalendar.timeInMillis) / DAY_IN_MILLIS).toInt()
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ internal class YieldBalancesConverter(
|
|||
?.firstOrNull { it.type == StakingActionType.CLAIM_REWARDS }
|
||||
|
||||
InnerYieldBalanceState.Data(
|
||||
integrationId = yieldBalance?.integrationId,
|
||||
integrationId = yieldBalance?.stakingId?.integrationId,
|
||||
reward = YieldReward(
|
||||
rewardsCrypto = cryptoRewardsValue.format { crypto(cryptoCurrency) },
|
||||
rewardsFiat = fiatRewardsValue.format {
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ viewBindingDelegate = "1.5.9"
|
|||
xmlShimmer = "1.1.3"
|
||||
zxingQrCode = "3.5.1"
|
||||
kotlinSerialization = "1.8.0"
|
||||
kotlinDatetime = "0.6.2"
|
||||
arrow = "1.2.4" # 2.0.1 breaks the build
|
||||
reownCore = "1.1.2"
|
||||
reownWeb3 = "1.1.2"
|
||||
|
|
@ -258,6 +259,7 @@ viewBindingDelegate = { module = "com.github.kirich1409:viewbindingpropertydeleg
|
|||
xmlShimmer = { module = "com.github.skydoves:androidveil", version.ref = "xmlShimmer" }
|
||||
zxing-qrCore = { module = "com.google.zxing:core", version.ref = "zxingQrCode" }
|
||||
kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" }
|
||||
kotlin-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinDatetime" }
|
||||
arrow-core = { module = "io.arrow-kt:arrow-core", version.ref = "arrow" }
|
||||
arrow-fx = { module = "io.arrow-kt:arrow-fx-coroutines", version.ref = "arrow" }
|
||||
reownCore = { module = "com.reown:android-core", version.ref = "reownCore" }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue