From 7b574b2a20c58137d45d361e579c3c7521dc7545 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 19 Dec 2025 15:29:07 +0400 Subject: [PATCH] Updated on 2026-08-14 --- .../currency/CryptoCurrencyExtensions.kt | 8 +- .../currency/CryptoCurrencyExtensionsTest.kt | 417 ++++++++++++++++++ .../YieldSupplyGetDustMinAmountUseCase.kt | 7 + .../YieldSupplyActiveMinAmountTransformer.kt | 10 +- .../impl/main/model/YieldSupplyModel.kt | 4 +- 5 files changed, 435 insertions(+), 11 deletions(-) create mode 100644 domain/models/src/test/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensionsTest.kt diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt index 09c43362de..cc408905ee 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensions.kt @@ -7,16 +7,16 @@ fun CryptoCurrency.Token.yieldSupplyKey(): String { } fun CryptoCurrencyStatus.hasNotSuppliedAmount(): Boolean { - val notSupplied = notSuppliedAmountOrNull() ?: return false + val notSupplied = notSuppliedCryptoAmountOrNull() ?: return false return notSupplied > BigDecimal.ZERO } -fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount: BigDecimal): Boolean { - val notSupplied = notSuppliedAmountOrNull() ?: return false +fun CryptoCurrencyStatus.shouldShowNotSuppliedNotification(dustAmount: BigDecimal): Boolean { + val notSupplied = notSuppliedCryptoAmountOrNull()?.multiply(this.value.fiatRate) ?: return false return notSupplied >= dustAmount } -fun CryptoCurrencyStatus.notSuppliedAmountOrNull(): BigDecimal? { +fun CryptoCurrencyStatus.notSuppliedCryptoAmountOrNull(): BigDecimal? { if (this.currency !is CryptoCurrency.Token) return null val supplyStatus = this.value.yieldSupplyStatus diff --git a/domain/models/src/test/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensionsTest.kt b/domain/models/src/test/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensionsTest.kt new file mode 100644 index 0000000000..402352cc42 --- /dev/null +++ b/domain/models/src/test/kotlin/com/tangem/domain/models/currency/CryptoCurrencyExtensionsTest.kt @@ -0,0 +1,417 @@ +package com.tangem.domain.models.currency + +import com.google.common.truth.Truth.assertThat +import com.tangem.domain.models.yield.supply.YieldSupplyStatus +import io.mockk.every +import io.mockk.mockk +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.TestInstance +import java.math.BigDecimal + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class CryptoCurrencyExtensionsTest { + + @Test + fun `GIVEN currency is Coin WHEN hasNotSuppliedAmount THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + ) + + val result = status.hasNotSuppliedAmount() + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN yieldSupplyStatus is null WHEN hasNotSuppliedAmount THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = null, + ) + + val result = status.hasNotSuppliedAmount() + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN yieldSupplyStatus isActive is false WHEN hasNotSuppliedAmount THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = false, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.TEN, + ), + ) + + val result = status.hasNotSuppliedAmount() + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN effectiveProtocolBalance is null WHEN hasNotSuppliedAmount THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = null, + ), + amount = BigDecimal.TEN, + ) + + val result = status.hasNotSuppliedAmount() + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN amount is null WHEN hasNotSuppliedAmount THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.TEN, + ), + amount = null, + ) + + val result = status.hasNotSuppliedAmount() + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN notSupplied is zero WHEN hasNotSuppliedAmount THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.TEN, + ), + amount = BigDecimal.TEN, + ) + + val result = status.hasNotSuppliedAmount() + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN notSupplied is negative WHEN hasNotSuppliedAmount THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.TEN, + ), + amount = BigDecimal.ONE, + ) + + val result = status.hasNotSuppliedAmount() + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN notSupplied is positive WHEN hasNotSuppliedAmount THEN returns true`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.ONE, + ), + amount = BigDecimal.TEN, + ) + + val result = status.hasNotSuppliedAmount() + + assertThat(result).isTrue() + } + + @Test + fun `GIVEN currency is Coin WHEN shouldShowNotSuppliedNotification THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + ) + + val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.ONE) + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN yieldSupplyStatus is null WHEN shouldShowNotSuppliedNotification THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = null, + ) + + val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.ONE) + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN yieldSupplyStatus isActive is false WHEN shouldShowNotSuppliedNotification THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = false, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.TEN, + ), + ) + + val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.ONE) + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN effectiveProtocolBalance is null WHEN shouldShowNotSuppliedNotification THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = null, + ), + amount = BigDecimal.TEN, + fiatRate = BigDecimal.ONE, + ) + + val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.ONE) + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN notSupplied in fiat is less than dustAmount WHEN shouldShowNotSuppliedNotification THEN returns false`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal("9"), + ), + amount = BigDecimal.TEN, + fiatRate = BigDecimal.ONE, + ) + + val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.TEN) + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN notSupplied in fiat equals dustAmount WHEN shouldShowNotSuppliedNotification THEN returns true`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal("5"), + ), + amount = BigDecimal.TEN, + fiatRate = BigDecimal("2"), + ) + + val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.TEN) + + assertThat(result).isTrue() + } + + @Test + fun `GIVEN notSupplied in fiat is greater than dustAmount WHEN shouldShowNotSuppliedNotification THEN returns true`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.ONE, + ), + amount = BigDecimal.TEN, + fiatRate = BigDecimal("2"), + ) + + val result = status.shouldShowNotSuppliedNotification(dustAmount = BigDecimal.TEN) + + assertThat(result).isTrue() + } + + @Test + fun `GIVEN USDT with EUR fiat rate and notSupplied below dust WHEN shouldShowNotSuppliedNotification THEN returns false`() { + val usdtToEurRate = BigDecimal("0.93") + val notSuppliedUsdt = BigDecimal("0.05") + val protocolBalance = BigDecimal("100") + val totalAmount = protocolBalance.add(notSuppliedUsdt) + val dustAmountEur = BigDecimal("0.1") + + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = protocolBalance, + ), + amount = totalAmount, + fiatRate = usdtToEurRate, + ) + + val result = status.shouldShowNotSuppliedNotification(dustAmount = dustAmountEur) + + assertThat(result).isFalse() + } + + @Test + fun `GIVEN USDT with EUR fiat rate and notSupplied above dust WHEN shouldShowNotSuppliedNotification THEN returns true`() { + val usdtToEurRate = BigDecimal("0.93") + val notSuppliedUsdt = BigDecimal("0.15") + val protocolBalance = BigDecimal("100") + val totalAmount = protocolBalance.add(notSuppliedUsdt) + val dustAmountEur = BigDecimal("0.1") + + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = protocolBalance, + ), + amount = totalAmount, + fiatRate = usdtToEurRate, + ) + + val result = status.shouldShowNotSuppliedNotification(dustAmount = dustAmountEur) + + assertThat(result).isTrue() + } + + @Test + fun `GIVEN currency is Coin WHEN notSuppliedCryptoAmountOrNull THEN returns null`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + ) + + val result = status.notSuppliedCryptoAmountOrNull() + + assertThat(result).isNull() + } + + @Test + fun `GIVEN yieldSupplyStatus is null WHEN notSuppliedCryptoAmountOrNull THEN returns null`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = null, + ) + + val result = status.notSuppliedCryptoAmountOrNull() + + assertThat(result).isNull() + } + + @Test + fun `GIVEN yieldSupplyStatus isActive is false WHEN notSuppliedCryptoAmountOrNull THEN returns null`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = false, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.TEN, + ), + ) + + val result = status.notSuppliedCryptoAmountOrNull() + + assertThat(result).isNull() + } + + @Test + fun `GIVEN effectiveProtocolBalance is null WHEN notSuppliedCryptoAmountOrNull THEN returns null`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = null, + ), + amount = BigDecimal.TEN, + ) + + val result = status.notSuppliedCryptoAmountOrNull() + + assertThat(result).isNull() + } + + @Test + fun `GIVEN amount is null WHEN notSuppliedCryptoAmountOrNull THEN returns null`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.TEN, + ), + amount = null, + ) + + val result = status.notSuppliedCryptoAmountOrNull() + + assertThat(result).isNull() + } + + @Test + fun `GIVEN valid data WHEN notSuppliedCryptoAmountOrNull THEN returns amount minus protocolBalance`() { + val status = createCryptoCurrencyStatus( + currency = mockk(), + yieldSupplyStatus = YieldSupplyStatus( + isActive = true, + isInitialized = true, + isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal("3"), + ), + amount = BigDecimal.TEN, + ) + + val result = status.notSuppliedCryptoAmountOrNull() + + assertThat(result).isEqualTo(BigDecimal("7")) + } + + private fun createCryptoCurrencyStatus( + currency: CryptoCurrency, + yieldSupplyStatus: YieldSupplyStatus? = null, + amount: BigDecimal? = null, + fiatRate: BigDecimal? = null, + ): CryptoCurrencyStatus { + val value = mockk { + every { this@mockk.yieldSupplyStatus } returns yieldSupplyStatus + every { this@mockk.amount } returns amount + every { this@mockk.fiatRate } returns fiatRate + } + return CryptoCurrencyStatus(currency = currency, value = value) + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt index 7814d60add..941311c2fa 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetDustMinAmountUseCase.kt @@ -3,6 +3,13 @@ package com.tangem.domain.yield.supply.usecase import com.tangem.domain.appcurrency.model.AppCurrency import java.math.BigDecimal +/** + * Use case for getting dust minimum amount for yield supply. + * + * Returns the minimum amount based on the selected [AppCurrency]. + * If [AppCurrency] is in [SUPPORTED_DUST_CURRENCIES], returns [DUST_MIN_AMOUNT], + * otherwise returns the original [minAmount] with trailing zeros stripped. + */ class YieldSupplyGetDustMinAmountUseCase { operator fun invoke(minAmount: BigDecimal, appCurrency: AppCurrency): BigDecimal { diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt index 54dd971067..6350642aae 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/active/model/transformers/YieldSupplyActiveMinAmountTransformer.kt @@ -11,8 +11,8 @@ 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.CryptoCurrencyStatus -import com.tangem.domain.models.currency.notSuppliedAmountOrNull -import com.tangem.domain.models.currency.shouldShowNotSuppliedInfoIcon +import com.tangem.domain.models.currency.notSuppliedCryptoAmountOrNull +import com.tangem.domain.models.currency.shouldShowNotSuppliedNotification import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.impl.active.entity.YieldSupplyActiveContentUM @@ -91,11 +91,11 @@ internal class YieldSupplyActiveMinAmountTransformer( } private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? { - return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustMinAmount)) { + return if (cryptoCurrencyStatus.shouldShowNotSuppliedNotification(dustMinAmount)) { val cryptoCurrency = cryptoCurrencyStatus.currency - val notDepositedAmount = cryptoCurrencyStatus.notSuppliedAmountOrNull() + val notSuppliedAmount = cryptoCurrencyStatus.notSuppliedCryptoAmountOrNull() val formattedAmount = - notDepositedAmount.format { crypto(symbol = "", decimals = cryptoCurrencyStatus.currency.decimals) } + notSuppliedAmount.format { crypto(symbol = "", decimals = cryptoCurrencyStatus.currency.decimals) } analyticsHandler.send( YieldSupplyAnalytics.NoticeAmountNotDeposited( token = cryptoCurrency.symbol, diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt index c6bb211f29..9cd65e6cb7 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/YieldSupplyModel.kt @@ -19,7 +19,7 @@ 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.currency.hasNotSuppliedAmount -import com.tangem.domain.models.currency.shouldShowNotSuppliedInfoIcon +import com.tangem.domain.models.currency.shouldShowNotSuppliedNotification import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.yield.supply.YieldSupplyStatus import com.tangem.domain.networks.single.SingleNetworkStatusFetcher @@ -351,7 +351,7 @@ internal class YieldSupplyModel @Inject constructor( minAmount = minAmount, appCurrency = appCurrency, ) - cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount) + cryptoCurrencyStatus.shouldShowNotSuppliedNotification(dustAmount) } else { false }