From 320b3deffbfc84955c39e61242ce5acd626f57f3 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 15 Jul 2024 12:46:32 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 14 +++++ .../tap/di/domain/StakingDomainModule.kt | 18 ++++++ .../local/preferences/PreferencesKeys.kt | 2 + .../local/token/DefaultUserTokensStore.kt | 15 ----- data/staking/build.gradle.kts | 4 +- .../data/staking/DefaultStakingRepository.kt | 61 +++++++++++++++++-- .../data/staking/di/StakingDataModule.kt | 3 + .../staking/SaveUnsubmittedHashUseCase.kt | 22 +++++++ .../staking/SendUnsubmittedHashesUseCase.kt | 16 +++++ .../domain/staking/SubmitHashUseCase.kt | 19 ++++++ .../model/UnsubmittedTransactionMetadata.kt | 6 ++ .../staking/repositories/StakingRepository.kt | 6 ++ .../repository/MockStakingRepository.kt | 9 +++ .../viewmodel/StakingViewModel.kt | 26 +++++++- 14 files changed, 199 insertions(+), 22 deletions(-) delete mode 100644 core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensStore.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/SaveUnsubmittedHashUseCase.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/SendUnsubmittedHashesUseCase.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/model/UnsubmittedTransactionMetadata.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index edd4f38350..2708e92fb4 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -47,6 +47,7 @@ import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardRepository import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.settings.repositories.SettingsRepository +import com.tangem.domain.staking.SendUnsubmittedHashesUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager @@ -88,6 +89,7 @@ import com.tangem.wallet.databinding.ActivityMainBinding import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.* import kotlinx.coroutines.flow.* +import timber.log.Timber import javax.inject.Inject import kotlin.coroutines.CoroutineContext @@ -149,6 +151,9 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var settingsRepository: SettingsRepository + @Inject + lateinit var sendUnsubmittedHashesUseCase: SendUnsubmittedHashesUseCase + @Inject lateinit var getPolkadotCheckHasResetUseCase: GetPolkadotCheckHasResetUseCase @@ -225,6 +230,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac observeStateUpdates() observePolkadotAccountHealthCheck() + sendStakingUnsubmittedHashes() if (intent != null) { deepLinksRegistry.launch(intent) @@ -583,4 +589,12 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } } } + + private fun sendStakingUnsubmittedHashes() { + lifecycleScope.launch { + sendUnsubmittedHashesUseCase.invoke() + .onRight { Timber.d("Submitting hashes succeeded") } + .onLeft { Timber.e(it) } + } + } } \ No newline at end of file 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 12aad7794a..49ee51f726 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 @@ -61,4 +61,22 @@ internal object StakingDomainModule { fun provideCreateEnterActionUseCase(stakingRepository: StakingRepository): InitializeStakingProcessUseCase { return InitializeStakingProcessUseCase(stakingRepository) } + + @Provides + @Singleton + fun provideSubmitHashUseCase(stakingRepository: StakingRepository): SubmitHashUseCase { + return SubmitHashUseCase(stakingRepository) + } + + @Provides + @Singleton + fun provideSaveUnsubmittedHashUseCase(stakingRepository: StakingRepository): SaveUnsubmittedHashUseCase { + return SaveUnsubmittedHashUseCase(stakingRepository) + } + + @Provides + @Singleton + fun provideSendUnsubmittedHashesUseCase(stakingRepository: StakingRepository): SendUnsubmittedHashesUseCase { + return SendUnsubmittedHashesUseCase(stakingRepository) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index 722573fc37..c517d9fdec 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -85,6 +85,8 @@ object PreferencesKeys { val IS_WALLET_NAMES_MIGRATION_DONE_KEY by lazy { booleanPreferencesKey(name = "isWalletNamesMigrationDone") } + val UNSUBMITTED_TRANSACTIONS_KEY by lazy { stringPreferencesKey(name = "unsubmittedTransactions") } + val IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY by lazy { booleanPreferencesKey(name = "isWalletSwapPromoOkxShown") } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensStore.kt deleted file mode 100644 index 23cea085d6..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultUserTokensStore.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.tangem.datasource.local.token - -import com.tangem.datasource.api.tangemTech.models.UserTokensResponse -import com.tangem.datasource.local.datastore.core.StringKeyDataStore -import com.tangem.datasource.local.datastore.core.StringKeyDataStoreDecorator -import com.tangem.domain.wallets.models.UserWalletId - -internal class DefaultUserTokensStore( - dataStore: StringKeyDataStore, -) : UserTokensStore, StringKeyDataStoreDecorator(dataStore) { - - override fun provideStringKey(key: UserWalletId): String { - return "user_tokens_${key.stringValue}" - } -} \ No newline at end of file diff --git a/data/staking/build.gradle.kts b/data/staking/build.gradle.kts index 009048e07e..0a1688dee1 100644 --- a/data/staking/build.gradle.kts +++ b/data/staking/build.gradle.kts @@ -11,7 +11,6 @@ android { } dependencies { - /** Core modules */ implementation(projects.core.datasource) implementation(projects.core.utils) @@ -33,14 +32,17 @@ dependencies { // endregion // region Others dependencies + implementation(deps.androidx.datastore) implementation(deps.jodatime) implementation(deps.kotlin.coroutines) implementation(deps.moshi) implementation(deps.moshi.kotlin) + implementation(projects.libs.blockchainSdk) implementation(deps.tangem.blockchain) { exclude(module = "joda-time") } + // endregion } 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 edb32a111b..c9c22d3596 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 @@ -14,11 +14,11 @@ import com.tangem.data.staking.converters.transaction.StakingTransactionStatusCo import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.stakekit.StakeKitApi -import com.tangem.datasource.api.stakekit.models.request.Address -import com.tangem.datasource.api.stakekit.models.request.ConstructTransactionRequestBody -import com.tangem.datasource.api.stakekit.models.request.EnterActionRequestBody -import com.tangem.datasource.api.stakekit.models.request.YieldBalanceRequestBody +import com.tangem.datasource.api.stakekit.models.request.* import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectListSync import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.core.lce.LceFlow @@ -33,14 +33,16 @@ import com.tangem.domain.wallets.models.UserWalletId import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.toFormattedString +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.math.BigDecimal -@Suppress("LargeClass") +@Suppress("LargeClass", "LongParameterList") internal class DefaultStakingRepository( private val stakeKitApi: StakeKitApi, + private val appPreferencesStore: AppPreferencesStore, private val stakingYieldsStore: StakingYieldsStore, private val stakingBalanceStore: StakingBalanceStore, private val cacheRegistry: CacheRegistry, @@ -372,6 +374,55 @@ internal class DefaultStakingRepository( } } + override suspend fun submitHash(transactionId: String, transactionHash: String) { + withContext(dispatchers.io) { + stakeKitApi.submitTransactionHash( + transactionId = transactionId, + body = SubmitTransactionHashRequestBody( + hash = transactionHash, + ), + ) + } + } + + override suspend fun storeUnsubmittedHash(unsubmittedTransactionMetadata: UnsubmittedTransactionMetadata) { + withContext(dispatchers.io) { + appPreferencesStore.editData { preferences -> + val savedTransactions = preferences.getObjectListOrDefault( + key = PreferencesKeys.UNSUBMITTED_TRANSACTIONS_KEY, + default = emptyList(), + ) + + preferences.setObjectList( + key = PreferencesKeys.UNSUBMITTED_TRANSACTIONS_KEY, + value = savedTransactions + unsubmittedTransactionMetadata, + ) + } + } + } + + override suspend fun sendUnsubmittedHashes() { + withContext(NonCancellable) { + val savedTransactions = appPreferencesStore.getObjectListSync( + key = PreferencesKeys.UNSUBMITTED_TRANSACTIONS_KEY, + ) + + savedTransactions.forEach { + stakeKitApi.submitTransactionHash( + transactionId = it.transactionId, + body = SubmitTransactionHashRequestBody(hash = it.transactionHash), + ) + } + + appPreferencesStore.editData { mutablePreferences -> + mutablePreferences.setObjectList( + key = PreferencesKeys.UNSUBMITTED_TRANSACTIONS_KEY, + value = emptyList(), + ) + } + } + } + private fun findPrefetchedYield(yields: List, currencyId: String, symbol: String): Yield? { return yields.find { it.token.coinGeckoId == currencyId && it.token.symbol == symbol } } 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 b855fe3386..87b0102d21 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 @@ -3,6 +3,7 @@ package com.tangem.data.staking.di import com.tangem.data.common.cache.CacheRegistry import com.tangem.data.staking.DefaultStakingRepository import com.tangem.datasource.api.stakekit.StakeKitApi +import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.token.StakingBalanceStore import com.tangem.datasource.local.token.StakingYieldsStore import com.tangem.domain.staking.repositories.StakingRepository @@ -22,6 +23,7 @@ internal object StakingDataModule { @Singleton fun provideStakingRepository( stakeKitApi: StakeKitApi, + appPreferencesStore: AppPreferencesStore, stakingTokenStore: StakingYieldsStore, stakingBalanceStore: StakingBalanceStore, dispatchers: CoroutineDispatcherProvider, @@ -30,6 +32,7 @@ internal object StakingDataModule { ): StakingRepository { return DefaultStakingRepository( stakeKitApi = stakeKitApi, + appPreferencesStore = appPreferencesStore, stakingYieldsStore = stakingTokenStore, stakingBalanceStore = stakingBalanceStore, dispatchers = dispatchers, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SaveUnsubmittedHashUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SaveUnsubmittedHashUseCase.kt new file mode 100644 index 0000000000..009469c351 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SaveUnsubmittedHashUseCase.kt @@ -0,0 +1,22 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.UnsubmittedTransactionMetadata +import com.tangem.domain.staking.repositories.StakingRepository + +/** + * Use case for saving hash that failed to submit during staking confirmation + */ +class SaveUnsubmittedHashUseCase(private val stakingRepository: StakingRepository) { + + suspend operator fun invoke(transactionId: String, transactionHash: String): Either { + return Either.catch { + stakingRepository.storeUnsubmittedHash( + unsubmittedTransactionMetadata = UnsubmittedTransactionMetadata( + transactionId = transactionId, + transactionHash = transactionHash, + ), + ) + } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SendUnsubmittedHashesUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SendUnsubmittedHashesUseCase.kt new file mode 100644 index 0000000000..5c2fbd531d --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SendUnsubmittedHashesUseCase.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.repositories.StakingRepository + +/** + * Use case for commiting hashes that failed to submit during staking confirmation + */ +class SendUnsubmittedHashesUseCase(private val stakingRepository: StakingRepository) { + + suspend operator fun invoke(): Either { + return Either.catch { + stakingRepository.sendUnsubmittedHashes() + } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt new file mode 100644 index 0000000000..091d9a2f6b --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt @@ -0,0 +1,19 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.repositories.StakingRepository + +/** + * Use case for submitting transaction hash to stakekit + */ +class SubmitHashUseCase(private val stakingRepository: StakingRepository) { + + suspend fun submitHash(transactionId: String, transactionHash: String): Either { + return Either.catch { + stakingRepository.submitHash( + transactionId = transactionId, + transactionHash = transactionHash, + ) + } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/UnsubmittedTransactionMetadata.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/UnsubmittedTransactionMetadata.kt new file mode 100644 index 0000000000..e57067b382 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/UnsubmittedTransactionMetadata.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.staking.model + +data class UnsubmittedTransactionMetadata( + val transactionHash: String, + val transactionId: String, +) \ 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 854aa92a93..8b74784341 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 @@ -69,4 +69,10 @@ interface StakingRepository { ): EnterAction suspend fun constructTransaction(transactionId: String): StakingTransaction + + suspend fun submitHash(transactionId: String, transactionHash: String) + + suspend fun storeUnsubmittedHash(unsubmittedTransactionMetadata: UnsubmittedTransactionMetadata) + + suspend fun sendUnsubmittedHashes() } \ 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 e6436b5f62..074c07f027 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 @@ -200,4 +200,13 @@ class MockStakingRepository : StakingRepository { ledgerHwAppId = null, isMessage = false, ) + + override suspend fun submitHash(transactionId: String, transactionHash: String) { + } + + override suspend fun storeUnsubmittedHash(unsubmittedTransactionMetadata: UnsubmittedTransactionMetadata) { + } + + override suspend fun sendUnsubmittedHashes() { + } } \ 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 61d9860dbb..77a9005b01 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 @@ -15,6 +15,8 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.staking.InitializeStakingProcessUseCase +import com.tangem.domain.staking.SaveUnsubmittedHashUseCase +import com.tangem.domain.staking.SubmitHashUseCase import com.tangem.domain.staking.model.Yield import com.tangem.domain.staking.model.transaction.StakingTransaction import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase @@ -58,6 +60,8 @@ internal class StakingViewModel @Inject constructor( private val initializeStakingProcessUseCase: InitializeStakingProcessUseCase, private val sendTransactionUseCase: SendTransactionUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, + private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase, + private val submitHashUseCase: SubmitHashUseCase, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents { @@ -286,12 +290,17 @@ internal class StakingViewModel @Inject constructor( ) }, ifRight = { txHash -> - val gasEstimate = stakingTransaction?.gasEstimate ?: return@fold + val transaction = stakingTransaction ?: return@fold + + submitHash(transaction.id, txHash) val txUrl = getExplorerTransactionUrlUseCase( txHash = txHash, networkId = cryptoCurrencyStatus.currency.network.id, ).getOrElse { "" } + + val gasEstimate = transaction.gasEstimate ?: error("No gas for transaction") + stateController.update( SetConfirmationStateCompletedTransformer( appCurrencyProvider = Provider { appCurrency }, @@ -304,6 +313,21 @@ internal class StakingViewModel @Inject constructor( ) } + private suspend fun submitHash(transactionId: String, transactionHash: String) { + submitHashUseCase.submitHash( + transactionId = transactionId, + transactionHash = transactionHash, + ) + .onLeft { + saveUnsubmittedHashUseCase.invoke( + transactionId = transactionId, + transactionHash = transactionHash, + ) + }.onRight { + Timber.d("Successful hash submission") + } + } + private fun isAssentState(): Boolean { return value.currentStep == StakingStep.Confirmation && (value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState ==