Updated on 2026-08-14
This commit is contained in:
parent
a821732662
commit
30431a3dbb
20 changed files with 540 additions and 34 deletions
|
|
@ -3,6 +3,8 @@ package com.tangem.tap.di.domain
|
||||||
import com.tangem.domain.blockaid.BlockAidGasEstimate
|
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.quotes.QuotesRepository
|
||||||
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyErrorResolver
|
import com.tangem.domain.yield.supply.YieldSupplyErrorResolver
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
import com.tangem.domain.yield.supply.YieldSupplyRepository
|
||||||
import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository
|
import com.tangem.domain.yield.supply.YieldSupplyTransactionRepository
|
||||||
|
|
@ -132,4 +134,18 @@ internal object YieldSupplyDomainModule {
|
||||||
yieldSupplyRepository = yieldSupplyRepository,
|
yieldSupplyRepository = yieldSupplyRepository,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideYieldSupplyMinAmountUseCase(
|
||||||
|
feeRepository: FeeRepository,
|
||||||
|
quotesRepository: QuotesRepository,
|
||||||
|
currenciesRepository: CurrenciesRepository,
|
||||||
|
): YieldSupplyMinAmountUseCase {
|
||||||
|
return YieldSupplyMinAmountUseCase(
|
||||||
|
feeRepository = feeRepository,
|
||||||
|
quotesRepository = quotesRepository,
|
||||||
|
currenciesRepository = currenciesRepository,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -21,6 +21,8 @@ dependencies {
|
||||||
implementation(projects.domain.legacy)
|
implementation(projects.domain.legacy)
|
||||||
implementation(projects.domain.blockaid.models)
|
implementation(projects.domain.blockaid.models)
|
||||||
implementation(projects.domain.blockaid)
|
implementation(projects.domain.blockaid)
|
||||||
|
implementation(projects.domain.quotes)
|
||||||
|
implementation(projects.domain.tokens)
|
||||||
|
|
||||||
/** Tandem SDK */
|
/** Tandem SDK */
|
||||||
implementation(tangemDeps.blockchain)
|
implementation(tangemDeps.blockchain)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
package com.tangem.domain.yield.supply.usecase
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import arrow.core.Either.Companion.catch
|
||||||
|
import com.tangem.blockchain.common.transaction.Fee
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.models.quote.QuoteStatus
|
||||||
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
import com.tangem.domain.quotes.QuotesRepository
|
||||||
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.transaction.FeeRepository
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.math.RoundingMode
|
||||||
|
|
||||||
|
class YieldSupplyMinAmountUseCase(
|
||||||
|
private val feeRepository: FeeRepository,
|
||||||
|
private val quotesRepository: QuotesRepository,
|
||||||
|
private val currenciesRepository: CurrenciesRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(
|
||||||
|
userWallet: UserWallet,
|
||||||
|
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
): Either<Throwable, BigDecimal> = catch {
|
||||||
|
val feeWithoutGas = feeRepository.getEthereumFeeWithoutGas(userWallet, cryptoCurrencyStatus.currency)
|
||||||
|
|
||||||
|
val fiatRate = cryptoCurrencyStatus.value.fiatRate ?: error("Fiat rate is missing")
|
||||||
|
require(fiatRate > BigDecimal.ZERO) { "Fiat rate for token must be > 0" }
|
||||||
|
|
||||||
|
val nativeCryptoCurrency = currenciesRepository.getNetworkCoin(
|
||||||
|
userWalletId = userWallet.walletId,
|
||||||
|
networkId = cryptoCurrencyStatus.currency.network.id,
|
||||||
|
derivationPath = cryptoCurrencyStatus.currency.network.derivationPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
val quotes =
|
||||||
|
quotesRepository.getMultiQuoteSyncOrNull(setOfNotNull(nativeCryptoCurrency.id.rawCurrencyId))
|
||||||
|
?: error("Quotes for native coin are unavailable")
|
||||||
|
|
||||||
|
val quotesStatus = quotes.firstOrNull() ?: error("Empty quotes list for native coin")
|
||||||
|
|
||||||
|
val nativeFiatRate = (quotesStatus.value as? QuoteStatus.Data)?.fiatRate
|
||||||
|
?: error("Native fiat rate is missing")
|
||||||
|
require(nativeFiatRate > BigDecimal.ZERO) { "Native fiat rate must be > 0" }
|
||||||
|
|
||||||
|
val nativeGas = feeWithoutGas.fixFee(nativeCryptoCurrency, ETHEREUM_CONSTANT_GAS_LIMIT)
|
||||||
|
|
||||||
|
val rateRatio = nativeFiatRate.divide(
|
||||||
|
fiatRate,
|
||||||
|
cryptoCurrencyStatus.currency.decimals,
|
||||||
|
RoundingMode.HALF_UP,
|
||||||
|
)
|
||||||
|
|
||||||
|
val tokenValue = rateRatio.multiply(nativeGas.amount.value)
|
||||||
|
|
||||||
|
val feeBuffered = tokenValue.multiply(FEE_BUFFER_MULTIPLIER)
|
||||||
|
|
||||||
|
feeBuffered
|
||||||
|
.divide(MAX_FEE_PERCENT, cryptoCurrencyStatus.currency.decimals, RoundingMode.HALF_UP)
|
||||||
|
.stripTrailingZeros()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Fee.fixFee(cryptoCurrency: CryptoCurrency, gasLimit: BigInteger) = when (this) {
|
||||||
|
is Fee.Ethereum.Legacy -> copy(
|
||||||
|
gasLimit = gasLimit,
|
||||||
|
amount = amount.copy(
|
||||||
|
value = gasPrice.multiply(gasLimit)
|
||||||
|
.toBigDecimal().movePointLeft(cryptoCurrency.decimals),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
is Fee.Ethereum.EIP1559 -> copy(
|
||||||
|
gasLimit = gasLimit,
|
||||||
|
amount = amount.copy(
|
||||||
|
value = maxFeePerGas.multiply(gasLimit)
|
||||||
|
.toBigDecimal().movePointLeft(cryptoCurrency.decimals),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else -> this
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
val FEE_BUFFER_MULTIPLIER: BigDecimal = BigDecimal("1.25")
|
||||||
|
val MAX_FEE_PERCENT: BigDecimal = BigDecimal("0.04")
|
||||||
|
val ETHEREUM_CONSTANT_GAS_LIMIT = 350_000.toBigInteger()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
package com.tangem.domain.yield.supply
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth
|
||||||
|
import com.tangem.blockchain.common.Amount
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
|
import com.tangem.blockchain.common.transaction.Fee
|
||||||
|
import com.tangem.domain.models.StatusSource
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
|
import com.tangem.domain.models.network.Network
|
||||||
|
import com.tangem.domain.models.network.NetworkAddress
|
||||||
|
import com.tangem.domain.models.quote.QuoteStatus
|
||||||
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.quotes.QuotesRepository
|
||||||
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.transaction.FeeRepository
|
||||||
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.math.RoundingMode
|
||||||
|
|
||||||
|
class YieldSupplyMinAmountUseCaseTest {
|
||||||
|
|
||||||
|
private val feeRepository: FeeRepository = mockk(relaxed = true)
|
||||||
|
private val quotesRepository: QuotesRepository = mockk(relaxed = true)
|
||||||
|
private val currenciesRepository: CurrenciesRepository = mockk(relaxed = true)
|
||||||
|
|
||||||
|
private val useCase = YieldSupplyMinAmountUseCase(
|
||||||
|
feeRepository = feeRepository,
|
||||||
|
quotesRepository = quotesRepository,
|
||||||
|
currenciesRepository = currenciesRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN valid inputs WHEN invoke THEN return expected min amount`() = runTest {
|
||||||
|
val network = createNetwork()
|
||||||
|
val nativeCoin = createNativeCoin(network)
|
||||||
|
val token = createToken(network)
|
||||||
|
|
||||||
|
val tokenFiatRate = BigDecimal("1.00")
|
||||||
|
val tokenStatus = CryptoCurrencyStatus(
|
||||||
|
currency = token,
|
||||||
|
value = CryptoCurrencyStatus.Loaded(
|
||||||
|
amount = BigDecimal.ZERO,
|
||||||
|
fiatAmount = BigDecimal.ZERO,
|
||||||
|
fiatRate = tokenFiatRate,
|
||||||
|
priceChange = BigDecimal.ZERO,
|
||||||
|
yieldBalance = null,
|
||||||
|
yieldSupplyStatus = null,
|
||||||
|
hasCurrentNetworkTransactions = false,
|
||||||
|
pendingTransactions = emptySet(),
|
||||||
|
networkAddress = NetworkAddress.Single(
|
||||||
|
NetworkAddress.Address(value = "0xabc", type = NetworkAddress.Address.Type.Primary),
|
||||||
|
),
|
||||||
|
sources = CryptoCurrencyStatus.Sources(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val userWallet = createUserWallet()
|
||||||
|
val maxFeePerGas = BigInteger("158320679232")
|
||||||
|
val fee = createEip1559Fee(maxFeePerGas)
|
||||||
|
|
||||||
|
coEvery { feeRepository.getEthereumFeeWithoutGas(userWallet, token) } returns fee
|
||||||
|
coEvery {
|
||||||
|
currenciesRepository.getNetworkCoin(
|
||||||
|
userWalletId = userWallet.walletId,
|
||||||
|
networkId = token.network.id,
|
||||||
|
derivationPath = token.network.derivationPath,
|
||||||
|
)
|
||||||
|
} returns nativeCoin
|
||||||
|
|
||||||
|
val nativeFiatRate = BigDecimal("0.20353756561552608")
|
||||||
|
coEvery {
|
||||||
|
quotesRepository.getMultiQuoteSyncOrNull(setOf(nativeCoin.id.rawCurrencyId!!))
|
||||||
|
} returns setOf(
|
||||||
|
QuoteStatus(
|
||||||
|
rawCurrencyId = CryptoCurrency.RawID("polygon-ecosystem-token"),
|
||||||
|
value = QuoteStatus.Data(
|
||||||
|
source = StatusSource.ACTUAL,
|
||||||
|
fiatRate = nativeFiatRate,
|
||||||
|
priceChange = BigDecimal("0.09000000000000007"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val expected = expectedMinAmount(maxFeePerGas, nativeFiatRate, tokenFiatRate, token.decimals)
|
||||||
|
val result = useCase(userWallet, tokenStatus).getOrNull()
|
||||||
|
Truth.assertThat(result).isEqualTo(expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN missing token fiat rate WHEN invoke THEN return left with error`() = runTest {
|
||||||
|
val network = createNetwork()
|
||||||
|
val token = createToken(network)
|
||||||
|
val tokenStatus = CryptoCurrencyStatus(
|
||||||
|
currency = token,
|
||||||
|
value = CryptoCurrencyStatus.Custom(
|
||||||
|
amount = BigDecimal.ZERO,
|
||||||
|
fiatAmount = null,
|
||||||
|
fiatRate = null,
|
||||||
|
priceChange = null,
|
||||||
|
yieldBalance = null,
|
||||||
|
yieldSupplyStatus = null,
|
||||||
|
hasCurrentNetworkTransactions = false,
|
||||||
|
pendingTransactions = emptySet(),
|
||||||
|
networkAddress = NetworkAddress.Single(
|
||||||
|
NetworkAddress.Address(value = "0xabc", type = NetworkAddress.Address.Type.Primary),
|
||||||
|
),
|
||||||
|
sources = CryptoCurrencyStatus.Sources(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val userWallet = createUserWallet()
|
||||||
|
val result = useCase(userWallet, tokenStatus)
|
||||||
|
Truth.assertThat(result.isLeft()).isTrue()
|
||||||
|
Truth.assertThat(result.leftOrNull()?.message).isEqualTo("Fiat rate is missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN quotes unavailable WHEN invoke THEN return left with error`() = runTest {
|
||||||
|
val network = createNetwork()
|
||||||
|
val nativeCoin = createNativeCoin(network)
|
||||||
|
val token = createToken(network)
|
||||||
|
val tokenStatus = CryptoCurrencyStatus(
|
||||||
|
currency = token,
|
||||||
|
value = CryptoCurrencyStatus.Custom(
|
||||||
|
amount = BigDecimal.ZERO,
|
||||||
|
fiatAmount = BigDecimal.ZERO,
|
||||||
|
fiatRate = BigDecimal.ONE,
|
||||||
|
priceChange = BigDecimal.ZERO,
|
||||||
|
yieldBalance = null,
|
||||||
|
yieldSupplyStatus = null,
|
||||||
|
hasCurrentNetworkTransactions = false,
|
||||||
|
pendingTransactions = emptySet(),
|
||||||
|
networkAddress = NetworkAddress.Single(
|
||||||
|
NetworkAddress.Address(value = "0xabc", type = NetworkAddress.Address.Type.Primary),
|
||||||
|
),
|
||||||
|
sources = CryptoCurrencyStatus.Sources(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
val userWallet = createUserWallet()
|
||||||
|
val maxFeePerGas = BigInteger("158320679232")
|
||||||
|
val fee = createEip1559Fee(maxFeePerGas)
|
||||||
|
|
||||||
|
coEvery { feeRepository.getEthereumFeeWithoutGas(userWallet, token) } returns fee
|
||||||
|
coEvery {
|
||||||
|
currenciesRepository.getNetworkCoin(
|
||||||
|
userWalletId = userWallet.walletId,
|
||||||
|
networkId = token.network.id,
|
||||||
|
derivationPath = token.network.derivationPath,
|
||||||
|
)
|
||||||
|
} returns nativeCoin
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
quotesRepository.getMultiQuoteSyncOrNull(setOf(nativeCoin.id.rawCurrencyId!!))
|
||||||
|
} returns null
|
||||||
|
val result = useCase(userWallet, tokenStatus)
|
||||||
|
Truth.assertThat(result.isLeft()).isTrue()
|
||||||
|
Truth.assertThat(result.leftOrNull()?.message).isEqualTo("Quotes for native coin are unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNetwork(): Network {
|
||||||
|
val derivationPath = Network.DerivationPath.None
|
||||||
|
return Network(
|
||||||
|
id = Network.ID(Network.RawID("polygon"), derivationPath),
|
||||||
|
backendId = "polygon",
|
||||||
|
name = "Polygon",
|
||||||
|
currencySymbol = "MATIC",
|
||||||
|
derivationPath = derivationPath,
|
||||||
|
isTestnet = false,
|
||||||
|
standardType = Network.StandardType.ERC20,
|
||||||
|
hasFiatFeeRate = false,
|
||||||
|
canHandleTokens = true,
|
||||||
|
transactionExtrasType = Network.TransactionExtrasType.NONE,
|
||||||
|
nameResolvingType = Network.NameResolvingType.ENS,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createNativeCoin(network: Network): CryptoCurrency.Coin {
|
||||||
|
val nativeCoinId = CryptoCurrency.ID(
|
||||||
|
prefix = CryptoCurrency.ID.Prefix.COIN_PREFIX,
|
||||||
|
body = CryptoCurrency.ID.Body.NetworkId(network.rawId),
|
||||||
|
suffix = CryptoCurrency.ID.Suffix.RawID("polygon-ecosystem-token"),
|
||||||
|
)
|
||||||
|
return CryptoCurrency.Coin(
|
||||||
|
id = nativeCoinId,
|
||||||
|
network = network,
|
||||||
|
name = "Polygon",
|
||||||
|
symbol = "MATIC",
|
||||||
|
decimals = 18,
|
||||||
|
iconUrl = null,
|
||||||
|
isCustom = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createToken(network: Network): CryptoCurrency.Token {
|
||||||
|
val tokenId = CryptoCurrency.ID(
|
||||||
|
prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX,
|
||||||
|
body = CryptoCurrency.ID.Body.NetworkId(network.rawId),
|
||||||
|
suffix = CryptoCurrency.ID.Suffix.RawID("test-token", "0xContract"),
|
||||||
|
)
|
||||||
|
return CryptoCurrency.Token(
|
||||||
|
id = tokenId,
|
||||||
|
network = network,
|
||||||
|
name = "Test Token",
|
||||||
|
symbol = "TT",
|
||||||
|
decimals = 18,
|
||||||
|
iconUrl = null,
|
||||||
|
isCustom = false,
|
||||||
|
contractAddress = "0xContract",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createUserWallet(): UserWallet {
|
||||||
|
val wallet = mockk<UserWallet>(relaxed = true)
|
||||||
|
every { wallet.walletId } returns UserWalletId("001122")
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createEip1559Fee(maxFeePerGas: BigInteger): Fee.Ethereum.EIP1559 {
|
||||||
|
return Fee.Ethereum.EIP1559(
|
||||||
|
amount = Amount(value = BigDecimal.ZERO, blockchain = Blockchain.Ethereum),
|
||||||
|
gasLimit = BigInteger.ZERO,
|
||||||
|
maxFeePerGas = maxFeePerGas,
|
||||||
|
priorityFee = BigInteger.ZERO,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expectedMinAmount(
|
||||||
|
maxFeePerGas: BigInteger,
|
||||||
|
nativeFiatRate: BigDecimal,
|
||||||
|
tokenFiatRate: BigDecimal,
|
||||||
|
decimals: Int,
|
||||||
|
): BigDecimal {
|
||||||
|
val gasLimit = BigInteger("350000")
|
||||||
|
val nativeGas = maxFeePerGas.multiply(gasLimit).toBigDecimal().movePointLeft(decimals)
|
||||||
|
val rateRatio = nativeFiatRate.divide(tokenFiatRate, decimals, RoundingMode.HALF_UP)
|
||||||
|
val tokenValue = rateRatio.multiply(nativeGas)
|
||||||
|
val feeBuffered = tokenValue.multiply(BigDecimal("1.25"))
|
||||||
|
return feeBuffered
|
||||||
|
.divide(BigDecimal("0.04"), decimals, RoundingMode.HALF_UP)
|
||||||
|
.stripTrailingZeros()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ internal sealed class YieldSupplyFeeUM {
|
||||||
val feeValue: TextReference,
|
val feeValue: TextReference,
|
||||||
val currentNetworkFeeValue: TextReference,
|
val currentNetworkFeeValue: TextReference,
|
||||||
val maxNetworkFeeValue: TextReference,
|
val maxNetworkFeeValue: TextReference,
|
||||||
|
val minAmountFeeValue: TextReference,
|
||||||
) : YieldSupplyFeeUM()
|
) : YieldSupplyFeeUM()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.tangem.features.yield.supply.impl.common.formatter
|
||||||
|
|
||||||
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
import com.tangem.core.ui.extensions.combinedReference
|
||||||
|
import com.tangem.core.ui.extensions.stringReference
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||||
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
|
import com.tangem.utils.StringsSigns
|
||||||
|
import java.math.BigDecimal
|
||||||
|
|
||||||
|
internal class YieldSupplyMinAmountFormatter(
|
||||||
|
private val feeCryptoCurrency: CryptoCurrency,
|
||||||
|
private val appCurrency: AppCurrency,
|
||||||
|
) {
|
||||||
|
|
||||||
|
operator fun invoke(feeValue: BigDecimal, fiatRate: BigDecimal?): TextReference {
|
||||||
|
val cryptoFee = feeValue.format { crypto(feeCryptoCurrency) }
|
||||||
|
val fiatFeeValue = fiatRate?.let(feeValue::multiply)
|
||||||
|
val fiatFee = fiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||||
|
|
||||||
|
return combinedReference(
|
||||||
|
stringReference(cryptoFee),
|
||||||
|
stringReference(" ${StringsSigns.DOT} "),
|
||||||
|
stringReference(fiatFee),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -141,6 +141,7 @@ private class YieldSupplyActionContentPreviewProvider : PreviewParameterProvider
|
||||||
feeValue = stringReference("0.00020 ETH • \$0.99"),
|
feeValue = stringReference("0.00020 ETH • \$0.99"),
|
||||||
currentNetworkFeeValue = stringReference("1.45 USDT • \$1.45"),
|
currentNetworkFeeValue = stringReference("1.45 USDT • \$1.45"),
|
||||||
maxNetworkFeeValue = stringReference("8.50 USDT • \$8.50"),
|
maxNetworkFeeValue = stringReference("8.50 USDT • \$8.50"),
|
||||||
|
minAmountFeeValue = stringReference("50 USDT • \$50"),
|
||||||
),
|
),
|
||||||
isPrimaryButtonEnabled = false,
|
isPrimaryButtonEnabled = false,
|
||||||
isTransactionSending = false,
|
isTransactionSending = false,
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ internal class YieldSupplyModel @Inject constructor(
|
||||||
resourceReference(
|
resourceReference(
|
||||||
R.string.yield_module_token_details_earn_notification_apy,
|
R.string.yield_module_token_details_earn_notification_apy,
|
||||||
),
|
),
|
||||||
stringReference(tokenStatus.apy.toString() + "%"),
|
stringReference(" ${tokenStatus.apy}%"),
|
||||||
),
|
),
|
||||||
onClick = ::onActiveClick,
|
onClick = ::onActiveClick,
|
||||||
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
|
isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend,
|
||||||
|
|
|
||||||
|
|
@ -42,10 +42,14 @@ internal class YieldSupplyPromoModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onHowItWorksClick() {
|
override fun onHowItWorksClick() {
|
||||||
urlOpener.openUrl("https://tangem.com/") // TODO replace with real link
|
urlOpener.openUrl(HOW_IT_WORKS_URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartEarningClick() {
|
override fun onStartEarningClick() {
|
||||||
bottomSheetNavigation.activate(YieldSupplyPromoConfig.Action)
|
bottomSheetNavigation.activate(YieldSupplyPromoConfig.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val HOW_IT_WORKS_URL = "https://tangem.com/en/blog/post/savings-account "
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,4 +11,5 @@ internal data class YieldSupplyActiveContentUM(
|
||||||
val subtitleLink: TextReference,
|
val subtitleLink: TextReference,
|
||||||
val notificationUM: NotificationUM?,
|
val notificationUM: NotificationUM?,
|
||||||
val apy: TextReference? = null,
|
val apy: TextReference? = null,
|
||||||
|
val minAmount: TextReference?,
|
||||||
)
|
)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.yield.supply.impl.subcomponents.active.model
|
package com.tangem.features.yield.supply.impl.subcomponents.active.model
|
||||||
|
|
||||||
|
import arrow.core.getOrElse
|
||||||
import com.tangem.common.ui.notifications.NotificationUM
|
import com.tangem.common.ui.notifications.NotificationUM
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
|
|
@ -11,12 +12,17 @@ import com.tangem.core.ui.extensions.stringReference
|
||||||
import com.tangem.core.ui.extensions.wrappedList
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||||
import com.tangem.core.ui.format.bigdecimal.format
|
import com.tangem.core.ui.format.bigdecimal.format
|
||||||
|
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||||
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrency
|
import com.tangem.domain.models.currency.CryptoCurrency
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetProtocolBalanceUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetProtocolBalanceUseCase
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
|
||||||
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
|
||||||
import com.tangem.features.yield.supply.impl.R
|
import com.tangem.features.yield.supply.impl.R
|
||||||
|
import com.tangem.features.yield.supply.impl.common.formatter.YieldSupplyMinAmountFormatter
|
||||||
import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveComponent
|
import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveComponent
|
||||||
import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM
|
import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM
|
||||||
|
import com.tangem.utils.StringsSigns.DASH_SIGN
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -29,12 +35,15 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val yieldSupplyGetProtocolBalanceUseCase: YieldSupplyGetProtocolBalanceUseCase,
|
private val yieldSupplyGetProtocolBalanceUseCase: YieldSupplyGetProtocolBalanceUseCase,
|
||||||
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
|
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
|
||||||
|
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
||||||
|
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params: YieldSupplyActiveComponent.Params = paramsContainer.require()
|
private val params: YieldSupplyActiveComponent.Params = paramsContainer.require()
|
||||||
|
|
||||||
private val cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow
|
private val cryptoCurrencyStatusFlow = params.cryptoCurrencyStatusFlow
|
||||||
private val cryptoCurrency = cryptoCurrencyStatusFlow.value.currency
|
private val cryptoCurrency = cryptoCurrencyStatusFlow.value.currency
|
||||||
|
private var appCurrency = AppCurrency.Default
|
||||||
|
|
||||||
val uiState: StateFlow<YieldSupplyActiveContentUM>
|
val uiState: StateFlow<YieldSupplyActiveContentUM>
|
||||||
field = MutableStateFlow(
|
field = MutableStateFlow(
|
||||||
|
|
@ -48,6 +57,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
||||||
),
|
),
|
||||||
subtitleLink = resourceReference(R.string.common_read_more),
|
subtitleLink = resourceReference(R.string.common_read_more),
|
||||||
notificationUM = null,
|
notificationUM = null,
|
||||||
|
minAmount = null,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -55,6 +65,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
||||||
subscribeOnCurrencyUpdates()
|
subscribeOnCurrencyUpdates()
|
||||||
|
|
||||||
modelScope.launch(dispatchers.default) {
|
modelScope.launch(dispatchers.default) {
|
||||||
|
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
|
||||||
val protocolBalance = yieldSupplyGetProtocolBalanceUseCase(
|
val protocolBalance = yieldSupplyGetProtocolBalanceUseCase(
|
||||||
userWalletId = params.userWallet.walletId,
|
userWalletId = params.userWallet.walletId,
|
||||||
cryptoCurrency = cryptoCurrency,
|
cryptoCurrency = cryptoCurrency,
|
||||||
|
|
@ -93,6 +104,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
loadApy()
|
loadApy()
|
||||||
|
loadMinAmount()
|
||||||
|
|
||||||
uiState.update {
|
uiState.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
|
|
@ -126,6 +138,27 @@ internal class YieldSupplyActiveModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun loadMinAmount() {
|
||||||
|
modelScope.launch(dispatchers.default) {
|
||||||
|
yieldSupplyMinAmountUseCase(
|
||||||
|
params.userWallet,
|
||||||
|
cryptoCurrencyStatusFlow.value,
|
||||||
|
).onRight { minAmount ->
|
||||||
|
val minAmountTextReference = YieldSupplyMinAmountFormatter(
|
||||||
|
cryptoCurrencyStatusFlow.value.currency,
|
||||||
|
appCurrency,
|
||||||
|
).invoke(minAmount, cryptoCurrencyStatusFlow.value.value.fiatRate)
|
||||||
|
uiState.update {
|
||||||
|
it.copy(minAmount = minAmountTextReference)
|
||||||
|
}
|
||||||
|
}.onLeft {
|
||||||
|
uiState.update {
|
||||||
|
it.copy(minAmount = TextReference.Str(DASH_SIGN))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val AAVEV3_PREFIX = "a"
|
const val AAVEV3_PREFIX = "a"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@ internal fun YieldSupplyActiveContent(
|
||||||
CurrentApy(state.apy)
|
CurrentApy(state.apy)
|
||||||
chartComponent.Content(Modifier.padding(bottom = 12.dp))
|
chartComponent.Content(Modifier.padding(bottom = 12.dp))
|
||||||
}
|
}
|
||||||
YieldSupplyActiveMyFunds(state = state, isBalanceHidden = isBalanceHidden)
|
|
||||||
|
|
||||||
AnimatedVisibility(state.notificationUM != null) {
|
AnimatedVisibility(state.notificationUM != null) {
|
||||||
val wrappedNotification = remember(this) { requireNotNull(state.notificationUM) }
|
val wrappedNotification = remember(this) { requireNotNull(state.notificationUM) }
|
||||||
|
|
@ -69,6 +68,8 @@ internal fun YieldSupplyActiveContent(
|
||||||
containerColor = TangemTheme.colors.background.action,
|
containerColor = TangemTheme.colors.background.action,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
YieldSupplyActiveMyFunds(state = state, isBalanceHidden = isBalanceHidden)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -163,6 +164,15 @@ private fun YieldSupplyActiveMyFunds(state: YieldSupplyActiveContentUM, isBalanc
|
||||||
info = state.availableBalance,
|
info = state.availableBalance,
|
||||||
isBalanceHidden = isBalanceHidden,
|
isBalanceHidden = isBalanceHidden,
|
||||||
)
|
)
|
||||||
|
HorizontalDivider(
|
||||||
|
thickness = 0.5.dp,
|
||||||
|
color = TangemTheme.colors.stroke.primary,
|
||||||
|
)
|
||||||
|
InfoRow(
|
||||||
|
title = resourceReference(R.string.yield_module_fee_policy_sheet_min_amount_title),
|
||||||
|
info = state.minAmount,
|
||||||
|
isBalanceHidden = false,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,6 +261,7 @@ private class YieldSupplyActiveBottomSheetPreviewProvider : PreviewParameterProv
|
||||||
subtitleLink = resourceReference(R.string.common_read_more),
|
subtitleLink = resourceReference(R.string.common_read_more),
|
||||||
notificationUM = NotificationUM.Error.InvalidAmount,
|
notificationUM = NotificationUM.Error.InvalidAmount,
|
||||||
apy = stringReference("5,14%"),
|
apy = stringReference("5,14%"),
|
||||||
|
minAmount = stringReference("50 USDT"),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.tangem.features.yield.supply.impl.subcomponents.approve.model
|
package com.tangem.features.yield.supply.impl.subcomponents.approve.model
|
||||||
|
|
||||||
import arrow.core.getOrElse
|
import arrow.core.getOrElse
|
||||||
|
import com.tangem.blockchain.common.TransactionData
|
||||||
|
import com.tangem.blockchain.common.transaction.TransactionFee
|
||||||
import com.tangem.core.decompose.di.ModelScoped
|
import com.tangem.core.decompose.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
import com.tangem.core.decompose.model.ParamsContainer
|
||||||
|
|
@ -194,40 +196,47 @@ internal class YieldSupplyApproveModel @Inject constructor(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ifRight = { fee ->
|
ifRight = { fee ->
|
||||||
val feeCryptoValue = fee.normal.amount.value
|
applyFee(fee, approvalTransitionData)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val feeFiatValue = feeCryptoCurrencyStatus.value.fiatRate?.let { rate ->
|
private suspend fun applyFee(transactionFee: TransactionFee, approvalTransitionData: TransactionData.Uncompiled) {
|
||||||
feeCryptoValue?.multiply(rate)
|
val feeCryptoValue = transactionFee.normal.amount.value
|
||||||
}
|
|
||||||
val cryptoFee = feeCryptoValue.format { crypto(feeCryptoCurrencyStatus.currency) }
|
|
||||||
val fiatFee = feeFiatValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
|
||||||
|
|
||||||
uiState.update {
|
val feeFiatValue = feeCryptoCurrencyStatus.value.fiatRate?.let { rate ->
|
||||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
feeCryptoValue?.multiply(rate)
|
||||||
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
|
}
|
||||||
} else {
|
val cryptoFee = feeCryptoValue.format { crypto(feeCryptoCurrencyStatus.currency) }
|
||||||
it.copy(
|
val fiatFee = feeFiatValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||||
isPrimaryButtonEnabled = true,
|
|
||||||
yieldSupplyFeeUM = YieldSupplyFeeUM.Content(
|
uiState.update {
|
||||||
transactionDataList = persistentListOf(approvalTransitionData.copy(fee = fee.normal)),
|
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
||||||
feeValue = combinedReference(
|
it.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
|
||||||
stringReference(cryptoFee),
|
} else {
|
||||||
stringReference(" $DOT "),
|
it.copy(
|
||||||
stringReference(fiatFee),
|
isPrimaryButtonEnabled = true,
|
||||||
),
|
yieldSupplyFeeUM = YieldSupplyFeeUM.Content(
|
||||||
currentNetworkFeeValue = TextReference.EMPTY,
|
transactionDataList = persistentListOf(
|
||||||
maxNetworkFeeValue = TextReference.EMPTY,
|
approvalTransitionData.copy(fee = transactionFee.normal),
|
||||||
),
|
),
|
||||||
)
|
feeValue = combinedReference(
|
||||||
}
|
stringReference(cryptoFee),
|
||||||
}
|
stringReference(" $DOT "),
|
||||||
yieldSupplyNotificationsUpdateTrigger.triggerUpdate(
|
stringReference(fiatFee),
|
||||||
data = YieldSupplyNotificationData(
|
),
|
||||||
feeValue = feeCryptoValue,
|
currentNetworkFeeValue = TextReference.EMPTY,
|
||||||
feeError = null,
|
maxNetworkFeeValue = TextReference.EMPTY,
|
||||||
|
minAmountFeeValue = TextReference.EMPTY,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
yieldSupplyNotificationsUpdateTrigger.triggerUpdate(
|
||||||
|
data = YieldSupplyNotificationData(
|
||||||
|
feeValue = feeCryptoValue,
|
||||||
|
feeError = null,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import com.tangem.features.yield.supply.impl.common.ui.YieldSupplyFeeRow
|
||||||
import com.tangem.utils.StringsSigns
|
import com.tangem.utils.StringsSigns
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
internal fun YieldSupplyFeePolicyContent(
|
internal fun YieldSupplyFeePolicyContent(
|
||||||
yieldSupplyFeeUM: YieldSupplyFeeUM,
|
yieldSupplyFeeUM: YieldSupplyFeeUM,
|
||||||
|
|
@ -61,6 +62,28 @@ internal fun YieldSupplyFeePolicyContent(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
)
|
)
|
||||||
SpacerH24()
|
SpacerH24()
|
||||||
|
FooterContainer(
|
||||||
|
footer = resourceReference(
|
||||||
|
id = R.string.yield_module_fee_policy_sheet_min_amount_note,
|
||||||
|
formatArgs = wrappedList(networkName),
|
||||||
|
),
|
||||||
|
paddingValues = PaddingValues(
|
||||||
|
top = 8.dp,
|
||||||
|
start = 12.dp,
|
||||||
|
end = 12.dp,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
val minAmount = when (yieldSupplyFeeUM) {
|
||||||
|
is YieldSupplyFeeUM.Content -> yieldSupplyFeeUM.minAmountFeeValue
|
||||||
|
YieldSupplyFeeUM.Error -> stringReference(StringsSigns.DASH_SIGN)
|
||||||
|
YieldSupplyFeeUM.Loading -> null
|
||||||
|
}
|
||||||
|
YieldSupplyFeeRow(
|
||||||
|
title = resourceReference(R.string.yield_module_fee_policy_sheet_min_amount_title),
|
||||||
|
value = minAmount,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
SpacerH16()
|
||||||
FooterContainer(
|
FooterContainer(
|
||||||
footer = resourceReference(
|
footer = resourceReference(
|
||||||
id = R.string.yield_module_fee_policy_sheet_current_fee_note,
|
id = R.string.yield_module_fee_policy_sheet_current_fee_note,
|
||||||
|
|
@ -101,6 +124,18 @@ internal fun YieldSupplyFeePolicyContent(
|
||||||
value = maxFee,
|
value = maxFee,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Text(
|
||||||
|
text = stringResourceSafe(R.string.yield_module_fee_policy_tangem_service_fee_title),
|
||||||
|
style = TangemTheme.typography.caption2,
|
||||||
|
color = TangemTheme.colors.text.tertiary,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
top = 8.dp,
|
||||||
|
start = 12.dp,
|
||||||
|
end = 12.dp,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,11 +151,11 @@ private fun YieldSupplyFeePolicyContent_Preview() {
|
||||||
feeValue = stringReference("0.0001 ETH • \$1.45"),
|
feeValue = stringReference("0.0001 ETH • \$1.45"),
|
||||||
maxNetworkFeeValue = stringReference("8.50 USDT • \$8.50"),
|
maxNetworkFeeValue = stringReference("8.50 USDT • \$8.50"),
|
||||||
currentNetworkFeeValue = stringReference("1.45 USDT • \$1.45"),
|
currentNetworkFeeValue = stringReference("1.45 USDT • \$1.45"),
|
||||||
|
minAmountFeeValue = stringReference("50 USDT • \$50"),
|
||||||
),
|
),
|
||||||
tokenSymbol = "USDT",
|
tokenSymbol = "USDT",
|
||||||
networkName = "Ethereum",
|
networkName = "Ethereum",
|
||||||
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
modifier = Modifier.background(TangemTheme.colors.background.primary),
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,7 @@ internal class YieldSupplyStartEarningComponent(
|
||||||
onClick = model::onClick,
|
onClick = model::onClick,
|
||||||
enabled = state.isPrimaryButtonEnabled,
|
enabled = state.isPrimaryButtonEnabled,
|
||||||
iconResId = icon,
|
iconResId = icon,
|
||||||
|
showProgress = state.isTransactionSending,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyEstimateEnterFeeUseCase
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
|
||||||
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
|
||||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase
|
import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase
|
||||||
import com.tangem.features.yield.supply.impl.R
|
import com.tangem.features.yield.supply.impl.R
|
||||||
import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory
|
import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory
|
||||||
|
|
@ -61,11 +62,13 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
||||||
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
|
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
|
||||||
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
|
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
|
||||||
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
|
private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase,
|
||||||
|
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
|
||||||
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
|
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
|
||||||
|
|
||||||
private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require()
|
private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require()
|
||||||
|
|
||||||
private val cryptoCurrency = params.cryptoCurrency
|
private val cryptoCurrency = params.cryptoCurrency
|
||||||
|
private var minAmount: BigDecimal by Delegates.notNull()
|
||||||
var userWallet: UserWallet by Delegates.notNull()
|
var userWallet: UserWallet by Delegates.notNull()
|
||||||
|
|
||||||
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
|
val cryptoCurrencyStatusFlow: StateFlow<CryptoCurrencyStatus>
|
||||||
|
|
@ -112,6 +115,14 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun calculateMinAmount(userWallet: UserWallet, cryptoCurrencyStatus: CryptoCurrencyStatus) {
|
||||||
|
yieldSupplyMinAmountUseCase(userWallet, cryptoCurrencyStatus).onRight {
|
||||||
|
minAmount = it
|
||||||
|
}.onLeft {
|
||||||
|
minAmount = BigDecimal.ZERO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getMaxFee(): BigDecimal? {
|
private suspend fun getMaxFee(): BigDecimal? {
|
||||||
if (uiState.value.maxFee != BigDecimal.ZERO) return uiState.value.maxFee
|
if (uiState.value.maxFee != BigDecimal.ZERO) return uiState.value.maxFee
|
||||||
val yieldTokenStatus = yieldSupplyGetTokenStatusUseCase(cryptoCurrency as CryptoCurrency.Token)
|
val yieldTokenStatus = yieldSupplyGetTokenStatusUseCase(cryptoCurrency as CryptoCurrency.Token)
|
||||||
|
|
@ -166,6 +177,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
||||||
updatedTransactionList = updatedTransactionList,
|
updatedTransactionList = updatedTransactionList,
|
||||||
feeValue = feeSum,
|
feeValue = feeSum,
|
||||||
maxNetworkFee = maxFee,
|
maxNetworkFee = maxFee,
|
||||||
|
minAmount = minAmount,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
yieldSupplyNotificationsUpdateTrigger.triggerUpdate(
|
yieldSupplyNotificationsUpdateTrigger.triggerUpdate(
|
||||||
|
|
@ -276,6 +288,7 @@ internal class YieldSupplyStartEarningModel @Inject constructor(
|
||||||
feeCryptoCurrencyStatusFlow.update { feeCurrencyStatus }
|
feeCryptoCurrencyStatusFlow.update { feeCurrencyStatus }
|
||||||
|
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
|
calculateMinAmount(userWallet, currencyStatus)
|
||||||
onLoadFee()
|
onLoadFee()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,14 @@ import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
|
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyActionUM
|
||||||
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
|
import com.tangem.features.yield.supply.impl.common.entity.YieldSupplyFeeUM
|
||||||
|
import com.tangem.features.yield.supply.impl.common.formatter.YieldSupplyMinAmountFormatter
|
||||||
import com.tangem.utils.StringsSigns.DOT
|
import com.tangem.utils.StringsSigns.DOT
|
||||||
import com.tangem.utils.transformer.Transformer
|
import com.tangem.utils.transformer.Transformer
|
||||||
import kotlinx.collections.immutable.toPersistentList
|
import kotlinx.collections.immutable.toPersistentList
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
internal class YieldSupplyStartEarningFeeContentTransformer(
|
internal class YieldSupplyStartEarningFeeContentTransformer(
|
||||||
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
private val feeCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||||
|
|
@ -23,6 +25,7 @@ internal class YieldSupplyStartEarningFeeContentTransformer(
|
||||||
private val updatedTransactionList: List<TransactionData.Uncompiled>,
|
private val updatedTransactionList: List<TransactionData.Uncompiled>,
|
||||||
private val feeValue: BigDecimal,
|
private val feeValue: BigDecimal,
|
||||||
private val maxNetworkFee: BigDecimal,
|
private val maxNetworkFee: BigDecimal,
|
||||||
|
private val minAmount: BigDecimal,
|
||||||
) : Transformer<YieldSupplyActionUM> {
|
) : Transformer<YieldSupplyActionUM> {
|
||||||
override fun transform(prevState: YieldSupplyActionUM): YieldSupplyActionUM {
|
override fun transform(prevState: YieldSupplyActionUM): YieldSupplyActionUM {
|
||||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||||
|
|
@ -46,6 +49,11 @@ internal class YieldSupplyStartEarningFeeContentTransformer(
|
||||||
}
|
}
|
||||||
val maxFiatFee = maxFiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
val maxFiatFee = maxFiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) }
|
||||||
|
|
||||||
|
val minAmountTextReference = YieldSupplyMinAmountFormatter(
|
||||||
|
cryptoCurrency,
|
||||||
|
appCurrency,
|
||||||
|
).invoke(minAmount, cryptoCurrencyStatus.value.fiatRate)
|
||||||
|
|
||||||
return if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
return if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.Loading) {
|
||||||
prevState.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
|
prevState.copy(yieldSupplyFeeUM = YieldSupplyFeeUM.Loading)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -67,6 +75,7 @@ internal class YieldSupplyStartEarningFeeContentTransformer(
|
||||||
stringReference(" $DOT "),
|
stringReference(" $DOT "),
|
||||||
stringReference(maxFiatFee),
|
stringReference(maxFiatFee),
|
||||||
),
|
),
|
||||||
|
minAmountFeeValue = minAmountTextReference,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,7 @@ internal class YieldSupplyStopEarningComponent(
|
||||||
onClick = model::onClick,
|
onClick = model::onClick,
|
||||||
iconResId = walletInterationIcon(params.userWallet),
|
iconResId = walletInterationIcon(params.userWallet),
|
||||||
enabled = state.isPrimaryButtonEnabled,
|
enabled = state.isPrimaryButtonEnabled,
|
||||||
|
showProgress = state.isTransactionSending,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
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.tokens.FetchCurrencyStatusUseCase
|
||||||
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase
|
||||||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||||
|
|
@ -51,6 +52,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
|
||||||
private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger,
|
private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger,
|
||||||
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
|
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
|
||||||
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
|
||||||
|
private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase,
|
||||||
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
|
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
|
||||||
|
|
||||||
private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require()
|
private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require()
|
||||||
|
|
@ -136,6 +138,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
ifRight = {
|
ifRight = {
|
||||||
|
fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id)
|
||||||
yieldSupplyDeactivateUseCase(cryptoCurrency)
|
yieldSupplyDeactivateUseCase(cryptoCurrency)
|
||||||
params.callback.onTransactionSent()
|
params.callback.onTransactionSent()
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ internal class YieldSupplyStopEarningFeeContentTransformer(
|
||||||
),
|
),
|
||||||
currentNetworkFeeValue = TextReference.EMPTY,
|
currentNetworkFeeValue = TextReference.EMPTY,
|
||||||
maxNetworkFeeValue = TextReference.EMPTY,
|
maxNetworkFeeValue = TextReference.EMPTY,
|
||||||
|
minAmountFeeValue = TextReference.EMPTY,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue