Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-12 13:16:10 +05:00
parent aa9956d69f
commit 8cee77705f
7 changed files with 80 additions and 6 deletions

View file

@ -219,4 +219,10 @@ internal object YieldSupplyDomainModule {
yieldSupplyRepository = yieldSupplyRepository,
)
}
@Provides
@Singleton
fun provideYieldSupplyGetDustMinAmountUseCase(): YieldSupplyGetDustMinAmountUseCase {
return YieldSupplyGetDustMinAmountUseCase()
}
}

View file

@ -11,9 +11,9 @@ fun CryptoCurrencyStatus.hasNotSuppliedAmount(): Boolean {
return notSupplied > BigDecimal.ZERO
}
fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount: BigDecimal): Boolean {
fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount: BigDecimal): Boolean {
val notSupplied = notSuppliedAmountOrNull() ?: return false
return notSupplied >= minAmount
return notSupplied >= dustAmount
}
fun CryptoCurrencyStatus.notSuppliedAmountOrNull(): BigDecimal? {

View file

@ -0,0 +1,20 @@
package com.tangem.domain.yield.supply.usecase
import com.tangem.domain.appcurrency.model.AppCurrency
import java.math.BigDecimal
class YieldSupplyGetDustMinAmountUseCase {
operator fun invoke(minAmount: BigDecimal, appCurrency: AppCurrency): BigDecimal {
return if (appCurrency.code in SUPPORTED_DUST_CURRENCIES) {
DUST_MIN_AMOUNT
} else {
minAmount.stripTrailingZeros()
}
}
companion object {
private val DUST_MIN_AMOUNT = BigDecimal("0.1")
private val SUPPORTED_DUST_CURRENCIES = setOf("EUR", "USD", "AUD", "CAD", "GBP")
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.domain.yield.supply.usecase
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.appcurrency.model.AppCurrency
import org.junit.jupiter.api.Test
import java.math.BigDecimal
class YieldSupplyGetDustMinAmountUseCaseTest {
private val useCase = YieldSupplyGetDustMinAmountUseCase()
@Test
fun `GIVEN supported currency WHEN invoke THEN return dust min amount`() {
val minAmount = BigDecimal("123.456")
val appCurrency = AppCurrency(code = "EUR", name = "Euro", symbol = "")
val result = useCase(minAmount, appCurrency)
assertThat(result).isEqualTo(BigDecimal("0.1"))
}
@Test
fun `GIVEN unsupported currency WHEN invoke THEN return min amount stripped`() {
val minAmount = BigDecimal("1.2300")
val appCurrency = AppCurrency(code = "JPY", name = "Japanese Yen", symbol = "¥")
val result = useCase(minAmount, appCurrency)
assertThat(result).isEqualTo(BigDecimal("1.23"))
}
}

View file

@ -58,6 +58,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val urlOpener: UrlOpener,
private val appRouter: AppRouter,
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
) : Model(), YieldSupplyStopEarningComponent.ModelCallback,
YieldSupplyApproveComponent.ModelCallback {
@ -241,11 +242,13 @@ internal class YieldSupplyActiveModel @Inject constructor(
userWalletId,
cryptoCurrencyStatusFlow.value,
).onRight { minAmount ->
val dustAmount = yieldSupplyGetDustMinAmountUseCase(minAmount = minAmount, appCurrency = appCurrency)
uiState.update(
YieldSupplyActiveMinAmountTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value,
appCurrency = appCurrency,
minAmount = minAmount,
dustMinAmount = dustAmount,
analyticsHandler = analyticsHandler,
onApprove = ::onApprove,
),

View file

@ -28,11 +28,12 @@ import java.math.BigDecimal
* - Builds the fee policy note text using the minimum amount.
* - Adds contextual notifications:
* - Approval required notification when spending is not yet allowed (emits analytics on CTA).
* - "Not all amount supplied" info when wallet balance exceeds the supplied balance by more than [minAmount].
* - "Not all amount supplied" info when the not-supplied balance exceeds the dust threshold [dustMinAmount].
*
* @property cryptoCurrencyStatus Current currency status used to calculate values and flags.
* @property appCurrency Preferred fiat currency for formatting.
* @property minAmount Protocol-required minimal amount to deposit/supply (in crypto units).
* @property dustMinAmount Threshold used to detect dust/not-supplied balance (in crypto units).
* @property analyticsHandler Analytics reporter for user actions.
* @property onApprove Action invoked when the "Approve" notification button is tapped.
*/
@ -40,6 +41,7 @@ internal class YieldSupplyActiveMinAmountTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val appCurrency: AppCurrency,
private val minAmount: BigDecimal,
private val dustMinAmount: BigDecimal,
private val analyticsHandler: AnalyticsEventHandler,
private val onApprove: () -> Unit,
) : Transformer<YieldSupplyActiveContentUM> {
@ -89,7 +91,7 @@ internal class YieldSupplyActiveMinAmountTransformer(
}
private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? {
return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)) {
return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustMinAmount)) {
val cryptoCurrency = cryptoCurrencyStatus.currency
val notDepositedAmount = cryptoCurrencyStatus.notSuppliedAmountOrNull()
val formattedAmount =

View file

@ -1,5 +1,6 @@
package com.tangem.features.yield.supply.impl.main.model
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import android.os.SystemClock
import com.tangem.common.routing.AppRoute.YieldSupplyPromo
@ -12,6 +13,8 @@ import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -28,6 +31,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyDeactivateUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyIsAvailableUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetDustMinAmountUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
@ -55,6 +59,7 @@ internal class YieldSupplyModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val appRouter: AppRouter,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
@ -65,6 +70,7 @@ internal class YieldSupplyModel @Inject constructor(
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
private val yieldSupplyRepository: YieldSupplyRepository,
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
private val yieldSupplyGetDustMinAmountUseCase: YieldSupplyGetDustMinAmountUseCase,
) : Model(), YieldSupplyClickIntents {
private val params = paramsContainer.require<YieldSupplyComponent.Params>()
@ -73,6 +79,7 @@ internal class YieldSupplyModel @Inject constructor(
field = MutableStateFlow<YieldSupplyUM>(YieldSupplyUM.Initial)
private val cryptoCurrency = params.cryptoCurrency
private var appCurrency: AppCurrency = AppCurrency.Default
var userWallet: UserWallet by Delegates.notNull()
private val fetchCurrencyJobHolder = JobHolder()
@ -85,7 +92,8 @@ internal class YieldSupplyModel @Inject constructor(
}
private fun checkIfYieldSupplyIsAvailable() {
modelScope.launch(dispatchers.io) {
modelScope.launch(dispatchers.default) {
appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }
val isAvailable = yieldSupplyIsAvailableUseCase(params.userWalletId, params.cryptoCurrency)
if (isAvailable) {
subscribeOnCurrencyStatusUpdates()
@ -345,7 +353,11 @@ internal class YieldSupplyModel @Inject constructor(
val minAmount = yieldSupplyMinAmountUseCase(userWalletId = userWallet.walletId, cryptoCurrencyStatus)
.getOrNull()
if (minAmount != null) {
cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)
val dustAmount = yieldSupplyGetDustMinAmountUseCase(
minAmount = minAmount,
appCurrency = appCurrency,
)
cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(dustAmount)
} else {
false
}