Updated on 2026-08-14
This commit is contained in:
parent
a2b440444e
commit
588456cc71
7 changed files with 240 additions and 39 deletions
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.data.staking.fetcher
|
||||
|
||||
import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody
|
||||
import com.tangem.domain.staking.fetcher.YieldBalanceFetcherParams
|
||||
import com.tangem.domain.staking.model.StakingID
|
||||
|
||||
/**
|
||||
* Implementor of internal logic of YieldBalanceFetcher
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal interface YieldBalanceFetcherImplementor<out Params : YieldBalanceFetcherParams> {
|
||||
|
||||
/** Create set of [StakingID] */
|
||||
suspend fun createStakingIds(params: @UnsafeVariance Params): Set<StakingID>
|
||||
|
||||
/**
|
||||
* Fetch yield balances
|
||||
*
|
||||
* @param params params
|
||||
* @param stakingIds set of [StakingID]
|
||||
* @param requests requests
|
||||
*/
|
||||
suspend fun fetch(
|
||||
params: @UnsafeVariance Params,
|
||||
stakingIds: Set<StakingID>,
|
||||
requests: List<YieldBalanceRequestBody>,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.tangem.data.staking.multi
|
||||
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.staking.fetcher.YieldBalanceFetcherImplementor
|
||||
import com.tangem.data.staking.store.YieldsBalancesStore
|
||||
import com.tangem.data.staking.utils.StakingIdFactory
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody
|
||||
import com.tangem.domain.staking.fetcher.YieldBalanceFetcherParams
|
||||
import com.tangem.domain.staking.model.StakingID
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Implementor of fetcher for refreshing multiple yield balances
|
||||
*
|
||||
* @property yieldsBalancesStore yields balances store
|
||||
* @property stakingIdFactory factory for creating [StakingID]
|
||||
* @property stakeKitApi StakeKit API
|
||||
* @property dispatchers dispatchers
|
||||
*/
|
||||
internal class MultiYieldBalanceFetcherImplementor(
|
||||
private val yieldsBalancesStore: YieldsBalancesStore,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : YieldBalanceFetcherImplementor<YieldBalanceFetcherParams.Multi> {
|
||||
|
||||
override suspend fun createStakingIds(params: YieldBalanceFetcherParams.Multi): Set<StakingID> {
|
||||
return params.currencyIdWithNetworkMap.flatMapTo(hashSetOf()) { (currencyId, network) ->
|
||||
stakingIdFactory.create(userWalletId = params.userWalletId, currencyId = currencyId, network = network)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun fetch(
|
||||
params: YieldBalanceFetcherParams.Multi,
|
||||
stakingIds: Set<StakingID>,
|
||||
requests: List<YieldBalanceRequestBody>,
|
||||
) {
|
||||
safeApiCall(
|
||||
call = {
|
||||
val yieldBalances = withContext(dispatchers.io) {
|
||||
stakeKitApi.getMultipleYieldBalances(requests).bind()
|
||||
}
|
||||
|
||||
yieldsBalancesStore.storeActual(userWalletId = params.userWalletId, values = yieldBalances)
|
||||
},
|
||||
onError = {
|
||||
Timber.e(it, "Unable to fetch yield balances $params")
|
||||
|
||||
yieldsBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = stakingIds)
|
||||
|
||||
throw it
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.tangem.data.staking.single
|
||||
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.staking.fetcher.YieldBalanceFetcherImplementor
|
||||
import com.tangem.data.staking.store.YieldsBalancesStore
|
||||
import com.tangem.data.staking.utils.StakingIdFactory
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.domain.staking.fetcher.YieldBalanceFetcherParams
|
||||
import com.tangem.domain.staking.model.StakingID
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Implementor of fetcher for refreshing single yield balance
|
||||
*
|
||||
* @property yieldsBalancesStore yields balances store
|
||||
* @property stakingIdFactory factory for creating [StakingID]
|
||||
* @property stakeKitApi StakeKit API
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class SingleYieldBalanceFetcherImplementor(
|
||||
private val yieldsBalancesStore: YieldsBalancesStore,
|
||||
private val stakingIdFactory: StakingIdFactory,
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : YieldBalanceFetcherImplementor<YieldBalanceFetcherParams.Single> {
|
||||
|
||||
override suspend fun createStakingIds(params: YieldBalanceFetcherParams.Single): Set<StakingID> {
|
||||
val dataStakingId = stakingIdFactory.createForDefault(
|
||||
userWalletId = params.userWalletId,
|
||||
currencyId = params.currencyId,
|
||||
network = params.network,
|
||||
) ?: return emptySet()
|
||||
|
||||
return setOf(
|
||||
StakingID(integrationId = dataStakingId.integrationId, address = dataStakingId.address),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun fetch(
|
||||
params: YieldBalanceFetcherParams.Single,
|
||||
stakingIds: Set<StakingID>,
|
||||
requests: List<YieldBalanceRequestBody>,
|
||||
) {
|
||||
fetchInternal(
|
||||
params = params,
|
||||
stakingId = stakingIds.first(),
|
||||
request = requests.first(),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchInternal(
|
||||
params: YieldBalanceFetcherParams.Single,
|
||||
stakingId: StakingID,
|
||||
request: YieldBalanceRequestBody,
|
||||
) {
|
||||
safeApiCall(
|
||||
call = {
|
||||
val result = withContext(dispatchers.io) {
|
||||
stakeKitApi.getSingleYieldBalance(
|
||||
integrationId = stakingId.integrationId,
|
||||
body = request,
|
||||
).bind()
|
||||
}
|
||||
|
||||
yieldsBalancesStore.storeActual(
|
||||
userWalletId = params.userWalletId,
|
||||
values = setOf(
|
||||
YieldBalanceWrapperDTO(
|
||||
balances = result,
|
||||
integrationId = request.integrationId,
|
||||
addresses = request.addresses,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
onError = {
|
||||
Timber.e(it, "Unable to fetch yield balances $params")
|
||||
|
||||
yieldsBalancesStore.storeError(userWalletId = params.userWalletId, stakingIds = setOf(stakingId))
|
||||
|
||||
throw it
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -59,32 +59,14 @@ internal class DefaultYieldsBalancesStore(
|
|||
}
|
||||
|
||||
override suspend fun refresh(userWalletId: UserWalletId, stakingId: StakingID) {
|
||||
updateBalanceInRuntime(userWalletId, stakingId) {
|
||||
updateBalancesInRuntime(userWalletId = userWalletId, stakingIds = setOf(stakingId)) {
|
||||
it.copySealed(source = StatusSource.CACHE)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun refresh(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
||||
runtimeStore.update(default = emptyMap()) { stored ->
|
||||
stored.toMutableMap().apply {
|
||||
val storedBalances = this[userWalletId].orEmpty()
|
||||
|
||||
val balances = stakingIds.mapTo(hashSetOf()) { stakingId ->
|
||||
val balance = storedBalances.firstOrNull {
|
||||
it.integrationId == stakingId.integrationId &&
|
||||
it.address == stakingId.address
|
||||
}
|
||||
?: createDefaultBalance(id = stakingId)
|
||||
|
||||
balance.copySealed(source = StatusSource.CACHE)
|
||||
}
|
||||
|
||||
val updatedBalances = storedBalances.addOrReplace(balances) { old, new ->
|
||||
old.integrationId == new.integrationId && old.address == new.address
|
||||
}
|
||||
|
||||
put(key = userWalletId, value = updatedBalances)
|
||||
}
|
||||
updateBalancesInRuntime(userWalletId = userWalletId, stakingIds = stakingIds) {
|
||||
it.copySealed(source = StatusSource.CACHE)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,8 +77,8 @@ internal class DefaultYieldsBalancesStore(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun storeError(userWalletId: UserWalletId, stakingId: StakingID) {
|
||||
updateBalanceInRuntime(userWalletId, stakingId) {
|
||||
override suspend fun storeError(userWalletId: UserWalletId, stakingIds: Set<StakingID>) {
|
||||
updateBalancesInRuntime(userWalletId = userWalletId, stakingIds = stakingIds) {
|
||||
it.copySealed(source = StatusSource.ONLY_CACHE)
|
||||
}
|
||||
}
|
||||
|
|
@ -127,26 +109,27 @@ internal class DefaultYieldsBalancesStore(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun updateBalanceInRuntime(
|
||||
private suspend fun updateBalancesInRuntime(
|
||||
userWalletId: UserWalletId,
|
||||
stakingID: StakingID,
|
||||
stakingIds: Set<StakingID>,
|
||||
update: (YieldBalance) -> YieldBalance,
|
||||
) {
|
||||
runtimeStore.update(default = emptyMap()) { stored ->
|
||||
stored.toMutableMap().apply {
|
||||
val balance = this[userWalletId].orEmpty()
|
||||
.firstOrNull {
|
||||
it.integrationId == stakingID.integrationId &&
|
||||
it.address == stakingID.address
|
||||
}
|
||||
?: createDefaultBalance(id = stakingID)
|
||||
val portfolioBalances = stored[userWalletId].orEmpty()
|
||||
|
||||
val updatedBalances = this[userWalletId].orEmpty()
|
||||
.addOrReplace(item = update(balance)) {
|
||||
it.integrationId == balance.integrationId &&
|
||||
it.address == balance.address
|
||||
val balances = stakingIds.mapTo(hashSetOf()) { stakingId ->
|
||||
val balance = portfolioBalances.firstOrNull {
|
||||
it.integrationId == stakingId.integrationId && it.address == stakingId.address
|
||||
}
|
||||
|
||||
if (balance != null) update(balance) else createDefaultBalance(id = stakingId)
|
||||
}
|
||||
|
||||
val updatedBalances = portfolioBalances.addOrReplace(items = balances) { old, new ->
|
||||
old.integrationId == new.integrationId && old.address == new.address
|
||||
}
|
||||
|
||||
put(key = userWalletId, value = updatedBalances)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,6 @@ interface YieldsBalancesStore {
|
|||
/** Store actual [values] by [userWalletId] */
|
||||
suspend fun storeActual(userWalletId: UserWalletId, values: Set<YieldBalanceWrapperDTO>)
|
||||
|
||||
/** Store error by [userWalletId] and [stakingId] */
|
||||
suspend fun storeError(userWalletId: UserWalletId, stakingId: StakingID)
|
||||
/** Store error by [userWalletId] and [stakingIds] */
|
||||
suspend fun storeError(userWalletId: UserWalletId, stakingIds: Set<StakingID>)
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ internal class YieldsBalancesStoreUpdateMethodsTest {
|
|||
|
||||
@Test
|
||||
fun `store error if runtime store is empty`() = runTest {
|
||||
store.storeError(userWalletId = userWalletId, stakingId = stakingId)
|
||||
store.storeError(userWalletId = userWalletId, stakingIds = setOf(stakingId))
|
||||
|
||||
val runtimeExpected = mapOf(
|
||||
userWalletId to setOf(
|
||||
|
|
@ -162,7 +162,7 @@ internal class YieldsBalancesStoreUpdateMethodsTest {
|
|||
),
|
||||
)
|
||||
|
||||
store.storeError(userWalletId = userWalletId, stakingId = stakingId)
|
||||
store.storeError(userWalletId = userWalletId, stakingIds = setOf(stakingId))
|
||||
|
||||
val runtimeExpected = mapOf(
|
||||
userWalletId to setOf(wrapper.toDomain(source = StatusSource.ONLY_CACHE)),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.domain.staking.fetcher
|
||||
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.Network
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
/**
|
||||
* Params for fetchers of yield balance
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface YieldBalanceFetcherParams {
|
||||
|
||||
/** User wallet ID */
|
||||
val userWalletId: UserWalletId
|
||||
|
||||
/**
|
||||
* Params for fetching multiple yield balances
|
||||
*
|
||||
* @property userWalletId user wallet ID
|
||||
* @property currencyIdWithNetworkMap map of currency ID to network
|
||||
*/
|
||||
data class Multi(
|
||||
override val userWalletId: UserWalletId,
|
||||
val currencyIdWithNetworkMap: Map<CryptoCurrency.ID, Network>,
|
||||
) : YieldBalanceFetcherParams
|
||||
|
||||
/**
|
||||
* Params for fetching single yield balance
|
||||
*
|
||||
* @property userWalletId user wallet ID
|
||||
* @property currencyId currency ID
|
||||
* @property network network
|
||||
*/
|
||||
data class Single(
|
||||
override val userWalletId: UserWalletId,
|
||||
val currencyId: CryptoCurrency.ID,
|
||||
val network: Network,
|
||||
) : YieldBalanceFetcherParams
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue