diff --git a/data/staking/src/main/java/com/tangem/data/staking/fetcher/YieldBalanceFetcherImplementor.kt b/data/staking/src/main/java/com/tangem/data/staking/fetcher/YieldBalanceFetcherImplementor.kt new file mode 100644 index 0000000000..27835a3da0 --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/fetcher/YieldBalanceFetcherImplementor.kt @@ -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 { + + /** Create set of [StakingID] */ + suspend fun createStakingIds(params: @UnsafeVariance Params): Set + + /** + * Fetch yield balances + * + * @param params params + * @param stakingIds set of [StakingID] + * @param requests requests + */ + suspend fun fetch( + params: @UnsafeVariance Params, + stakingIds: Set, + requests: List, + ) +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/multi/MultiYieldBalanceFetcherImplementor.kt b/data/staking/src/main/java/com/tangem/data/staking/multi/MultiYieldBalanceFetcherImplementor.kt new file mode 100644 index 0000000000..8edc3997bc --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/multi/MultiYieldBalanceFetcherImplementor.kt @@ -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 { + + override suspend fun createStakingIds(params: YieldBalanceFetcherParams.Multi): Set { + 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, + requests: List, + ) { + 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 + }, + ) + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/single/SingleYieldBalanceFetcherImplementor.kt b/data/staking/src/main/java/com/tangem/data/staking/single/SingleYieldBalanceFetcherImplementor.kt new file mode 100644 index 0000000000..6a47800b12 --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/single/SingleYieldBalanceFetcherImplementor.kt @@ -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 { + + override suspend fun createStakingIds(params: YieldBalanceFetcherParams.Single): Set { + 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, + requests: List, + ) { + 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 + }, + ) + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt b/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt index c3a8b2a74c..18fb91c240 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/store/DefaultYieldsBalancesStore.kt @@ -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) { - 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) { + 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, 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) } } diff --git a/data/staking/src/main/java/com/tangem/data/staking/store/YieldsBalancesStore.kt b/data/staking/src/main/java/com/tangem/data/staking/store/YieldsBalancesStore.kt index 3371d07f1c..326dc6fc73 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/store/YieldsBalancesStore.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/store/YieldsBalancesStore.kt @@ -25,6 +25,6 @@ interface YieldsBalancesStore { /** Store actual [values] by [userWalletId] */ suspend fun storeActual(userWalletId: UserWalletId, values: Set) - /** 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) } \ No newline at end of file diff --git a/data/staking/src/test/kotlin/com/tangem/data/staking/store/YieldsBalancesStoreUpdateMethodsTest.kt b/data/staking/src/test/kotlin/com/tangem/data/staking/store/YieldsBalancesStoreUpdateMethodsTest.kt index 8c51eacb19..dc05b3db34 100644 --- a/data/staking/src/test/kotlin/com/tangem/data/staking/store/YieldsBalancesStoreUpdateMethodsTest.kt +++ b/data/staking/src/test/kotlin/com/tangem/data/staking/store/YieldsBalancesStoreUpdateMethodsTest.kt @@ -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)), diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/fetcher/YieldBalanceFetcherParams.kt b/domain/staking/src/main/java/com/tangem/domain/staking/fetcher/YieldBalanceFetcherParams.kt new file mode 100644 index 0000000000..ce15ede323 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/fetcher/YieldBalanceFetcherParams.kt @@ -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, + ) : 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 +} \ No newline at end of file