Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-18 20:34:37 +04:00
parent 5bdb9513aa
commit 8924c1a93b
20 changed files with 261 additions and 200 deletions

View file

@ -4,9 +4,13 @@ import android.content.Context
import androidx.datastore.core.DataStoreFactory
import androidx.datastore.dataStoreFile
import com.squareup.moshi.Moshi
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.datastore.RuntimeDataStore
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.token.*
import com.tangem.datasource.local.token.utils.YieldBalancesSerializer
import com.tangem.datasource.utils.MoshiDataStoreSerializer
import com.tangem.datasource.utils.mapWithStringKeyTypes
import com.tangem.datasource.utils.setTypes
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
@ -35,11 +39,16 @@ internal object StakingStoreModule {
dispatchers: CoroutineDispatcherProvider,
): StakingBalanceStore {
return DefaultStakingBalanceStore(
dataStore = DataStoreFactory.create(
serializer = YieldBalancesSerializer(moshi),
persistenceStore = DataStoreFactory.create(
serializer = MoshiDataStoreSerializer(
moshi = moshi,
types = mapWithStringKeyTypes(valueTypes = setTypes<YieldBalanceWrapperDTO>()),
defaultValue = emptyMap(),
),
produceFile = { context.dataStoreFile(fileName = "yield_balances") },
scope = CoroutineScope(context = dispatchers.io + SupervisorJob()),
),
runtimeStore = RuntimeSharedStore(),
)
}

View file

@ -2,52 +2,82 @@ package com.tangem.datasource.local.token
import androidx.datastore.core.DataStore
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.token.entity.YieldBalanceWrappersDTO
import com.tangem.datasource.local.datastore.RuntimeSharedStore
import com.tangem.datasource.local.token.converter.YieldBalanceConverter
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.extensions.addOrReplace
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
internal typealias YieldBalanceWrappersDTO = Map<String, Set<YieldBalanceWrapperDTO>>
internal typealias YieldBalanceListByWalletId = Map<UserWalletId, Set<YieldBalance>>
/**
* Default implementation of [StakingBalanceStore]
*
* @property persistenceStore persistence store
* @property runtimeStore runtime store
*/
internal class DefaultStakingBalanceStore(
private val dataStore: DataStore<YieldBalanceWrappersDTO>,
private val persistenceStore: DataStore<YieldBalanceWrappersDTO>,
private val runtimeStore: RuntimeSharedStore<YieldBalanceListByWalletId>,
) : StakingBalanceStore {
override fun get(userWalletId: UserWalletId): Flow<Set<YieldBalanceWrapperDTO>> {
return dataStore.data.map { it[userWalletId.stringValue].orEmpty() }
}
override suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalanceWrapperDTO>? {
return dataStore.data.firstOrNull()
?.get(userWalletId.stringValue)
}
override suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>) {
dataStore.updateData { current ->
current.toMutableMap().apply {
this[userWalletId.stringValue] = items
override fun get(userWalletId: UserWalletId): Flow<Set<YieldBalance>> = channelFlow {
val cachedBalances = persistenceStore.data
.map {
val wrappers = it[userWalletId.stringValue].orEmpty()
YieldBalanceConverter(isCached = true).convertSet(input = wrappers)
}
.firstOrNull()
.orEmpty()
if (cachedBalances.isNotEmpty()) {
send(cachedBalances)
}
runtimeStore.get()
.map { it[userWalletId].orEmpty() }
.onEach {
val mergedBalances = mergeYieldBalances(cachedBalances = cachedBalances, runtimeBalances = it)
send(mergedBalances)
}
.launchIn(scope = this)
}
override fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow<YieldBalance?> {
return get(userWalletId).map { balances ->
balances.getBalance(address = address, integrationId = integrationId)
}
}
override fun get(
userWalletId: UserWalletId,
address: String,
integrationId: String,
): Flow<YieldBalanceWrapperDTO?> {
return get(userWalletId)
.map { balances ->
balances.firstOrNull { it.integrationId == integrationId && it.addresses.address == address }
}
override suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalance>? {
return runtimeStore.getSyncOrNull()?.getValue(userWalletId)
}
override suspend fun getSyncOrNull(
userWalletId: UserWalletId,
address: String,
integrationId: String,
): YieldBalanceWrapperDTO? {
return getSyncOrNull(userWalletId)
?.firstOrNull { it.integrationId == integrationId && it.addresses.address == address }
): YieldBalance? {
val balances = getSyncOrNull(userWalletId) ?: return null
return balances.getBalance(address, integrationId)
}
override suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>) {
coroutineScope {
launch {
storeInRuntimeStore(
userWalletId = userWalletId,
items = YieldBalanceConverter(isCached = false).convertSet(input = items),
)
}
launch { storeInPersistenceStore(userWalletId = userWalletId, items = items) }
}
}
override suspend fun store(
@ -56,10 +86,85 @@ internal class DefaultStakingBalanceStore(
address: String,
item: YieldBalanceWrapperDTO,
) {
val balances = getSyncOrNull(userWalletId)
?.addOrReplace(item) { it.integrationId == integrationId && it.addresses.address == address }
?: setOf(item)
coroutineScope {
launch {
storeInRuntimeStore(userWalletId, integrationId, address, item)
storeInPersistenceStore(userWalletId, integrationId, address, item)
}
}
}
store(userWalletId, balances)
private suspend fun storeInRuntimeStore(
userWalletId: UserWalletId,
integrationId: String,
address: String,
item: YieldBalanceWrapperDTO,
) {
val newBalance = YieldBalanceConverter(isCached = false).convert(value = item)
val balances = getSyncOrNull(userWalletId)
?.addOrReplace(newBalance) { it.integrationId == integrationId && it.address == address }
?: setOf(newBalance)
storeInRuntimeStore(userWalletId = userWalletId, items = balances)
}
private suspend fun storeInRuntimeStore(userWalletId: UserWalletId, items: Set<YieldBalance>) {
runtimeStore.update(default = emptyMap()) {
it.toMutableMap().apply {
this[userWalletId] = items
}
}
}
private suspend fun storeInPersistenceStore(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>) {
persistenceStore.updateData { current ->
current.toMutableMap().apply {
this[userWalletId.stringValue] = items
}
}
}
private suspend fun storeInPersistenceStore(
userWalletId: UserWalletId,
integrationId: String,
address: String,
item: YieldBalanceWrapperDTO,
) {
persistenceStore.updateData { current ->
current.toMutableMap().apply {
this[userWalletId.stringValue] = current[userWalletId.stringValue]
?.addOrReplace(item) { it.integrationId == integrationId && it.addresses.address == address }
?: setOf(item)
}
}
}
private fun mergeYieldBalances(
cachedBalances: Set<YieldBalance>,
runtimeBalances: Set<YieldBalance>,
): Set<YieldBalance> {
return runtimeBalances
.map { runtime ->
if (runtime is YieldBalance.Error) {
cachedBalances.getBalance(address = runtime.address, integrationId = runtime.integrationId)
?: runtime
} else {
runtime
}
}
.toSet()
}
private fun Set<YieldBalance>.getBalance(address: String?, integrationId: String?): YieldBalance? {
return firstOrNull { yieldBalance ->
val data = yieldBalance as? YieldBalance.Data
val balance = data?.balance
val isCorrectAddress = address != null && address == data?.address
val isCorrectIntegration = integrationId != null && balance?.integrationId == integrationId
isCorrectIntegration && isCorrectAddress
}
}
}

View file

@ -1,24 +1,29 @@
package com.tangem.datasource.local.token
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
/** Staking balance store */
interface StakingBalanceStore {
fun get(userWalletId: UserWalletId): Flow<Set<YieldBalanceWrapperDTO>>
/** Get flow of [YieldBalanceList] by [userWalletId] */
fun get(userWalletId: UserWalletId): Flow<Set<YieldBalance>>
suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalanceWrapperDTO>?
/** Get flow of [YieldBalance] by [userWalletId], [address] and [integrationId] */
fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow<YieldBalance?>
/** Get [YieldBalanceList] synchronously or null by [userWalletId] */
suspend fun getSyncOrNull(userWalletId: UserWalletId): Set<YieldBalance>?
/** Get [YieldBalance] synchronously or null by [userWalletId], [address] and [integrationId] */
suspend fun getSyncOrNull(userWalletId: UserWalletId, address: String, integrationId: String): YieldBalance?
/** Store [items] by [userWalletId] */
suspend fun store(userWalletId: UserWalletId, items: Set<YieldBalanceWrapperDTO>)
fun get(userWalletId: UserWalletId, address: String, integrationId: String): Flow<YieldBalanceWrapperDTO?>
suspend fun getSyncOrNull(
userWalletId: UserWalletId,
address: String,
integrationId: String,
): YieldBalanceWrapperDTO?
/** Store [item] by [userWalletId], [integrationId] and [address] */
suspend fun store(userWalletId: UserWalletId, integrationId: String, address: String, item: YieldBalanceWrapperDTO)
}

View file

@ -1,10 +1,10 @@
package com.tangem.data.staking.converters
package com.tangem.datasource.local.token.converter
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO.BalanceTypeDTO
import com.tangem.domain.staking.model.stakekit.BalanceType
import com.tangem.utils.converter.Converter
internal class BalanceTypeConverter : Converter<BalanceTypeDTO, BalanceType> {
internal object BalanceTypeConverter : Converter<BalanceTypeDTO, BalanceType> {
override fun convert(value: BalanceTypeDTO): BalanceType {
return when (value) {

View file

@ -1,16 +1,14 @@
package com.tangem.data.staking.converters.action
package com.tangem.datasource.local.token.converter
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
import com.tangem.domain.staking.model.stakekit.PendingAction
import com.tangem.utils.converter.Converter
internal class PendingActionConverter : Converter<BalanceDTO.PendingAction, PendingAction> {
private val stakingActionTypeConverter by lazy(LazyThreadSafetyMode.NONE) { StakingActionTypeConverter() }
internal object PendingActionConverter : Converter<BalanceDTO.PendingAction, PendingAction> {
override fun convert(value: BalanceDTO.PendingAction): PendingAction {
return PendingAction(
type = stakingActionTypeConverter.convert(value.type),
type = StakingActionTypeConverter.convert(value.type),
passthrough = value.passthrough,
args = with(value.args) {
PendingAction.PendingActionArgs(

View file

@ -1,11 +1,11 @@
package com.tangem.data.staking.converters.action
package com.tangem.datasource.local.token.converter
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionTypeDTO
import com.tangem.domain.staking.model.stakekit.action.StakingActionType
import com.tangem.utils.converter.Converter
@Suppress("CyclomaticComplexMethod")
class StakingActionTypeConverter : Converter<StakingActionTypeDTO, StakingActionType> {
object StakingActionTypeConverter : Converter<StakingActionTypeDTO, StakingActionType> {
override fun convert(value: StakingActionTypeDTO): StakingActionType {
return when (value) {

View file

@ -1,11 +1,11 @@
package com.tangem.data.staking.converters
package com.tangem.datasource.local.token.converter
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
import com.tangem.domain.staking.model.stakekit.NetworkType
import com.tangem.utils.converter.TwoWayConverter
@Suppress("CyclomaticComplexMethod", "LongMethod")
class StakingNetworkTypeConverter : TwoWayConverter<NetworkTypeDTO, NetworkType> {
object StakingNetworkTypeConverter : TwoWayConverter<NetworkTypeDTO, NetworkType> {
override fun convert(value: NetworkTypeDTO): NetworkType {
return when (value) {

View file

@ -1,17 +1,15 @@
package com.tangem.data.staking.converters
package com.tangem.datasource.local.token.converter
import com.tangem.datasource.api.stakekit.models.response.model.TokenDTO
import com.tangem.domain.staking.model.stakekit.Token
import com.tangem.utils.converter.TwoWayConverter
class TokenConverter(
private val stakingNetworkTypeConverter: StakingNetworkTypeConverter,
) : TwoWayConverter<TokenDTO, Token> {
object TokenConverter : TwoWayConverter<TokenDTO, Token> {
override fun convert(value: TokenDTO): Token {
return Token(
name = value.name,
network = stakingNetworkTypeConverter.convert(value.network),
network = StakingNetworkTypeConverter.convert(value.network),
symbol = value.symbol,
decimals = value.decimals,
address = value.address,
@ -24,7 +22,7 @@ class TokenConverter(
override fun convertBack(value: Token): TokenDTO {
return TokenDTO(
name = value.name,
network = stakingNetworkTypeConverter.convertBack(value.network),
network = StakingNetworkTypeConverter.convertBack(value.network),
symbol = value.symbol,
decimals = value.decimals,
address = value.address,

View file

@ -1,36 +1,36 @@
package com.tangem.data.staking.converters
package com.tangem.datasource.local.token.converter
import com.tangem.data.staking.converters.action.PendingActionConverter
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
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
internal class YieldBalanceConverter : Converter<YieldBalanceWrapperDTO, YieldBalance> {
internal class YieldBalanceConverter(private val isCached: Boolean) : Converter<YieldBalanceWrapperDTO, YieldBalance> {
private val pendingActionConverter by lazy(LazyThreadSafetyMode.NONE) { PendingActionConverter() }
private val networkTypeConverter by lazy(LazyThreadSafetyMode.NONE) { StakingNetworkTypeConverter() }
private val tokenConverter by lazy(LazyThreadSafetyMode.NONE) { TokenConverter(networkTypeConverter) }
private val balanceTypeConverter by lazy(LazyThreadSafetyMode.NONE) { BalanceTypeConverter() }
override fun convert(value: YieldBalanceWrapperDTO): YieldBalance {
return if (value.balances.isEmpty()) {
YieldBalance.Empty
YieldBalance.Empty(
integrationId = value.integrationId,
address = value.addresses.address,
isCached = isCached,
)
} else {
YieldBalance.Data(
integrationId = value.integrationId,
address = value.addresses.address,
balance = YieldBalanceItem(
items = value.balances.map { item ->
BalanceItem(
groupId = item.groupId,
token = tokenConverter.convert(item.tokenDTO),
type = balanceTypeConverter.convert(item.type),
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
pendingActions = PendingActionConverter
.convertList(item.pendingActions)
.sortedBy { it.passthrough },
isPending = false,
@ -39,6 +39,7 @@ internal class YieldBalanceConverter : Converter<YieldBalanceWrapperDTO, YieldBa
.sortedWith(compareBy({ it.type }, { it.amount })),
integrationId = value.integrationId,
),
isCached = isCached,
)
}
}

View file

@ -1,5 +0,0 @@
package com.tangem.datasource.local.token.entity
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
internal typealias YieldBalanceWrappersDTO = Map<String, Set<YieldBalanceWrapperDTO>>

View file

@ -1,36 +0,0 @@
package com.tangem.datasource.local.token.utils
import androidx.datastore.core.Serializer
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.token.entity.YieldBalanceWrappersDTO
import java.io.InputStream
import java.io.OutputStream
internal class YieldBalancesSerializer(moshi: Moshi) : Serializer<YieldBalanceWrappersDTO> {
private val adapter by lazy {
val type = Types.newParameterizedType(
Map::class.java,
String::class.java,
Types.newParameterizedType(Set::class.java, YieldBalanceWrapperDTO::class.java),
)
moshi.adapter<YieldBalanceWrappersDTO>(type)
}
override val defaultValue: YieldBalanceWrappersDTO = emptyMap()
override suspend fun readFrom(input: InputStream): YieldBalanceWrappersDTO {
return input.bufferedReader().use { reader ->
adapter.fromJson(reader.readText()) ?: defaultValue
}
}
override suspend fun writeTo(t: YieldBalanceWrappersDTO, output: OutputStream) {
output.bufferedWriter().use { writer ->
writer.write(adapter.toJson(t))
}
}
}

View file

@ -15,10 +15,10 @@ import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toCompressedPublicKey
import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.staking.converters.*
import com.tangem.data.staking.converters.YieldBalanceListConverter
import com.tangem.data.staking.converters.YieldConverter
import com.tangem.data.staking.converters.action.ActionStatusConverter
import com.tangem.data.staking.converters.action.EnterActionResponseConverter
import com.tangem.data.staking.converters.action.StakingActionTypeConverter
import com.tangem.data.staking.converters.transaction.GasEstimateConverter
import com.tangem.data.staking.converters.transaction.StakingTransactionConverter
import com.tangem.data.staking.converters.transaction.StakingTransactionStatusConverter
@ -32,6 +32,8 @@ import com.tangem.datasource.api.stakekit.models.response.model.action.StakingAc
import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction
import com.tangem.datasource.local.token.StakingBalanceStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter
import com.tangem.datasource.local.token.converter.TokenConverter
import com.tangem.domain.staking.model.StakingApproval
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
@ -77,36 +79,19 @@ internal class DefaultStakingRepository(
moshi: Moshi,
) : StakingRepository {
private val stakingNetworkTypeConverter = StakingNetworkTypeConverter()
private val networkTypeConverter = StakingNetworkTypeConverter()
private val transactionStatusConverter = StakingTransactionStatusConverter()
private val transactionTypeConverter = StakingTransactionTypeConverter()
private val actionStatusConverter = ActionStatusConverter()
private val stakingActionTypeConverter = StakingActionTypeConverter()
private val tokenConverter = TokenConverter(
stakingNetworkTypeConverter = stakingNetworkTypeConverter,
)
private val yieldConverter = YieldConverter(
tokenConverter = tokenConverter,
)
private val gasEstimateConverter = GasEstimateConverter(
tokenConverter = tokenConverter,
)
private val transactionConverter = StakingTransactionConverter(
networkTypeConverter = networkTypeConverter,
transactionStatusConverter = transactionStatusConverter,
transactionTypeConverter = transactionTypeConverter,
gasEstimateConverter = gasEstimateConverter,
)
private val enterActionResponseConverter = EnterActionResponseConverter(
actionStatusConverter = actionStatusConverter,
stakingActionTypeConverter = stakingActionTypeConverter,
transactionConverter = transactionConverter,
)
private val yieldBalanceConverter = YieldBalanceConverter()
private val yieldBalanceListConverter = YieldBalanceListConverter(yieldBalanceConverter)
private val tronStakeKitTransactionAdapter by lazy { moshi.adapter(TronStakeKitTransaction::class.java) }
private val networkTypeAdapter by lazy { moshi.adapter(NetworkTypeDTO::class.java) }
private val stakingActionStatusAdapter by lazy { moshi.adapter(StakingActionStatusDTO::class.java) }
@ -157,7 +142,7 @@ internal class DefaultStakingRepository(
return withContext(dispatchers.io) {
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
val networkTypeDto = networkTypeConverter.convertBack(networkType)
val networkTypeDto = StakingNetworkTypeConverter.convertBack(networkType)
val networkTypeString = networkTypeDto.extractJsonName()
val actionStatusDTO = actionStatusConverter.convertBack(stakingActionStatus)
@ -301,7 +286,7 @@ internal class DefaultStakingRepository(
)
}
gasEstimateConverter.convert(gasEstimateDTO.getOrThrow())
GasEstimateConverter.convert(gasEstimateDTO.getOrThrow())
}
}
@ -382,7 +367,7 @@ internal class DefaultStakingRepository(
.distinctUntilChanged()
.collectLatest {
if (it != null) {
send(yieldBalanceConverter.convert(it))
send(it)
} else {
error("No yield balance available for currency ${cryptoCurrency.id.value}")
}
@ -408,10 +393,8 @@ internal class DefaultStakingRepository(
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
?: error("Could not get integrationId")
val result = stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId)
?: return@withContext YieldBalance.Error
yieldBalanceConverter.convert(result)
stakingBalanceStore.getSyncOrNull(userWalletId, address, integrationId)
?: YieldBalance.Error(integrationId, address)
}
override suspend fun fetchMultiYieldBalance(
@ -434,7 +417,7 @@ internal class DefaultStakingRepository(
return@invokeOnExpire
}
val yields = yieldConverter.convertListIgnoreErrors(
val yields = YieldConverter.convertListIgnoreErrors(
input = yieldDTOs,
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
)
@ -485,7 +468,7 @@ internal class DefaultStakingRepository(
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList> {
return stakingBalanceStore.get(userWalletId)
.map(yieldBalanceListConverter::convert)
.map(YieldBalanceListConverter::convert)
.flowOn(dispatchers.io)
}
@ -495,7 +478,7 @@ internal class DefaultStakingRepository(
): Flow<YieldBalanceList> = channelFlow {
stakingBalanceStore.get(userWalletId)
.onEach {
val balances = yieldBalanceListConverter.convert(it)
val balances = YieldBalanceListConverter.convert(it)
send(balances)
}
.launchIn(scope = this + dispatchers.io)
@ -510,15 +493,19 @@ internal class DefaultStakingRepository(
cryptoCurrencies: List<CryptoCurrency>,
): YieldBalanceList = withContext(dispatchers.io) {
fetchMultiYieldBalance(userWalletId, cryptoCurrencies)
val result = stakingBalanceStore.getSyncOrNull(userWalletId) ?: return@withContext YieldBalanceList.Error
yieldBalanceListConverter.convert(result)
stakingBalanceStore.getSyncOrNull(userWalletId)?.let(YieldBalanceListConverter::convert)
?: YieldBalanceList.Error
}
override suspend fun isAnyTokenStaked(userWalletId: UserWalletId): Boolean {
return withContext(dispatchers.io) {
stakingBalanceStore.getSyncOrNull(userWalletId)
?.let {
it.isNotEmpty() && it.any { yieldBalance -> yieldBalance.balances.isNotEmpty() }
it.isNotEmpty() &&
it.any { yieldBalance ->
(yieldBalance as? YieldBalance.Data)?.balance?.items?.isNotEmpty() == true
}
}
?: false
}
@ -537,7 +524,7 @@ internal class DefaultStakingRepository(
),
args = ActionRequestBodyArgs(
amount = params.amount.toPlainString(),
inputToken = tokenConverter.convertBack(params.token),
inputToken = TokenConverter.convertBack(params.token),
validatorAddress = params.validatorAddress,
validatorAddresses = listOf(params.validatorAddress), // check on other networks
tronResource = getTronResource(network),
@ -609,7 +596,7 @@ internal class DefaultStakingRepository(
}
private suspend fun getEnabledYieldsSync(): List<Yield> {
return yieldConverter.convertListIgnoreErrors(
return YieldConverter.convertListIgnoreErrors(
input = stakingYieldsStore.getSync(),
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
)

View file

@ -1,20 +1,16 @@
package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.domain.staking.model.stakekit.YieldBalance
import com.tangem.domain.staking.model.stakekit.YieldBalanceList
import com.tangem.utils.converter.Converter
internal class YieldBalanceListConverter(
private val yieldBalanceConverter: YieldBalanceConverter,
) : Converter<Set<YieldBalanceWrapperDTO>, YieldBalanceList> {
internal object YieldBalanceListConverter : Converter<Set<YieldBalance>, YieldBalanceList> {
override fun convert(value: Set<YieldBalanceWrapperDTO>): YieldBalanceList {
override fun convert(value: Set<YieldBalance>): YieldBalanceList {
return if (value.isEmpty()) {
YieldBalanceList.Empty
} else {
YieldBalanceList.Data(
balances = value.map(yieldBalanceConverter::convert),
)
YieldBalanceList.Data(balances = value.toList())
}
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.datasource.api.stakekit.models.response.model.AddressArgumentD
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.MetadataDTO.RewardScheduleDTO
import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO.ValidatorDTO.ValidatorStatusDTO
import com.tangem.datasource.local.token.converter.TokenConverter
import com.tangem.domain.staking.model.stakekit.AddressArgument
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.Yield.Metadata.RewardSchedule
@ -11,15 +12,20 @@ import com.tangem.domain.staking.model.stakekit.Yield.Validator.ValidatorStatus
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.toImmutableList
class YieldConverter(
private val tokenConverter: TokenConverter,
) : Converter<YieldDTO, Yield> {
internal object YieldConverter : Converter<YieldDTO, Yield> {
private val PARTNERS = listOf(
"cosmosvaloper1wrx0x9m9ykdhw9sg04v7uljme53wuj03aa5d4f",
"H2tJNyMHnRF6ahCQLQ1sSycM4FGchymuzyYzUqKEuydk",
)
private val PARTNERS_NAMES = listOf("Meria")
override fun convert(value: YieldDTO): Yield {
return Yield(
id = value.id.asMandatory("id"),
token = tokenConverter.convert(value.token.asMandatory("token")),
tokens = value.tokens.asMandatory("tokens").map { tokenConverter.convert(it) },
token = TokenConverter.convert(value.token.asMandatory("token")),
tokens = value.tokens.asMandatory("tokens").map(TokenConverter::convert),
args = convertArgs(value.args.asMandatory("args")),
status = convertStatus(value.status.asMandatory("status")),
apy = value.apy.asMandatory("apy"),
@ -85,9 +91,9 @@ class YieldConverter(
logoUri = metadataDTO.logoUri.asMandatory("logoUri"),
description = metadataDTO.description.asMandatory("description"),
documentation = metadataDTO.documentation,
gasFeeToken = tokenConverter.convert(metadataDTO.gasFeeTokenDTO.asMandatory("gasFeeTokenDTO")),
token = tokenConverter.convert(metadataDTO.tokenDTO.asMandatory("tokenDTO")),
tokens = metadataDTO.tokensDTO.asMandatory("tokensDTO").map { tokenConverter.convert(it) },
gasFeeToken = TokenConverter.convert(metadataDTO.gasFeeTokenDTO.asMandatory("gasFeeTokenDTO")),
token = TokenConverter.convert(metadataDTO.tokenDTO.asMandatory("tokenDTO")),
tokens = metadataDTO.tokensDTO.asMandatory("tokensDTO").map(TokenConverter::convert),
type = metadataDTO.type.asMandatory("type"),
rewardSchedule = convertRewardSchedule(metadataDTO.rewardSchedule.asMandatory("rewardSchedule")),
cooldownPeriod = metadataDTO.cooldownPeriod?.let { convertPeriod(it) },
@ -183,14 +189,4 @@ class YieldConverter(
private fun isStrategicPartner(validatorAddress: String?, validatorName: String): Boolean {
return PARTNERS.any { it == validatorAddress } || PARTNERS_NAMES.any { it.equals(validatorName, true) }
}
private companion object {
val PARTNERS = listOf(
"cosmosvaloper1wrx0x9m9ykdhw9sg04v7uljme53wuj03aa5d4f",
"H2tJNyMHnRF6ahCQLQ1sSycM4FGchymuzyYzUqKEuydk",
)
val PARTNERS_NAMES = listOf(
"Meria",
)
}
}

View file

@ -2,12 +2,12 @@ package com.tangem.data.staking.converters.action
import com.tangem.data.staking.converters.transaction.StakingTransactionConverter
import com.tangem.datasource.api.stakekit.models.response.ActionDTO
import com.tangem.datasource.local.token.converter.StakingActionTypeConverter
import com.tangem.domain.staking.model.stakekit.action.StakingAction
import com.tangem.utils.converter.Converter
class EnterActionResponseConverter(
private val actionStatusConverter: ActionStatusConverter,
private val stakingActionTypeConverter: StakingActionTypeConverter,
private val transactionConverter: StakingTransactionConverter,
) : Converter<ActionDTO, StakingAction> {
@ -16,7 +16,7 @@ class EnterActionResponseConverter(
id = value.id,
integrationId = value.integrationId,
status = actionStatusConverter.convert(value.status),
type = stakingActionTypeConverter.convert(value.type),
type = StakingActionTypeConverter.convert(value.type),
currentStepIndex = value.currentStepIndex,
amount = value.amount,
validatorAddress = value.validatorAddress,

View file

@ -1,18 +1,16 @@
package com.tangem.data.staking.converters.transaction
import com.tangem.data.staking.converters.TokenConverter
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingGasEstimateDTO
import com.tangem.datasource.local.token.converter.TokenConverter
import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate
import com.tangem.utils.converter.Converter
class GasEstimateConverter(
private val tokenConverter: TokenConverter,
) : Converter<StakingGasEstimateDTO, StakingGasEstimate> {
internal object GasEstimateConverter : Converter<StakingGasEstimateDTO, StakingGasEstimate> {
override fun convert(value: StakingGasEstimateDTO): StakingGasEstimate {
return StakingGasEstimate(
amount = value.amount,
token = tokenConverter.convert(value.token),
token = TokenConverter.convert(value.token),
gasLimit = value.gasLimit,
)
}

View file

@ -1,21 +1,19 @@
package com.tangem.data.staking.converters.transaction
import com.tangem.data.staking.converters.StakingNetworkTypeConverter
import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingTransactionDTO
import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
import com.tangem.utils.converter.Converter
class StakingTransactionConverter(
private val networkTypeConverter: StakingNetworkTypeConverter,
private val transactionStatusConverter: StakingTransactionStatusConverter,
private val transactionTypeConverter: StakingTransactionTypeConverter,
private val gasEstimateConverter: GasEstimateConverter,
) : Converter<StakingTransactionDTO, StakingTransaction> {
override fun convert(value: StakingTransactionDTO): StakingTransaction {
return StakingTransaction(
id = value.id,
network = networkTypeConverter.convert(value.network),
network = StakingNetworkTypeConverter.convert(value.network),
status = transactionStatusConverter.convert(value.status),
type = transactionTypeConverter.convert(value.type),
hash = value.hash,
@ -23,7 +21,7 @@ class StakingTransactionConverter(
unsignedTransaction = value.unsignedTransaction,
stepIndex = value.stepIndex,
error = value.error,
gasEstimate = value.gasEstimate?.let { gasEstimateConverter.convert(it) },
gasEstimate = value.gasEstimate?.let(GasEstimateConverter::convert),
stakeId = value.stakeId,
explorerUrl = value.explorerUrl,
ledgerHwAppId = value.ledgerHwAppId,

View file

@ -6,9 +6,14 @@ import java.math.BigDecimal
sealed class YieldBalance {
abstract val integrationId: String?
abstract val address: String?
data class Data(
override val integrationId: String?,
override val address: String,
val balance: YieldBalanceItem,
val address: String,
val isCached: Boolean,
) : YieldBalance() {
fun getTotalWithRewardsStakingBalance(): BigDecimal {
return balance.items.sumOf { it.amount }
@ -34,9 +39,13 @@ sealed class YieldBalance {
}
}
data object Empty : YieldBalance()
data class Empty(
override val integrationId: String?,
override val address: String,
val isCached: Boolean,
) : YieldBalance()
data object Error : YieldBalance()
data class Error(override val integrationId: String?, override val address: String?) : YieldBalance()
}
data class YieldBalanceItem(

View file

@ -2,9 +2,7 @@ package com.tangem.domain.staking.model.stakekit
sealed class YieldBalanceList {
data class Data(
val balances: List<YieldBalance>,
) : YieldBalanceList() {
data class Data(val balances: List<YieldBalance>) : YieldBalanceList() {
fun getBalance(address: String?, integrationId: String?): YieldBalance {
return balances.firstOrNull { yieldBalance ->
@ -15,7 +13,7 @@ sealed class YieldBalanceList {
val isCorrectIntegration = integrationId != null && balance?.integrationId == integrationId
isCorrectIntegration && isCorrectAddress
} ?: YieldBalance.Error
} ?: YieldBalance.Error(integrationId = integrationId, address = address)
}
}

View file

@ -138,13 +138,13 @@ class MockStakingRepository : StakingRepository {
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): Flow<YieldBalance> = channelFlow {
send(YieldBalance.Error)
send(YieldBalance.Error(integrationId = null, address = null))
}
override suspend fun getSingleYieldBalanceSync(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,
): YieldBalance = YieldBalance.Error
): YieldBalance = YieldBalance.Error(integrationId = null, address = null)
override suspend fun fetchMultiYieldBalance(
userWalletId: UserWalletId,
@ -158,7 +158,11 @@ class MockStakingRepository : StakingRepository {
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): Flow<YieldBalanceList> {
return flowOf(YieldBalanceList.Data(listOf(YieldBalance.Error)))
return flowOf(
YieldBalanceList.Data(
balances = listOf(YieldBalance.Error(integrationId = null, address = null)),
),
)
}
override fun getMultiYieldBalanceUpdatesLegacy(
@ -170,7 +174,7 @@ class MockStakingRepository : StakingRepository {
userWalletId: UserWalletId,
cryptoCurrencies: List<CryptoCurrency>,
): YieldBalanceList = YieldBalanceList.Data(
balances = listOf(YieldBalance.Error),
balances = listOf(YieldBalance.Error(integrationId = null, address = null)),
)
override suspend fun createAction(