Updated on 2026-08-14

This commit is contained in:
Tangem 2022-08-03 10:21:05 +03:00
parent 67095aaa51
commit 90589b7de1
11 changed files with 192 additions and 107 deletions

View file

@ -0,0 +1,51 @@
package com.tangem.tap.common.extensions
import com.google.firebase.crashlytics.FirebaseCrashlytics
import com.tangem.blockchain.common.BlockchainError
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.common.card.Card
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.analytics.AnalyticsHandler
import com.tangem.tap.common.analytics.AnalyticsParam
import com.tangem.tap.features.demo.DemoTransactionSender
/**
[REDACTED_AUTHOR]
*/
fun AnalyticsHandler.logSendTransactionError(
error: BlockchainError,
action: Analytics.ActionToLog,
parameters: Map<AnalyticsParam, String>? = mapOf(),
card: Card? = null,
) {
when (val blockchainSdkError = (error as BlockchainSdkError)) {
is BlockchainSdkError.WrappedTangemError -> {
val tangemSdkError = (blockchainSdkError.tangemError as? TangemSdkError) ?: return
logCardSdkError(
error = tangemSdkError,
actionToLog = action,
parameters = parameters,
card = card,
)
}
else -> {
when {
blockchainSdkError.customMessage.contains(DemoTransactionSender.ID) -> return
else -> {
val params = parameters?.toMutableMap() ?: mutableMapOf()
params[AnalyticsParam.ACTION] = action.key
params[AnalyticsParam.ERROR_CODE] = error.code.toString()
params[AnalyticsParam.ERROR_DESCRIPTION] = "${error.javaClass.simpleName}: ${error.customMessage}"
params[AnalyticsParam.ERROR_KEY] = "BlockchainSdkError"
FirebaseCrashlytics.getInstance().apply {
params.forEach { setCustomKey(it.key.param, it.value) }
recordException(error)
}
}
}
}
}
}

View file

@ -44,10 +44,6 @@ sealed class TapError(
val customMessage: String = "Unsupported state:"
) : TapError(R.string.common_custom_string, listOf("$customMessage $stateError"))
sealed class XmlError {
object AssetAccountNotCreated : TapError(R.string.send_error_no_account_xlm)
}
sealed class WalletManager {
object CreationError : CustomError("Can't create wallet manager")
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)

View file

@ -7,6 +7,7 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.CommonSigner
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
@ -24,6 +25,7 @@ import com.tangem.common.extensions.toHexString
import com.tangem.crypto.CryptoUtils
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.Analytics
import com.tangem.tap.common.extensions.logSendTransactionError
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.toFormattedString
import com.tangem.tap.features.details.redux.walletconnect.WalletConnectSession
@ -38,8 +40,8 @@ import com.tangem.tap.tangemSdk
import com.tangem.tap.tangemSdkManager
import com.trustwallet.walletconnect.models.ethereum.WCEthereumSignMessage
import com.trustwallet.walletconnect.models.ethereum.WCEthereumTransaction
import java.math.BigDecimal
import timber.log.Timber
import java.math.BigDecimal
class WalletConnectSdkHelper {
@ -69,7 +71,7 @@ class WalletConnectSdkHelper {
(walletManager as? EthereumGasLoader)?.getGasPrice()) {
is Result.Success -> result.data.toBigDecimal()
is Result.Failure -> {
Timber.e(result.error)
(result.error as? Throwable)?.let { Timber.e(it) }
return null
}
null -> return null
@ -143,13 +145,11 @@ class WalletConnectSdkHelper {
HEX_PREFIX + data.walletManager.wallet.recentTransactions.last().hash
}
is SimpleResult.Failure -> {
(result.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
Analytics.ActionToLog.WalletConnectTransaction,
)
}
Timber.e(result.error)
store.state.globalState.analyticsHandlers?.logSendTransactionError(
result.error,
Analytics.ActionToLog.WalletConnectTransaction,
)
Timber.e(result.error as BlockchainSdkError)
null
}
}

View file

