Updated on 2026-08-14

This commit is contained in:
Tangem 2024-10-25 17:49:53 +03:00
parent dab5fb51b6
commit 967d5520a1
37 changed files with 452 additions and 408 deletions

View file

@ -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,
)
}

View file

@ -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<List<BalanceDTO>>
@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<GetActionsResponse>
@POST("actions/enter")
suspend fun createEnterAction(@Body body: ActionRequestBody): ApiResponse<EnterActionResponse>
suspend fun createEnterAction(@Body body: ActionRequestBody): ApiResponse<ActionDTO>
@POST("actions/exit")
suspend fun createExitAction(@Body body: ActionRequestBody): ApiResponse<EnterActionResponse>
suspend fun createExitAction(@Body body: ActionRequestBody): ApiResponse<ActionDTO>
@POST("actions/pending")
suspend fun createPendingAction(@Body body: PendingActionRequestBody): ApiResponse<EnterActionResponse>
suspend fun createPendingAction(@Body body: PendingActionRequestBody): ApiResponse<ActionDTO>
@POST("actions/enter/estimate-gas")
suspend fun estimateGasOnEnter(@Body body: ActionRequestBody): ApiResponse<StakingGasEstimateDTO>

View file

@ -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")

View file

@ -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<ActionDTO>,
@Json(name = "hasNextPage")
val hasNextPage: Boolean,
@Json(name = "limit")
val limit: Int,
@Json(name = "page")
val page: Int,
)

View file

@ -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())
}
}

View file

@ -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()
}
}

View file

@ -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<List<StakingAction>>,
) : StakingActionsStore {
private val mutex = Mutex()
override fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow<List<StakingAction>> {
return dataStore.get(composeKey(userWalletId, cryptoCurrencyId))
}
override suspend fun store(
userWalletId: UserWalletId,
cryptoCurrencyId: CryptoCurrency.ID,
items: List<StakingAction>,
) {
mutex.withLock {
dataStore.store(composeKey(userWalletId, cryptoCurrencyId), items)
}
}
private fun composeKey(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): String {
return userWalletId.stringValue + cryptoCurrencyId.value
}
}

View file

@ -4,13 +4,13 @@ import com.tangem.datasource.api.stakekit.models.response.model.YieldDTO
internal class DefaultStakingYieldsStore : StakingYieldsStore {
private var yields = mutableListOf<YieldDTO>()
private var yields = listOf<YieldDTO>()
override fun get(): List<YieldDTO> {
return yields
}
override fun store(items: List<YieldDTO>) {
yields = items.toMutableList()
yields = items
}
}

View file

@ -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<List<StakingAction>>
suspend fun store(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID, items: List<StakingAction>)
}

View file

@ -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<StakingAction>,
) {
withContext(dispatchers.io) {
stakingActionsStore.store(userWalletId, cryptoCurrencyId, actions)
}
}
override fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow<List<StakingAction>> {
return stakingActionsStore.get(userWalletId, cryptoCurrencyId)
}
}

View file

@ -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<UserWalletId, CopyOnWriteArrayList<PendingTransaction>>()
override fun saveTransaction(userWalletId: UserWalletId, transaction: PendingTransaction) {
val transactions = pendingTransactionsMap.computeIfAbsent(userWalletId) { CopyOnWriteArrayList() }
transactions.add(transaction)
}
override fun removeTransactions(userWalletId: UserWalletId, transactions: Set<PendingTransaction>) {
pendingTransactionsMap[userWalletId]?.removeAll(transactions)
}
override fun getTransactionsWithBalanceItems(
userWalletId: UserWalletId,
): List<Pair<PendingTransaction, BalanceItem>> {
return pendingTransactionsMap[userWalletId]?.mapNotNull { pendingTransaction: PendingTransaction ->
PendingTransactionItemConverter.convert(pendingTransaction)?.let { balanceItem ->
pendingTransaction to balanceItem
}
} ?: emptyList()
}
}

View file

@ -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<StakingAction> {
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)

View file

@ -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<PendingTransaction, BalanceItem?> {
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,
)
}
}

View file

@ -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<StakingActionStatusDTO, StakingActionStatus> {
class ActionStatusConverter : Converter<StakingActionStatusDTO, StakingActionStatus> {
override fun convert(value: StakingActionStatusDTO): StakingActionStatus {
return when (value) {
StakingActionStatusDTO.CANCELED -> StakingActionStatus.CANCELED
@ -16,4 +17,16 @@ class ActionStatusConverter : Converter<StakingActionStatusDTO, StakingActionSta
else -> 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
}
}
}

View file

@ -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<EnterActionResponse, StakingAction> {
) : Converter<ActionDTO, StakingAction> {
override fun convert(value: EnterActionResponse): StakingAction {
override fun convert(value: ActionDTO): StakingAction {
return StakingAction(
id = value.id,
integrationId = value.integrationId,

View file

@ -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

View file

@ -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,
)

View file

@ -3,5 +3,4 @@ package com.tangem.domain.staking.model
data class SubmitHashData(
val transactionHash: String,
val transactionId: String,
val pendingTransaction: PendingTransaction,
)

View file

@ -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()

View file

@ -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<StakingError, Unit> {
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) }
}
}

View file

@ -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<StakingError, List<StakingAction>> {
return stakingActionRepository.get(
userWalletId = userWalletId,
cryptoCurrencyId = cryptoCurrencyId,
).map<List<StakingAction>, Either<StakingError, List<StakingAction>>> { it.right() }
.catch { emit(stakingErrorResolver.resolve(it).left()) }
}
}

View file

@ -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<StakingError, List<BalanceItem>> {
return Either.catch {
stakingPendingTransactionRepository.getTransactionsWithBalanceItems(userWalletId).map { it.second }
}.mapLeft {
stakingErrorResolver.resolve(it)
}
}
}

View file

@ -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<BalanceItem>,
balancesId: Int,
processingActions: List<StakingAction>,
): Either<StakingError, List<BalanceItem>> {
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<BalanceItem>,
newBalancesId: Int,
pendingData: List<Pair<PendingTransaction, BalanceItem>>,
): Pair<List<BalanceItem>, List<PendingTransaction>> {
val balances = realData.groupBy { BalanceIdentity(it.groupId, it.type, it.amount, it.date) }
.mapValues { it.value.toMutableList() }
.toMutableMap()
private fun mergeBalancesAndProcessingActions(
realBalances: List<BalanceItem>,
processingActions: List<StakingAction>,
): List<BalanceItem> {
val balances = realBalances.toMutableList()
val transactionsToRemove = mutableListOf<PendingTransaction>()
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<BalanceItem>, 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<BalanceItem>, 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)
}
}
}

View file

@ -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<StakingError, Unit> {
return Either.catch {
stakingPendingTransactionRepository.saveTransaction(
userWalletId = userWalletId,
transaction = transaction,
)
}.mapLeft {
stakingErrorResolver.resolve(it)
}
}
}

View file

@ -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<StakingAction>)
fun get(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow<List<StakingAction>>
}

View file

@ -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<Pair<PendingTransaction, BalanceItem>>
fun saveTransaction(userWalletId: UserWalletId, transaction: PendingTransaction)
fun removeTransactions(userWalletId: UserWalletId, transactions: Set<PendingTransaction>)
}

View file

@ -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<StakingAction>
suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,

View file

@ -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()
}

View file

@ -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<StakingAction> {
return emptyList()
}
override suspend fun fetchSingleYieldBalance(
userWalletId: UserWalletId,
cryptoCurrency: CryptoCurrency,

View file

@ -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(

View file

@ -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 {

View file

@ -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<StakingTransaction>) -> 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<FullTransactionData>,
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<StakingTransaction>,
transactionHashes: List<String>,
pendingTransaction: PendingTransaction,
) {
private suspend fun submitHash(transactions: List<StakingTransaction>, transactionHashes: List<String>) {
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,

View file

@ -47,6 +47,5 @@ internal object ConfirmationStatePreviewData {
isApprovalNeeded = false,
reduceAmountBy = null,
pendingActions = null,
possiblePendingTransaction = null,
)
}

View file

@ -63,7 +63,6 @@ internal class SetConfirmationStateInitTransformer(
reduceAmountBy = null,
pendingAction = pendingAction,
pendingActions = pendingActions.takeIf { isComposePendingActions },
possiblePendingTransaction = null,
),
)
}

View file

@ -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<StakingUiState> {
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,
),
),
)
}
}

View file

@ -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<StakingAction> = 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<PendingAction> = persistentListOf(),
pendingAction: PendingAction? = pendingActions.firstOrNull(),

View file

@ -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<AppCurrency> = createSelectedAppCurrencyFlow()
private val stakingPendingBalances: List<BalanceItem>
get() = getStakingPendingTransactionsUseCase(userWalletId).getOrElse { emptyList() }
private val stakingPendingBalances: List<BalanceItem> = emptyList() // TODO staking
private var cryptoCurrencyStatus: CryptoCurrencyStatus? = null
private var stakingEntryInfo: StakingEntryInfo? = null