Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-04 15:18:02 +04:00
parent 85545863e3
commit 926e89e975
16 changed files with 158 additions and 223 deletions

View file

@ -239,6 +239,13 @@ sealed class AnalyticsParam {
MobileWallet("Mobile Wallet"),
}
enum class SignInType(val value: String) {
Card("Card"),
Biometric("Biometric"),
NoSecurity("No Security"),
AccessCode("Access Code"),
}
companion object Key {
const val BLOCKCHAIN = "Blockchain"
const val TOKEN_PARAM = "Token"
@ -297,6 +304,11 @@ sealed class AnalyticsParam {
const val REFERRAL_ID = "Referral_ID"
const val SEARCHED = "Searched"
const val RATE_TYPE = "Rate Type"
const val SIGN_IN_TYPE = "Sign in type"
const val WALLETS_COUNT = "Wallets Count"
const val WALLET_TYPE = "Wallet Type"
const val BACKUPED = "Backuped"
const val MEMO = "Memo"
}
}

View file

@ -2,65 +2,41 @@ package com.tangem.core.analytics.models
sealed class Basic(
event: String,
params: Map<String, String> = mapOf(),
) : AnalyticsEvent("Basic", event, params) {
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent(category = "Basic", event = event, params = params) {
/**
* Tracks card scanning from specific entry points (Introduction, Main, My Wallets, Sign In).
* The originating screen is reported via the [AnalyticsParam.SOURCE] parameter.
*/
class CardWasScanned(
source: AnalyticsParam.ScreensSources,
) : Basic(
event = "Card Was Scanned",
params = mapOf(
AnalyticsParam.Key.SOURCE to source.value,
AnalyticsParam.SOURCE to source.value,
),
)
class SignedInLegacy(
currency: AnalyticsParam.WalletType,
batch: String,
signInType: SignInType,
walletsCount: String,
isImported: Boolean,
hasBackup: Boolean?,
) : Basic(
event = "Signed in",
params = buildMap {
put(AnalyticsParam.Key.CURRENCY, currency.value)
put(AnalyticsParam.Key.BATCH, batch)
put("Wallet Type", if (isImported) "Seed Phrase" else "Seedless")
put("Sign in type", signInType.name)
put("Wallets Count", walletsCount)
if (hasBackup != null) {
put("Backuped", if (hasBackup) "Yes" else "No")
}
},
) {
enum class SignInType {
Card, Biometric
}
}
), CriticalEvent
/**
* Tracks any sign-in into a wallet (card scan, FaceID, or wallet switch).
* Counted as a single sign-in per session subsequent card scans within the same session are ignored.
*/
class SignedIn(
signInType: SignInType,
signInType: AnalyticsParam.SignInType,
walletsCount: Int,
isImported: Boolean,
hasBackup: Boolean?,
isBackedUp: Boolean,
) : Basic(
event = "Signed in",
params = buildMap {
put("Sign in type", signInType.value)
put("Wallets Count", walletsCount.toString())
put("Wallet Type", if (isImported) "Seed Phrase" else "Seedless")
if (hasBackup != null) {
put("Backuped", if (hasBackup) "Yes" else "No")
}
put(AnalyticsParam.SIGN_IN_TYPE, signInType.value)
put(AnalyticsParam.WALLETS_COUNT, walletsCount.toString())
put(AnalyticsParam.WALLET_TYPE, if (isImported) "Seed Phrase" else "Seedless")
put(AnalyticsParam.BACKUPED, if (isBackedUp) "Yes" else "No")
},
) {
enum class SignInType(val value: String) {
Card("Card"),
Biometric("Biometric"),
NoSecurity("No Security"),
AccessCode("Access Code"),
}
), CriticalEvent, OneTimePerSessionEvent {
override val oneTimeEventId: String = id
}
class ButtonBuy(
@ -68,39 +44,46 @@ sealed class Basic(
) : Basic(
event = "Button - Buy",
params = buildMap {
put(AnalyticsParam.Key.SOURCE, source.value)
put(AnalyticsParam.SOURCE, source.value)
},
)
class ToppedUp(userWalletId: String, currency: AnalyticsParam.WalletType) :
/**
* Tracks the first time a user wallet is topped up. Sent once per wallet, when the balance
* transitions from zero to positive (Total Balance for multi-currency wallets, or Balance for Note).
* A wallet scanned with a non-zero balance does not count as a top-up the event must be sent
* only after all tokens have finished loading.
*/
class ToppedUp(userWalletId: String, walletType: AnalyticsParam.WalletType) :
Basic(
event = "Topped up",
params = mapOf(AnalyticsParam.Key.CURRENCY to currency.value),
params = mapOf(AnalyticsParam.CURRENCY to walletType.value),
),
OneTimeAnalyticsEvent {
OneTimeAnalyticsEvent, AppsFlyerIncludedEvent, CriticalEvent {
override val oneTimeEventId: String = id + userWalletId
}
/**
* Tracks transaction submission from various screens (Send, Swap, WalletConnect, Sell, Approve, Staking).
*/
class TransactionSent(sentFrom: AnalyticsParam.TxSentFrom, memoType: MemoType) :
Basic(
event = "Transaction sent",
params = buildMap {
this[AnalyticsParam.Key.SOURCE] = sentFrom.value
put(AnalyticsParam.SOURCE, sentFrom.value)
if (sentFrom is AnalyticsParam.TxData) {
this[AnalyticsParam.Key.BLOCKCHAIN] = sentFrom.blockchain
this[AnalyticsParam.Key.TOKEN_PARAM] = sentFrom.token
sentFrom.feeType?.value?.let {
this[AnalyticsParam.Key.FEE_TYPE] = it
}
this[AnalyticsParam.Key.FEE_TOKEN] = sentFrom.feeToken
put(AnalyticsParam.BLOCKCHAIN, sentFrom.blockchain)
put(AnalyticsParam.TOKEN_PARAM, sentFrom.token)
sentFrom.feeType?.value?.let { put(AnalyticsParam.FEE_TYPE, it) }
put(AnalyticsParam.FEE_TOKEN, sentFrom.feeToken)
}
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
this[AnalyticsParam.Key.PERMISSION_TYPE] = sentFrom.permissionType
put(AnalyticsParam.PERMISSION_TYPE, sentFrom.permissionType)
}
this["Memo"] = memoType.name
put(AnalyticsParam.MEMO, memoType.name)
},
), AppsFlyerIncludedEvent {
), AppsFlyerIncludedEvent, CriticalEvent {
enum class MemoType {
Empty, Full, Null
}
@ -110,31 +93,33 @@ sealed class Basic(
}
}
/**
* Tracks the user invoking the "Request Support" email flow from various screens of the app.
*/
class ButtonSupport(source: AnalyticsParam.ScreensSources) : Basic(
event = "Request Support",
params = mapOf(
AnalyticsParam.Key.SOURCE to source.value,
AnalyticsParam.SOURCE to source.value,
),
), CriticalEvent
/**
* Tracks loading of the user's total balance after sign-in. Reports whether the balance is
* empty, has funds, failed to load, or could not be returned because of a custom token.
*/
class BalanceLoaded(balance: AnalyticsParam.CardBalanceState, tokensCount: Int?) : Basic(
event = "Balance Loaded",
params = buildMap {
put(AnalyticsParam.BALANCE, balance.value)
tokensCount?.let { put(AnalyticsParam.TOKENS_COUNT, it.toString()) }
},
), AppsFlyerIncludedEvent, CriticalEvent
class TokenBalance(balance: AnalyticsParam.EmptyFull, token: String) : Basic(
event = "Token Balance",
params = mapOf(
AnalyticsParam.STATE to balance.value,
AnalyticsParam.TOKEN_PARAM to token,
),
)
class BiometryFailed(
source: AnalyticsParam.ScreensSources,
reason: BiometricFailReason,
) : Basic(
event = "Biometry Failed",
params = mapOf(
AnalyticsParam.Key.SOURCE to source.value,
"Reason" to reason.value,
),
) {
sealed class BiometricFailReason(val value: String) {
data object AuthenticationLockout : BiometricFailReason("BiometricsAuthenticationLockout")
data object AuthenticationLockoutPermanent : BiometricFailReason("BiometricsAuthenticationLockoutPermanent")
data object BiometricsAuthenticationDisabled : BiometricFailReason("BiometricsAuthenticationDisabled")
data object AllKeysInvalidated : BiometricFailReason("AllKeysInvalidated")
data object AuthenticationCancelled : BiometricFailReason("AuthenticationCancelled")
data object AuthenticationAlreadyInProgress : BiometricFailReason("AuthenticationAlreadyInProgress")
data class Other(val reason: String) : BiometricFailReason(reason)
}
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.core.analytics.models
/**
* Marker interface for analytics events that require special attention.
*
* Implemented by events listed in the analytics specification (events.csv).
*/
interface CriticalEvent

View file

@ -2,20 +2,22 @@ package com.tangem.core.analytics.models.event
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.CriticalEvent
sealed class SignIn(
event: String,
params: Map<String, String> = emptyMap(),
) : AnalyticsEvent("Sign In", event, params) {
) : AnalyticsEvent(category = "Sign In", event = event, params = params) {
data class ScreenOpened(
val walletsCount: Int,
) : SignIn(
/**
* Tracks the user landing on the app's sign-in screen when a saved card exists.
*/
class ScreenOpened(walletsCount: Int) : SignIn(
event = "Sign In Screen Opened",
params = mapOf(
"Wallets Count" to walletsCount.toString(),
AnalyticsParam.WALLETS_COUNT to walletsCount.toString(),
),
)
), CriticalEvent
class ButtonBiometricSignIn : SignIn(event = "Button - Biometric Sign In")
@ -26,24 +28,17 @@ sealed class SignIn(
) : SignIn(event = "Error - Biometric Updated")
class ButtonWallet(
signInType: SignInType,
signInType: AnalyticsParam.SignInType,
walletsCount: Int,
) : SignIn(
event = "Button - Wallet",
params = buildMap {
put("Wallets Count", walletsCount.toString())
put("Sign in type", signInType.value)
put(AnalyticsParam.WALLETS_COUNT, walletsCount.toString())
put(AnalyticsParam.SIGN_IN_TYPE, signInType.value)
},
) {
enum class SignInType(val value: String) {
Card("Card"),
Biometric("Biometric"),
NoSecurity("No Security"),
AccessCode("Access Code"),
}
}
)
data class ButtonAddWallet(
class ButtonAddWallet(
val sources: AnalyticsParam.ScreensSources,
) : SignIn(
event = "Button - Add Wallet",