Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-21 21:40:19 +07:00
commit 9f68fc1511
19 changed files with 277 additions and 77 deletions

@ -1 +1 @@
Subproject commit 27ddebaece01b8e13a9bbafd51f5ff1d64efc2a6
Subproject commit 7c568701ed57a31b0f1086820435dfc5761714c1

View file

@ -12,10 +12,13 @@ import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.transaction.usecase.*
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import javax.inject.Singleton
@Suppress("TooManyFunctions")
@ -46,6 +49,7 @@ internal object TransactionDomainModule {
walletManagersFacade: WalletManagersFacade,
singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
dispatchers: CoroutineDispatcherProvider,
): SendTransactionUseCase {
return SendTransactionUseCase(
demoConfig = DemoConfig,
@ -53,6 +57,7 @@ internal object TransactionDomainModule {
transactionRepository = transactionRepository,
walletManagersFacade = walletManagersFacade,
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
parallelUpdatingScope = CoroutineScope(SupervisorJob() + dispatchers.io),
getHotWalletSigner = tangemHotWalletSignerFactory::create,
)
}

View file

@ -175,4 +175,13 @@ interface TangemTechApi {
@Header("Cache-Control") cacheControl: String = "max-age=600",
): ApiResponse<PromoBannerResponse>
// endregion
/**
* Stores transaction hash in cache to prevent duplicate push
* notifications for yield operations (deposit, withdraw, send).
* Used when yield operations generate intermediate transactions
* that should not trigger notifications.
*/
@POST("v1/transaction-events")
suspend fun transactionEvents(@Body name: TransactionEventBody): ApiResponse<Unit>
}

View file

@ -0,0 +1,23 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class TransactionEventBody(
@Json(name = "transactionId") val transactionId: String,
@Json(name = "operationType") val operationType: OperationType,
)
@JsonClass(generateAdapter = false)
enum class OperationType {
@Json(name = "YIELD_DEPOSIT")
YIELD_DEPOSIT,
@Json(name = "YIELD_WITHDRAW")
YIELD_WITHDRAW,
@Json(name = "YIELD_SEND")
YIELD_SEND,
}

View file

@ -20,13 +20,19 @@ import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.nft.models.NFTAsset
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.datasource.api.common.response.fold
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.OperationType
import com.tangem.datasource.api.tangemTech.models.TransactionEventBody
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.models.EventTransactionTypeDto
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.math.BigDecimal
@ -34,6 +40,7 @@ import java.math.BigInteger
@Suppress("LargeClass")
internal class DefaultTransactionRepository(
private val tangemTechApi: TangemTechApi,
private val walletManagersFacade: WalletManagersFacade,
private val walletManagersStore: WalletManagersStore,
private val dispatchers: CoroutineDispatcherProvider,
@ -415,6 +422,31 @@ internal class DefaultTransactionRepository(
preparer.prepareAndSignMultiple(transactionData, signer)
}
override suspend fun sendTransactionHash(hash: String, transactionType: EventTransactionTypeDto) {
runCatching(dispatchers.io) {
val operationType = when (transactionType) {
EventTransactionTypeDto.DEPOSIT -> OperationType.YIELD_DEPOSIT
EventTransactionTypeDto.WITHDRAW -> OperationType.YIELD_WITHDRAW
EventTransactionTypeDto.SEND -> OperationType.YIELD_SEND
}
val body = TransactionEventBody(
operationType = operationType,
transactionId = hash,
)
val response = tangemTechApi.transactionEvents(body)
response.fold(
onSuccess = {
Timber.d("Successfully sent yield supply transaction hash: $hash")
},
onError = { error ->
Timber.e(error, "Failed to send yield supply transaction hash: $hash")
},
)
}.onFailure { error ->
Timber.e(error, "Failed to send yield supply transaction hash: $hash")
}
}
private suspend fun getPreparer(network: Network, userWalletId: UserWalletId): TransactionPreparer {
val blockchain = network.toBlockchain()
val walletManager = walletManagersFacade.getOrCreateWalletManager(

View file

@ -4,6 +4,7 @@ import com.tangem.data.transaction.DefaultFeeRepository
import com.tangem.data.transaction.DefaultTransactionRepository
import com.tangem.data.transaction.DefaultWalletAddressServiceRepository
import com.tangem.data.transaction.error.DefaultFeeErrorResolver
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.transaction.FeeRepository
@ -25,11 +26,13 @@ internal object TransactionDataModule {
@Provides
@Singleton
fun providesTransactionRepository(
tangemTechApi: TangemTechApi,
walletManagersFacade: WalletManagersFacade,
walletManagersStore: WalletManagersStore,
dispatchers: CoroutineDispatcherProvider,
): TransactionRepository {
return DefaultTransactionRepository(
tangemTechApi = tangemTechApi,
walletManagersFacade = walletManagersFacade,
walletManagersStore = walletManagersStore,
dispatchers = dispatchers,

View file

@ -22,6 +22,7 @@ dependencies {
/** Core */
implementation(projects.core.datasource)
implementation(projects.core.utils)
implementation(projects.core.analytics)
/** Domain */
implementation(projects.domain.yieldSupply)

View file

@ -5,6 +5,8 @@ import com.tangem.blockchain.yieldsupply.YieldSupplyProvider
import com.tangem.blockchainsdk.utils.fromNetworkId
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.blockchainsdk.utils.toNetworkId
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.core.analytics.models.ExceptionAnalyticsEvent
import com.tangem.data.yield.supply.converters.YieldMarketTokenConverter
import com.tangem.data.yield.supply.converters.YieldTokenChartConverter
import com.tangem.datasource.api.common.response.getOrThrow
@ -32,6 +34,7 @@ internal class DefaultYieldSupplyRepository(
private val store: YieldMarketsStore,
private val walletManagersFacade: WalletManagersFacade,
private val dispatchers: CoroutineDispatcherProvider,
private val analyticsExceptionHandler: AnalyticsExceptionHandler,
) : YieldSupplyRepository {
private val statusMap: MutableMap<String, YieldSupplyEnterStatus> = ConcurrentHashMap()
@ -74,7 +77,15 @@ internal class DefaultYieldSupplyRepository(
userWalletId = userWalletId,
blockchain = cryptoCurrency.network.toBlockchain(),
derivationPath = cryptoCurrency.network.derivationPath.value,
) ?: error("Wallet manager not found")
)
if (walletManager == null) {
analyticsExceptionHandler.sendException(
ExceptionAnalyticsEvent(
exception = IllegalStateException("Wallet manager not found"),
),
)
return@withContext false
}
(walletManager as? YieldSupplyProvider)?.isSupported() ?: false
}

View file

@ -1,5 +1,6 @@
package com.tangem.data.yield.supply.di
import com.tangem.core.analytics.api.AnalyticsExceptionHandler
import com.tangem.data.yield.supply.DefaultYieldSupplyRepository
import com.tangem.data.yield.supply.DefaultYieldSupplyErrorResolver
import com.tangem.data.yield.supply.DefaultYieldSupplyTransactionRepository
@ -39,12 +40,14 @@ internal object YieldSupplyDataModule {
store: YieldMarketsStore,
walletManagersFacade: WalletManagersFacade,
dispatchers: CoroutineDispatcherProvider,
analyticsExceptionHandler: AnalyticsExceptionHandler,
): YieldSupplyRepository {
return DefaultYieldSupplyRepository(
yieldSupplyApi = yieldSupplyApi,
store = store,
dispatchers = dispatchers,
walletManagersFacade = walletManagersFacade,
analyticsExceptionHandler = analyticsExceptionHandler,
)
}

View file

@ -1,21 +1,33 @@
package com.tangem.domain.models.currency
import java.math.BigDecimal
fun CryptoCurrency.Token.yieldSupplyKey(): String {
return "${network.backendId}_$contractAddress"
}
fun CryptoCurrencyStatus.yieldSupplyNotAllAmountSupplied(): Boolean {
if (this.currency !is CryptoCurrency.Token) return false
fun CryptoCurrencyStatus.hasNotSuppliedAmount(): Boolean {
val notSupplied = notSuppliedAmountOrNull() ?: return false
return notSupplied > BigDecimal.ZERO
}
fun CryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount: BigDecimal): Boolean {
val notSupplied = notSuppliedAmountOrNull() ?: return false
return notSupplied >= minAmount
}
fun CryptoCurrencyStatus.notSuppliedAmountOrNull(): BigDecimal? {
if (this.currency !is CryptoCurrency.Token) return null
val supplyStatus = this.value.yieldSupplyStatus
if (supplyStatus?.isActive != true) return false
if (supplyStatus?.isActive != true) return null
val protocolBalance = supplyStatus.effectiveProtocolBalance
val amount = this.value.amount
return if (protocolBalance != null && amount != null) {
amount > protocolBalance
amount.minus(protocolBalance)
} else {
false
null
}
}

View file

@ -0,0 +1,11 @@
package com.tangem.domain.transaction.models
/**
* DTO representing the type of a transaction event on tangem backend to send
* info about transaction happens and its hash
*/
enum class EventTransactionTypeDto {
DEPOSIT,
WITHDRAW,
SEND,
}

View file

@ -9,6 +9,7 @@ import com.tangem.blockchain.nft.models.NFTAsset
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.transaction.models.EventTransactionTypeDto
import java.math.BigDecimal
import java.math.BigInteger
@ -123,4 +124,6 @@ interface TransactionRepository {
userWalletId: UserWalletId,
network: Network,
): com.tangem.blockchain.extensions.Result<List<ByteArray>>
suspend fun sendTransactionHash(hash: String, transactionType: EventTransactionTypeDto)
}

View file

@ -3,6 +3,7 @@ package com.tangem.domain.transaction.usecase
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.common.BlockchainSdkError
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
@ -10,28 +11,38 @@ import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.transaction.TransactionsSendResult
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.network.ResultChecker
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyExitCallData
import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplySendCallData
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.simple
import com.tangem.domain.card.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.card.models.TwinKey
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.demo.DemoTransactionSender
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
import com.tangem.domain.transaction.TransactionRepository
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.error.parseWrappedError
import com.tangem.domain.transaction.models.EventTransactionTypeDto
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.models.wallet.UserWallet
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@Suppress("LongParameterList")
class SendTransactionUseCase(
private val demoConfig: DemoConfig,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val transactionRepository: TransactionRepository,
private val walletManagersFacade: WalletManagersFacade,
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
private val parallelUpdatingScope: CoroutineScope,
private val getHotWalletSigner: (UserWallet.Hot) -> TransactionSigner,
) {
suspend operator fun invoke(
@ -100,7 +111,10 @@ class SendTransactionUseCase(
)
}
.fold(
ifRight = { result -> result.hashes.right() },
ifRight = { result ->
processSentTransactionsHashes(txsData, result.hashes)
result.hashes.right()
},
ifLeft = { it.left() },
)
}
@ -114,6 +128,36 @@ class SendTransactionUseCase(
.map { it.first() }
}
private fun processSentTransactionsHashes(transactions: List<TransactionData>, hashes: List<String>) {
parallelUpdatingScope.launch {
withContext(NonCancellable) {
transactions.forEachIndexed { ind, tx ->
sendHashToBackendIfNeeded(tx, hashes[ind])
}
}
}
}
/**
* Sends tx hash to backend for specific transaction types
*/
private suspend fun sendHashToBackendIfNeeded(transaction: TransactionData, txHash: String) {
(transaction as? TransactionData.Uncompiled)?.let { tx ->
val extras = tx.extras
when (extras) {
is EthereumTransactionExtras -> {
val txType = when (extras.callData) {
is EthereumYieldSupplyEnterCallData -> EventTransactionTypeDto.DEPOSIT
is EthereumYieldSupplySendCallData -> EventTransactionTypeDto.SEND
is EthereumYieldSupplyExitCallData -> EventTransactionTypeDto.WITHDRAW
else -> return
}
transactionRepository.sendTransactionHash(txHash, txType)
}
}
}
}
private suspend fun sendDemo(
userWallet: UserWallet,
network: Network,

View file

@ -17,7 +17,8 @@ import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
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.yieldSupplyNotAllAmountSupplied
import com.tangem.domain.models.currency.hasNotSuppliedAmount
import com.tangem.domain.models.currency.shouldShowNotSuppliedInfoIcon
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.yield.supply.YieldSupplyStatus
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
@ -29,6 +30,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.YieldSupplyMinAmountUseCase
import com.tangem.features.yield.supply.api.YieldSupplyComponent
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R
@ -64,6 +66,7 @@ internal class YieldSupplyModel @Inject constructor(
private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase,
private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase,
private val yieldSupplyRepository: YieldSupplyRepository,
private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase,
) : Model(), YieldSupplyClickIntents {
private val params = paramsContainer.require<YieldSupplyComponent.Params>()
@ -303,7 +306,11 @@ internal class YieldSupplyModel @Inject constructor(
private fun loadActiveState(cryptoCurrencyStatus: CryptoCurrencyStatus, yieldSupplyStatus: YieldSupplyStatus) {
val cryptoCurrencyToken = cryptoCurrency as? CryptoCurrency.Token ?: return
val showWarningIcon = !yieldSupplyStatus.isAllowedToSpend
val showInfoIcon = cryptoCurrencyStatus.yieldSupplyNotAllAmountSupplied()
val state = uiState.value
val isShowInfoIconPrevState = when (state) {
is YieldSupplyUM.Content -> state.showInfoIcon
else -> false
}
if (!yieldSupplyStatus.isAllowedToSpend) {
analyticsEventsHandler.send(
YieldSupplyAnalytics.NoticeApproveNeeded(
@ -331,12 +338,13 @@ internal class YieldSupplyModel @Inject constructor(
),
onClick = ::onActiveClick,
showWarningIcon = showWarningIcon,
showInfoIcon = showInfoIcon,
showInfoIcon = isShowInfoIconPrevState,
apy = tokenStatus.apy.toString(),
)
}
}.onLeft {
Timber.e(it)
computeAndApplyShowInfoIcon(cryptoCurrencyStatus)
}.onLeft { t ->
Timber.e(t)
uiState.update {
YieldSupplyUM.Content(
title = resourceReference(
@ -348,14 +356,36 @@ internal class YieldSupplyModel @Inject constructor(
rewardsApy = TextReference.EMPTY,
onClick = ::onActiveClick,
showWarningIcon = showWarningIcon,
showInfoIcon = showInfoIcon,
showInfoIcon = isShowInfoIconPrevState,
apy = "",
)
}
computeAndApplyShowInfoIcon(cryptoCurrencyStatus)
}
}
}
private fun computeAndApplyShowInfoIcon(cryptoCurrencyStatus: CryptoCurrencyStatus) {
modelScope.launch(dispatchers.default) {
val isShowInfoIcon = if (cryptoCurrencyStatus.hasNotSuppliedAmount()) {
val minAmount = yieldSupplyMinAmountUseCase(userWallet, cryptoCurrencyStatus).getOrNull()
if (minAmount != null) {
cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)
} else {
false
}
} else {
false
}
uiState.update { state ->
when (state) {
is YieldSupplyUM.Content -> state.copy(showInfoIcon = isShowInfoIcon)
else -> state
}
}
}
}
private fun sendInfoAboutProtocolStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) {
if (lastYieldSupplyStatus == cryptoCurrencyStatus.value.yieldSupplyStatus) return
val token = cryptoCurrency as? CryptoCurrency.Token ?: return

View file

@ -108,7 +108,7 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, modifier: Modifier =
Text(
modifier = Modifier.weight(1.0f, fill = false),
text = supplyUM.title.resolveReference(),
style = TangemTheme.typography.subtitle1,
style = TangemTheme.typography.subtitle2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.primary1,
@ -119,12 +119,12 @@ private fun SupplyContent(supplyUM: YieldSupplyUM.Content, modifier: Modifier =
) {
Text(
text = StringsSigns.DOT,
style = TangemTheme.typography.subtitle1,
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
Text(
text = supplyUM.rewardsApy.resolveReference(),
style = TangemTheme.typography.subtitle1,
style = TangemTheme.typography.subtitle2,
maxLines = 1,
color = TangemTheme.colors.text.accent,
)

View file

@ -110,6 +110,7 @@ private fun ColumnScope.Content(yieldSupplyPromoUM: YieldSupplyPromoUM, clickInt
style = LabelStyle.REGULAR,
icon = R.drawable.ic_information_24,
onClick = clickIntents::onApyInfoClick,
onIconClick = clickIntents::onApyInfoClick,
),
)
SpacerH32()

View file

@ -1,12 +1,10 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.model
import arrow.core.getOrElse
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
@ -16,7 +14,6 @@ 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.CryptoCurrencyStatus
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetProtocolBalanceUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase
@ -31,13 +28,10 @@ import com.tangem.features.yield.supply.impl.subcomponents.active.model.transfor
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.transformer.update
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import java.math.BigDecimal
import javax.inject.Inject
@Suppress("LongParameterList")
@ -68,7 +62,7 @@ internal class YieldSupplyActiveModel @Inject constructor(
providerTitle = resourceReference(R.string.yield_module_provider),
subtitle = resourceReference(
id = R.string.yield_module_earn_sheet_provider_description,
formatArgs = wrappedList(cryptoCurrency.symbol, cryptoCurrency.symbol),
formatArgs = wrappedList(cryptoCurrency.symbol, AAVEV3_PREFIX + cryptoCurrency.symbol),
),
subtitleLink = resourceReference(R.string.common_read_more),
notifications = persistentListOf(),
@ -121,7 +115,6 @@ internal class YieldSupplyActiveModel @Inject constructor(
uiState.update {
it.copy(
notifications = getNotifications(cryptoCurrencyStatus),
availableBalance = stringReference(
protocolBalance.format {
crypto(
@ -156,54 +149,6 @@ internal class YieldSupplyActiveModel @Inject constructor(
}
}
private fun getNotifications(cryptoCurrencyStatus: CryptoCurrencyStatus): ImmutableList<NotificationUM> {
val approvalNotification = if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isAllowedToSpend != true) {
NotificationUM.Error(
title = resourceReference(R.string.yield_module_approve_needed_notification_title),
subtitle = resourceReference(R.string.yield_module_approve_needed_notification_description),
iconResId = R.drawable.ic_alert_triangle_20,
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
text = resourceReference(R.string.yield_module_approve_needed_notification_cta),
onClick = params.callback::onApprove,
),
)
} else {
null
}
val notSuppliedNotification = getNotSuppliedNotification(cryptoCurrencyStatus)
return listOfNotNull(
approvalNotification,
notSuppliedNotification,
).toPersistentList()
}
private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? {
val value = cryptoCurrencyStatus.value
val isActive = value.yieldSupplyStatus?.isActive == true
val effectiveProtocolBalance = value.yieldSupplyStatus?.effectiveProtocolBalance ?: null
val amount = value.amount
if (!isActive || effectiveProtocolBalance == null || amount == null) return null
val notDepositedAmount = amount.minus(effectiveProtocolBalance)
return if (notDepositedAmount > BigDecimal.ZERO) {
val formattedAmount =
notDepositedAmount.format { crypto(symbol = "", decimals = cryptoCurrencyStatus.currency.decimals) }
analyticsHandler.send(
YieldSupplyAnalytics.NoticeAmountNotDeposited(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
),
)
NotificationUM.Info.YieldSupplyNotAllAmountSupplied(
formattedAmount = formattedAmount,
symbol = cryptoCurrency.symbol,
)
} else {
null
}
}
private fun loadMinAmount() {
modelScope.launch(dispatchers.default) {
yieldSupplyMinAmountUseCase(
@ -215,6 +160,8 @@ internal class YieldSupplyActiveModel @Inject constructor(
cryptoCurrencyStatus = cryptoCurrencyStatusFlow.value,
appCurrency = appCurrency,
minAmount = minAmount,
analyticsHandler = analyticsHandler,
onApprove = params.callback::onApprove,
),
)
}.onLeft {

View file

@ -1,5 +1,8 @@
package com.tangem.features.yield.supply.impl.subcomponents.active.model.transformers
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
@ -8,18 +11,37 @@ 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.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toPersistentList
import java.math.BigDecimal
/**
* Computes and sets minimum amount (fiat) and fee description note
* Transformer that populates minimum supply amount and related hints for the active Yield Supply screen.
*
* - Sets the displayed minimum amount in fiat and crypto.
* - 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].
*
* @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 analyticsHandler Analytics reporter for user actions.
* @property onApprove Action invoked when the "Approve" notification button is tapped.
*/
internal class YieldSupplyActiveMinAmountTransformer(
private val cryptoCurrencyStatus: CryptoCurrencyStatus,
private val appCurrency: AppCurrency,
private val minAmount: BigDecimal,
private val analyticsHandler: AnalyticsEventHandler,
private val onApprove: () -> Unit,
) : Transformer<YieldSupplyActiveContentUM> {
override fun transform(prevState: YieldSupplyActiveContentUM): YieldSupplyActiveContentUM {
@ -41,6 +63,49 @@ internal class YieldSupplyActiveMinAmountTransformer(
return prevState.copy(
minAmount = stringReference(minAmountFiatText),
minFeeDescription = minFeeNoteValue,
notifications = getNotifications(),
)
}
private fun getNotifications(): ImmutableList<NotificationUM> {
val approvalNotification = if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isAllowedToSpend != true) {
NotificationUM.Error(
title = resourceReference(R.string.yield_module_approve_needed_notification_title),
subtitle = resourceReference(R.string.yield_module_approve_needed_notification_description),
iconResId = R.drawable.ic_alert_triangle_20,
buttonState = NotificationConfig.ButtonsState.PrimaryButtonConfig(
text = resourceReference(R.string.yield_module_approve_needed_notification_cta),
onClick = onApprove,
),
)
} else {
null
}
val notSuppliedNotification = getNotSuppliedNotification(cryptoCurrencyStatus)
return listOfNotNull(
approvalNotification,
notSuppliedNotification,
).toPersistentList()
}
private fun getNotSuppliedNotification(cryptoCurrencyStatus: CryptoCurrencyStatus): NotificationUM? {
return if (cryptoCurrencyStatus.shouldShowNotSuppliedInfoIcon(minAmount)) {
val cryptoCurrency = cryptoCurrencyStatus.currency
val notDepositedAmount = cryptoCurrencyStatus.notSuppliedAmountOrNull()
val formattedAmount =
notDepositedAmount.format { crypto(symbol = "", decimals = cryptoCurrencyStatus.currency.decimals) }
analyticsHandler.send(
YieldSupplyAnalytics.NoticeAmountNotDeposited(
token = cryptoCurrency.symbol,
blockchain = cryptoCurrency.network.name,
),
)
NotificationUM.Info.YieldSupplyNotAllAmountSupplied(
formattedAmount = formattedAmount,
symbol = cryptoCurrency.symbol,
)
} else {
null
}
}
}

View file

@ -5,7 +5,7 @@
# https://github.com/tangem/tangem-sdk-android/
# https://github.com/tangem/vico
tangemBlockchainSdk = "develop-1308"
tangemBlockchainSdk = "develop-1309"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-564"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^