Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-30 20:48:10 +03:00
parent 5a8ebb312c
commit 047a48eaf0
12 changed files with 76 additions and 18 deletions

View file

@ -1,5 +1,6 @@
package com.tangem.tap.di.domain
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.tokens.repository.CurrenciesRepository
@ -129,12 +130,14 @@ internal object WalletsDomainModule {
userWalletsListRepository: UserWalletsListRepository,
hotWalletFeatureToggles: HotWalletFeatureToggles,
walletsRepository: WalletsRepository,
analyticsEventHandler: AnalyticsEventHandler,
): SaveWalletUseCase {
return SaveWalletUseCase(
userWalletsListManager = userWalletsListManager,
userWalletsListRepository = userWalletsListRepository,
walletsRepository = walletsRepository,
useNewRepository = hotWalletFeatureToggles.isHotWalletEnabled,
analyticsEventHandler = analyticsEventHandler,
)
}

View file

@ -21,6 +21,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.domain.wallets.R
import com.tangem.domain.wallets.analytics.WalletSettingsAnalyticEvents
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
@ -390,6 +391,7 @@ internal class DefaultUserWalletsListRepository(
if (newUserWallet.walletId == oldUserWallet.walletId &&
oldUserWallet is UserWallet.Hot && newUserWallet is UserWallet.Cold
) {
trackWalletUpgradeEvent()
removeHotWalletsFromSDKAndRepos(walletIds = listOf(oldUserWallet.walletId))
// When upgrading from Hot to Cold, if biometric lock is available, set it
if (hasBiometry()) {
@ -536,4 +538,8 @@ internal class DefaultUserWalletsListRepository(
),
)
}
private fun trackWalletUpgradeEvent() {
analyticsEventHandler.send(event = WalletSettingsAnalyticEvents.WalletUpgraded())
}
}

View file

@ -85,6 +85,7 @@ sealed class AnalyticsParam {
data object ImportWallet : ScreensSources("Import Wallet")
data object CreateWalletIntro : ScreensSources("Create Wallet Intro")
data object AddNewWallet : ScreensSources("Add New Wallet")
data object AddNew : ScreensSources("Add New")
data object CreateWallet : ScreensSources("Create Wallet")
}

View file

