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 ^