Updated on 2026-08-14

This commit is contained in:
Tangem 2026-01-10 14:08:10 +03:00
parent aa621dd48f
commit c8b7934d93
5 changed files with 152 additions and 8 deletions

View file

@ -26,7 +26,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Suppress("TooManyFunctions")
@Suppress("TooManyFunctions", "LargeClass")
@Module
@InstallIn(SingletonComponent::class)
internal object TransactionDomainModule {
@ -346,4 +346,21 @@ internal object TransactionDomainModule {
getHotWalletSigner = tangemHotWalletSignerFactory::create,
)
}
@Provides
@Singleton
fun provideEstimateFeeForTokenUseCase(
walletManagersFacade: WalletManagersFacade,
gaslessTransactionRepository: GaslessTransactionRepository,
currenciesRepository: CurrenciesRepository,
getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
): EstimateFeeForTokenUseCase {
return EstimateFeeForTokenUseCase(
gaslessTransactionRepository = gaslessTransactionRepository,
walletManagersFacade = walletManagersFacade,
demoConfig = DemoConfig,
currenciesRepository = currenciesRepository,
getMultiCryptoCurrencyStatusUseCase = getMultiCryptoCurrencyStatusUseCase,
)
}
}

View file

@ -14,7 +14,7 @@ interface GaslessTxServiceApi {
@GET("api/v1/tokens")
suspend fun getSupportedTokens(): ApiResponse<GaslessServiceResponse<GaslessSupportedTokens>>
@POST("api/v1/sign")
@POST("api/v1/transaction/sign")
suspend fun signGaslessTransaction(
@Body transaction: GaslessTransactionRequest,
): ApiResponse<GaslessServiceResponse<GaslessSignedTransactionResultDTO>>

View file

@ -1,5 +1,6 @@
package com.tangem.data.transaction.convertes
import com.tangem.blockchain.extensions.formatHex
import com.tangem.datasource.api.gasless.models.FeeData
import com.tangem.datasource.api.gasless.models.TransactionData
import com.tangem.domain.transaction.models.GaslessTransactionData
@ -27,7 +28,7 @@ class GaslessTxDataToGaslessRequestConverter : Converter<GaslessTransactionData,
return TransactionData(
to = transaction.to,
value = transaction.value.toString(),
data = transaction.data.toHexString(),
data = transaction.data.toHexString().formatHex(),
)
}

View file

@ -266,11 +266,6 @@ class CreateAndSendGaslessTransactionUseCase(
}
private fun buildTransaction(transactionData: TransactionData.Uncompiled): GaslessTransactionData.Transaction {
val amount = transactionData.amount.value
?.movePointRight(transactionData.amount.decimals)
?.toBigInteger()
?: error("Transaction amount value is null")
val callData = (transactionData.extras as? EthereumTransactionExtras)?.callData
?: error("Ethereum call data is required")

View file

@ -0,0 +1,131 @@
package com.tangem.domain.transaction.usecase.gasless
import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.either
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.extensions.Result
import com.tangem.domain.demo.DemoTransactionSender
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.GetMultiCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.GaslessTransactionRepository
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.error.mapToFeeError
import com.tangem.domain.transaction.models.TransactionFeeExtended
import com.tangem.domain.transaction.raiseIllegalStateError
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.walletmanager.WalletManagersFacade
import java.math.BigDecimal
class EstimateFeeForTokenUseCase(
private val gaslessTransactionRepository: GaslessTransactionRepository,
private val walletManagersFacade: WalletManagersFacade,
private val demoConfig: DemoConfig,
private val currenciesRepository: CurrenciesRepository,
private val getMultiCryptoCurrencyStatusUseCase: GetMultiCryptoCurrencyStatusUseCase,
) {
private val tokenFeeCalculator = TokenFeeCalculator(
walletManagersFacade = walletManagersFacade,
gaslessTransactionRepository = gaslessTransactionRepository,
demoConfig = demoConfig,
)
suspend operator fun invoke(
userWallet: UserWallet,
token: CryptoCurrency,
amount: BigDecimal,
): Either<GetFeeError, TransactionFeeExtended> {
return either {
if (!gaslessTransactionRepository.isNetworkSupported(token.network)) {
raise(GetFeeError.GaslessError.NetworkIsNotSupported)
}
val userCurrenciesStatusesByNetwork = getMultiCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWallet.walletId,
).getOrNull()?.filter {
it.currency.network.id == token.network.id
} ?: raiseIllegalStateError("currencies list is null for userWalletId=${userWallet.walletId}")
val tokenCurrencyStatus = userCurrenciesStatusesByNetwork.find {
it.currency.id == token.id
} ?: raiseIllegalStateError("token currency not found for network ${token.network.id}")
val amountData = amount.convertToSdkAmount(tokenCurrencyStatus)
val result = if (userWallet is UserWallet.Cold &&
demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)
) {
demoTransactionSender(userWallet, token).estimateFee(
amount = amountData,
destination = "",
)
} else {
walletManagersFacade.estimateFee(
amount = amountData,
userWalletId = userWallet.walletId,
network = token.network,
)
}
val initialTxFee = when (result) {
is Result.Success -> result.data
is Result.Failure -> raise(result.mapToFeeError())
null -> raise(GetFeeError.UnknownError)
}
val initialFeeEth = initialTxFee.normal as? Fee.Ethereum
?: raiseIllegalStateError(
error = "only Fee.Ethereum supported, but was different",
)
val nativeCurrency = currenciesRepository.getNetworkCoin(
userWalletId = userWallet.walletId,
networkId = token.network.id,
derivationPath = token.network.derivationPath,
)
val nativeCurrencyStatus = userCurrenciesStatusesByNetwork.find {
it.currency.id == nativeCurrency.id
} ?: raiseIllegalStateError("native currency not found for network ${token.network.id}")
val walletManager = prepareWalletManager(userWallet, token.network)
tokenFeeCalculator.calculateTokenFee(
walletManager = walletManager,
tokenForPayFeeStatus = tokenCurrencyStatus,
nativeCurrencyStatus = nativeCurrencyStatus,
initialFee = initialFeeEth,
).bind()
}
}
@Suppress("NullableToStringCall")
private suspend fun Raise<GetFeeError>.prepareWalletManager(
userWallet: UserWallet,
network: Network,
): EthereumWalletManager {
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWallet.walletId,
network = network,
)
val ethereumWalletManager = walletManager as? EthereumWalletManager
?: raiseIllegalStateError("WalletManager type ${walletManager?.javaClass?.name} not supported")
return ethereumWalletManager
}
private suspend fun demoTransactionSender(
userWallet: UserWallet,
cryptoCurrency: CryptoCurrency,
): DemoTransactionSender {
return DemoTransactionSender(
walletManagersFacade
.getOrCreateWalletManager(userWallet.walletId, cryptoCurrency.network)
?: error("WalletManager is null"),
)
}
}