Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-19 15:29:07 +04:00
parent afddda1198
commit 7b574b2a20
5 changed files with 435 additions and 11 deletions

View file

@ -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

View file

@ -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<CryptoCurrency.Coin>(),
)
val result = status.hasNotSuppliedAmount()
assertThat(result).isFalse()
}
@Test
fun `GIVEN yieldSupplyStatus is null WHEN hasNotSuppliedAmount THEN returns false`() {
val status = createCryptoCurrencyStatus(
currency = mockk<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Coin>(),
)
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Coin>(),
)
val result = status.notSuppliedCryptoAmountOrNull()
assertThat(result).isNull()
}
@Test
fun `GIVEN yieldSupplyStatus is null WHEN notSuppliedCryptoAmountOrNull THEN returns null`() {
val status = createCryptoCurrencyStatus(
currency = mockk<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrency.Token>(),
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<CryptoCurrencyStatus.Value> {
every { this@mockk.yieldSupplyStatus } returns yieldSupplyStatus
every { this@mockk.amount } returns amount
every { this@mockk.fiatRate } returns fiatRate
}
return CryptoCurrencyStatus(currency = currency, value = value)
}
}

View file

@ -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 {

View file

@ -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,

View file

@ -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
}