@ -1,6 +1,9 @@
package com.tangem.domain.wallets.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AppsFlyerIncludedEvent
import com.tangem.core.analytics.models.OneTimeAnalyticsEvent
sealed class Settings(
category: String = "Settings",
@ -9,4 +12,13 @@ sealed class Settings(
) : AnalyticsEvent(category, event, params) {
class ButtonManageTokens : Settings(event = "Button - Manage Tokens")
class ColdWalletAdded(
source: AnalyticsParam.ScreensSources?,
) : Settings(
event = "Cold Wallet Added",
params = mapOf(AnalyticsParam.SOURCE to (source?.value ?: "Unknown")),
), OneTimeAnalyticsEvent, AppsFlyerIncludedEvent {
override val oneTimeEventId: String = id
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION
import com.tangem.core.analytics.models.AnalyticsParam.Key.STATUS
import com.tangem.core.analytics.models.AppsFlyerIncludedEvent
sealed class WalletSettingsAnalyticEvents(
category: String = "Settings / Wallet Settings",
@ -166,6 +167,10 @@ sealed class WalletSettingsAnalyticEvents(
event = "Button - Start Upgrade",
)
class WalletUpgraded : WalletSettingsAnalyticEvents(
event = "Wallet Upgraded",
), AppsFlyerIncludedEvent
enum class RecoveryPhraseScreenAction(val value: String) {
Upgrade("Upgrade"),
Backup("Backup"),

View file

@ -6,9 +6,12 @@ import arrow.core.raise.either
import arrow.core.right
import com.tangem.common.doOnFailure
import com.tangem.common.doOnSuccess
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.domain.common.wallets.UserWalletsListRepository
import com.tangem.domain.common.wallets.error.SaveWalletError
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.analytics.Settings
import com.tangem.domain.wallets.legacy.UserWalletsListError
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.domain.wallets.repository.WalletsRepository
@ -16,8 +19,6 @@ import com.tangem.domain.wallets.repository.WalletsRepository
/**
* Use case for saving user wallet
*
* @property userWalletsListManager user wallets list manager
*
[REDACTED_AUTHOR]
*/
class SaveWalletUseCase(
@ -25,14 +26,21 @@ class SaveWalletUseCase(
private val userWalletsListRepository: UserWalletsListRepository,
private val walletsRepository: WalletsRepository,
private val useNewRepository: Boolean,
private val analyticsEventHandler: AnalyticsEventHandler,
) {
suspend operator fun invoke(userWallet: UserWallet, canOverride: Boolean = false): Either<SaveWalletError, Unit> {
suspend operator fun invoke(
userWallet: UserWallet,
canOverride: Boolean = false,
analyticsSource: AnalyticsParam.ScreensSources? = null,
): Either<SaveWalletError, Unit> {
return if (useNewRepository) {
either {
val newUserWallet =
userWalletsListRepository.userWalletsSync().none { it.walletId == userWallet.walletId }
val userWallet = userWalletsListRepository.saveWithoutLock(userWallet, canOverride).bind()
val userWallet = userWalletsListRepository.saveWithoutLock(userWallet, canOverride)
.onRight { trackColdWalletAddedIfNeeded(analyticsSource, it) }
.bind()
if (newUserWallet) {
when (userWallet) {
@ -76,4 +84,11 @@ class SaveWalletUseCase(
}
}
}
private suspend fun trackColdWalletAddedIfNeeded(source: AnalyticsParam.ScreensSources?, userWallet: UserWallet) {
val hasHotWallet = userWalletsListRepository.userWalletsSync().any { it is UserWallet.Hot }
if (hasHotWallet && userWallet is UserWallet.Cold) {
analyticsEventHandler.send(event = Settings.ColdWalletAdded(source))
}
}
}

View file

@ -75,7 +75,12 @@ internal class UserWalletSaver @Inject constructor(
private suspend fun Raise<Error>.saveWallet(userWallet: UserWallet) {
fold(
block = { saveWalletUseCase(userWallet).bind() },
block = {
saveWalletUseCase(
userWallet = userWallet,
analyticsSource = AnalyticsParam.ScreensSources.Settings,
).bind()
},
recover = { error ->
when (error) {
is SaveWalletError.WalletAlreadySaved -> {

View file

@ -133,7 +133,10 @@ internal class CreateHardwareWalletModel @Inject constructor(
return
}
saveWalletUseCase(userWallet = userWallet).fold(
saveWalletUseCase(
userWallet = userWallet,
analyticsSource = AnalyticsParam.ScreensSources.AddNew,
).fold(
ifLeft = { saveWalletError ->
delay(HIDE_PROGRESS_DELAY)
setLoading(false)

View file

@ -5,6 +5,7 @@ import arrow.core.getOrElse
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.ByteArrayKey
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -267,6 +268,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
scanResponse = scanResponse.updateScanResponseAfterBackup(),
),
canOverride = true,
analyticsSource = AnalyticsParam.ScreensSources.Onboarding,
)
userWalletCreated
}

View file

@ -4,6 +4,7 @@ import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.common.extensions.toHexString
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@ -173,7 +174,10 @@ internal class OnboardingVisaInProgressModel @Inject constructor(
}
val userWallet = createUserWallet(params.scanResponse, newTokens)
saveWalletUseCase(userWallet)
saveWalletUseCase(
userWallet = userWallet,
analyticsSource = AnalyticsParam.ScreensSources.Onboarding,
)
visaAuthTokenStorage.remove(params.scanResponse.card.cardId)
otpStorage.removeOTP(params.scanResponse.card.cardId)

View file

@ -72,7 +72,7 @@ sealed class WalletScreenAnalyticsEvent {
}
put("Wallet Type", seedPhrase)
},
)
), AppsFlyerIncludedEvent
data class NoticeFinishActivation(
private val activationState: ActivationState,

View file

@ -197,8 +197,10 @@ internal class WelcomeModel @Inject constructor(
onSuccess = { scanResponse ->
val userWallet =
coldUserWalletBuilderFactory.create(scanResponse = scanResponse).build() ?: return@scan
saveWalletUseCase.invoke(userWallet)
.onLeft { error ->
saveWalletUseCase.invoke(
userWallet = userWallet,
analyticsSource = AnalyticsParam.ScreensSources.SignIn,
).onLeft { error ->
if (error is SaveWalletError.WalletAlreadySaved) {
userWalletsListRepository.unlock(
userWallet.walletId,