@ -1,7 +1,13 @@
package com.tangem.tap.features.demo
import com.tangem.blockchain.blockchains.bitcoin.BitcoinWalletManager
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.toBlockchainCustomError
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.common.CompletionResult
@ -289,7 +295,7 @@ class DemoTransactionSender(
publicKey = walletManager.wallet.publicKey
)
return when (signerResponse) {
is CompletionResult.Success -> SimpleResult.Failure(Exception(ID))
is CompletionResult.Success -> SimpleResult.Failure(Exception(ID).toBlockchainCustomError())
is CompletionResult.Failure -> SimpleResult.fromTangemSdkError(signerResponse.error)
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.Message
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.core.TangemSdkError
import com.tangem.tap.common.redux.ErrorAction
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.common.redux.ToastNotificationAction
@ -155,7 +156,10 @@ sealed class SendAction : SendScreenAction {
val reduceAmount: BigDecimal,
) : Dialog()
data class SendTransactionFails(val errorMessage: String) : Dialog()
sealed class SendTransactionFails : Dialog() {
data class CardSdkError(val error: TangemSdkError): Dialog()
data class BlockchainSdkError(val error: com.tangem.blockchain.common.BlockchainSdkError): Dialog()
}
object Hide : Dialog()
}

View file

@ -22,6 +22,7 @@ import com.tangem.tap.common.analytics.AnalyticsParam
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.logSendTransactionError
import com.tangem.tap.common.extensions.safeUpdate
import com.tangem.tap.common.extensions.stripZeroPlainString
import com.tangem.tap.common.redux.AppDialog
@ -52,14 +53,13 @@ import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdk
import com.tangem.wallet.R
import java.util.EnumSet
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.rekotlin.Action
import org.rekotlin.Middleware
import timber.log.Timber
import java.util.*
/**
[REDACTED_AUTHOR]
@ -79,13 +79,19 @@ class SendMiddleware {
is SendActionUi.CheckIfTransactionDataWasProvided -> {
val transactionData = appState()?.sendState?.externalTransactionData
if (transactionData != null) {
store.dispatchOnMain(AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
transactionData.destinationAddress, false
))
store.dispatchOnMain(
AddressPayIdVerifyAction.AddressVerification.SetWalletAddress(
transactionData.destinationAddress, false,
),
)
store.dispatchOnMain(AmountActionUi.SetMainCurrency(MainCurrencyType.CRYPTO))
store.dispatchOnMain(AmountActionUi.HandleUserInput(transactionData.amount))
store.dispatchOnMain(AmountAction.SetAmount(transactionData.amount.toBigDecimal(),
false))
store.dispatchOnMain(
AmountAction.SetAmount(
transactionData.amount.toBigDecimal(),
false,
),
)
}
}
}
@ -93,7 +99,6 @@ class SendMiddleware {
}
}
}
}
private fun verifyAndSendTransaction(
@ -113,23 +118,31 @@ private fun verifyAndSendTransaction(
when {
hadTezosError -> {
val reduceAmount = walletManager.wallet.blockchain.minimalAmount()
dispatch(SendAction.Dialog.TezosWarningDialog(reduceCallback = {
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
dispatch(AmountActionUi.CheckAmountToSend)
}, sendAllCallback = {
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData,
dispatch
)
}, reduceAmount))
dispatch(
SendAction.Dialog.TezosWarningDialog(
reduceCallback = {
dispatch(AmountAction.SetAmount(typedAmount.value!!.minus(reduceAmount), false))
dispatch(AmountActionUi.CheckAmountToSend)
},
sendAllCallback = {
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData,
dispatch,
)
},
reduceAmount,
),
)
}
transactionErrors.isNotEmpty() -> {
dispatch(SendAction.SendError(createValidateTransactionError(transactionErrors, walletManager)))
}
else -> {
sendTransaction(action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch)
sendTransaction(
action, walletManager, amountToSend, feeAmount, destinationAddress,
sendState.transactionExtrasState, card, sendState.externalTransactionData, dispatch,
)
}
}
}
@ -150,7 +163,9 @@ private fun sendTransaction(
transactionExtras.xlmMemo?.memo?.let { txData = txData.copy(extras = StellarTransactionExtras(it)) }
transactionExtras.binanceMemo?.memo?.let { txData = txData.copy(extras = BinanceTransactionExtras(it.toString())) }
transactionExtras.xrpDestinationTag?.tag?.let { txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it)) }
transactionExtras.xrpDestinationTag?.tag?.let {
txData = txData.copy(extras = XrpTransactionBuilder.XrpTransactionExtras(it))
}
scope.launch {
val updateWalletResult = walletManager.safeUpdate()
@ -178,14 +193,14 @@ private fun sendTransaction(
val signer = TangemSigner(
card = card,
tangemSdk = tangemSdk,
initialMessage = action.messageForSigner
initialMessage = action.messageForSigner,
) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey.seedKey,
remainingSignatures = signResponse.remainingSignatures
)
remainingSignatures = signResponse.remainingSignatures,
),
)
}
val sendResult = try {
@ -211,7 +226,7 @@ private fun sendTransaction(
store.state.globalState.analyticsHandlers?.triggerEvent(
event = AnalyticsEvent.TRANSACTION_IS_SENT,
card = card,
blockchain = walletManager.wallet.blockchain.currency
blockchain = walletManager.wallet.blockchain.currency,
)
dispatch(SendAction.SendSuccess)
@ -231,69 +246,48 @@ private fun sendTransaction(
}
}
is SimpleResult.Failure -> {
when (sendResult.error) {
store.state.globalState.feedbackManager?.infoHolder?.updateOnSendError(
wallet = walletManager.wallet,
host = walletManager.currentHost,
amountToSend = amountToSend,
feeAmount = feeAmount,
destinationAddress = destinationAddress,
)
store.state.globalState.analyticsHandlers?.logSendTransactionError(
error = sendResult.error,
action = Analytics.ActionToLog.SendTransaction,
parameters = mapOf(AnalyticsParam.BLOCKCHAIN to walletManager.wallet.blockchain.currency),
card = card,
)
val error = (sendResult.error as? BlockchainSdkError) ?: return@withContext
when (error) {
is BlockchainSdkError.WrappedTangemError -> {
val tangemSdkError = (error.tangemError as? TangemSdkError) ?: return@withContext
if (tangemSdkError is TangemSdkError.UserCancelled) return@withContext
dispatch(SendAction.Dialog.SendTransactionFails.CardSdkError(tangemSdkError))
}
is BlockchainSdkError.CreateAccountUnderfunded -> {
val error = sendResult.error as BlockchainSdkError.CreateAccountUnderfunded
// from XLM, XRP
val reserve = error.minReserve.value?.stripZeroPlainString() ?: "0"
val symbol = error.minReserve.currencySymbol
dispatch(SendAction.SendError(TapError.CreateAccountUnderfunded(listOf(reserve, symbol))))
}
is BlockchainSdkError.SendException -> {
sendResult.error?.let { FirebaseCrashlytics.getInstance().recordException(it) }
}
is Throwable -> {
val throwable = sendResult.error as Throwable
val message = throwable.message
val infoHolder = store.state.globalState.feedbackManager?.infoHolder
else -> {
when {
message == null -> {
dispatch(SendAction.SendError(TapError.UnknownError))
infoHolder?.updateOnSendError(
wallet = walletManager.wallet,
host = walletManager.currentHost,
amountToSend = amountToSend,
feeAmount = feeAmount,
destinationAddress = destinationAddress
error.customMessage.contains(DemoTransactionSender.ID) -> {
store.dispatchDialogShow(
AppDialog.SimpleOkDialogRes(
headerId = R.string.common_done,
messageId = R.string.alert_demo_feature_disabled,
onOk = { dispatch(NavigationAction.PopBackTo()) },
),
)
dispatch(SendAction.Dialog.SendTransactionFails("unknown error"))
}
message.contains("50002") -> {
// user was cancelled the operation by closing the Sdk bottom sheet
}
// make it easier latter by handling an appropriate enumError or, like on iOS,
// accept a string identifier of the error message
message.contains("Target account is not created. To create account send 1+ XLM.") -> {
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
message.contains(DemoTransactionSender.ID) -> {
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatchDialogShow(AppDialog.SimpleOkDialogRes(
R.string.common_done,
R.string.alert_demo_feature_disabled
) { dispatch(NavigationAction.PopBackTo()) })
}
else -> {
(sendResult.error as? TangemSdkError)?.let { error ->
store.state.globalState.analyticsHandlers?.logCardSdkError(
error,
Analytics.ActionToLog.SendTransaction,
mapOf(
AnalyticsParam.BLOCKCHAIN
to walletManager.wallet.blockchain.currency),
card = card,
)
}
Timber.e(throwable)
FirebaseCrashlytics.getInstance().recordException(throwable)
dispatch(SendAction.SendError(TapError.CustomError(message)))
infoHolder?.updateOnSendError(
wallet = walletManager.wallet,
host = walletManager.currentHost,
amountToSend = amountToSend,
feeAmount = feeAmount,
destinationAddress = destinationAddress
)
dispatch(SendAction.Dialog.SendTransactionFails(message))
dispatch(SendAction.Dialog.SendTransactionFails.BlockchainSdkError(error))
}
}
}
@ -328,7 +322,10 @@ fun extractErrorsForAmountField(errors: EnumSet<TransactionError>): EnumSet<Tran
return showIntoAmountField
}
fun createValidateTransactionError(errorList: EnumSet<TransactionError>, walletManager: WalletManager): TapError.ValidateTransactionErrors {
fun createValidateTransactionError(
errorList: EnumSet<TransactionError>,
walletManager: WalletManager,
): TapError.ValidateTransactionErrors {
val tapErrors = errorList.map {
when (it) {
TransactionError.AmountExceedsBalance -> TapError.AmountExceedsBalance

View file

@ -2,6 +2,7 @@ package com.tangem.tap.features.send.ui.dialogs
import android.content.Context
import androidx.appcompat.app.AlertDialog
import com.tangem.tangem_sdk_new.extensions.localizedDescription
import com.tangem.tap.common.feedback.SendTransactionFailedEmail
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.features.send.redux.SendAction
@ -14,14 +15,22 @@ import com.tangem.wallet.R
class SendTransactionFailsDialog {
companion object {
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails): AlertDialog {
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.CardSdkError): AlertDialog {
return create(context, dialog.error.localizedDescription(context))
}
fun create(context: Context, dialog: SendAction.Dialog.SendTransactionFails.BlockchainSdkError): AlertDialog {
return create(context, dialog.error.customMessage)
}
private fun create(context: Context, errorMessage: String): AlertDialog {
return AlertDialog.Builder(context).apply {
setTitle(R.string.alert_failed_to_send_transaction_title)
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, dialog.errorMessage))
setMessage(context.getString(R.string.alert_failed_to_send_transaction_message, errorMessage))
setNeutralButton(R.string.alert_button_send_feedback) { _, _ ->
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(dialog.errorMessage)))
store.dispatch(GlobalAction.SendEmail(SendTransactionFailedEmail(errorMessage)))
}
setPositiveButton(R.string.common_no) { _, _ -> }
setPositiveButton(R.string.common_cancel) { _, _ -> }
setOnDismissListener { store.dispatch(SendAction.Dialog.Hide) }
}.create()
}

View file

@ -8,7 +8,12 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.text.bold
import com.tangem.common.extensions.remove
import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.extensions.beginDelayedTransition
import com.tangem.tap.common.extensions.enableError
import com.tangem.tap.common.extensions.getColor
import com.tangem.tap.common.extensions.getString
import com.tangem.tap.common.extensions.show
import com.tangem.tap.common.extensions.update
import com.tangem.tap.common.redux.getMessageString
import com.tangem.tap.common.text.DecimalDigitsInputFilter
import com.tangem.tap.domain.MultiMessageError
@ -17,7 +22,17 @@ import com.tangem.tap.features.BaseStoreFragment
import com.tangem.tap.features.send.redux.AddressPayIdVerifyAction.Error
import com.tangem.tap.features.send.redux.FeeAction
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.send.redux.states.*
import com.tangem.tap.features.send.redux.states.AddressPayIdState
import com.tangem.tap.features.send.redux.states.AmountState
import com.tangem.tap.features.send.redux.states.FeeState
import com.tangem.tap.features.send.redux.states.MainCurrencyType
import com.tangem.tap.features.send.redux.states.ReceiptLayoutType
import com.tangem.tap.features.send.redux.states.ReceiptState
import com.tangem.tap.features.send.redux.states.SendState
import com.tangem.tap.features.send.redux.states.StateId
import com.tangem.tap.features.send.redux.states.TransactionExtraError
import com.tangem.tap.features.send.redux.states.TransactionExtrasState
import com.tangem.tap.features.send.redux.states.XlmMemoType
import com.tangem.tap.features.send.ui.FeeUiHelper
import com.tangem.tap.features.send.ui.SendFragment
import com.tangem.tap.features.send.ui.dialogs.SendTransactionFailsDialog
@ -117,7 +132,13 @@ class SendStateSubscriber(fragment: BaseStoreFragment) :
dialog?.show()
}
}
is SendAction.Dialog.SendTransactionFails -> {
is SendAction.Dialog.SendTransactionFails.CardSdkError -> {
if (dialog == null) {
dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
dialog?.show()
}
}
is SendAction.Dialog.SendTransactionFails.BlockchainSdkError -> {
if (dialog == null) {
dialog = SendTransactionFailsDialog.create(fg.requireContext(), state.dialog)
dialog?.show()

View file

@ -159,7 +159,7 @@
<string name="alert_failed_to_send_email_title">Не удалось отправить электронное письмо</string>
<string name="alert_failed_to_send_email_message">Причина: %s</string>
<string name="alert_failed_to_send_transaction_title">Не могу отправить транзакцию</string>
<string name="alert_failed_to_send_transaction_message">Причина: %s. Хотите отправить отзыв?</string>
<string name="alert_failed_to_send_transaction_message">Причина: %s</string>
<string name="alert_troubleshooting_scan_card_title">У Вас возникли трудности со сканированием вашей карты?</string>
<string name="alert_troubleshooting_scan_card_message">Пожалуйста, попробуйте приложить карту точно так, как показано на анимации, или обратитесь в поддержку</string>
<string name="alert_troubleshooting_scan_card_ok">Отмена</string>

View file

@ -55,7 +55,7 @@
<string name="alert_failed_to_send_email_title" translatable="false">Failed to send email</string>
<string name="alert_failed_to_send_email_message" translatable="false">Reason: %s</string>
<string name="alert_failed_to_send_transaction_title" translatable="false">Can\'t send a transaction</string>
<string name="alert_failed_to_send_transaction_message" translatable="false">Reason: %s. Do you want to send feedback?</string>
<string name="alert_failed_to_send_transaction_message" translatable="false">Reason: %s</string>
<string name="alert_troubleshooting_scan_card_title" translatable="false">Are you having difficulty scanning your card?</string>
<string name="alert_troubleshooting_scan_card_message" translatable="false">Please try to tap the card exactly as shown in the animation or request support.</string>
<string name="alert_troubleshooting_scan_card_ok" translatable="false">I\'m okay</string>

View file

@ -1,8 +1,9 @@
ext.versions = [
kotlin : '1.6.10',
build_gradle : '7.1.3',
tangem_card_sdk : 'develop-157',
tangem_blockchain_sdk: 'develop-99',
tangem_card_sdk : 'develop-159',
// tangem_blockchain_sdk: 'develop-99',
tangem_blockchain_sdk: '0.0.1',
]
ext.environmentConfig = [