Updated on 2026-08-14
This commit is contained in:
parent
a8e621c2c4
commit
d1883231ed
6 changed files with 75 additions and 29 deletions
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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%
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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? {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue