From d1883231ed988fd099cf64dfa09e5c8f5dc96a63 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 6 Oct 2025 19:54:30 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../solana/WcSolanaSignTransactionUseCase.kt | 7 +- .../swap/domain/models/ExpressDataError.kt | 7 ++ .../feature/swap/domain/SwapInteractorImpl.kt | 69 ++++++++++++++----- .../feature/swap/analytics/SwapEvents.kt | 19 +++-- .../tangem/feature/swap/model/SwapModel.kt | 1 + .../com/tangem/lib/crypto/BlockchainUtils.kt | 1 + 6 files changed, 75 insertions(+), 29 deletions(-) diff --git a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt index 89a968a5fd..c0d665b854 100644 --- a/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt +++ b/data/wallet-connect/src/main/kotlin/com/tangem/data/walletconnect/network/solana/WcSolanaSignTransactionUseCase.kt @@ -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.WcSignState 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.AssistedFactory import dagger.assisted.AssistedInject @@ -107,7 +108,7 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor( } 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 { @@ -124,10 +125,6 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor( return isLargeHash(data) } - private companion object { - private const val LARGE_HASH_SIZE = 930 // bytes - } - @AssistedFactory interface Factory { fun create( diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ExpressDataError.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ExpressDataError.kt index 1446dd81b2..568214f788 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ExpressDataError.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ExpressDataError.kt @@ -6,6 +6,8 @@ sealed class ExpressDataError { abstract val code: Int + open val message: String? = null + data class BadRequest(override val code: Int) : ExpressDataError() data class SwapsAreUnavailableNowError(override val code: Int) : ExpressDataError() @@ -57,4 +59,9 @@ sealed class ExpressDataError { data object UnknownError : ExpressDataError() { override val code: Int = -1 } + + data object TooLargeSolanaTransactionError : ExpressDataError() { + override val code: Int = -2 + override val message: String = "tooLargeSolanaTransaction" + } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index fbcde7d463..95c16d0921 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -3,6 +3,7 @@ package com.tangem.feature.swap.domain import android.util.Base64 import arrow.core.Either import arrow.core.getOrElse +import com.tangem.blockchain.blockchains.solana.SolanaTransactionHelper import com.tangem.blockchain.common.Amount import com.tangem.blockchain.common.Blockchain 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.toStringWithRightOffset 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.models.ProxyAmount import dagger.assisted.Assisted @@ -1325,10 +1327,23 @@ internal class SwapInteractorImpl @AssistedInject constructor( val otherNativeFee = transaction.otherNativeFeeWei ?.movePointLeft(nativeCoinDecimals) ?: BigDecimal.ZERO + 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( network = fromToken.currency.network, - transaction = transaction, + transactionBytes = transactionBytes, ) .toTxFeeState(fromToken.currency, otherNativeFee) } else { @@ -1387,22 +1402,34 @@ internal class SwapInteractorImpl @AssistedInject constructor( ) }, ifLeft = { error -> - val rates = getQuotes(fromToken.currency.id) - val fromTokenSwapInfo = TokenSwapInfo( - tokenAmount = amount, - amountFiat = rates[fromToken.currency.id]?.multiply(amount.value) - ?: BigDecimal.ZERO, - cryptoCurrencyStatus = fromToken, - ) - SwapState.SwapError( - fromTokenSwapInfo, - error, - IncludeFeeInAmount.Excluded, + produceDexSwapDataError( + error = error, + fromToken = fromToken, + amount = amount, ) }, ) } + 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( network: Network, transaction: ExpressTransactionModel.DEX, @@ -1446,13 +1473,9 @@ internal class SwapInteractorImpl @AssistedInject constructor( } } - private suspend fun getFeeDataForSolanaDexSwap( - network: Network, - transaction: ExpressTransactionModel.DEX, - ): TransactionFee { - val txData = transaction.txData + private suspend fun getFeeDataForSolanaDexSwap(network: Network, transactionBytes: ByteArray): TransactionFee { val transactionData = TransactionData.Compiled( - value = TransactionData.Compiled.Data.Bytes(Base64.decode(txData, Base64.NO_WRAP)), + value = TransactionData.Compiled.Data.Bytes(transactionBytes), ) return getFeeUseCase( @@ -2018,6 +2041,16 @@ internal class SwapInteractorImpl @AssistedInject constructor( 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 { private const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12% private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5% diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt index 0f2e700cd8..8d8ed4d072 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/analytics/SwapEvents.kt @@ -2,6 +2,11 @@ package com.tangem.feature.swap.analytics import com.tangem.common.ui.bottomsheet.permission.state.ApproveType 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.ui.FeeType @@ -108,14 +113,16 @@ sealed class SwapEvents( val receiveToken: String, val provider: SwapProvider, val errorCode: Int, + val errorMessage: String?, ) : SwapEvents( event = "Notice - Express Error", - params = mapOf( - "Send Token" to sendToken, - "Receive Token" to receiveToken, - "Provider" to provider.name, - "Error Code" to errorCode.toString(), - ), + params = buildMap { + put(SEND_TOKEN, sendToken) + put(RECEIVE_TOKEN, receiveToken) + put(PROVIDER, provider.name) + put(ERROR_CODE, errorCode.toString()) + errorMessage?.let { put(ERROR_MESSAGE, it) } + }, ) // TODO parameters diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index 4f013dac67..cf53aeb958 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -504,6 +504,7 @@ internal class SwapModel @Inject constructor( receiveToken = receiveToken ?: "", provider = provider, errorCode = error.code, + errorMessage = error.message, ), ) } diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index aec26f5567..17fd386e25 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -21,6 +21,7 @@ import java.math.BigDecimal object BlockchainUtils { private const val XRP_X_ADDRESS = 'X' + const val SOLANA_TRANSACTION_SIZE_THRESHOLD_BYTES = 930 /** Decodes XRP Blockchain address */ fun decodeRippleXAddress(xAddress: String, blockchainId: String): XrpTaggedAddress? {