diff --git a/app/src/main/assets/tangem-app-config b/app/src/main/assets/tangem-app-config index 5fc86d29bc..78be297b0f 160000 --- a/app/src/main/assets/tangem-app-config +++ b/app/src/main/assets/tangem-app-config @@ -1 +1 @@ -Subproject commit 5fc86d29bc2c0dc7c057ab0242b2cfa3e9f48daf +Subproject commit 78be297b0f09bc2be1e9b0fbab9384f17bf94c37 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 dedbd42301..06cfd97a8a 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 @@ -51,6 +51,8 @@ class TokenItemStateConverter( }, private val onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)? = null, private val onYieldPromoCloseClick: (() -> Unit)? = null, + private val onYieldPromoShown: ((cryptoCurrency: CryptoCurrency) -> Unit)? = null, + private val onYieldPromoClicked: ((cryptoCurrency: CryptoCurrency) -> Unit)? = null, private val titleStateProvider: (CryptoCurrencyStatus) -> TokenItemState.TitleState = { currencyStatus -> createTitleState( currencyStatus = currencyStatus, @@ -75,6 +77,8 @@ class TokenItemStateConverter( yieldSupplyPromoBannerKey = yieldSupplyPromoBannerKey, onApyLabelClick = onApyLabelClick, onYieldPromoCloseClick = onYieldPromoCloseClick, + onYieldPromoShown = onYieldPromoShown, + onYieldPromoClicked = onYieldPromoClicked, ) }, private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null, @@ -393,12 +397,15 @@ class TokenItemStateConverter( } } + @Suppress("LongParameterList") private fun createPromoBannerState( status: CryptoCurrencyStatus, yieldModuleApyMap: Map, yieldSupplyPromoBannerKey: String?, onApyLabelClick: ((CryptoCurrencyStatus, ApySource, String) -> Unit)?, onYieldPromoCloseClick: (() -> Unit)?, + onYieldPromoShown: ((cryptoCurrency: CryptoCurrency) -> Unit)?, + onYieldPromoClicked: ((cryptoCurrency: CryptoCurrency) -> Unit)?, ): TokenItemState.PromoBannerState { val token = status.currency as? CryptoCurrency.Token ?: return TokenItemState.PromoBannerState.Empty if (status.value !is CryptoCurrencyStatus.Loaded) { @@ -418,11 +425,15 @@ class TokenItemStateConverter( wrappedList(yieldSupplyApy), ), onPromoBannerClick = { + onYieldPromoClicked?.invoke(status.currency) onApyLabelClick?.invoke(status, ApySource.YIELD_SUPPLY, yieldSupplyApy.toString()) }, onCloseClick = { onYieldPromoCloseClick?.invoke() }, + onPromoShown = { + onYieldPromoShown?.invoke(status.currency) + }, ) } diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt index b511dd504e..c05783e382 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/event/MainScreenAnalyticsEvent.kt @@ -134,6 +134,28 @@ sealed class MainScreenAnalyticsEvent( STATE to state, ), ) + + data class YieldPromo( + val token: String, + val blockchain: String, + ) : MainScreenAnalyticsEvent( + event = "Yield Promo", + params = mapOf( + TOKEN_PARAM to token, + BLOCKCHAIN to blockchain, + ), + ) + + data class YieldPromoClicked( + val token: String, + val blockchain: String, + ) : MainScreenAnalyticsEvent( + event = "Yield Promo Clicked", + params = mapOf( + TOKEN_PARAM to token, + BLOCKCHAIN to blockchain, + ), + ) // endregion companion object { diff --git a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt index 9e3d88dcb4..55d88cd09e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/utils/NetworkLogsSaveInterceptor.kt @@ -32,8 +32,16 @@ class NetworkLogsSaveInterceptor( @Throws(IOException::class) override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() + val host = request.url.host + val path = request.url.encodedPath + val isRestrictedUrl = restrictedForLogURLs.contains(host + path) + val isRestrictedHost = restrictedForLogHosts.any { host.contains(it) } - logRequestMessage(chain, request) + if (isRestrictedUrl || isRestrictedHost) { + logEmptyRequestMessage(chain, request) + } else { + logRequestMessage(chain, request) + } val startNs = System.nanoTime() val response: Response @@ -44,11 +52,6 @@ class NetworkLogsSaveInterceptor( throw e } - val host = request.url.host - val path = request.url.encodedPath - val isRestrictedUrl = restrictedForLogURLs.contains(host + path) - val isRestrictedHost = restrictedForLogHosts.any { host.contains(it) } - if (isRestrictedUrl || isRestrictedHost) { logResponseWithEmptyMessage(response, startNs) } else { @@ -58,6 +61,13 @@ class NetworkLogsSaveInterceptor( return response } + private fun logEmptyRequestMessage(chain: Interceptor.Chain, request: Request) { + val connection = chain.connection() + val connectionProtocol = if (connection != null) " ${connection.protocol()}" else "" + + saveLogMessage("--> ${request.method} ${request.url}$connectionProtocol\n") + } + private fun logRequestMessage(chain: Interceptor.Chain, request: Request) { val connection = chain.connection() val connectionProtocol = if (connection != null) " ${connection.protocol()}" else "" diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index dcdf6d4a87..81ee6497c5 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -4,7 +4,7 @@ Trotzdem überspringen Zugangscode nicht festgelegt Code ändern - Dein Zugangscode dient zum Entsperren Deiner Wallet und zum Schutz des Zugriffs auf Deine Vermögenswerte. + Dein Zugangscode entsperrt und schützt den Zugriff auf Deine Geldbörse. Trotzdem verwenden Dieser Zugangscode kann leicht erraten werden Zugangscode eingeben @@ -49,6 +49,7 @@ Neues Konto Konto hinzufügen Konto bearbeiten + Bitte versuche es später erneut. Sollte das Problem weiterhin bestehen, kontaktiere bitte unseren Support. Wir helfen Dir gerne bei der Lösung. %1$s in %2$s Hauptkonto Du hast das Limit von %1$s aktiven Konten bereits überschritten. Archiviere eines zur Wiederherstellung @@ -96,7 +97,7 @@ Tokens im %1$s -Netzwerk werden von dieser Karte oder Ring aufgrund einer Firmware-Einschränkung nicht unterstützt. Hast du Probleme beim Scannen deiner Karte oder Ring? Diese Karte oder Ring ist für die Zusammenarbeit mit Tangem nicht geeignet - Lege zunächst einen Zugangscode fest, um die Biometrie zu aktivieren. + Lege einen Zugangscode fest, um die Biometrie zu aktivieren. Verwende die %1$s um Deine Wallet zu entsperren und sensible Aktionen wie das Signieren von Transaktionen zu bestätigen. Bei Hardware-Wallets ist zum Signieren weiterhin eine Karte oder ein Ring erforderlich. Standardgebühr Aktiviere die Option Standardgebühr, um die Transaktionsgebühren automatisch festzulegen und die Gebührenseite beim Senden von Geldern zu überspringen. Du kannst bei Bedarf jederzeit zu dieser Seite zurückkehren. @@ -145,10 +146,10 @@ Guthaben sind ausgeblendet Laut den Blockchain-Entwicklern befinden sich der Kaspa-Token derzeit in der Betaphase. Bleibe dran für Updates! Beta-Phase - Die biometrischen Daten sind auf Deinem Gerät deaktiviert, sodass Du sie nicht zum Entsperren Deine Wallet verwenden kannst. Aktiviere die Biometrie in den Einstellungen Deines Geräts, um diese Methode wieder zu verwenden. + Die Biometrie ist auf Deinem Gerät deaktiviert, daher kannst Du sie nicht zum Entsperren Deiner Wallets verwenden. Aktiviere die Biometrie in den Geräteeinstellungen, um diese Methode wieder nutzen zu können. Biometrische Authentifizierung deaktiviert Bitte Karte oder Ring scannen - Du hast das Limit an biometrischen Entsperrversuchen erreicht. Bitte entsperren Deine Wallet durch Antippen Deines Geräts oder gib den Zugangscode ein. + Du hast das Limit an biometrischen Entsperrversuchen erreicht. Bitte entsperre Deine Wallet mit einer Karte/einem Ring oder gib Deinen Zugangscode ein. Biometrische Authentifizierung gesperrt Bitte versuche es in 30 Sekunden erneut oder scanne die Karte oder Ring Die biometrische Anmeldung ist vorübergehend gesperrt. Bitte versuche es in 30 Sekunden erneut oder entsperre Deine Wallet durch Antippen Deines Geräts oder mit einem Zugangscode. @@ -374,6 +375,7 @@ Allgemeine Geschäftsbedingungen Nutzungsbedingungen An + Zu %s Heute %d Token @@ -385,6 +387,7 @@ Überweisung Die Daten konnten nicht geladen werden… Ich verstehe + Ich verstehe, fahre bitte fort. Es ist ein Fehler aufgetreten. Bitte versuche es erneut. Nicht erreichbar Staking beenden @@ -398,6 +401,7 @@ Verfügbare Netzwerke Die Ableitung Deines Tokens entspricht der Ableitung von %1$s. Dein Token wird diesem Konto gutgeschrieben. Die Herleitung stammt aus einem anderen Bericht. + Token hinzugefügt %1$s Konto Vertragsadresse Vertragsadresse ist ungültig Bitte wähle das Netzwerk @@ -725,6 +729,7 @@ Top-Gewinner Top-Verlierer Beliebt + Ertragsmodus Staking ist der einfachste Weg, um Belohnungen für Deine Kryptowährung zu erhalten. %s Verdiene bis zu %s APY Token hinzugefügt @@ -800,8 +805,17 @@ Mobile Wallet erfordert %1$s oder später Alle Neuigkeiten Gefällt mir + + %dStunde her + %dStunden her + + + %dMinute her + %dMinuten her + Kurze Zusammenfassung Verwandte Nachrichten + Verwandte Token Quellen Auf dem Laufenden bleiben NFC ist auf deinem Gerät nicht verfügbar @@ -978,6 +992,7 @@ Andere Währungen Beliebte Fiats Suche nach Währung + Diese Transaktion wurde bereits verarbeitet. Es sind keine weiteren Maßnahmen erforderlich. Die besten Preise erzielen... Sofort Durch die Nutzung der Onramp-Funktionalität stimmst Du den %1$s und %2$s des Anbieters zu. @@ -1074,6 +1089,7 @@ Karte oder Ring zurücksetzen Mir ist bewusst, dass ich nach der Durchführung dieser Aktion keinen Zugriff mehr auf die aktuelle Wallet habe. Mir ist klar, dass ich diese Karte oder Ring nicht verwenden kann, um meinen Zugangscode auf den anderen Karten oder Ringe der aktuellen Wallet wiederherzustellen + Mir ist bewusst, dass ich den Zugang zu meiner Tangem Pay Karte und allen darauf befindlichen Geldern vollständig verliere, ohne die Möglichkeit der Wiederherstellung Durch das Zurücksetzen auf Werkseinstellungen wird die Wallet vollständig von der ausgewählten Karte oder Ring gelöscht. Du kannst die aktuelle Wallet nicht wiederherstellen oder die Karte oder Ring verwenden, um den Zugangscode wiederherzustellen. Beim Zurücksetzen auf die Werkseinstellungen wird die Wallet der ausgewählten Karte oder Ring vollständig gelöscht und aus der App entfernt. Es ist nicht möglich, die aktuelle Wallet wiederherzustellen. Alle Tangem-Geräte wurden zurückgesetzt. @@ -1227,6 +1243,8 @@ Eine Netzwerkgebühr ist eine kleine Zahlung, die erforderlich ist, um Deine Transaktion auf der Blockchain zu verarbeiten und zu bestätigen. Um mit dem Staking zu beginnen, muss Dein TON-Konto mit einer Transaktion von 1 TON aktiviert werden. Das Guthaben verbleibt auf Deinem Konto, dieser Schritt dient lediglichder aktivierung für das Staking. Kontoaktivierung + Die Netzwerkgebühr hat sich geändert. Bitte überprüfe den neuen Betrag, bevor Du fortfährst. + Netzwerkgebühr aktualisiert Die Anzahl der zu stakenden Krypros muss mindesten %s betragen Der Stakingbetrag wird aufgrund der Netzwerkregeln auf %1$s TRX aufgerundet. Der Betrag der unstaked wird, wird aufgrund der Netzwerkregeln auf %1$s TRX gerundet. @@ -1265,6 +1283,7 @@ Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Du die Verwendung Deines Tokens für das Staking genehmigst. Indem Du die Staking-Funktionalität nutzt, stimmst Du den %1$s und %2$s des Anbieters zu. Gesperrt + Höchstbetrag: %s Migrieren Natives Staking Derzeit sind keine aktiven Validatoren für das Staking verfügbar. Bitte versuchen Sie es später erneut. @@ -1417,6 +1436,7 @@ Ihre Karte ist eingefroren. Hilfe erhalten Andere + Nicht nutzbar auf gerooteten Geräten Abgeschlossen Abgelehnt Ausstehend @@ -1429,6 +1449,7 @@ Entsperren der Karte fehlgeschlagen. Versuchen Sie es später erneut. Ihre Karte ist entsperrt. Abhebung + Auf gerooteten Geräten nicht nutzbar. KYC abbrechen Guthaben hinzufügen Aufladeoptionen @@ -1466,6 +1487,7 @@ Tausche beliebige Vermögenswerte in Deinem Portfolio gegen eine Karte. Kartendetails Karte entsperren + Komm zurück zur App, falls du es vergisst. Dein PIN-Code Auszahlung Auszahlung derzeit nicht möglich @@ -1473,6 +1495,7 @@ Auszahlung läuft PIN-Code ändern Kehren Sie zur App zurück, falls Sie ihn vergessen. + Mir ist bewusst, dass ich den Zugriff auf meine Tangem Pay Card und alle darauf befindlichen Guthaben vollständig und ohne Möglichkeit der Wiederherstellung verliere. Kartenausstellung fehlgeschlagen Ein technischer Fehler ist aufgetreten, bitte versuchen Sie es erneut, indem Sie auf die Schaltfläche unten klicken Ein technischer Fehler ist aufgetreten, bitte kontaktieren Sie den Support @@ -1491,6 +1514,7 @@ KYC in Bearbeitung Status anzeigen KYC für Tangem Pay in Arbeit + Über die Schaltflächen unten kannst Du Deinen aktuellen KYC-Status einsehen oder ihn abbrechen. Holen Sie sich Ihre kostenlose virtuelle Tangem Visa-Karte Nutzen Sie USDC für alltägliche Zahlungen Karte erhalten @@ -1507,9 +1531,11 @@ Wir beheben ein technisches Problem. Bitte versuchen Sie es später erneut. Service vorübergehend nicht verfügbar Daten können derzeit nicht angezeigt werden, Kartenzahlungen funktionieren jedoch weiterhin. - Synchronisation erforderlich + Satz \nPIN-Code + Nicht synchronisiert + Zugang wiederherstellen Nutzen Sie USDC für alltägliche Zahlungen - Tangem Pay ist vorübergehend nicht verfügbar + Tangem Pay ist vorübergehend nicht erreichbar. Tangem Pay Klicken Sie auf die Schaltfläche unten, um den Zugriff wiederherzustellen Ihr PIN-Code @@ -1976,7 +2002,7 @@ Damit Sie beim nächsten Aufladen Ihrer Brieftasche keine erhöhte Provision zahlen, soll der Betrag um %s XTZ reduziert werden Wenn der Yield-Modus aktiviert ist, gehen alle zukünftigen Einzahlungen an diese Adresse an Aave. Du kannst über Dein Guthaben weiterhin frei verfügen. Deine %s wird an Aave übermittelt - Die Lieferung von %1$s %2$s an Aave steht noch aus. + Lieferung %1$s %2$s nach Aave Genehmigen Die Genehmigung Deines Tokens wurde widerrufen. Erteilen diese erneut, um die Servicefunktionalität fortzusetzen. Genehmigung erforderlich @@ -2057,6 +2083,7 @@ Ertragsmodus Bearbeitung Deiner Einzahlung Ertragsmodus + Yield-Mode-Vertragsbereitstellung Ertragsmodus aktivieren %1$s geliefert an Aave Ertragsmodus deaktiviert diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 9bc5664570..582d2ca04c 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -61,8 +61,8 @@ Are you sure you want to discard new account? Are you sure you want to discard edits? Unsaved Changes - Some custom tokens were moved from “%1$s” to “%2$s” as their derivation belongs to that account. - Some custom tokens were moved + Some custom tokens will be automatically moved from “%1$s” to “%2$s” as their derivation belongs to that account. + Some custom tokens will be automatically moved Can’t find your token? Go to the Market section on the main page and add it to your portfolio for purchase Can’t find your token? Go to the Market section on the main page and add it to your portfolio for selling. Sell diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/YieldSupplyPromoBanner.kt b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/YieldSupplyPromoBanner.kt index b949061451..71ccba070a 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/YieldSupplyPromoBanner.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/token/internal/YieldSupplyPromoBanner.kt @@ -18,6 +18,7 @@ import android.content.res.Configuration import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box import androidx.compose.material3.ripple +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.vectorResource @@ -39,6 +40,9 @@ internal fun YieldSupplyPromoBanner(state: PromoBannerState, modifier: Modifier @Composable internal fun YieldSupplyPromoBanner(state: PromoBannerState.Content, modifier: Modifier = Modifier) { + LaunchedEffect(state) { + state.onPromoShown() + } val bgColor = TangemTheme.colors.control.unchecked Column(modifier = modifier) { Row( 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 a2180bb614..7193eed339 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 @@ -269,6 +269,7 @@ sealed class TokenItemState { val title: TextReference, val onPromoBannerClick: () -> Unit, val onCloseClick: () -> Unit, + val onPromoShown: () -> Unit = {}, ) : PromoBannerState() data object Empty : PromoBannerState() 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 9edbf243da..1d69153c6e 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 @@ -57,6 +57,8 @@ sealed class CryptoCurrencyWarning { data object MigrationMaticToPol : CryptoCurrencyWarning() + data object MigrationClore : CryptoCurrencyWarning() + /** * Shows a warning about an available fee resource for a transaction in several blockchains (ex. Koinos) */ 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..6ca1aa64ce 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 @@ -41,16 +41,16 @@ class GetCurrencyWarningsUseCase( // don't add here notifications that require async requests return combine( - getCoinRelatedWarnings( + flow = getCoinRelatedWarnings( userWalletId = userWalletId, networkId = currency.network.id, currencyId = currency.id, derivationPath = derivationPath, isSingleWalletWithTokens = isSingleWalletWithTokens, ), - flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus)), - flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)), - flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)), + flow2 = flowOf(currencyChecksRepository.getRentInfoWarning(userWalletId, currencyStatus)), + flow3 = flowOf(currencyChecksRepository.getExistentialDeposit(userWalletId, currency.network)), + flow4 = flowOf(currencyChecksRepository.getFeeResourceAmount(userWalletId, currency.network)), ) { coinRelatedWarnings, maybeRentWarning, maybeEdWarning, maybeFeeResource -> setOfNotNull( maybeRentWarning, @@ -62,6 +62,7 @@ class GetCurrencyWarningsUseCase( getBeaconChainShutdownWarning(rawId = currency.network.id.rawId), getAssetRequirementsWarning(userWalletId = userWalletId, currency = currency), getMigrationFromMaticToPolWarning(currency), + getCloreMigrationWarning(currency), ) }.flowOn(dispatchers.io) } @@ -261,11 +262,20 @@ class GetCurrencyWarningsUseCase( } } + private fun getCloreMigrationWarning(currency: CryptoCurrency): CryptoCurrencyWarning? { + return if (currency.symbol == CLORE_SYMBOL && BlockchainUtils.isClore(currency.network.rawId)) { + CryptoCurrencyWarning.MigrationClore + } else { + null + } + } + private fun BigDecimal?.isZero(): Boolean { return this?.signum() == 0 } companion object { private const val MATIC_SYMBOL = "MATIC" + private const val CLORE_SYMBOL = "CLORE" } } \ No newline at end of file diff --git a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt index f0b5c39b43..9dcaed0bd4 100644 --- a/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt +++ b/domain/yield-supply/src/main/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCase.kt @@ -102,14 +102,16 @@ class YieldSupplyGetRewardsBalanceUseCase( }.flowOn(dispatcherProvider.default) private fun calculateMinVisibleDecimals(perTickDeltaAbs: BigDecimal, maxDecimals: Int): Int { - if (perTickDeltaAbs <= BigDecimal.ZERO) return MIN_DECIMALS + val effectiveMin = MIN_DECIMALS.coerceAtMost(maxDecimals) + + if (perTickDeltaAbs <= BigDecimal.ZERO) return effectiveMin val perTickAsDouble = perTickDeltaAbs.toDouble() - if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return MIN_DECIMALS + if (perTickAsDouble.isNaN() || perTickAsDouble.isInfinite()) return effectiveMin val safe = if (perTickAsDouble <= 0.0) EPSILON else perTickAsDouble val raw = ceil(-ln(safe) / LN_10) - return raw.toInt().coerceIn(MIN_DECIMALS, maxDecimals) + return raw.toInt().coerceIn(effectiveMin, maxDecimals) } private fun perTickDelta(amount: BigDecimal, apyFraction: BigDecimal): BigDecimal { diff --git a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt index 878b0ca49e..6d40c26c08 100644 --- a/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt +++ b/domain/yield-supply/src/test/java/com/tangem/domain/yield/supply/usecase/YieldSupplyGetRewardsBalanceUseCaseTest.kt @@ -459,4 +459,72 @@ class YieldSupplyGetRewardsBalanceUseCaseTest { YieldSupplyGetRewardsBalanceUseCase.FIAT_MAX_DECIMALS, ) } + + @Test + fun `GIVEN token with decimals less than MIN_DECIMALS WHEN invoke THEN does not crash`() = runTest { + val network = createNetwork() + val tokenId = CryptoCurrency.ID( + prefix = CryptoCurrency.ID.Prefix.TOKEN_PREFIX, + body = CryptoCurrency.ID.Body.NetworkId(network.rawId), + suffix = CryptoCurrency.ID.Suffix.RawID("low-decimals-token", "0xLowDecimals"), + ) + val tokenWithLowDecimals = CryptoCurrency.Token( + id = tokenId, + network = network, + name = "Low Decimals Token", + symbol = "LDT", + decimals = 0, + iconUrl = null, + isCustom = false, + contractAddress = "0xLowDecimals", + ) + + val amount = BigDecimal("100.00") + val apy = BigDecimal("10.0") + + val status = CryptoCurrencyStatus( + currency = tokenWithLowDecimals, + value = CryptoCurrencyStatus.Custom( + amount = amount, + fiatAmount = null, + fiatRate = BigDecimal.ONE, + priceChange = null, + stakingBalance = null, + yieldSupplyStatus = null, + hasCurrentNetworkTransactions = false, + pendingTransactions = emptySet(), + networkAddress = NetworkAddress.Single( + NetworkAddress.Address(value = "0xabc", type = NetworkAddress.Address.Type.Primary), + ), + sources = CryptoCurrencyStatus.Sources(), + ), + ) + + coEvery { repository.getCachedMarkets() } returns listOf( + YieldMarketToken( + tokenAddress = tokenWithLowDecimals.contractAddress, + chainId = 1, + apy = apy, + isActive = true, + maxFeeNative = BigDecimal.ZERO, + maxFeeUSD = BigDecimal.ZERO, + backendId = "id", + ), + ) + + val dispatcherProvider = testDispatcherProvider(this) + val useCase = YieldSupplyGetRewardsBalanceUseCase(repository, dispatcherProvider) + val appCurrency = AppCurrency.Default + + val deferred = async { useCase(status, appCurrency).take(2).toList() } + + testScheduler.advanceUntilIdle() + advanceTimeBy(TICK_MILLIS) + testScheduler.advanceUntilIdle() + + val emissions = deferred.await() + assertThat(emissions).hasSize(2) + assertThat(emissions[0].cryptoBalance).isNotNull() + assertThat(emissions[1].cryptoBalance).isNotNull() + } } \ 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 9121e9d618..067870cc0e 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 @@ -707,6 +707,11 @@ internal class SwapInteractorImpl @AssistedInject constructor( userWalletId: UserWalletId, ): Throwable? { val currency = fromToken.currency + val blockchain = currency.network.toBlockchain() + // Stellar validation removed because swap uses destination = "0" and throws an error + if (blockchain == Blockchain.Stellar) { + return null + } val fee = Fee.Common( amount = Amount( value = when (feeState) { @@ -714,7 +719,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( is TxFeeState.MultipleFeeState -> feeState.normalFee.feeValue is TxFeeState.SingleFeeState -> feeState.fee.feeValue }, - blockchain = currency.network.toBlockchain(), + blockchain = blockchain, ), ) 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 3bef7418c8..d65921fdb9 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 @@ -59,6 +59,7 @@ internal class TokenDetailsNotificationsAnalyticsSender( is TokenDetailsNotification.RequiredTrustlineWarning, is TokenDetailsNotification.KoinosMana, is TokenDetailsNotification.MigrationMaticToPol, + is TokenDetailsNotification.MigrationClore, is TokenDetailsNotification.UsedOutdatedData, -> null } 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 b1ae6761a1..5733db0e18 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 @@ -258,6 +258,11 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { subtitle = resourceReference(id = R.string.warning_matic_migration_message), ) + data object MigrationClore : Warning( + title = resourceReference(id = R.string.warning_clore_migration_title), + subtitle = resourceReference(id = R.string.warning_clore_migration_message), + ) + data object UsedOutdatedData : TokenDetailsNotification( config = NotificationConfig( subtitle = resourceReference(R.string.warning_some_token_balances_not_updated), 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 0b4d3a4e71..eb54aca878 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 @@ -155,6 +155,7 @@ internal class TokenDetailsNotificationConverter( }, ) is CryptoCurrencyWarning.MigrationMaticToPol -> MigrationMaticToPol + is CryptoCurrencyWarning.MigrationClore -> MigrationClore is CryptoCurrencyWarning.UsedOutdatedDataWarning -> UsedOutdatedData } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt index 1802e3e2a7..2fa16a3f5f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/child/wallet/model/intents/WalletContentClickIntents.kt @@ -8,6 +8,7 @@ import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.staking.StakingBalance import com.tangem.domain.models.wallet.UserWallet @@ -24,6 +25,7 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplyEnterStatusUseCase import com.tangem.domain.yield.supply.usecase.YieldSupplySetShouldShowMainPromoUseCase import com.tangem.feature.wallet.presentation.account.AccountDependencies +import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController @@ -61,6 +63,10 @@ internal interface WalletContentClickIntents { fun onYieldPromoCloseClick() + fun onYieldPromoShown(cryptoCurrency: CryptoCurrency) + + fun onYieldPromoClicked(cryptoCurrency: CryptoCurrency) + fun onAccountExpandClick(account: Account) fun onAccountCollapseClick(account: Account) @@ -80,7 +86,7 @@ internal interface WalletContentClickIntents { fun onNFTClick(userWallet: UserWallet) } -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") @ModelScoped internal class WalletContentClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, @@ -99,6 +105,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val accountDependencies: AccountDependencies, private val yieldSupplyEnterStatusUseCase: YieldSupplyEnterStatusUseCase, private val yieldSupplySetShouldShowMainPromoUseCase: YieldSupplySetShouldShowMainPromoUseCase, + private val tokenListAnalyticsSender: TokenListAnalyticsSender, ) : BaseWalletClickIntents(), WalletContentClickIntents { override fun onDetailsClick() { @@ -204,6 +211,27 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( } } + override fun onYieldPromoShown(cryptoCurrency: CryptoCurrency) { + modelScope.launch(dispatchers.io) { + tokenListAnalyticsSender.sendYieldPromoShown( + userWalletId = stateHolder.getSelectedWalletId(), + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + ) + } + } + + override fun onYieldPromoClicked(cryptoCurrency: CryptoCurrency) { + modelScope.launch(dispatchers.io) { + analyticsEventHandler.send( + MainScreenAnalyticsEvent.YieldPromoClicked( + token = cryptoCurrency.symbol, + blockchain = cryptoCurrency.network.name, + ), + ) + } + } + override fun onAccountExpandClick(account: Account) { val userWalletId = stateHolder.getSelectedWalletId() analyticsEventHandler.send(MainScreenAnalyticsEvent.AccountShowTokens()) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt index 215aa3ca8d..c3de527e77 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/TokenListAnalyticsSender.kt @@ -8,6 +8,7 @@ import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.common.extensions.isZero import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent import com.tangem.core.decompose.di.ModelScoped import com.tangem.domain.analytics.CheckIsWalletToppedUpUseCase import com.tangem.domain.analytics.model.WalletBalanceState @@ -34,6 +35,7 @@ internal class TokenListAnalyticsSender @Inject constructor( ) { private val balanceWasSentMap = mutableMapOf() + private val yieldPromoShownMap = mutableMapOf() private val mutex = Mutex() private val loadingTraces = mutableMapOf() @@ -234,6 +236,19 @@ internal class TokenListAnalyticsSender @Inject constructor( } } + fun sendYieldPromoShown(userWalletId: UserWalletId, token: String, blockchain: String) { + val key = "${userWalletId.stringValue}_${blockchain}_$token" + if (yieldPromoShownMap[key] == true) return + + analyticsEventHandler.send( + MainScreenAnalyticsEvent.YieldPromo( + token = token, + blockchain = blockchain, + ), + ) + yieldPromoShownMap[key] = true + } + companion object { const val BALANCE_LOADED_TRACE_NAME = "Total_balance_loaded" const val HAS_ERROR = "has_error" 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 2520491085..2d42286d42 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 @@ -78,6 +78,8 @@ internal class TokenListStateConverter( onItemLongClick = { _, status -> onTokenLongClick(accountId, status) }, onApyLabelClick = { status, apySource, apy -> onApyLabelClick(status, apySource, apy) }, onYieldPromoCloseClick = clickIntents::onYieldPromoCloseClick, + onYieldPromoShown = clickIntents::onYieldPromoShown, + onYieldPromoClicked = clickIntents::onYieldPromoClicked, ) override fun convert(value: WalletTokensListState): WalletTokensListState { diff --git a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt index b51011e498..91fb30dd4f 100644 --- a/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt +++ b/libs/crypto/src/main/java/com/tangem/lib/crypto/BlockchainUtils.kt @@ -115,6 +115,10 @@ object BlockchainUtils { return blockchain == Blockchain.BSC || blockchain == Blockchain.BSCTestnet } + fun isClore(blockchainId: String): Boolean { + return Blockchain.fromId(blockchainId) == Blockchain.Clore + } + data class BlockchainInfo( val blockchainId: String, val name: String,