diff --git a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt index f7901863e9..a13d84a125 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/DefaultWalletAccountsFetcher.kt @@ -25,6 +25,7 @@ import com.tangem.datasource.api.tangemTech.models.orDefault import com.tangem.datasource.utils.getSyncOrNull import com.tangem.domain.models.wallet.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.logging.TangemLogger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.withContext @@ -143,6 +144,11 @@ internal class DefaultWalletAccountsFetcher @Inject constructor( apiResponse.bind().enrichByAccountId() }, onError = { error -> + TangemLogger.e( + "pushInternal wallet=$userWalletId: PUT /accounts failed, " + + "isPreconditionFailed=${error.isNetworkError(code = Code.PRECONDITION_FAILED)}, error=$error", + ) + if (error.isNetworkError(code = Code.PRECONDITION_FAILED)) { throw error } diff --git a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt index 361622055b..38bb45edde 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt @@ -59,6 +59,8 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor( storeWalletAccounts: suspend (UserWalletId, GetWalletAccountsResponse) -> Unit, ): FetchResult { val isResponseUpToDate = error.isNetworkError(code = Code.NOT_MODIFIED) + val isNotFoundError = error.isNetworkError(code = Code.NOT_FOUND) + if (isResponseUpToDate) { TangemLogger.e("ETag is up to date, no need to update accounts for wallet: $userWalletId") val response = requireNotNull(savedAccountsResponse) { @@ -71,13 +73,16 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor( val response = savedAccountsResponse ?: createDefaultResponse(userWalletId) val (accountDTOs, userTokensResponse) = response.accounts to response.toUserTokensResponse() - val isNotFoundError = error.isNetworkError(code = Code.NOT_FOUND) if (isNotFoundError) { val eTag = createWallet(userWalletId) if (eTag != null) { pushWalletAccounts(accountDTOs, eTag) userTokensSaver.pushWithRetryer(userWalletId, userTokensResponse) + } else { + TangemLogger.e( + "handle wallet=$userWalletId: account creation skipped, createWallet returned null eTag", + ) } } @@ -112,10 +117,17 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor( private suspend fun createWallet(userWalletId: UserWalletId): String? { val creationResponse = walletServerBinder.bind(userWalletId) - return if (creationResponse is ApiResponse.Success && creationResponse.code == Code.CREATED) { + val isCreated = creationResponse is ApiResponse.Success && creationResponse.code == Code.CREATED + val eTag = if (isCreated) { creationResponse.headers[ETAG_HEADER]?.firstOrNull() } else { null } + + if (eTag == null) { + TangemLogger.e("ETag is null for wallet: $userWalletId, isCreated: $isCreated") + } + + return eTag } } \ No newline at end of file diff --git a/data/common/src/main/kotlin/com/tangem/data/common/wallet/DefaultWalletServerBinder.kt b/data/common/src/main/kotlin/com/tangem/data/common/wallet/DefaultWalletServerBinder.kt index 57272ffb53..a77d02d27a 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/wallet/DefaultWalletServerBinder.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/wallet/DefaultWalletServerBinder.kt @@ -1,6 +1,7 @@ package com.tangem.data.common.wallet import com.tangem.datasource.api.common.response.ApiResponse +import com.tangem.datasource.api.common.response.ETAG_HEADER import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.api.tangemTech.converters.WalletIdBodyConverter import com.tangem.datasource.local.appsflyer.AppsFlyerStore @@ -9,6 +10,7 @@ import com.tangem.domain.common.wallets.getSyncOrNull import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.logging.TangemLogger import kotlinx.coroutines.withContext internal class DefaultWalletServerBinder( @@ -19,7 +21,12 @@ internal class DefaultWalletServerBinder( ) : WalletServerBinder { override suspend fun bind(userWalletId: UserWalletId): ApiResponse? { - val userWallet = userWalletsListRepository.getSyncOrNull(id = userWalletId) ?: return null + val userWallet = userWalletsListRepository.getSyncOrNull(id = userWalletId) + + if (userWallet == null) { + TangemLogger.e("bind wallet=$userWalletId: user wallet not found locally, skipping createWallet call") + return null + } return bind(userWallet) } @@ -31,6 +38,12 @@ internal class DefaultWalletServerBinder( tangemTechApi.createWallet( body = WalletIdBodyConverter.convert(userWallet, conversionData), ) + }.also { response -> + val eTag = response.headers[ETAG_HEADER]?.firstOrNull() + TangemLogger.i( + "bind wallet=${userWallet.walletId}: createWallet code=${(response as? ApiResponse.Success)?.code}, " + + "hasETag=${eTag != null}, eTagNotEmpty=${!eTag.isNullOrEmpty()}", + ) } } } \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt index c97999b6f1..cc090e4f0e 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt @@ -11,10 +11,11 @@ import com.tangem.core.navigation.finisher.AppFinisher import com.tangem.feature.tester.impl.R import com.tangem.feature.tester.presentation.common.components.appbar.TopBarWithRefreshUM import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureToggleGroupUM -import com.tangem.feature.tester.presentation.featuretoggles.state.TesterFeatureToggleUM import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesScreenUM +import com.tangem.feature.tester.presentation.featuretoggles.state.TesterFeatureToggleUM import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter import com.tangem.utils.info.AppInfoProvider +import com.tangem.utils.logging.TangemLogger import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -40,7 +41,7 @@ internal class FeatureTogglesViewModel @Inject constructor( ) : ViewModel() { // Declared before `state` so it is initialized before `initState()` runs in the field initializer. - private val appVersion: Version? = Version.create(appInfoProvider.appVersion) + private val appVersion: Version? = createAppVersion() val state: StateFlow field = MutableStateFlow(initState()) @@ -137,6 +138,20 @@ internal class FeatureTogglesViewModel @Inject constructor( .toImmutableList() } + private fun createAppVersion(): Version? { + val rawVersion = appInfoProvider.appVersion + // Drop the build-type suffix (e.g. "6.0-internal" -> "6.0") so the version parses, mirroring + // DefaultVersionProvider; otherwise every toggle falls back to the "Not planned yet" group. + val sanitizedVersion = rawVersion.substringBefore(delimiter = '-') + val version = Version.create(sanitizedVersion) + + if (version == null) { + TangemLogger.e("Failed to parse app version: raw=$rawVersion, sanitized=$sanitizedVersion") + } + + return version + } + private fun statusOf(toggleVersion: String): TesterFeatureToggleUM.Status { val toggle = parseToggleVersion(toggleVersion) ?: return TesterFeatureToggleUM.Status.UNDEFINED val app = appVersion ?: return TesterFeatureToggleUM.Status.UNDEFINED diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt index 8f98d6d139..5325ece800 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletWarningsClickIntents.kt @@ -60,7 +60,7 @@ internal interface WalletWarningsClickIntents { fun onCloseAlreadySignedHashesWarningClick() - fun onGenerateMissedAddressesClick(missedAddressCurrencies: List) + fun onGenerateMissedAddressesClick(userWalletId: UserWalletId, missedAddressCurrencies: List) fun onOpenUnlockWalletsBottomSheetClick() @@ -164,19 +164,20 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } } - override fun onGenerateMissedAddressesClick(missedAddressCurrencies: List) { + override fun onGenerateMissedAddressesClick( + userWalletId: UserWalletId, + missedAddressCurrencies: List, + ) { analyticsEventHandler.send(MainScreen.NoticeScanYourCardTapped()) modelScope.launch { - val userWallet = getSelectedUserWallet() ?: return@launch - derivePublicKeysUseCase( - userWalletId = userWallet.walletId, + userWalletId = userWalletId, currencies = missedAddressCurrencies, ).fold( ifLeft = { TangemLogger.e("Failed to derive public keys", it) }, ifRight = { - fetchCryptoCurrencies(userWalletId = userWallet.walletId, currencies = missedAddressCurrencies) + fetchCryptoCurrencies(userWalletId = userWalletId, currencies = missedAddressCurrencies) }, ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index 4b9931244c..b488d4e170 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -336,7 +336,10 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( tangemIcon = walletInterationIcon(userWallet), missingAddressesCount = currencies.count(), onGenerateClick = { - clickIntents.onGenerateMissedAddressesClick(missedAddressCurrencies = currencies) + clickIntents.onGenerateMissedAddressesClick( + userWalletId = userWallet.walletId, + missedAddressCurrencies = currencies, + ) }, ), condition = currencies.isNotEmpty(), diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt index 5085257879..a8b6e590d5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetWalletNotificationsFactory.kt @@ -215,7 +215,10 @@ internal class GetWalletNotificationsFactory @Inject constructor( tangemIcon = walletInterationIcon(userWallet), missingAddressesCount = currencies.count(), onGenerateClick = { - clickIntents.onGenerateMissedAddressesClick(missedAddressCurrencies = currencies) + clickIntents.onGenerateMissedAddressesClick( + userWalletId = userWallet.walletId, + missedAddressCurrencies = currencies, + ) }, ), condition = currencies.isNotEmpty(),