Updated on 2026-08-14

This commit is contained in:
Tangem 2026-05-20 12:30:46 +04:00
parent 29f55aae33
commit 9678c49b37
17 changed files with 151 additions and 57 deletions

View file

@ -108,6 +108,7 @@ sealed class AnalyticsParam {
override val token: String,
override val feeType: FeeType,
override val feeToken: String,
override val feeAssetType: FeeAssetType,
) : TxSentFrom("Send"), TxData
data class Swap(
@ -115,6 +116,7 @@ sealed class AnalyticsParam {
override val token: String,
override val feeType: FeeType,
override val feeToken: String,
override val feeAssetType: FeeAssetType,
) : TxSentFrom("Swap"), TxData
data class Staking(
@ -122,6 +124,7 @@ sealed class AnalyticsParam {
override val token: String,
override val feeType: FeeType,
override val feeToken: String,
override val feeAssetType: FeeAssetType,
) : TxSentFrom("Staking"), TxData
data class Approve(
@ -129,6 +132,7 @@ sealed class AnalyticsParam {
override val token: String,
override val feeType: FeeType,
override val feeToken: String,
override val feeAssetType: FeeAssetType,
val permissionType: String,
) : TxSentFrom("Approve"), TxData
@ -137,6 +141,7 @@ sealed class AnalyticsParam {
override val token: String,
override val feeType: FeeType?,
override val feeToken: String,
override val feeAssetType: FeeAssetType,
) : TxSentFrom("WalletConnect"), TxData
data object Sell : TxSentFrom("Sell")
@ -146,6 +151,7 @@ sealed class AnalyticsParam {
override val token: String,
override val feeType: FeeType,
override val feeToken: String,
override val feeAssetType: FeeAssetType,
) : TxSentFrom("NFT"), TxData
data class SendWithSwap(
@ -153,6 +159,7 @@ sealed class AnalyticsParam {
override val token: String,
override val feeType: FeeType,
override val feeToken: String,
override val feeAssetType: FeeAssetType,
) : TxSentFrom("Send&Swap"), TxData
data class Earning(
@ -160,6 +167,7 @@ sealed class AnalyticsParam {
override val token: String,
override val feeType: FeeType,
override val feeToken: String,
override val feeAssetType: FeeAssetType,
) : TxSentFrom("Earning"), TxData
}
@ -168,6 +176,12 @@ sealed class AnalyticsParam {
val token: String
val feeToken: String
val feeType: FeeType?
val feeAssetType: FeeAssetType
}
sealed class FeeAssetType(val value: String) {
data object Coin : FeeAssetType("Coin")
data object Token : FeeAssetType("Token")
}
sealed class FeeType(val value: String) {
@ -298,6 +312,7 @@ sealed class AnalyticsParam {
const val SEARCHED = "Searched"
const val RATE_TYPE = "Rate Type"
const val SCREEN_TYPE = "Screen Type"
const val FEE_ASSET_TYPE = "Fee Asset Type"
}
}

View file

@ -94,6 +94,7 @@ sealed class Basic(
this[AnalyticsParam.Key.FEE_TYPE] = it
}
this[AnalyticsParam.Key.FEE_TOKEN] = sentFrom.feeToken
this[AnalyticsParam.Key.FEE_ASSET_TYPE] = sentFrom.feeAssetType.value
}
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
this[AnalyticsParam.Key.PERMISSION_TYPE] = sentFrom.permissionType

View file

@ -7,6 +7,7 @@ import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.formatHex
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.TxSentFrom
import com.tangem.core.analytics.models.Basic
import com.tangem.core.analytics.models.Basic.TransactionSent.MemoType
@ -91,6 +92,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
token = network.currencySymbol,
feeType = null,
feeToken = network.currencySymbol,
feeAssetType = AnalyticsParam.FeeAssetType.Coin,
),
memoType = MemoType.Null,
),

View file

@ -46,11 +46,13 @@ sealed class StakingAnalyticsEvent(
data class StakeInProgressScreenOpened(
val validator: String,
val action: StakingActionType,
val feeAssetType: AnalyticsParam.FeeAssetType,
) : StakingAnalyticsEvent(
event = "Stake In Progress Screen Opened",
params = mapOf(
"Validator" to validator,
"Action" to action.asAnalyticName,
AnalyticsParam.Key.FEE_ASSET_TYPE to feeAssetType.value,
),
), AppsFlyerIncludedEvent

View file

@ -157,6 +157,7 @@ sealed class WcAnalyticEvents(
network: Network,
securityStatus: CheckDAppResult,
accountDerivation: Int?,
feeAssetType: AnalyticsParam.FeeAssetType = AnalyticsParam.FeeAssetType.Coin,
) : WcAnalyticEvents(
event = "Signature Request Handled",
params = buildMap {
@ -168,6 +169,7 @@ sealed class WcAnalyticEvents(
accountDerivation?.let {
put(AnalyticsParam.ACCOUNT_DERIVATION, it.toString())
}
put(AnalyticsParam.Key.FEE_ASSET_TYPE, feeAssetType.value)
},
), AppsFlyerIncludedEvent

View file

@ -266,7 +266,13 @@ internal class GiveApprovalModel @Inject constructor(
private fun sendApproveSuccessAnalytics(feeContent: FeeSelectorUM.Content) {
val currency = params.cryptoCurrencyStatus.currency
val feeToken = feeContent.feeExtraInfo.feeCryptoCurrencyStatus.currency.symbol
val feeCurrency = feeContent.feeExtraInfo.feeCryptoCurrencyStatus.currency
val feeToken = feeCurrency.symbol
val feeAssetType = if (feeCurrency is CryptoCurrency.Coin) {
AnalyticsParam.FeeAssetType.Coin
} else {
AnalyticsParam.FeeAssetType.Token
}
val permissionType = when (uiState.value.approveType) {
ApproveType.LIMITED -> "Current transaction"
ApproveType.UNLIMITED -> "Unlimited"
@ -276,6 +282,7 @@ internal class GiveApprovalModel @Inject constructor(
token = currency.symbol,
feeType = feeContent.toAnalyticType(),
feeToken = feeToken,
feeAssetType = feeAssetType,
permissionType = permissionType,
)
analyticsEventHandler.send(

View file

@ -30,6 +30,7 @@ internal sealed class SendAnalyticEvents(
val isNonceNotEmpty: Boolean,
private val ensStatus: AnalyticsParam.EmptyFull,
private val feeToken: String,
private val feeAssetType: AnalyticsParam.FeeAssetType,
private val fromDerivationIndex: Int?,
private val toDerivationIndex: Int?,
) : SendAnalyticEvents(
@ -47,6 +48,7 @@ internal sealed class SendAnalyticEvents(
}
put(ENS_ADDRESS, ensAddress)
put(FEE_TOKEN, feeToken)
put(AnalyticsParam.Key.FEE_ASSET_TYPE, feeAssetType.value)
},
), AppsFlyerIncludedEvent

View file

@ -31,6 +31,11 @@ internal class SendAnalyticHelper @Inject constructor(
val feeSelectorUM = sendUM.feeSelectorUM as? FeeSelectorUM.Content ?: return
val feeType = feeSelectorUM.toAnalyticType()
val feeTokenSymbol = feeToken.symbol
val feeAssetType = if (feeToken is CryptoCurrency.Coin) {
AnalyticsParam.FeeAssetType.Coin
} else {
AnalyticsParam.FeeAssetType.Token
}
val fromDerivationIndex = account?.derivationIndex?.value
val destination = destinationUM?.addressTextField?.actualAddress ?: return
val destinationAccount = getAccountCurrencyByAddressUseCase(destination)
@ -46,6 +51,7 @@ internal class SendAnalyticHelper @Inject constructor(
fromDerivationIndex = fromDerivationIndex,
toDerivationIndex = toDerivationIndex,
feeToken = feeTokenSymbol,
feeAssetType = feeAssetType,
),
)
analyticsEventHandler.send(
@ -55,6 +61,7 @@ internal class SendAnalyticHelper @Inject constructor(
token = cryptoCurrency.symbol,
feeType = feeType,
feeToken = feeTokenSymbol,
feeAssetType = feeAssetType,
),
memoType = getSendTransactionMemoType(destinationUM.memoTextField),
),

View file

@ -37,6 +37,7 @@ internal class NFTSendAnalyticHelper @Inject constructor(
token = NFT_SEND_CATEGORY, // should send "NFT" in token param
feeType = feeType,
feeToken = cryptoCurrency.symbol,
feeAssetType = AnalyticsParam.FeeAssetType.Coin,
),
memoType = getSendTransactionMemoType(destinationUM?.memoTextField),
),

View file

@ -73,6 +73,7 @@ internal class StakingAnalyticSender(
feeType = AnalyticsParam.FeeType.Normal,
permissionType = ApproveType.LIMITED.name,
feeToken = tokenCryptoCurrency.symbol,
feeAssetType = AnalyticsParam.FeeAssetType.Coin,
),
memoType = Basic.TransactionSent.MemoType.Null,
),
@ -82,7 +83,7 @@ internal class StakingAnalyticSender(
fun sendTransactionStakingAnalytics(value: StakingUiState, cryptoCurrencyStatus: CryptoCurrencyStatus) {
val validatorState = value.validatorState as? StakingStates.ValidatorState.Data
val validatorName = validatorState?.chosenTarget?.name ?: return
val feeAssetType = AnalyticsParam.FeeAssetType.Coin // support only coin as fee asset for staking transactions
analyticsEventHandler.send(
Basic.TransactionSent(
sentFrom = AnalyticsParam.TxSentFrom.Staking(
@ -90,6 +91,7 @@ internal class StakingAnalyticSender(
token = value.cryptoCurrencySymbol,
feeType = AnalyticsParam.FeeType.Normal,
feeToken = cryptoCurrencyStatus.currency.symbol,
feeAssetType = feeAssetType,
),
memoType = Basic.TransactionSent.MemoType.Null,
),
@ -98,6 +100,7 @@ internal class StakingAnalyticSender(
StakingAnalyticsEvent.StakeInProgressScreenOpened(
validator = validatorName,
action = getStakingActionType(value),
feeAssetType = feeAssetType,
),
)
}

View file

@ -61,6 +61,7 @@ internal sealed class SendWithSwapAnalyticEvents(
val feeType: AnalyticsParam.FeeType,
val fromToken: CryptoCurrency,
val toToken: CryptoCurrency,
val feeAssetType: AnalyticsParam.FeeAssetType,
val fromDerivationIndex: Int?,
val toDerivationIndex: Int?,
) : SendWithSwapAnalyticEvents(
@ -74,6 +75,7 @@ internal sealed class SendWithSwapAnalyticEvents(
put(RECEIVE_BLOCKCHAIN, toToken.network.name)
if (fromDerivationIndex != null) put(ACCOUNT_DERIVATION_FROM, fromDerivationIndex.toString())
if (toDerivationIndex != null) put(ACCOUNT_DERIVATION_TO, toDerivationIndex.toString())
put(AnalyticsParam.Key.FEE_ASSET_TYPE, feeAssetType.value)
},
), AppsFlyerIncludedEvent

View file

@ -503,12 +503,19 @@ internal class SendWithSwapConfirmModel @Inject constructor(
.getOrNull()?.account
val toDerivationIndex = destinationAccount?.derivationIndex?.value
val feeToken = getSelectedFeeToken()
val feeAssetType = if (feeToken is CryptoCurrency.Coin) {
AnalyticsParam.FeeAssetType.Coin
} else {
AnalyticsParam.FeeAssetType.Token
}
analyticsEventHandler.send(
SendWithSwapAnalyticEvents.TransactionScreenOpened(
providerName = selectedProvider.name,
feeType = feeType,
fromToken = fromCurrency,
toToken = toCurrency,
feeAssetType = feeAssetType,
fromDerivationIndex = fromDerivationIndex,
toDerivationIndex = toDerivationIndex,
),
@ -519,7 +526,8 @@ internal class SendWithSwapConfirmModel @Inject constructor(
blockchain = fromCurrency.network.name,
token = fromCurrency.symbol,
feeType = feeType,
feeToken = getSelectedFeeToken().symbol,
feeToken = feeToken.symbol,
feeAssetType = feeAssetType,
),
memoType = Basic.TransactionSent.MemoType.Null,
),

View file

@ -5,6 +5,7 @@ import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_FR
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACCOUNT_DERIVATION_TO
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_MESSAGE
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.AnalyticsParam.Key.FEE_TOKEN
import com.tangem.core.analytics.models.AnalyticsParam.Key.PROVIDER
import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_TOKEN
@ -103,6 +104,7 @@ sealed class SwapEvents(
val sendToken: String,
val receiveToken: String,
val feeToken: String,
val feeAssetType: AnalyticsParam.FeeAssetType,
val fromDerivationIndex: Int?,
val toDerivationIndex: Int?,
val referralId: String?,
@ -118,6 +120,7 @@ sealed class SwapEvents(
if (fromDerivationIndex != null) put(ACCOUNT_DERIVATION_FROM, fromDerivationIndex.toString())
if (toDerivationIndex != null) put(ACCOUNT_DERIVATION_TO, toDerivationIndex.toString())
put(FEE_TOKEN, feeToken)
put(AnalyticsParam.Key.FEE_ASSET_TYPE, feeAssetType.value)
putAll(getReferralParams(referralId))
},
), AppsFlyerIncludedEvent

View file

@ -1093,7 +1093,7 @@ internal class SwapModel @Inject constructor(
}
},
)
sendSuccessEvent()
sendSwapInProgressEvent()
router.replaceAll(SwapRoute.Success)
}
@ -1169,7 +1169,7 @@ internal class SwapModel @Inject constructor(
}
}
private suspend fun sendSuccessEvent() {
private suspend fun sendSwapInProgressEvent() {
val provider = dataState.selectedProvider ?: return
val fee = (getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL
val fromSwapCurrencyStatus = dataState.fromSwapCurrencyStatus ?: return
@ -1177,6 +1177,12 @@ internal class SwapModel @Inject constructor(
val fromDerivationIndex = fromSwapCurrencyStatus.account.derivationIndex?.value
val toDerivationIndex = toSwapCurrencyStatus.account.derivationIndex?.value
val feeToken = getFeeToken()
val feeAssetType = if (feeToken is CryptoCurrency.Coin) {
AnalyticsParam.FeeAssetType.Coin
} else {
AnalyticsParam.FeeAssetType.Token
}
analyticsEventHandler.send(
SwapEvents.SwapInProgressScreen(
provider = provider,
@ -1185,7 +1191,8 @@ internal class SwapModel @Inject constructor(
receiveBlockchain = toSwapCurrencyStatus.currency.network.name,
sendToken = fromSwapCurrencyStatus.currency.symbol,
receiveToken = toSwapCurrencyStatus.currency.symbol,
feeToken = getFeeToken().symbol,
feeToken = feeToken.symbol,
feeAssetType = feeAssetType,
fromDerivationIndex = fromDerivationIndex,
toDerivationIndex = toDerivationIndex,
referralId = appsFlyerStore.get()?.refcode,
@ -1558,11 +1565,18 @@ internal class SwapModel @Inject constructor(
}
private fun sendSuccessSwapEvent(fromToken: CryptoCurrency, feeType: FeeType) {
val feeToken = getFeeToken()
val feeAssetType = if (feeToken is CryptoCurrency.Coin) {
AnalyticsParam.FeeAssetType.Coin
} else {
AnalyticsParam.FeeAssetType.Token
}
val event = AnalyticsParam.TxSentFrom.Swap(
blockchain = fromToken.network.name,
token = fromToken.symbol,
feeType = AnalyticsParam.FeeType.fromString(feeType.getNameForAnalytics()),
feeToken = getFeeToken().symbol,
feeToken = feeToken.symbol,
feeAssetType = feeAssetType,
)
analyticsEventHandler.send(
Basic.TransactionSent(

View file

@ -25,6 +25,7 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.isHotWallet
import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetContractAddressUseCase
@ -141,59 +142,69 @@ internal class YieldSupplyApproveModel @Inject constructor(
userWallet = userWallet,
network = cryptoCurrency.network,
).fold(
ifLeft = { error ->
TangemLogger.e(error.toString())
uiState.update(YieldSupplyTransactionReadyTransformer)
analyticsEventHandler.send(
YieldSupplyAnalytics.EarnErrors(
action = YieldSupplyAnalytics.Action.Approve,
errorDescription = error.getAnalyticsDescription(),
),
)
yieldSupplyAlertFactory.getSendTransactionErrorState(
error = error,
popBack = params.callback::onDismissClick,
onFailedTxEmailClick = { errorMessage ->
modelScope.launch(dispatchers.default) {
yieldSupplyAlertFactory.onFailedTxEmailClick(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
errorMessage = errorMessage,
)
}
},
)
params.callback.onTransactionProgress(false)
},
ifRight = { txHash ->
yieldSupplyPendingTracker.addPending(
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrency,
txIds = listOf(txHash),
)
val event = AnalyticsParam.TxSentFrom.Earning(
blockchain = cryptoCurrency.network.name,
token = cryptoCurrency.symbol,
feeType = AnalyticsParam.FeeType.Normal,
feeToken = feeCryptoCurrencyStatus.currency.symbol,
)
analyticsEventHandler.send(
Basic.TransactionSent(
sentFrom = event,
memoType = Basic.TransactionSent.MemoType.Null,
),
)
analyticsEventHandler.send(YieldSupplyAnalytics.ApprovalAction(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
action = YieldSupplyAnalytics.Action.Approve,
))
params.callback.onTransactionSent()
},
ifLeft = ::onTransactionError,
ifRight = { onTransactionSuccess(it) },
)
}
}
private fun onTransactionError(error: SendTransactionError) {
TangemLogger.e(error.toString())
uiState.update(YieldSupplyTransactionReadyTransformer)
analyticsEventHandler.send(
YieldSupplyAnalytics.EarnErrors(
action = YieldSupplyAnalytics.Action.Approve,
errorDescription = error.getAnalyticsDescription(),
),
)
yieldSupplyAlertFactory.getSendTransactionErrorState(
error = error,
popBack = params.callback::onDismissClick,
onFailedTxEmailClick = { errorMessage ->
modelScope.launch(dispatchers.default) {
yieldSupplyAlertFactory.onFailedTxEmailClick(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
errorMessage = errorMessage,
)
}
},
)
params.callback.onTransactionProgress(false)
}
private suspend fun onTransactionSuccess(txHash: String) {
yieldSupplyPendingTracker.addPending(
userWalletId = userWallet.walletId,
cryptoCurrency = cryptoCurrency,
txIds = listOf(txHash),
)
val feeAssetType = if (feeCryptoCurrencyStatus.currency is CryptoCurrency.Coin) {
AnalyticsParam.FeeAssetType.Coin
} else {
AnalyticsParam.FeeAssetType.Token
}
val event = AnalyticsParam.TxSentFrom.Earning(
blockchain = cryptoCurrency.network.name,
token = cryptoCurrency.symbol,
feeType = AnalyticsParam.FeeType.Normal,
feeToken = feeCryptoCurrencyStatus.currency.symbol,
feeAssetType = feeAssetType,
)
analyticsEventHandler.send(
Basic.TransactionSent(
sentFrom = event,
memoType = Basic.TransactionSent.MemoType.Null,
),
)
analyticsEventHandler.send(YieldSupplyAnalytics.ApprovalAction(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
action = YieldSupplyAnalytics.Action.Approve,
))
params.callback.onTransactionSent()
}
private fun subscribeOnCurrencyStatusUpdates() {
modelScope.launch {
feeCryptoCurrencyStatusFlow.update {

View file

@ -17,6 +17,7 @@ import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSy
import com.tangem.domain.account.status.utils.CryptoCurrencyStatusOperations.getCryptoCurrencyStatus
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isHotWallet
@ -267,11 +268,18 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
cryptoCurrency = cryptoCurrency,
yieldSupplyPendingStatus = YieldSupplyPendingStatus.Enter(txsData),
)
val feeCurrency = feeCryptoCurrencyStatusFlow.value.currency
val feeAssetType = if (feeCurrency is CryptoCurrency.Coin) {
AnalyticsParam.FeeAssetType.Coin
} else {
AnalyticsParam.FeeAssetType.Token
}
val event = AnalyticsParam.TxSentFrom.Earning(
blockchain = cryptoCurrency.network.name,
token = cryptoCurrency.symbol,
feeType = AnalyticsParam.FeeType.Normal,
feeToken = feeCryptoCurrencyStatusFlow.value.currency.symbol,
feeToken = feeCurrency.symbol,
feeAssetType = feeAssetType,
)
analytics.send(
YieldSupplyAnalytics.FundsEarned(

View file

@ -195,11 +195,17 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
referralId = appsFlyerStore.get()?.refcode,
),
)
val feeAssetType = if (feeCryptoCurrencyStatus.currency is CryptoCurrency.Coin) {
AnalyticsParam.FeeAssetType.Coin
} else {
AnalyticsParam.FeeAssetType.Token
}
val event = AnalyticsParam.TxSentFrom.Earning(
blockchain = cryptoCurrency.network.name,
token = cryptoCurrency.symbol,
feeType = AnalyticsParam.FeeType.Normal,
feeToken = feeCryptoCurrencyStatus.currency.symbol,
feeAssetType = feeAssetType,
)
analytics.send(
Basic.TransactionSent(