Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-21 21:35:19 +05:00
parent 0b671b4ac1
commit 1d75e884c8
15 changed files with 340 additions and 23 deletions

View file

@ -12,10 +12,17 @@ android {
dependencies {
/** Core modules */
implementation(projects.core.datasource)
implementation(projects.core.utils)
/** Common modules */
implementation(projects.data.common)
/** Domain modules */
implementation(projects.domain.tokens.models)
implementation(projects.domain.staking)
implementation(projects.domain.wallets.models)
// region DI

View file

@ -2,23 +2,33 @@ package com.tangem.data.staking
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toCoinId
import com.tangem.data.staking.converters.StakingNetworkTypeConverter
import com.tangem.data.staking.converters.TokenConverter
import com.tangem.data.staking.converters.YieldConverter
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.staking.converters.*
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.api.stakekit.models.request.Address
import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.datasource.local.token.StakingBalanceStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingEntryInfo
import com.tangem.domain.staking.model.Yield
import com.tangem.domain.staking.model.*
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.NetworkStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.cancellable
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
internal class DefaultStakingRepository(
private val stakeKitApi: StakeKitApi,
private val stakingYieldsStore: StakingYieldsStore,
private val stakingBalanceStore: StakingBalanceStore,
private val cacheRegistry: CacheRegistry,
private val dispatchers: CoroutineDispatcherProvider,
) : StakingRepository {
@ -31,6 +41,10 @@ internal class DefaultStakingRepository(
tokenConverter = tokenConverter,
)
private val yieldBalanceConverter = YieldBalanceConverter()
private val yieldBalanceListConverter = YieldBalanceListConverter()
override fun isStakingSupported(currencyId: String): Boolean {
return integrationIds.contains(currencyId)
}
@ -94,9 +108,93 @@ internal class DefaultStakingRepository(
}
}
override suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,
networkStatus: NetworkStatus,
integrationId: String,
refresh: Boolean,
) = withContext(dispatchers.io) {
cacheRegistry.invokeOnExpire(
key = getYieldBalancesKey(userWalletId),
skipCache = refresh,
block = {
val result = stakeKitApi.getSingleYieldBalance(
integrationId = integrationId,
body = getBalanceRequestData(networkStatus),
).getOrThrow()
stakingBalanceStore.store(
integrationId,
YieldBalanceWrapperDTO(
balances = result,
integrationId = integrationId,
),
)
},
)
}
override fun getSingleYieldBalanceFlow(
userWalletId: UserWalletId,
networkStatus: NetworkStatus,
integrationId: String,
): Flow<List<YieldBalance>> = channelFlow {
launch(dispatchers.io) {
stakingBalanceStore.get(integrationId)
.collectLatest {
send(yieldBalanceConverter.convertList(it))
}
}
withContext(dispatchers.io) {
fetchSingleYieldBalance(
userWalletId,
networkStatus,
integrationId,
)
}
}.cancellable()
override suspend fun fetchMultiYieldBalance(
userWalletId: UserWalletId,
networks: Set<NetworkStatus>,
integrationId: String,
refresh: Boolean,
) = withContext(dispatchers.io) {
cacheRegistry.invokeOnExpire(
key = getYieldBalancesKey(userWalletId),
skipCache = refresh,
block = {
val result = stakeKitApi.getMultipleYieldBalances(
networks.map(::getBalanceRequestData),
).getOrThrow()
stakingBalanceStore.store(result)
},
)
}
override fun getMultiYieldBalanceFlow(
userWalletId: UserWalletId,
networks: Set<NetworkStatus>,
integrationId: String,
): Flow<List<YieldBalanceList>> = channelFlow {
launch(dispatchers.io) {
stakingBalanceStore.get()
.collectLatest { send(yieldBalanceListConverter.convertList(it)) }
}
withContext(dispatchers.io) {
fetchMultiYieldBalance(
userWalletId,
networks,
integrationId,
)
}
}.cancellable()
private fun findPrefetchedYield(yields: List<Yield>, currencyId: String, symbol: String): Yield? {
return yields
.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol }
return yields.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol }
}
private suspend fun getEnabledYields(): List<Yield>? {
@ -104,6 +202,28 @@ internal class DefaultStakingRepository(
return yields.map { yieldConverter.convert(it) }
}
private fun getBalanceRequestData(networkStatus: NetworkStatus): YieldBalanceRequestBody {
val networkAddress = when (val network = networkStatus.value) {
NetworkStatus.MissedDerivation -> null
is NetworkStatus.NoAccount -> network.address
is NetworkStatus.Unreachable -> network.address
is NetworkStatus.Verified -> network.address
}
val address = networkAddress?.defaultAddress?.value.orEmpty()
return YieldBalanceRequestBody(
addresses = Address(
address = address,
additionalAddresses = null, // todo fill additional addresses metadata if needed
explorerUrl = "", // todo fill exporer url
),
args = YieldBalanceRequestBody.YieldBalanceRequestArgs(
validatorAddresses = listOf(), // todo add validator addresses
),
)
}
private fun getYieldBalancesKey(userWalletId: UserWalletId) = "yield_balance_${userWalletId.stringValue}"
companion object {
private val integrationIds = setOf(
Blockchain.Solana.toCoinId(),

View file

@ -0,0 +1,16 @@
package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO
import com.tangem.domain.staking.model.BalanceType
import com.tangem.domain.staking.model.YieldBalance
import com.tangem.utils.converter.Converter
internal class YieldBalanceConverter : Converter<BalanceDTO, YieldBalance> {
override fun convert(value: BalanceDTO): YieldBalance {
return YieldBalance(
type = BalanceType.valueOf(value.type.name),
amount = value.amount,
pricePerShare = value.pricePerShare,
)
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.data.staking.converters
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
import com.tangem.domain.staking.model.YieldBalanceList
import com.tangem.utils.converter.Converter
internal class YieldBalanceListConverter : Converter<YieldBalanceWrapperDTO, YieldBalanceList> {
internal val converter by lazy(LazyThreadSafetyMode.NONE) {
YieldBalanceConverter()
}
override fun convert(value: YieldBalanceWrapperDTO): YieldBalanceList {
return YieldBalanceList(
balances = converter.convertList(value.balances),
)
}
}

View file

@ -1,7 +1,9 @@
package com.tangem.data.staking.di
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.staking.DefaultStakingRepository
import com.tangem.datasource.api.stakekit.StakeKitApi
import com.tangem.datasource.local.token.StakingBalanceStore
import com.tangem.datasource.local.token.StakingYieldsStore
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -20,12 +22,16 @@ internal object StakingDataModule {
fun provideStakingRepository(
stakeKitApi: StakeKitApi,
stakingTokenStore: StakingYieldsStore,
stakingBalanceStore: StakingBalanceStore,
dispatchers: CoroutineDispatcherProvider,
cacheRegistry: CacheRegistry,
): StakingRepository {
return DefaultStakingRepository(
stakeKitApi = stakeKitApi,
stakingYieldsStore = stakingTokenStore,
stakingBalanceStore = stakingBalanceStore,
dispatchers = dispatchers,
cacheRegistry = cacheRegistry,
)
}
}