From 1c7148015fd0232388076a40ca21d8d7d0ffcc46 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 25 Mar 2024 13:56:11 +0500 Subject: [PATCH 1/3] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 31 ++++ .../java/com/tangem/tap/di/ActivityModule.kt | 19 ++ .../tap/di/domain/TokensDomainModule.kt | 9 + .../local/preferences/PreferencesKeys.kt | 10 ++ .../tangem/data/tokens/di/TokensDataModule.kt | 12 ++ .../repository/DefaultNetworksRepository.kt | 1 + ...ultPolkadotAccountHealthCheckRepository.kt | 169 ++++++++++++++++++ .../GetPolkadotCheckHasImmortalUseCase.kt | 10 ++ .../tokens/GetPolkadotCheckHasResetUseCase.kt | 11 ++ .../RunPolkadotAccountHealthCheckUseCase.kt | 17 ++ .../PolkadotAccountHealthCheckRepository.kt | 14 ++ .../analytics/WalletScreenAnalyticsEvent.kt | 62 ++++--- .../implementors/MultiWalletContentLoader.kt | 3 + .../MultiWalletContentLoaderFactory.kt | 3 + .../SingleWalletWithTokenContentLoader.kt | 3 + ...ngleWalletWithTokenContentLoaderFactory.kt | 3 + .../subscribers/BasicTokenListSubscriber.kt | 20 +++ .../MultiWalletTokenListSubscriber.kt | 3 + .../SingleWalletWithTokenListSubscriber.kt | 3 + gradle/dependencies.toml | 2 +- 20 files changed, 383 insertions(+), 22 deletions(-) create mode 100644 data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt create mode 100644 domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 116e6dcf12..3a06ab01ef 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -19,12 +19,14 @@ import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.view.WindowCompat +import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import arrow.core.getOrElse import by.kirich1409.viewbindingdelegate.viewBinding import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.navigation.AppScreen import com.tangem.core.navigation.NavigationAction @@ -35,10 +37,13 @@ import com.tangem.data.card.sdk.CardSdkLifecycleObserver import com.tangem.domain.apptheme.model.AppThemeMode import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository +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.UserWalletsListManagerFeatureToggles 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 import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.tester.api.TesterRouter @@ -99,6 +104,7 @@ val userWalletsListManagerSafe: UserWalletsListManager? val userWalletsListManager: UserWalletsListManager get() = userWalletsListManagerSafe!! +@Suppress("LargeClass") @AndroidEntryPoint class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbackHolder { @@ -148,6 +154,15 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac @Inject lateinit var generalUserWalletsListManager: UserWalletsListManager + @Inject + lateinit var getPolkadotCheckHasResetUseCase: GetPolkadotCheckHasResetUseCase + + @Inject + lateinit var getPolkadotCheckHasImmortalUseCase: GetPolkadotCheckHasImmortalUseCase + + @Inject + lateinit var analyticsEventsHandler: AnalyticsEventHandler + internal val viewModel: MainViewModel by viewModels() private lateinit var appThemeModeFlow: SharedFlow @@ -177,6 +192,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac checkForNotificationPermission() observeStateUpdates() + observePolkadotAccountHealthCheck() if (intent != null) { deepLinksRegistry.launch(intent) @@ -482,4 +498,19 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), 0) } } + + private fun observePolkadotAccountHealthCheck() { + lifecycleScope.launch { + getPolkadotCheckHasResetUseCase() + .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .collect { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotAccountReset(it)) + } + getPolkadotCheckHasImmortalUseCase() + .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .collect { + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it)) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt index 2ff65f28a2..9a0f838f4a 100644 --- a/app/src/main/java/com/tangem/tap/di/ActivityModule.kt +++ b/app/src/main/java/com/tangem/tap/di/ActivityModule.kt @@ -4,6 +4,9 @@ import android.content.Context import com.tangem.domain.card.ScanCardUseCase import com.tangem.domain.card.repository.CardSdkConfigRepository import com.tangem.domain.exchange.RampStateManager +import com.tangem.domain.tokens.GetPolkadotCheckHasImmortalUseCase +import com.tangem.domain.tokens.GetPolkadotCheckHasResetUseCase +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.tap.domain.TangemSdkManager import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository import com.tangem.tap.network.exchangeServices.DefaultRampManager @@ -57,4 +60,20 @@ internal object ActivityModule { fun provideActivityDelayedWorkCoroutineScope(): CoroutineScope { return CoroutineScope(SupervisorJob() + Dispatchers.IO) } + + @Provides + @Singleton + fun provideGetPolkadotCheckHasResetUseCase( + polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, + ): GetPolkadotCheckHasResetUseCase { + return GetPolkadotCheckHasResetUseCase(polkadotAccountHealthCheckRepository) + } + + @Provides + @Singleton + fun provideGetPolkadotCheckHasImmortalUseCase( + polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, + ): GetPolkadotCheckHasImmortalUseCase { + return GetPolkadotCheckHasImmortalUseCase(polkadotAccountHealthCheckRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index c67960a860..8f377b67ad 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -332,4 +332,13 @@ internal object TokensDomainModule { ): IsAmountSubtractAvailableUseCase { return IsAmountSubtractAvailableUseCase(currenciesRepository, dispatchers) } + + @Provides + @ViewModelScoped + fun provideRunPolkadotAccountHealthCheckUseCase( + repository: PolkadotAccountHealthCheckRepository, + dispatchers: CoroutineDispatcherProvider, + ): RunPolkadotAccountHealthCheckUseCase { + return RunPolkadotAccountHealthCheckUseCase(repository, dispatchers) + } } \ 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 22d8aa8ea3..464387831e 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 @@ -63,6 +63,16 @@ object PreferencesKeys { val APP_LOGS_KEY by lazy { stringPreferencesKey(name = "app_logs") } + val POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX by lazy { + stringPreferencesKey(name = "POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX") + } + val POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS by lazy { + stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS") + } + val POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS by lazy { + stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS") + } + fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region") } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 05c56cfaf9..ef0aef1c7e 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -119,4 +119,16 @@ internal object TokensDataModule { fun provideCurrencyChecksRepository(walletManagersFacade: WalletManagersFacade): CurrencyChecksRepository { return DefaultCurrencyChecksRepository(walletManagersFacade = walletManagersFacade) } + + @Provides + @Singleton + fun providePolkadotAccountHealthCheckRepository( + walletManagersFacade: WalletManagersFacade, + appPreferencesStore: AppPreferencesStore, + ): PolkadotAccountHealthCheckRepository { + return DefaultPolkadotAccountHealthCheckRepository( + walletManagersFacade = walletManagersFacade, + appPreferencesStore = appPreferencesStore, + ) + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt index 44096bdcd7..f25a65291c 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultNetworksRepository.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.collectLatest import timber.log.Timber +@Suppress("LongParameterList") internal class DefaultNetworksRepository( private val networksStatusesStore: NetworksStatusesStore, private val walletManagersFacade: WalletManagersFacade, diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt new file mode 100644 index 0000000000..e43b946f97 --- /dev/null +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt @@ -0,0 +1,169 @@ +package com.tangem.data.tokens.repository + +import com.tangem.blockchain.blockchains.polkadot.AccountCheckProvider +import com.tangem.blockchain.common.Blockchain +import com.tangem.blockchain.common.BlockchainSdkError +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX +import com.tangem.datasource.local.preferences.utils.getObjectListSync +import com.tangem.datasource.local.preferences.utils.getObjectMap +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import com.tangem.domain.walletmanager.WalletManagersFacade +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.extensions.addOrReplace +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber + +internal class DefaultPolkadotAccountHealthCheckRepository( + private val walletManagersFacade: WalletManagersFacade, + private val appPreferencesStore: AppPreferencesStore, +) : PolkadotAccountHealthCheckRepository { + + private val hasImmortalTransaction = MutableSharedFlow() + private val hasResetTransaction = MutableSharedFlow() + + private val mutex = Mutex() + + override suspend fun runCheck(userWalletId: UserWalletId, network: Network) { + // Run Polkadot account health check + if (Blockchain.fromId(network.id.value) != Blockchain.Polkadot) return + + mutex.withLock { + val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) + val accountCheckProvider = requireNotNull(walletManager as AccountCheckProvider) { + Timber.e("Unable to cast wallet manager to AccountCheckProvider") + return + } + val address = walletManager.wallet.address + + checkHasReset(accountCheckProvider, address) + checkHasImmortal(accountCheckProvider, address) + } + } + + override fun subscribeToHasImmortalResults() = hasImmortalTransaction.asSharedFlow() + + override fun subscribeToHasResetResults() = hasResetTransaction.asSharedFlow() + + private suspend fun checkHasReset(polkadotManager: AccountCheckProvider, address: String) { + val checkedAddresses = appPreferencesStore.getObjectListSync(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + if (checkedAddresses.contains(address)) return + runCatching { + val accountInfo = requireNotNull(polkadotManager.getAccountInfo().account) { + Timber.e("Account info is null") + } + val nonce = accountInfo.nonce + val extrinsicCount = accountInfo.countExtrinsic + + // Account was reset + if (nonce != null && extrinsicCount != null) { + hasResetTransaction.emit(nonce < extrinsicCount) + updateCheckedResetAddressesList(address) + } + }.onFailure { + if ((it as? BlockchainSdkError.CustomError)?.customMessage == ACCOUNT_NOT_FOUND) { + updateCheckedResetAddressesList(address) + } + } + } + + private suspend fun checkHasImmortal(polkadotManager: AccountCheckProvider, address: String) { + val checkedAddresses = appPreferencesStore.getObjectListSync(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) + if (checkedAddresses.contains(address)) return + + runCatching { + do { + // Getting batch of extrinsics to check + val lastChecked = appPreferencesStore.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val lastExtrinsic = lastChecked[address] + val extrinsicListResult = polkadotManager.getExtrinsicList(afterExtrinsicId = lastExtrinsic) + // Checking extrinsic one by one + extrinsicListResult.extrinsic?.forEach { tx -> + val hash = tx.hash + val id = tx.id + if (hash != null && id != null) { + val details = polkadotManager.getExtrinsicDetail(hash) + + // We found an `immortal` transaction + if (details.lifetime == null) { + hasImmortalTransaction.emit(true) + updateCheckedImmutableAddressesList(address) + clearLastCheckedTransaction(address) + return + } + + // Saving last checked transaction + updateLastCheckedTransaction(address, id) + } + } + } while (!extrinsicListResult.extrinsic.isNullOrEmpty()) + + // We checked all transactions up to current moment and did not found an `immortal` transaction + hasImmortalTransaction.emit(false) + updateCheckedImmutableAddressesList(address) + clearLastCheckedTransaction(address) + } + } + + private suspend fun updateLastCheckedTransaction(address: String, txId: Long) { + appPreferencesStore.editData { + val savedList = it.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val updatedList = savedList.toMutableMap() + updatedList[address] = txId + it.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX, updatedList) + } + } + + private suspend fun clearLastCheckedTransaction(address: String) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val updatedList = savedList.toMutableMap() + updatedList.remove(address) + mutablePreferences.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX, updatedList) + } + } + + private suspend fun updateCheckedImmutableAddressesList(address: String) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectList(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) + val updatedList = savedList?.toMutableList() + updatedList?.addOrReplace(address) { it == address } + + if (updatedList != null) { + mutablePreferences.setObjectList( + POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS, + updatedList.toList(), + ) + } else { + mutablePreferences.remove(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) + } + } + } + + private suspend fun updateCheckedResetAddressesList(address: String) { + appPreferencesStore.editData { mutablePreferences -> + val savedList = mutablePreferences.getObjectList(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + val updatedList = savedList?.toMutableList() + updatedList?.addOrReplace(address) { it == address } + + if (updatedList != null) { + mutablePreferences.setObjectList( + POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS, + updatedList.toList(), + ) + } else { + mutablePreferences.remove(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + } + } + } + + private companion object { + const val ACCOUNT_NOT_FOUND = "Record Not Found" + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt new file mode 100644 index 0000000000..04df6436d3 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt @@ -0,0 +1,10 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import kotlinx.coroutines.flow.Flow + +class GetPolkadotCheckHasImmortalUseCase( + private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, +) { + operator fun invoke(): Flow = polkadotAccountHealthCheckRepository.subscribeToHasImmortalResults() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt new file mode 100644 index 0000000000..b67e44d13b --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import kotlinx.coroutines.flow.Flow + +class GetPolkadotCheckHasResetUseCase( + private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, +) { + + operator fun invoke(): Flow = polkadotAccountHealthCheckRepository.subscribeToHasResetResults() +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt new file mode 100644 index 0000000000..11f9080959 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.tokens + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext + +class RunPolkadotAccountHealthCheckUseCase( + private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend operator fun invoke(userWalletId: UserWalletId, network: Network) = withContext(dispatchers.io) { + polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt new file mode 100644 index 0000000000..2da0aa6226 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.tokens.repository + +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface PolkadotAccountHealthCheckRepository { + + suspend fun runCheck(userWalletId: UserWalletId, network: Network) + + fun subscribeToHasImmortalResults(): Flow + + fun subscribeToHasResetResults(): Flow +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index 088622ed27..04535b6a3b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -23,7 +23,7 @@ sealed class WalletScreenAnalyticsEvent { override val oneTimeEventId: String = id + userWalletId.stringValue } - object WalletOpened : Basic(event = "Wallet Opened") + data object WalletOpened : Basic(event = "Wallet Opened") class CardWasScanned(source: AnalyticsParam.ScannedFrom) : Basic( event = "Card Was Scanned", @@ -48,13 +48,33 @@ sealed class WalletScreenAnalyticsEvent { ) } + sealed class Token( + event: String, + params: Map = mapOf(), + ) : AnalyticsEvent(category = "Token", event = event, params = params) { + + class PolkadotAccountReset(hasReset: Boolean) : Token( + event = "Polkadot Account Reset", + params = mapOf( + AnalyticsParam.STATE to if (hasReset) "Yes" else "No", + ), + ) + + class PolkadotImmortalTransactions(hasImmortalTransaction: Boolean) : Token( + event = "Polkadot Immortal Transactions", + params = mapOf( + AnalyticsParam.STATE to if (hasImmortalTransaction) "Yes" else "No", + ), + ) + } + sealed class MainScreen( event: String, params: Map = mapOf(), ) : AnalyticsEvent(category = "Main Screen", event = event, params = params) { - object ScreenOpened : MainScreen(event = "Screen opened") - object WalletSwipe : MainScreen(event = "Wallet Swipe") + data object ScreenOpened : MainScreen(event = "Screen opened") + data object WalletSwipe : MainScreen(event = "Wallet Swipe") class EnableBiometrics(state: AnalyticsParam.OnOffState) : MainScreen( event = "Enable Biometric", @@ -66,37 +86,37 @@ sealed class WalletScreenAnalyticsEvent { params = mapOf("Result" to result.value), ) - object NoticeBackupYourWalletTapped : MainScreen(event = "Notice - Backup Your Wallet Tapped") - object NoticeScanYourCardTapped : MainScreen(event = "Notice - Scan Your Card Tapped") - object NoticeWalletLocked : MainScreen(event = "Notice - Wallet Locked") - object WalletUnlockTapped : MainScreen(event = "Notice - Wallet Unlock Tapped") + data object NoticeBackupYourWalletTapped : MainScreen(event = "Notice - Backup Your Wallet Tapped") + data object NoticeScanYourCardTapped : MainScreen(event = "Notice - Scan Your Card Tapped") + data object NoticeWalletLocked : MainScreen(event = "Notice - Wallet Locked") + data object WalletUnlockTapped : MainScreen(event = "Notice - Wallet Unlock Tapped") - object NetworksUnreachable : MainScreen(event = "Notice - Networks Unreachable") + data object NetworksUnreachable : MainScreen(event = "Notice - Networks Unreachable") - object MissingAddresses : MainScreen(event = "Notice - Missing Addresses") + data object MissingAddresses : MainScreen(event = "Notice - Missing Addresses") - object CardSignedTransactions : MainScreen(event = "Notice - Card Signed Transactions") + data object CardSignedTransactions : MainScreen(event = "Notice - Card Signed Transactions") - object HowDoYouLikeTangem : MainScreen(event = "Notice - How Do You Like Tangem") + data object HowDoYouLikeTangem : MainScreen(event = "Notice - How Do You Like Tangem") - object ProductSampleCard : MainScreen(event = "Notice - Product Sample Card") + data object ProductSampleCard : MainScreen(event = "Notice - Product Sample Card") - object TestnetCard : MainScreen(event = "Notice - Testnet Card") + data object TestnetCard : MainScreen(event = "Notice - Testnet Card") - object DemoCard : MainScreen(event = "Notice - Demo Card") + data object DemoCard : MainScreen(event = "Notice - Demo Card") - object DevelopmentCard : MainScreen(event = "Notice - Development Card") + data object DevelopmentCard : MainScreen(event = "Notice - Development Card") - object WalletUnlock : MainScreen(event = "Notice - Wallet Unlock") + data object WalletUnlock : MainScreen(event = "Notice - Wallet Unlock") - object BackupYourWallet : MainScreen(event = "Notice - Backup Your Wallet") + data object BackupYourWallet : MainScreen(event = "Notice - Backup Your Wallet") - object UnlockAllWithBiometrics : MainScreen(event = "Button - Unlock All With Biometrics") + data object UnlockAllWithBiometrics : MainScreen(event = "Button - Unlock All With Biometrics") - object UnlockWithCardScan : MainScreen(event = "Button - Unlock With Card Scan") + data object UnlockWithCardScan : MainScreen(event = "Button - Unlock With Card Scan") - object EditWalletTapped : MainScreen(event = "Button - Edit Wallet Tapped") + data object EditWalletTapped : MainScreen(event = "Button - Edit Wallet Tapped") - object DeleteWalletTapped : MainScreen(event = "Button - Delete Wallet Tapped") + data object DeleteWalletTapped : MainScreen(event = "Button - Delete Wallet Tapped") } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index ba746cd830..bc6d40794b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -29,6 +30,7 @@ internal class MultiWalletContentLoader( private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val reduxStateHolder: ReduxStateHolder, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -42,6 +44,7 @@ internal class MultiWalletContentLoader( getTokenListUseCase = getTokenListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, applyTokenListSortingUseCase = applyTokenListSortingUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ), MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 794574309a..540fdea589 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -26,6 +27,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase, private val reduxStateHolder: ReduxStateHolder, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader { @@ -41,6 +43,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( reduxStateHolder = reduxStateHolder, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, applyTokenListSortingUseCase = applyTokenListSortingUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index 525f4952ae..655c6255a3 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -24,6 +25,7 @@ internal class SingleWalletWithTokenContentLoader( private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory, private val getCardTokensListUseCase: GetCardTokensListUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -36,6 +38,7 @@ internal class SingleWalletWithTokenContentLoader( walletWithFundsChecker = walletWithFundsChecker, getCardTokensListUseCase = getCardTokensListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ), MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index e92241ad9a..e00b6fe74c 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender @@ -21,6 +22,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val getCardTokensListUseCase: GetCardTokensListUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader { @@ -34,6 +36,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( getCardTokensListUseCase = getCardTokensListUseCase, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 0dcd95aa6a..7dcd29e739 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -4,7 +4,9 @@ import arrow.core.Either import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError +import com.tangem.domain.tokens.model.NetworkGroup import com.tangem.domain.tokens.model.TokenList import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender @@ -33,6 +35,7 @@ internal abstract class BasicTokenListSubscriber( private val tokenListAnalyticsSender: TokenListAnalyticsSender, private val walletWithFundsChecker: WalletWithFundsChecker, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : WalletSubscriber() { private val sendAnalyticsJobHolder = JobHolder() @@ -53,6 +56,8 @@ internal abstract class BasicTokenListSubscriber( coroutineScope.launch { onTokenListReceived(maybeTokenList) }.saveIn(onTokenListReceivedJobHolder) + + coroutineScope.launch { startCheck(maybeTokenList) } }, flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(), transform = { maybeTokenList, maybeAppCurrency -> @@ -72,6 +77,21 @@ internal abstract class BasicTokenListSubscriber( ) } + private suspend fun startCheck(maybeTokenList: Either) { + // Run Polkadot account health check + maybeTokenList.getOrNull()?.let { tokenList -> + val cryptoCurrencies = when (tokenList) { + is TokenList.GroupedByNetwork -> tokenList.groups.flatMap(NetworkGroup::currencies) + is TokenList.Ungrouped -> tokenList.currencies + is TokenList.Empty -> emptyList() + } + + cryptoCurrencies.forEach { + runPolkadotAccountHealthCheckUseCase(userWallet.walletId, it.currency.network) + } + } + } + protected open suspend fun onTokenListReceived(maybeTokenList: Either) { /* no-op */ } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index 9185370945..e3de1238a9 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -5,6 +5,7 @@ import arrow.core.getOrElse import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.GetTokenListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.TokenList @@ -24,6 +25,7 @@ internal class MultiWalletTokenListSubscriber( tokenListAnalyticsSender: TokenListAnalyticsSender, walletWithFundsChecker: WalletWithFundsChecker, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -31,6 +33,7 @@ internal class MultiWalletTokenListSubscriber( tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { override fun tokenListFlow(): MaybeTokenListFlow = getTokenListUseCase(userWallet.walletId) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index 54c8ca07de..952fcb8c29 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.subscribers import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.tokens.GetCardTokensListUseCase +import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker @@ -17,6 +18,7 @@ internal class SingleWalletWithTokenListSubscriber( tokenListAnalyticsSender: TokenListAnalyticsSender, walletWithFundsChecker: WalletWithFundsChecker, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, ) : BasicTokenListSubscriber( userWallet = userWallet, stateHolder = stateHolder, @@ -24,6 +26,7 @@ internal class SingleWalletWithTokenListSubscriber( tokenListAnalyticsSender = tokenListAnalyticsSender, walletWithFundsChecker = walletWithFundsChecker, getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, ) { override fun tokenListFlow(): MaybeTokenListFlow = getCardTokensListUseCase(userWallet.walletId) diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 64ca08576f..838452da7b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-536" +tangemBlockchainSdk = "release-app_5.8-542" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-338" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 4c77120575cdb747ae435a39a7194d735a69ef0e Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 1 Apr 2024 11:11:12 +0500 Subject: [PATCH 2/3] Updated on 2026-08-14 --- .../tokens/RunPolkadotAccountHealthCheckUseCase.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt index 11f9080959..8afa023e85 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt @@ -1,5 +1,6 @@ package com.tangem.domain.tokens +import arrow.core.Either import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.wallets.models.UserWalletId @@ -11,7 +12,10 @@ class RunPolkadotAccountHealthCheckUseCase( private val dispatchers: CoroutineDispatcherProvider, ) { - suspend operator fun invoke(userWalletId: UserWalletId, network: Network) = withContext(dispatchers.io) { - polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) - } + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = + withContext(dispatchers.io) { + Either.catch { + polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) + } + } } \ No newline at end of file From cc07b0ba263fb04f4e10359b40b7f044d697ad75 Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 3 Apr 2024 15:39:22 +0500 Subject: [PATCH 3/3] Updated on 2026-08-14 --- .../main/java/com/tangem/tap/MainActivity.kt | 10 +- .../tap/di/domain/TokensDomainModule.kt | 3 +- .../local/preferences/AppPreferencesStore.kt | 12 ++ .../local/preferences/PreferencesKeys.kt | 6 +- .../utils/AppPreferencesStoreExt.kt | 9 + .../tangem/data/tokens/di/TokensDataModule.kt | 2 + ...ultPolkadotAccountHealthCheckRepository.kt | 158 ++++++++++-------- .../GetPolkadotCheckHasImmortalUseCase.kt | 3 +- .../tokens/GetPolkadotCheckHasResetUseCase.kt | 3 +- .../RunPolkadotAccountHealthCheckUseCase.kt | 12 +- .../PolkadotAccountHealthCheckRepository.kt | 4 +- gradle/dependencies.toml | 2 +- 12 files changed, 129 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/MainActivity.kt b/app/src/main/java/com/tangem/tap/MainActivity.kt index 3a06ab01ef..2a453f6cc8 100644 --- a/app/src/main/java/com/tangem/tap/MainActivity.kt +++ b/app/src/main/java/com/tangem/tap/MainActivity.kt @@ -503,13 +503,19 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac lifecycleScope.launch { getPolkadotCheckHasResetUseCase() .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .distinctUntilChanged() .collect { - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotAccountReset(it)) + analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotAccountReset(it.second)) } + } + lifecycleScope.launch { getPolkadotCheckHasImmortalUseCase() .flowWithLifecycle(lifecycle, minActiveState = Lifecycle.State.CREATED) + .distinctUntilChanged() .collect { - analyticsEventsHandler.send(WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it)) + analyticsEventsHandler.send( + WalletScreenAnalyticsEvent.Token.PolkadotImmortalTransactions(it.second), + ) } } } diff --git a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt index 8f377b67ad..2e373e7c69 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TokensDomainModule.kt @@ -337,8 +337,7 @@ internal object TokensDomainModule { @ViewModelScoped fun provideRunPolkadotAccountHealthCheckUseCase( repository: PolkadotAccountHealthCheckRepository, - dispatchers: CoroutineDispatcherProvider, ): RunPolkadotAccountHealthCheckUseCase { - return RunPolkadotAccountHealthCheckUseCase(repository, dispatchers) + return RunPolkadotAccountHealthCheckUseCase(repository) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt index 2b1de6b7b0..905fe4f767 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/AppPreferencesStore.kt @@ -72,6 +72,12 @@ class AppPreferencesStore( return this[key]?.let(adapter::fromJson).orEmpty() } + /** Get set of data [T] by string [key] */ + inline fun MutablePreferences.getObjectSet(key: Preferences.Key): Set? { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) + return this[key]?.let(adapter::fromJson) + } + /** * Set data [T] by string [key] to [MutablePreferences] * @@ -97,4 +103,10 @@ class AppPreferencesStore( this[key] = adapter.toJson(value) } + + /** Sets set of data [T] by string [key] to [MutablePreferences] */ + inline fun MutablePreferences.setObjectSet(key: Preferences.Key, value: Set) { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) + this[key] = adapter.toJson(value) + } } \ 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 464387831e..2a6b0457d3 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 @@ -63,13 +63,13 @@ object PreferencesKeys { val APP_LOGS_KEY by lazy { stringPreferencesKey(name = "app_logs") } - val POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX by lazy { + val POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY by lazy { stringPreferencesKey(name = "POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX") } - val POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS by lazy { + val POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY by lazy { stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS") } - val POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS by lazy { + val POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY by lazy { stringPreferencesKey(name = "POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS") } diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt index cd1facee55..aa3171803c 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt @@ -128,6 +128,15 @@ suspend inline fun AppPreferencesStore.getObjectMap(key: Preferences val type = Types.newParameterizedType(Map::class.java, String::class.java, V::class.java) val adapter = moshi.adapter>(type) + return data.firstOrNull() + ?.get(key) + ?.let(adapter::fromJson) + .orEmpty() +} + +/** Get set of data [T] by string [key], or empty if data is not found */ +suspend inline fun AppPreferencesStore.getObjectSetSync(key: Preferences.Key): Set { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) return data.firstOrNull() ?.get(key) ?.let(adapter::fromJson) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index ef0aef1c7e..062692cd06 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -125,10 +125,12 @@ internal object TokensDataModule { fun providePolkadotAccountHealthCheckRepository( walletManagersFacade: WalletManagersFacade, appPreferencesStore: AppPreferencesStore, + dispatchers: CoroutineDispatcherProvider, ): PolkadotAccountHealthCheckRepository { return DefaultPolkadotAccountHealthCheckRepository( walletManagersFacade = walletManagersFacade, appPreferencesStore = appPreferencesStore, + dispatchers = dispatchers, ) } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt index e43b946f97..049991280b 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultPolkadotAccountHealthCheckRepository.kt @@ -1,49 +1,64 @@ package com.tangem.data.tokens.repository +import androidx.datastore.preferences.core.Preferences import com.tangem.blockchain.blockchains.polkadot.AccountCheckProvider +import com.tangem.blockchain.blockchains.polkadot.network.accounthealthcheck.ExtrinsicListItemResponse import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS -import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS -import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX -import com.tangem.datasource.local.preferences.utils.getObjectListSync +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY import com.tangem.datasource.local.preferences.utils.getObjectMap +import com.tangem.datasource.local.preferences.utils.getObjectSetSync import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.extensions.addOrReplace +import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import kotlinx.coroutines.withContext import timber.log.Timber +import java.util.concurrent.ConcurrentHashMap +import kotlin.collections.set internal class DefaultPolkadotAccountHealthCheckRepository( private val walletManagersFacade: WalletManagersFacade, private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, ) : PolkadotAccountHealthCheckRepository { - private val hasImmortalTransaction = MutableSharedFlow() - private val hasResetTransaction = MutableSharedFlow() + private val hasImmortalTransaction = MutableSharedFlow>() + private val hasResetTransaction = MutableSharedFlow>() private val mutex = Mutex() + private val mutexes = ConcurrentHashMap() override suspend fun runCheck(userWalletId: UserWalletId, network: Network) { // Run Polkadot account health check if (Blockchain.fromId(network.id.value) != Blockchain.Polkadot) return - mutex.withLock { - val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) - val accountCheckProvider = requireNotNull(walletManager as AccountCheckProvider) { - Timber.e("Unable to cast wallet manager to AccountCheckProvider") - return - } - val address = walletManager.wallet.address + val walletManager = walletManagersFacade.getOrCreateWalletManager(userWalletId, network) + val address = requireNotNull(walletManager?.wallet?.address) { + Timber.e("Address is null") + return + } + val accountCheckProvider = requireNotNull(walletManager as? AccountCheckProvider) { + Timber.e("Unable to cast wallet manager to AccountCheckProvider") + return + } - checkHasReset(accountCheckProvider, address) - checkHasImmortal(accountCheckProvider, address) + // use a separate mutexForKey for each key to avoid multiple calls block() to the same key + // also used mutex to safe create mutexForKey, otherwise it can lead to multiple calls for the same key + val mutexForKey = mutex.withLock { mutexes.getOrPut(address) { Mutex() } } + mutexForKey.withLock { + withContext(dispatchers.io) { + checkHasReset(accountCheckProvider, address) + checkHasImmortal(accountCheckProvider, address) + } } } @@ -52,7 +67,7 @@ internal class DefaultPolkadotAccountHealthCheckRepository( override fun subscribeToHasResetResults() = hasResetTransaction.asSharedFlow() private suspend fun checkHasReset(polkadotManager: AccountCheckProvider, address: String) { - val checkedAddresses = appPreferencesStore.getObjectListSync(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + val checkedAddresses = appPreferencesStore.getObjectSetSync(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY) if (checkedAddresses.contains(address)) return runCatching { val accountInfo = requireNotNull(polkadotManager.getAccountInfo().account) { @@ -63,102 +78,97 @@ internal class DefaultPolkadotAccountHealthCheckRepository( // Account was reset if (nonce != null && extrinsicCount != null) { - hasResetTransaction.emit(nonce < extrinsicCount) - updateCheckedResetAddressesList(address) + val hasReset = nonce < extrinsicCount + updateChecked(address, POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY) + hasResetTransaction.emit(address to hasReset) } }.onFailure { if ((it as? BlockchainSdkError.CustomError)?.customMessage == ACCOUNT_NOT_FOUND) { - updateCheckedResetAddressesList(address) + updateChecked(address, POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS_KEY) } } } - private suspend fun checkHasImmortal(polkadotManager: AccountCheckProvider, address: String) { - val checkedAddresses = appPreferencesStore.getObjectListSync(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) + private suspend fun checkHasImmortal(accountCheckerProvider: AccountCheckProvider, address: String) { + val checkedAddresses = + appPreferencesStore.getObjectSetSync(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY) if (checkedAddresses.contains(address)) return runCatching { do { // Getting batch of extrinsics to check - val lastChecked = appPreferencesStore.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val lastChecked = appPreferencesStore.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY) val lastExtrinsic = lastChecked[address] - val extrinsicListResult = polkadotManager.getExtrinsicList(afterExtrinsicId = lastExtrinsic) - // Checking extrinsic one by one - extrinsicListResult.extrinsic?.forEach { tx -> - val hash = tx.hash - val id = tx.id - if (hash != null && id != null) { - val details = polkadotManager.getExtrinsicDetail(hash) - - // We found an `immortal` transaction - if (details.lifetime == null) { - hasImmortalTransaction.emit(true) - updateCheckedImmutableAddressesList(address) - clearLastCheckedTransaction(address) - return - } - - // Saving last checked transaction - updateLastCheckedTransaction(address, id) + val extrinsicListResult = accountCheckerProvider.getExtrinsicList(afterExtrinsicId = lastExtrinsic) + extrinsicListResult.extrinsic + ?.forEach { tx -> + // Checking extrinsic one by one + if (checkTx(tx, address, accountCheckerProvider)) return } - } } while (!extrinsicListResult.extrinsic.isNullOrEmpty()) // We checked all transactions up to current moment and did not found an `immortal` transaction - hasImmortalTransaction.emit(false) - updateCheckedImmutableAddressesList(address) + hasImmortalTransaction.emit(address to false) + updateChecked(address, POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY) clearLastCheckedTransaction(address) } } + private suspend fun checkTx( + tx: ExtrinsicListItemResponse, + address: String, + accountCheckerProvider: AccountCheckProvider, + ): Boolean { + val hash = tx.hash + val id = tx.id + if (hash != null && id != null) { + val details = accountCheckerProvider.getExtrinsicDetail(hash) + + // We found an `immortal` transaction + if (details.lifetime == null) { + hasImmortalTransaction.emit(address to true) + updateChecked(address, POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS_KEY) + clearLastCheckedTransaction(address) + return true + } + + // Saving last checked transaction + updateLastCheckedTransaction(address, id) + } + return false + } + private suspend fun updateLastCheckedTransaction(address: String, txId: Long) { appPreferencesStore.editData { - val savedList = it.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val savedList = it.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY) val updatedList = savedList.toMutableMap() updatedList[address] = txId - it.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX, updatedList) + it.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY, updatedList) } } private suspend fun clearLastCheckedTransaction(address: String) { appPreferencesStore.editData { mutablePreferences -> - val savedList = mutablePreferences.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX) + val savedList = mutablePreferences.getObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY) val updatedList = savedList.toMutableMap() updatedList.remove(address) - mutablePreferences.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX, updatedList) + mutablePreferences.setObjectMap(POLKADOT_HEALTH_CHECK_LAST_INDEXED_TX_KEY, updatedList) } } - private suspend fun updateCheckedImmutableAddressesList(address: String) { + private suspend fun updateChecked(address: String, key: Preferences.Key) { appPreferencesStore.editData { mutablePreferences -> - val savedList = mutablePreferences.getObjectList(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) - val updatedList = savedList?.toMutableList() - updatedList?.addOrReplace(address) { it == address } + val savedList = mutablePreferences.getObjectSet(key) + val updatedList = savedList?.toMutableSet() ?: mutableSetOf() + updatedList.add(address) - if (updatedList != null) { - mutablePreferences.setObjectList( - POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS, - updatedList.toList(), + if (updatedList.isNotEmpty()) { + mutablePreferences.setObjectSet( + key, + updatedList.toSet(), ) } else { - mutablePreferences.remove(POLKADOT_HEALTH_CHECKED_IMMUTABLE_ACCOUNTS) - } - } - } - - private suspend fun updateCheckedResetAddressesList(address: String) { - appPreferencesStore.editData { mutablePreferences -> - val savedList = mutablePreferences.getObjectList(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) - val updatedList = savedList?.toMutableList() - updatedList?.addOrReplace(address) { it == address } - - if (updatedList != null) { - mutablePreferences.setObjectList( - POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS, - updatedList.toList(), - ) - } else { - mutablePreferences.remove(POLKADOT_HEALTH_CHECKED_RESET_ACCOUNTS) + mutablePreferences.remove(key) } } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt index 04df6436d3..527f6e1703 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasImmortalUseCase.kt @@ -6,5 +6,6 @@ import kotlinx.coroutines.flow.Flow class GetPolkadotCheckHasImmortalUseCase( private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, ) { - operator fun invoke(): Flow = polkadotAccountHealthCheckRepository.subscribeToHasImmortalResults() + operator fun invoke(): Flow> = + polkadotAccountHealthCheckRepository.subscribeToHasImmortalResults() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt index b67e44d13b..33ce7e960e 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetPolkadotCheckHasResetUseCase.kt @@ -7,5 +7,6 @@ class GetPolkadotCheckHasResetUseCase( private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, ) { - operator fun invoke(): Flow = polkadotAccountHealthCheckRepository.subscribeToHasResetResults() + operator fun invoke(): Flow> = + polkadotAccountHealthCheckRepository.subscribeToHasResetResults() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt index 8afa023e85..f7b3131361 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/RunPolkadotAccountHealthCheckUseCase.kt @@ -4,18 +4,12 @@ import arrow.core.Either import com.tangem.domain.tokens.model.Network import com.tangem.domain.tokens.repository.PolkadotAccountHealthCheckRepository import com.tangem.domain.wallets.models.UserWalletId -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.withContext class RunPolkadotAccountHealthCheckUseCase( private val polkadotAccountHealthCheckRepository: PolkadotAccountHealthCheckRepository, - private val dispatchers: CoroutineDispatcherProvider, ) { - suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = - withContext(dispatchers.io) { - Either.catch { - polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) - } - } + suspend operator fun invoke(userWalletId: UserWalletId, network: Network): Either = Either.catch { + polkadotAccountHealthCheckRepository.runCheck(userWalletId, network) + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt index 2da0aa6226..a0cd15dd47 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PolkadotAccountHealthCheckRepository.kt @@ -8,7 +8,7 @@ interface PolkadotAccountHealthCheckRepository { suspend fun runCheck(userWalletId: UserWalletId, network: Network) - fun subscribeToHasImmortalResults(): Flow + fun subscribeToHasImmortalResults(): Flow> - fun subscribeToHasResetResults(): Flow + fun subscribeToHasResetResults(): Flow> } \ No newline at end of file diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 838452da7b..6430f26a99 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -85,7 +85,7 @@ web3j = "4.10.1" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.8-542" +tangemBlockchainSdk = "release-app_5.8-562" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "release-app_5.8-338" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^