From e2247020e42703d3a7d7fe9cbb7fbb118cf9bdb4 Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Oct 2025 15:00:44 +0500 Subject: [PATCH 01/25] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 12 ----------- .../com/tangem/common/routing/AppRoute.kt | 6 ------ .../send/v2/api/entry/SendEntryRoute.kt | 18 +++++++++++++++++ .../DefaultSendEntryPointComponent.kt | 17 ++++++++++++++++ .../send/v2/entrypoint/SendEntryRoute.kt | 11 ---------- .../entrypoint/model/SendEntryPointModel.kt | 20 ++++++++++++++++++- features/swap-v2/api/build.gradle.kts | 2 ++ .../swap/v2/api/SendWithSwapComponent.kt | 3 +++ .../DefaultSendWithSwapComponent.kt | 18 +++++++++++------ 9 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/entry/SendEntryRoute.kt delete mode 100644 features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/SendEntryRoute.kt diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 3713ecba7b..f2f3a36937 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -36,7 +36,6 @@ import com.tangem.features.send.v2.api.SendComponent import com.tangem.features.send.v2.api.SendEntryPointComponent import com.tangem.features.staking.api.StakingComponent import com.tangem.features.swap.SwapComponent -import com.tangem.features.swap.v2.api.SendWithSwapComponent import com.tangem.features.tangempay.components.TangemPayDetailsComponent import com.tangem.features.tangempay.components.TangemPayOnboardingComponent import com.tangem.features.tangempay.components.TangemPayOnboardingComponent.Params.* @@ -108,7 +107,6 @@ internal class ChildFactory @Inject constructor( private val createWalletBackupComponentFactory: CreateWalletBackupComponent.Factory, private val updateAccessCodeComponentFactory: UpdateAccessCodeComponent.Factory, private val viewPhraseComponentFactory: ViewPhraseComponent.Factory, - private val sendWithSwapComponentFactory: SendWithSwapComponent.Factory, private val sendEntryPointComponentFactory: SendEntryPointComponent.Factory, private val tangemPayDetailsComponentFactory: TangemPayDetailsComponent.Factory, private val tangemPayOnboardingComponentFactory: TangemPayOnboardingComponent.Factory, @@ -567,16 +565,6 @@ internal class ChildFactory @Inject constructor( componentFactory = sendEntryPointComponentFactory, ) } - is AppRoute.SendWithSwap -> { - createComponentChild( - context = context, - params = SendWithSwapComponent.Params( - userWalletId = route.userWalletId, - currency = route.currency, - ), - componentFactory = sendWithSwapComponentFactory, - ) - } is AppRoute.CreateAccount -> { createComponentChild( context = context, diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 74f9b32244..41c3ede5ee 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -361,12 +361,6 @@ sealed class AppRoute(val path: String) : Route { path = "/send_entry_point/${userWalletId.stringValue}/${currency.id.value}?", ) - @Serializable - data class SendWithSwap( - val userWalletId: UserWalletId, - val currency: CryptoCurrency, - ) : AppRoute(path = "/send_with_swap/${userWalletId.stringValue}/${currency.symbol}") - @Serializable data class CreateAccount( val userWalletId: UserWalletId, diff --git a/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/entry/SendEntryRoute.kt b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/entry/SendEntryRoute.kt new file mode 100644 index 0000000000..2fab40cdc0 --- /dev/null +++ b/features/send-v2/api/src/main/java/com/tangem/features/send/v2/api/entry/SendEntryRoute.kt @@ -0,0 +1,18 @@ +package com.tangem.features.send.v2.api.entry + +import com.tangem.core.decompose.navigation.Route + +/** + * Route for switching send and send via swap flows. + */ +sealed class SendEntryRoute : Route { + + /** Route to send screen */ + data object Send : SendEntryRoute() + /** Route to send via swap screen */ + data object SendWithSwap : SendEntryRoute() + /** Route to choose token screen for send via swap */ + data class ChooseToken( + val showSendViaSwapNotification: Boolean, + ) : SendEntryRoute() +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt index fb1684acf2..b9f865983a 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/DefaultSendEntryPointComponent.kt @@ -15,6 +15,8 @@ import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.stack.StackNavigation import com.arkivanov.decompose.router.stack.childStack import com.arkivanov.decompose.router.stack.pop +import com.arkivanov.decompose.value.ObserveLifecycleMode +import com.arkivanov.decompose.value.subscribe import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child import com.tangem.core.decompose.context.childByContext @@ -25,11 +27,14 @@ import com.tangem.features.managetokens.component.ChooseManagedTokensComponent import com.tangem.features.send.v2.api.SendComponent import com.tangem.features.send.v2.api.SendEntryPointComponent import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents +import com.tangem.features.send.v2.api.entry.SendEntryRoute import com.tangem.features.send.v2.entrypoint.model.SendEntryPointModel import com.tangem.features.swap.v2.api.SendWithSwapComponent import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch internal class DefaultSendEntryPointComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, @@ -54,6 +59,7 @@ internal class DefaultSendEntryPointComponent @AssistedInject constructor( userWalletId = params.userWalletId, currency = params.cryptoCurrency, callback = model, + currentRoute = model.currentRoute.asStateFlow(), ), ) @@ -83,6 +89,17 @@ internal class DefaultSendEntryPointComponent @AssistedInject constructor( }, ) + init { + childStack.subscribe( + lifecycle = lifecycle, + mode = ObserveLifecycleMode.CREATE_DESTROY, + ) { stack -> + componentScope.launch { + model.currentRoute.emit(stack.active.configuration) + } + } + } + @Composable override fun Content(modifier: Modifier) { val childStackValue by childStack.subscribeAsState() diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/SendEntryRoute.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/SendEntryRoute.kt deleted file mode 100644 index 470cfb6215..0000000000 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/SendEntryRoute.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.tangem.features.send.v2.entrypoint - -import com.tangem.core.decompose.navigation.Route - -internal sealed class SendEntryRoute : Route { - data object Send : SendEntryRoute() - data object SendWithSwap : SendEntryRoute() - data class ChooseToken( - val showSendViaSwapNotification: Boolean, - ) : SendEntryRoute() -} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt index 24f122f9bd..02ba094907 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/entrypoint/model/SendEntryPointModel.kt @@ -1,19 +1,22 @@ package com.tangem.features.send.v2.entrypoint.model import com.tangem.common.ui.notifications.NotificationId +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.navigation.Router import com.tangem.domain.notifications.ShouldShowNotificationUseCase import com.tangem.features.managetokens.component.ChooseManagedTokensComponent import com.tangem.features.send.v2.api.SendComponent -import com.tangem.features.send.v2.entrypoint.SendEntryRoute +import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents +import com.tangem.features.send.v2.api.entry.SendEntryRoute import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateTrigger import com.tangem.features.swap.v2.api.SendWithSwapComponent import com.tangem.features.swap.v2.api.subcomponents.SwapAmountUpdateTrigger import com.tangem.utils.coroutines.CoroutineDispatcherProvider import jakarta.inject.Inject import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch @Suppress("LongParameterList") @@ -24,6 +27,7 @@ internal class SendEntryPointModel @Inject constructor( private val sendAmountUpdateTrigger: SendAmountUpdateTrigger, private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger, private val shouldShowNotificationUseCase: ShouldShowNotificationUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, ) : Model(), SendComponent.ModelCallback, SendWithSwapComponent.ModelCallback, @@ -32,6 +36,8 @@ internal class SendEntryPointModel @Inject constructor( private var lastSavedAmount = "" private var isEnterInFiat = false + val currentRoute = MutableStateFlow(SendEntryRoute.Send) + override fun onConvertToAnotherToken(lastAmount: String, isEnterInFiatSelected: Boolean) { lastSavedAmount = lastAmount isEnterInFiat = isEnterInFiatSelected @@ -53,6 +59,12 @@ internal class SendEntryPointModel @Inject constructor( modelScope.launch { sendAmountUpdateTrigger.triggerUpdateAmount(lastAmount, isEnterInFiat) router.replaceAll(SendEntryRoute.Send) + analyticsEventHandler.send( + CommonSendAnalyticEvents.AmountScreenOpened( + categoryName = CommonSendAnalyticEvents.SEND_CATEGORY, + source = CommonSendAnalyticEvents.CommonSendSource.Send, + ), + ) } } @@ -64,6 +76,12 @@ internal class SendEntryPointModel @Inject constructor( router.pop() delay(10L) router.replaceAll(SendEntryRoute.SendWithSwap) + analyticsEventHandler.send( + CommonSendAnalyticEvents.AmountScreenOpened( + categoryName = CommonSendAnalyticEvents.SEND_CATEGORY, + source = CommonSendAnalyticEvents.CommonSendSource.SendWithSwap, + ), + ) } } diff --git a/features/swap-v2/api/build.gradle.kts b/features/swap-v2/api/build.gradle.kts index fab13fd59c..6973365628 100644 --- a/features/swap-v2/api/build.gradle.kts +++ b/features/swap-v2/api/build.gradle.kts @@ -13,6 +13,8 @@ dependencies { implementation(projects.core.decompose) implementation(projects.core.ui) + api(projects.features.sendV2.api) + /** Common */ implementation(projects.common.ui) diff --git a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/SendWithSwapComponent.kt b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/SendWithSwapComponent.kt index 365495e6e5..ce7a97b04f 100644 --- a/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/SendWithSwapComponent.kt +++ b/features/swap-v2/api/src/main/java/com/tangem/features/swap/v2/api/SendWithSwapComponent.kt @@ -4,6 +4,8 @@ import com.tangem.core.decompose.factory.ComponentFactory import com.tangem.core.ui.decompose.ComposableContentComponent import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.features.send.v2.api.entry.SendEntryRoute +import kotlinx.coroutines.flow.StateFlow interface SendWithSwapComponent : ComposableContentComponent { @@ -11,6 +13,7 @@ interface SendWithSwapComponent : ComposableContentComponent { val userWalletId: UserWalletId, val currency: CryptoCurrency, val callback: ModelCallback? = null, + val currentRoute: StateFlow, ) interface Factory : ComponentFactory diff --git a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt index c26536b21c..2006967f4f 100644 --- a/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt +++ b/features/swap-v2/impl/src/main/java/com/tangem/features/swap/v2/impl/sendviaswap/DefaultSendWithSwapComponent.kt @@ -23,6 +23,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.swap.models.R import com.tangem.domain.swap.models.SwapDirection import com.tangem.features.send.v2.api.analytics.CommonSendAnalyticEvents +import com.tangem.features.send.v2.api.entry.SendEntryRoute import com.tangem.features.send.v2.api.subcomponents.destination.DestinationRoute import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponent import com.tangem.features.send.v2.api.subcomponents.destination.SendDestinationComponentParams @@ -85,12 +86,17 @@ internal class DefaultSendWithSwapComponent @AssistedInject constructor( componentScope.launch { when (val activeComponent = stack.active.instance) { is SwapAmountComponent -> { - analyticsEventHandler.send( - CommonSendAnalyticEvents.AmountScreenOpened( - categoryName = model.analyticCategoryName, - source = model.analyticsSendSource, - ), - ) + if ( + params.currentRoute.value is SendEntryRoute.SendWithSwap && + model.currentRoute.value != stack.active.configuration + ) { + analyticsEventHandler.send( + CommonSendAnalyticEvents.AmountScreenOpened( + categoryName = model.analyticCategoryName, + source = model.analyticsSendSource, + ), + ) + } activeComponent.updateState(model.uiState.value.amountUM) } is SendDestinationComponent -> { From 5af3da5b7597272a82f800085f6e7943533b6ec7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 12:37:24 +0500 Subject: [PATCH 02/25] Updated on 2026-08-14 --- gradle/tangem_dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index d1245e4f75..f3ceb42d40 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.30-1282" +tangemBlockchainSdk = "releases-5.30-1283" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.30-567" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From 739061ef0ef8ce3a997b9c905030482df83fedd7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 10:38:41 +0300 Subject: [PATCH 03/25] Updated on 2026-08-14 --- tangem-android-tools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tangem-android-tools b/tangem-android-tools index 888c626fc5..baaed0af71 160000 --- a/tangem-android-tools +++ b/tangem-android-tools @@ -1 +1 @@ -Subproject commit 888c626fc514542c3d7568b83d8afe92777b1e0c +Subproject commit baaed0af71b07b226796a023bf4ac2ea410918e2 From fc78ee3faa3b5970cb307b4b75ec167278acd517 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 09:46:05 +0200 Subject: [PATCH 04/25] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 1 + .../tangem/feature/walletsettings/model/WalletSettingsModel.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 258d009305..86a0fef6b9 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -255,6 +255,7 @@ Slow Speed and fee Finish + Forget Free From Synchronize addresses diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt index 5453245f07..8e67d40a1a 100644 --- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt +++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt @@ -205,7 +205,7 @@ internal class WalletSettingsModel @Inject constructor( ), firstActionBuilder = { EventMessageAction( - title = resourceReference(R.string.common_delete), + title = resourceReference(R.string.common_forget), isWarning = true, onClick = ::forgetWallet, ) From 709eb125b31aae90a9ee0c500ee42a41856ae6a2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 11:27:19 +0200 Subject: [PATCH 05/25] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-ja/strings.xml | 2 +- core/res/src/main/res/values-ru/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 2 +- .../impl/model/PushNotificationsModel.kt | 2 +- .../impl/presentation/ui/StakingInitialInfoContent.kt | 2 +- .../feature/wallet/child/wallet/model/WalletModel.kt | 8 ++++---- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 56f11d7388..c4d939bbf7 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1200,7 +1200,7 @@ Monatlich Wöchentlich Belohnungen - Belohnungen auf Solana werden automatisch Deinem Staking-Guthaben hinzugefügt und können nicht separat angezeigt werden. + Belohnungen auf Solana werden automatisch Deinem Staking-Guthaben hinzugefügt und können nicht separat angezeigt werden. Stake gesperrt Mehr staken Beim Staking %1$s wird Dein gesamtes %2$s -Guthaben eingesetzt. Alle weiteren %2$s due Du in Deine Tangem-Wallet einzahlst, werden ebenfalls automatisch eingesetzt. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 52be016eb1..7384640798 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -1202,7 +1202,7 @@ 毎月 毎週 報酬 - Solanaの報酬は自動的にステーキング残高に追加され、個別に表示することはできません。 + Solanaの報酬は自動的にステーキング残高に追加され、個別に表示することはできません。 ステーキングはロックされています もっとステーキングする %1$sをステーキングすると、 %2$s残高全体がステーキングされます。Tangemウォレットに入金した追加の%2$sも、自動的にステーキングされます。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index e65d507051..c24dfefd62 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -1142,7 +1142,7 @@ Месяц Еженедельно Вознаграждения - Вознаграждения в сети Solana автоматически добавляются к вашему стейкинг-балансу и не могут отображаться отдельно. + Вознаграждения в сети Solana автоматически добавляются к вашему стейкинг-балансу и не могут отображаться отдельно. Стейкинг закрыт Застейкать еще При стейкинге %1$s используется весь ваш баланс в %2$s. Любой дополнительный %2$s депозит будет автоматически застейкан. diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 86a0fef6b9..acc79a2262 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1227,7 +1227,7 @@ Monthly Weekly Rewards - Rewards on Solana are automatically added to your staking balance and cannot be shown separately. + Rewards on Solana are automatically added to your staking balance and cannot be shown separately. Stake locked Stake more When staking %1$s, your entire %2$s balance is staked. Any additional %2$s you deposit to your Tangem Wallet will also be automatically staked. diff --git a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt index edc28adbb1..8e955454ce 100644 --- a/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt +++ b/features/push-notifications/impl/src/main/java/com/tangem/features/pushnotifications/impl/model/PushNotificationsModel.kt @@ -54,12 +54,12 @@ internal class PushNotificationsModel @Inject constructor( modelScope.launch { neverRequestPermissionUseCase(PUSH_PERMISSION) neverToInitiallyAskPermissionUseCase(PUSH_PERMISSION) - params.modelCallbacks.onDenySystemPermission() if (params.isBottomSheet) { notificationsRepository.setUserAllowToSubscribeOnPushNotifications(false) } else { params.nextRoute?.let { appRouter.push(it) } } + params.modelCallbacks.onDenySystemPermission() } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 4e9fa839b4..d838576585 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -240,7 +240,7 @@ private fun StakingRewardBlock( TangemTheme.colors.text.tertiary } RewardBlockType.RewardUnavailable.SolanaRewardUnavailable -> { - resourceReference(R.string.staking_soloana_details_auto_claiming_rewards_daily_text) to + resourceReference(R.string.staking_solana_details_auto_claiming_rewards_daily_text) to TangemTheme.colors.text.tertiary } RewardBlockType.NoRewards -> { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt index ebf62b0e39..3faf4f6d3a 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/WalletModel.kt @@ -245,14 +245,14 @@ internal class WalletModel @Inject constructor( "isBiometricsEnabled $isBiometricsEnabled," + "isHuaweiDevice $isHuaweiDevice", ) - if (!isBiometricsEnabled) return@launch - if (!shouldShow) { - return@launch - } if (!shouldAskNotificationPermissionsViaBs) { notificationsRepository.setShouldAskNotificationPermissionsViaBs(true) return@launch } + if (!isBiometricsEnabled) return@launch + if (!shouldShow) { + return@launch + } delay(timeMillis = 1_800) From 8dec1af7aa9dfe98ad10135c290ec21645f26eeb Mon Sep 17 00:00:00 2001 From: Tangem Date: Thu, 16 Oct 2025 16:58:38 +0500 Subject: [PATCH 06/25] Updated on 2026-08-14 --- .../tangem/tap/routing/utils/ChildFactory.kt | 6 ++++- .../com/tangem/common/routing/AppRoute.kt | 1 + core/res/src/main/res/values-de/strings.xml | 2 +- core/res/src/main/res/values-ja/strings.xml | 2 +- core/res/src/main/res/values-ru/strings.xml | 2 +- core/res/src/main/res/values/strings.xml | 17 +++++++++--- .../DefaultCurrencyChecksRepository.kt | 21 +++++++++++++++ .../model/warnings/CryptoCurrencyWarning.kt | 6 +++++ .../tokens/GetCurrencyWarningsUseCase.kt | 26 ++++++++++++++++++- .../repository/CurrencyChecksRepository.kt | 7 +++++ ...okenDetailsNotificationsAnalyticsSender.kt | 5 ++++ .../components/TokenDetailsNotification.kt | 9 +++++++ .../TokenDetailsNotificationConverter.kt | 5 ++++ .../supply/api/YieldSupplyPromoComponent.kt | 1 + .../supply/impl/main/entity/YieldSupplyUM.kt | 2 ++ .../impl/main/model/YieldSupplyModel.kt | 8 ++++++ ...ieldSupplyTokenStatusSuccessTransformer.kt | 1 + .../impl/main/ui/YieldSupplyBlockContent.kt | 3 +++ .../impl/promo/entity/YieldSupplyPromoUM.kt | 1 + .../impl/promo/model/YieldSupplyPromoModel.kt | 6 ++++- .../impl/promo/ui/YieldSupplyPromoContent.kt | 5 ++-- 21 files changed, 124 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index f2f3a36937..e75766a69a 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -628,7 +628,11 @@ internal class ChildFactory @Inject constructor( is AppRoute.YieldSupplyPromo -> { createComponentChild( context = context, - params = YieldSupplyPromoComponent.Params(route.userWalletId, route.cryptoCurrency), + params = YieldSupplyPromoComponent.Params( + userWalletId = route.userWalletId, + currency = route.cryptoCurrency, + apy = route.apy, + ), componentFactory = yieldSupplyPromoComponentFactory, ) } diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 41c3ede5ee..ea0474e6e9 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -410,5 +410,6 @@ sealed class AppRoute(val path: String) : Route { data class YieldSupplyPromo( val userWalletId: UserWalletId, val cryptoCurrency: CryptoCurrency, + val apy: String, ) : AppRoute(path = "/yield_supply_promo/${userWalletId.stringValue}/${cryptoCurrency.symbol}") } \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index c4d939bbf7..86569cbb95 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -262,7 +262,7 @@ Importieren In Arbeit Später - Mehr erfahren + Mehr erfahren %1$s übrig Legacy Bitcoin Gesperrt diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 7384640798..ccca2a4779 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -895,7 +895,7 @@ 最大%d日 %s分 - 下記より利用可能 + 下記より利用可能 以下が手に入ります。 サービスは外部プロバイダーによって提供されます。 \nTangemは責任を負いません。 この画面を閉じて、トークンの詳細画面で取引状況を確認できます。 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index c24dfefd62..5ed2ce02b7 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -202,7 +202,7 @@ Импортировать В процессе Позже - Узнать больше + Узнать больше Осталось %1$s Заблокирован Основная сеть diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index acc79a2262..f6e86e035a 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -22,6 +22,7 @@ You have already exceeded the limit of 20 active accounts. Archive one to recover. Can\'t recover account Archived + We couldn’t archive account. Please try again later. We couldn’t create account. Please try again later. Account created Archive account @@ -260,6 +261,7 @@ From Synchronize addresses Get started + Get token Go to provider Go to token Got it @@ -268,7 +270,7 @@ Import In progress Later - Learn more + Learn more %1$s left Legacy Bitcoin Locked @@ -617,6 +619,7 @@ The selected token is currently unavailable for actions within the crypto wallet. But worry not, you can express your interest by upvoting it. Upvote The wallet doesn\'t support more than one network + About coin To buy, exchange, or receive this asset, add it to your portfolio This asset is currently not supported in the wallet This asset is not available for this wallet @@ -652,6 +655,7 @@ Trending Staking is the easiest way to receive rewards on your crypto. %s Earn up to %s APY + Token Added About %s %d exchange @@ -917,7 +921,7 @@ up to %d days %s min - Available from + Available from You get Service is provided by an external provider. \nTangem is not responsible. You can close this screen and check the transaction status on the token details screen. @@ -1300,6 +1304,10 @@ You receive Choose token not available + Deposit + Service fees + Fee + Withdrawal Failed to load data. Try again later. Hide Technical issues detected. Please try again later or contact support. @@ -1335,6 +1343,7 @@ Sending funds will be available once the pending transaction(s) in network %s is complete Selling %s is not supported by current providers, but we are working to add more options. Staking %s is not supported by current providers, but we are working to add more options. + Text here Generate XPUB Hide You are about to hide this token from the main screen. You can add it back anytime through the manage tokens page. @@ -1822,8 +1831,8 @@ Aave is a decentralized protocol managing over $81.9 billion in total value. Decentralized and self-custodial By using service, you agree with provider\n%1$s and %2$s - Earn %s%% yearly - Aave • Variable Interest Rate + Connect Aave + Aave %1$s%% • Variable Interest Rate Aave Avg %s Last year returns diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index f374404cbe..362d59b4a9 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -4,7 +4,9 @@ import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider import com.tangem.blockchain.common.FeeResourceAmountProvider import com.tangem.blockchain.common.MinimumSendAmountProvider import com.tangem.blockchain.common.ReserveAmountProvider +import com.tangem.blockchain.common.Token import com.tangem.blockchain.common.UtxoAmountLimitProvider +import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.data.tokens.converters.UtxoConverter import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus @@ -21,6 +23,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.isZero import com.tangem.utils.extensions.orZero import kotlinx.coroutines.withContext +import timber.log.Timber import java.math.BigDecimal internal class DefaultCurrencyChecksRepository( @@ -161,4 +164,22 @@ internal class DefaultCurrencyChecksRepository( else -> null } } + + override suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal? { + require(cryptoCurrency is CryptoCurrency.Token) + return runCatching { + val walletManager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + blockchain = cryptoCurrency.network.toBlockchain(), + derivationPath = cryptoCurrency.network.derivationPath.value, + ) ?: error("Wallet manager not found") + walletManager.getProtocolBalance( + token = Token( + symbol = cryptoCurrency.symbol, + contractAddress = cryptoCurrency.contractAddress, + decimals = cryptoCurrency.decimals, + ), + ) + }.onFailure(Timber::e).getOrThrow() + } } \ No newline at end of file diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index 083b0aba95..a00521acac 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -68,4 +68,10 @@ sealed class CryptoCurrencyWarning { val requiredAmount: BigDecimal, val currencyDecimals: Int, ) : CryptoCurrencyWarning() + + data class YieldSupplyNotDepositedAmount( + val currency: CryptoCurrency, + val currencySymbol: String, + val amount: BigDecimal, + ) : CryptoCurrencyWarning() } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index d4611e9d40..3afc61c48b 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -51,7 +51,8 @@ class GetCurrencyWarningsUseCase( flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus)), flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)), flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)), - ) { coinRelatedWarnings, maybeRentWarning, maybeEdWarning, maybeFeeResource -> + flowOf(currencyChecksRepository.getProtocolBalance(userWalletId, currency)), + ) { coinRelatedWarnings, maybeRentWarning, maybeEdWarning, maybeFeeResource, yieldSupplyProtocolBalance -> setOfNotNull( maybeRentWarning, maybeEdWarning?.let { getExistentialDepositWarning(currency, it) }, @@ -62,6 +63,7 @@ class GetCurrencyWarningsUseCase( getBeaconChainShutdownWarning(rawId = currency.network.id.rawId), getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency), getMigrationFromMaticToPolWarning(currency), + getYieldSupplyWarning(cryptoCurrencyStatus = currencyStatus, yieldSupplyProtocolBalance), ) }.flowOn(dispatchers.io) } @@ -261,6 +263,28 @@ class GetCurrencyWarningsUseCase( } } + private fun getYieldSupplyWarning( + cryptoCurrencyStatus: CryptoCurrencyStatus, + protocolBalance: BigDecimal?, + ): CryptoCurrencyWarning? { + val value = cryptoCurrencyStatus.value + val isActive = value.yieldSupplyStatus?.isActive == true + val amount = value.amount + + if (!isActive || protocolBalance == null || amount == null) return null + + val notDepositedAmount = amount.minus(protocolBalance) + return if (notDepositedAmount > BigDecimal.ZERO) { + CryptoCurrencyWarning.YieldSupplyNotDepositedAmount( + currency = cryptoCurrencyStatus.currency, + amount = notDepositedAmount, + currencySymbol = cryptoCurrencyStatus.currency.symbol, + ) + } else { + null + } + } + private fun BigDecimal?.isZero(): Boolean { return this?.signum() == 0 } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt index 8351958b10..46307255e9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt @@ -61,4 +61,11 @@ interface CurrencyChecksRepository { currencyStatus: CryptoCurrencyStatus, balanceAfterTransaction: BigDecimal, ): CryptoCurrencyWarning.Rent? + + /** + * Returns the YieldSupplied protocol balance in Aave for the given `cryptoCurrency`. + * This represents the amount supplied to the protocol for the specified `userWalletId` + * (e.g., aTokens balance). Returns null if not applicable or unknown. + */ + suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal? } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index c02b4801b9..3eba98c4e1 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -7,6 +7,7 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification +import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics internal class TokenDetailsNotificationsAnalyticsSender( private val cryptoCurrency: CryptoCurrency, @@ -44,6 +45,10 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.KaspaIncompleteTransactionWarning -> TokenDetailsAnalyticsEvent.Notice.Reveal( currency = cryptoCurrency, ) + is TokenDetailsNotification.YieldSupplyNotTransferedToAave -> YieldSupplyAnalytics.NoticeNotEnoughMinAmount( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + ) is TokenDetailsNotification.NetworksUnreachable, is TokenDetailsNotification.ExistentialDeposit, is TokenDetailsNotification.NetworksNoAccount, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index 35937498ee..97ce580d0d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -6,6 +6,7 @@ import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet @@ -257,4 +258,12 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { iconResId = R.drawable.ic_error_sync_24, ), ) + + data class YieldSupplyNotTransferedToAave(val tokenName: String, val amount: String) : Warning( + title = resourceReference( + id = R.string.yield_module_amount_not_transfered_to_aave_title, + wrappedList(amount, tokenName), + ), + subtitle = stringReference(""), + ) } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt index a192ef46d0..c5081a966c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsNotificationConverter.kt @@ -24,6 +24,7 @@ import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import timber.log.Timber import java.math.BigDecimal +import kotlin.String internal class TokenDetailsNotificationConverter( private val userWalletId: UserWalletId, @@ -155,6 +156,10 @@ internal class TokenDetailsNotificationConverter( ) is CryptoCurrencyWarning.MigrationMaticToPol -> MigrationMaticToPol is CryptoCurrencyWarning.UsedOutdatedDataWarning -> UsedOutdatedData + is CryptoCurrencyWarning.YieldSupplyNotDepositedAmount -> YieldSupplyNotTransferedToAave( + tokenName = warning.currencySymbol, + amount = warning.amount.format { crypto(symbol = "", decimals = warning.currency.decimals) }, + ) } } diff --git a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyPromoComponent.kt b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyPromoComponent.kt index 0516efb037..0b98f700df 100644 --- a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyPromoComponent.kt +++ b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/YieldSupplyPromoComponent.kt @@ -10,6 +10,7 @@ interface YieldSupplyPromoComponent : ComposableContentComponent { data class Params( val userWalletId: UserWalletId, val currency: CryptoCurrency, + val apy: String, ) interface Factory : ComponentFactory diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/entity/YieldSupplyUM.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/entity/YieldSupplyUM.kt index 30008334c4..112b1dbb24 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/entity/YieldSupplyUM.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/entity/YieldSupplyUM.kt @@ -9,6 +9,7 @@ internal sealed class YieldSupplyUM { data object Initial : YieldSupplyUM() data class Available( + val apy: String, val title: TextReference, val onClick: () -> Unit, ) : YieldSupplyUM() @@ -18,6 +19,7 @@ internal sealed class YieldSupplyUM { data object Unavailable : YieldSupplyUM() data class Content( + val apy: String, val title: TextReference, val subtitle: TextReference, val rewardsApy: TextReference, 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 0895525f5e..8b1e2a09d7 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 @@ -144,10 +144,17 @@ internal class YieldSupplyModel @Inject constructor( } override fun onStartEarningClick() { + val yieldSupplyUM = uiState.value + val apy = when (yieldSupplyUM) { + is YieldSupplyUM.Available -> yieldSupplyUM.apy + is YieldSupplyUM.Content -> yieldSupplyUM.apy + else -> "" + } appRouter.push( YieldSupplyPromo( userWalletId = params.userWalletId, cryptoCurrency = params.cryptoCurrency, + apy = apy, ), ) } @@ -218,6 +225,7 @@ internal class YieldSupplyModel @Inject constructor( ), onClick = ::onActiveClick, isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend, + apy = tokenStatus.apy.toString(), ) } }.onLeft { diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/transformers/YieldSupplyTokenStatusSuccessTransformer.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/transformers/YieldSupplyTokenStatusSuccessTransformer.kt index 4fed5db894..338012ba0e 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/transformers/YieldSupplyTokenStatusSuccessTransformer.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/model/transformers/YieldSupplyTokenStatusSuccessTransformer.kt @@ -21,6 +21,7 @@ internal class YieldSupplyTokenStatusSuccessTransformer( formatArgs = wrappedList(tokenStatus.apy), ), onClick = onStartEarningClick, + apy = tokenStatus.apy.toString(), ) } } \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/ui/YieldSupplyBlockContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/ui/YieldSupplyBlockContent.kt index b596d93729..49ea1ef511 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/ui/YieldSupplyBlockContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/main/ui/YieldSupplyBlockContent.kt @@ -308,6 +308,7 @@ private class PreviewProvider : PreviewParameterProvider { R.string.yield_module_token_details_earn_notification_title, wrappedList("5.1"), ), + apy = "5.1", onClick = {}, ), YieldSupplyUM.Content( @@ -315,6 +316,7 @@ private class PreviewProvider : PreviewParameterProvider { subtitle = stringReference("Interest accrues automatically"), rewardsApy = stringReference("5.1 % APY"), onClick = {}, + apy = "5.1", isAllowedToSpend = false, ), YieldSupplyUM.Content( @@ -322,6 +324,7 @@ private class PreviewProvider : PreviewParameterProvider { subtitle = stringReference("Interest accrues automatically"), rewardsApy = stringReference("5.1 % APY"), onClick = {}, + apy = "5.1", isAllowedToSpend = true, ), YieldSupplyUM.Loading, diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/entity/YieldSupplyPromoUM.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/entity/YieldSupplyPromoUM.kt index 51eefdad41..fca29d33ea 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/entity/YieldSupplyPromoUM.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/entity/YieldSupplyPromoUM.kt @@ -6,4 +6,5 @@ data class YieldSupplyPromoUM( val tosLink: String, val policyLink: String, val title: TextReference, + val subtitle: TextReference, ) \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt index 4a3d0411df..812323df89 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt @@ -33,7 +33,11 @@ internal class YieldSupplyPromoModel @Inject constructor( val uiState: YieldSupplyPromoUM = YieldSupplyPromoUM( tosLink = "https://tangem.com/terms-of-service/", // TODO replace with real link policyLink = "https://tangem.com/privacy-policy/", // TODO replace with real link - title = resourceReference(R.string.yield_module_promo_screen_title, wrappedList("5.3")), + title = resourceReference(R.string.yield_module_promo_screen_title), + subtitle = resourceReference( + R.string.yield_module_promo_screen_variable_rate_info, + wrappedList(params.apy), + ), ) init { diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt index 5839f6fb28..e036e90dd0 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/ui/YieldSupplyPromoContent.kt @@ -73,7 +73,7 @@ internal fun YieldSupplyPromoContent( SpacerH8() Label( state = LabelUM( - text = resourceReference(R.string.yield_module_promo_screen_variable_rate_info), + text = yieldSupplyPromoUM.subtitle, style = LabelStyle.REGULAR, icon = R.drawable.ic_information_24, onIconClick = clickIntents::onApyInfoClick, @@ -223,7 +223,8 @@ private fun YieldSupplyPromoContent_Preview() { yieldSupplyPromoUM = YieldSupplyPromoUM( tosLink = "https://tangem.com/terms-of-service/", policyLink = "https://tangem.com/privacy-policy/", - title = resourceReference(R.string.yield_module_promo_screen_title, wrappedList("5.3")), + title = resourceReference(R.string.yield_module_promo_screen_title), + subtitle = resourceReference(R.string.yield_module_promo_screen_variable_rate_info, wrappedList("5.3")), ), clickIntents = object : YieldSupplyPromoClickIntents { override fun onBackClick() {} From 593f5b364c07f16c84253296c6bb09e7982af70d Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 11:44:23 +0500 Subject: [PATCH 07/25] Updated on 2026-08-14 --- .../tokendetails/state/components/TokenDetailsNotification.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index 97ce580d0d..814e1fbe1b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -6,7 +6,6 @@ import com.tangem.core.ui.components.notifications.NotificationConfig import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet @@ -264,6 +263,6 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { id = R.string.yield_module_amount_not_transfered_to_aave_title, wrappedList(amount, tokenName), ), - subtitle = stringReference(""), + subtitle = TextReference.EMPTY, ) } \ No newline at end of file From cc037ed347541e21a7735ef599d5c2b46f194588 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 14:39:40 +0500 Subject: [PATCH 08/25] Updated on 2026-08-14 --- .../ui/tokens/TokenItemStateConverter.kt | 6 ++---- .../components/token/internal/TokenTitle.kt | 21 +++++++++++++------ .../components/token/state/TokenItemState.kt | 1 + 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index de0d5e70fe..9b7763de26 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -174,10 +174,12 @@ class TokenItemStateConverter( wrappedList(apy), ) } + val isActive = currencyStatus.value.yieldSupplyStatus?.isActive ?: false TokenItemState.TitleState.Content( text = stringReference(currencyStatus.currency.name), hasPending = value.hasCurrentNetworkTransactions, earnApy = earnApyText, + earnApyIsActive = isActive, ) } } @@ -186,10 +188,6 @@ class TokenItemStateConverter( private fun resolveEarnApy(cryptoCurrencyStatus: CryptoCurrencyStatus, apyMap: Map): String? { if (apyMap.isEmpty()) return null - val isYieldSupplyActive = (cryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded) - ?.yieldSupplyStatus?.isActive == true - if (isYieldSupplyActive) return null - val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return null return apyMap[token.yieldSupplyKey()] diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt index 3fadd11e81..685f7696ee 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt @@ -63,6 +63,7 @@ private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Mo YieldSupplyApyLabel( apy = state.earnApy, + isActive = state.earnApyIsActive, modifier = Modifier.align(alignment = Alignment.CenterVertically), ) } @@ -81,18 +82,26 @@ private fun CurrencyNameText(name: String, isAvailable: Boolean, modifier: Modif } @Composable -private fun YieldSupplyApyLabel(apy: TextReference?, modifier: Modifier = Modifier) { +private fun YieldSupplyApyLabel(apy: TextReference?, isActive: Boolean, modifier: Modifier = Modifier) { AnimatedVisibility(visible = apy != null, modifier = modifier) { Box( - modifier = modifier.background( - color = TangemTheme.colors.text.accent.copy(alpha = 0.1f), - shape = TangemTheme.shapes.roundedCornersSmall2, - ), + modifier = if (isActive) { + modifier.background( + color = TangemTheme.colors.text.accent.copy(alpha = 0.1f), + shape = TangemTheme.shapes.roundedCornersSmall2, + ) + } else { + modifier + }, ) { Text( text = apy?.resolveReference().orEmpty(), style = TangemTheme.typography.caption1, - color = TangemTheme.colors.text.accent, + color = if (isActive) { + TangemTheme.colors.text.accent + } else { + TangemTheme.colors.text.tertiary + }, modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), ) } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt index f86a303999..88fc97f796 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/state/TokenItemState.kt @@ -169,6 +169,7 @@ sealed class TokenItemState { val hasPending: Boolean = false, val isAvailable: Boolean = true, val earnApy: TextReference? = null, + val earnApyIsActive: Boolean = false, ) : TitleState() data object Loading : TitleState() From 56b509927ccd053ea1d76a0d7b9fa82af06e0c9e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 14:26:57 +0500 Subject: [PATCH 09/25] Updated on 2026-08-14 --- .../exchangeServices/DefaultRampManager.kt | 4 ++++ .../common/ui/tokens/TokenActionsUtils.kt | 3 +++ core/res/src/main/res/values/strings.xml | 9 +++++++++ .../model/ScenarioUnavailabilityReason.kt | 2 ++ .../analytics/TokenScreenAnalyticsEvent.kt | 1 + .../tokens/actions/CommonActionsFactory.kt | 19 +++++++++++-------- .../tokenlist/model/OnrampTokenListModel.kt | 5 ++++- .../active/model/YieldSupplyActiveModel.kt | 2 +- 8 files changed, 35 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt index 5cf37e3a1c..6a0cca84f4 100644 --- a/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt +++ b/app/src/main/java/com/tangem/tap/network/exchangeServices/DefaultRampManager.kt @@ -114,6 +114,7 @@ internal class DefaultRampManager( userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus, ): ScenarioUnavailabilityReason { + val yieldSupplyStatus = cryptoCurrencyStatus.value.yieldSupplyStatus return when { cryptoCurrencyStatus.value.amount.isNullOrZero() -> { ScenarioUnavailabilityReason.EmptyBalance(ScenarioUnavailabilityReason.WithdrawalScenario.SEND) @@ -127,6 +128,9 @@ internal class DefaultRampManager( networkName = cryptoCurrencyStatus.currency.network.name, ) } + yieldSupplyStatus?.isAllowedToSpend == false && yieldSupplyStatus.isActive -> { + ScenarioUnavailabilityReason.YieldSupplyApprovalRequired + } else -> ScenarioUnavailabilityReason.None } } diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt index 374cbde833..49a9714fa8 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenActionsUtils.kt @@ -68,6 +68,9 @@ fun ScenarioUnavailabilityReason.getUnavailabilityReasonText(): TextReference { -> { resourceReference(id = R.string.token_button_unavailability_reason_loading) } + ScenarioUnavailabilityReason.YieldSupplyApprovalRequired -> resourceReference( + R.string.token_button_unavailability_reason_yield_supply_approval, + ) ScenarioUnavailabilityReason.None -> { throw IllegalArgumentException("The unavailability reason must be other than None") } diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index f6e86e035a..4055e42ef5 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -1305,10 +1305,19 @@ Choose token not available Deposit + Dispute + Explore transaction Service fees Fee + Completed + Declined + Pending + The bank rejected this transaction request. + This fee goes to cover the cost of handling your transfer. Withdrawal + Change PIN Failed to load data. Try again later. + Freeze Card Hide Technical issues detected. Please try again later or contact support. Receive unavailable now diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt index bf816df605..64053517d7 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/ScenarioUnavailabilityReason.kt @@ -56,6 +56,8 @@ sealed class ScenarioUnavailabilityReason { data object TrustlineRequired : ScenarioUnavailabilityReason() + data object YieldSupplyApprovalRequired : ScenarioUnavailabilityReason() + enum class WithdrawalScenario { SELL, SEND // TODO staking create&process STAKING } diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt index b33b3562ed..3365ce6d87 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt @@ -192,6 +192,7 @@ sealed class TokenScreenAnalyticsEvent( is ScenarioUnavailabilityReason.NotExchangeable, is ScenarioUnavailabilityReason.NotSupportedBySellService, is ScenarioUnavailabilityReason.StakingUnavailable, + ScenarioUnavailabilityReason.YieldSupplyApprovalRequired, -> UNAVAILABLE ScenarioUnavailabilityReason.UnassociatedAsset -> ASSET_REQUIREMENT ScenarioUnavailabilityReason.TrustlineRequired -> TRUSTLINE_REQUIREMENT diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt index 180041ff13..bef54bd155 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/actions/CommonActionsFactory.kt @@ -1,7 +1,6 @@ package com.tangem.domain.tokens.actions import com.tangem.domain.exchange.RampStateManager -import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId @@ -69,7 +68,7 @@ internal class CommonActionsFactory( async { getSwapUnavailabilityReason( userWalletId = userWallet.walletId, - currency = cryptoCurrencyStatus.currency, + currencyStatus = cryptoCurrencyStatus, requirementsDeferred = requirementsDeferred, ) } @@ -175,17 +174,21 @@ internal class CommonActionsFactory( private suspend fun getSwapUnavailabilityReason( userWalletId: UserWalletId, - currency: CryptoCurrency, + currencyStatus: CryptoCurrencyStatus, requirementsDeferred: Deferred?, ): ScenarioUnavailabilityReason { val swapUnavailabilityReason = rampStateManager - .availableForSwap(userWalletId = userWalletId, cryptoCurrency = currency) + .availableForSwap(userWalletId = userWalletId, cryptoCurrency = currencyStatus.currency) val shouldCheckAssetRequirements = swapUnavailabilityReason == ScenarioUnavailabilityReason.None && requirementsDeferred != null - return if (shouldCheckAssetRequirements) { - getReceiveScenario(requirementsDeferred.await()) - } else { - swapUnavailabilityReason + + val yieldSupplyStatus = currencyStatus.value.yieldSupplyStatus + val isUnavailableByYieldSupply = yieldSupplyStatus?.isAllowedToSpend == false && yieldSupplyStatus.isActive + + return when { + isUnavailableByYieldSupply -> ScenarioUnavailabilityReason.YieldSupplyApprovalRequired + shouldCheckAssetRequirements -> getReceiveScenario(requirementsDeferred.await()) + else -> swapUnavailabilityReason } } } \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt index a61e5837a8..c8f65cda5a 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/tokenlist/model/OnrampTokenListModel.kt @@ -402,7 +402,10 @@ internal class OnrampTokenListModel @Inject constructor( cryptoCurrency = status.currency, ).isAvailable() && !status.currency.isCustom - isAvailable && status.value !is CryptoCurrencyStatus.NoQuote + val supplyStatus = status.value.yieldSupplyStatus + val isUnavailableByYieldSupply = supplyStatus?.isAllowedToSpend == false && supplyStatus.isActive + + isAvailable && status.value !is CryptoCurrencyStatus.NoQuote && !isUnavailableByYieldSupply } } } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt index f54b17d1f9..2b63d7405c 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt @@ -19,8 +19,8 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.yield.supply.usecase.YieldSupplyGetProtocolBalanceUseCase 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.api.analytics.YieldSupplyAnalytics +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.entity.YieldSupplyActiveContentUM From 6afae8e619dbd444fd6469a80030730b2cd61820 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 14:27:13 +0500 Subject: [PATCH 10/25] Updated on 2026-08-14 --- core/res/src/main/res/values/strings.xml | 2 ++ .../feature/swap/domain/models/ExpressDataError.kt | 5 +++++ .../tangem/feature/swap/domain/SwapInteractorImpl.kt | 8 ++++++++ .../java/com/tangem/feature/swap/model/SwapModel.kt | 11 +---------- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 4055e42ef5..2158343988 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -538,6 +538,7 @@ Complete setup by securing the app with an access code. If you do, you\'ll need to start over. Are you sure you want to quit the activation process? + Keeps your crypto safe and offline. Slim as a credit card, safer than a bank vault. If you do, you\'ll need to start over. Recover existing wallet via Google Drive backup Google Drive backup @@ -547,6 +548,7 @@ Please back up your wallet before creating an access code. Finalize backup first Incomplete + Other methods Physical devices that securely store your private key offline. Recovery phrase Your private keys are securely encrypted and stored on your phone diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ExpressDataError.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ExpressDataError.kt index 568214f788..1f13d0910e 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ExpressDataError.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/ExpressDataError.kt @@ -64,4 +64,9 @@ sealed class ExpressDataError { override val code: Int = -2 override val message: String = "tooLargeSolanaTransaction" } + + data object DexActiveSupplyError : ExpressDataError() { + override val code: Int = -3 + override val message: String = "dexActiveSupplyError" + } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 7e47fff730..bbebf6f219 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -334,6 +334,14 @@ internal class SwapInteractorImpl @AssistedInject constructor( isBalanceWithoutFeeEnough: Boolean, expressOperationType: ExpressOperationType, ): Pair { + if (fromToken.value.yieldSupplyStatus?.isActive == true) { + return provider to produceDexSwapDataError( + error = ExpressDataError.DexActiveSupplyError, + fromToken = fromToken, + amount = amount, + ) + } + val maybeQuotes = repository.findBestQuote( userWallet = userWallet, fromContractAddress = fromToken.currency.getContractAddress(), diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index cf53aeb958..a8bb23deaf 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -1267,16 +1267,7 @@ internal class SwapModel @Inject constructor( toToken.currency.id.value } - return groupToFind.available.find { idToFind == it.currencyStatus.currency.id.value }?.providers - ?.filter { provider -> - // !!!WARNING!!! Filter out dex provider if yield supply is active - val yieldSupplyStatus = fromToken.value.yieldSupplyStatus - if (yieldSupplyStatus != null && yieldSupplyStatus.isActive) { - provider.type == ExchangeProviderType.CEX - } else { - true - } - }.orEmpty() + return groupToFind.available.find { idToFind == it.currencyStatus.currency.id.value }?.providers.orEmpty() } private fun Map.getLastLoadedSuccessStates(): SuccessLoadedSwapData { From 63125dc0f3c082943629a3c101ec239a121ecfef Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 16:01:41 +0500 Subject: [PATCH 11/25] Updated on 2026-08-14 --- .../DefaultCurrencyChecksRepository.kt | 18 +++++---- .../tokens/GetCurrencyWarningsUseCase.kt | 2 +- .../repository/CurrencyChecksRepository.kt | 2 +- ...okenDetailsNotificationsAnalyticsSender.kt | 2 +- .../api/analytics/YieldSupplyAnalytics.kt | 37 ++++++++++++------- .../approve/model/YieldSupplyApproveModel.kt | 5 +++ .../model/YieldSupplyStopEarningModel.kt | 11 ++++++ 7 files changed, 54 insertions(+), 23 deletions(-) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index 362d59b4a9..3583ff7332 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -165,19 +165,23 @@ internal class DefaultCurrencyChecksRepository( } } - override suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal? { - require(cryptoCurrency is CryptoCurrency.Token) + override suspend fun getProtocolBalance( + userWalletId: UserWalletId, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): BigDecimal? { + val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return null + if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == false) return null return runCatching { val walletManager = walletManagersFacade.getOrCreateWalletManager( userWalletId = userWalletId, - blockchain = cryptoCurrency.network.toBlockchain(), - derivationPath = cryptoCurrency.network.derivationPath.value, + blockchain = token.network.toBlockchain(), + derivationPath = token.network.derivationPath.value, ) ?: error("Wallet manager not found") walletManager.getProtocolBalance( token = Token( - symbol = cryptoCurrency.symbol, - contractAddress = cryptoCurrency.contractAddress, - decimals = cryptoCurrency.decimals, + symbol = token.symbol, + contractAddress = token.contractAddress, + decimals = token.decimals, ), ) }.onFailure(Timber::e).getOrThrow() diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index 3afc61c48b..d491aff2a6 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -51,7 +51,7 @@ class GetCurrencyWarningsUseCase( flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus)), flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)), flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)), - flowOf(currencyChecksRepository.getProtocolBalance(userWalletId, currency)), + flowOf(currencyChecksRepository.getProtocolBalance(userWalletId, currencyStatus)), ) { coinRelatedWarnings, maybeRentWarning, maybeEdWarning, maybeFeeResource, yieldSupplyProtocolBalance -> setOfNotNull( maybeRentWarning, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt index 46307255e9..d1b4b99d81 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrencyChecksRepository.kt @@ -67,5 +67,5 @@ interface CurrencyChecksRepository { * This represents the amount supplied to the protocol for the specified `userWalletId` * (e.g., aTokens balance). Returns null if not applicable or unknown. */ - suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal? + suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus): BigDecimal? } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index 3eba98c4e1..ad3fcd0c4e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -45,7 +45,7 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.KaspaIncompleteTransactionWarning -> TokenDetailsAnalyticsEvent.Notice.Reveal( currency = cryptoCurrency, ) - is TokenDetailsNotification.YieldSupplyNotTransferedToAave -> YieldSupplyAnalytics.NoticeNotEnoughMinAmount( + is TokenDetailsNotification.YieldSupplyNotTransferedToAave -> YieldSupplyAnalytics.NoticeAmountNotDeposited( token = cryptoCurrency.symbol, blockchain = cryptoCurrency.network.name, ) diff --git a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt index faddabab9e..596a7bb892 100644 --- a/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt +++ b/features/yield-supply/api/src/main/java/com/tangem/features/yield/supply/api/analytics/YieldSupplyAnalytics.kt @@ -33,6 +33,17 @@ sealed class YieldSupplyAnalytics( ), ) + data class StopEarningScreen( + val token: String, + val blockchain: String, + ) : YieldSupplyAnalytics( + event = "Stop Earning Screen", + params = mapOf( + TOKEN_PARAM to token, + BLOCKCHAIN to blockchain, + ), + ) + data class ButtonStartEarning( val token: String, val blockchain: String, @@ -55,6 +66,17 @@ sealed class YieldSupplyAnalytics( ), ) + data class ButtonGiveApprove( + val token: String, + val blockchain: String, + ) : YieldSupplyAnalytics( + event = " Button - Give Approve", + params = mapOf( + TOKEN_PARAM to token, + BLOCKCHAIN to blockchain, + ), + ) + data object ButtonFeePolicy : YieldSupplyAnalytics( event = "Button - Fee Policy", ) @@ -150,22 +172,11 @@ sealed class YieldSupplyAnalytics( event = "APY Chart", ) - data class NoticeCommissionTooHigh( + data class NoticeAmountNotDeposited( val token: String, val blockchain: String, ) : YieldSupplyAnalytics( - event = "Notice - Commission Is Too High", - params = mapOf( - TOKEN_PARAM to token, - BLOCKCHAIN to blockchain, - ), - ) - - data class NoticeNotEnoughMinAmount( - val token: String, - val blockchain: String, - ) : YieldSupplyAnalytics( - event = "Notice - Not Enough Min Amount", + event = "Notice - Amount Not Deposited", params = mapOf( TOKEN_PARAM to token, BLOCKCHAIN to blockchain, diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt index 5a7ac6bd0a..66f9d0f195 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/approve/model/YieldSupplyApproveModel.kt @@ -119,6 +119,11 @@ internal class YieldSupplyApproveModel @Inject constructor( val yieldSupplyFeeUM = uiState.value.yieldSupplyFeeUM as? YieldSupplyFeeUM.Content ?: return uiState.update(YieldSupplyTransactionInProgressTransformer) + analyticsEventHandler.send(YieldSupplyAnalytics.ButtonGiveApprove( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + )) + modelScope.launch(dispatchers.default) { sendTransactionUseCase( txData = yieldSupplyFeeUM.transactionDataList.first(), diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt index a6f111df3a..9e704f9967 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt @@ -97,6 +97,13 @@ internal class YieldSupplyStopEarningModel @Inject constructor( ) init { + val currency = params.cryptoCurrencyStatusFlow.value.currency + analytics.send( + YieldSupplyAnalytics.StopEarningScreen( + token = currency.symbol, + blockchain = currency.network.name, + ), + ) modelScope.launch { appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default } subscribeOnCurrencyStatusUpdates() @@ -133,6 +140,10 @@ internal class YieldSupplyStopEarningModel @Inject constructor( ifLeft = { error -> Timber.e(error.toString()) uiState.update(YieldSupplyTransactionReadyTransformer) + analytics.send(YieldSupplyAnalytics.EarnErrors( + action = YieldSupplyAnalytics.Action.Stop, + errorDescription = error.getAnalyticsDescription(), + )) yieldSupplyAlertFactory.getSendTransactionErrorState( error = error, popBack = params.callback::onBackClick, From 70294eef52725a2ddb306db5c6c600f007fe105c Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 17:14:28 +0300 Subject: [PATCH 12/25] Updated on 2026-08-14 --- .../datasource/api/stakekit/StakeKitApi.kt | 1 + .../local/token/DefaultStakingYieldsStore.kt | 15 +++++- .../data/staking/DefaultStakingRepository.kt | 47 +++++++++++++++---- .../staking/model/StakingIntegrationID.kt | 13 +++++ tangem-android-tools | 2 +- 5 files changed, 65 insertions(+), 13 deletions(-) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt index d6519755c5..b6304ee6a3 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/stakekit/StakeKitApi.kt @@ -19,6 +19,7 @@ interface StakeKitApi { @Query("preferredValidatorsOnly") preferredValidatorsOnly: Boolean? = null, @Query("ledgerWalletAPICompatible") ledgerWalletAPICompatible: Boolean? = null, @Query("type") type: YieldType? = null, + @Query("yieldId") yieldId: String? = null, @Query("revenueOption") revenueOption: RevenueOption? = null, @Query("page") page: Int? = null, @Query("network") network: String? = null, diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt index 73800ea6dd..cdac7d46bf 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/token/DefaultStakingYieldsStore.kt @@ -24,8 +24,19 @@ internal class DefaultStakingYieldsStore( } override suspend fun store(items: List) { - dataStore.updateData { _ -> - items + dataStore.updateData { data -> + val updatedItems = data.toMutableList() + items.forEach { newItem -> + val existingItemIndex = data.indexOfFirst { it.id == newItem.id } + if (existingItemIndex != -1) { + // Update existing item + updatedItems[existingItemIndex] = newItem + } else { + // Add new item + updatedItems.add(newItem) + } + } + updatedItems } } diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index 5230a94938..e279943212 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -24,6 +24,7 @@ import com.tangem.datasource.api.common.response.ApiResponse import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.stakekit.StakeKitApi import com.tangem.datasource.api.stakekit.models.request.* +import com.tangem.datasource.api.stakekit.models.response.EnabledYieldsResponse import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction @@ -56,6 +57,8 @@ import com.tangem.lib.crypto.BlockchainUtils.isCardano import com.tangem.lib.crypto.BlockchainUtils.isSolana import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.* import kotlinx.coroutines.withContext import timber.log.Timber @@ -91,17 +94,20 @@ internal class DefaultStakingRepository( override suspend fun fetchEnabledYields() { withContext(dispatchers.io) { - when (val stakingTokensWithYields = stakeKitApi.getEnabledYields(preferredValidatorsOnly = false)) { - is ApiResponse.Success -> stakingYieldsStore.store( - stakingTokensWithYields.data.data.filter { - it.isAvailable == true - }, - ) - else -> { - stakingYieldsStore.store(emptyList()) - throw (stakingTokensWithYields as ApiResponse.Error).cause + val yieldsResponses = getAvailableIntegrationsIds().map { + async { it.getYieldRequest() } + }.awaitAll() + + val yields = yieldsResponses.flatMap { response -> + when (response) { + is ApiResponse.Success -> response.data.data.filter { yield -> yield.isAvailable == true } + else -> { + Timber.e("Error fetching enabled yields: ${(response as? ApiResponse.Error)?.cause}") + emptyList() + } } } + stakingYieldsStore.store(yields) } } @@ -151,6 +157,26 @@ internal class DefaultStakingRepository( } } + private suspend fun StakingIntegrationID.getYieldRequest(): ApiResponse { + return when (this) { + is StakingIntegrationID.Coin -> stakeKitApi.getEnabledYields( + preferredValidatorsOnly = false, + network = networkId, + ) + is StakingIntegrationID.EthereumToken -> stakeKitApi.getEnabledYields( + preferredValidatorsOnly = false, + yieldId = value, + network = networkId, + ) + } + } + + private fun getAvailableIntegrationsIds(): List { + return StakingIntegrationID.entries.filterNot { + it.blockchain == Blockchain.Cardano && !stakingFeatureToggles.isCardanoStakingEnabled + } + } + private fun NetworkTypeDTO.extractJsonName(): String { return networkTypeAdapter.toJson(this).replace("\"", "") } @@ -364,7 +390,8 @@ internal class DefaultStakingRepository( ) val transaction = transactionConverter.convert(transactionResponse.getOrThrow()) - val unsignedTransaction = transaction.unsignedTransaction ?: error("No unsigned transaction available") + val unsignedTransaction = + transaction.unsignedTransaction ?: error("No unsigned transaction available") val transactionData = TransactionData.Compiled( value = getTransactionDataType(networkId, unsignedTransaction), fee = fee, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt index f1514e69a5..ff38b75b71 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/model/StakingIntegrationID.kt @@ -23,31 +23,43 @@ sealed interface StakingIntegrationID { /** Approval requirements for the staking integration. Defaults to no approval needed */ val approval: StakingApproval get() = StakingApproval.Empty + /** + * Represents the network ID associated with the staking integration from provider + * https://docs.yield.xyz/reference/yieldscontroller_getyields + */ + val networkId: String + /** Represents blockchains whose native coins can be staked */ enum class Coin : StakingIntegrationID { Ton { override val value: String = "ton-ton-chorus-one-pools-staking" override val blockchain: Blockchain = Blockchain.TON + override val networkId: String = "ton" }, Solana { override val value: String = "solana-sol-native-multivalidator-staking" override val blockchain: Blockchain = Blockchain.Solana + override val networkId: String = "solana" }, Cosmos { override val value: String = "cosmos-atom-native-staking" override val blockchain: Blockchain = Blockchain.Cosmos + override val networkId: String = "cosmos" }, Tron { override val value: String = "tron-trx-native-staking" override val blockchain: Blockchain = Blockchain.Tron + override val networkId: String = "tron" }, BSC { override val value: String = "bsc-bnb-native-staking" override val blockchain: Blockchain = Blockchain.BSC + override val networkId: String = "binance" }, Cardano { override val value: String = "cardano-ada-native-staking" override val blockchain: Blockchain = Blockchain.Cardano + override val networkId: String = "cardano" }, } @@ -61,6 +73,7 @@ sealed interface StakingIntegrationID { override val value: String = "ethereum-matic-native-staking" override val approval: StakingApproval.Needed = StakingApproval.Needed(spenderAddress = "0x5e3Ef299fDDf15eAa0432E6e66473ace8c13D908") + override val networkId: String = "ethereum" }, ; diff --git a/tangem-android-tools b/tangem-android-tools index baaed0af71..b64f8659fc 160000 --- a/tangem-android-tools +++ b/tangem-android-tools @@ -1 +1 @@ -Subproject commit baaed0af71b07b226796a023bf4ac2ea410918e2 +Subproject commit b64f8659fce65e7749d50923b32bfad13e6b8cac From dbd39309576291555dc18cdfdf61a29614e74188 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 17:16:48 +0300 Subject: [PATCH 13/25] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a8cf062a6b..a15190759d 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a8cf062a6bb58332458c0f5b43026e062378e5c7 +Subproject commit a15190759db4ee45691f099c0924f02e1a1c7fd4 From 92c95095cc9d95c37435cff059b508af027e02d2 Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 17:19:36 +0300 Subject: [PATCH 14/25] Updated on 2026-08-14 --- app/src/main/assets/tangem-app-config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index a15190759d..4272136431 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit a15190759db4ee45691f099c0924f02e1a1c7fd4 +Subproject commit 4272136431c3629230803e70c4d2cf412365418d From fbfd7476fafc68b0b3ac1312b4be4d026850555f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Oct 2025 13:01:49 +0500 Subject: [PATCH 15/25] Updated on 2026-08-14 --- .../tap/di/domain/YieldSupplyDomainModule.kt | 28 ++++++ .../api/common/config/YieldSupply.kt | 24 +++-- .../config/environment/EnvironmentConfig.kt | 1 + .../converter/EnvironmentConfigConverter.kt | 1 + .../models/EnvironmentConfigModel.kt | 1 + .../managers/MockEnvironmentConfigStorage.kt | 2 + core/res/src/main/res/values-ja/strings.xml | 33 ++++++- core/res/src/main/res/values/strings.xml | 8 +- .../domain/yield/supply/FeeExtensions.kt | 23 +++++ .../domain/yield/supply/YieldSupplyConst.kt | 5 ++ .../YieldSupplyEstimateEnterFeeUseCase.kt | 21 +---- .../YieldSupplyGetCurrentFeeUseCase.kt | 65 ++++++++++++++ .../usecase/YieldSupplyGetMaxFeeUseCase.kt | 67 ++++++++++++++ .../usecase/YieldSupplyMinAmountUseCase.kt | 29 ++---- ...atter.kt => YieldSupplyAmountFormatter.kt} | 24 +++-- .../impl/common/ui/YieldSupplyFeeRow.kt | 2 + .../entity/YieldSupplyActiveContentUM.kt | 5 +- .../active/model/YieldSupplyActiveModel.kt | 64 ++++++++++++- .../active/ui/YieldSupplyActiveContent.kt | 89 ++++++++++++++++++- ...SupplyStartEarningFeeContentTransformer.kt | 4 +- 20 files changed, 430 insertions(+), 66 deletions(-) create mode 100644 domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/FeeExtensions.kt create mode 100644 domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyConst.kt create mode 100644 domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetCurrentFeeUseCase.kt create mode 100644 domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetMaxFeeUseCase.kt rename features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/formatter/{YieldSupplyMinAmountFormatter.kt => YieldSupplyAmountFormatter.kt} (53%) diff --git a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt index de38050dcf..ea0ad78e44 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/YieldSupplyDomainModule.kt @@ -148,4 +148,32 @@ internal object YieldSupplyDomainModule { currenciesRepository = currenciesRepository, ) } + + @Provides + @Singleton + fun provideYieldSupplyGetCurrentFeeUseCase( + feeRepository: FeeRepository, + quotesRepository: QuotesRepository, + currenciesRepository: CurrenciesRepository, + ): YieldSupplyGetCurrentFeeUseCase { + return YieldSupplyGetCurrentFeeUseCase( + feeRepository = feeRepository, + quotesRepository = quotesRepository, + currenciesRepository = currenciesRepository, + ) + } + + @Provides + @Singleton + fun provideYieldSupplyGetMaxFeeUseCase( + yieldSupplyRepository: YieldSupplyRepository, + quotesRepository: QuotesRepository, + currenciesRepository: CurrenciesRepository, + ): YieldSupplyGetMaxFeeUseCase { + return YieldSupplyGetMaxFeeUseCase( + yieldSupplyRepository = yieldSupplyRepository, + quotesRepository = quotesRepository, + currenciesRepository = currenciesRepository, + ) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/YieldSupply.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/YieldSupply.kt index 0665ec199b..6b7d1ab493 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/common/config/YieldSupply.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/config/YieldSupply.kt @@ -41,32 +41,44 @@ internal class YieldSupply( private fun createDevEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.DEV, baseUrl = "[REDACTED_ENV_URL]", - headers = createHeaders(), + headers = createHeaders(ApiEnvironment.DEV), ) private fun createStageEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.STAGE, baseUrl = "[REDACTED_ENV_URL]", - headers = createHeaders(), + headers = createHeaders(ApiEnvironment.STAGE), ) private fun createMockedEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.MOCK, baseUrl = "[REDACTED_ENV_URL]", - headers = createHeaders(), + headers = createHeaders(ApiEnvironment.MOCK), ) private fun createProdEnvironment(): ApiEnvironmentConfig = ApiEnvironmentConfig( environment = ApiEnvironment.PROD, baseUrl = "https://yield.tangem.org/", - headers = createHeaders(), + headers = createHeaders(ApiEnvironment.PROD), ) - private fun createHeaders() = buildMap { + private fun createHeaders(apiEnvironment: ApiEnvironment) = buildMap { put(key = "api-key", value = ProviderSuspend { - environmentConfigStorage.getConfigSync().yieldModuleApiKey.orEmpty() + getApiKey(apiEnvironment) }) putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values) putAll(from = RequestHeader.AuthenticationHeader(authProvider).values) } + + private fun getApiKey(apiEnvironment: ApiEnvironment): String { + return when (apiEnvironment) { + ApiEnvironment.MOCK, + ApiEnvironment.DEV, + ApiEnvironment.DEV_2, + ApiEnvironment.DEV_3, + ApiEnvironment.STAGE, + -> environmentConfigStorage.getConfigSync().yieldModuleApiKeyDev + ApiEnvironment.PROD -> environmentConfigStorage.getConfigSync().yieldModuleApiKey + } ?: error("No tangem tech api config provided") + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/EnvironmentConfig.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/EnvironmentConfig.kt index 384af56181..252782f20a 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/EnvironmentConfig.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/EnvironmentConfig.kt @@ -21,4 +21,5 @@ data class EnvironmentConfig( val tangemApiKeyDev: String? = null, val tangemApiKeyStage: String? = null, val yieldModuleApiKey: String? = null, + val yieldModuleApiKeyDev: String? = null, ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt index cf52beb064..5c43a7c952 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/config/environment/converter/EnvironmentConfigConverter.kt @@ -30,6 +30,7 @@ internal object EnvironmentConfigConverter : Converterすでにアクティブアカウントの上限(20件)に達しています。復元するには、1つをアーカイブしてください。 アカウントを復元できません アーカイブ済み + アカウントをアーカイブできませんでした。しばらくしてからもう一度お試しください。 アカウントを作成できませんでした。しばらくしてからもう一度お試しください。 アカウントを作成しました アカウントをアーカイブする @@ -249,10 +250,12 @@ 遅い 速度と料金 終了 + 忘れる 無料 送信元 アドレスを同期する はじめる + トークンを取得 プロバイダーへ移動 トークンへ移動 わかりました @@ -526,14 +529,20 @@ アクセスコードでアプリを保護して、設定を完了してください。 そうした場合は、最初からやり直す必要があります。 本当にアクティベーション処理を終了してもよろしいですか? + 暗号資産をオフラインで厳重保管。カードサイズで、金庫以上の安心を。 実行すると、最初からやり直す必要があります。 Googleドライブのバックアップから既存のウォレットを復元する Googleドライブのバックアップ + さらに強固なセキュリティのために、新しいウォレットを作成して資産を移動しましょう。 + 新しいウォレットを作成 Tangemの高性能ハードウェアウォレットで、セキュリティをさらに強化しましょう。 ハードウェアウォレット + 現在のウォレットをTangemウォレットに移します。 + 現在のウォレットをアップグレードする バックアップへ移動 アクセスコードを作成する前にウォレットをバックアップしてください。 まずバックアップを完了する + その他の方法 秘密鍵をオフラインで安全に保存する物理デバイス。 リカバリーフレーズ 鍵はアプリに保存されます @@ -603,6 +612,7 @@ 選択したトークンは現在、暗号資産ウォレット内でのアクションには利用できません。しかし、心配しないでください。賛成票を投じることで関心を表明できます。 賛成票を投じる ウォレットは複数のネットワークをサポートしていません。 + コインについて このアセットを購入・交換・受け取るには、ポートフォリオに追加してください。 このアセットは現在ウォレットで利用できません このアセットはこのウォレットでは使用できません。 @@ -638,6 +648,7 @@ トレンド ステーキングは暗号資産で報酬を受け取る最も簡単な方法です。 %s 最大%s APYを獲得 + トークンを追加しました %sについて %d取引所 @@ -895,7 +906,7 @@ 最大%d日 %s分 - 下記より利用可能 + 利用可能: 以下が手に入ります。 サービスは外部プロバイダーによって提供されます。 \nTangemは責任を負いません。 この画面を閉じて、トークンの詳細画面で取引状況を確認できます。 @@ -1275,7 +1286,20 @@ 受け取る トークンを選択 利用不可 + 入金 + 異議申し立て + 取引を表示 + サービス手数料 + 手数料 + 完了 + 拒否 + 保留中 + 銀行がこの取引リクエストを拒否しました。 + この手数料は、送金処理にかかるコストをカバーするためのものです。 + 出金 + PINを変更する データの読み込みに失敗しました。しばらくしてからもう一度お試しください。 + カードの一時停止 非表示 技術的な問題が検出されました。しばらくしてからもう一度お試しいただくか、サポートにお問い合わせください。 現在、受け取りは利用できません @@ -1310,6 +1334,7 @@ ネットワーク%s内の保留中の取引が完了すると、送金が可能になります。 %sの売却は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 %sのステーキングは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 + ここにテキストを入力 XPUBを生成する 非表示 このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。 @@ -1729,10 +1754,10 @@ 今後の%sの入金はすべて、取引手数料が差し引かれて自動的にAaveに供給されます。 ネットワーク手数料が上限手数料を超えた場合、手数料が下がるまで取引は成立しません。この制限は後で変更できます。 最大手数料 - 取引手数料は、預入額の4%未満である必要があります。Tangemは、この条件を満たす十分な残高が貯まった時点で、Aaveへの資金移動を行います。 + 取引手数料は入金額の4%未満である必要があります。残高がこの条件を満たすのに十分な金額になった場合にのみ、TangemはAaveに資金を送ります。 最低入金額 手数料ポリシー - Tangemはまた、得られた利回りに対して3%のサービス手数料を差し引きます。 + Tangemはまた、得られた利回りに対して3%のサービス手数料を差し引きます。 ネットワーク手数料が現在高すぎます。設定した上限を下回るまで待機しています。 過去のリターン ここに説明を入力してください。1〜3行が理想的です。[プレースホルダー] @@ -1748,7 +1773,7 @@ 分散型・自己管理型 サービスを利用することにより、プロバイダー\n %1$sおよび%2$sに同意したことになります 年間%s%%の収益 - Aave • 変動金利 + Aave %1$s%% • 変動金利 Aave 平均%s 昨年のリターン diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 2158343988..48aa70401e 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -542,8 +542,12 @@ If you do, you\'ll need to start over. Recover existing wallet via Google Drive backup Google Drive backup + Create a new secure wallet and transfer your funds for extra protection. + Create new wallet Level up your security with the superior Tangem hardware wallet. Hardware Wallet + Move your current wallet into Tangem Wallet. + Upgrade current wallet Go to backup Please back up your wallet before creating an access code. Finalize backup first @@ -1813,6 +1817,8 @@ APY %1$s%% Available Current APY + When topping up for lending, a network fee will be deducted from the amount — never more than %1$s + The network fee is currently too high to execute lending. Funds will be supplied once it drops to %1$s or below. My funds Your %1$s is now deposited in Aave and earning interest. You hold a%2$s token, which represents your balance and grows over time. When you top up, funds go to Aave to earn interest, minus a transaction fee. Earn @@ -1824,7 +1830,7 @@ All future %s top-ups will be supplied to Aave automatically, with the transaction fee deducted. If network fees rise above maximum fee, the transaction won’t go through until they decrease. You can change this limit later. Maximum fee - The transaction fee must stay below 4% of your deposit. Tangem will transfer funds to Aave only once your balance is large enough to meet this condition. + The transaction fee must stay below 4% of your deposit. Tangem will transfer funds to Aave only once your balance is large enough to meet this condition. Minimal top-up Fee policy Tangem also takes a 3% service fee on the yield earned. diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/FeeExtensions.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/FeeExtensions.kt new file mode 100644 index 0000000000..4032d39149 --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/FeeExtensions.kt @@ -0,0 +1,23 @@ +package com.tangem.domain.yield.supply + +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.domain.models.currency.CryptoCurrency +import java.math.BigInteger + +fun Fee.fixFee(cryptoCurrency: CryptoCurrency, gasLimit: BigInteger): Fee = 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 +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyConst.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyConst.kt new file mode 100644 index 0000000000..f4ae8729f3 --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyConst.kt @@ -0,0 +1,5 @@ +package com.tangem.domain.yield.supply + +object YieldSupplyConst { + val YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT = 350_000.toBigInteger() +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEstimateEnterFeeUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEstimateEnterFeeUseCase.kt index 273114f51c..58f1a8476f 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEstimateEnterFeeUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyEstimateEnterFeeUseCase.kt @@ -3,17 +3,16 @@ package com.tangem.domain.yield.supply.usecase import arrow.core.Either import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.common.TransactionData -import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData import com.tangem.domain.blockaid.BlockAidGasEstimate import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.yield.supply.fixFee import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.transaction.FeeRepository import com.tangem.domain.transaction.error.FeeErrorResolver import com.tangem.domain.transaction.error.GetFeeError import com.tangem.utils.extensions.isSingleItem import timber.log.Timber -import java.math.BigInteger class YieldSupplyEstimateEnterFeeUseCase( private val feeRepository: FeeRepository, @@ -107,24 +106,6 @@ class YieldSupplyEstimateEnterFeeUseCase( return withCalculatedFees + withEstimatedFees } - 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 { // Using constant gas limit to avoid fee calculation errors when contract address is not deployed yet val ETHEREUM_CONSTANT_GAS_LIMIT = 500_000.toBigInteger() diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetCurrentFeeUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetCurrentFeeUseCase.kt new file mode 100644 index 0000000000..c9ee2d6a93 --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetCurrentFeeUseCase.kt @@ -0,0 +1,65 @@ +package com.tangem.domain.yield.supply.usecase + +import arrow.core.Either +import arrow.core.Either.Companion.catch +import com.tangem.domain.yield.supply.fixFee +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 com.tangem.domain.yield.supply.YieldSupplyConst.YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT +import java.math.BigDecimal +import java.math.RoundingMode + +/** + * Calculates current fee for Yield Supply enter transaction expressed in token units. + */ +class YieldSupplyGetCurrentFeeUseCase( + private val feeRepository: FeeRepository, + private val quotesRepository: QuotesRepository, + private val currenciesRepository: CurrenciesRepository, +) { + + suspend operator fun invoke( + userWallet: UserWallet, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): Either = 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, + YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT, + ) + + val rateRatio = nativeFiatRate.divide( + fiatRate, + cryptoCurrencyStatus.currency.decimals, + RoundingMode.HALF_UP, + ) + + val tokenValue = rateRatio.multiply(nativeGas.amount.value) + + tokenValue.stripTrailingZeros() + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetMaxFeeUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetMaxFeeUseCase.kt new file mode 100644 index 0000000000..e463368cd3 --- /dev/null +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetMaxFeeUseCase.kt @@ -0,0 +1,67 @@ +package com.tangem.domain.yield.supply.usecase + +import arrow.core.Either +import arrow.core.Either.Companion.catch +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.yield.supply.YieldSupplyRepository +import java.math.BigDecimal +import java.math.RoundingMode + +/** + * Calculates max allowed network fee for Yield Supply enter transaction expressed in token units. + * + * Uses YieldMarketToken.maxFeeNative (native coin units) and converts it to token units with the + * same conversion logic as [YieldSupplyGetCurrentFeeUseCase]: based on fiat rate ratio + * (nativeFiatRate / tokenFiatRate). + */ +class YieldSupplyGetMaxFeeUseCase( + private val yieldSupplyRepository: YieldSupplyRepository, + private val quotesRepository: QuotesRepository, + private val currenciesRepository: CurrenciesRepository, +) { + + suspend operator fun invoke( + userWallet: UserWallet, + cryptoCurrencyStatus: CryptoCurrencyStatus, + ): Either = catch { + val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token + ?: error("CryptoCurrency must be token for max fee calculation") + + 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 marketToken = yieldSupplyRepository.getTokenStatus(token) + val maxFeeNative = marketToken.maxFeeNative.toBigDecimal() + + val rateRatio = nativeFiatRate.divide( + fiatRate, + cryptoCurrencyStatus.currency.decimals, + RoundingMode.HALF_UP, + ) + + val tokenValue = rateRatio.multiply(maxFeeNative) + + tokenValue.stripTrailingZeros() + } +} \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyMinAmountUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyMinAmountUseCase.kt index b544b7fe62..9e6f8b109d 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyMinAmountUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyMinAmountUseCase.kt @@ -2,16 +2,15 @@ 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.yield.supply.fixFee 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 com.tangem.domain.yield.supply.YieldSupplyConst.YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT import java.math.BigDecimal -import java.math.BigInteger import java.math.RoundingMode class YieldSupplyMinAmountUseCase( @@ -45,7 +44,10 @@ class YieldSupplyMinAmountUseCase( ?: 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 nativeGas = feeWithoutGas.fixFee( + nativeCryptoCurrency, + YIELD_SUPPLY_EVM_CONSTANT_GAS_LIMIT, + ) val rateRatio = nativeFiatRate.divide( fiatRate, @@ -62,27 +64,8 @@ class YieldSupplyMinAmountUseCase( .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() } } \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/formatter/YieldSupplyMinAmountFormatter.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/formatter/YieldSupplyAmountFormatter.kt similarity index 53% rename from features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/formatter/YieldSupplyMinAmountFormatter.kt rename to features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/formatter/YieldSupplyAmountFormatter.kt index 08c48d9cb0..296cd74afb 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/formatter/YieldSupplyMinAmountFormatter.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/formatter/YieldSupplyAmountFormatter.kt @@ -2,24 +2,38 @@ package com.tangem.features.yield.supply.impl.common.formatter import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.format.bigdecimal.approximateAmount 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 com.tangem.utils.StringsSigns.DOT import java.math.BigDecimal -internal class YieldSupplyMinAmountFormatter( +internal class YieldSupplyAmountFormatter( private val feeCryptoCurrency: CryptoCurrency, private val appCurrency: AppCurrency, ) { - operator fun invoke(feeValue: BigDecimal, fiatRate: BigDecimal?): TextReference { + operator fun invoke(feeValue: BigDecimal, fiatRate: BigDecimal?, showCrypto: Boolean = true): TextReference { val cryptoFee = feeValue.format { crypto(feeCryptoCurrency) } val fiatFeeValue = fiatRate?.let(feeValue::multiply) - val fiatFee = fiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) } + val fiatFee = if (showCrypto) { + fiatFeeValue.format { + fiat(appCurrency.code, appCurrency.symbol) + .approximateAmount() + } + } else { + fiatFeeValue.format { + fiat(appCurrency.code, appCurrency.symbol) + } + } - return stringReference(cryptoFee + " ${StringsSigns.DOT} " + fiatFee) + return if (showCrypto) { + stringReference("$cryptoFee $DOT $fiatFee") + } else { + stringReference(fiatFee) + } } } \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyFeeRow.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyFeeRow.kt index 2f2901b79d..dc00a547a8 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyFeeRow.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/ui/YieldSupplyFeeRow.kt @@ -12,6 +12,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.SpacerWMax import com.tangem.core.ui.components.TextShimmer @@ -46,6 +47,7 @@ internal fun YieldSupplyFeeRow(title: TextReference, value: TextReference?) { text = targetValue.resolveReference(), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.End, ) } else { TextShimmer( diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/entity/YieldSupplyActiveContentUM.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/entity/YieldSupplyActiveContentUM.kt index 7b1e55f50a..9b93df6c2a 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/entity/YieldSupplyActiveContentUM.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/entity/YieldSupplyActiveContentUM.kt @@ -10,6 +10,9 @@ internal data class YieldSupplyActiveContentUM( val subtitle: TextReference, val subtitleLink: TextReference, val notificationUM: NotificationUM?, - val apy: TextReference? = null, val minAmount: TextReference?, + val currentFee: TextReference?, + val feeDescription: TextReference?, + val apy: TextReference? = null, + val isHighFee: Boolean = false, ) \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt index 2b63d7405c..1b9157aa2e 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt @@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList 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.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency @@ -19,9 +20,11 @@ import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.yield.supply.usecase.YieldSupplyGetProtocolBalanceUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyGetTokenStatusUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyMinAmountUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetCurrentFeeUseCase +import com.tangem.domain.yield.supply.usecase.YieldSupplyGetMaxFeeUseCase import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics 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.common.formatter.YieldSupplyAmountFormatter import com.tangem.features.yield.supply.impl.subcomponents.active.YieldSupplyActiveComponent import com.tangem.features.yield.supply.impl.subcomponents.active.entity.YieldSupplyActiveContentUM import com.tangem.utils.StringsSigns.DASH_SIGN @@ -40,6 +43,8 @@ internal class YieldSupplyActiveModel @Inject constructor( private val yieldSupplyGetProtocolBalanceUseCase: YieldSupplyGetProtocolBalanceUseCase, private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase, private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase, + private val yieldSupplyGetCurrentFeeUseCase: YieldSupplyGetCurrentFeeUseCase, + private val yieldSupplyGetMaxFeeUseCase: YieldSupplyGetMaxFeeUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, ) : Model() { @@ -62,6 +67,9 @@ internal class YieldSupplyActiveModel @Inject constructor( subtitleLink = resourceReference(R.string.common_read_more), notificationUM = null, minAmount = null, + currentFee = null, + feeDescription = null, + isHighFee = false, ), ) @@ -115,6 +123,7 @@ internal class YieldSupplyActiveModel @Inject constructor( loadApy() loadMinAmount() + loadFees() uiState.update { it.copy( @@ -154,10 +163,14 @@ internal class YieldSupplyActiveModel @Inject constructor( params.userWallet, cryptoCurrencyStatusFlow.value, ).onRight { minAmount -> - val minAmountTextReference = YieldSupplyMinAmountFormatter( + val minAmountTextReference = YieldSupplyAmountFormatter( cryptoCurrencyStatusFlow.value.currency, appCurrency, - ).invoke(minAmount, cryptoCurrencyStatusFlow.value.value.fiatRate) + ).invoke( + feeValue = minAmount, + fiatRate = cryptoCurrencyStatusFlow.value.value.fiatRate, + showCrypto = false, + ) uiState.update { it.copy(minAmount = minAmountTextReference) } @@ -169,6 +182,51 @@ internal class YieldSupplyActiveModel @Inject constructor( } } + private fun loadFees() { + modelScope.launch(dispatchers.default) { + val cryptoStatus = cryptoCurrencyStatusFlow.value + + val currentFee = yieldSupplyGetCurrentFeeUseCase( + userWallet = params.userWallet, + cryptoCurrencyStatus = cryptoStatus, + ).getOrNull() + + val maxFee = yieldSupplyGetMaxFeeUseCase( + userWallet = params.userWallet, + cryptoCurrencyStatus = cryptoStatus, + ).getOrNull() + + val currentFeeText = currentFee?.let { + YieldSupplyAmountFormatter( + cryptoStatus.currency, + appCurrency, + ).invoke( + feeValue = it, + fiatRate = cryptoStatus.value.fiatRate, + showCrypto = false, + ) + } + + val isHighFee = if (currentFee != null && maxFee != null) currentFee > maxFee else false + + val maxFiatFee = cryptoStatus.value.fiatRate?.multiply(maxFee) + .format { fiat(appCurrency.code, appCurrency.symbol) } + val feeDescription = if (isHighFee) { + resourceReference(R.string.yield_module_earn_sheet_high_fee_description, wrappedList(maxFiatFee)) + } else { + resourceReference(R.string.yield_module_earn_sheet_fee_description, wrappedList(maxFiatFee)) + } + + uiState.update { + it.copy( + currentFee = currentFeeText ?: stringReference(DASH_SIGN), + isHighFee = isHighFee, + feeDescription = feeDescription, + ) + } + } + } + private companion object { const val AAVEV3_PREFIX = "a" } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/ui/YieldSupplyActiveContent.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/ui/YieldSupplyActiveContent.kt index a20183fb10..9e2346fe2f 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/ui/YieldSupplyActiveContent.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/ui/YieldSupplyActiveContent.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.LinkAnnotation +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.withLink import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter @@ -70,6 +71,22 @@ internal fun YieldSupplyActiveContent( } YieldSupplyActiveMyFunds(state = state, isBalanceHidden = isBalanceHidden) + + AnimatedVisibility(state.feeDescription != null) { + Text( + modifier = Modifier.padding(horizontal = 12.dp), + text = state.feeDescription?.resolveReference().orEmpty(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) + } + + Text( + modifier = Modifier.padding(horizontal = 12.dp), + text = stringResourceSafe(R.string.yield_module_fee_policy_sheet_min_amount_note), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + ) } } @@ -99,7 +116,9 @@ private fun CurrentApy(apy: TextReference?, modifier: Modifier = Modifier) { painterResource(R.drawable.ic_arrow_up_8), tint = TangemTheme.colors.text.accent, contentDescription = null, - modifier = Modifier.padding(end = 6.dp).size(12.dp), + modifier = Modifier + .padding(end = 6.dp) + .size(12.dp), ) Text( modifier = modifier, @@ -113,6 +132,7 @@ private fun CurrentApy(apy: TextReference?, modifier: Modifier = Modifier) { } } +@Suppress("LongMethod") @Composable private fun YieldSupplyActiveMyFunds(state: YieldSupplyActiveContentUM, isBalanceHidden: Boolean) { Column( @@ -173,6 +193,15 @@ private fun YieldSupplyActiveMyFunds(state: YieldSupplyActiveContentUM, isBalanc info = state.minAmount, isBalanceHidden = false, ) + HorizontalDivider( + thickness = 0.5.dp, + color = TangemTheme.colors.stroke.primary, + ) + HighComissionInfoRow( + title = resourceReference(R.string.common_network_fee_title), + info = state.currentFee, + isHighComission = state.isHighFee, + ) } } @@ -220,6 +249,7 @@ private fun InfoRow(title: TextReference, isBalanceHidden: Boolean, info: TextRe text = currentInfo.orMaskWithStars(isBalanceHidden).resolveReference(), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.End, ) } else { TextShimmer( @@ -231,6 +261,57 @@ private fun InfoRow(title: TextReference, isBalanceHidden: Boolean, info: TextRe } } +@Composable +private fun HighComissionInfoRow(title: TextReference, info: TextReference?, isHighComission: Boolean) { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = Modifier.padding(horizontal = 4.dp, vertical = 12.dp), + ) { + Text( + text = title.resolveReference(), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.primary1, + ) + SpacerWMax() + + AnimatedContent(info) { currentInfo -> + if (currentInfo != null) { + if (isHighComission) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Icon( + painterResource(R.drawable.ic_token_info_24), + contentDescription = null, + modifier = Modifier.size(20.dp), + tint = TangemTheme.colors.text.warning, + ) + Text( + text = currentInfo.resolveReference(), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.warning, + textAlign = TextAlign.End, + ) + } + } else { + Text( + text = currentInfo.resolveReference(), + style = TangemTheme.typography.body1, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.End, + ) + } + } else { + TextShimmer( + text = title.resolveReference(), + style = TangemTheme.typography.body1, + ) + } + } + } +} + // region Preview @Composable @Preview(showBackground = true, widthDp = 360) @@ -262,6 +343,12 @@ private class YieldSupplyActiveBottomSheetPreviewProvider : PreviewParameterProv notificationUM = NotificationUM.Error.InvalidAmount, apy = stringReference("5,14%"), minAmount = stringReference("50 USDT"), + isHighFee = true, + feeDescription = stringReference( + "The network fee is currently too high to execute lending." + + "Funds will be supplied once it drops to \$12 or below. ", + ), + currentFee = stringReference("30 USDT"), ), ) } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/transformers/YieldSupplyStartEarningFeeContentTransformer.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/transformers/YieldSupplyStartEarningFeeContentTransformer.kt index 5e0ba9a4a9..51e5126eb7 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/transformers/YieldSupplyStartEarningFeeContentTransformer.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/transformers/YieldSupplyStartEarningFeeContentTransformer.kt @@ -10,7 +10,7 @@ import com.tangem.domain.appcurrency.model.AppCurrency 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.YieldSupplyFeeUM -import com.tangem.features.yield.supply.impl.common.formatter.YieldSupplyMinAmountFormatter +import com.tangem.features.yield.supply.impl.common.formatter.YieldSupplyAmountFormatter import com.tangem.utils.StringsSigns.DOT import com.tangem.utils.transformer.Transformer import kotlinx.collections.immutable.toPersistentList @@ -49,7 +49,7 @@ internal class YieldSupplyStartEarningFeeContentTransformer( } val maxFiatFee = maxFiatFeeValue.format { fiat(appCurrency.code, appCurrency.symbol) } - val minAmountTextReference = YieldSupplyMinAmountFormatter( + val minAmountTextReference = YieldSupplyAmountFormatter( cryptoCurrency, appCurrency, ).invoke(minAmount, cryptoCurrencyStatus.value.fiatRate) From c1ca73561df76d875a7be0a18b3c1b80bcbf84bd Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Oct 2025 23:45:25 +0500 Subject: [PATCH 16/25] Updated on 2026-08-14 --- .../com/tangem/utils/TangemBlogUrlBuilder.kt | 2 + ...dShowYieldSupplyDepositedWarningUseCase.kt | 11 +- ...wYieldSupplyDepositedWarningUseCaseTest.kt | 107 ------------------ .../supply/impl/common/YieldSupplyTrigger.kt | 39 +++++++ .../impl/di/YieldSupplyFeatureModule.kt | 17 +++ .../impl/main/model/YieldSupplyModel.kt | 24 ++++ .../impl/promo/model/YieldSupplyPromoModel.kt | 5 +- .../model/YieldSupplyStartEarningModel.kt | 14 +-- .../model/YieldSupplyStopEarningModel.kt | 11 +- 9 files changed, 101 insertions(+), 129 deletions(-) delete mode 100644 domain/tokens/src/test/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCaseTest.kt create mode 100644 features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/YieldSupplyTrigger.kt diff --git a/core/utils/src/main/java/com/tangem/utils/TangemBlogUrlBuilder.kt b/core/utils/src/main/java/com/tangem/utils/TangemBlogUrlBuilder.kt index 4b6406002d..3bb43d00e2 100644 --- a/core/utils/src/main/java/com/tangem/utils/TangemBlogUrlBuilder.kt +++ b/core/utils/src/main/java/com/tangem/utils/TangemBlogUrlBuilder.kt @@ -22,4 +22,6 @@ object TangemBlogUrlBuilder { const val RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP = "https://tangem.com/en/blog/post/give-revoke-permission/" const val YIELD_SUPPLY_HOW_IT_WORKS_URL = "https://tangem.com/en/blog/post/savings-account" + const val YIELD_SUPPLY_TOS_URL = "https://aave.com/terms-of-service" + const val YIELD_SUPPLY_PRIVACY_URL = "https://aave.com/privacy-policy" } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCase.kt index 417e84e987..fa90d45903 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCase.kt @@ -5,15 +5,18 @@ import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.withContext +@Suppress("UnusedPrivateProperty") class NeedShowYieldSupplyDepositedWarningUseCase( private val yieldSupplyWarningsViewedRepository: YieldSupplyWarningsViewedRepository, private val dispatchers: CoroutineDispatcherProvider, ) { suspend operator fun invoke(cryptoCurrencyStatus: CryptoCurrencyStatus?): Boolean = withContext(dispatchers.io) { - val hasActiveLending = cryptoCurrencyStatus?.value?.yieldSupplyStatus?.isActive == true - if (!hasActiveLending) return@withContext false - val showedWarnings = yieldSupplyWarningsViewedRepository.getViewedWarnings() - return@withContext !showedWarnings.contains(cryptoCurrencyStatus?.currency?.name) + // TEMPORARY REQUIREMENTS + return@withContext false + // val hasActiveLending = cryptoCurrencyStatus?.value?.yieldSupplyStatus?.isActive == true + // if (!hasActiveLending) return@withContext false + // val showedWarnings = yieldSupplyWarningsViewedRepository.getViewedWarnings() + // return@withContext !showedWarnings.contains(cryptoCurrencyStatus?.currency?.name) } } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCaseTest.kt deleted file mode 100644 index 1873ddeed3..0000000000 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/NeedShowYieldSupplyDepositedWarningUseCaseTest.kt +++ /dev/null @@ -1,107 +0,0 @@ -package com.tangem.domain.tokens - -import com.google.common.truth.Truth.assertThat -import com.tangem.domain.models.currency.CryptoCurrencyStatus -import com.tangem.domain.models.network.NetworkAddress -import com.tangem.domain.models.serialization.SerializedBigDecimal -import com.tangem.domain.models.yield.supply.YieldSupplyStatus -import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository -import com.tangem.domain.tokens.mock.MockTokens -import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.impl.annotations.RelaxedMockK -import io.mockk.junit5.MockKExtension -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith - -@OptIn(ExperimentalCoroutinesApi::class) -@ExtendWith(MockKExtension::class) -class NeedShowYieldSupplyDepositedWarningUseCaseTest { - - @RelaxedMockK - private lateinit var repository: YieldSupplyWarningsViewedRepository - - private lateinit var dispatchers: TestingCoroutineDispatcherProvider - - @BeforeEach - fun setup() { - dispatchers = TestingCoroutineDispatcherProvider() - } - - @Test - fun `GIVEN null status WHEN invoke THEN returns false`() = runTest { - val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers) - - val result = useCase.invoke(null) - - assertThat(result).isFalse() - coVerify(exactly = 0) { repository.getViewedWarnings() } - } - - @Test - fun `GIVEN inactive lending WHEN invoke THEN returns false`() = runTest { - val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers) - val status = createStatus(isActive = false) - - val result = useCase.invoke(status) - - assertThat(result).isFalse() - coVerify(exactly = 0) { repository.getViewedWarnings() } - } - - @Test - fun `GIVEN active lending and not viewed WHEN invoke THEN returns true`() = runTest { - val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers) - val status = createStatus(isActive = true) - coEvery { repository.getViewedWarnings() } returns emptySet() - - val result = useCase.invoke(status) - - assertThat(result).isTrue() - coVerify(exactly = 1) { repository.getViewedWarnings() } - } - - @Test - fun `GIVEN active lending and already viewed WHEN invoke THEN returns false`() = runTest { - val useCase = NeedShowYieldSupplyDepositedWarningUseCase(repository, dispatchers) - val status = createStatus(isActive = true) - coEvery { repository.getViewedWarnings() } returns setOf(status.currency.name) - - val result = useCase.invoke(status) - - assertThat(result).isFalse() - coVerify(exactly = 1) { repository.getViewedWarnings() } - } - - private fun createStatus(isActive: Boolean): CryptoCurrencyStatus { - val currency = MockTokens.token1 - val yieldSupplyStatus = YieldSupplyStatus( - isActive = isActive, - isInitialized = true, - isAllowedToSpend = true, - ) - val value = CryptoCurrencyStatus.NoQuote( - amount = SerializedBigDecimal.ZERO, - yieldBalance = null, - yieldSupplyStatus = yieldSupplyStatus, - hasCurrentNetworkTransactions = false, - pendingTransactions = emptySet(), - networkAddress = NetworkAddress.Single( - defaultAddress = NetworkAddress.Address( - value = "address", - type = NetworkAddress.Address.Type.Primary, - ), - ), - sources = CryptoCurrencyStatus.Sources(), - ) - - return CryptoCurrencyStatus( - currency = currency, - value = value, - ) - } -} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/YieldSupplyTrigger.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/YieldSupplyTrigger.kt new file mode 100644 index 0000000000..691095237a --- /dev/null +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/common/YieldSupplyTrigger.kt @@ -0,0 +1,39 @@ +package com.tangem.features.yield.supply.impl.common + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Trigger for entering/exiting protocol from other components + */ +interface YieldSupplyProtocolTrigger { + suspend fun onEnterProtocol() + suspend fun onExitProtocol() +} + +/** + * Listener to observe entering/exiting protocol events + */ +interface YieldSupplyProtocolListener { + val enterProtocolTriggerFlow: Flow + val exitProtocolTriggerFlow: Flow +} + +@Singleton +internal class DefaultYieldSupplyProtocolTrigger @Inject constructor() : + YieldSupplyProtocolTrigger, + YieldSupplyProtocolListener { + + override val enterProtocolTriggerFlow = MutableSharedFlow() + override val exitProtocolTriggerFlow = MutableSharedFlow() + + override suspend fun onEnterProtocol() { + enterProtocolTriggerFlow.emit(Unit) + } + + override suspend fun onExitProtocol() { + exitProtocolTriggerFlow.emit(Unit) + } +} \ No newline at end of file diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/di/YieldSupplyFeatureModule.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/di/YieldSupplyFeatureModule.kt index 45b8806bd6..9226bcfa8e 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/di/YieldSupplyFeatureModule.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/di/YieldSupplyFeatureModule.kt @@ -3,6 +3,10 @@ package com.tangem.features.yield.supply.impl.di import com.tangem.core.configtoggle.feature.FeatureTogglesManager import com.tangem.features.yield.supply.impl.DefaultYieldSupplyFeatureToggles import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles +import com.tangem.features.yield.supply.impl.common.DefaultYieldSupplyProtocolTrigger +import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolListener +import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolTrigger +import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -18,4 +22,17 @@ internal object YieldSupplyFeatureModule { fun provideYieldFeatureToggles(featureTogglesManager: FeatureTogglesManager): YieldSupplyFeatureToggles { return DefaultYieldSupplyFeatureToggles(featureTogglesManager) } +} + +@InstallIn(SingletonComponent::class) +@Module +internal interface YieldSupplyProtocolModuleBinds { + + @Singleton + @Binds + fun bindYieldSupplyProtocolTrigger(impl: DefaultYieldSupplyProtocolTrigger): YieldSupplyProtocolTrigger + + @Singleton + @Binds + fun bindYieldSupplyProtocolListener(impl: DefaultYieldSupplyProtocolTrigger): YieldSupplyProtocolListener } \ No newline at end of file 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 8b1e2a09d7..053cd01001 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 @@ -27,6 +27,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyIsAvailableUseCase import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.api.YieldSupplyComponent import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics +import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolListener import com.tangem.features.yield.supply.impl.main.entity.YieldSupplyUM import com.tangem.features.yield.supply.impl.main.model.transformers.YieldSupplyTokenStatusSuccessTransformer import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -56,6 +57,7 @@ internal class YieldSupplyModel @Inject constructor( private val yieldSupplyIsAvailableUseCase: YieldSupplyIsAvailableUseCase, private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase, private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase, + private val yieldSupplyProtocolListener: YieldSupplyProtocolListener, ) : Model(), YieldSupplyClickIntents { private val params = paramsContainer.require() @@ -83,6 +85,28 @@ internal class YieldSupplyModel @Inject constructor( init { checkIfYieldSupplyIsAvailable() + observeProtocolEvents() + } + + private fun observeProtocolEvents() { + yieldSupplyProtocolListener.exitProtocolTriggerFlow.onEach { + uiState.update { + YieldSupplyUM.Processing.Exit + } + coroutineScope.launch(dispatchers.io) { + delay(PROCESSING_UPDATE_DELAY) + fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id) + } + }.launchIn(modelScope) + yieldSupplyProtocolListener.enterProtocolTriggerFlow.onEach { + uiState.update { + YieldSupplyUM.Processing.Enter + } + coroutineScope.launch(dispatchers.io) { + delay(PROCESSING_UPDATE_DELAY) + fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id) + } + }.launchIn(modelScope) } private fun checkIfYieldSupplyIsAvailable() { diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt index 812323df89..09c57f0305 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/promo/model/YieldSupplyPromoModel.kt @@ -15,6 +15,7 @@ import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.impl.promo.YieldSupplyPromoConfig import com.tangem.features.yield.supply.impl.promo.entity.YieldSupplyPromoUM +import com.tangem.utils.TangemBlogUrlBuilder import com.tangem.utils.TangemBlogUrlBuilder.YIELD_SUPPLY_HOW_IT_WORKS_URL import com.tangem.utils.coroutines.CoroutineDispatcherProvider import javax.inject.Inject @@ -31,8 +32,8 @@ internal class YieldSupplyPromoModel @Inject constructor( val params: YieldSupplyPromoComponent.Params = paramsContainer.require() val uiState: YieldSupplyPromoUM = YieldSupplyPromoUM( - tosLink = "https://tangem.com/terms-of-service/", // TODO replace with real link - policyLink = "https://tangem.com/privacy-policy/", // TODO replace with real link + tosLink = TangemBlogUrlBuilder.YIELD_SUPPLY_TOS_URL, + policyLink = TangemBlogUrlBuilder.YIELD_SUPPLY_PRIVACY_URL, title = resourceReference(R.string.yield_module_promo_screen_title), subtitle = resourceReference( R.string.yield_module_promo_screen_variable_rate_info, diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt index f7390118e6..aaa41615e2 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/startearning/model/YieldSupplyStartEarningModel.kt @@ -15,7 +15,6 @@ 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.models.wallet.UserWallet -import com.tangem.domain.tokens.FetchCurrencyStatusUseCase import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.transaction.error.GetFeeError @@ -29,6 +28,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyStartEarningUseCase import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory +import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolTrigger 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.transformer.YieldSupplyTransactionInProgressTransformer @@ -41,7 +41,6 @@ import com.tangem.features.yield.supply.impl.subcomponents.startearning.model.tr import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero import com.tangem.utils.transformer.update -import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber @@ -63,11 +62,11 @@ internal class YieldSupplyStartEarningModel @Inject constructor( private val yieldSupplyEstimateEnterFeeUseCase: YieldSupplyEstimateEnterFeeUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger, - private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, private val yieldSupplyAlertFactory: YieldSupplyAlertFactory, private val yieldSupplyActivateUseCase: YieldSupplyActivateUseCase, private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase, private val yieldSupplyMinAmountUseCase: YieldSupplyMinAmountUseCase, + private val yieldSupplyProtocolTrigger: YieldSupplyProtocolTrigger, ) : Model(), YieldSupplyNotificationsComponent.ModelCallback { private val params: YieldSupplyStartEarningComponent.Params = paramsContainer.require() @@ -253,14 +252,9 @@ internal class YieldSupplyStartEarningModel @Inject constructor( ) }, ifRight = { - yieldSupplyActivateUseCase(cryptoCurrency) - modelScope.launch(NonCancellable) { - fetchCurrencyStatusUseCase( - userWalletId = userWallet.walletId, - cryptoCurrency.id, - ) - } + yieldSupplyProtocolTrigger.onEnterProtocol() analytics.send(YieldSupplyAnalytics.FundsEarned) + yieldSupplyActivateUseCase(cryptoCurrency) modelScope.launch { params.callback.onTransactionSent() } diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt index 9e704f9967..268ab35f31 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/stopearning/model/YieldSupplyStopEarningModel.kt @@ -13,7 +13,6 @@ 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.tokens.FetchCurrencyStatusUseCase import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.transaction.usecase.SendTransactionUseCase @@ -22,6 +21,7 @@ import com.tangem.domain.yield.supply.usecase.YieldSupplyStopEarningUseCase import com.tangem.features.yield.supply.impl.R import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory +import com.tangem.features.yield.supply.impl.common.YieldSupplyProtocolTrigger 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.transformer.YieldSupplyTransactionInProgressTransformer @@ -35,7 +35,6 @@ import com.tangem.utils.TangemBlogUrlBuilder import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.extensions.orZero import com.tangem.utils.transformer.update -import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber @@ -56,7 +55,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor( private val yieldSupplyNotificationsUpdateTrigger: YieldSupplyNotificationsUpdateTrigger, private val yieldSupplyAlertFactory: YieldSupplyAlertFactory, private val yieldSupplyDeactivateUseCase: YieldSupplyDeactivateUseCase, - private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, + private val yieldSupplyProtocolTrigger: YieldSupplyProtocolTrigger, ) : Model(), YieldSupplyNotificationsComponent.ModelCallback { private val params: YieldSupplyStopEarningComponent.Params = paramsContainer.require() @@ -159,6 +158,7 @@ internal class YieldSupplyStopEarningModel @Inject constructor( ) }, ifRight = { + yieldSupplyProtocolTrigger.onExitProtocol() analytics.send( YieldSupplyAnalytics.FundsWithdrawn( token = cryptoCurrency.symbol, @@ -166,10 +166,9 @@ internal class YieldSupplyStopEarningModel @Inject constructor( ), ) yieldSupplyDeactivateUseCase(cryptoCurrency) - modelScope.launch(NonCancellable) { - fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id) + modelScope.launch { + params.callback.onTransactionSent() } - params.callback.onTransactionSent() }, ) } From af7f6547db2836f7a1f9831e2d1407f40bd15f35 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Oct 2025 14:25:27 +0500 Subject: [PATCH 17/25] Updated on 2026-08-14 --- core/res/src/main/res/values-de/strings.xml | 6 +- core/res/src/main/res/values-ja/strings.xml | 4 +- core/res/src/main/res/values-ru/strings.xml | 20 ++-- core/res/src/main/res/values/strings.xml | 14 ++- .../YieldSupplyGetTokenStatusUseCase.kt | 2 +- .../components/TokenDetailsNotification.kt | 4 +- .../impl/main/model/YieldSupplyModel.kt | 101 ++++++++++++------ .../active/model/YieldSupplyActiveModel.kt | 2 +- 8 files changed, 101 insertions(+), 52 deletions(-) diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 86569cbb95..c5d3a6d70e 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -1684,8 +1684,8 @@ Historische Renditen Sofortige Auszahlung Wie funktioniert das? - Verdiene %s%% jährlich - Aave • Variabler Zinssatz + Mit Aave verbinden + Aave %1$s • Variabler Zinssatz Aave Durchschnitt %s Renditen des letzten Jahres @@ -1698,7 +1698,7 @@ Effektiver Jahreszins für Versorgung Lass Dein Geld arbeiten – verdiene Zinsen auf Dein Guthaben. Verdienst auf Dein Guthaben - Verdienen %1$s%% pro Jahr + Lass dein Guthaben arbeiten Der Stakingservice ist derzeit nicht verfügbar. Bitte versuche es später erneut. Einnahmen nicht verfügbar Chart konnte nicht geladen werden... diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 7ec812032b..16107ea2c1 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -1772,7 +1772,7 @@ Aaveは、総額819億ドル以上の資産を管理する分散型プロトコルです。 分散型・自己管理型 サービスを利用することにより、プロバイダー\n %1$sおよび%2$sに同意したことになります - 年間%s%%の収益 + Aave を接続 Aave %1$s%% • 変動金利 Aave 平均%s @@ -1796,7 +1796,7 @@ 利息は自動的に発生します Aaveの利回り 入金の処理中 - 年間%1$s%%の収益 + 残高を活用 自動 取引のネットワーク手数料をカバーするために、 %1$s %2$sを入金してください %s手数料を支払えません diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 5ed2ce02b7..168ba8b435 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -192,8 +192,10 @@ Медленно Скорость и комиссия Завершить + Забыть Из Синхронизировать адреса + Начать зарабатывать К провайдеру Перейти в токен Понятно @@ -527,6 +529,7 @@ Выбранный токен не доступен в кошельке на данный момент. Но не переживайте, вы можете выразить свой интерес проголосовав за его добавление. Голосовать Кошелёк не поддерживает более одной сети + О монете Чтобы купить, обменять или получить данный токен, вам нужно добавить его к себе в портфель Этот актив в настоящее время не поддерживается в кошельке Этот токен не доступен для данного кошелька @@ -561,6 +564,7 @@ В тренде Стейкинг — простой способ получать доход с вашей криптовалюты. %s Получайте до %s APY + Токен добавлен О %s %d биржа @@ -838,6 +842,7 @@ до %d дней %s мин + Доступно от Вы получите Вы можете закрыть этот экран и проверить статус транзакции на экране информации о токене. До @@ -1214,6 +1219,7 @@ Вы получите Выберите токен не доступен + Комиссия Это мой кошелек Балансы скрыты Балансы показаны @@ -1590,7 +1596,7 @@ Минимальный депозит Политика комиссий Tangem взимает комиссию за обслуживание в размере 3% от полученного дохода. - Комиссия в сети сейчас слишком высокая. Ждём, пока она упадёт ниже вашего лимита. + Ваши средства будут автоматически переведены в Aave, как только комиссия сети снизится или баланс достигнет минимально необходимой суммы. Историческая доходность Необходимо разрешение для токена Проверьте ваше интернет соединение @@ -1603,10 +1609,10 @@ Aave — это децентрализованный протокол, управляющий активами на сумму более 81,9 миллиарда долларов США. Децентрализованный и некастодиальный Используя сервис, вы соглашаетесь с условиями провайдера %1$s и %2$s - Зарабатывайте %s%% в год - Aave • Ставка с плавающим процентом + Подключить Aave + Aave %1$s%%• Ставка с плавающим процентом Aave - Среднее %s + Сред. %s Доходность за прошлый год Текущая процентная ставка всегда переменная и автоматически рассчитывается смарт-контрактом AAVE в блокчейне на основе текущего спроса и предложения. При поддержке @@ -1618,16 +1624,16 @@ Следующие пополнения вашего счёта автоматически поступят в Aave. Активен На паузе - Закончить зарабатывать + Завершить заработок Выключив эту функцию, вы выведёте средства из Aave, получите их обратно в %s в кошельке и перестанете зарабатывать награды. Комиссия сети будет вычтена из суммы вашего вывода. Годовая доходность (APY) APY Пусть ваши деньги работают — зарабатывайте проценты на свой баланс. Проценты начисляются автоматически. - Доходность Aave + Доходность Отправка ваших средств - Зарабатывайте %1$s%% в год + Пусть ваш баланс работает на вас! Автоматически Внесите немного %1$s %2$s, чтобы покрыть комиссию сети за транзакции. Невозможно покрыть комиссию в %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 48aa70401e..fa0fd9a2ea 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -992,6 +992,10 @@ I realize that I can\'t use this card to recover my access code on the other cards of the current wallet Factory Reset will completely delete the wallet from the selected card or ring. You will not be able to restore the current wallet or use the card or ring to recover the access code. Factory Reset will completely delete the wallet from the selected card or ring and remove it from the app. You will not be able to restore the current wallet. + All Tangem devices have been reset. + Something went wrong with activation process. Please reset cards one by one. + Card verification failed + Please reset the next device to continue Ring owners get 3 commission-free swaps on Changelly until 15.11! Swap With 0% Fees Now! Log into the app and check your balance without scanning the card or ring @@ -1834,7 +1838,7 @@ Minimal top-up Fee policy Tangem also takes a 3% service fee on the yield earned. - Network fee is too high right now. Waiting until it falls below your limit. + Your funds will be automatically transferred to Aave once network fees are lower or your balance meets the minimum required amount. Historical returns Write description here. In one, two or three lines will be awesome. [PLACEHOLDER] Some token approve needed @@ -1863,16 +1867,16 @@ Your next top-ups will be automatically supplied to Aave. Active Paused - Stop earning + Disable yield mode Turning off will withdraw your funds from Aave, return them to %s in your wallet, and stop earning rewards. The network fee will be deducted from the amount you withdraw. Supply APY APY - Make your money work — earn interest on your balance. + Let your funds work in the background while you stay in control. Interest accrues automatically - Aave yield + Yield mode Processing your deposit - Earn %1$s%% per year + Make your balance work for you Automatic Deposit some %1$s %2$s to cover the network fee for transactions Unable to cover %s fee diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetTokenStatusUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetTokenStatusUseCase.kt index 36737e8d43..e86b0308e0 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetTokenStatusUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetTokenStatusUseCase.kt @@ -13,6 +13,6 @@ class YieldSupplyGetTokenStatusUseCase( suspend operator fun invoke(token: CryptoCurrency.Token): Either = Either.catch { val tokens = yieldSupplyRepository.getCachedMarkets().orEmpty() val cachedStatus = tokens.firstOrNull { it.yieldSupplyKey == token.yieldSupplyKey() } - cachedStatus ?: error("YieldMarketToken not found") + cachedStatus ?: yieldSupplyRepository.getTokenStatus(token) } } \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index 814e1fbe1b..b124a9b02e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -263,6 +263,8 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { id = R.string.yield_module_amount_not_transfered_to_aave_title, wrappedList(amount, tokenName), ), - subtitle = TextReference.EMPTY, + subtitle = resourceReference( + id = R.string.yield_module_high_fee_error, + ), ) } \ No newline at end of file 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 053cd01001..5d1d974638 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 @@ -8,6 +8,7 @@ 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.extensions.TextReference import com.tangem.core.ui.extensions.combinedReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference @@ -17,7 +18,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.network.TxInfo import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.yield.supply.YieldSupplyStatus -import com.tangem.domain.tokens.FetchCurrencyStatusUseCase +import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyActivateUseCase @@ -50,7 +51,7 @@ internal class YieldSupplyModel @Inject constructor( private val appRouter: AppRouter, private val getUserWalletUseCase: GetUserWalletUseCase, private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase, - private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, + private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher, @DelayedWork private val coroutineScope: CoroutineScope, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val yieldSupplyGetTokenStatusUseCase: YieldSupplyGetTokenStatusUseCase, @@ -95,7 +96,12 @@ internal class YieldSupplyModel @Inject constructor( } coroutineScope.launch(dispatchers.io) { delay(PROCESSING_UPDATE_DELAY) - fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id) + singleNetworkStatusFetcher( + params = SingleNetworkStatusFetcher.Params( + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + ), + ) } }.launchIn(modelScope) yieldSupplyProtocolListener.enterProtocolTriggerFlow.onEach { @@ -104,7 +110,12 @@ internal class YieldSupplyModel @Inject constructor( } coroutineScope.launch(dispatchers.io) { delay(PROCESSING_UPDATE_DELAY) - fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id) + singleNetworkStatusFetcher( + params = SingleNetworkStatusFetcher.Params( + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + ), + ) } }.launchIn(modelScope) } @@ -130,6 +141,7 @@ internal class YieldSupplyModel @Inject constructor( currencyId = cryptoCurrency.id, isSingleWalletWithTokens = false, ).onEach { maybeCryptoCurrency -> + Timber.tag("getSingleCryptoCurrencyStatusUseCase").d("update $maybeCryptoCurrency") maybeCryptoCurrency.fold( ifRight = { cryptoCurrencyStatus -> cryptoCurrencyStatusFlow.update { cryptoCurrencyStatus } @@ -211,7 +223,12 @@ internal class YieldSupplyModel @Inject constructor( hasActiveTransaction && yieldTransaction != null -> { coroutineScope.launch(dispatchers.io) { delay(PROCESSING_UPDATE_DELAY) - fetchCurrencyStatusUseCase(userWalletId = userWallet.walletId, cryptoCurrency.id) + singleNetworkStatusFetcher( + params = SingleNetworkStatusFetcher.Params( + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + ), + ) } uiState.update { when (yieldTransaction) { @@ -230,33 +247,10 @@ internal class YieldSupplyModel @Inject constructor( ), ) } - modelScope.launch(dispatchers.default) { - yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken) - .onRight { tokenStatus -> - uiState.update { - YieldSupplyUM.Content( - title = resourceReference( - R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title, - ), - subtitle = resourceReference( - R.string.yield_module_token_details_earn_notification_earning_on_your_balance_subtitle, - ), - rewardsApy = combinedReference( - resourceReference( - R.string.yield_module_token_details_earn_notification_apy, - ), - stringReference(" ${tokenStatus.apy}%"), - ), - onClick = ::onActiveClick, - isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend, - apy = tokenStatus.apy.toString(), - ) - } - }.onLeft { - Timber.e(it) - uiState.update { YieldSupplyUM.Loading } - } - } + loadActiveState( + cryptoCurrencyToken = cryptoCurrencyToken, + yieldSupplyStatus = yieldSupplyStatus, + ) } else -> { @@ -265,6 +259,49 @@ internal class YieldSupplyModel @Inject constructor( } } + private fun loadActiveState(cryptoCurrencyToken: CryptoCurrency.Token, yieldSupplyStatus: YieldSupplyStatus) { + modelScope.launch(dispatchers.default) { + yieldSupplyGetTokenStatusUseCase(cryptoCurrencyToken) + .onRight { tokenStatus -> + uiState.update { + YieldSupplyUM.Content( + title = resourceReference( + R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title, + ), + subtitle = resourceReference( + R.string.yield_module_token_details_earn_notification_earning_on_your_balance_subtitle, + ), + rewardsApy = combinedReference( + resourceReference( + R.string.yield_module_token_details_earn_notification_apy, + ), + stringReference(" ${tokenStatus.apy}%"), + ), + onClick = ::onActiveClick, + isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend, + apy = tokenStatus.apy.toString(), + ) + } + }.onLeft { + Timber.e(it) + uiState.update { + YieldSupplyUM.Content( + title = resourceReference( + R.string.yield_module_token_details_earn_notification_earning_on_your_balance_title, + ), + subtitle = resourceReference( + R.string.yield_module_token_details_earn_notification_earning_on_your_balance_subtitle, + ), + rewardsApy = TextReference.EMPTY, + onClick = ::onActiveClick, + isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend, + apy = "", + ) + } + } + } + } + private fun sendInfoAboutProtocolStatus(cryptoCurrencyStatus: CryptoCurrencyStatus) { if (lastYieldSupplyStatus == cryptoCurrencyStatus.value.yieldSupplyStatus) return val token = cryptoCurrency as? CryptoCurrency.Token ?: return diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt index 1b9157aa2e..268470e09f 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt @@ -209,7 +209,7 @@ internal class YieldSupplyActiveModel @Inject constructor( val isHighFee = if (currentFee != null && maxFee != null) currentFee > maxFee else false - val maxFiatFee = cryptoStatus.value.fiatRate?.multiply(maxFee) + val maxFiatFee = maxFee?.multiply(cryptoStatus.value.fiatRate) .format { fiat(appCurrency.code, appCurrency.symbol) } val feeDescription = if (isHighFee) { resourceReference(R.string.yield_module_earn_sheet_high_fee_description, wrappedList(maxFiatFee)) From 305e86960f1b4adb0a99c22689327024da5010de Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Oct 2025 17:24:44 +0500 Subject: [PATCH 18/25] Updated on 2026-08-14 --- .../common/ui/notifications/NotificationUM.kt | 6 +++++- .../DefaultCurrencyChecksRepository.kt | 18 +++++++++++------- .../model/warnings/CryptoCurrencyWarning.kt | 7 ++++++- .../components/TokenDetailsNotification.kt | 9 ++++++++- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt index 7a8229120e..8c2f75bb85 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/NotificationUM.kt @@ -381,7 +381,11 @@ sealed class NotificationUM(val config: NotificationConfig) { title = TextReference.Res(R.string.send_notification_invalid_amount_title), subtitle = TextReference.Res( id = R.string.send_notification_invalid_amount_rent_fee, - formatArgs = wrappedList(rentInfo.exemptionAmount), + formatArgs = wrappedList( + rentInfo.exemptionAmount.format { + crypto(rentInfo.cryptoCurrency) + }, + ), ), ) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index 3583ff7332..485f1afd18 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -1,11 +1,7 @@ package com.tangem.data.tokens.repository import com.tangem.blockchain.blockchains.polkadot.ExistentialDepositProvider -import com.tangem.blockchain.common.FeeResourceAmountProvider -import com.tangem.blockchain.common.MinimumSendAmountProvider -import com.tangem.blockchain.common.ReserveAmountProvider -import com.tangem.blockchain.common.Token -import com.tangem.blockchain.common.UtxoAmountLimitProvider +import com.tangem.blockchain.common.* import com.tangem.blockchainsdk.utils.toBlockchain import com.tangem.data.tokens.converters.UtxoConverter import com.tangem.domain.models.currency.CryptoCurrency @@ -144,7 +140,11 @@ internal class DefaultCurrencyChecksRepository( return when { balanceValue.amount.isZero() && stakingTotalBalance.isZero() -> null balanceValue.amount < rentData.exemptionAmount && stakingTotalBalance.isZero() -> { - CryptoCurrencyWarning.Rent(rentData.rent, rentData.exemptionAmount) + CryptoCurrencyWarning.Rent( + rent = rentData.rent, + exemptionAmount = rentData.exemptionAmount, + cryptoCurrency = currencyStatus.currency, + ) } else -> null } @@ -159,7 +159,11 @@ internal class DefaultCurrencyChecksRepository( return when { balanceAfterTransaction.isZero() -> null balanceAfterTransaction < rentData.exemptionAmount -> { - CryptoCurrencyWarning.Rent(rentData.rent, rentData.exemptionAmount) + CryptoCurrencyWarning.Rent( + rent = rentData.rent, + exemptionAmount = rentData.exemptionAmount, + cryptoCurrency = currencyStatus.currency, + ) } else -> null } diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt index a00521acac..cb4b78542e 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/warnings/CryptoCurrencyWarning.kt @@ -39,8 +39,13 @@ sealed class CryptoCurrencyWarning { * @param rent Amount that will be charged in overtime if the blockchain does not have an amount greater than * the [exemptionAmount] * @param exemptionAmount Amount that should be on the blockchain balance not to pay rent + * @param cryptoCurrency Currency in which the rent is charged */ - data class Rent(val rent: BigDecimal, val exemptionAmount: BigDecimal) : CryptoCurrencyWarning() + data class Rent( + val rent: BigDecimal, + val exemptionAmount: BigDecimal, + val cryptoCurrency: CryptoCurrency, + ) : CryptoCurrencyWarning() data class SwapPromo( val promoId: PromoId, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index b124a9b02e..b1ae6761a1 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -7,6 +7,8 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.networkIconResId import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.format.bigdecimal.crypto +import com.tangem.core.ui.format.bigdecimal.format import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning @@ -136,7 +138,12 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { title = TextReference.Res(R.string.warning_rent_fee_title), subtitle = TextReference.Res( id = R.string.warning_solana_rent_fee_message, - formatArgs = wrappedList(rentInfo.rent, rentInfo.exemptionAmount), + formatArgs = wrappedList( + rentInfo.rent, + rentInfo.exemptionAmount.format { + crypto(rentInfo.cryptoCurrency) + }, + ), ), onCloseClick = onCloseClick, ) From db03d6d86022c05afbd528e126c7c1da7494860f Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Oct 2025 19:47:53 +0500 Subject: [PATCH 19/25] Updated on 2026-08-14 --- .../MockUpdateWalletManagerResultFactory.kt | 4 +- .../ui/tokens/TokenItemStateConverter.kt | 5 +- .../local/network/entity/NetworkStatusDM.kt | 1 + .../NetworkYieldSupplyStatusConverter.kt | 2 + .../NetworkStatusDataModelConverterTest.kt | 2 + .../NetworkYieldSupplyStatusConverterTest.kt | 3 ++ .../SimpleNetworkStatusConverterTest.kt | 2 + .../utils/NetworkStatusFactoryTest.kt | 1 + .../DefaultCurrencyChecksRepository.kt | 2 +- .../UpdateWalletManagerResultFactory.kt | 1 + ...DefaultYieldSupplyTransactionRepository.kt | 50 ++++++++++++------- .../currency/CryptoCurrencyExtensions.kt | 16 ++++++ .../models/yield/supply/YieldSupplyStatus.kt | 5 +- .../YieldSupplyTransactionRepository.kt | 2 +- .../YieldSupplyGetProtocolBalanceUseCase.kt | 2 +- .../active/model/YieldSupplyActiveModel.kt | 9 ++-- gradle/tangem_dependencies.toml | 2 +- 17 files changed, 80 insertions(+), 29 deletions(-) diff --git a/common/test/src/main/java/com/tangem/common/test/domain/walletmanager/MockUpdateWalletManagerResultFactory.kt b/common/test/src/main/java/com/tangem/common/test/domain/walletmanager/MockUpdateWalletManagerResultFactory.kt index 7abe7f7217..645598c7cd 100644 --- a/common/test/src/main/java/com/tangem/common/test/domain/walletmanager/MockUpdateWalletManagerResultFactory.kt +++ b/common/test/src/main/java/com/tangem/common/test/domain/walletmanager/MockUpdateWalletManagerResultFactory.kt @@ -67,10 +67,12 @@ class MockUpdateWalletManagerResultFactory { value = BigDecimal.ONE, currencyRawId = CryptoCurrency.RawID("token"), contractAddress = "0xTokenAddress", - yieldSupplyStatus = YieldSupplyStatus( + yieldSupplyStatus = + YieldSupplyStatus( isActive = true, isInitialized = true, isAllowedToSpend = false, + effectiveProtocolBalance = BigDecimal.ONE, ), ), ), diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index 9b7763de26..a5d7fe36e0 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -19,6 +19,7 @@ import com.tangem.domain.models.StatusSource import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.yieldSupplyKey +import com.tangem.domain.models.currency.yieldSupplyNotAllAmountSupplied import com.tangem.domain.models.staking.YieldBalance import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance import com.tangem.utils.StringsSigns.DASH_SIGN @@ -246,7 +247,9 @@ class TokenItemStateConverter( isFlickering = status.value.isFlickering(), icons = buildList { if (status.value.yieldSupplyStatus?.isActive == true && - status.value.yieldSupplyStatus?.isAllowedToSpend == false) { + status.value.yieldSupplyStatus?.isAllowedToSpend == false || + status.yieldSupplyNotAllAmountSupplied() + ) { TokenItemState.FiatAmountState.Content.IconUM( iconRes = R.drawable.ic_alert_triangle_20, tint = IconTint.Warning, diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/network/entity/NetworkStatusDM.kt b/core/datasource/src/main/java/com/tangem/datasource/local/network/entity/NetworkStatusDM.kt index 37dfea2ebb..8d7cd539c0 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/network/entity/NetworkStatusDM.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/network/entity/NetworkStatusDM.kt @@ -119,6 +119,7 @@ sealed interface NetworkStatusDM { @Json(name = "is_active") val isActive: Boolean, @Json(name = "is_initialized") val isInitialized: Boolean, @Json(name = "is_allowed_to_spend") val isAllowedToSpend: Boolean, + @Json(name = "effective_protocol_balance") val effectiveProtocolBalance: BigDecimal? = null, ) @JsonClass(generateAdapter = true) diff --git a/data/networks/src/main/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverter.kt b/data/networks/src/main/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverter.kt index 4dddbb56bc..8f8c4c7792 100644 --- a/data/networks/src/main/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverter.kt +++ b/data/networks/src/main/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverter.kt @@ -23,6 +23,7 @@ internal class NetworkYieldSupplyStatusConverter( isActive = it.isActive, isInitialized = it.isInitialized, isAllowedToSpend = it.isAllowedToSpend, + effectiveProtocolBalance = it.effectiveProtocolBalance, ) id to status @@ -38,6 +39,7 @@ internal class NetworkYieldSupplyStatusConverter( isActive = yieldSupplyStatus.isActive, isInitialized = yieldSupplyStatus.isInitialized, isAllowedToSpend = yieldSupplyStatus.isAllowedToSpend, + effectiveProtocolBalance = yieldSupplyStatus.effectiveProtocolBalance, ) } } diff --git a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkStatusDataModelConverterTest.kt b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkStatusDataModelConverterTest.kt index 0d16b0ac0a..c42a8a9a9d 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkStatusDataModelConverterTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkStatusDataModelConverterTest.kt @@ -62,6 +62,7 @@ internal class NetworkStatusDataModelConverterTest { isActive = false, isInitialized = false, isAllowedToSpend = false, + effectiveProtocolBalance = BigDecimal.ONE, ), ID( prefix = Prefix.COIN_PREFIX, @@ -94,6 +95,7 @@ internal class NetworkStatusDataModelConverterTest { isActive = false, isInitialized = false, isAllowedToSpend = false, + effectiveProtocolBalance = BigDecimal.ONE, ), ), ), diff --git a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverterTest.kt b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverterTest.kt index 1c21deff5d..af36491079 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverterTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/converters/NetworkYieldSupplyStatusConverterTest.kt @@ -8,6 +8,7 @@ import com.tangem.domain.models.network.Network import com.tangem.domain.models.yield.supply.YieldSupplyStatus import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance +import java.math.BigDecimal @TestInstance(TestInstance.Lifecycle.PER_CLASS) internal class NetworkYieldSupplyStatusConverterTest { @@ -21,6 +22,7 @@ internal class NetworkYieldSupplyStatusConverterTest { isActive = true, isInitialized = true, isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.ONE, ) @Test @@ -70,6 +72,7 @@ internal class NetworkYieldSupplyStatusConverterTest { isActive = true, isInitialized = true, isAllowedToSpend = true, + effectiveProtocolBalance = BigDecimal.ONE, ) } } \ No newline at end of file diff --git a/data/networks/src/test/java/com/tangem/data/networks/converters/SimpleNetworkStatusConverterTest.kt b/data/networks/src/test/java/com/tangem/data/networks/converters/SimpleNetworkStatusConverterTest.kt index 2ca9c9e06c..5e7375de67 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/converters/SimpleNetworkStatusConverterTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/converters/SimpleNetworkStatusConverterTest.kt @@ -68,6 +68,7 @@ internal class SimpleNetworkStatusConverterTest { isActive = false, isInitialized = false, isAllowedToSpend = false, + effectiveProtocolBalance = BigDecimal.ONE, ), ), ), @@ -103,6 +104,7 @@ internal class SimpleNetworkStatusConverterTest { isActive = false, isInitialized = false, isAllowedToSpend = false, + effectiveProtocolBalance = BigDecimal.ONE, ), ), source = StatusSource.CACHE, diff --git a/data/networks/src/test/java/com/tangem/data/networks/utils/NetworkStatusFactoryTest.kt b/data/networks/src/test/java/com/tangem/data/networks/utils/NetworkStatusFactoryTest.kt index 2350f4e822..caa0cf1566 100644 --- a/data/networks/src/test/java/com/tangem/data/networks/utils/NetworkStatusFactoryTest.kt +++ b/data/networks/src/test/java/com/tangem/data/networks/utils/NetworkStatusFactoryTest.kt @@ -272,6 +272,7 @@ internal class NetworkStatusFactoryTest(private val model: Model) { isActive = false, isInitialized = false, isAllowedToSpend = false, + effectiveProtocolBalance = BigDecimal.ONE, ), ), ), diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index 485f1afd18..ff39057e0a 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -181,7 +181,7 @@ internal class DefaultCurrencyChecksRepository( blockchain = token.network.toBlockchain(), derivationPath = token.network.derivationPath.value, ) ?: error("Wallet manager not found") - walletManager.getProtocolBalance( + walletManager.getEffectiveProtocolBalance( token = Token( symbol = token.symbol, contractAddress = token.contractAddress, diff --git a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/UpdateWalletManagerResultFactory.kt b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/UpdateWalletManagerResultFactory.kt index 55054ba3c8..a8d65edb01 100644 --- a/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/UpdateWalletManagerResultFactory.kt +++ b/data/wallet-manager/src/main/java/com/tangem/data/walletmanager/UpdateWalletManagerResultFactory.kt @@ -128,6 +128,7 @@ internal class UpdateWalletManagerResultFactory { isActive = type.isActive, isInitialized = type.isInitialized, isAllowedToSpend = type.isAllowedToSpend, + effectiveProtocolBalance = type.effectiveProtocolBalance, ), ) } diff --git a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt index 12c2a98044..c761bd1160 100644 --- a/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt +++ b/data/yield-supply/src/main/java/com/tangem/data/yield/supply/DefaultYieldSupplyTransactionRepository.kt @@ -91,24 +91,26 @@ internal class DefaultYieldSupplyTransactionRepository( ) } - override suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal? = - withContext(dispatchers.io) { - require(cryptoCurrency is CryptoCurrency.Token) - runCatching { - val walletManager = walletManagersFacade.getOrCreateWalletManager( - userWalletId = userWalletId, - blockchain = cryptoCurrency.network.toBlockchain(), - derivationPath = cryptoCurrency.network.derivationPath.value, - ) ?: error("Wallet manager not found") - walletManager.getProtocolBalance( - token = Token( - symbol = cryptoCurrency.symbol, - contractAddress = cryptoCurrency.contractAddress, - decimals = cryptoCurrency.decimals, - ), - ) - }.onFailure(Timber::e).getOrThrow() - } + override suspend fun getEffectiveProtocolBalance( + userWalletId: UserWalletId, + cryptoCurrency: CryptoCurrency, + ): BigDecimal? = withContext(dispatchers.io) { + require(cryptoCurrency is CryptoCurrency.Token) + runCatching { + val walletManager = walletManagersFacade.getOrCreateWalletManager( + userWalletId = userWalletId, + blockchain = cryptoCurrency.network.toBlockchain(), + derivationPath = cryptoCurrency.network.derivationPath.value, + ) ?: error("Wallet manager not found") + walletManager.getEffectiveProtocolBalance( + token = Token( + symbol = cryptoCurrency.symbol, + contractAddress = cryptoCurrency.contractAddress, + decimals = cryptoCurrency.decimals, + ), + ) + }.onFailure(Timber::e).getOrThrow() + } @Suppress("LongParameterList") private suspend fun buildEnterTransactions( @@ -222,6 +224,17 @@ internal class DefaultYieldSupplyTransactionRepository( ): YieldSupplyStatus? = withContext(dispatchers.io) { runCatching { val sdkSupplyStatus = walletManager.getYieldSupplyStatus(cryptoCurrency.contractAddress) + val protocolBalance = if (sdkSupplyStatus?.isActive == true) { + walletManager.getEffectiveProtocolBalance( + token = Token( + symbol = cryptoCurrency.symbol, + contractAddress = cryptoCurrency.contractAddress, + decimals = cryptoCurrency.decimals, + ), + ) + } else { + null + } val isAllowedToSpend = walletManager.isAllowedToSpend( Token( symbol = cryptoCurrency.symbol, @@ -234,6 +247,7 @@ internal class DefaultYieldSupplyTransactionRepository( isActive = sdkSupplyStatus?.isActive == true, isInitialized = sdkSupplyStatus?.isInitialized == true, isAllowedToSpend = isAllowedToSpend, + effectiveProtocolBalance = protocolBalance, ) }.onFailure(Timber::e).getOrNull() } 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 b3fb460bdb..532c2f1621 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 @@ -2,4 +2,20 @@ package com.tangem.domain.models.currency fun CryptoCurrency.Token.yieldSupplyKey(): String { return "${network.backendId}_$contractAddress" +} + +fun CryptoCurrencyStatus.yieldSupplyNotAllAmountSupplied(): Boolean { + if (this.currency !is CryptoCurrency.Token) return false + + val supplyStatus = this.value.yieldSupplyStatus + if (supplyStatus?.isActive != true) return false + + val protocolBalance = supplyStatus.effectiveProtocolBalance + val amount = this.value.amount + + return if (protocolBalance != null && amount != null) { + amount > protocolBalance + } else { + false + } } \ No newline at end of file diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/yield/supply/YieldSupplyStatus.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/yield/supply/YieldSupplyStatus.kt index 3a8165a366..138dc5ad5b 100644 --- a/domain/models/src/main/kotlin/com/tangem/domain/models/yield/supply/YieldSupplyStatus.kt +++ b/domain/models/src/main/kotlin/com/tangem/domain/models/yield/supply/YieldSupplyStatus.kt @@ -1,5 +1,6 @@ package com.tangem.domain.models.yield.supply +import com.tangem.domain.models.serialization.SerializedBigDecimal import kotlinx.serialization.Serializable /** @@ -11,10 +12,12 @@ import kotlinx.serialization.Serializable * @property isActive Indicates if the yield token is currently active. * @property isInitialized Indicates if the yield token has been initialized. * @property isAllowedToSpend Indicates if spending from the yield module is permitted. - */ + * @property effectiveProtocolBalance Indicates the balance (excluding service fee) + * */ @Serializable data class YieldSupplyStatus( val isActive: Boolean, val isInitialized: Boolean, val isAllowedToSpend: Boolean, + val effectiveProtocolBalance: SerializedBigDecimal?, ) \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyTransactionRepository.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyTransactionRepository.kt index 68982a6e66..dccf5c9e1d 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyTransactionRepository.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/YieldSupplyTransactionRepository.kt @@ -23,5 +23,5 @@ interface YieldSupplyTransactionRepository { suspend fun getYieldContractAddress(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): String? - suspend fun getProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal? + suspend fun getEffectiveProtocolBalance(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): BigDecimal? } \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetProtocolBalanceUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetProtocolBalanceUseCase.kt index a36158a7b6..3bc5de3b71 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetProtocolBalanceUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetProtocolBalanceUseCase.kt @@ -19,7 +19,7 @@ class YieldSupplyGetProtocolBalanceUseCase( ): Either = Either.catch { requireNotNull(cryptoCurrency as CryptoCurrency.Token) - yieldSupplyTransactionRepository.getProtocolBalance( + yieldSupplyTransactionRepository.getEffectiveProtocolBalance( userWalletId = userWalletId, cryptoCurrency = cryptoCurrency, ) diff --git a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt index 268470e09f..a74719003c 100644 --- a/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt +++ b/features/yield-supply/impl/src/main/java/com/tangem/features/yield/supply/impl/subcomponents/active/model/YieldSupplyActiveModel.kt @@ -102,10 +102,11 @@ internal class YieldSupplyActiveModel @Inject constructor( private fun subscribeOnCurrencyUpdates() { cryptoCurrencyStatusFlow.onEach { cryptoCurrencyStatus -> - val protocolBalance = yieldSupplyGetProtocolBalanceUseCase( - userWalletId = params.userWallet.walletId, - cryptoCurrency = cryptoCurrency, - ).getOrNull() + val protocolBalance = cryptoCurrencyStatus.value.yieldSupplyStatus?.effectiveProtocolBalance + ?: yieldSupplyGetProtocolBalanceUseCase( + userWalletId = params.userWallet.walletId, + cryptoCurrency = cryptoCurrency, + ).getOrNull() val approvalNotification = if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isAllowedToSpend != true) { NotificationUM.Error( diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index f3ceb42d40..1cd201c597 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "releases-5.30-1283" +tangemBlockchainSdk = "releases-5.30-1285" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "releases-5.30-567" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ From b2bedf905e2c4c61bdaabeb9974ab169970a873a Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Oct 2025 13:23:37 +0300 Subject: [PATCH 20/25] Updated on 2026-08-14 --- .../com/tangem/core/ui/components/atoms/text/BoundCounter.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt index 6a97038b3d..7bda686f02 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/atoms/text/BoundCounter.kt @@ -31,7 +31,9 @@ internal class BoundCounter( } fun addNextChar() { - string += text[charPosition(string.count())] + val nextIndex = charPosition(string.count()) + if (nextIndex < 0 || nextIndex >= text.length) return + string += text[nextIndex] width += nextCharWidth() _nextCharWidth = null } From fd08217cb6e1d89a5a7d26df02d00d2fdf5e93a7 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Oct 2025 13:56:57 +0300 Subject: [PATCH 21/25] Updated on 2026-08-14 --- .../components/common/WcNavigationUtils.kt | 4 ++-- .../model/WcSendTransactionModel.kt | 18 ++++++++---------- .../transaction/routes/WcTransactionRoutes.kt | 4 +--- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt index dd7da71fa0..7a34577605 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/components/common/WcNavigationUtils.kt @@ -10,8 +10,8 @@ import com.tangem.features.send.v2.api.params.FeeSelectorParams.FeeSelectorDetai import com.tangem.features.walletconnect.connections.components.AlertsComponentV2 import com.tangem.features.walletconnect.connections.utils.WcAlertsFactory.createCommonTransactionAppInfoAlertUM import com.tangem.features.walletconnect.transaction.components.send.WcCustomAllowanceComponent -import com.tangem.features.walletconnect.transaction.components.send.WcSendingProcessComponent import com.tangem.features.walletconnect.transaction.components.send.WcSendMultipleTransactionsComponent +import com.tangem.features.walletconnect.transaction.components.send.WcSendingProcessComponent import com.tangem.features.walletconnect.transaction.entity.common.WcCommonTransactionModel import com.tangem.features.walletconnect.transaction.model.WcSendTransactionModel import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes @@ -65,7 +65,7 @@ internal fun getWcCommonScreen( WcSendMultipleTransactionsComponent( appComponentContext = appComponentContext, model = model, - onConfirm = config.onConfirm, + onConfirm = { model.onMultiTransactionConfirm() }, ) } WcTransactionRoutes.TransactionProcess -> { diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt index 8b201362c2..9e386e1956 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/model/WcSendTransactionModel.kt @@ -148,7 +148,7 @@ internal class WcSendTransactionModel @Inject constructor( sign = { if (isMultipleSignRequired(useCase)) { analytics.send(SolanaLargeTransaction(useCase.rawSdkRequest.dAppMetaData.name)) - openMultipleTransaction(useCase) + openMultipleTransaction() } else { useCase.sign() } @@ -173,15 +173,13 @@ internal class WcSendTransactionModel @Inject constructor( } } - private fun openMultipleTransaction(useCase: WcSignUseCase<*>) { - stackNavigation.pushNew( - WcTransactionRoutes.MultipleTransactions( - onConfirm = { - useCase.sign() - stackNavigation.pushNew(WcTransactionRoutes.TransactionProcess) - }, - ), - ) + private fun openMultipleTransaction() { + stackNavigation.pushNew(WcTransactionRoutes.MultipleTransactions) + } + + fun onMultiTransactionConfirm() { + useCase.sign() + stackNavigation.pushNew(WcTransactionRoutes.TransactionProcess) } /** diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcTransactionRoutes.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcTransactionRoutes.kt index cc1d9513d7..40b2a98e49 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcTransactionRoutes.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/transaction/routes/WcTransactionRoutes.kt @@ -44,9 +44,7 @@ internal sealed class WcTransactionRoutes : TangemBottomSheetConfigContent, Rout } @Serializable - data class MultipleTransactions( - val onConfirm: () -> Unit, - ) : WcTransactionRoutes() + data object MultipleTransactions : WcTransactionRoutes() @Serializable data object TransactionProcess : WcTransactionRoutes() From 660d00ffb244ab6e83f532e0d572993ea4535209 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Oct 2025 14:10:26 +0300 Subject: [PATCH 22/25] Updated on 2026-08-14 --- .../connections/model/WcPairModel.kt | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt index 5dab9f0577..7cc617b264 100644 --- a/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt +++ b/features/walletconnect/impl/src/main/kotlin/com/tangem/features/walletconnect/connections/model/WcPairModel.kt @@ -22,7 +22,11 @@ import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.models.wallet.isLocked import com.tangem.domain.models.wallet.isMultiCurrency import com.tangem.domain.walletconnect.WcAnalyticEvents -import com.tangem.domain.walletconnect.model.* +import com.tangem.domain.walletconnect.model.WcPairError +import com.tangem.domain.walletconnect.model.WcPairError.Unknown +import com.tangem.domain.walletconnect.model.WcPairRequest +import com.tangem.domain.walletconnect.model.WcSessionApprove +import com.tangem.domain.walletconnect.model.WcSessionProposal import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData import com.tangem.domain.walletconnect.usecase.pair.WcPairState import com.tangem.domain.walletconnect.usecase.pair.WcPairUseCase @@ -114,35 +118,40 @@ internal class WcPairModel @Inject constructor( val availableWallets = pairState.dAppSession.proposalNetwork.keys .filter { !it.isLocked && it.isMultiCurrency } sessionProposal = pairState.dAppSession - proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWalletFlow.value) - additionallyEnabledNetworks = proposalNetwork.available - appInfoUiState.transformerUpdate( - WcAppInfoTransformer( - dAppSession = sessionProposal, - dAppVerifiedStateConverter = dAppVerifiedStateConverter, - onDismiss = ::rejectPairing, - onConnect = ::onConnect, - onWalletClick = { - stackNavigation.pushNew( - WcAppInfoRoutes.SelectWallet(selectedUserWalletFlow.value.walletId), - ) - }.takeIf { availableWallets.size >= WC_WALLETS_SELECTOR_MIN_COUNT }, - onNetworksClick = { - stackNavigation.pushNew( - WcAppInfoRoutes.SelectNetworks( - missingRequiredNetworks = proposalNetwork.missingRequired, - requiredNetworks = proposalNetwork.required, - availableNetworks = proposalNetwork.available, - enabledAvailableNetworks = additionallyEnabledNetworks, - notAddedNetworks = proposalNetwork.notAdded, - ), - ) - }, - userWallet = selectedUserWalletFlow.value, - proposalNetwork = proposalNetwork, - additionallyEnabledNetworks = additionallyEnabledNetworks, - ), - ) + val foundNetwork = sessionProposal.proposalNetwork[selectedUserWalletFlow.value] + if (foundNetwork == null) { + processError(Unknown("Selected wallet not found")) + } else { + proposalNetwork = foundNetwork + additionallyEnabledNetworks = proposalNetwork.available + appInfoUiState.transformerUpdate( + WcAppInfoTransformer( + dAppSession = sessionProposal, + dAppVerifiedStateConverter = dAppVerifiedStateConverter, + onDismiss = ::rejectPairing, + onConnect = ::onConnect, + onWalletClick = { + stackNavigation.pushNew( + WcAppInfoRoutes.SelectWallet(selectedUserWalletFlow.value.walletId), + ) + }.takeIf { availableWallets.size >= WC_WALLETS_SELECTOR_MIN_COUNT }, + onNetworksClick = { + stackNavigation.pushNew( + WcAppInfoRoutes.SelectNetworks( + missingRequiredNetworks = proposalNetwork.missingRequired, + requiredNetworks = proposalNetwork.required, + availableNetworks = proposalNetwork.available, + enabledAvailableNetworks = additionallyEnabledNetworks, + notAddedNetworks = proposalNetwork.notAdded, + ), + ) + }, + userWallet = selectedUserWalletFlow.value, + proposalNetwork = proposalNetwork, + additionallyEnabledNetworks = additionallyEnabledNetworks, + ), + ) + } } } } @@ -234,7 +243,7 @@ internal class WcPairModel @Inject constructor( override fun onWalletSelected(userWalletId: UserWalletId) { val selectedUserWallet = sessionProposal.proposalNetwork.keys.first { it.walletId == userWalletId } - proposalNetwork = sessionProposal.proposalNetwork.getValue(selectedUserWallet) + proposalNetwork = sessionProposal.proposalNetwork[selectedUserWallet] ?: return selectedUserWalletFlow.update { selectedUserWallet } additionallyEnabledNetworks = proposalNetwork.available appInfoUiState.transformerUpdate( From c49b3a91768c07039bd46e9c770680505ba4d875 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Oct 2025 15:36:13 +0300 Subject: [PATCH 23/25] Updated on 2026-08-14 --- .../data/markets/converters/TokenMarketListConverter.kt | 1 + .../main/kotlin/com/tangem/domain/markets/TokenMarket.kt | 1 + .../impl/model/converters/MarketsTokenItemConverter.kt | 1 + .../impl/ui/components/MarketsListLazyColumn.kt | 4 ++-- .../ui/preview/MarketChartListItemPreviewDataProvider.kt | 6 ++++++ .../markets/tokenlist/impl/ui/state/MarketsListItemUM.kt | 9 +++++++++ 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketListConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketListConverter.kt index f8e13d53fc..067f886293 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketListConverter.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketListConverter.kt @@ -41,6 +41,7 @@ internal object TokenMarketListConverter : Converter { items( items = state.items, - key = { it.id.value + TOKEN_LAZY_LIST_ID_SEPARATOR + it.marketCap.toString() }, + key = { it.getComposeKey() }, ) { item -> MarketsListItem( model = item, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt index 3371a2f40d..16d1279d95 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/preview/MarketChartListItemPreviewDataProvider.kt @@ -27,6 +27,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet ), isUnder100kMarketCap = false, stakingRate = stringReference("APY 12.34%"), + updateTimestamp = 0, ), MarketsListItemUM( id = CryptoCurrency.RawID("1"), @@ -41,6 +42,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet chartData = null, isUnder100kMarketCap = false, stakingRate = stringReference("APY 12.34%"), + updateTimestamp = 0, ), MarketsListItemUM( id = CryptoCurrency.RawID("1"), @@ -57,6 +59,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet ), isUnder100kMarketCap = false, stakingRate = stringReference("APY 12.34%"), + updateTimestamp = 0, ), MarketsListItemUM( id = CryptoCurrency.RawID("1"), @@ -73,6 +76,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet ), isUnder100kMarketCap = false, stakingRate = stringReference("APY 12.34%"), + updateTimestamp = 0, ), MarketsListItemUM( id = CryptoCurrency.RawID("1"), @@ -89,6 +93,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet ), isUnder100kMarketCap = false, stakingRate = stringReference("APY 12.34%"), + updateTimestamp = 0, ), MarketsListItemUM( id = CryptoCurrency.RawID("1"), @@ -105,6 +110,7 @@ internal class MarketChartListItemPreviewDataProvider : CollectionPreviewParamet ), isUnder100kMarketCap = false, stakingRate = stringReference("APY 12.34%"), + updateTimestamp = 0, ), ), ) \ No newline at end of file diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt index 5f29995bff..a83ea2e7d6 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/state/MarketsListItemUM.kt @@ -21,6 +21,7 @@ data class MarketsListItemUM( val chartData: MarketChartRawData?, val isUnder100kMarketCap: Boolean, val stakingRate: TextReference?, + val updateTimestamp: Long?, ) { val chartType: MarketChartLook.Type = when (trendType) { PriceChangeType.UP -> MarketChartLook.Type.Growing @@ -33,4 +34,12 @@ data class MarketsListItemUM( val text: String, val changeType: PriceChangeType? = null, ) + + fun getComposeKey(): String { + return id.value + TOKEN_LAZY_LIST_ID_SEPARATOR + marketCap.toString() + updateTimestamp + } + + companion object { + const val TOKEN_LAZY_LIST_ID_SEPARATOR = "@" + } } \ No newline at end of file From 8467ada25da28aa632da7811f9219013aebe38f6 Mon Sep 17 00:00:00 2001 From: Tangem Date: Sun, 19 Oct 2025 12:38:07 +0200 Subject: [PATCH 24/25] Updated on 2026-08-14 --- .../tap/di/domain/StakingDomainModule.kt | 7 ++ .../ui/tokens/TokenItemStateConverter.kt | 68 ++++++++++++------- .../components/token/internal/TokenTitle.kt | 24 +++---- .../bigdecimal/BigDecimalPercentFormat.kt | 13 +++- .../data/staking/DefaultStakingRepository.kt | 2 +- .../staking/repositories/StakingRepository.kt | 2 + .../staking/usecase/StakingApyFlowUseCase.kt | 37 ++++++++++ .../implementors/MultiWalletContentLoader.kt | 3 + .../MultiWalletContentLoaderFactory.kt | 3 + .../SingleWalletWithTokenContentLoader.kt | 3 + ...ngleWalletWithTokenContentLoaderFactory.kt | 3 + .../transformers/SetTokenListTransformer.kt | 5 +- .../converter/TokenListStateConverter.kt | 7 +- .../subscribers/BasicTokenListSubscriber.kt | 25 +++++-- .../MultiWalletTokenListSubscriber.kt | 2 + .../SingleWalletWithTokenListSubscriber.kt | 2 + 16 files changed, 156 insertions(+), 50 deletions(-) create mode 100644 domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index f98c7d2bd2..92e80ff6db 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -6,6 +6,7 @@ import com.tangem.domain.staking.repositories.StakingErrorResolver import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.staking.repositories.StakingTransactionHashRepository import com.tangem.domain.staking.single.SingleYieldBalanceFetcher +import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.walletmanager.WalletManagersFacade import dagger.Module import dagger.Provides @@ -216,4 +217,10 @@ internal object StakingDomainModule { fun provideStakingIdFactory(walletManagersFacade: WalletManagersFacade): StakingIdFactory { return StakingIdFactory(walletManagersFacade = walletManagersFacade) } + + @Provides + @Singleton + fun provideStakingApyFlowUseCase(stakingRepository: StakingRepository): StakingApyFlowUseCase { + return StakingApyFlowUseCase(stakingRepository) + } } \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt index a5d7fe36e0..0c5042c6d8 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/tokens/TokenItemStateConverter.kt @@ -7,6 +7,7 @@ import com.tangem.core.ui.components.icons.IconTint import com.tangem.core.ui.components.marketprice.PriceChangeType import com.tangem.core.ui.components.marketprice.utils.PriceChangeConverter import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList @@ -16,15 +17,14 @@ import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.percent import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.StatusSource -import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.yieldSupplyKey import com.tangem.domain.models.currency.yieldSupplyNotAllAmountSupplied import com.tangem.domain.models.staking.YieldBalance import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.converter.Converter -import com.tangem.utils.extensions.isZero import com.tangem.utils.extensions.orZero import kotlinx.collections.immutable.toImmutableList import java.math.BigDecimal @@ -40,12 +40,13 @@ import java.math.BigDecimal */ class TokenItemStateConverter( private val appCurrency: AppCurrency, - private val apyMap: Map = emptyMap(), + private val yieldModuleApyMap: Map = emptyMap(), + private val stakingApyMap: Map = emptyMap(), private val iconStateProvider: (CryptoCurrencyStatus) -> CurrencyIconState = { CryptoCurrencyToIconStateConverter().convert(it) }, private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { - createTitleState(it, apyMap) + createTitleState(it, yieldModuleApyMap, stakingApyMap) }, private val subtitleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.SubtitleState? = { createSubtitleState(it, appCurrency) @@ -154,7 +155,8 @@ class TokenItemStateConverter( private fun createTitleState( currencyStatus: CryptoCurrencyStatus, - apyMap: Map, + yieldModuleApyMap: Map, + stakingApyMap: Map, ): TokenItemState.TitleState { return when (val value = currencyStatus.value) { is CryptoCurrencyStatus.Loading, @@ -169,13 +171,11 @@ class TokenItemStateConverter( is CryptoCurrencyStatus.NoQuote, is CryptoCurrencyStatus.NoAccount, -> { - val earnApyText = resolveEarnApy(currencyStatus, apyMap)?.let { apy -> - resourceReference( - R.string.yield_module_earn_badge, - wrappedList(apy), - ) - } - val isActive = currencyStatus.value.yieldSupplyStatus?.isActive ?: false + val (earnApyText, isActive) = resolveEarnApy( + cryptoCurrencyStatus = currencyStatus, + yieldModuleApyMap = yieldModuleApyMap, + stakingApyMap = stakingApyMap, + ) TokenItemState.TitleState.Content( text = stringReference(currencyStatus.currency.name), hasPending = value.hasCurrentNetworkTransactions, @@ -186,12 +186,36 @@ class TokenItemStateConverter( } } - private fun resolveEarnApy(cryptoCurrencyStatus: CryptoCurrencyStatus, apyMap: Map): String? { - if (apyMap.isEmpty()) return null + private fun resolveEarnApy( + cryptoCurrencyStatus: CryptoCurrencyStatus, + yieldModuleApyMap: Map, + stakingApyMap: Map, + ): Pair { + val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token + if (token != null && yieldModuleApyMap.isNotEmpty()) { + val yieldSupplyApy = yieldModuleApyMap[token.yieldSupplyKey()] + if (yieldSupplyApy != null) { + val isActive = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive ?: false + return resourceReference( + R.string.yield_module_earn_badge, + wrappedList(yieldSupplyApy), + ) to isActive + } + } - val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return null + if (stakingApyMap.isNotEmpty()) { + val stakingKey = cryptoCurrencyStatus.currency.stakingKey() + val stakingApy = stakingApyMap[stakingKey]?.format { percent(withPercentSign = false) } + if (stakingApy != null) { + val hasStakedBalance = cryptoCurrencyStatus.value.yieldBalance is YieldBalance.Data + return resourceReference( + R.string.yield_module_earn_badge, + wrappedList(stakingApy), + ) to hasStakedBalance + } + } - return apyMap[token.yieldSupplyKey()] + return null to false } private fun createSubtitleState( @@ -255,14 +279,6 @@ class TokenItemStateConverter( tint = IconTint.Warning, ).let(::add) } - if (!status.getStakedBalance().isZero()) { - add( - TokenItemState.FiatAmountState.Content.IconUM( - iconRes = R.drawable.ic_staking_24, - tint = IconTint.Accent, - ), - ) - } if (status.value.sources.total == StatusSource.ONLY_CACHE) { add( TokenItemState.FiatAmountState.Content.IconUM( @@ -308,5 +324,9 @@ class TokenItemStateConverter( } fun CryptoCurrencyStatus.Value.isFlickering(): Boolean = sources.total == StatusSource.CACHE + + private fun CryptoCurrency.stakingKey(): String { + return "${network.backendId}_$symbol" + } } } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt index 685f7696ee..f762f3b848 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/TokenTitle.kt @@ -3,11 +3,7 @@ package com.tangem.core.ui.components.token.internal import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -85,14 +81,14 @@ private fun CurrencyNameText(name: String, isAvailable: Boolean, modifier: Modif private fun YieldSupplyApyLabel(apy: TextReference?, isActive: Boolean, modifier: Modifier = Modifier) { AnimatedVisibility(visible = apy != null, modifier = modifier) { Box( - modifier = if (isActive) { - modifier.background( - color = TangemTheme.colors.text.accent.copy(alpha = 0.1f), - shape = TangemTheme.shapes.roundedCornersSmall2, - ) - } else { - modifier - }, + modifier = Modifier.background( + color = if (isActive) { + TangemTheme.colors.text.accent.copy(alpha = 0.1f) + } else { + TangemTheme.colors.control.unchecked + }, + shape = TangemTheme.shapes.roundedCornersSmall2, + ), ) { Text( text = apy?.resolveReference().orEmpty(), @@ -100,7 +96,7 @@ private fun YieldSupplyApyLabel(apy: TextReference?, isActive: Boolean, modifier color = if (isActive) { TangemTheme.colors.text.accent } else { - TangemTheme.colors.text.tertiary + TangemTheme.colors.text.secondary }, modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormat.kt b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormat.kt index 84e5870e68..8d22eff0a6 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormat.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/format/bigdecimal/BigDecimalPercentFormat.kt @@ -7,6 +7,7 @@ import java.util.Locale class BigDecimalPercentFormat( val isWithoutSign: Boolean = true, + val withPercentSign: Boolean = true, val locale: Locale = Locale.getDefault(), ) : BigDecimalFormat { override fun invoke(value: BigDecimal): String = default()(value) @@ -16,24 +17,30 @@ class BigDecimalPercentFormat( fun BigDecimalFormatScope.percent( withoutSign: Boolean = true, + withPercentSign: Boolean = true, locale: Locale = Locale.getDefault(), ): BigDecimalPercentFormat { return BigDecimalPercentFormat( isWithoutSign = withoutSign, locale = locale, + withPercentSign = withPercentSign, ) } // == Formatters == private fun BigDecimalPercentFormat.default(): BigDecimalFormat = BigDecimalFormat { value -> - val formatter = NumberFormat.getPercentInstance(locale).apply { + val formatter = if (withPercentSign) { + NumberFormat.getPercentInstance(locale) + } else { + NumberFormat.getNumberInstance(locale) + }.apply { maximumFractionDigits = 2 minimumFractionDigits = 2 roundingMode = RoundingMode.HALF_UP } - val valueToFormat = if (isWithoutSign) value.abs() else value + val finalValue = if (withPercentSign) valueToFormat else valueToFormat.movePointRight(2) - formatter.format(valueToFormat) + formatter.format(finalValue) } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt index e279943212..f6341c0957 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingRepository.kt @@ -500,7 +500,7 @@ internal class DefaultStakingRepository( ) } - private fun getEnabledYields(): Flow> { + override fun getEnabledYields(): Flow> { return stakingYieldsStore.get().map { YieldConverter.convertListIgnoreErrors( input = it, diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt index efbbae2c38..7e360eb780 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingRepository.kt @@ -22,6 +22,8 @@ interface StakingRepository { suspend fun fetchEnabledYields() + fun getEnabledYields(): Flow> + suspend fun getEntryInfo(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): StakingEntryInfo suspend fun getYield(cryptoCurrencyId: CryptoCurrency.ID, symbol: String): Yield diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt new file mode 100644 index 0000000000..5138158704 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/usecase/StakingApyFlowUseCase.kt @@ -0,0 +1,37 @@ +package com.tangem.domain.staking.usecase + +import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.repositories.StakingRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import java.math.BigDecimal + +/** + * Emits a map of APY values per currency for staking. + * + * Return map: + * - key: currency staking key (network.backendId + "_" + symbol) + * - value: APY as string + */ +class StakingApyFlowUseCase(private val stakingRepository: StakingRepository) { + + operator fun invoke(): Flow> { + return stakingRepository.getEnabledYields() + .map { yields -> + yields.associate { yield -> + val key = "${yield.token.network.name.lowercase()}_${yield.token.symbol}" + val apy = calculateApy(yield) + key to apy + } + } + } + + private fun calculateApy(yield: Yield): BigDecimal { + val rates = yield.validators.mapNotNull { it.rewardInfo?.rate } + return if (rates.isNotEmpty()) { + rates.maxOf { it } + } else { + yield.apy + } + } +} \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt index 0aee41a87b..5eb6975247 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoader.kt @@ -8,6 +8,7 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase @@ -44,6 +45,7 @@ internal class MultiWalletContentLoader( private val currenciesRepository: CurrenciesRepository, private val accountDependencies: AccountDependencies, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, + private val stakingApyFlowUseCase: StakingApyFlowUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -60,6 +62,7 @@ internal class MultiWalletContentLoader( runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, accountDependencies = accountDependencies, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, + stakingApyFlowUseCase = stakingApyFlowUseCase, ).let(::add) WalletNFTListSubscriber( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt index 2def028fe4..63d8447f57 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/MultiWalletContentLoaderFactory.kt @@ -8,6 +8,7 @@ import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.repository.CurrenciesRepository import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase @@ -42,6 +43,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( private val currenciesRepository: CurrenciesRepository, private val accountDependencies: AccountDependencies, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, + private val stakingApyFlowUseCase: StakingApyFlowUseCase, ) { fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader { @@ -65,6 +67,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor( currenciesRepository = currenciesRepository, accountDependencies = accountDependencies, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, + stakingApyFlowUseCase = stakingApyFlowUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt index 4bbae24b19..7b01ba0107 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoader.kt @@ -4,6 +4,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents @@ -34,6 +35,7 @@ internal class SingleWalletWithTokenContentLoader( private val getStoryContentUseCase: GetStoryContentUseCase, private val accountDependencies: AccountDependencies, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, + private val stakingApyFlowUseCase: StakingApyFlowUseCase, ) : WalletContentLoader(id = userWallet.walletId) { override fun create(): List { @@ -49,6 +51,7 @@ internal class SingleWalletWithTokenContentLoader( runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase, accountDependencies = accountDependencies, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, + stakingApyFlowUseCase = stakingApyFlowUseCase, ).let(::add) MultiWalletWarningsSubscriber( userWallet = userWallet, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt index 743e1bf3c8..4c178e1136 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletWithTokenContentLoaderFactory.kt @@ -5,6 +5,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.promo.GetStoryContentUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents @@ -35,6 +36,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( private val getStoryContentUseCase: GetStoryContentUseCase, private val accountDependencies: AccountDependencies, private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, + private val stakingApyFlowUseCase: StakingApyFlowUseCase, ) { fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader { @@ -54,6 +56,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor( shouldSaveUserWalletsUseCase = shouldSaveUserWalletsUseCase, accountDependencies = accountDependencies, yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase, + stakingApyFlowUseCase = stakingApyFlowUseCase, ) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt index 9b15ca02a0..5255206967 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListTransformer.kt @@ -10,6 +10,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.converte import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons import timber.log.Timber +import java.math.BigDecimal internal class SetTokenListTransformer( private val params: TokenConverterParams, @@ -17,6 +18,7 @@ internal class SetTokenListTransformer( private val appCurrency: AppCurrency, private val clickIntents: WalletClickIntents, private val yieldSupplyApyMap: Map = emptyMap(), + private val stakingApyMap: Map = emptyMap(), ) : WalletStateTransformer(userWallet.walletId) { override fun transform(prevState: WalletState): WalletState { @@ -59,7 +61,8 @@ internal class SetTokenListTransformer( selectedWallet = userWallet, appCurrency = appCurrency, clickIntents = clickIntents, - apyMap = yieldSupplyApyMap, + yieldModuleApyMap = yieldSupplyApyMap, + stakingApyMap = stakingApyMap, ).convert(value = this) } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt index eee1c4776a..9134b2295d 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TokenListStateConverter.kt @@ -27,6 +27,7 @@ import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.mutate import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toPersistentList +import java.math.BigDecimal import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState.OrganizeTokensButtonConfig as WalletOrganizeTokensButtonConfig internal class TokenListStateConverter( @@ -34,7 +35,8 @@ internal class TokenListStateConverter( private val params: TokenConverterParams, private val selectedWallet: UserWallet, private val clickIntents: WalletClickIntents, - private val apyMap: Map, + private val yieldModuleApyMap: Map, + private val stakingApyMap: Map, ) : Converter { private val onTokenClick: (accountId: AccountId?, currencyStatus: CryptoCurrencyStatus) -> Unit = @@ -49,7 +51,8 @@ internal class TokenListStateConverter( private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter( appCurrency = appCurrency, - apyMap = apyMap, + yieldModuleApyMap = yieldModuleApyMap, + stakingApyMap = stakingApyMap, onItemClick = { _, status -> onTokenClick(accountId, status) }, onItemLongClick = { _, status -> onTokenLongClick(accountId, status) }, ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index 80068f621c..328d3a5cbe 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -16,6 +16,7 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase +import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents import com.tangem.feature.wallet.presentation.account.AccountDependencies import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender @@ -25,12 +26,14 @@ import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetToken import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.TokenConverterParams import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.combine6 import com.tangem.utils.coroutines.saveIn import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.* import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import timber.log.Timber +import java.math.BigDecimal @Suppress("LongParameterList") internal abstract class BasicTokenListSubscriber : WalletSubscriber() { @@ -43,6 +46,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { protected abstract val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase protected abstract val accountDependencies: AccountDependencies protected abstract val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase + protected abstract val stakingApyFlowUseCase: StakingApyFlowUseCase private val sendAnalyticsJobHolder = JobHolder() private val onTokenListReceivedJobHolder = JobHolder() @@ -81,12 +85,14 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { }, flow2 = appCurrencyFlow(), flow3 = yieldSupplyApyFlow(), - transform = { maybeTokenList, appCurrency, yieldSupplyApyMap -> + flow4 = stakingApyFlow(), + transform = { maybeTokenList, appCurrency, yieldSupplyApyMap, stakingApyMap -> singleAccountTransform( maybeTokenList = maybeTokenList, appCurrency = appCurrency, portfolioId = PortfolioId(userWallet.walletId), yieldSupplyApyMap = yieldSupplyApyMap, + stakingApyMap = stakingApyMap, ) }, ) @@ -97,6 +103,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { appCurrency: AppCurrency, portfolioId: PortfolioId, yieldSupplyApyMap: Map, + stakingApyMap: Map, ) { val tokenList = maybeTokenList.getOrElse( ifLoading = { maybeContent -> @@ -124,6 +131,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { params = TokenConverterParams.Wallet(portfolioId, tokenList), appCurrency = appCurrency, yieldSupplyApyMap = yieldSupplyApyMap, + stakingApyMap = stakingApyMap, ) walletWithFundsChecker.check(tokenList) @@ -143,8 +151,8 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { } } - private fun createAccountListFlow(coroutineScope: CoroutineScope): Flow<*> = combine( - flow = accountListFlow(coroutineScope) + private fun createAccountListFlow(coroutineScope: CoroutineScope): Flow<*> = combine6( + flow1 = accountListFlow(coroutineScope) .onEach { accountStatusList -> coroutineScope.launch { sendTokenListAnalytics( @@ -165,7 +173,8 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet), flow4 = accountDependencies.isAccountsModeEnabledUseCase(), flow5 = yieldSupplyApyFlow(), - transform = { accountList, appCurrency, expandedAccounts, isAccountMode, yieldSupplyApyMap -> + flow6 = stakingApyFlow(), + transform = { accountList, appCurrency, expandedAccounts, isAccountMode, yieldSupplyApyMap, stakingApyMap -> val accountFlattenTokensList = accountList.flattenTokens() val accountFlattenCurrencies = accountFlattenTokensList .map { it.flattenCurrencies() } @@ -180,6 +189,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { appCurrency, PortfolioId(mainAccount.account.accountId), yieldSupplyApyMap, + stakingApyMap, ) when { @@ -198,7 +208,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { ) false -> { val convertParams = TokenConverterParams.Account(accountList, expandedAccounts) - updateContent(convertParams, appCurrency, yieldSupplyApyMap) + updateContent(convertParams, appCurrency, yieldSupplyApyMap, stakingApyMap) accountFlattenTokensList .map { tokenList -> coroutineScope.launch { walletWithFundsChecker.check(tokenList) } } .joinAll() @@ -232,6 +242,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { params: TokenConverterParams, appCurrency: AppCurrency, yieldSupplyApyMap: Map, + stakingApyMap: Map, ) { stateHolder.update( SetTokenListTransformer( @@ -240,6 +251,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { appCurrency = appCurrency, clickIntents = clickIntents, yieldSupplyApyMap = yieldSupplyApyMap, + stakingApyMap = stakingApyMap, ), ) } @@ -255,4 +267,7 @@ internal abstract class BasicTokenListSubscriber : WalletSubscriber() { private fun yieldSupplyApyFlow(): Flow> = yieldSupplyApyFlowUseCase() .distinctUntilChanged() + + private fun stakingApyFlow(): Flow> = stakingApyFlowUseCase() + .distinctUntilChanged() } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt index f56709b1d7..f64dafc766 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/MultiWalletTokenListSubscriber.kt @@ -10,6 +10,7 @@ import com.tangem.domain.models.TotalFiatBalance import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.tokens.ApplyTokenListSortingUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError @@ -36,6 +37,7 @@ internal class MultiWalletTokenListSubscriber( override val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, override val accountDependencies: AccountDependencies, override val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, + override val stakingApyFlowUseCase: StakingApyFlowUseCase, ) : BasicTokenListSubscriber() { override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt index 6866fdbf8f..15892dd13b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletWithTokenListSubscriber.kt @@ -7,6 +7,7 @@ import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.lce.LceFlow import com.tangem.domain.models.tokenlist.TokenList import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.staking.usecase.StakingApyFlowUseCase import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase @@ -31,6 +32,7 @@ internal class SingleWalletWithTokenListSubscriber( override val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase, override val accountDependencies: AccountDependencies, override val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase, + override val stakingApyFlowUseCase: StakingApyFlowUseCase, ) : BasicTokenListSubscriber() { override fun tokenListFlow(coroutineScope: CoroutineScope): LceFlow { From a90cb2dadb34c435d975f4813e5418f038b9fda5 Mon Sep 17 00:00:00 2001 From: Tangem Date: Mon, 20 Oct 2025 22:04:03 +0500 Subject: [PATCH 25/25] Updated on 2026-08-14 --- .../data/tokens/repository/DefaultCurrencyChecksRepository.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt index ff39057e0a..48ac28966a 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrencyChecksRepository.kt @@ -174,7 +174,8 @@ internal class DefaultCurrencyChecksRepository( cryptoCurrencyStatus: CryptoCurrencyStatus, ): BigDecimal? { val token = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return null - if (cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive == false) return null + val isActive = cryptoCurrencyStatus.value.yieldSupplyStatus?.isActive ?: false + if (!isActive) return null return runCatching { val walletManager = walletManagersFacade.getOrCreateWalletManager( userWalletId = userWalletId,