From a3295ea1814d6b8aa0df079d1d36df8fbbabe897 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 May 2024 18:53:42 +0300 Subject: [PATCH 1/2] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 6 +- .../BiometricUserWalletsListManager.kt | 15 ++-- .../GeneralUserWalletsListManager.kt | 71 ++++++++++++------- .../RuntimeUserWalletsListManager.kt | 8 +-- .../tap/domain/userWalletList/utils/Mapper.kt | 12 +++- .../configs/feature_toggles_config.json | 2 +- .../wallets/legacy/UserWalletsListManager.kt | 10 +-- .../UserWalletsListManagerExtensions.kt | 2 +- .../wallets/usecase/DeleteWalletUseCase.kt | 21 +++--- .../wallet/viewmodels/WalletViewModel.kt | 17 ++--- .../viewmodels/WalletsUpdateActionResolver.kt | 35 ++------- .../intents/WalletCardClickIntents.kt | 31 +++++--- 12 files changed, 123 insertions(+), 107 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 94171ca9e9..83dd77c841 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -41,7 +41,6 @@ import com.tangem.domain.settings.repositories.SettingsRepository import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase import com.tangem.domain.wallets.legacy.UserWalletsListManager -import com.tangem.domain.wallets.legacy.asLockable import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.features.managetokens.navigation.ManageTokensUi @@ -440,10 +439,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } private fun navigateToInitialScreen(intentWhichStartedActivity: Intent?) { - val canSaveWallets = runCatching { userWalletsListManager.asLockable()?.isLockedSync } - .fold(onSuccess = { true }, onFailure = { false }) - - if (canSaveWallets && userWalletsListManager.hasUserWallets) { + if (userWalletsListManager.isLockable && userWalletsListManager.hasUserWallets) { store.dispatch( NavigationAction.NavigateTo( screen = AppScreen.Welcome, diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 99418a7aa0..96419972cb 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -12,6 +12,7 @@ import com.tangem.tap.domain.userWalletList.repository.UserWalletsKeysRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsPublicInformationRepository import com.tangem.tap.domain.userWalletList.repository.UserWalletsSensitiveInformationRepository import com.tangem.tap.domain.userWalletList.utils.encryptionKey +import com.tangem.tap.domain.userWalletList.utils.lockAll import com.tangem.tap.domain.userWalletList.utils.toUserWallets import com.tangem.tap.domain.userWalletList.utils.updateWith import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -27,6 +28,8 @@ internal class BiometricUserWalletsListManager( ) : UserWalletsListManager.Lockable { private val state = MutableStateFlow(State()) + override val isLockable: Boolean = true + override val userWallets: Flow> get() = state .mapLatest { it.userWallets } @@ -78,11 +81,13 @@ internal class BiometricUserWalletsListManager( } override fun lock() { - state.update { State() } - } - - override fun isLockable(): Boolean { - return true + state.update { prevState -> + prevState.copy( + encryptionKeys = emptyList(), + userWallets = prevState.userWallets.lockAll(), + isLocked = true, + ) + } } override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt index 762ba82f54..abec2ac6e8 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/GeneralUserWalletsListManager.kt @@ -33,31 +33,49 @@ internal class GeneralUserWalletsListManager( ) : UserWalletsListManager.Lockable { private val applicationScope = CoroutineScope(dispatchers.io) - private val implementation = MutableStateFlow(runtimeUserWalletsListManager) + private val implementation: MutableStateFlow = MutableStateFlow(value = null) + + private val requireImplementation: UserWalletsListManager + get() = requireNotNull(implementation.value) { + "UserWalletsListManager is not initialized" + } init { subscribeOnCurrentManager() } + override val isLockable: Boolean + get() = requireImplementation.isLockable + override val userWallets: Flow> - get() = implementation.flatMapLatest { it.userWallets } + get() = implementation.transformLatest { impl -> + if (impl != null && impl.hasUserWallets) { + emitAll(impl.userWallets) + } + } override val selectedUserWallet: Flow - get() = implementation.flatMapLatest { it.selectedUserWallet } + get() = implementation.transformLatest { impl -> + if (impl != null && impl.hasUserWallets) { + emitAll(impl.selectedUserWallet) + } + } override val selectedUserWalletSync: UserWallet? - get() = implementation.value.selectedUserWalletSync + get() = requireImplementation.selectedUserWalletSync override val hasUserWallets: Boolean - get() = implementation.value.hasUserWallets + get() = requireImplementation.hasUserWallets override val walletsCount: Int - get() = implementation.value.walletsCount + get() = requireImplementation.walletsCount override val isLocked: Flow - get() = implementation.flatMapLatest { - if (it is UserWalletsListManager.Lockable) { - it.isLocked + get() = implementation.transformLatest { impl -> + if (impl == null) return@transformLatest + + if (impl is UserWalletsListManager.Lockable) { + emitAll(impl.isLocked) } else { error("RuntimeUserWalletsListManager is not lockable") } @@ -65,43 +83,45 @@ internal class GeneralUserWalletsListManager( override val isLockedSync: Boolean get() { - val implementation = implementation.value - return if (implementation is UserWalletsListManager.Lockable) { - implementation.isLockedSync + val impl = requireImplementation + + return if (impl is UserWalletsListManager.Lockable) { + impl.isLockedSync } else { error("RuntimeUserWalletsListManager is not lockable") } } override suspend fun select(userWalletId: UserWalletId): CompletionResult { - return implementation.value.select(userWalletId) + return requireImplementation.select(userWalletId) } override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult { - return implementation.value.save(userWallet, canOverride) + return requireImplementation.save(userWallet, canOverride) } override suspend fun update( userWalletId: UserWalletId, update: suspend (UserWallet) -> UserWallet, ): CompletionResult { - return implementation.value.update(userWalletId, update) + return requireImplementation.update(userWalletId, update) } override suspend fun delete(userWalletIds: List): CompletionResult { - return implementation.value.delete(userWalletIds) + return requireImplementation.delete(userWalletIds) } override suspend fun clear(): CompletionResult { - return implementation.value.clear() + return requireImplementation.clear() } override suspend fun get(userWalletId: UserWalletId): CompletionResult { - return implementation.value.get(userWalletId) + return requireImplementation.get(userWalletId) } override suspend fun unlock(type: UserWalletsListManager.Lockable.UnlockType): CompletionResult { - val implementation = implementation.value + val implementation = requireImplementation + return if (implementation is UserWalletsListManager.Lockable) { implementation.unlock(type) } else { @@ -110,7 +130,8 @@ internal class GeneralUserWalletsListManager( } override fun lock() { - val implementation = implementation.value + val implementation = requireImplementation + return if (implementation is UserWalletsListManager.Lockable) { implementation.lock() } else { @@ -118,10 +139,6 @@ internal class GeneralUserWalletsListManager( } } - override fun isLockable(): Boolean { - return implementation.value.isLockable() - } - private fun subscribeOnCurrentManager() { appPreferencesStore.get(key = PreferencesKeys.SAVE_USER_WALLETS_KEY, default = false) .distinctUntilChanged() @@ -144,17 +161,17 @@ internal class GeneralUserWalletsListManager( destinationManager = possibleManager, ) - previousManager.clear() + previousManager?.clear() } .flowOn(dispatchers.io) .launchIn(applicationScope) } private suspend fun copySelectedUserWallet( - sourceManager: UserWalletsListManager, + sourceManager: UserWalletsListManager?, destinationManager: UserWalletsListManager, ): UserWalletsListManager { - sourceManager.selectedUserWalletSync?.let { selectedWallet -> + sourceManager?.selectedUserWalletSync?.let { selectedWallet -> destinationManager.save(selectedWallet, canOverride = true) } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt index 31ef3366dd..ca9a5bd68e 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/RuntimeUserWalletsListManager.kt @@ -13,6 +13,8 @@ import kotlinx.coroutines.flow.* internal class RuntimeUserWalletsListManager : UserWalletsListManager { private val state = MutableStateFlow(State()) + override val isLockable: Boolean = false + override val userWallets: Flow> get() = state .mapLatest { listOfNotNull(it.userWallet) } @@ -34,7 +36,7 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { * only 1 wallet stored in runtime implementation */ override val walletsCount: Int - get() = 1 + get() = if (hasUserWallets) 1 else 0 override suspend fun select(userWalletId: UserWalletId): CompletionResult = catching { state.value.userWallet @@ -85,10 +87,6 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager { state.value.userWallet ?: walletNotFound() } - override fun isLockable(): Boolean { - return false - } - private fun saveInternal(userWallet: UserWallet): CompletionResult = catching { state.update { prevState -> prevState.copy( diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt index 451e717e9e..7b869eb69f 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/utils/Mapper.kt @@ -61,4 +61,14 @@ internal fun List.updateWith( ?: wallet } } -} \ No newline at end of file +} + +internal fun List.lockAll(): List = map(UserWallet::lock) + +internal fun UserWallet.lock(): UserWallet = copy( + scanResponse = scanResponse.copy( + card = scanResponse.card.copy( + wallets = emptyList(), + ), + ), +) \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 7b611616d8..2bb2538503 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -25,7 +25,7 @@ }, { "name": "TOKEN_LIST_LCE_ENABLED", - "version": "5.10.0" + "version": "5.11.0" }, { "name": "CARDANO_TOKENS_SUPPORT_ENABLED", diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt index 213d780d07..e51ef51dc7 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManager.kt @@ -7,6 +7,11 @@ import kotlinx.coroutines.flow.Flow interface UserWalletsListManager { + /** + * Indicates that the [UserWalletsListManager] is [UserWalletsListManager.Lockable] + * */ + val isLockable: Boolean + /** [Flow] with all saved [UserWallet]s updates */ val userWallets: Flow> @@ -84,11 +89,6 @@ interface UserWalletsListManager { */ suspend fun get(userWalletId: UserWalletId): CompletionResult - /** - * Indicates that the [UserWalletsListManager] supports [UserWalletsListManager.Lockable] - * */ - fun isLockable(): Boolean - interface Lockable : UserWalletsListManager { /** diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt index a6bdbde07f..9570eaa90d 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/legacy/UserWalletsListManagerExtensions.kt @@ -49,7 +49,7 @@ suspend fun UserWalletsListManager.unlockIfLockable(type: UnlockType = UnlockTyp * [UserWalletsListManager.Lockable] otherwise * */ fun UserWalletsListManager.asLockable(): UserWalletsListManager.Lockable? { - if (this.isLockable()) { + if (this.isLockable) { return this as? UserWalletsListManager.Lockable } return null diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt index 59084184cc..b4db11932f 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/DeleteWalletUseCase.kt @@ -1,17 +1,14 @@ package com.tangem.domain.wallets.usecase import arrow.core.Either -import arrow.core.left import arrow.core.raise.either -import arrow.core.right import com.tangem.common.doOnFailure -import com.tangem.common.doOnSuccess import com.tangem.domain.wallets.legacy.UserWalletsListManager import com.tangem.domain.wallets.models.DeleteWalletError import com.tangem.domain.wallets.models.UserWalletId /** - * Use case for updating user wallet + * Use case for deleting user wallet * * @property userWalletsListManager user wallets list manager * @@ -19,13 +16,21 @@ import com.tangem.domain.wallets.models.UserWalletId */ class DeleteWalletUseCase(private val userWalletsListManager: UserWalletsListManager) { - suspend operator fun invoke(userWalletId: UserWalletId): Either { + /** + * Deletes user wallet with provided ID. + * + * @param userWalletId ID of user wallet to be deleted. + * + * @return [Either] with [DeleteWalletError] or [Boolean] which indicates that there are still saved wallets. + * */ + suspend operator fun invoke(userWalletId: UserWalletId): Either { return either { userWalletsListManager.delete(userWalletIds = listOf(userWalletId)) - .doOnSuccess { return Unit.right() } - .doOnFailure { return DeleteWalletError.UnableToDelete.left() } + .doOnFailure { + raise(DeleteWalletError.UnableToDelete) + } - return Unit.right() + userWalletsListManager.hasUserWallets } } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 775bb89a39..d7865401e0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.navigation.AppScreen import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase import com.tangem.domain.settings.CanUseBiometryUseCase import com.tangem.domain.settings.IsWalletsScrollPreviewEnabled @@ -32,10 +31,11 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.JobHolder import com.tangem.utils.coroutines.saveIn import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.* +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") @@ -197,9 +197,9 @@ internal class WalletViewModel @Inject constructor( is WalletsUpdateActionResolver.Action.UpdateWalletName -> { stateHolder.update(transformer = RenameWalletTransformer(action.selectedWalletId, action.name)) } - is WalletsUpdateActionResolver.Action.NoAccessibleWallets -> closeScreen(screen = AppScreen.Welcome) - is WalletsUpdateActionResolver.Action.NoWallets -> closeScreen(screen = AppScreen.Home) - is WalletsUpdateActionResolver.Action.Unknown -> Unit + is WalletsUpdateActionResolver.Action.Unknown -> { + Timber.w("Unable to perfom action: $action") + } } } @@ -313,13 +313,6 @@ internal class WalletViewModel @Inject constructor( ) } - private fun closeScreen(screen: AppScreen) { - if (!screenLifecycleProvider.isBackgroundState.value) { - stateHolder.clear() - router.popBackStack(screen = screen) - } - } - private fun scrollToWallet(index: Int, onConsume: () -> Unit = {}) { stateHolder.update( ScrollToWalletTransformer( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt index 94b310fd4a..737fb1a107 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletsUpdateActionResolver.kt @@ -1,5 +1,6 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels +import arrow.core.getOrElse import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase @@ -23,16 +24,14 @@ internal class WalletsUpdateActionResolver @Inject constructor( ) { fun resolve(wallets: List, currentState: WalletScreenState): Action { - val selectedWallet = wallets.getSelectedWallet() + val selectedWallet = getSelectedWalletSyncUseCase().getOrElse { + error("Unable to find selected wallet: $it") + } - val action = if (selectedWallet == null) { - createNoSelectedWalletAction(wallets) + val action = if (isFirstInitialization(currentState)) { + createInitializeWalletsAction(wallets, selectedWallet) } else { - if (isFirstInitialization(currentState)) { - createInitializeWalletsAction(wallets, selectedWallet) - } else { - getUpdateContentAction(currentState, wallets, selectedWallet) - } + getUpdateContentAction(currentState, wallets, selectedWallet) } Timber.d("Resolved action: $action") @@ -40,22 +39,6 @@ internal class WalletsUpdateActionResolver @Inject constructor( return action } - private fun List.getSelectedWallet(): UserWallet? { - return when { - isEmpty() -> null - size == 1 -> if (first().isLocked) null else first() - else -> getSelectedWalletSyncUseCase().fold(ifLeft = { null }, ifRight = { it }) - } - } - - private fun createNoSelectedWalletAction(wallets: List): Action { - return when { - wallets.isEmpty() -> Action.NoWallets - wallets.all(UserWallet::isLocked) -> Action.NoAccessibleWallets - else -> Action.Unknown - } - } - private fun isFirstInitialization(state: WalletScreenState): Boolean { return state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX } @@ -289,10 +272,6 @@ internal class WalletsUpdateActionResolver @Inject constructor( } } - data object NoAccessibleWallets : Action() - - data object NoWallets : Action() - data object Unknown : Action() } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt index 10d52a0047..d2208db001 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCardClickIntents.kt @@ -1,7 +1,11 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents +import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.card.DeleteSavedAccessCodesUseCase +import com.tangem.core.navigation.AppScreen +import com.tangem.core.navigation.NavigationAction +import com.tangem.core.navigation.ReduxNavController import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.DeleteWalletUseCase @@ -43,6 +47,7 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( private val deleteSavedAccessCodesUseCase: DeleteSavedAccessCodesUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val reduxStateHolder: ReduxStateHolder, + private val reduxNavController: ReduxNavController, private val dispatchers: CoroutineDispatcherProvider, ) : BaseWalletClickIntents(), WalletCardClickIntents { @@ -81,18 +86,26 @@ internal class WalletCardClickIntentsImplementor @Inject constructor( viewModelScope.launch(dispatchers.main) { walletScreenContentLoader.cancel(userWalletId) - val deletedUserWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch + val walletToDelete = getUserWalletUseCase(userWalletId).getOrNull() ?: return@launch + val hasUserWallets = deleteWalletUseCase(userWalletId).getOrElse { + Timber.e("Unable to delete user wallet: $it") + return@launch + } - deleteSavedAccessCodesUseCase(cardId = deletedUserWallet.cardId) - .onLeft { Timber.e(it.toString()) } + deleteSavedAccessCodesUseCase(cardId = walletToDelete.cardId).onLeft { + Timber.e("Unable to delete user wallet access code: $it") + } - deleteWalletUseCase(userWalletId) - .onRight { - getSelectedWalletSyncUseCase().getOrNull()?.let { - reduxStateHolder.onUserWalletSelected(it) - } + if (hasUserWallets) { + val selectedWallet = getSelectedWalletSyncUseCase().getOrElse { + error("Unable to find selected wallet: $it") } - .onLeft { Timber.e(it.toString()) } + + reduxStateHolder.onUserWalletSelected(selectedWallet) + } else { + stateHolder.clear() + reduxNavController.navigate(NavigationAction.PopBackTo(AppScreen.Home)) + } } } } \ No newline at end of file From aa52b659ce3bc86a169857681d6e5e9f43df4451 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 May 2024 21:31:23 +0400 Subject: [PATCH 2/2] Updated on 2026-08-14 --- .../walletconnect/WalletConnectSdkHelper.kt | 33 ++++++++++++++----- .../data/DefaultWalletConnectRepository.kt | 12 ++++--- .../domain/WalletConnectInteractor.kt | 18 +++++++--- .../domain/WalletConnectRepository.kt | 2 +- .../domain/WcSessionRequestConverter.kt | 18 ++++++---- .../domain/models/WalletConnectError.kt | 12 ++++--- .../walletconnect/WalletConnectMiddleware.kt | 13 ++++++++ 7 files changed, 79 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index cd502dc24e..2dfd015a7e 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -48,19 +48,30 @@ class WalletConnectSdkHelper { } @Suppress("MagicNumber") - suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData? { + suspend fun prepareTransactionData(data: EthTransactionData): WcTransactionData { val transaction = data.transaction - val blockchain = Blockchain.fromNetworkId(data.networkId) ?: return null - val walletManager = getWalletManager(blockchain, data.rawDerivationPath) ?: return null + val blockchain = requireNotNull(Blockchain.fromNetworkId(data.networkId)) { + "Blockchain not found" + } + val walletManager = requireNotNull(getWalletManager(blockchain, data.rawDerivationPath)) { + "WalletManager not found" + } walletManager.safeUpdate(isDemoCard()) val wallet = walletManager.wallet - val balance = wallet.amounts[AmountType.Coin]?.value ?: return null + val balance = requireNotNull(wallet.amounts[AmountType.Coin]?.value) { + "Coin balance not found" + } val decimals = wallet.blockchain.decimals() - val value = (transaction.value ?: "0").hexToBigDecimal() - .movePointLeft(decimals) ?: return null + val value = (transaction.value ?: "0") + .hexToBigDecimal() + .movePointLeft(decimals) + + requireNotNull(value) { + "Transaction amount is null" + } val gasLimit = getGasLimitFromTx(value, walletManager, transaction) @@ -69,20 +80,23 @@ class WalletConnectSdkHelper { is Result.Success -> result.data.toBigDecimal() is Result.Failure -> { (result.error as? Throwable)?.let { Timber.e(it, "getGasPrice failed") } - return null + + error("Unable to get gas price: ${result.error}") } - null -> return null + null -> error("Gas price is null") } val fee = (gasLimit * gasPrice).movePointLeft(decimals) val total = value + fee + val destinationAddress = requireNotNull(transaction.to) { "Destination address is null" } + val transactionData = TransactionData( amount = Amount(value, wallet.blockchain), // TODO refactoring fee = Fee.Common(Amount(fee, wallet.blockchain)), sourceAddress = transaction.from, - destinationAddress = transaction.to!!, + destinationAddress = destinationAddress, extras = EthereumTransactionExtras( data = transaction.data.removePrefix(HEX_PREFIX).hexToBytes(), gasLimit = gasLimit.toBigInteger(), @@ -102,6 +116,7 @@ class WalletConnectSdkHelper { id = data.id, type = data.type, ) + return WcTransactionData( type = data.type, transaction = transactionData, diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt index a4c78ad4b9..df6ad4d1af 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/data/DefaultWalletConnectRepository.kt @@ -374,6 +374,7 @@ internal class DefaultWalletConnectRepository( override fun rejectRequest(requestData: RequestData, error: WalletConnectError) { val session = currentSessions.find { it.topic == requestData.topic } + analyticsHandler.send( WalletConnect.RequestHandled( WalletConnect.RequestHandledParams( @@ -385,17 +386,18 @@ internal class DefaultWalletConnectRepository( ), ), ) - cancelRequest(requestData.topic, requestData.requestId) + + cancelRequest(requestData.topic, requestData.requestId, error.error) } - override fun cancelRequest(topic: String, id: Long) { + override fun cancelRequest(topic: String, id: Long, message: String) { Web3Wallet.respondSessionRequest( params = Wallet.Params.SessionRequestResponse( sessionTopic = topic, jsonRpcResponse = Wallet.Model.JsonRpcResponse.JsonRpcError( id = id, code = 0, - message = "", + message = message, ), ), onSuccess = {}, @@ -406,8 +408,8 @@ internal class DefaultWalletConnectRepository( override fun reject() { Web3Wallet.rejectSession( params = Wallet.Params.SessionReject( - sessionProposal?.proposerPublicKey ?: "", - "", + proposerPublicKey = sessionProposal?.proposerPublicKey ?: "", + reason = "", ), onSuccess = { Timber.d("Rejected successfully: $it") diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt index e2e43d4975..187f705e63 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectInteractor.kt @@ -260,10 +260,18 @@ class WalletConnectInteractor( ) else -> { currentRequest = sessionRequest - val data = prepareRequestData(sessionRequest) - if (data != null) { - handler.onSessionRequest(data) + + val data = prepareRequestData(sessionRequest).getOrElse { e -> + val wrappedError = e as? WalletConnectError ?: WalletConnectError.UnknownError( + message = e.localizedMessage ?: "Unknown error", + ) + + walletConnectRepository.rejectRequest(requestData, wrappedError) + handler.onSessionRejected(wrappedError) + return } + + handler.onSessionRequest(data) } } } @@ -353,7 +361,9 @@ class WalletConnectInteractor( } } - private suspend fun prepareRequestData(sessionRequest: WalletConnectEvents.SessionRequest): WcPreparedRequest? { + private suspend fun prepareRequestData( + sessionRequest: WalletConnectEvents.SessionRequest, + ): Result { return sessionRequestConverter.prepareRequest(sessionRequest, userWalletId) } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt index 33d62fca22..23e7f0bc82 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WalletConnectRepository.kt @@ -27,5 +27,5 @@ interface WalletConnectRepository { fun rejectRequest(requestData: RequestData, error: WalletConnectError) - fun cancelRequest(topic: String, id: Long) + fun cancelRequest(topic: String, id: Long, message: String = "") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt index 12085cad12..e02d7cc679 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/WcSessionRequestConverter.kt @@ -4,6 +4,7 @@ import com.tangem.tap.domain.walletconnect.WalletConnectSdkHelper import com.tangem.tap.domain.walletconnect2.domain.mapper.mapToTransaction import com.tangem.tap.domain.walletconnect2.domain.models.BnbData import com.tangem.tap.domain.walletconnect2.domain.models.EthTransactionData +import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectError import com.tangem.tap.domain.walletconnect2.domain.models.WalletConnectEvents import com.tangem.tap.features.details.redux.walletconnect.WcEthTransactionType @@ -17,15 +18,18 @@ internal class WcSessionRequestConverter( suspend fun prepareRequest( sessionRequest: WalletConnectEvents.SessionRequest, userWalletId: String, - ): WcPreparedRequest? { - val networkId = blockchainHelper.chainIdToNetworkIdOrNull(sessionRequest.chainId ?: "") ?: return null + ): Result = runCatching { + val networkId = requireNotNull(blockchainHelper.chainIdToNetworkIdOrNull(sessionRequest.chainId.orEmpty())) { + "Failed to get network ID for chain ID: ${sessionRequest.chainId}" + } val derivationPath = getDerivationPath( sessionsRepository = sessionsRepository, sessionRequest = sessionRequest, userWalletId = userWalletId, walletAddress = getWalletAddress(sessionRequest.request), ) - return when (val request = sessionRequest.request) { + + when (val request = sessionRequest.request) { is WcRequest.EthSendTransaction -> { val data = sdkHelper.prepareTransactionData( EthTransactionData( @@ -38,7 +42,8 @@ internal class WcSessionRequestConverter( metaName = sessionRequest.metaName, metaUrl = sessionRequest.metaUrl, ), - ) ?: return null + ) + WcPreparedRequest.EthTransaction( preparedRequestData = data, topic = sessionRequest.topic, @@ -58,7 +63,8 @@ internal class WcSessionRequestConverter( metaName = sessionRequest.metaName, metaUrl = sessionRequest.metaUrl, ), - ) ?: return null + ) + WcPreparedRequest.EthTransaction( preparedRequestData = data, topic = sessionRequest.topic, @@ -122,7 +128,7 @@ internal class WcSessionRequestConverter( derivationPath = derivationPath, ) } - else -> null + else -> throw WalletConnectError.UnsupportedMethod } } diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt index 7d6504d923..40d82cdb51 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect2/domain/models/WalletConnectError.kt @@ -20,8 +20,12 @@ sealed class WalletConnectError(val error: String) : Exception() { override val message: String?, ) : WalletConnectError("ExternalApprovalError") - object WrongUserWallet : WalletConnectError("WrongUserWallet") - object UnsupportedMethod : WalletConnectError("UnsupportedMethod") - object SigningError : WalletConnectError("SigningError") - object ValidationError : WalletConnectError("ValidationError") + data class UnknownError( + override val message: String, + ) : WalletConnectError(message) + + data object WrongUserWallet : WalletConnectError("WrongUserWallet") + data object UnsupportedMethod : WalletConnectError("UnsupportedMethod") + data object SigningError : WalletConnectError("SigningError") + data object ValidationError : WalletConnectError("ValidationError") } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt index b480ea809c..7b34183f53 100644 --- a/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/details/redux/walletconnect/WalletConnectMiddleware.kt @@ -9,6 +9,7 @@ import com.tangem.domain.qrscanning.models.SourceType import com.tangem.feature.qrscanning.QrScanningRouter import com.tangem.tap.common.extensions.dispatchOnMain import com.tangem.tap.common.extensions.inject +import com.tangem.tap.common.redux.AppDialog import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor @@ -20,6 +21,7 @@ import com.tangem.tap.features.demo.DemoHelper import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.wallet.R import kotlinx.coroutines.launch import org.rekotlin.Action import org.rekotlin.Middleware @@ -135,6 +137,17 @@ class WalletConnectMiddleware { ), ) } + is WalletConnectError.UnknownError -> { + store.dispatchOnMain( + GlobalAction.ShowDialog( + AppDialog.SimpleOkDialogRes( + headerId = R.string.wallet_connect_title, + messageId = R.string.wallet_connect_error_with_framework_message, + args = listOf(action.error.message), + ), + ), + ) + } is WalletConnectError.ExternalApprovalError -> { Timber.e(action.error, "ExternalApprovalError ${action.error.message}") // do not show dialog on this event