Updated on 2026-08-14

This commit is contained in:
Tangem 2023-05-31 09:58:40 +03:00
parent e182d88ebe
commit 73ba136063
13 changed files with 274 additions and 100 deletions

View file

@ -87,12 +87,54 @@ sealed class AnalyticsParam {
}
sealed class TxSentFrom(val value: String) {
object Send : TxSentFrom("Send")
object Swap : TxSentFrom("Swap")
data class Send(
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
) : TxSentFrom("Send"), TxData
data class Swap(
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
) : TxSentFrom("Swap"), TxData
data class Approve(
override val blockchain: String,
override val token: String,
override val feeType: FeeType,
val permissionType: String,
) : TxSentFrom("Approve"), TxData
object WalletConnect : TxSentFrom("WalletConnect")
object Sell : TxSentFrom("Sell")
}
sealed interface TxData {
val blockchain: String
val token: String
val feeType: FeeType
}
sealed class FeeType(val value: String) {
object Fixed : FeeType("Fixed")
object Min : FeeType("Min")
object Normal : FeeType("Normal")
object Max : FeeType("Max")
companion object {
fun fromString(feeType: String): FeeType {
return when (feeType) {
Min.value -> Min
Normal.value -> Normal
Max.value -> Max
Fixed.value -> Fixed
else -> Fixed
}
}
}
}
sealed class WalletCreationType(val value: String) {
object PrivateKey : WalletCreationType("Private key")
object NewSeed : WalletCreationType("New seed")
@ -100,9 +142,13 @@ sealed class AnalyticsParam {
}
companion object Key {
const val BLOCKCHAIN = "blockchain"
const val TOKEN = "Token"
const val SOURCE = "Source"
const val BALANCE = "Balance"
const val BATCH = "Batch"
const val FEE_TYPE = "Fee Type"
const val PERMISSION_TYPE = "Permission Type"
const val PRODUCT_TYPE = "Product Type"
const val FIRMWARE = "Firmware"
const val CURRENCY = "Currency"

View file

@ -51,7 +51,17 @@ sealed class Basic(
class TransactionSent(sentFrom: AnalyticsParam.TxSentFrom) : Basic(
event = "Transaction sent",
params = mapOf(AnalyticsParam.SOURCE to sentFrom.value),
params = buildMap {
this[AnalyticsParam.SOURCE] = sentFrom.value
if (sentFrom is AnalyticsParam.TxData) {
this[AnalyticsParam.BLOCKCHAIN] = sentFrom.blockchain
this[AnalyticsParam.TOKEN] = sentFrom.token
this[AnalyticsParam.FEE_TYPE] = sentFrom.feeType.value
}
if (sentFrom is AnalyticsParam.TxSentFrom.Approve) {
this[AnalyticsParam.PERMISSION_TYPE] = sentFrom.permissionType
}
},
)
class ScanError(error: Throwable) : Basic(

View file

@ -7,28 +7,20 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider
import com.tangem.blockchain.blockchains.stellar.StellarTransactionExtras
import com.tangem.blockchain.blockchains.ton.TonTransactionExtras
import com.tangem.blockchain.blockchains.xrp.XrpTransactionBuilder
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.TransactionError
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.services.Result
import com.tangem.core.analytics.Analytics
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.extensions.withMainContext
import com.tangem.tap.DELAY_SDK_DIALOG_CLOSE
import com.tangem.domain.models.scan.CardDTO
import com.tangem.tap.*
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Basic
import com.tangem.tap.common.analytics.events.Token
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppDialog
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
@ -39,25 +31,11 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessage
import com.tangem.tap.domain.extensions.minimalAmount
import com.tangem.tap.features.demo.DemoTransactionSender
import com.tangem.tap.features.demo.isDemoCard
import com.tangem.tap.features.send.redux.AddressPayIdActionUi
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction
import com.tangem.tap.features.send.redux.AmountAction
import com.tangem.tap.features.send.redux.AmountActionUi
import com.tangem.tap.features.send.redux.*
import com.tangem.tap.features.send.redux.FeeAction.RequestFee
import com.tangem.tap.features.send.redux.PrepareSendScreen
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.SendActionUi
import com.tangem.tap.features.send.redux.states.ButtonState
import com.tangem.tap.features.send.redux.states.ExternalTransactionData
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import com.tangem.tap.userWalletsListManager
import com.tangem.tap.walletCurrenciesManager
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
@ -65,7 +43,7 @@ import kotlinx.coroutines.launch
import org.rekotlin.Action
import org.rekotlin.Middleware
import timber.log.Timber
import java.util.*
import java.util.EnumSet
/**
[REDACTED_AUTHOR]
@ -134,9 +112,16 @@ private fun verifyAndSendTransaction(
},
sendAllCallback = {
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData,
dispatch,
action = action,
walletManager = walletManager,
amountToSend = amountToSend,
feeAmount = feeAmount,
feeType = sendState.feeState.selectedFeeType,
destinationAddress = destinationAddress,
transactionExtras = sendState.transactionExtrasState,
card = card,
externalTransactionData = sendState.externalTransactionData,
dispatch = dispatch,
)
},
reduceAmount,
@ -145,8 +130,16 @@ private fun verifyAndSendTransaction(
}
else -> {
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch,
action = action,
walletManager = walletManager,
amountToSend = amountToSend,
feeAmount = feeAmount,
feeType = sendState.feeState.selectedFeeType,
destinationAddress = destinationAddress,
transactionExtras = sendState.transactionExtrasState,
card = card,
externalTransactionData = sendState.externalTransactionData,
dispatch = dispatch,
)
}
}
@ -158,6 +151,7 @@ private fun sendTransaction(
walletManager: WalletManager,
amountToSend: Amount,
feeAmount: Amount,
feeType: FeeType,
destinationAddress: String,
transactionExtras: TransactionExtrasState,
card: CardDTO,
@ -253,7 +247,15 @@ private fun sendTransaction(
Analytics.send(Basic.TransactionSent(AnalyticsParam.TxSentFrom.Sell))
dispatch(WalletAction.TradeCryptoAction.FinishSelling(externalTransactionData.transactionId))
} else {
Analytics.send(Basic.TransactionSent(AnalyticsParam.TxSentFrom.Send))
Analytics.send(
Basic.TransactionSent(
sentFrom = AnalyticsParam.TxSentFrom.Send(
blockchain = walletManager.wallet.blockchain.fullName,
token = amountToSend.currencySymbol,
feeType = feeType.convertToAnalyticsFeeType(),
),
),
)
dispatch(NavigationAction.PopBackTo())
}
scope.launch(Dispatchers.IO) {

View file

@ -1,6 +1,7 @@
package com.tangem.tap.features.send.redux.states
import com.tangem.blockchain.common.Amount
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.features.wallet.redux.ProgressState
import java.math.BigDecimal
@ -8,7 +9,16 @@ import java.math.BigDecimal
[REDACTED_AUTHOR]
*/
enum class FeeType {
SINGLE, LOW, NORMAL, PRIORITY
SINGLE, LOW, NORMAL, PRIORITY;
}
fun FeeType.convertToAnalyticsFeeType(): AnalyticsParam.FeeType {
return when (this) {
FeeType.SINGLE -> AnalyticsParam.FeeType.Fixed
FeeType.LOW -> AnalyticsParam.FeeType.Min
FeeType.NORMAL -> AnalyticsParam.FeeType.Normal
FeeType.PRIORITY -> AnalyticsParam.FeeType.Max
}
}
data class FeeState(

View file

@ -34,14 +34,11 @@ class TransactionManagerImpl(
) : TransactionManager {
override suspend fun sendApproveTransaction(
networkId: String,
feeAmount: BigDecimal,
gasLimit: Int,
destinationAddress: String,
dataToSign: String,
txData: ApproveTxData,
derivationPath: String?,
analyticsData: AnalyticsData,
): SendTxResult {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
val blockchain = requireNotNull(Blockchain.fromNetworkId(txData.networkId)) { "blockchain not found" }
val walletManager = getActualWalletManager(blockchain, derivationPath)
walletManager.update()
val amount = Amount(value = BigDecimal.ZERO, blockchain = blockchain)
@ -49,40 +46,55 @@ class TransactionManagerImpl(
walletManager = walletManager,
amount = amount,
blockchain = blockchain,
feeAmount = feeAmount,
gasLimit = gasLimit,
destinationAddress = destinationAddress,
dataToSign = dataToSign,
feeAmount = txData.feeAmount,
gasLimit = txData.gasLimit,
destinationAddress = txData.destinationAddress,
dataToSign = txData.dataToSign,
successEvent = AnalyticsParam.TxSentFrom.Approve(
blockchain = blockchain.fullName,
token = analyticsData.tokenSymbol,
feeType = AnalyticsParam.FeeType.fromString(analyticsData.feeType),
permissionType = analyticsData.permissionType ?: "",
),
)
}
override suspend fun sendTransaction(
networkId: String,
amountToSend: BigDecimal,
feeAmount: BigDecimal,
gasLimit: Int,
destinationAddress: String,
dataToSign: String,
txData: SwapTxData,
isSwap: Boolean,
currencyToSend: Currency,
derivationPath: String?,
analyticsData: AnalyticsData,
): SendTxResult {
val blockchain = requireNotNull(Blockchain.fromNetworkId(networkId)) { "blockchain not found" }
val blockchain = requireNotNull(Blockchain.fromNetworkId(txData.networkId)) { "blockchain not found" }
val walletManager = getActualWalletManager(blockchain, derivationPath)
walletManager.update()
val amount = if (isSwap) {
createAmountForSwap(amountToSend, currencyToSend, blockchain)
createAmountForSwap(txData.amountToSend, txData.currencyToSend, blockchain)
} else {
createAmount(amountToSend, currencyToSend, blockchain)
createAmount(txData.amountToSend, txData.currencyToSend, blockchain)
}
val successEvent = if (isSwap) {
AnalyticsParam.TxSentFrom.Swap(
blockchain = blockchain.fullName,
token = analyticsData.tokenSymbol,
feeType = AnalyticsParam.FeeType.fromString(analyticsData.feeType),
)
} else {
AnalyticsParam.TxSentFrom.Send(
blockchain = blockchain.fullName,
token = analyticsData.tokenSymbol,
feeType = AnalyticsParam.FeeType.fromString(analyticsData.feeType),
)
}
return sendTransactionInternal(
walletManager = walletManager,
amount = amount,
blockchain = blockchain,
feeAmount = feeAmount,
gasLimit = gasLimit,
destinationAddress = destinationAddress,
dataToSign = dataToSign,
feeAmount = txData.feeAmount,
gasLimit = txData.gasLimit,
destinationAddress = txData.destinationAddress,
dataToSign = txData.dataToSign,
successEvent = successEvent,
)
}
@ -95,6 +107,7 @@ class TransactionManagerImpl(
gasLimit: Int,
destinationAddress: String,
dataToSign: String,
successEvent: AnalyticsParam.TxSentFrom,
): SendTxResult {
val txData = walletManager.createTransaction(
amount = amount,
@ -110,7 +123,10 @@ class TransactionManagerImpl(
FirebaseCrashlytics.getInstance().recordException(ex)
return SendTxResult.UnknownError(ex)
}
return handleSendResult(sendResult)
return handleSendResult(
result = sendResult,
successEvent = successEvent,
)
}
override fun getExplorerTransactionLink(networkId: String, txAddress: String): String {
@ -319,10 +335,10 @@ class TransactionManagerImpl(
}
}
private fun handleSendResult(result: SimpleResult): SendTxResult {
private fun handleSendResult(result: SimpleResult, successEvent: AnalyticsParam.TxSentFrom): SendTxResult {
when (result) {
is SimpleResult.Success -> {
analytics.send(Basic.TransactionSent(AnalyticsParam.TxSentFrom.Swap))
analytics.send(Basic.TransactionSent(sentFrom = successEvent))
return SendTxResult.Success
}
is SimpleResult.Failure -> {

View file

@ -4,16 +4,13 @@ import com.tangem.feature.swap.domain.cache.SwapDataCache
import com.tangem.feature.swap.domain.converters.CryptoCurrencyConverter
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.domain.PermissionOptions
import com.tangem.feature.swap.domain.models.domain.PreparedSwapConfigState
import com.tangem.feature.swap.domain.models.domain.SwapApproveType
import com.tangem.feature.swap.domain.models.toStringWithRightOffset
import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.ProxyFees
import com.tangem.lib.crypto.models.ProxyFiatCurrency
import com.tangem.lib.crypto.models.*
import com.tangem.lib.crypto.models.transactions.SendTxResult
import com.tangem.utils.toFiatString
import java.math.BigDecimal
@ -115,12 +112,19 @@ internal class SwapInteractorImpl @Inject constructor(
permissionOptions.approveData.approveModel.data
}
val result = transactionManager.sendApproveTransaction(
networkId = networkId,
feeAmount = permissionOptions.txFee.feeValue,
gasLimit = permissionOptions.txFee.gasLimit,
destinationAddress = permissionOptions.approveData.approveModel.toAddress,
dataToSign = dataToSign,
txData = ApproveTxData(
networkId = networkId,
feeAmount = permissionOptions.txFee.feeValue,
gasLimit = permissionOptions.txFee.gasLimit,
destinationAddress = permissionOptions.approveData.approveModel.toAddress,
dataToSign = dataToSign,
),
derivationPath = derivationPath,
analyticsData = AnalyticsData(
feeType = permissionOptions.txFee.feeType.getNameForAnalytics(),
tokenSymbol = permissionOptions.fromToken.symbol,
permissionType = permissionOptions.approveType.getNameForAnalytics(),
),
)
return when (result) {
is SendTxResult.Success -> {
@ -188,15 +192,21 @@ internal class SwapInteractorImpl @Inject constructor(
): TxState {
val amount = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format, use only digits" }
val result = transactionManager.sendTransaction(
networkId = networkId,
amountToSend = amount,
currencyToSend = cryptoCurrencyConverter.convert(currencyToSend),
feeAmount = fee.feeValue,
gasLimit = fee.gasLimit,
destinationAddress = swapStateData.swapModel.transaction.toWalletAddress,
dataToSign = swapStateData.swapModel.transaction.data,
txData = SwapTxData(
networkId = networkId,
amountToSend = amount,
currencyToSend = cryptoCurrencyConverter.convert(currencyToSend),
feeAmount = fee.feeValue,
gasLimit = fee.gasLimit,
destinationAddress = swapStateData.swapModel.transaction.toWalletAddress,
dataToSign = swapStateData.swapModel.transaction.data,
),
isSwap = true,
derivationPath = derivationPath,
analyticsData = AnalyticsData(
feeType = fee.feeType.getNameForAnalytics(),
tokenSymbol = currencyToSend.symbol,
),
)
return when (result) {
is SendTxResult.Success -> {
@ -587,12 +597,14 @@ internal class SwapInteractorImpl @Inject constructor(
gasLimit = normalFeeGas,
feeFiatFormatted = normalFiatFee,
feeCryptoFormatted = normalCryptoFee,
feeType = FeeType.NORMAL,
),
priorityFee = TxFee(
feeValue = priorityFeeValue,
gasLimit = priorityFeeGas,
feeFiatFormatted = priorityFiatFee,
feeCryptoFormatted = priorityCryptoFee,
feeType = FeeType.PRIORITY,
),
)
}

View file

@ -2,4 +2,11 @@ package com.tangem.feature.swap.domain.models.domain
enum class SwapApproveType {
LIMITED, UNLIMITED
}
fun SwapApproveType.getNameForAnalytics(): String {
return when (this) {
SwapApproveType.LIMITED -> "Transaction"
SwapApproveType.UNLIMITED -> "Unlimited"
}
}

View file

@ -77,4 +77,16 @@ data class TxFee(
val gasLimit: Int,
val feeFiatFormatted: String,
val feeCryptoFormatted: String,
)
val feeType: FeeType,
)
enum class FeeType {
NORMAL, PRIORITY
}
fun FeeType.getNameForAnalytics(): String {
return when (this) {
FeeType.NORMAL -> "Normal"
FeeType.PRIORITY -> "Max"
}
}

View file

@ -25,6 +25,7 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.getActiveIconRes
import com.tangem.core.ui.extensions.getActiveIconResByCoinId
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.swap.domain.models.ui.FeeType
import com.tangem.feature.swap.domain.models.ui.TxFee
import com.tangem.feature.swap.models.*
import com.tangem.feature.swap.presentation.R
@ -394,6 +395,7 @@ val stateSelectable = SelectableItemsState<TxFee>(
gasLimit = 0,
feeFiatFormatted = "",
feeCryptoFormatted = "",
feeType = FeeType.NORMAL,
),
),
items = listOf(
@ -407,6 +409,7 @@ val stateSelectable = SelectableItemsState<TxFee>(
gasLimit = 0,
feeFiatFormatted = "",
feeCryptoFormatted = "",
feeType = FeeType.NORMAL,
),
),
Item(
@ -419,6 +422,7 @@ val stateSelectable = SelectableItemsState<TxFee>(
gasLimit = 0,
feeFiatFormatted = "",
feeCryptoFormatted = "",
feeType = FeeType.NORMAL,
),
),
).toImmutableList(),

View file

@ -1,8 +1,6 @@
package com.tangem.lib.crypto
import com.tangem.lib.crypto.models.Currency
import com.tangem.lib.crypto.models.ProxyFees
import com.tangem.lib.crypto.models.ProxyNetworkInfo
import com.tangem.lib.crypto.models.*
import com.tangem.lib.crypto.models.transactions.SendTxResult
import java.math.BigDecimal
@ -11,26 +9,25 @@ interface TransactionManager {
@Suppress("LongParameterList")
@Throws(IllegalStateException::class)
suspend fun sendApproveTransaction(
networkId: String,
feeAmount: BigDecimal,
gasLimit: Int,
destinationAddress: String,
dataToSign: String,
txData: ApproveTxData,
derivationPath: String?,
analyticsData: AnalyticsData,
): SendTxResult
@Suppress("LongParameterList")
/**
* Send transaction
*
* @param txData data to build a tx
* @param derivationPath for select right walletManager
* @param analyticsData data for send analytics event
* @return result of transaction
*/
@Throws(IllegalStateException::class)
suspend fun sendTransaction(
networkId: String,
amountToSend: BigDecimal,
feeAmount: BigDecimal,
gasLimit: Int,
destinationAddress: String,
dataToSign: String,
txData: SwapTxData,
isSwap: Boolean,
currencyToSend: Currency,
derivationPath: String?,
analyticsData: AnalyticsData,
): SendTxResult
/**

View file

@ -0,0 +1,14 @@
package com.tangem.lib.crypto.models
/**
* Analytics data for send events in analytics engine
*
* @property feeType type of fee (min,max,normal)
* @property tokenSymbol symbol
* @property permissionType optional parameter used for type tx approve
*/
data class AnalyticsData(
val feeType: String,
val tokenSymbol: String,
val permissionType: String? = null,
)

View file

@ -0,0 +1,20 @@
package com.tangem.lib.crypto.models
import java.math.BigDecimal
/**
* Tx data for create and make approve transaction
*
* @property networkId id of network
* @property feeAmount amount of fee
* @property gasLimit gasLimit for given tx
* @property destinationAddress address to send tx
* @property dataToSign data to sing with signer
*/
data class ApproveTxData(
val networkId: String,
val feeAmount: BigDecimal,
val gasLimit: Int,
val destinationAddress: String,
val dataToSign: String,
)

View file

@ -0,0 +1,24 @@
package com.tangem.lib.crypto.models
import java.math.BigDecimal
/**
* Tx data for create and make transaction
*
* @property networkId id of network
* @property feeAmount amount of fee
* @property gasLimit gasLimit for given tx
* @property destinationAddress address to send tx
* @property dataToSign data to sing with signer
* @property amountToSend amount of tx
* @property currencyToSend currency for tx
*/
data class SwapTxData(
val networkId: String,
val feeAmount: BigDecimal,
val gasLimit: Int,
val destinationAddress: String,
val dataToSign: String,
val amountToSend: BigDecimal,
val currencyToSend: Currency,
)