diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index be10ba1059..53e9546b1b 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -1,10 +1,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.staking.* -import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.staking.repositories.StakingRepository -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.* import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -51,6 +48,32 @@ internal object StakingDomainModule { ) } + @Provides + @Singleton + fun provideFetchActionsUseCase( + stakingRepository: StakingRepository, + stakingActionRepository: StakingActionRepository, + stakingErrorResolver: StakingErrorResolver, + ): FetchActionsUseCase { + return FetchActionsUseCase( + stakingRepository = stakingRepository, + stakingActionRepository = stakingActionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + + @Provides + @Singleton + fun provideGetActionsUseCase( + stakingActionRepository: StakingActionRepository, + stakingErrorResolver: StakingErrorResolver, + ): GetActionsUseCase { + return GetActionsUseCase( + stakingActionRepository = stakingActionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + @Provides @Singleton fun provideGetStakingTokensUseCase( @@ -123,38 +146,12 @@ internal object StakingDomainModule { ) } - @Provides - @Singleton - fun provideSavePendingTransactionUseCase( - stakingPendingTransactionRepository: StakingPendingTransactionRepository, - stakingErrorResolver: StakingErrorResolver, - ): SavePendingTransactionUseCase { - return SavePendingTransactionUseCase( - stakingPendingTransactionRepository = stakingPendingTransactionRepository, - stakingErrorResolver = stakingErrorResolver, - ) - } - @Provides @Singleton fun provideInvalidatePendingTransactionsUseCase( - stakingPendingTransactionRepository: StakingPendingTransactionRepository, stakingErrorResolver: StakingErrorResolver, ): InvalidatePendingTransactionsUseCase { return InvalidatePendingTransactionsUseCase( - stakingPendingTransactionRepository = stakingPendingTransactionRepository, - stakingErrorResolver = stakingErrorResolver, - ) - } - - @Provides - @Singleton - fun provideGetPendingTransactionsUseCase( - stakingPendingTransactionRepository: StakingPendingTransactionRepository, - stakingErrorResolver: StakingErrorResolver, - ): GetStakingPendingTransactionsUseCase { - return GetStakingPendingTransactionsUseCase( - stakingPendingTransactionRepository = stakingPendingTransactionRepository, stakingErrorResolver = stakingErrorResolver, ) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt index 487f53ea0e..745fa5fe39 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt @@ -3,7 +3,8 @@ package com.tangem.datasource.api.stakekit import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.stakekit.models.request.* import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse -import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse +import com.tangem.datasource.api.stakekit.models.response.ActionDTO +import com.tangem.datasource.api.stakekit.models.response.GetActionsResponse import com.tangem.datasource.api.stakekit.models.response.model.BalanceDTO import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO import com.tangem.datasource.api.stakekit.models.response.model.transaction.StakingGasEstimateDTO @@ -35,14 +36,23 @@ interface StakeKitApi { @Body body: YieldBalanceRequestBody, ): ApiResponse> + @GET("actions") + suspend fun getActions( + @Query("walletAddress") walletAddress: String, + @Query("network") network: String, + @Query("status") status: String, + @Query("sort") sort: String = "createdAtDesc", + @Query("limit") limit: Int = 50, + ): ApiResponse + @POST("actions/enter") - suspend fun createEnterAction(@Body body: ActionRequestBody): ApiResponse + suspend fun createEnterAction(@Body body: ActionRequestBody): ApiResponse @POST("actions/exit") - suspend fun createExitAction(@Body body: ActionRequestBody): ApiResponse + suspend fun createExitAction(@Body body: ActionRequestBody): ApiResponse @POST("actions/pending") - suspend fun createPendingAction(@Body body: PendingActionRequestBody): ApiResponse + suspend fun createPendingAction(@Body body: PendingActionRequestBody): ApiResponse @POST("actions/enter/estimate-gas") suspend fun estimateGasOnEnter(@Body body: ActionRequestBody): ApiResponse diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnterActionResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/ActionDTO.kt similarity index 97% rename from core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnterActionResponse.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/ActionDTO.kt index 9413c43398..87382ee7ef 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/EnterActionResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/ActionDTO.kt @@ -9,7 +9,7 @@ import org.joda.time.DateTime import java.math.BigDecimal @JsonClass(generateAdapter = true) -data class EnterActionResponse( +data class ActionDTO( @Json(name = "id") val id: String, @Json(name = "integrationId") diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/GetActionsResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/GetActionsResponse.kt new file mode 100644 index 0000000000..0e116d42be --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/models/response/GetActionsResponse.kt @@ -0,0 +1,16 @@ +package com.tangem.datasource.api.stakekit.models.response + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class GetActionsResponse( + @Json(name = "data") + val data: List, + @Json(name = "hasNextPage") + val hasNextPage: Boolean, + @Json(name = "limit") + val limit: Int, + @Json(name = "page") + val page: Int, +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/StakingBalanceStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt similarity index 54% rename from core/datasource/src/main/java/com/tangem/datasource/di/StakingBalanceStoreModule.kt rename to core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt index 21d78318c1..3dda78cc57 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/StakingBalanceStoreModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/StakingStoreModule.kt @@ -1,8 +1,9 @@ package com.tangem.datasource.di import com.tangem.datasource.local.datastore.RuntimeDataStore +import com.tangem.datasource.local.token.* import com.tangem.datasource.local.token.DefaultStakingBalanceStore -import com.tangem.datasource.local.token.StakingBalanceStore +import com.tangem.datasource.local.token.DefaultStakingYieldsStore import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -11,11 +12,23 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object StakingBalanceStoreModule { +internal object StakingStoreModule { + + @Provides + @Singleton + fun provideStakingTokensStore(): StakingYieldsStore { + return DefaultStakingYieldsStore() + } @Provides @Singleton fun provideStakingBalanceStore(): StakingBalanceStore { return DefaultStakingBalanceStore(dataStore = RuntimeDataStore()) } + + @Provides + @Singleton + fun provideStakingActionsStore(): StakingActionsStore { + return DefaultStakingActionsStore(dataStore = RuntimeDataStore()) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt deleted file mode 100644 index d55c4066ec..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/di/StakingTokensStoreModule.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.tangem.datasource.di - -import com.tangem.datasource.local.token.DefaultStakingYieldsStore -import com.tangem.datasource.local.token.StakingYieldsStore -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -internal object StakingTokensStoreModule { - - @Provides - @Singleton - fun provideStakingTokensStore(): StakingYieldsStore { - return DefaultStakingYieldsStore() - } -} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingActionsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingActionsStore.kt new file mode 100644 index 0000000000..39f499ca68 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingActionsStore.kt @@ -0,0 +1,34 @@ +package com.tangem.datasource.local.token + +import com.tangem.datasource.local.datastore.core.StringKeyDataStore +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +internal class DefaultStakingActionsStore( + private val dataStore: StringKeyDataStore>, +) : StakingActionsStore { + + private val mutex = Mutex() + + override fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> { + return dataStore.get(composeKey(userWalletId, cryptoCurrencyId)) + } + + override suspend fun store( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + items: List, + ) { + mutex.withLock { + dataStore.store(composeKey(userWalletId, cryptoCurrencyId), items) + } + } + + private fun composeKey(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): String { + return userWalletId.stringValue + cryptoCurrencyId.value + } +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt index db8de27d38..d30a83e4c3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt @@ -4,13 +4,13 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO internal class DefaultStakingYieldsStore : StakingYieldsStore { - private var yields = mutableListOf() + private var yields = listOf() override fun get(): List { return yields } override fun store(items: List) { - yields = items.toMutableList() + yields = items } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingActionsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingActionsStore.kt new file mode 100644 index 0000000000..aa6de02da4 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/StakingActionsStore.kt @@ -0,0 +1,13 @@ +package com.tangem.datasource.local.token + +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface StakingActionsStore { + + fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> + + suspend fun store(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, items: List) +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt new file mode 100644 index 0000000000..888f6a342c --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingActionRepository.kt @@ -0,0 +1,30 @@ +package com.tangem.data.staking + +import com.tangem.datasource.local.token.StakingActionsStore +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext + +internal class DefaultStakingActionRepository( + private val stakingActionsStore: StakingActionsStore, + private val dispatchers: CoroutineDispatcherProvider, +) : StakingActionRepository { + + override suspend fun store( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + actions: List, + ) { + withContext(dispatchers.io) { + stakingActionsStore.store(userWalletId, cryptoCurrencyId, actions) + } + } + + override fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> { + return stakingActionsStore.get(userWalletId, cryptoCurrencyId) + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt deleted file mode 100644 index 008ea7161a..0000000000 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.data.staking - -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.BalanceItem -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.wallets.models.UserWalletId -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.CopyOnWriteArrayList - -internal class DefaultStakingPendingTransactionRepository : StakingPendingTransactionRepository { - - private val pendingTransactionsMap = ConcurrentHashMap>() - - override fun saveTransaction(userWalletId: UserWalletId, transaction: PendingTransaction) { - val transactions = pendingTransactionsMap.computeIfAbsent(userWalletId) { CopyOnWriteArrayList() } - transactions.add(transaction) - } - - override fun removeTransactions(userWalletId: UserWalletId, transactions: Set) { - pendingTransactionsMap[userWalletId]?.removeAll(transactions) - } - - override fun getTransactionsWithBalanceItems( - userWalletId: UserWalletId, - ): List> { - return pendingTransactionsMap[userWalletId]?.mapNotNull { pendingTransaction: PendingTransaction -> - PendingTransactionItemConverter.convert(pendingTransaction)?.let { balanceItem -> - pendingTransaction to balanceItem - } - } ?: emptyList() - } -} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index ce9296103a..e57c15dd67 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -25,19 +25,19 @@ import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConv import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.api.stakekit.models.request.* +import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO +import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO 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.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo -import com.tangem.domain.staking.model.stakekit.NetworkType -import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.YieldBalance -import com.tangem.domain.staking.model.stakekit.YieldBalanceList +import com.tangem.domain.staking.model.stakekit.* import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate @@ -102,6 +102,8 @@ internal class DefaultStakingRepository( 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) } override fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String = with(cryptoCurrencyId) { rawNetworkId.plus(rawCurrencyId) @@ -140,6 +142,40 @@ internal class DefaultStakingRepository( } } + override suspend fun getActions( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): List { + return withContext(dispatchers.io) { + val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty() + + val networkTypeDto = networkTypeConverter.convertBack(networkType) + val networkTypeString = networkTypeDto.extractJsonName() + + val actionStatusDTO = actionStatusConverter.convertBack(stakingActionStatus) + val actionStatusString = actionStatusDTO.extractJsonName() + + enterActionResponseConverter.convertListIgnoreErrors( + input = stakeKitApi.getActions( + walletAddress = address, + network = networkTypeString, + status = actionStatusString, + ).getOrThrow().data, + onError = { Timber.e("Error converting staking actions list: $it") }, + ) + } + } + + private fun NetworkTypeDTO.extractJsonName(): String { + return networkTypeAdapter.toJson(this).replace("\"", "") + } + + private fun StakingActionStatusDTO.extractJsonName(): String { + return stakingActionStatusAdapter.toJson(this).replace("\"", "") + } + override suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo { return withContext(dispatchers.io) { val yield = getYield(cryptoCurrencyId, symbol) diff --git a/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt deleted file mode 100644 index 026c1435bd..0000000000 --- a/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem.data.staking - -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.BalanceItem -import com.tangem.utils.converter.Converter -import org.joda.time.DateTime - -internal object PendingTransactionItemConverter : Converter { - - override fun convert(value: PendingTransaction): BalanceItem? { - return BalanceItem( - groupId = value.groupId ?: return null, - token = value.token, - type = value.type, - amount = value.amount, - rawCurrencyId = value.rawCurrencyId, - validatorAddress = value.validator?.address, - date = DateTime.now(), - pendingActions = emptyList(), - isPending = true, - ) - } -} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt index 4bc04836a3..0a4ce1e6ff 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/action/ActionStatusConverter.kt @@ -2,9 +2,10 @@ package com.tangem.data.staking.converters.action import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus -import com.tangem.utils.converter.Converter +import com.tangem.utils.converter.TwoWayConverter + +class ActionStatusConverter : TwoWayConverter { -class ActionStatusConverter : Converter { override fun convert(value: StakingActionStatusDTO): StakingActionStatus { return when (value) { StakingActionStatusDTO.CANCELED -> StakingActionStatus.CANCELED @@ -16,4 +17,16 @@ class ActionStatusConverter : Converter StakingActionStatus.UNKNOWN } } + + override fun convertBack(value: StakingActionStatus): StakingActionStatusDTO { + return when (value) { + StakingActionStatus.CANCELED -> StakingActionStatusDTO.CANCELED + StakingActionStatus.CREATED -> StakingActionStatusDTO.CREATED + StakingActionStatus.WAITING_FOR_NEXT -> StakingActionStatusDTO.WAITING_FOR_NEXT + StakingActionStatus.PROCESSING -> StakingActionStatusDTO.PROCESSING + StakingActionStatus.FAILED -> StakingActionStatusDTO.FAILED + StakingActionStatus.SUCCESS -> StakingActionStatusDTO.SUCCESS + else -> StakingActionStatusDTO.UNKNOWN + } + } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt index d06ab798d2..b46fd75f85 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/action/EnterActionResponseConverter.kt @@ -1,7 +1,7 @@ package com.tangem.data.staking.converters.action import com.tangem.data.staking.converters.transaction.StakingTransactionConverter -import com.tangem.datasource.api.stakekit.models.response.EnterActionResponse +import com.tangem.datasource.api.stakekit.models.response.ActionDTO import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.utils.converter.Converter @@ -9,9 +9,9 @@ class EnterActionResponseConverter( private val actionStatusConverter: ActionStatusConverter, private val stakingActionTypeConverter: StakingActionTypeConverter, private val transactionConverter: StakingTransactionConverter, -) : Converter { +) : Converter { - override fun convert(value: EnterActionResponse): StakingAction { + override fun convert(value: ActionDTO): StakingAction { return StakingAction( id = value.id, integrationId = value.integrationId, diff --git a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt index e440b5c172..2226847178 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/di/StakingDataModule.kt @@ -2,8 +2,8 @@ package com.tangem.data.staking.di import com.squareup.moshi.Moshi import com.tangem.data.common.cache.CacheRegistry +import com.tangem.data.staking.* import com.tangem.data.staking.DefaultStakingErrorResolver -import com.tangem.data.staking.DefaultStakingPendingTransactionRepository import com.tangem.data.staking.DefaultStakingRepository import com.tangem.data.staking.DefaultStakingTransactionHashRepository import com.tangem.data.staking.converters.error.StakeKitErrorConverter @@ -11,12 +11,10 @@ import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitErrorResponse import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.token.StakingActionsStore import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore -import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.staking.repositories.StakingRepository -import com.tangem.domain.staking.repositories.StakingTransactionHashRepository +import com.tangem.domain.staking.repositories.* import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles @@ -73,8 +71,14 @@ internal object StakingDataModule { @Provides @Singleton - fun provideStakingPendingTransactionRepository(): StakingPendingTransactionRepository { - return DefaultStakingPendingTransactionRepository() + fun provideStakingActionRepository( + stakingActionsStore: StakingActionsStore, + dispatchers: CoroutineDispatcherProvider, + ): StakingActionRepository { + return DefaultStakingActionRepository( + stakingActionsStore = stakingActionsStore, + dispatchers = dispatchers, + ) } @Provides diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/PendingTransaction.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/PendingTransaction.kt deleted file mode 100644 index 408de7dc0d..0000000000 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/PendingTransaction.kt +++ /dev/null @@ -1,16 +0,0 @@ -package com.tangem.domain.staking.model - -import com.tangem.domain.staking.model.stakekit.BalanceType -import com.tangem.domain.staking.model.stakekit.Token -import com.tangem.domain.staking.model.stakekit.Yield -import java.math.BigDecimal - -data class PendingTransaction( - val groupId: String?, - val token: Token, - val type: BalanceType, - val amount: BigDecimal, - val rawCurrencyId: String?, - val validator: Yield.Validator?, - val balancesId: Int, -) \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/SubmitHashData.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/SubmitHashData.kt index 684264334d..662c27c5c8 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/SubmitHashData.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/SubmitHashData.kt @@ -3,5 +3,4 @@ package com.tangem.domain.staking.model data class SubmitHashData( val transactionHash: String, val transactionId: String, - val pendingTransaction: PendingTransaction, ) \ No newline at end of file diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt index d1f0f3a3f3..ef9946cdb7 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt @@ -32,14 +32,6 @@ sealed class YieldBalance { .distinctBy { it.validatorAddress } .size } - - fun getBalancesUniqueId(): Int { - // need to exclude rewards because their amount may change frequently - return balance.items - .filter { it.type != BalanceType.REWARDS } - .map { it.amount.toString() + it.type.toString() + it.groupId } - .hashCode() - } } data object Empty : YieldBalance() diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt new file mode 100644 index 0000000000..1e437878e6 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/FetchActionsUseCase.kt @@ -0,0 +1,41 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.stakekit.NetworkType +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus +import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId + +/** + * Use case for getting pending actions list. + */ +class FetchActionsUseCase( + private val stakingRepository: StakingRepository, + private val stakingActionRepository: StakingActionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + suspend operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): Either { + return Either + .catch { + val actions = stakingRepository.getActions( + userWalletId = userWalletId, + cryptoCurrency = cryptoCurrency, + networkType = networkType, + stakingActionStatus = stakingActionStatus, + ) + + stakingActionRepository.store(userWalletId, cryptoCurrency.id, actions) + } + .mapLeft { stakingErrorResolver.resolve(it) } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt new file mode 100644 index 0000000000..3190d22711 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetActionsUseCase.kt @@ -0,0 +1,34 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.domain.core.utils.EitherFlow +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.repositories.StakingActionRepository +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map + +/** + * Use case for getting pending actions list. + */ +class GetActionsUseCase( + private val stakingActionRepository: StakingActionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): EitherFlow> { + return stakingActionRepository.get( + userWalletId = userWalletId, + cryptoCurrencyId = cryptoCurrencyId, + ).map, Either>> { it.right() } + .catch { emit(stakingErrorResolver.resolve(it).left()) } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingPendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingPendingTransactionsUseCase.kt deleted file mode 100644 index 15557232b0..0000000000 --- a/domain/staking/src/main/java/com/tangem/domain/staking/GetStakingPendingTransactionsUseCase.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.tangem.domain.staking - -import arrow.core.Either -import com.tangem.domain.staking.model.stakekit.BalanceItem -import com.tangem.domain.staking.model.stakekit.StakingError -import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.wallets.models.UserWalletId - -/** - * Use case for getting saved staking pending transactions. - */ -class GetStakingPendingTransactionsUseCase( - private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, - private val stakingErrorResolver: StakingErrorResolver, -) { - - operator fun invoke(userWalletId: UserWalletId): Either> { - return Either.catch { - stakingPendingTransactionRepository.getTransactionsWithBalanceItems(userWalletId).map { it.second } - }.mapLeft { - stakingErrorResolver.resolve(it) - } - } -} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt index 2c07ae8ea0..1b0f6b9128 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt @@ -1,104 +1,85 @@ package com.tangem.domain.staking import arrow.core.Either -import com.tangem.domain.staking.model.PendingTransaction import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.BalanceType import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.wallets.models.UserWalletId -import org.joda.time.DateTime -import java.math.BigDecimal import java.util.UUID class InvalidatePendingTransactionsUseCase( - private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, private val stakingErrorResolver: StakingErrorResolver, ) { operator fun invoke( - userWalletId: UserWalletId, balanceItems: List, - balancesId: Int, + processingActions: List, ): Either> { return Either.catch { - val (balancesToDisplay, transactionsToRemove) = mergeRealAndPendingTransactions( - realData = balanceItems, - newBalancesId = balancesId, - pendingData = stakingPendingTransactionRepository.getTransactionsWithBalanceItems(userWalletId), + val balancesToDisplay = mergeBalancesAndProcessingActions( + realBalances = balanceItems, + processingActions = processingActions, ) - - stakingPendingTransactionRepository.removeTransactions(userWalletId, transactionsToRemove.toSet()) - balancesToDisplay }.mapLeft { stakingErrorResolver.resolve(it) } } - private fun mergeRealAndPendingTransactions( - realData: List, - newBalancesId: Int, - pendingData: List>, - ): Pair, List> { - val balances = realData.groupBy { BalanceIdentity(it.groupId, it.type, it.amount, it.date) } - .mapValues { it.value.toMutableList() } - .toMutableMap() + private fun mergeBalancesAndProcessingActions( + realBalances: List, + processingActions: List, + ): List { + val balances = realBalances.toMutableList() - val transactionsToRemove = mutableListOf() - - pendingData.forEach { (pendingTransaction, balanceItem) -> - val key = BalanceIdentity(balanceItem.groupId, balanceItem.type, balanceItem.amount, balanceItem.date) - val oldBalancesId = pendingTransaction.balancesId - - when { - newBalancesId != oldBalancesId -> { - transactionsToRemove.add(pendingTransaction) + processingActions.forEach { action -> + when (action.type) { + StakingActionType.STAKE, StakingActionType.VOTE, StakingActionType.VOTE_LOCKED -> { + processEnterAction(balances, action) } - balances.containsKey(key) -> { - val removed = balances[key]?.removeIf { !it.isPending } ?: false - if (removed) { - balances[key]?.add(balanceItem) - } + StakingActionType.WITHDRAW -> { + modifyBalancesByStatus(balances, action, BalanceType.UNSTAKED) + } + StakingActionType.UNLOCK_LOCKED -> { + modifyBalancesByStatus(balances, action, BalanceType.LOCKED) + } + StakingActionType.UNSTAKE -> { + modifyBalancesByStatus(balances, action, BalanceType.STAKED) } else -> { - val groupId = UUID.randomUUID().toString() - val now = DateTime.now() - - balances[BalanceIdentity(groupId, BalanceType.STAKED, pendingTransaction.amount, now)] = - mutableListOf( - BalanceItem( - groupId = groupId, - type = pendingTransaction.type, - amount = pendingTransaction.amount, - rawCurrencyId = pendingTransaction.rawCurrencyId, - validatorAddress = pendingTransaction.validator?.address, - date = null, - pendingActions = emptyList(), - token = pendingTransaction.token, - isPending = true, - ), - ) - - balances.entries.find { - it.key.type == BalanceType.STAKED && - it.key.amount == pendingTransaction.amount && - !it.value.any { it.isPending } - } - ?.key - ?.let { balances.remove(it) } + // intentionally do nothing } } } - return balances.values.flatten() to transactionsToRemove + return balances } - private data class BalanceIdentity( - val groupId: String, - val type: BalanceType, - val amount: BigDecimal, - val date: DateTime?, - ) + private fun processEnterAction(balances: MutableList, action: StakingAction) { + balances.add( + BalanceItem( + groupId = UUID.randomUUID().toString(), + token = balances[0].token, + type = BalanceType.STAKED, + amount = action.amount, + rawCurrencyId = null, + validatorAddress = action.validatorAddress ?: action.validatorAddresses?.get(0) ?: "", + date = null, + pendingActions = emptyList(), + isPending = true, + ), + ) + } + + private fun modifyBalancesByStatus(balances: MutableList, action: StakingAction, type: BalanceType) { + val index = balances.indexOfFirst { + !it.isPending && it.amount == action.amount && it.type == type + } + + if (index != -1) { + balances[index] = balances[index].copy(isPending = true) + } + } } \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt deleted file mode 100644 index ef80e06e64..0000000000 --- a/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.tangem.domain.staking - -import arrow.core.Either -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.StakingError -import com.tangem.domain.staking.repositories.StakingErrorResolver -import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository -import com.tangem.domain.wallets.models.UserWalletId - -/** - * Use case for saving hash that failed to submit during staking confirmation - */ -class SavePendingTransactionUseCase( - private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, - private val stakingErrorResolver: StakingErrorResolver, -) { - - operator fun invoke(userWalletId: UserWalletId, transaction: PendingTransaction): Either { - return Either.catch { - stakingPendingTransactionRepository.saveTransaction( - userWalletId = userWalletId, - transaction = transaction, - ) - }.mapLeft { - stakingErrorResolver.resolve(it) - } - } -} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt new file mode 100644 index 0000000000..142c9d08f6 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingActionRepository.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.staking.repositories + +import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface StakingActionRepository { + + suspend fun store(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, actions: List) + + fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt deleted file mode 100644 index 708b8e1352..0000000000 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt +++ /dev/null @@ -1,14 +0,0 @@ -package com.tangem.domain.staking.repositories - -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.BalanceItem -import com.tangem.domain.wallets.models.UserWalletId - -interface StakingPendingTransactionRepository { - - fun getTransactionsWithBalanceItems(userWalletId: UserWalletId): List> - - fun saveTransaction(userWalletId: UserWalletId, transaction: PendingTransaction) - - fun removeTransactions(userWalletId: UserWalletId, transactions: Set) -} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index 568683be77..e68adf73a9 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -6,10 +6,12 @@ import com.tangem.blockchain.common.transaction.Fee import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo +import com.tangem.domain.staking.model.stakekit.NetworkType import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.YieldBalanceList import com.tangem.domain.staking.model.stakekit.action.StakingAction +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingGasEstimate import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction @@ -33,6 +35,13 @@ interface StakingRepository { fun getStakingAvailability(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): StakingAvailability + suspend fun getActions( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): List + suspend fun fetchSingleYieldBalance( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt index c1f5dfaac8..624325cda9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/CurrencyStatusError.kt @@ -2,7 +2,7 @@ package com.tangem.domain.tokens.error sealed class CurrencyStatusError { - object UnableToCreateCurrency : CurrencyStatusError() + data object UnableToCreateCurrency : CurrencyStatusError() data class DataError(val cause: Throwable) : CurrencyStatusError() } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt index 0dc93edf8d..fa3e6119d7 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockStakingRepository.kt @@ -117,6 +117,15 @@ class MockStakingRepository : StakingRepository { cryptoCurrency: CryptoCurrency, ): StakingAvailability = StakingAvailability.Unavailable + override suspend fun getActions( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + networkType: NetworkType, + stakingActionStatus: StakingActionStatus, + ): List { + return emptyList() + } + override suspend fun fetchSingleYieldBalance( userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 56aa176637..cdc4967df2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -9,7 +9,6 @@ import com.tangem.core.ui.components.list.RoundedListWithDividersItemData import com.tangem.core.ui.event.StateEvent import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig -import com.tangem.domain.staking.model.PendingTransaction import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType @@ -120,7 +119,6 @@ internal sealed class StakingStates { val transactionDoneState: TransactionDoneState, val isApprovalNeeded: Boolean, val reduceAmountBy: BigDecimal?, - val possiblePendingTransaction: PendingTransaction?, ) : ConfirmationState() data class Empty( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt index 55446f2303..aa8342846e 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingBalanceUpdater.kt @@ -1,6 +1,9 @@ package com.tangem.features.staking.impl.presentation.state.helpers import com.tangem.domain.staking.FetchStakingYieldBalanceUseCase +import com.tangem.domain.staking.FetchActionsUseCase +import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.action.StakingActionStatus import com.tangem.domain.tokens.FetchPendingTransactionsUseCase import com.tangem.domain.tokens.UpdateDelayedNetworkStatusUseCase import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -20,16 +23,21 @@ internal class StakingBalanceUpdater @AssistedInject constructor( private val stakingYieldBalanceUseCase: FetchStakingYieldBalanceUseCase, private val getTxHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val getTxHistoryItemsUseCase: GetTxHistoryItemsUseCase, + private val fetchActionsUseCase: FetchActionsUseCase, @DelayedWork private val coroutineScope: CoroutineScope, @Assisted private val userWallet: UserWallet, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, + @Assisted private val yield: Yield, ) { fun scheduleUpdates() { coroutineScope.launch { listOf( // we should update network to find pending tx after 1 sec async { - fetchPendingTransactionsUseCase(userWallet.walletId, setOf(cryptoCurrencyStatus.currency.network)) + fetchPendingTransactionsUseCase( + userWalletId = userWallet.walletId, + networks = setOf(cryptoCurrencyStatus.currency.network), + ) }, // we should update tx history and network for new balances async { @@ -41,6 +49,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor( async { updateNetworkStatuses() }, + async { + updateProcessingActions() + }, ).awaitAll() } } @@ -54,6 +65,9 @@ internal class StakingBalanceUpdater @AssistedInject constructor( async { updateNetworkStatuses(delay = 0) }, + async { + updateProcessingActions() + }, ).awaitAll() } } @@ -91,9 +105,22 @@ internal class StakingBalanceUpdater @AssistedInject constructor( } } + private suspend fun updateProcessingActions() { + fetchActionsUseCase( + userWalletId = userWallet.walletId, + cryptoCurrency = cryptoCurrencyStatus.currency, + networkType = yield.token.network, + stakingActionStatus = StakingActionStatus.PROCESSING, + ) + } + @AssistedFactory interface Factory { - fun create(cryptoCurrencyStatus: CryptoCurrencyStatus, userWallet: UserWallet): StakingBalanceUpdater + fun create( + cryptoCurrencyStatus: CryptoCurrencyStatus, + userWallet: UserWallet, + yield: Yield, + ): StakingBalanceUpdater } private companion object { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index 2ac221975e..5e5538c4e1 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -6,7 +6,6 @@ import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.staking.* -import com.tangem.domain.staking.model.PendingTransaction import com.tangem.domain.staking.model.SubmitHashData import com.tangem.domain.staking.model.stakekit.* import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType @@ -36,7 +35,6 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import timber.log.Timber import java.math.BigDecimal -import java.util.UUID @Suppress("LongParameterList") internal class StakingTransactionSender @AssistedInject constructor( @@ -48,7 +46,6 @@ internal class StakingTransactionSender @AssistedInject constructor( private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val submitHashUseCase: SubmitHashUseCase, private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase, - private val savePendingTransactionUseCase: SavePendingTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @Assisted private val userWallet: UserWallet, @@ -57,7 +54,7 @@ internal class StakingTransactionSender @AssistedInject constructor( ) { private val balanceUpdater: StakingBalanceUpdater - get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet) + get() = stakingBalanceUpdater.create(cryptoCurrencyStatus, userWallet, yield) suspend fun constructAndSendTransactions( onConstructSuccess: (List) -> Unit, @@ -95,16 +92,11 @@ internal class StakingTransactionSender @AssistedInject constructor( sendStakingTransaction( fullTransactionsData = fullTransactionsData, - possiblePendingTransaction = composePendingTransaction(confirmationState), onSendSuccess = onSendSuccess, onSendError = onSendError, ) } - private fun composePendingTransaction(confirmationState: StakingStates.ConfirmationState.Data): PendingTransaction { - return confirmationState.possiblePendingTransaction ?: composeStakeTransaction() - } - private suspend fun getStakingTransactions( state: StakingUiState, confirmationState: StakingStates.ConfirmationState.Data, @@ -214,7 +206,6 @@ internal class StakingTransactionSender @AssistedInject constructor( private suspend fun sendStakingTransaction( fullTransactionsData: List, - possiblePendingTransaction: PendingTransaction, onSendSuccess: (txUrl: String) -> Unit, onSendError: (SendTransactionError?) -> Unit, ) { @@ -230,7 +221,6 @@ internal class StakingTransactionSender @AssistedInject constructor( submitHash( transactions = fullTransactionsData.map { it.stakeKitTransaction }, transactionHashes = transactionHashes, - pendingTransaction = possiblePendingTransaction, ) val txUrl = getExplorerTransactionUrlUseCase( txHash = transactionHashes.last(), @@ -243,11 +233,7 @@ internal class StakingTransactionSender @AssistedInject constructor( ) } - private suspend fun submitHash( - transactions: List, - transactionHashes: List, - pendingTransaction: PendingTransaction, - ) { + private suspend fun submitHash(transactions: List, transactionHashes: List) { transactions .zip(transactionHashes) .forEach { (transaction, transactionHash) -> @@ -255,7 +241,6 @@ internal class StakingTransactionSender @AssistedInject constructor( SubmitHashData( transactionId = transaction.id, transactionHash = transactionHash, - pendingTransaction = pendingTransaction, ), ) .onLeft { error -> @@ -271,9 +256,6 @@ internal class StakingTransactionSender @AssistedInject constructor( ) }.onRight { Timber.d("Successful hash submission") - if (transaction.type != StakingTransactionType.FREEZE_ENERGY) { - savePendingTransactionUseCase.invoke(userWallet.walletId, pendingTransaction) - } } } } @@ -292,23 +274,6 @@ internal class StakingTransactionSender @AssistedInject constructor( ) } - private fun composeStakeTransaction(): PendingTransaction { - val state = stateController.value - - val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data - val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId) - - return PendingTransaction( - groupId = UUID.randomUUID().toString(), - token = token, - type = BalanceType.STAKED, - amount = (state.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value ?: BigDecimal.ZERO, - rawCurrencyId = cryptoCurrencyStatus.currency.id.rawCurrencyId, - validator = (state.validatorState as? StakingStates.ValidatorState.Data)?.chosenValidator, - balancesId = yieldBalance?.getBalancesUniqueId() ?: 0, - ) - } - private data class FullTransactionData( val stakeKitTransaction: StakingTransaction, val tangemTransaction: TransactionData.Compiled, diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt index 016a6a0625..dbcf70f6bb 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt @@ -47,6 +47,5 @@ internal object ConfirmationStatePreviewData { isApprovalNeeded = false, reduceAmountBy = null, pendingActions = null, - possiblePendingTransaction = null, ) } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt index a37a92cdcd..8387933908 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateInitTransformer.kt @@ -63,7 +63,6 @@ internal class SetConfirmationStateInitTransformer( reduceAmountBy = null, pendingAction = pendingAction, pendingActions = pendingActions.takeIf { isComposePendingActions }, - possiblePendingTransaction = null, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetPossiblePendingTransactionTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetPossiblePendingTransactionTransformer.kt deleted file mode 100644 index 26508e49b5..0000000000 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetPossiblePendingTransactionTransformer.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.tangem.features.staking.impl.presentation.state.transformers - -import com.tangem.domain.staking.model.PendingTransaction -import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.staking.model.stakekit.YieldBalance -import com.tangem.domain.tokens.model.CryptoCurrencyStatus -import com.tangem.features.staking.impl.presentation.state.BalanceState -import com.tangem.features.staking.impl.presentation.state.StakingStates -import com.tangem.features.staking.impl.presentation.state.StakingUiState -import com.tangem.utils.transformer.Transformer - -internal class SetPossiblePendingTransactionTransformer( - private val yield: Yield, - private val balanceState: BalanceState, - private val cryptoCurrencyStatus: CryptoCurrencyStatus, -) : Transformer { - - override fun transform(prevState: StakingUiState): StakingUiState { - val possibleConfirmationState = - prevState.confirmationState as? StakingStates.ConfirmationState.Data ?: return prevState - val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data - - val balancesId = yieldBalance?.getBalancesUniqueId() ?: 0 - val token = yield.getCurrentToken(cryptoCurrencyStatus.currency.id.rawCurrencyId) - - return prevState.copy( - confirmationState = possibleConfirmationState.copy( - possiblePendingTransaction = PendingTransaction( - groupId = balanceState.groupId, - token = token, - type = balanceState.type, - amount = balanceState.cryptoDecimal, - rawCurrencyId = balanceState.rawCurrencyId, - validator = balanceState.validator, - balancesId = balancesId, - ), - ), - ) - } -} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index 6639fdf7cb..6850137ee4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -25,14 +25,13 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.SendFeedbackEmailUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType -import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase -import com.tangem.domain.staking.IsAnyTokenStakedUseCase -import com.tangem.domain.staking.IsApproveNeededUseCase +import com.tangem.domain.staking.* import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.YieldBalance +import com.tangem.domain.staking.model.stakekit.action.StakingAction import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.tokens.* @@ -74,7 +73,6 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import timber.log.Timber import java.math.BigDecimal import java.util.concurrent.CopyOnWriteArrayList import javax.inject.Inject @@ -108,6 +106,7 @@ internal class StakingViewModel @Inject constructor( private val stakingBalanceUpdater: StakingBalanceUpdater.Factory, private val analyticsEventHandler: AnalyticsEventHandler, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, + private val getActionsUseCase: GetActionsUseCase, @DelayedWork private val coroutineScope: CoroutineScope, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents { @@ -131,6 +130,7 @@ internal class StakingViewModel @Inject constructor( ?: error("This screen can't be opened without `Yield`") private var cryptoCurrencyStatus: CryptoCurrencyStatus by Delegates.notNull() + private var processingActions: List = emptyList() private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null private var innerRouter: InnerStakingRouter by Delegates.notNull() @@ -141,9 +141,8 @@ internal class StakingViewModel @Inject constructor( get() { val yieldBalance = cryptoCurrencyStatus.value.yieldBalance as? YieldBalance.Data return invalidatePendingTransactionsUseCase( - userWalletId = userWallet.walletId, balanceItems = yieldBalance?.balance?.items ?: emptyList(), - balancesId = yieldBalance?.getBalancesUniqueId() ?: 0, + processingActions = processingActions, ).getOrElse { emptyList() } } @@ -153,6 +152,7 @@ internal class StakingViewModel @Inject constructor( stakingBalanceUpdater.create( cryptoCurrencyStatus, userWallet, + yield, ) } @@ -215,11 +215,6 @@ internal class StakingViewModel @Inject constructor( override fun onNextClick(balanceState: BalanceState?) { stakingStateRouter.onNextClick() - if (balanceState != null && (isInitState() || isAssentState())) { - stateController.update( - SetPossiblePendingTransactionTransformer(yield, balanceState, cryptoCurrencyStatus), - ) - } } override fun getFee() { @@ -281,7 +276,6 @@ internal class StakingViewModel @Inject constructor( transactionsInProgress.addAll(constructedTransactions) }, onConstructError = { error -> - Timber.e(error.toString()) analyticsEventHandler.send( StakingAnalyticsEvents.StakekitError( token = value.cryptoCurrencySymbol, @@ -297,7 +291,6 @@ internal class StakingViewModel @Inject constructor( stateController.update(SetConfirmationStateCompletedTransformer(txUrl)) }, onSendError = { error -> - Timber.e(error.toString()) analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol)) stakingEventFactory.createSendTransactionErrorAlert(error) stateController.update(SetConfirmationStateResetAssentTransformer) @@ -527,7 +520,6 @@ internal class StakingViewModel @Inject constructor( userWalletId = userWalletId, ).fold( ifLeft = { error -> - Timber.e(error.toString()) analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol)) stateController.update( SetConfirmationStateAssentApprovalTransformer( @@ -549,7 +541,6 @@ internal class StakingViewModel @Inject constructor( network = tokenCryptoCurrency.network, ).fold( ifLeft = { error -> - Timber.e(error.toString()) analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencySymbol)) stateController.update( SetConfirmationStateAssentApprovalTransformer( @@ -760,7 +751,6 @@ internal class StakingViewModel @Inject constructor( userWallet = wallet }, ifLeft = { - Timber.e(it.toString()) stakingEventFactory.createGenericErrorAlert(it.toString()) stateController.update(SetConfirmationStateResetAssentTransformer) }, @@ -790,10 +780,10 @@ internal class StakingViewModel @Inject constructor( setupApprovalNeeded() setupIsAnyTokenStaked() checkIfSubtractAvailable() + subscribeOnActionsUpdates() subscribeOnStepChanges() }, ifLeft = { error -> - Timber.e(error.toString()) stakingEventFactory.createGenericErrorAlert(error.toString()) stateController.update(SetConfirmationStateResetAssentTransformer) }, @@ -831,18 +821,7 @@ internal class StakingViewModel @Inject constructor( .onEach { when { isInitState() -> { - stateController.updateAll( - SetInitialDataStateTransformer( - clickIntents = this@StakingViewModel, - yield = yield, - isAnyTokenStaked = isAnyTokenStaked, - cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, - userWalletProvider = Provider { userWallet }, - appCurrencyProvider = Provider { appCurrency }, - balancesToShowProvider = Provider { balancesToShow }, - ), - SetConfirmationStateEmptyTransformer, - ) + updateInitialData() onRefreshSwipe(isRefreshing = false) } isAssentState() -> { @@ -862,6 +841,40 @@ internal class StakingViewModel @Inject constructor( .launchIn(viewModelScope) } + private fun subscribeOnActionsUpdates() { + getActionsUseCase( + userWalletId = userWalletId, + cryptoCurrencyId = cryptoCurrencyId, + ) + .conflate() + .distinctUntilChanged() + .onEach { result -> + result.getOrNull()?.let { actions -> + processingActions = actions + if (isInitState()) { + updateInitialData() + } + } + } + .flowOn(dispatchers.main) + .launchIn(viewModelScope) + } + + private fun updateInitialData() { + stateController.updateAll( + SetInitialDataStateTransformer( + clickIntents = this@StakingViewModel, + yield = yield, + isAnyTokenStaked = isAnyTokenStaked, + cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, + userWalletProvider = Provider { userWallet }, + appCurrencyProvider = Provider { appCurrency }, + balancesToShowProvider = Provider { balancesToShow }, + ), + SetConfirmationStateEmptyTransformer, + ) + } + private fun prepareForConfirmation( pendingActions: ImmutableList = persistentListOf(), pendingAction: PendingAction? = pendingActions.firstOrNull(), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 1b91c07eab..a63d88e92a 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -32,7 +32,6 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.ShouldShowSwapPromoTokenUseCase -import com.tangem.domain.staking.GetStakingPendingTransactionsUseCase import com.tangem.domain.staking.GetStakingAvailabilityUseCase import com.tangem.domain.staking.GetStakingEntryInfoUseCase import com.tangem.domain.staking.GetStakingIntegrationIdUseCase @@ -121,7 +120,6 @@ internal class TokenDetailsViewModel @Inject constructor( private val swapTransactionStatusStore: SwapTransactionStatusStore, private val isDemoCardUseCase: IsDemoCardUseCase, private val associateAssetUseCase: AssociateAssetUseCase, - private val getStakingPendingTransactionsUseCase: GetStakingPendingTransactionsUseCase, private val reduxStateHolder: ReduxStateHolder, private val analyticsEventsHandler: AnalyticsEventHandler, private val vibratorHapticManager: VibratorHapticManager, @@ -151,8 +149,7 @@ internal class TokenDetailsViewModel @Inject constructor( private val warningsJobHolder = JobHolder() private val swapTxJobHolder = JobHolder() private val selectedAppCurrencyFlow: StateFlow = createSelectedAppCurrencyFlow() - private val stakingPendingBalances: List - get() = getStakingPendingTransactionsUseCase(userWalletId).getOrElse { emptyList() } + private val stakingPendingBalances: List = emptyList() // TODO staking private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null private var stakingEntryInfo: StakingEntryInfo? = null