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.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(

View file

@ -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"
}
}

View file

@ -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%

View file

@ -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

View file

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

View file

@ -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? {