Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-22 18:47:08 +05:00
parent 9860ecc4cb
commit 5c11931241
21 changed files with 541 additions and 63 deletions

View file

@ -154,6 +154,7 @@ dependencies {
implementation(projects.domain.walletManager) implementation(projects.domain.walletManager)
implementation(projects.domain.walletManager.models) implementation(projects.domain.walletManager.models)
implementation(projects.domain.yieldSupply) implementation(projects.domain.yieldSupply)
implementation(projects.domain.blockaid)
implementation(projects.common) implementation(projects.common)
implementation(projects.common.routing) implementation(projects.common.routing)

View file

@ -1,5 +1,6 @@
package com.tangem.tap.di.domain package com.tangem.tap.di.domain
import com.tangem.domain.blockaid.BlockAidGasEstimate
import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.error.FeeErrorResolver import com.tangem.domain.transaction.error.FeeErrorResolver
import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository
@ -42,10 +43,12 @@ internal object YieldSupplyDomainModule {
fun provideYieldSupplyEstimateEnterFeeUseCase( fun provideYieldSupplyEstimateEnterFeeUseCase(
feeRepository: FeeRepository, feeRepository: FeeRepository,
feeErrorResolver: FeeErrorResolver, feeErrorResolver: FeeErrorResolver,
blockAidGasEstimate: BlockAidGasEstimate,
): YieldSupplyEstimateEnterFeeUseCase { ): YieldSupplyEstimateEnterFeeUseCase {
return YieldSupplyEstimateEnterFeeUseCase( return YieldSupplyEstimateEnterFeeUseCase(
feeRepository = feeRepository, feeRepository = feeRepository,
feeErrorResolver = feeErrorResolver, feeErrorResolver = feeErrorResolver,
blockAidGasEstimate = blockAidGasEstimate,
) )
} }

View file

@ -1,9 +1,11 @@
package com.tangem.datasource.api.common.blockaid package com.tangem.datasource.api.common.blockaid
import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionBulkScanRequest
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionScanRequest
import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest import com.tangem.datasource.api.common.blockaid.models.request.SolanaTransactionScanRequest
import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse import com.tangem.datasource.api.common.blockaid.models.response.DomainScanResponse
import com.tangem.datasource.api.common.blockaid.models.response.GasEstimationResponse
import com.tangem.datasource.api.common.blockaid.models.response.SolanaTransactionResponse import com.tangem.datasource.api.common.blockaid.models.response.SolanaTransactionResponse
import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse import com.tangem.datasource.api.common.blockaid.models.response.TransactionScanResponse
import retrofit2.http.Body import retrofit2.http.Body
@ -19,4 +21,7 @@ interface BlockAidApi {
@POST("solana/message/scan") @POST("solana/message/scan")
suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): SolanaTransactionResponse suspend fun scanSolanaMessage(@Body request: SolanaTransactionScanRequest): SolanaTransactionResponse
@POST("evm/transaction-bulk/scan")
suspend fun scanEvmTransactionBulk(@Body request: EvmTransactionBulkScanRequest): List<GasEstimationResponse>
} }

View file

@ -0,0 +1,7 @@
package com.tangem.datasource.api.common.blockaid.models.request
enum class BlockAidScanOptions(val value: String) {
Simulation("simulation"),
Validation("validation"),
GasEstimation("gas_estimation"),
}

View file

@ -14,9 +14,25 @@ data class EvmTransactionScanRequest(
@Json(name = "metadata") val metadata: TransactionMetadata, @Json(name = "metadata") val metadata: TransactionMetadata,
) )
@JsonClass(generateAdapter = true)
data class EvmTransactionBulkScanRequest(
@Json(name = "chain") val chain: String,
@Json(name = "options") val options: List<String>,
@Json(name = "metadata") val metadata: TransactionMetadata,
@Json(name = "data") val data: List<Data>,
@Json(name = "aggregated") val aggregated: Boolean = false,
)
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class RpcData( data class RpcData(
@Json(name = "jsonrpc") val jsonrpc: String = "2.0", @Json(name = "jsonrpc") val jsonrpc: String = "2.0",
@Json(name = "method") val method: String, @Json(name = "method") val method: String,
@Json(name = "params") val params: List<Map<String, String>>, @Json(name = "params") val params: List<Map<String, String>>,
)
@JsonClass(generateAdapter = true)
data class Data(
@Json(name = "from") val from: String,
@Json(name = "to") val to: String,
@Json(name = "data") val data: String,
) )

View file

@ -0,0 +1,14 @@
package com.tangem.datasource.api.common.blockaid.models.response
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class GasEstimationResponse(
@Json(name = "gas_estimation") val gasEstimation: GasEstimationItem,
)
@JsonClass(generateAdapter = true)
data class GasEstimationItem(
@Json(name = "estimate") val estimate: String,
)

View file

@ -12,6 +12,7 @@ android {
dependencies { dependencies {
/* Project - Domain */ /* Project - Domain */
implementation(projects.data.common) implementation(projects.data.common)
implementation(projects.domain.models)
implementation(projects.domain.blockaid) implementation(projects.domain.blockaid)
implementation(projects.domain.blockaid.models) implementation(projects.domain.blockaid.models)
@ -20,6 +21,7 @@ dependencies {
/* Project - Core */ /* Project - Core */
implementation(projects.core.utils) implementation(projects.core.utils)
implementation(projects.libs.blockchainSdk)
/* DI */ /* DI */
implementation(deps.hilt.core) implementation(deps.hilt.core)

View file

@ -0,0 +1,31 @@
package com.tangem.data.blockaid
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.common.Blockchain
import com.tangem.datasource.api.common.blockaid.models.request.BlockAidScanOptions
import com.tangem.datasource.api.common.blockaid.models.request.Data
import com.tangem.datasource.api.common.blockaid.models.request.EvmTransactionBulkScanRequest
import com.tangem.datasource.api.common.blockaid.models.response.TransactionMetadata
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.TransactionData as SDKTransactionData
internal class BlockAidEvmScanTransactionConverter(
private val blockchain: Blockchain,
) : Converter<List<SDKTransactionData.Uncompiled>, EvmTransactionBulkScanRequest> {
override fun convert(value: List<SDKTransactionData.Uncompiled>): EvmTransactionBulkScanRequest {
return EvmTransactionBulkScanRequest(
chain = blockchain.getChainId().toString(),
options = listOf(BlockAidScanOptions.GasEstimation.value),
metadata = TransactionMetadata(domain = "https://tangem.com"),
data = value.map {
Data(
from = it.sourceAddress,
to = it.destinationAddress,
data = (it.extras as? EthereumTransactionExtras)?.callData?.dataHex.orEmpty(),
)
},
aggregated = false,
)
}
}

View file

@ -3,11 +3,19 @@ package com.tangem.data.blockaid
import com.domain.blockaid.models.dapp.CheckDAppResult import com.domain.blockaid.models.dapp.CheckDAppResult
import com.domain.blockaid.models.dapp.DAppData import com.domain.blockaid.models.dapp.DAppData
import com.domain.blockaid.models.transaction.CheckTransactionResult import com.domain.blockaid.models.transaction.CheckTransactionResult
import com.domain.blockaid.models.transaction.GasEstimationResult
import com.domain.blockaid.models.transaction.TransactionData import com.domain.blockaid.models.transaction.TransactionData
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.blockchain.common.TransactionData as SDKTransactionData
interface BlockAidRepository { interface BlockAidRepository {
suspend fun verifyDAppDomain(data: DAppData): CheckDAppResult suspend fun verifyDAppDomain(data: DAppData): CheckDAppResult
suspend fun verifyTransaction(data: TransactionData): CheckTransactionResult suspend fun verifyTransaction(data: TransactionData): CheckTransactionResult
suspend fun getGasEstimation(
cryptoCurrency: CryptoCurrency,
transactionDataList: List<SDKTransactionData.Uncompiled>,
): GasEstimationResult
} }

View file

@ -0,0 +1,19 @@
package com.tangem.data.blockaid
import arrow.core.Either
import com.domain.blockaid.models.transaction.GasEstimationResult
import com.tangem.blockchain.common.TransactionData
import com.tangem.domain.blockaid.BlockAidGasEstimate
import com.tangem.domain.models.currency.CryptoCurrency
import javax.inject.Inject
class DefaultBlockAidGasEstimate @Inject constructor(
private val repository: BlockAidRepository,
) : BlockAidGasEstimate {
override suspend fun getGasEstimation(
cryptoCurrency: CryptoCurrency,
transactionDataList: List<TransactionData.Uncompiled>,
): Either<Throwable, GasEstimationResult> = Either.catch {
repository.getGasEstimation(cryptoCurrency = cryptoCurrency, transactionDataList = transactionDataList)
}
}

View file

@ -3,12 +3,18 @@ package com.tangem.data.blockaid
import com.domain.blockaid.models.dapp.CheckDAppResult import com.domain.blockaid.models.dapp.CheckDAppResult
import com.domain.blockaid.models.dapp.DAppData import com.domain.blockaid.models.dapp.DAppData
import com.domain.blockaid.models.transaction.CheckTransactionResult import com.domain.blockaid.models.transaction.CheckTransactionResult
import com.domain.blockaid.models.transaction.GasEstimationResult
import com.domain.blockaid.models.transaction.TransactionData import com.domain.blockaid.models.transaction.TransactionData
import com.domain.blockaid.models.transaction.TransactionParams import com.domain.blockaid.models.transaction.TransactionParams
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.data.blockaid.converters.GasEstimationResponseConverter
import com.tangem.datasource.api.common.blockaid.BlockAidApi import com.tangem.datasource.api.common.blockaid.BlockAidApi
import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest import com.tangem.datasource.api.common.blockaid.models.request.DomainScanRequest
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import com.tangem.blockchain.common.TransactionData as SDKTransactionData
internal class DefaultBlockAidRepository( internal class DefaultBlockAidRepository(
private val api: BlockAidApi, private val api: BlockAidApi,
@ -30,6 +36,17 @@ internal class DefaultBlockAidRepository(
} }
} }
override suspend fun getGasEstimation(
cryptoCurrency: CryptoCurrency,
transactionDataList: List<SDKTransactionData.Uncompiled>,
): GasEstimationResult {
val blockchain = cryptoCurrency.network.toBlockchain()
return when {
blockchain.isEvm() -> scanEvmTransactionBulk(blockchain, transactionDataList)
else -> error("Gas estimation with BlockAid not supported by ${blockchain.fullName}")
}
}
private suspend fun scanEvmTransaction(data: TransactionData): CheckTransactionResult = private suspend fun scanEvmTransaction(data: TransactionData): CheckTransactionResult =
withContext(dispatchers.io) { withContext(dispatchers.io) {
val response = api.scanJsonRpc(mapper.mapToEvmRequest(data)) val response = api.scanJsonRpc(mapper.mapToEvmRequest(data))
@ -41,4 +58,15 @@ internal class DefaultBlockAidRepository(
val response = api.scanSolanaMessage(mapper.mapToSolanaRequest(data)) val response = api.scanSolanaMessage(mapper.mapToSolanaRequest(data))
mapper.mapToDomain(response) mapper.mapToDomain(response)
} }
private suspend fun scanEvmTransactionBulk(
blockchain: Blockchain,
transactionDataList: List<SDKTransactionData.Uncompiled>,
): GasEstimationResult = withContext(dispatchers.io) {
val response = api.scanEvmTransactionBulk(
BlockAidEvmScanTransactionConverter(blockchain).convert(transactionDataList),
)
GasEstimationResponseConverter.convert(response)
}
} }

View file

@ -0,0 +1,14 @@
package com.tangem.data.blockaid.converters
import com.domain.blockaid.models.transaction.GasEstimationResult
import com.tangem.blockchain.extensions.hexToBigInteger
import com.tangem.datasource.api.common.blockaid.models.response.GasEstimationResponse
import com.tangem.utils.converter.Converter
internal object GasEstimationResponseConverter : Converter<List<GasEstimationResponse>, GasEstimationResult> {
override fun convert(value: List<GasEstimationResponse>): GasEstimationResult {
return GasEstimationResult(
estimatedGasList = value.map { it.gasEstimation.estimate.hexToBigInteger() },
)
}
}

View file

@ -1,6 +1,8 @@
package com.tangem.data.blockaid.di package com.tangem.data.blockaid.di
import com.tangem.data.blockaid.DefaultBlockAidGasEstimate
import com.tangem.data.blockaid.DefaultBlockAidVerifier import com.tangem.data.blockaid.DefaultBlockAidVerifier
import com.tangem.domain.blockaid.BlockAidGasEstimate
import com.tangem.domain.blockaid.BlockAidVerifier import com.tangem.domain.blockaid.BlockAidVerifier
import dagger.Binds import dagger.Binds
import dagger.Module import dagger.Module
@ -15,4 +17,8 @@ interface BlockAidDataModule {
@Binds @Binds
@Singleton @Singleton
fun bindVerifier(verifier: DefaultBlockAidVerifier): BlockAidVerifier fun bindVerifier(verifier: DefaultBlockAidVerifier): BlockAidVerifier
@Binds
@Singleton
fun bindBlockAidGasEstimate(gasEstimate: DefaultBlockAidGasEstimate): BlockAidGasEstimate
} }

View file

@ -1,7 +1,13 @@
package com.tangem.data.transaction package com.tangem.data.transaction
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
import com.tangem.blockchain.blockchains.ethereum.eip1559.isSupportEIP1559
import com.tangem.blockchain.blockchains.ethereum.network.EthereumFeeHistory
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.Result
import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.blockchainsdk.utils.toBlockchain
@ -12,6 +18,8 @@ import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.domain.walletmanager.WalletManagersFacade
import java.math.BigDecimal
import java.math.BigInteger
internal class DefaultFeeRepository( internal class DefaultFeeRepository(
private val walletManagersFacade: WalletManagersFacade, private val walletManagersFacade: WalletManagersFacade,
@ -22,6 +30,53 @@ internal class DefaultFeeRepository(
return networkId.toBlockchain().isFeeApproximate(amountType) return networkId.toBlockchain().isFeeApproximate(amountType)
} }
override suspend fun getEthereumFeeWithoutGas(
userWallet: UserWallet,
cryptoCurrency: CryptoCurrency,
): Fee.Ethereum {
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWalletId = userWallet.walletId,
network = cryptoCurrency.network,
)
val blockchain = cryptoCurrency.network.toBlockchain()
val ethereumWalletManager = walletManager as? EthereumWalletManager
?: error("Not supported for ${cryptoCurrency.network}")
val fee = if (blockchain.isSupportEIP1559) {
val gasHistory = when (val gasHistory = ethereumWalletManager.getGasHistory()) {
is Result.Failure -> throw gasHistory.error
is Result.Success -> gasHistory.data
}
val marketPriorityFee = when (gasHistory) {
is EthereumFeeHistory.Common -> gasHistory.marketPriorityFee
is EthereumFeeHistory.Fallback -> gasHistory.gasPrice.toBigDecimal() * MULTIPLIER_GAS_PRICE_NORMAL_FEE
}
val maxFeePerGas = gasHistory.baseFee * MULTIPLIER_GAS_PRICE_NORMAL_FEE + marketPriorityFee
getEthEip1559Fee(
maxFeePerGas = maxFeePerGas.toBigInteger(),
priorityFee = marketPriorityFee.toBigInteger(),
blockchain = blockchain,
)
} else {
val gasPrice = when (val gasPrice = ethereumWalletManager.getGasPrice()) {
is Result.Failure -> throw gasPrice.error
is Result.Success -> gasPrice.data
}
getEthLegacyFee(
gasPrice = gasPrice,
blockchain = blockchain,
)
}
return fee
}
override suspend fun calculateFee( override suspend fun calculateFee(
userWallet: UserWallet, userWallet: UserWallet,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
@ -54,4 +109,37 @@ internal class DefaultFeeRepository(
?: error("WalletManager is null"), ?: error("WalletManager is null"),
) )
} }
private fun getEthLegacyFee(gasPrice: BigInteger, blockchain: Blockchain): Fee.Ethereum.Legacy {
val amount = Amount(
value = BigDecimal.ZERO,
blockchain = blockchain,
)
return Fee.Ethereum.Legacy(
amount = amount,
gasLimit = BigInteger.ZERO,
gasPrice = gasPrice,
)
}
private fun getEthEip1559Fee(
maxFeePerGas: BigInteger,
priorityFee: BigInteger,
blockchain: Blockchain,
): Fee.Ethereum.EIP1559 {
val amount = Amount(
value = BigDecimal.ZERO,
blockchain = blockchain,
)
return Fee.Ethereum.EIP1559(
amount = amount,
gasLimit = BigInteger.ZERO,
maxFeePerGas = maxFeePerGas,
priorityFee = priorityFee,
)
}
private companion object {
val MULTIPLIER_GAS_PRICE_NORMAL_FEE = "1.2".toBigDecimal() // 120%
}
} }

View file

@ -1,13 +1,23 @@
plugins { plugins {
alias(deps.plugins.kotlin.jvm) alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
id("configuration") id("configuration")
} }
android {
namespace = "com.tangem.domain.blockaid"
}
dependencies { dependencies {
/* Project - Domain */ /** Project - Domain */
implementation(projects.domain.core) implementation(projects.domain.core)
implementation(projects.domain.blockaid.models) implementation(projects.domain.blockaid.models)
/* Other */ /** Tangem SDK */
implementation(tangemDeps.blockchain)
/** Other */
implementation(deps.moshi.adapters) implementation(deps.moshi.adapters)
} }

View file

@ -0,0 +1,7 @@
package com.domain.blockaid.models.transaction
import java.math.BigInteger
data class GasEstimationResult(
val estimatedGasList: List<BigInteger>,
)

View file

@ -0,0 +1,14 @@
package com.tangem.domain.blockaid
import arrow.core.Either
import com.domain.blockaid.models.transaction.GasEstimationResult
import com.tangem.blockchain.common.TransactionData
import com.tangem.domain.models.currency.CryptoCurrency
interface BlockAidGasEstimate {
suspend fun getGasEstimation(
cryptoCurrency: CryptoCurrency,
transactionDataList: List<TransactionData.Uncompiled>,
): Either<Throwable, GasEstimationResult>
}

