Updated on 2026-08-14

This commit is contained in:
Tangem 2025-10-06 19:54:30 +03:00
parent a8e621c2c4
commit d1883231ed
6 changed files with 75 additions and 29 deletions

View file

@ -21,6 +21,7 @@ import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
import com.tangem.domain.walletconnect.usecase.method.SignRequirements import com.tangem.domain.walletconnect.usecase.method.SignRequirements
import com.tangem.domain.walletconnect.usecase.method.WcSignState import com.tangem.domain.walletconnect.usecase.method.WcSignState
import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase import com.tangem.domain.walletconnect.usecase.method.WcTransactionUseCase
import com.tangem.lib.crypto.BlockchainUtils.SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -107,7 +108,7 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor(
} }
private fun isLargeHash(hash: ByteArray): Boolean { private fun isLargeHash(hash: ByteArray): Boolean {
return hash.size > LARGE_HASH_SIZE return hash.size > SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES
} }
private fun getFormattedHash(hash: ByteArray): ByteArray { private fun getFormattedHash(hash: ByteArray): ByteArray {
@ -124,10 +125,6 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor(
return isLargeHash(data) return isLargeHash(data)
} }
private companion object {
private const val LARGE_HASH_SIZE = 930 // bytes
}
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create( fun create(

View file

@ -6,6 +6,8 @@ sealed class ExpressDataError {
abstract val code: Int abstract val code: Int
open val message: String? = null
data class BadRequest(override val code: Int) : ExpressDataError() data class BadRequest(override val code: Int) : ExpressDataError()
data class SwapsAreUnavailableNowError(override val code: Int) : ExpressDataError() data class SwapsAreUnavailableNowError(override val code: Int) : ExpressDataError()
@ -57,4 +59,9 @@ sealed class ExpressDataError {
data object UnknownError : ExpressDataError() { data object UnknownError : ExpressDataError() {
override val code: Int = -1 override val code: Int = -1
} }
data object TooLargeSolanaTransactionError : ExpressDataError() {
override val code: Int = -2
override val message: String = "tooLargeSolanaTransaction"
}
} }

View file

@ -3,6 +3,7 @@ package com.tangem.feature.swap.domain
import android.util.Base64 import android.util.Base64
import arrow.core.Either import arrow.core.Either
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.blockchain.blockchains.solana.SolanaTransactionHelper
import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
@ -44,6 +45,7 @@ import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.swap.domain.models.domain.*
import com.tangem.feature.swap.domain.models.toStringWithRightOffset import com.tangem.feature.swap.domain.models.toStringWithRightOffset
import com.tangem.feature.swap.domain.models.ui.* import com.tangem.feature.swap.domain.models.ui.*
import com.tangem.lib.crypto.BlockchainUtils.SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES
import com.tangem.lib.crypto.UserWalletManager import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.ProxyAmount import com.tangem.lib.crypto.models.ProxyAmount
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -1325,10 +1327,23 @@ internal class SwapInteractorImpl @AssistedInject constructor(
val otherNativeFee = transaction.otherNativeFeeWei val otherNativeFee = transaction.otherNativeFeeWei
?.movePointLeft(nativeCoinDecimals) ?.movePointLeft(nativeCoinDecimals)
?: BigDecimal.ZERO ?: BigDecimal.ZERO
val txFeeState = if (isSolana(networkId)) { val txFeeState = if (isSolana(networkId)) {
val transactionBytes = Base64.decode(transaction.txData, Base64.NO_WRAP)
val formattedHash = getFormattedHash(transactionBytes)
if (formattedHash.size > SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES) {
return produceDexSwapDataError(
error = ExpressDataError.TooLargeSolanaTransactionError,
fromToken = fromToken,
amount = amount,
)
}
getFeeDataForSolanaDexSwap( getFeeDataForSolanaDexSwap(
network = fromToken.currency.network, network = fromToken.currency.network,
transaction = transaction, transactionBytes = transactionBytes,
) )
.toTxFeeState(fromToken.currency, otherNativeFee) .toTxFeeState(fromToken.currency, otherNativeFee)
} else { } else {
@ -1387,22 +1402,34 @@ internal class SwapInteractorImpl @AssistedInject constructor(
) )
}, },
ifLeft = { error -> ifLeft = { error ->
val rates = getQuotes(fromToken.currency.id) produceDexSwapDataError(
val fromTokenSwapInfo = TokenSwapInfo( error = error,
tokenAmount = amount, fromToken = fromToken,
amountFiat = rates[fromToken.currency.id]?.multiply(amount.value) amount = amount,
?: BigDecimal.ZERO,
cryptoCurrencyStatus = fromToken,
)
SwapState.SwapError(
fromTokenSwapInfo,
error,
IncludeFeeInAmount.Excluded,
) )
}, },
) )
} }
private suspend fun produceDexSwapDataError(
error: ExpressDataError,
fromToken: CryptoCurrencyStatus,
amount: SwapAmount,
): SwapState.SwapError {
val rates = getQuotes(fromToken.currency.id)
val fromTokenSwapInfo = TokenSwapInfo(
tokenAmount = amount,
amountFiat = rates[fromToken.currency.id]?.multiply(amount.value)
?: BigDecimal.ZERO,
cryptoCurrencyStatus = fromToken,
)
return SwapState.SwapError(
fromTokenSwapInfo,
error,
IncludeFeeInAmount.Excluded,
)
}
private suspend fun getFeeDataForDexSwap( private suspend fun getFeeDataForDexSwap(
network: Network, network: Network,
transaction: ExpressTransactionModel.DEX, transaction: ExpressTransactionModel.DEX,
@ -1446,13 +1473,9 @@ internal class SwapInteractorImpl @AssistedInject constructor(
} }
} }
private suspend fun getFeeDataForSolanaDexSwap( private suspend fun getFeeDataForSolanaDexSwap(network: Network, transactionBytes: ByteArray): TransactionFee {
network: Network,
transaction: ExpressTransactionModel.DEX,
): TransactionFee {
val txData = transaction.txData
val transactionData = TransactionData.Compiled( val transactionData = TransactionData.Compiled(
value = TransactionData.Compiled.Data.Bytes(Base64.decode(txData, Base64.NO_WRAP)), value = TransactionData.Compiled.Data.Bytes(transactionBytes),
) )
return getFeeUseCase( return getFeeUseCase(
@ -2018,6 +2041,16 @@ internal class SwapInteractorImpl @AssistedInject constructor(
return networkId == Blockchain.Solana.toNetworkId() return networkId == Blockchain.Solana.toNetworkId()
} }
// TODO create usecase [REDACTED_TASK_KEY]
private fun getFormattedHash(hash: ByteArray): ByteArray {
return try {
SolanaTransactionHelper.removeSignaturesPlaceholders(hash)
} catch (e: Exception) {
Timber.e("Failed to format the hash: ${e.message}")
hash
}
}
companion object { companion object {
private const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12% private const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12%
private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5% private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5%

View file

@ -2,6 +2,11 @@ package com.tangem.feature.swap.analytics
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsEvent
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.Key.PROVIDER
import com.tangem.core.analytics.models.AnalyticsParam.Key.RECEIVE_TOKEN
import com.tangem.core.analytics.models.AnalyticsParam.Key.SEND_TOKEN
import com.tangem.feature.swap.domain.models.domain.SwapProvider import com.tangem.feature.swap.domain.models.domain.SwapProvider
import com.tangem.feature.swap.domain.models.ui.FeeType import com.tangem.feature.swap.domain.models.ui.FeeType
@ -108,14 +113,16 @@ sealed class SwapEvents(
val receiveToken: String, val receiveToken: String,
val provider: SwapProvider, val provider: SwapProvider,
val errorCode: Int, val errorCode: Int,
val errorMessage: String?,
) : SwapEvents( ) : SwapEvents(
event = "Notice - Express Error", event = "Notice - Express Error",
params = mapOf( params = buildMap {
"Send Token" to sendToken, put(SEND_TOKEN, sendToken)
"Receive Token" to receiveToken, put(RECEIVE_TOKEN, receiveToken)
"Provider" to provider.name, put(PROVIDER, provider.name)
"Error Code" to errorCode.toString(), put(ERROR_CODE, errorCode.toString())
), errorMessage?.let { put(ERROR_MESSAGE, it) }
},
) )
// TODO parameters // TODO parameters

View file

@ -504,6 +504,7 @@ internal class SwapModel @Inject constructor(
receiveToken = receiveToken ?: "", receiveToken = receiveToken ?: "",
provider = provider, provider = provider,
errorCode = error.code, errorCode = error.code,
errorMessage = error.message,
), ),
) )
} }

View file

@ -21,6 +21,7 @@ import java.math.BigDecimal
object BlockchainUtils { object BlockchainUtils {
private const val XRP_X_ADDRESS = 'X' private const val XRP_X_ADDRESS = 'X'
const val SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES = 930
/** Decodes XRP Blockchain address */ /** Decodes XRP Blockchain address */
fun decodeRippleXAddress(xAddress: String, blockchainId: String): XrpTaggedAddress? { fun decodeRippleXAddress(xAddress: String, blockchainId: String): XrpTaggedAddress? {