View file

@ -2,6 +2,7 @@ package com.tangem.domain.transaction
import com.tangem.blockchain.common.AmountType import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network import com.tangem.domain.models.network.Network
@ -18,4 +19,6 @@ interface FeeRepository {
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
transactionData: TransactionData, transactionData: TransactionData,
): TransactionFee ): TransactionFee
suspend fun getEthereumFeeWithoutGas(userWallet: UserWallet, cryptoCurrency: CryptoCurrency): Fee.Ethereum
} }

View file

@ -18,12 +18,15 @@ dependencies {
implementation(projects.domain.transaction.models) implementation(projects.domain.transaction.models)
implementation(projects.domain.transaction) implementation(projects.domain.transaction)
implementation(projects.domain.legacy) implementation(projects.domain.legacy)
implementation(projects.domain.blockaid.models)
implementation(projects.domain.blockaid)
/** Tandem SDK */ /** Tandem SDK */
implementation(tangemDeps.blockchain) implementation(tangemDeps.blockchain)
/** Other */ /** Other */
implementation(deps.arrow.core) implementation(deps.arrow.core)
implementation(deps.timber)
/** tests */ /** tests */
testImplementation(projects.common.test) testImplementation(projects.common.test)

View file

@ -5,23 +5,85 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData
import com.tangem.domain.blockaid.BlockAidGasEstimate
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.error.FeeErrorResolver import com.tangem.domain.transaction.error.FeeErrorResolver
import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.utils.extensions.isSingleItem import com.tangem.utils.extensions.isSingleItem
import timber.log.Timber
import java.math.BigInteger
class YieldSupplyEstimateEnterFeeUseCase( class YieldSupplyEstimateEnterFeeUseCase(
private val feeRepository: FeeRepository, private val feeRepository: FeeRepository,
private val feeErrorResolver: FeeErrorResolver, private val feeErrorResolver: FeeErrorResolver,
private val blockAidGasEstimate: BlockAidGasEstimate,
) { ) {
suspend operator fun invoke( suspend operator fun invoke(
userWallet: UserWallet, userWallet: UserWallet,
cryptoCurrency: CryptoCurrency, cryptoCurrency: CryptoCurrency,
transactionDataList: List<TransactionData.Uncompiled>, transactionDataList: List<TransactionData.Uncompiled>,
): Either<GetFeeError, List<TransactionData.Uncompiled>> = Either.catch { ): Either<GetFeeError, List<TransactionData.Uncompiled>> = Either.catch {
val withCalculatedFee = transactionDataList.filter { if (transactionDataList.isSingleItem()) {
transactionDataList.map { transaction ->
transaction.copy(
fee = feeRepository.calculateFee(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionData = transaction,
).normal,
)
}
} else {
val estimatedFees = estimateFeeWithBlockAid(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionDataList = transactionDataList,
)
if (estimatedFees.isNullOrEmpty()) {
// Fallback in case block aid returns error
estimateFeeWithStatic(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionDataList = transactionDataList,
)
} else {
estimatedFees
}
}
}.mapLeft(feeErrorResolver::resolve)
private suspend fun estimateFeeWithBlockAid(
userWallet: UserWallet,
cryptoCurrency: CryptoCurrency,
transactionDataList: List<TransactionData.Uncompiled>,
): List<TransactionData.Uncompiled>? {
val estimatedFees = blockAidGasEstimate.getGasEstimation(
cryptoCurrency = cryptoCurrency,
transactionDataList = transactionDataList,
).onLeft(Timber::e).getOrNull() ?: return null
if (estimatedFees.estimatedGasList.isEmpty()) return null
val fee = feeRepository.getEthereumFeeWithoutGas(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
)
return transactionDataList.zip(estimatedFees.estimatedGasList) { transaction, estimatedGas ->
transaction.copy(fee = fee.fixFee(cryptoCurrency, estimatedGas))
}
}
private suspend fun estimateFeeWithStatic(
userWallet: UserWallet,
cryptoCurrency: CryptoCurrency,
transactionDataList: List<TransactionData.Uncompiled>,
): List<TransactionData.Uncompiled> {
val withCalculatedFees = transactionDataList.filter {
(it.extras as? EthereumTransactionExtras)?.callData !is EthereumYieldSupplyEnterCallData (it.extras as? EthereumTransactionExtras)?.callData !is EthereumYieldSupplyEnterCallData
}.map { transaction -> }.map { transaction ->
transaction.copy( transaction.copy(
@ -33,38 +95,30 @@ class YieldSupplyEstimateEnterFeeUseCase(
) )
} }
val withEstimatedCalculatedFee = transactionDataList.filter { val calculatedFee = withCalculatedFees.firstOrNull()?.fee ?: error("Must be any calculated fee")
val withEstimatedFees = transactionDataList.filter {
(it.extras as? EthereumTransactionExtras)?.callData is EthereumYieldSupplyEnterCallData (it.extras as? EthereumTransactionExtras)?.callData is EthereumYieldSupplyEnterCallData
}.map { transaction -> }.map { transaction ->
if (transactionDataList.isSingleItem()) { transaction.copy(fee = calculatedFee.fixFee(cryptoCurrency, ETHEREUM_CONSTANT_GAS_LIMIT))
transaction.copy(
fee = feeRepository.calculateFee(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionData = transaction,
).normal,
)
} else {
transaction.copy(fee = withCalculatedFee.firstOrNull()?.fee?.fixFee(cryptoCurrency))
}
} }
// Transactions order must be preserved // First return calculated fees then estimated
withCalculatedFee + withEstimatedCalculatedFee return withCalculatedFees + withEstimatedFees
}.mapLeft(feeErrorResolver::resolve) }
private fun Fee.fixFee(cryptoCurrency: CryptoCurrency) = when (this) { private fun Fee.fixFee(cryptoCurrency: CryptoCurrency, gasLimit: BigInteger) = when (this) {
is Fee.Ethereum.Legacy -> copy( is Fee.Ethereum.Legacy -> copy(
gasLimit = ETHEREUM_CONSTANT_GAS_LIMIT, gasLimit = gasLimit,
amount = amount.copy( amount = amount.copy(
value = gasPrice.multiply(ETHEREUM_CONSTANT_GAS_LIMIT) value = gasPrice.multiply(gasLimit)
.toBigDecimal().movePointLeft(cryptoCurrency.decimals), .toBigDecimal().movePointLeft(cryptoCurrency.decimals),
), ),
) )
is Fee.Ethereum.EIP1559 -> copy( is Fee.Ethereum.EIP1559 -> copy(
gasLimit = ETHEREUM_CONSTANT_GAS_LIMIT, gasLimit = gasLimit,
amount = amount.copy( amount = amount.copy(
value = maxFeePerGas.multiply(ETHEREUM_CONSTANT_GAS_LIMIT) value = maxFeePerGas.multiply(gasLimit)
.toBigDecimal().movePointLeft(cryptoCurrency.decimals), .toBigDecimal().movePointLeft(cryptoCurrency.decimals),
), ),
) )
@ -73,6 +127,6 @@ class YieldSupplyEstimateEnterFeeUseCase(
private companion object { private companion object {
// Using constant gas limit to avoid fee calculation errors when contract address is not deployed yet // Using constant gas limit to avoid fee calculation errors when contract address is not deployed yet
val ETHEREUM_CONSTANT_GAS_LIMIT = 350_000.toBigInteger() val ETHEREUM_CONSTANT_GAS_LIMIT = 500_000.toBigInteger()
} }
} }

View file

@ -1,6 +1,8 @@
package com.tangem.domain.yield.supply package com.tangem.domain.yield.supply
import arrow.core.Either import arrow.core.Either
import arrow.core.right
import com.domain.blockaid.models.transaction.GasEstimationResult
import com.google.common.truth.Truth import com.google.common.truth.Truth
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.common.Blockchain import com.tangem.blockchain.common.Blockchain
@ -10,6 +12,7 @@ import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderF
import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.yieldsupply.YieldSupplyContractCallDataProviderFactory import com.tangem.blockchain.yieldsupply.YieldSupplyContractCallDataProviderFactory
import com.tangem.domain.blockaid.BlockAidGasEstimate
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWallet
@ -17,9 +20,7 @@ import com.tangem.domain.transaction.FeeRepository
import com.tangem.domain.transaction.error.FeeErrorResolver import com.tangem.domain.transaction.error.FeeErrorResolver
import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase
import io.mockk.coEvery import io.mockk.*
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -30,7 +31,8 @@ import java.math.BigInteger
class YieldSupplyEstimateEnterFeeUseCaseTest { class YieldSupplyEstimateEnterFeeUseCaseTest {
private val feeRepository: FeeRepository = mockk() private val feeRepository: FeeRepository = mockk()
private val feeErrorResolver: FeeErrorResolver = mockk() private val feeErrorResolver: FeeErrorResolver = mockk()
private val useCase = YieldSupplyEstimateEnterFeeUseCase(feeRepository, feeErrorResolver) private val blockAidGasEstimate: BlockAidGasEstimate = mockk()
private val useCase = YieldSupplyEstimateEnterFeeUseCase(feeRepository, feeErrorResolver, blockAidGasEstimate)
private val userWallet: UserWallet = mockk() private val userWallet: UserWallet = mockk()
private val cryptoCurrency: CryptoCurrency = mockk(relaxed = true) { private val cryptoCurrency: CryptoCurrency = mockk(relaxed = true) {
@ -72,45 +74,57 @@ class YieldSupplyEstimateEnterFeeUseCaseTest {
@Test @Test
fun `test enter transaction uses constant gas limit Legacy`() = runTest { fun `test enter transaction uses constant gas limit Legacy`() = runTest {
val fee = TransactionFee.Single(ethLegacyFee()) val fee = TransactionFee.Single(ethLegacyFee())
val deployTx = getDeployTx()
val enterTx = getEnterTx()
coEvery { blockAidGasEstimate.getGasEstimation(any(), any()) } returns GasEstimationResult(emptyList()).right()
coEvery { feeRepository.calculateFee(any(), any(), any()) } returns fee coEvery { feeRepository.calculateFee(any(), any(), any()) } returns fee
val result = useCase( val result = useCase(
userWallet = userWallet, userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
transactionDataList = listOf( transactionDataList = listOf(deployTx, enterTx),
getDeployTx(),
getEnterTx(),
),
) )
Truth.assertThat(result.isRight()).isTrue() Truth.assertThat(result.isRight()).isTrue()
val txs = (result as Either.Right).value coVerify(ordering = Ordering.ORDERED) {
blockAidGasEstimate.getGasEstimation(any(), any())
feeRepository.calculateFee(any(), any(), deployTx)
}
coVerify(inverse = true) {
feeRepository.calculateFee(any(), any(), enterTx)
}
val txs = (result as Either.Right).value
val deployFee = txs.first().fee as Fee.Ethereum.Legacy val deployFee = txs.first().fee as Fee.Ethereum.Legacy
val enterFee = txs.last().fee as Fee.Ethereum.Legacy val enterFee = txs.last().fee as Fee.Ethereum.Legacy
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000)) Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000))
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(350_000)) Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(500_000))
} }
@Test @Test
fun `test not enter transaction uses gas limit Legacy`() = runTest { fun `test not enter transaction uses gas limit Legacy`() = runTest {
val fee = TransactionFee.Single(ethLegacyFee()) val fee = TransactionFee.Single(ethLegacyFee())
val deployTx = getDeployTx()
val approveTx = getApproveTx()
coEvery { blockAidGasEstimate.getGasEstimation(any(), any()) } returns GasEstimationResult(emptyList()).right()
coEvery { feeRepository.calculateFee(any(), any(), any()) } returnsMany listOf(fee, fee) coEvery { feeRepository.calculateFee(any(), any(), any()) } returnsMany listOf(fee, fee)
val result = useCase( val result = useCase(
userWallet = userWallet, userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
transactionDataList = listOf( transactionDataList = listOf(deployTx, approveTx),
getDeployTx(),
getApproveTx(),
),
) )
Truth.assertThat(result.isRight()).isTrue() Truth.assertThat(result.isRight()).isTrue()
val txs = (result as Either.Right).value coVerify(ordering = Ordering.ORDERED) {
blockAidGasEstimate.getGasEstimation(any(), any())
feeRepository.calculateFee(any(), any(), deployTx)
feeRepository.calculateFee(any(), any(), approveTx)
}
val txs = (result as Either.Right).value
val deployFee = txs.first().fee as Fee.Ethereum.Legacy val deployFee = txs.first().fee as Fee.Ethereum.Legacy
val approveFee = txs.last().fee as Fee.Ethereum.Legacy val approveFee = txs.last().fee as Fee.Ethereum.Legacy
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000)) Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000))
@ -120,69 +134,88 @@ class YieldSupplyEstimateEnterFeeUseCaseTest {
@Test @Test
fun `test enter transaction with wrong order uses gas limit Legacy`() = runTest { fun `test enter transaction with wrong order uses gas limit Legacy`() = runTest {
val fee = TransactionFee.Single(ethLegacyFee()) val fee = TransactionFee.Single(ethLegacyFee())
val enterTx = getEnterTx()
val deployTx = getDeployTx()
coEvery { blockAidGasEstimate.getGasEstimation(any(), any()) } returns GasEstimationResult(emptyList()).right()
coEvery { feeRepository.calculateFee(any(), any(), any()) } returnsMany listOf(fee, fee) coEvery { feeRepository.calculateFee(any(), any(), any()) } returnsMany listOf(fee, fee)
val result = useCase( val result = useCase(
userWallet = userWallet, userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
transactionDataList = listOf( transactionDataList = listOf(enterTx, deployTx),
getEnterTx(),
getDeployTx(),
),
) )
Truth.assertThat(result.isRight()).isTrue() Truth.assertThat(result.isRight()).isTrue()
val txs = (result as Either.Right).value coVerify(ordering = Ordering.ORDERED) {
blockAidGasEstimate.getGasEstimation(any(), any())
feeRepository.calculateFee(any(), any(), deployTx)
}
coVerify(inverse = true) {
feeRepository.calculateFee(any(), any(), enterTx)
}
val txs = (result as Either.Right).value
val deployFee = txs.first().fee as Fee.Ethereum.Legacy val deployFee = txs.first().fee as Fee.Ethereum.Legacy
val enterFee = txs.last().fee as Fee.Ethereum.Legacy val enterFee = txs.last().fee as Fee.Ethereum.Legacy
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000)) Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000))
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(350_000)) Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(500_000))
} }
@Test @Test
fun `test enter transaction uses constant gas limit Eip1559`() = runTest { fun `test enter transaction uses constant gas limit Eip1559`() = runTest {
val fee = TransactionFee.Single(ethEip1559Fee()) val fee = TransactionFee.Single(ethEip1559Fee())
val deployTx = getDeployTx()
val enterTx = getEnterTx()
coEvery { blockAidGasEstimate.getGasEstimation(any(), any()) } returns GasEstimationResult(emptyList()).right()
coEvery { feeRepository.calculateFee(any(), any(), any()) } returns fee coEvery { feeRepository.calculateFee(any(), any(), any()) } returns fee
val result = useCase( val result = useCase(
userWallet = userWallet, userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
transactionDataList = listOf( transactionDataList = listOf(deployTx, enterTx),
getDeployTx(),
getEnterTx(),
),
) )
Truth.assertThat(result.isRight()).isTrue() Truth.assertThat(result.isRight()).isTrue()
val txs = (result as Either.Right).value coVerify(ordering = Ordering.ORDERED) {
blockAidGasEstimate.getGasEstimation(any(), any())
feeRepository.calculateFee(any(), any(), deployTx)
}
coVerify(inverse = true) {
feeRepository.calculateFee(any(), any(), enterTx)
}
val txs = (result as Either.Right).value
val deployFee = txs.first().fee as Fee.Ethereum.EIP1559 val deployFee = txs.first().fee as Fee.Ethereum.EIP1559
val enterFee = txs.last().fee as Fee.Ethereum.EIP1559 val enterFee = txs.last().fee as Fee.Ethereum.EIP1559
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000)) Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000))
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(350_000)) Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(500_000))
} }
@Test @Test
fun `test not enter transaction uses gas limit Eip1559`() = runTest { fun `test not enter transaction uses gas limit Eip1559`() = runTest {
val fee = TransactionFee.Single(ethEip1559Fee()) val fee = TransactionFee.Single(ethEip1559Fee())
val deployTx = getDeployTx()
val approveTx = getApproveTx()
coEvery { blockAidGasEstimate.getGasEstimation(any(), any()) } returns GasEstimationResult(emptyList()).right()
coEvery { feeRepository.calculateFee(any(), any(), any()) } returnsMany listOf(fee, fee) coEvery { feeRepository.calculateFee(any(), any(), any()) } returnsMany listOf(fee, fee)
val result = useCase( val result = useCase(
userWallet = userWallet, userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
transactionDataList = listOf( transactionDataList = listOf(deployTx, approveTx),
getDeployTx(),
getApproveTx(),
),
) )
Truth.assertThat(result.isRight()).isTrue() Truth.assertThat(result.isRight()).isTrue()
val txs = (result as Either.Right).value coVerify(ordering = Ordering.ORDERED) {
blockAidGasEstimate.getGasEstimation(any(), any())
feeRepository.calculateFee(any(), any(), deployTx)
feeRepository.calculateFee(any(), any(), approveTx)
}
val txs = (result as Either.Right).value
val deployFee = txs.first().fee as Fee.Ethereum.EIP1559 val deployFee = txs.first().fee as Fee.Ethereum.EIP1559
val approveFee = txs.last().fee as Fee.Ethereum.EIP1559 val approveFee = txs.last().fee as Fee.Ethereum.EIP1559
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000)) Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000))
@ -192,25 +225,137 @@ class YieldSupplyEstimateEnterFeeUseCaseTest {
@Test @Test
fun `test enter transaction with wrong order uses gas limit Eip1559`() = runTest { fun `test enter transaction with wrong order uses gas limit Eip1559`() = runTest {
val fee = TransactionFee.Single(ethEip1559Fee()) val fee = TransactionFee.Single(ethEip1559Fee())
val enterTx = getEnterTx()
val deployTx = getDeployTx()
coEvery { blockAidGasEstimate.getGasEstimation(any(), any()) } returns GasEstimationResult(emptyList()).right()
coEvery { feeRepository.calculateFee(any(), any(), any()) } returnsMany listOf(fee, fee) coEvery { feeRepository.calculateFee(any(), any(), any()) } returnsMany listOf(fee, fee)
val result = useCase( val result = useCase(
userWallet = userWallet, userWallet = userWallet,
cryptoCurrency = cryptoCurrency, cryptoCurrency = cryptoCurrency,
transactionDataList = listOf( transactionDataList = listOf(enterTx, deployTx),
getEnterTx(),
getDeployTx(),
),
) )
Truth.assertThat(result.isRight()).isTrue() Truth.assertThat(result.isRight()).isTrue()
val txs = (result as Either.Right).value coVerify(ordering = Ordering.ORDERED) {
blockAidGasEstimate.getGasEstimation(any(), any())
feeRepository.calculateFee(any(), any(), deployTx)
}
coVerify(inverse = true) {
feeRepository.calculateFee(any(), any(), enterTx)
}
val txs = (result as Either.Right).value
val deployFee = txs.first().fee as Fee.Ethereum.EIP1559 val deployFee = txs.first().fee as Fee.Ethereum.EIP1559
val enterFee = txs.last().fee as Fee.Ethereum.EIP1559 val enterFee = txs.last().fee as Fee.Ethereum.EIP1559
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000)) Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000))
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(350_000)) Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(500_000))
}
@Test
fun `test single transaction with fee Legacy`() = runTest {
val fee = TransactionFee.Single(ethLegacyFee())
val enterTx = getEnterTx()
coEvery { blockAidGasEstimate.getGasEstimation(any(), any()) } returns GasEstimationResult(emptyList()).right()
coEvery { feeRepository.calculateFee(any(), any(), any()) } returns fee
val result = useCase(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionDataList = listOf(enterTx),
)
Truth.assertThat(result.isRight()).isTrue()
coVerify(ordering = Ordering.ORDERED) {
feeRepository.calculateFee(any(), any(), enterTx)
}
coVerify(inverse = true) {
blockAidGasEstimate.getGasEstimation(any(), any())
}
val txs = (result as Either.Right).value
val enterFee = txs.first().fee as Fee.Ethereum.Legacy
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(21_000))
}
@Test
fun `test enter transaction uses block aid Eip1559`() = runTest {
val fee = ethEip1559Fee()
val deployTx = getDeployTx()
val approveTx = getApproveTx()
val enterTx = getEnterTx()
coEvery { blockAidGasEstimate.getGasEstimation(any(), any()) } returns GasEstimationResult(
estimatedGasList = listOf(1_000.toBigInteger(), 2_000.toBigInteger(), 3_000.toBigInteger()),
).right()
coEvery { feeRepository.calculateFee(any(), any(), any()) } returns mockk()
coEvery { feeRepository.getEthereumFeeWithoutGas(any(), any()) } returns fee
val result = useCase(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionDataList = listOf(deployTx, approveTx, enterTx),
)
Truth.assertThat(result.isRight()).isTrue()
coVerify(ordering = Ordering.ORDERED) {
blockAidGasEstimate.getGasEstimation(any(), any())
feeRepository.getEthereumFeeWithoutGas(any(), any())
}
coVerify(inverse = true) {
feeRepository.calculateFee(any(), any(), deployTx)
feeRepository.calculateFee(any(), any(), approveTx)
feeRepository.calculateFee(any(), any(), enterTx)
}
val txs = (result as Either.Right).value
val deployFee = txs.first().fee as Fee.Ethereum.EIP1559
val approveFee = txs[1].fee as Fee.Ethereum.EIP1559
val enterFee = txs.last().fee as Fee.Ethereum.EIP1559
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(1_000))
Truth.assertThat(approveFee.gasLimit).isEqualTo(BigInteger.valueOf(2_000))
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(3_000))
}
@Test
fun `test enter transaction uses block aid Legacy`() = runTest {
val fee = ethLegacyFee()
val deployTx = getDeployTx()
val approveTx = getApproveTx()
val enterTx = getEnterTx()
coEvery { blockAidGasEstimate.getGasEstimation(any(), any()) } returns GasEstimationResult(
estimatedGasList = listOf(1_000.toBigInteger(), 2_000.toBigInteger(), 3_000.toBigInteger()),
).right()
coEvery { feeRepository.calculateFee(any(), any(), any()) } returns mockk()
coEvery { feeRepository.getEthereumFeeWithoutGas(any(), any()) } returns fee
val result = useCase(
userWallet = userWallet,
cryptoCurrency = cryptoCurrency,
transactionDataList = listOf(deployTx, approveTx, enterTx),
)
Truth.assertThat(result.isRight()).isTrue()
coVerify(ordering = Ordering.ORDERED) {
blockAidGasEstimate.getGasEstimation(any(), any())
feeRepository.getEthereumFeeWithoutGas(any(), any())
}
coVerify(inverse = true) {
feeRepository.calculateFee(any(), any(), deployTx)
feeRepository.calculateFee(any(), any(), approveTx)
feeRepository.calculateFee(any(), any(), enterTx)
}
val txs = (result as Either.Right).value
val deployFee = txs.first().fee as Fee.Ethereum.Legacy
val approveFee = txs[1].fee as Fee.Ethereum.Legacy
val enterFee = txs.last().fee as Fee.Ethereum.Legacy
Truth.assertThat(deployFee.gasLimit).isEqualTo(BigInteger.valueOf(1_000))
Truth.assertThat(approveFee.gasLimit).isEqualTo(BigInteger.valueOf(2_000))
Truth.assertThat(enterFee.gasLimit).isEqualTo(BigInteger.valueOf(3_000))
} }
private fun getDeployTx() = uncompiled( private fun getDeployTx() = uncompiled(